refactor: use raw html form elements (#123)

* refactor: use raw html for form elements

* test: update snapshot to reflect minor changes in html output

* refactor: replace all reactstrap with paragon or html

* test: update snapshot

* fix: add a noop to the button in async button if needed

This is a flaw in the Button component in Paragon. It calls props.onClick even if it doesn't exist.

* refactor: use classnames for toggling class names

* fix: remove unneeded ids. fix some form markup

* fix: update snapshot to reflect removed labels
This commit is contained in:
Adam Butterworth
2019-04-05 13:07:44 -04:00
committed by GitHub
parent 6ab28a8306
commit 599cbfc649
21 changed files with 372 additions and 362 deletions

View File

@@ -16,6 +16,13 @@
"jsx-a11y/anchor-is-valid": [ "error", {
"components": [ "Link" ],
"specialLink": [ "to" ]
}],
"jsx-a11y/label-has-for": [ 2, {
"components": [ "label" ],
"required": {
"some": [ "nesting", "id" ]
},
"allowChildren": false
}]
},
"env": {

40
package-lock.json generated
View File

@@ -13933,16 +13933,6 @@
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
"dev": true
},
"lodash.isfunction": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz",
"integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw=="
},
"lodash.isobject": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz",
"integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
@@ -14009,11 +13999,6 @@
"integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=",
"dev": true
},
"lodash.tonumber": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/lodash.tonumber/-/lodash.tonumber-4.0.3.tgz",
"integrity": "sha1-C5azGzVnJ5Prf1pj7nkfG56QJdk="
},
"lodash.uniq": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
@@ -18792,15 +18777,6 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-popper": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-0.10.4.tgz",
"integrity": "sha1-rypBXqIike3VBGeNev2opu4ylao=",
"requires": {
"popper.js": "^1.14.1",
"prop-types": "^15.6.1"
}
},
"react-proptype-conditional-require": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/react-proptype-conditional-require/-/react-proptype-conditional-require-1.0.4.tgz",
@@ -18912,22 +18888,6 @@
"integrity": "sha512-HH2N/b5tRxh7ypIgCRsiBl/CTxRkTEPf9DhIstaM6hne4WiwM5/bBbWuvVlRZc/i3FdqZED3pZ//6n4mtxma4w==",
"dev": true
},
"reactstrap": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-7.1.0.tgz",
"integrity": "sha512-wtc4RkgnGn1TsZ0AxOZ2OqT+b8YmCWZj/tErPujWLepxzlEEhveZGC+uDerdaHVSAzJUP2DTk605iper7hutQQ==",
"requires": {
"@babel/runtime": "^7.2.0",
"classnames": "^2.2.3",
"lodash.isfunction": "^3.0.9",
"lodash.isobject": "^3.0.2",
"lodash.tonumber": "^4.0.3",
"prop-types": "^15.5.8",
"react-lifecycles-compat": "^3.0.4",
"react-popper": "^0.10.4",
"react-transition-group": "^2.3.1"
}
},
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",

View File

@@ -37,7 +37,7 @@
"@fortawesome/react-fontawesome": "^0.1.4",
"babel-polyfill": "^6.26.0",
"bootstrap": "^4.2.1",
"classnames": "^2.2.5",
"classnames": "^2.2.6",
"connected-react-router": "^5.0.1",
"email-prop-type": "^1.1.5",
"font-awesome": "^4.7.0",
@@ -59,7 +59,6 @@
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"react-transition-group": "^2.5.3",
"reactstrap": "^7.1.0",
"redux": "^4.0.1",
"redux-devtools-extension": "^2.13.2",
"redux-logger": "^3.0.6",

View File

@@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { Button, Col, Container, Row } from 'reactstrap';
import { Button } from '@edx/paragon';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
@@ -12,12 +12,9 @@ export default class ErrorPage extends Component {
const { username } = apiClient.getAuthenticationState().authentication;
return (
<Container
fluid
className="py-5 justify-content-center align-items-start text-center"
>
<Row>
<Col>
<div className="container-fluid py-5 justify-content-center align-items-start text-center">
<div className="row">
<div className="col">
<p className="my-0 py-5 text-muted">
<FormattedMessage
id="profile.error.message.text"
@@ -25,22 +22,25 @@ export default class ErrorPage extends Component {
description="error message when an unexpected error occurs"
/>
</p>
</Col>
</Row>
<Row>
<Col>
</div>
</div>
<div className="row">
<div className="col">
<Link to={`/u/${username}`}>
<Button color="primary">
<FormattedMessage
id="profile.error.button.text"
defaultMessage="Return to Your Profile"
description="text for button that navigates back to your profile page after an error has occured"
/>
</Button>
<Button
buttonType="primary"
label={
<FormattedMessage
id="profile.error.button.text"
defaultMessage="Return to Your Profile"
description="text for button that navigates back to your profile page after an error has occured"
/>
}
/>
</Link>
</Col>
</Row>
</Container>
</div>
</div>
</div>
);
}
}

View File

@@ -1,5 +1,4 @@
import React, { Component } from 'react';
import { Container } from 'reactstrap';
import { FormattedMessage } from 'react-intl';
export default class NotFoundPage extends Component {
@@ -7,10 +6,7 @@ export default class NotFoundPage extends Component {
render() {
return (
<Container
fluid
className="d-flex py-5 justify-content-center align-items-start text-center"
>
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
<p
className="my-0 py-5 text-muted"
style={{ maxWidth: '32em' }}
@@ -21,7 +17,7 @@ export default class NotFoundPage extends Component {
description="error message when a page does not exist"
/>
</p>
</Container>
</div>
);
}
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Container, Row, Col, Alert, Button } from 'reactstrap';
import { StatusAlert, Hyperlink } from '@edx/paragon';
import { connect } from 'react-redux';
import { injectIntl, intlShape } from 'react-intl';
@@ -89,9 +89,12 @@ export class ProfilePage extends React.Component {
}
return (
<Button color="primary" href={configuration.VIEW_MY_RECORDS_URL} target="blank">
{this.props.intl.formatMessage(messages['profile.viewMyRecords'])}
</Button>
<Hyperlink
className="btn btn-primary"
destination={configuration.VIEW_MY_RECORDS_URL}
target="_blank"
content={this.props.intl.formatMessage(messages['profile.viewMyRecords'])}
/>
);
}
@@ -116,11 +119,11 @@ export class ProfilePage extends React.Component {
}
return (
<Row>
<Col md={4} lg={3}>
<Alert color="danger">{photoUploadError.userMessage}</Alert>
</Col>
</Row>
<div className="row">
<div className="col-md-4 col-lg-3">
<StatusAlert alertType="danger" dialog={photoUploadError.userMessage} dismissible={false} open />
</div>
</div>
);
}
@@ -160,9 +163,9 @@ export class ProfilePage extends React.Component {
return (
<div className="profile-page">
<Banner />
<Container fluid>
<Row className="align-items-center pt-4 mb-4 pt-md-0 mb-md-0">
<Col xs="auto" md={4} lg={3}>
<div className="container-fluid">
<div className="row align-items-center pt-4 mb-4 pt-md-0 mb-md-0">
<div className="col-auto col-md-4 col-lg-3">
<div className="d-flex align-items-center d-md-block">
<ProfileAvatar
className="mb-md-3"
@@ -174,19 +177,19 @@ export class ProfilePage extends React.Component {
isEditable={this.props.isCurrentUserProfile && !requiresParentalConsent}
/>
</div>
</Col>
<Col className="pl-0">
</div>
<div className="col pl-0">
<div className="d-md-none">
{this.renderHeadingLockup()}
</div>
<div className="d-none d-md-block float-right">
{this.renderViewMyRecordsButton()}
</div>
</Col>
</Row>
</div>
</div>
{this.renderPhotoUploadErrorMessage()}
<Row>
<Col md={4} lg={4}>
<div className="row">
<div className="col-md-4 col-lg-4">
<div className="d-none d-md-block mb-4">
{this.renderHeadingLockup()}
</div>
@@ -224,8 +227,8 @@ export class ProfilePage extends React.Component {
formId="socialLinks"
{...commonFormProps}
/>
</Col>
<Col md={8} lg={{ size: 7, offset: 1 }} className="pt-md-3">
</div>
<div className="pt-md-3 col-md-8 col-lg-7 offset-lg-1">
{shouldShowAgeMessage ? <AgeMessage accountURL="#account" /> : null}
<Bio
bio={bio}
@@ -238,9 +241,9 @@ export class ProfilePage extends React.Component {
formId="certificates"
{...commonFormProps}
/>
</Col>
</Row>
</Container>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Alert } from 'reactstrap';
import { StatusAlert } from '@edx/paragon';
import { FormattedMessage } from 'react-intl';
import { configuration } from '../../config/environment';
@@ -8,27 +8,34 @@ const { ACCOUNT_SETTINGS_URL } = configuration;
function AgeMessage() {
return (
<Alert color="info">
<FormattedMessage
id="profile.age.headline"
defaultMessage="Your profile cannot be shared."
description="error message"
tagName="h6"
/>
<FormattedMessage
id="profile.age.details"
defaultMessage="To share your profile with other edX learners, you must confirm that you are over the age of 13."
description="error message"
tagName="p"
/>
<a href={ACCOUNT_SETTINGS_URL}>
<FormattedMessage
id="profile.age.set.date"
defaultMessage="Set your date of birth"
description="label on a link to set birthday"
/>
</a>
</Alert>
<StatusAlert
alertType="info"
dialog={
<React.Fragment>
<FormattedMessage
id="profile.age.headline"
defaultMessage="Your profile cannot be shared."
description="error message"
tagName="h6"
/>
<FormattedMessage
id="profile.age.details"
defaultMessage="To share your profile with other edX learners, you must confirm that you are over the age of 13."
description="error message"
tagName="p"
/>
<a href={ACCOUNT_SETTINGS_URL}>
<FormattedMessage
id="profile.age.set.date"
defaultMessage="Set your date of birth"
description="label on a link to set birthday"
/>
</a>
</React.Fragment>
}
dismissible={false}
open
/>
);
}

View File

@@ -1,8 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Form, FormFeedback, FormGroup, Input, Label } from 'reactstrap';
import { connect } from 'react-redux';
import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import messages from './Bio.messages';
@@ -55,22 +55,21 @@ class Bio extends React.Component {
cases={{
editing: (
<div role="dialog" aria-labelledby={`${formId}-label`}>
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<Label for={formId} id={`${formId}-label`}>
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor={formId}>
{intl.formatMessage(messages['profile.bio.about.me'])}
</Label>
<Input
type="textarea"
</label>
<textarea
className={classNames('form-control', { 'is-invalid': Boolean(error) })}
id={formId}
name={formId}
value={bio || ''}
invalid={error != null}
value={bio}
onChange={this.handleChange}
aria-describedby={`${formId}-error-feedback`}
/>
<FormFeedback id={`${formId}-error-feedback`}>{error}</FormFeedback>
</FormGroup>
<p className="invalid-feedback" id={`${formId}-error-feedback`}>{error}</p>
</div>
<FormControls
visibilityId="visibilityBio"
saveState={saveState}
@@ -78,7 +77,7 @@ class Bio extends React.Component {
cancelHandler={this.handleClose}
changeHandler={this.handleChange}
/>
</Form>
</form>
</div>
),
editable: (

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape, FormattedDate, FormattedMessage } from 'react-intl';
import { Row, Col, Card, CardBody, CardTitle, Button, Form } from 'reactstrap';
import { Hyperlink } from '@edx/paragon';
import { connect } from 'react-redux';
import get from 'lodash.get';
@@ -66,14 +66,14 @@ class Certificates extends React.Component {
})(certificateType);
return (
<Col key={downloadUrl} sm={6} className="d-flex align-items-stretch">
<Card className="mb-4 certificate flex-grow-1">
<div key={downloadUrl} className="col col-sm-6 d-flex align-items-stretch">
<div className="card mb-4 certificate flex-grow-1">
<div
className="certificate-type-illustration"
style={{ backgroundImage: `url(${certificateIllustration})` }}
/>
<CardBody className="d-flex flex-column">
<CardTitle>
<div className="card-body d-flex flex-column">
<div className="card-title">
<p className="small mb-0">
{intl.formatMessage(get(
messages,
@@ -82,7 +82,7 @@ class Certificates extends React.Component {
))}
</p>
<h4 className="certificate-title">{courseDisplayName}</h4>
</CardTitle>
</div>
<p className="small mb-0">
<FormattedMessage
id="profile.certificate.organization.label"
@@ -101,13 +101,16 @@ class Certificates extends React.Component {
/>
</p>
<div>
<Button outline color="primary" href={downloadUrl} target="blank">
{intl.formatMessage(messages['profile.certificates.view.certificate'])}
</Button>
<Hyperlink
className="btn btn-outline-primary"
destination={downloadUrl}
target="_blank"
content={intl.formatMessage(messages['profile.certificates.view.certificate'])}
/>
</div>
</CardBody>
</Card>
</Col>
</div>
</div>
</div>
);
}
@@ -121,7 +124,7 @@ class Certificates extends React.Component {
}
return (
<Row className="align-items-stretch">{this.props.certificates.map(certificate => this.renderCertificate(certificate))}</Row>
<div className="row align-items-stretch">{this.props.certificates.map(certificate => this.renderCertificate(certificate))}</div>
);
}
@@ -137,7 +140,7 @@ class Certificates extends React.Component {
cases={{
editing: (
<div role="dialog" aria-labelledby="course-certificates-label">
<Form onSubmit={this.handleSubmit}>
<form onSubmit={this.handleSubmit}>
<EditableItemHeader
headingId="course-certificates-label"
content={intl.formatMessage(messages['profile.certificates.my.certificates'])}
@@ -150,7 +153,7 @@ class Certificates extends React.Component {
cancelHandler={this.handleClose}
changeHandler={this.handleChange}
/>
</Form>
</form>
</div>
),
editable: (

View File

@@ -1,8 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Form, FormFeedback, FormGroup, Input, Label } from 'reactstrap';
import { connect } from 'react-redux';
import { injectIntl, intlShape } from 'react-intl';
import classNames from 'classnames';
import messages from './Country.messages';
@@ -66,27 +66,26 @@ class Country extends React.Component {
cases={{
editing: (
<div role="dialog" aria-labelledby={`${formId}-label`}>
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<Label for="country" id={`${formId}-label`}>
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="country">
{intl.formatMessage(messages['profile.country.label'])}
</Label>
<Input
</label>
<select
className={classNames('form-control', 'w-100', { 'is-invalid': Boolean(error) })}
type="select"
id={formId}
name={formId}
className="w-100"
value={country}
invalid={error != null}
onChange={this.handleChange}
aria-describedby={`${formId}-error-feedback`}
>
{sortedCountries.map(({ code, name }) => (
<option key={code} value={code}>{name}</option>
))}
</Input>
<FormFeedback id={`${formId}-error-feedback`}>{error}</FormFeedback>
</FormGroup>
</select>
<p className="invalid-feedback" id={`${formId}-error-feedback`}>{error}</p>
</div>
<FormControls
visibilityId="visibilityCountry"
saveState={saveState}
@@ -94,7 +93,7 @@ class Country extends React.Component {
cancelHandler={this.handleClose}
changeHandler={this.handleChange}
/>
</Form>
</form>
</div>
),
editable: (

View File

@@ -1,9 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Form, FormFeedback, FormGroup, Input, Label } from 'reactstrap';
import { connect } from 'react-redux';
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import get from 'lodash.get';
import classNames from 'classnames';
import messages from './Education.messages';
@@ -62,18 +62,16 @@ class Education extends React.Component {
cases={{
editing: (
<div role="dialog" aria-labelledby={`${formId}-label`}>
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<Label for="education" id={`${formId}-label`}>
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="education">
{intl.formatMessage(messages['profile.education.education'])}
</Label>
<Input
type="select"
</label>
<select
className={classNames('form-control', 'w-100', { 'is-invalid': Boolean(error) })}
id={formId}
name={formId}
className="w-100"
value={education}
invalid={error != null}
onChange={this.handleChange}
aria-describedby={`${formId}-error-feedback`}
>
@@ -86,9 +84,9 @@ class Education extends React.Component {
))}
</option>
))}
</Input>
<FormFeedback id={`${formId}-error-feedback`}>{error}</FormFeedback>
</FormGroup>
</select>
<p className="invalid-feedback" id={`${formId}-error-feedback`}>{error}</p>
</div>
<FormControls
visibilityId="visibilityEducation"
saveState={saveState}
@@ -96,7 +94,7 @@ class Education extends React.Component {
cancelHandler={this.handleClose}
changeHandler={this.handleChange}
/>
</Form>
</form>
</div>
),
editable: (

View File

@@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Form, FormFeedback, FormGroup, FormText, Label } from 'reactstrap';
import { connect } from 'react-redux';
import { injectIntl, intlShape } from 'react-intl';
@@ -48,7 +47,7 @@ class Name extends React.Component {
render() {
const {
formId, name, visibilityName, editMode, saveState, error, intl,
formId, name, visibilityName, editMode, saveState, intl,
} = this.props;
return (
@@ -58,11 +57,9 @@ class Name extends React.Component {
cases={{
editing: (
<div role="dialog" aria-labelledby={`${formId}-label`}>
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<Label for="name" id={`${formId}-label`}>
{intl.formatMessage(messages['profile.name.full.name'])}
</Label>
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<EditableItemHeader content={intl.formatMessage(messages['profile.name.full.name'])} />
{/*
This isn't a mistake - the name field should not be editable. But if it were,
you'd find the original code got deleted in the commit which added this comment.
@@ -72,11 +69,10 @@ class Name extends React.Component {
such to fully get rid of it.
*/}
<p className="h5">{name}</p>
<FormText id={`${formId}-help-text`}>
<small className="form-text text-muted" id={`${formId}-help-text`}>
{intl.formatMessage(messages['profile.name.details'])}
</FormText>
<FormFeedback id={`${formId}-error-feedback`}>{error}</FormFeedback>
</FormGroup>
</small>
</div>
<FormControls
visibilityId="visibilityName"
saveState={saveState}
@@ -84,7 +80,7 @@ class Name extends React.Component {
cancelHandler={this.handleClose}
changeHandler={this.handleChange}
/>
</Form>
</form>
</div>
),
editable: (
@@ -97,9 +93,9 @@ class Name extends React.Component {
visibility={visibilityName}
/>
<p className="h5">{name}</p>
<FormText>
<small className="form-text text-muted">
{intl.formatMessage(messages['profile.name.details'])}
</FormText>
</small>
</React.Fragment>
),
empty: (
@@ -108,9 +104,9 @@ class Name extends React.Component {
<EmptyContent onClick={this.handleOpen}>
{intl.formatMessage(messages['profile.name.empty'])}
</EmptyContent>
<FormText>
<small className="form-text text-muted">
{intl.formatMessage(messages['profile.name.details'])}
</FormText>
</small>
</React.Fragment>
),
static: (
@@ -137,7 +133,6 @@ Name.propTypes = {
visibilityName: PropTypes.oneOf(['private', 'all_users']),
editMode: PropTypes.oneOf(['editing', 'editable', 'empty', 'static']),
saveState: PropTypes.string,
error: PropTypes.string,
// Actions
changeHandler: PropTypes.func.isRequired,
@@ -154,7 +149,6 @@ Name.defaultProps = {
saveState: null,
name: null,
visibilityName: 'private',
error: null,
};
export default connect(

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { Spinner } from 'reactstrap';
import Banner from './elements/Banner';
@@ -13,7 +12,7 @@ function PageLoading() {
height: '50vh',
}}
>
<Spinner color="primary" />
<div className="spinner-border text-primary" role="status" />
</div>
</div>
);

View File

@@ -1,8 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Form, FormFeedback, FormGroup, Input, Label } from 'reactstrap';
import { connect } from 'react-redux';
import { injectIntl, intlShape } from 'react-intl';
import classNames from 'classnames';
import messages from './PreferredLanguage.messages';
@@ -76,27 +76,25 @@ class PreferredLanguage extends React.Component {
cases={{
editing: (
<div role="dialog" aria-labelledby={`${formId}-label`}>
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<Label for={formId} id={`${formId}-label`}>
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor={formId}>
{intl.formatMessage(messages['profile.preferredlanguage.label'])}
</Label>
<Input
type="select"
</label>
<select
id={formId}
name={formId}
className="w-100"
className={classNames('form-control', 'w-100', { 'is-invalid': Boolean(error) })}
value={value}
invalid={error != null}
onChange={this.handleChange}
aria-describedby={`${formId}-error-feedback`}
>
{sortedLanguages.map(({ code, name }) => (
<option key={code} value={code}>{name}</option>
))}
</Input>
<FormFeedback id={`${formId}-error-feedback`}>{error}</FormFeedback>
</FormGroup>
</select>
<p className="invalid-feedback" id={`${formId}-error-feedback`}>{error}</p>
</div>
<FormControls
visibilityId="visibilityLanguageProficiencies"
saveState={saveState}
@@ -104,7 +102,7 @@ class PreferredLanguage extends React.Component {
cancelHandler={this.handleClose}
changeHandler={this.handleChange}
/>
</Form>
</form>
</div>
),
editable: (

View File

@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Input, Spinner, Dropdown, DropdownToggle, DropdownMenu, DropdownItem, Button } from 'reactstrap';
import { Button, Dropdown } from '@edx/paragon';
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import { ReactComponent as DefaultAvatar } from '../../assets/avatar.svg';
@@ -11,10 +11,6 @@ class ProfileAvatar extends React.Component {
constructor(props) {
super(props);
this.state = {
dropdownOpen: false,
};
this.fileInput = React.createRef();
this.form = React.createRef();
@@ -22,7 +18,6 @@ class ProfileAvatar extends React.Component {
this.onClickDelete = this.onClickDelete.bind(this);
this.onChangeInput = this.onChangeInput.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.toggleDropdown = this.toggleDropdown.bind(this);
}
onClickUpload() {
@@ -43,19 +38,13 @@ class ProfileAvatar extends React.Component {
this.form.current.reset();
}
toggleDropdown() {
this.setState({
dropdownOpen: !this.state.dropdownOpen,
});
}
renderPending() {
return (
<div
className="position-absolute w-100 h-100 d-flex justify-content-center align-items-center rounded-circle"
style={{ backgroundColor: 'rgba(0,0,0,.65)' }}
>
<Spinner color="primary" />
<div className="spinner-border text-primary" role="status" />
</div>
);
}
@@ -64,49 +53,51 @@ class ProfileAvatar extends React.Component {
if (this.props.isDefault) {
return (
<Button
className="text-white btn-block"
color="link"
size="sm"
className={'text-white btn-block btn-sm'.split(' ')}
buttonType="link"
onClick={this.onClickUpload}
>
<FormattedMessage
id="profile.profileavatar.upload-button"
defaultMessage="Upload Photo"
description="Upload photo button"
/>
</Button>
);
}
return (
<Dropdown
isOpen={this.state.dropdownOpen}
toggle={this.toggleDropdown}
>
<DropdownToggle className="text-white btn-block" color="link" size="sm">
<FormattedMessage
id="profile.profileavatar.change-button"
defaultMessage="Change"
description="Change photo button"
/>
</DropdownToggle>
<DropdownMenu>
<DropdownItem onClick={this.onClickUpload}>
label={
<FormattedMessage
id="profile.profileavatar.upload-button"
defaultMessage="Upload Photo"
description="Upload photo button"
/>
</DropdownItem>
<DropdownItem onClick={this.onClickDelete}>
<FormattedMessage
id="profile.profileavatar.remove.button"
defaultMessage="Remove"
description="Remove photo button"
/>
</DropdownItem>
</DropdownMenu>
</Dropdown>
}
/>
);
}
return (
<Dropdown
buttonType="primary"
title={(
<FormattedMessage
id="profile.profileavatar.change-button"
defaultMessage="Change"
description="Change photo button"
/>
)}
menuItems={[
(
<button className="dropdown-item" onClick={this.onClickUpload}>
<FormattedMessage
id="profile.profileavatar.upload-button"
defaultMessage="Upload Photo"
description="Upload photo button"
/>
</button>
),
(
<button className="dropdown-item" onClick={this.onClickDelete}>
<FormattedMessage
id="profile.profileavatar.remove.button"
defaultMessage="Remove"
description="Remove photo button"
/>
</button>
),
]}
/>
);
}
@@ -148,9 +139,9 @@ class ProfileAvatar extends React.Component {
encType="multipart/form-data"
>
{/* The name of this input must be 'file' */}
<Input
className="d-none"
innerRef={this.fileInput}
<input
className="d-none form-control-file"
ref={this.fileInput}
type="file"
name="file"
id="photo-file"

View File

@@ -1,10 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Form, Input, Label, Alert } from 'reactstrap';
import { StatusAlert } from '@edx/paragon';
import { connect } from 'react-redux';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTwitter, faFacebook, faLinkedin } from '@fortawesome/free-brands-svg-icons';
import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import messages from './SocialLinks.messages';
@@ -158,14 +159,14 @@ class SocialLinks extends React.Component {
),
editing: (
<div role="dialog" aria-labelledby="social-links-label">
<Form onSubmit={this.handleSubmit}>
<form onSubmit={this.handleSubmit}>
<EditableItemHeader
headingId="social-links-label"
content={intl.formatMessage(messages['profile.sociallinks.social.links'])}
/>
{/* TODO: Replace this alert with per-field errors. Needs API update. */}
<div id="social-error-feedback">
{error !== null ? <Alert color="danger">{error}</Alert> : null}
{error !== null ? <StatusAlert alertType="danger" dialog={error} dismissible={false} open /> : null}
</div>
<ul className="list-unstyled">
{socialLinks.map(({ platform, socialLink }) => (
@@ -186,7 +187,7 @@ class SocialLinks extends React.Component {
cancelHandler={this.handleClose}
changeHandler={this.handleChange}
/>
</Form>
</form>
</div>
),
}}
@@ -282,14 +283,14 @@ function EditingListItem({
}) {
return (
<li className="form-group">
<Label for={`social-${platform}`}>{name}</Label>
<Input
<label htmlFor={`social-${platform}`}>{name}</label>
<input
className={classNames('form-control', { 'is-invalid': Boolean(error) })}
type="text"
id={`social-${platform}`}
name={platform}
value={value || ''}
onChange={onChange}
invalid={error != null}
aria-describedby="social-error-feedback"
/>
</li>

View File

@@ -1,8 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Icon } from '@edx/paragon';
import { Button, Spinner } from 'reactstrap';
import { Icon, Button } from '@edx/paragon';
function AsyncActionButton({
onClick,
@@ -16,7 +15,11 @@ function AsyncActionButton({
const renderIcon = () => {
if (variant === 'error') return <Icon className="icon fa fa-times-circle" />;
if (variant === 'complete') return <Icon className="icon fa fa-check-circle" />;
if (variant === 'pending') return <Spinner size="sm" color="white" />;
if (variant === 'pending') {
return (
<div className="spinner-border-sm spinner-border text-white" role="status" />
);
}
return null;
};
@@ -29,12 +32,14 @@ function AsyncActionButton({
return labels.default;
};
const isDisabled = variant === 'pending' || variant === 'complete' || variant === 'error';
return (
<Button
type={type}
aria-live="assertive"
onClick={onClick}
disabled={variant === 'pending' || variant === 'complete' || variant === 'error'}
onClick={onClick || (() => {})}
disabled={isDisabled}
className={classNames(
'btn-async-action',
'd-inline-flex align-items-center justify-content-center',
@@ -43,16 +48,20 @@ function AsyncActionButton({
'btn-state-pending': variant === 'pending',
'btn-state-complete': variant === 'complete',
'btn-state-error': variant === 'error',
[`btn-${color}`]: color != null,
disabled: isDisabled,
},
)}
color={color}
).split(' ')}
style={style}
>
<span aria-hidden className="icon-state d-inline-flex justify-content-start">
{renderIcon()}
</span>
{renderLabel()}
</Button>
label={(
<React.Fragment>
<span aria-hidden className="icon-state d-inline-flex justify-content-start">
{renderIcon()}
</span>
{renderLabel()}
</React.Fragment>
)}
/>
);
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Label, FormGroup } from 'reactstrap';
import { Button } from '@edx/paragon';
import { injectIntl, intlShape } from 'react-intl';
import messages from './FormControls.messages';
@@ -16,10 +16,10 @@ function FormControls({
return (
<React.Fragment>
<FormGroup className="mb-4">
<Label className="mb-1" size="sm" for={visibilityId}>
<div className="mb-4 form-group">
<label className="mb-1 col-form-label-sm" htmlFor={visibilityId}>
{intl.formatMessage(messages['profile.formcontrols.who.can.see'])}
</Label>
</label>
<VisibilitySelect
id={visibilityId}
className="w-auto"
@@ -28,8 +28,8 @@ function FormControls({
value={visibility}
onChange={changeHandler}
/>
</FormGroup>
<FormGroup>
</div>
<div className="form-group">
<AsyncActionButton
type="submit"
variant={buttonState}
@@ -39,10 +39,12 @@ function FormControls({
complete: intl.formatMessage(messages['profile.formcontrols.button.saved']),
}}
/>
<Button color="link" onClick={cancelHandler}>
{intl.formatMessage(messages['profile.formcontrols.button.cancel'])}
</Button>
</FormGroup>
<Button
buttonType="link"
onClick={cancelHandler}
label={intl.formatMessage(messages['profile.formcontrols.button.cancel'])}
/>
</div>
</React.Fragment>
);
}

View File

@@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Input } from 'reactstrap';
import { injectIntl, intlShape } from 'react-intl';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faEyeSlash, faEye } from '@fortawesome/free-regular-svg-icons';
@@ -41,14 +40,14 @@ function VisibilitySelect({ intl, className, ...props }) {
<span className="d-inline-block ml-1 mr-2" style={{ width: '1.5rem' }}>
<FontAwesomeIcon icon={icon} />
</span>
<Input className="d-inline-block w-auto" {...props} type="select">
<select className="d-inline-block w-auto form-control" {...props}>
<option key="private" value="private">
{intl.formatMessage(messages['profile.visibility.who.just.me'])}
</option>
<option key="all_users" value="all_users">
{intl.formatMessage(messages['profile.visibility.who.everyone'])}
</option>
</Input>
</select>
</span>
);
}

View File

@@ -16,13 +16,7 @@ exports[`<ProfilePage /> Renders correctly in various states app loading 1`] = `
<div
className="spinner-border text-primary"
role="status"
>
<span
className="sr-only"
>
Loading...
</span>
</div>
/>
</div>
</div>
`;
@@ -38,7 +32,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
className="container-fluid"
>
<div
className="align-items-center pt-4 mb-4 pt-md-0 mb-md-0 row"
className="row align-items-center pt-4 mb-4 pt-md-0 mb-md-0"
>
<div
className="col-auto col-md-4 col-lg-3"
@@ -77,7 +71,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
</div>
</div>
<div
className="pl-0 col"
className="col pl-0"
>
<div
className="d-md-none"
@@ -235,7 +229,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
className="container-fluid"
>
<div
className="align-items-center pt-4 mb-4 pt-md-0 mb-md-0 row"
className="row align-items-center pt-4 mb-4 pt-md-0 mb-md-0"
>
<div
className="col-auto col-md-4 col-lg-3"
@@ -254,14 +248,14 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
>
<div
className="dropdown"
onKeyDown={[Function]}
>
<button
aria-expanded={false}
aria-haspopup={true}
aria-label={null}
className="text-white btn-block btn btn-link btn-sm"
aria-haspopup="true"
className="btn dropdown-toggle btn-primary"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
<span>
@@ -270,17 +264,21 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
</button>
<div
aria-hidden={true}
aria-label={
<FormattedMessage
defaultMessage="Change"
description="Change photo button"
id="profile.profileavatar.change-button"
values={Object {}}
/>
}
className="dropdown-menu"
role="menu"
tabIndex="-1"
x-placement={undefined}
>
<button
className="dropdown-item"
onClick={[Function]}
role="menuitem"
tabIndex="0"
type="button"
onKeyDown={[Function]}
>
<span>
Upload Photo
@@ -289,9 +287,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
<button
className="dropdown-item"
onClick={[Function]}
role="menuitem"
tabIndex="0"
type="button"
onKeyDown={[Function]}
>
<span>
Remove
@@ -328,7 +324,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
</div>
</div>
<div
className="pl-0 col"
className="col pl-0"
>
<div
className="d-md-none"
@@ -356,14 +352,21 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
className="d-none d-md-block float-right"
>
<a
aria-label={null}
className="btn btn-primary"
href="undefined/records"
onClick={[Function]}
target="blank"
type={undefined}
target="_blank"
>
View My Records
<span>
<span
aria-hidden={false}
aria-label="Opens in a new window"
className="fa fa-external-link"
title="Opens in a new window"
/>
</span>
</a>
</div>
</div>
@@ -400,14 +403,21 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
className="d-md-none mb-4"
>
<a
aria-label={null}
className="btn btn-primary"
href="undefined/records"
onClick={[Function]}
target="blank"
type={undefined}
target="_blank"
>
View My Records
<span>
<span
aria-hidden={false}
aria-label="Opens in a new window"
className="fa fa-external-link"
title="Opens in a new window"
/>
</span>
</a>
</div>
<div
@@ -1157,13 +1167,13 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
</p>
</div>
<div
className="align-items-stretch row"
className="row align-items-stretch"
>
<div
className="d-flex align-items-stretch col-sm-6"
className="col col-sm-6 d-flex align-items-stretch"
>
<div
className="mb-4 certificate flex-grow-1 card"
className="card mb-4 certificate flex-grow-1"
>
<div
className="certificate-type-illustration"
@@ -1174,7 +1184,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
}
/>
<div
className="d-flex flex-column card-body"
className="card-body d-flex flex-column"
>
<div
className="card-title"
@@ -1217,14 +1227,21 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
</p>
<div>
<a
aria-label={null}
className="btn btn-outline-primary"
href="http://www.example.com/"
onClick={[Function]}
target="blank"
type={undefined}
target="_blank"
>
View Certificate
<span>
<span
aria-hidden={false}
aria-label="Opens in a new window"
className="fa fa-external-link"
title="Opens in a new window"
/>
</span>
</a>
</div>
</div>
@@ -1250,7 +1267,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="container-fluid"
>
<div
className="align-items-center pt-4 mb-4 pt-md-0 mb-md-0 row"
className="row align-items-center pt-4 mb-4 pt-md-0 mb-md-0"
>
<div
className="col-auto col-md-4 col-lg-3"
@@ -1269,14 +1286,14 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
>
<div
className="dropdown"
onKeyDown={[Function]}
>
<button
aria-expanded={false}
aria-haspopup={true}
aria-label={null}
className="text-white btn-block btn btn-link btn-sm"
aria-haspopup="true"
className="btn dropdown-toggle btn-primary"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
<span>
@@ -1285,17 +1302,21 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</button>
<div
aria-hidden={true}
aria-label={
<FormattedMessage
defaultMessage="Change"
description="Change photo button"
id="profile.profileavatar.change-button"
values={Object {}}
/>
}
className="dropdown-menu"
role="menu"
tabIndex="-1"
x-placement={undefined}
>
<button
className="dropdown-item"
onClick={[Function]}
role="menuitem"
tabIndex="0"
type="button"
onKeyDown={[Function]}
>
<span>
Upload Photo
@@ -1304,9 +1325,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
<button
className="dropdown-item"
onClick={[Function]}
role="menuitem"
tabIndex="0"
type="button"
onKeyDown={[Function]}
>
<span>
Remove
@@ -1343,7 +1362,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</div>
</div>
<div
className="pl-0 col"
className="col pl-0"
>
<div
className="d-md-none"
@@ -1371,14 +1390,21 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="d-none d-md-block float-right"
>
<a
aria-label={null}
className="btn btn-primary"
href="undefined/records"
onClick={[Function]}
target="blank"
type={undefined}
target="_blank"
>
View My Records
<span>
<span
aria-hidden={false}
aria-label="Opens in a new window"
className="fa fa-external-link"
title="Opens in a new window"
/>
</span>
</a>
</div>
</div>
@@ -1415,14 +1441,21 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="d-md-none mb-4"
>
<a
aria-label={null}
className="btn btn-primary"
href="undefined/records"
onClick={[Function]}
target="blank"
type={undefined}
target="_blank"
>
View My Records
<span>
<span
aria-hidden={false}
aria-label="Opens in a new window"
className="fa fa-external-link"
title="Opens in a new window"
/>
</span>
</a>
</div>
<div
@@ -2016,16 +2049,13 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
role="dialog"
>
<form
className=""
onSubmit={[Function]}
>
<div
className="form-group"
>
<label
className=""
htmlFor="bio"
id="bio-label"
>
About Me
</label>
@@ -2037,7 +2067,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
onChange={[Function]}
value="This is my bio"
/>
<div
<p
className="invalid-feedback"
id="bio-error-feedback"
/>
@@ -2085,6 +2115,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
id="visibilityBio"
name="visibilityBio"
onChange={[Function]}
type="select"
value="all_users"
>
<option
@@ -2104,11 +2135,12 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="form-group"
>
<button
aria-label={null}
aria-live="assertive"
className="btn-async-action d-inline-flex align-items-center justify-content-center btn-state-pending btn btn-primary disabled"
className="btn btn-async-action d-inline-flex align-items-center justify-content-center btn-state-pending btn-primary disabled"
disabled={true}
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
style={null}
type="submit"
>
@@ -2119,20 +2151,15 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
<div
className="spinner-border-sm spinner-border text-white"
role="status"
>
<span
className="sr-only"
>
Loading...
</span>
</div>
/>
</span>
Saving
</button>
<button
aria-label={null}
className="btn btn-link"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
Cancel
@@ -2230,13 +2257,13 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</p>
</div>
<div
className="align-items-stretch row"
className="row align-items-stretch"
>
<div
className="d-flex align-items-stretch col-sm-6"
className="col col-sm-6 d-flex align-items-stretch"
>
<div
className="mb-4 certificate flex-grow-1 card"
className="card mb-4 certificate flex-grow-1"
>
<div
className="certificate-type-illustration"
@@ -2247,7 +2274,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
}
/>
<div
className="d-flex flex-column card-body"
className="card-body d-flex flex-column"
>
<div
className="card-title"
@@ -2290,14 +2317,21 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</p>
<div>
<a
aria-label={null}
className="btn btn-outline-primary"
href="http://www.example.com/"
onClick={[Function]}
target="blank"
type={undefined}
target="_blank"
>
View Certificate
<span>
<span
aria-hidden={false}
aria-label="Opens in a new window"
className="fa fa-external-link"
title="Opens in a new window"
/>
</span>
</a>
</div>
</div>

View File

@@ -73,6 +73,18 @@ $fa-font-path: "~font-awesome/fonts";
margin-bottom: 1.2rem;
}
}
.dropdown {
@include media-breakpoint-up(md) {
margin-bottom: 1.2rem;
}
.btn {
color: $white;
background: transparent;
border-color: transparent;
margin: 0;
}
}
}
.profile-avatar {