mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
fix(a11y): Save buttons on Settings page (#49142)
* fix: make save buttons more accessible for screen reader users * fix: associate username input with label
This commit is contained in:
@@ -216,48 +216,55 @@ class AboutSettings extends Component<AboutProps, AboutState> {
|
||||
<SectionHeader>{t('settings.headings.personal-info')}</SectionHeader>
|
||||
<FullWidthRow>
|
||||
<form id='camper-identity' onSubmit={this.handleSubmit}>
|
||||
<FormGroup controlId='about-name'>
|
||||
<ControlLabel>
|
||||
<strong>{t('settings.labels.name')}</strong>
|
||||
</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.handleNameChange}
|
||||
type='text'
|
||||
value={name}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup controlId='about-location'>
|
||||
<ControlLabel>
|
||||
<strong>{t('settings.labels.location')}</strong>
|
||||
</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.handleLocationChange}
|
||||
type='text'
|
||||
value={location}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup controlId='about-picture'>
|
||||
<ControlLabel>
|
||||
<strong>{t('settings.labels.picture')}</strong>
|
||||
</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.handlePictureChange}
|
||||
type='url'
|
||||
value={picture}
|
||||
/>
|
||||
{this.showImageValidationWarning()}
|
||||
</FormGroup>
|
||||
<FormGroup controlId='about-about'>
|
||||
<ControlLabel>
|
||||
<strong>{t('settings.labels.about')}</strong>
|
||||
</ControlLabel>
|
||||
<FormControl
|
||||
componentClass='textarea'
|
||||
onChange={this.handleAboutChange}
|
||||
value={about}
|
||||
/>
|
||||
</FormGroup>
|
||||
<BlockSaveButton disabled={this.isFormPristine()} />
|
||||
<div role='group' aria-label={t('settings.headings.personal-info')}>
|
||||
<FormGroup controlId='about-name'>
|
||||
<ControlLabel>
|
||||
<strong>{t('settings.labels.name')}</strong>
|
||||
</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.handleNameChange}
|
||||
type='text'
|
||||
value={name}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup controlId='about-location'>
|
||||
<ControlLabel>
|
||||
<strong>{t('settings.labels.location')}</strong>
|
||||
</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.handleLocationChange}
|
||||
type='text'
|
||||
value={location}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup controlId='about-picture'>
|
||||
<ControlLabel>
|
||||
<strong>{t('settings.labels.picture')}</strong>
|
||||
</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.handlePictureChange}
|
||||
type='url'
|
||||
value={picture}
|
||||
/>
|
||||
{this.showImageValidationWarning()}
|
||||
</FormGroup>
|
||||
<FormGroup controlId='about-about'>
|
||||
<ControlLabel>
|
||||
<strong>{t('settings.labels.about')}</strong>
|
||||
</ControlLabel>
|
||||
<FormControl
|
||||
componentClass='textarea'
|
||||
onChange={this.handleAboutChange}
|
||||
value={about}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
<BlockSaveButton disabled={this.isFormPristine()}>
|
||||
{t('buttons.save')}{' '}
|
||||
<span className='sr-only'>
|
||||
{t('settings.headings.personal-info')}
|
||||
</span>
|
||||
</BlockSaveButton>
|
||||
</form>
|
||||
</FullWidthRow>
|
||||
<Spacer />
|
||||
|
||||
@@ -175,38 +175,46 @@ function EmailSettings({
|
||||
<ControlLabel>{t('settings.email.current')}</ControlLabel>
|
||||
<FormControl.Static>{currentEmail}</FormControl.Static>
|
||||
</FormGroup>
|
||||
<FormGroup controlId='new-email' validationState={newEmailValidation}>
|
||||
<ControlLabel>{t('settings.email.new')}</ControlLabel>
|
||||
<FormControl
|
||||
onChange={createHandleEmailFormChange('newEmail')}
|
||||
type='email'
|
||||
value={newEmail}
|
||||
/>
|
||||
{newEmailValidationMessage ? (
|
||||
<HelpBlock>{newEmailValidationMessage}</HelpBlock>
|
||||
) : null}
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
controlId='confirm-email'
|
||||
validationState={confirmEmailValidation}
|
||||
>
|
||||
<ControlLabel>{t('settings.email.confirm')}</ControlLabel>
|
||||
<FormControl
|
||||
onChange={createHandleEmailFormChange('confirmNewEmail')}
|
||||
type='email'
|
||||
value={confirmNewEmail}
|
||||
/>
|
||||
{confirmEmailValidationMessage ? (
|
||||
<HelpBlock>{confirmEmailValidationMessage}</HelpBlock>
|
||||
) : null}
|
||||
</FormGroup>
|
||||
<div role='group' aria-label={t('settings.email.heading')}>
|
||||
<FormGroup
|
||||
controlId='new-email'
|
||||
validationState={newEmailValidation}
|
||||
>
|
||||
<ControlLabel>{t('settings.email.new')}</ControlLabel>
|
||||
<FormControl
|
||||
onChange={createHandleEmailFormChange('newEmail')}
|
||||
type='email'
|
||||
value={newEmail}
|
||||
/>
|
||||
{newEmailValidationMessage ? (
|
||||
<HelpBlock>{newEmailValidationMessage}</HelpBlock>
|
||||
) : null}
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
controlId='confirm-email'
|
||||
validationState={confirmEmailValidation}
|
||||
>
|
||||
<ControlLabel>{t('settings.email.confirm')}</ControlLabel>
|
||||
<FormControl
|
||||
onChange={createHandleEmailFormChange('confirmNewEmail')}
|
||||
type='email'
|
||||
value={confirmNewEmail}
|
||||
/>
|
||||
{confirmEmailValidationMessage ? (
|
||||
<HelpBlock>{confirmEmailValidationMessage}</HelpBlock>
|
||||
) : null}
|
||||
</FormGroup>
|
||||
</div>
|
||||
<BlockSaveButton
|
||||
disabled={
|
||||
newEmailValidation !== 'success' ||
|
||||
confirmEmailValidation !== 'success' ||
|
||||
isPristine
|
||||
}
|
||||
/>
|
||||
>
|
||||
{t('buttons.save')}{' '}
|
||||
<span className='sr-only'>{t('settings.email.heading')}</span>
|
||||
</BlockSaveButton>
|
||||
</form>
|
||||
</FullWidthRow>
|
||||
<Spacer />
|
||||
|
||||
@@ -185,65 +185,70 @@ class InternetSettings extends Component<InternetProps, InternetState> {
|
||||
<SectionHeader>{t('settings.headings.internet')}</SectionHeader>
|
||||
<FullWidthRow>
|
||||
<form id='internet-presence' onSubmit={this.handleSubmit}>
|
||||
<FormGroup
|
||||
controlId='internet-github'
|
||||
validationState={githubProfileValidation}
|
||||
>
|
||||
<ControlLabel>GitHub</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.createHandleChange('githubProfile')}
|
||||
placeholder='https://github.com/user-name'
|
||||
type='url'
|
||||
value={githubProfile}
|
||||
/>
|
||||
{this.renderCheck(githubProfile, githubProfileValidation)}
|
||||
{this.renderHelpBlock(githubProfileValidationMessage)}
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
controlId='internet-linkedin'
|
||||
validationState={linkedinValidation}
|
||||
>
|
||||
<ControlLabel>LinkedIn</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.createHandleChange('linkedin')}
|
||||
placeholder='https://www.linkedin.com/in/user-name'
|
||||
type='url'
|
||||
value={linkedin}
|
||||
/>
|
||||
{this.renderCheck(linkedin, linkedinValidation)}
|
||||
{this.renderHelpBlock(linkedinValidationMessage)}
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
controlId='internet-picture'
|
||||
validationState={twitterValidation}
|
||||
>
|
||||
<ControlLabel>Twitter</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.createHandleChange('twitter')}
|
||||
placeholder='https://twitter.com/user-name'
|
||||
type='url'
|
||||
value={twitter}
|
||||
/>
|
||||
{this.renderCheck(twitter, twitterValidation)}
|
||||
{this.renderHelpBlock(twitterValidationMessage)}
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
controlId='internet-website'
|
||||
validationState={websiteValidation}
|
||||
>
|
||||
<ControlLabel>{t('settings.labels.personal')}</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.createHandleChange('website')}
|
||||
placeholder='https://example.com'
|
||||
type='url'
|
||||
value={website}
|
||||
/>
|
||||
{this.renderCheck(website, websiteValidation)}
|
||||
{this.renderHelpBlock(websiteValidationMessage)}
|
||||
</FormGroup>
|
||||
<div role='group' aria-label={t('settings.headings.internet')}>
|
||||
<FormGroup
|
||||
controlId='internet-github'
|
||||
validationState={githubProfileValidation}
|
||||
>
|
||||
<ControlLabel>GitHub</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.createHandleChange('githubProfile')}
|
||||
placeholder='https://github.com/user-name'
|
||||
type='url'
|
||||
value={githubProfile}
|
||||
/>
|
||||
{this.renderCheck(githubProfile, githubProfileValidation)}
|
||||
{this.renderHelpBlock(githubProfileValidationMessage)}
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
controlId='internet-linkedin'
|
||||
validationState={linkedinValidation}
|
||||
>
|
||||
<ControlLabel>LinkedIn</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.createHandleChange('linkedin')}
|
||||
placeholder='https://www.linkedin.com/in/user-name'
|
||||
type='url'
|
||||
value={linkedin}
|
||||
/>
|
||||
{this.renderCheck(linkedin, linkedinValidation)}
|
||||
{this.renderHelpBlock(linkedinValidationMessage)}
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
controlId='internet-picture'
|
||||
validationState={twitterValidation}
|
||||
>
|
||||
<ControlLabel>Twitter</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.createHandleChange('twitter')}
|
||||
placeholder='https://twitter.com/user-name'
|
||||
type='url'
|
||||
value={twitter}
|
||||
/>
|
||||
{this.renderCheck(twitter, twitterValidation)}
|
||||
{this.renderHelpBlock(twitterValidationMessage)}
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
controlId='internet-website'
|
||||
validationState={websiteValidation}
|
||||
>
|
||||
<ControlLabel>{t('settings.labels.personal')}</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.createHandleChange('website')}
|
||||
placeholder='https://example.com'
|
||||
type='url'
|
||||
value={website}
|
||||
/>
|
||||
{this.renderCheck(website, websiteValidation)}
|
||||
{this.renderHelpBlock(websiteValidationMessage)}
|
||||
</FormGroup>
|
||||
</div>
|
||||
<BlockSaveButton
|
||||
disabled={this.isFormPristine() || !this.isFormValid()}
|
||||
/>
|
||||
>
|
||||
{t('buttons.save')}{' '}
|
||||
<span className='sr-only'>{t('settings.headings.internet')}</span>
|
||||
</BlockSaveButton>
|
||||
</form>
|
||||
</FullWidthRow>
|
||||
</>
|
||||
|
||||
@@ -60,90 +60,92 @@ function PrivacySettings({
|
||||
<FullWidthRow>
|
||||
<p>{t('settings.privacy')}</p>
|
||||
<Form inline={true} onSubmit={submitNewProfileSettings}>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-profile')}
|
||||
explain={t('settings.disabled')}
|
||||
flag={privacyValues['isLocked']}
|
||||
flagName='isLocked'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('isLocked')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-name')}
|
||||
explain={t('settings.private-name')}
|
||||
flag={!privacyValues['showName']}
|
||||
flagName='name'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showName')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-location')}
|
||||
flag={!privacyValues['showLocation']}
|
||||
flagName='showLocation'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showLocation')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-about')}
|
||||
flag={!privacyValues['showAbout']}
|
||||
flagName='showAbout'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showAbout')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-points')}
|
||||
flag={!privacyValues['showPoints']}
|
||||
flagName='showPoints'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showPoints')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-heatmap')}
|
||||
flag={!privacyValues['showHeatMap']}
|
||||
flagName='showHeatMap'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showHeatMap')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-certs')}
|
||||
explain={t('settings.disabled')}
|
||||
flag={!privacyValues['showCerts']}
|
||||
flagName='showCerts'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showCerts')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-portfolio')}
|
||||
flag={!privacyValues['showPortfolio']}
|
||||
flagName='showPortfolio'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showPortfolio')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-timeline')}
|
||||
explain={t('settings.disabled')}
|
||||
flag={!privacyValues['showTimeLine']}
|
||||
flagName='showTimeLine'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showTimeLine')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-donations')}
|
||||
flag={!privacyValues['showDonation']}
|
||||
flagName='showPortfolio'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showDonation')}
|
||||
/>
|
||||
<div role='group' aria-label={t('settings.headings.privacy')}>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-profile')}
|
||||
explain={t('settings.disabled')}
|
||||
flag={privacyValues['isLocked']}
|
||||
flagName='isLocked'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('isLocked')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-name')}
|
||||
explain={t('settings.private-name')}
|
||||
flag={!privacyValues['showName']}
|
||||
flagName='name'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showName')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-location')}
|
||||
flag={!privacyValues['showLocation']}
|
||||
flagName='showLocation'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showLocation')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-about')}
|
||||
flag={!privacyValues['showAbout']}
|
||||
flagName='showAbout'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showAbout')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-points')}
|
||||
flag={!privacyValues['showPoints']}
|
||||
flagName='showPoints'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showPoints')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-heatmap')}
|
||||
flag={!privacyValues['showHeatMap']}
|
||||
flagName='showHeatMap'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showHeatMap')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-certs')}
|
||||
explain={t('settings.disabled')}
|
||||
flag={!privacyValues['showCerts']}
|
||||
flagName='showCerts'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showCerts')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-portfolio')}
|
||||
flag={!privacyValues['showPortfolio']}
|
||||
flagName='showPortfolio'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showPortfolio')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-timeline')}
|
||||
explain={t('settings.disabled')}
|
||||
flag={!privacyValues['showTimeLine']}
|
||||
flagName='showTimeLine'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showTimeLine')}
|
||||
/>
|
||||
<ToggleSetting
|
||||
action={t('settings.labels.my-donations')}
|
||||
flag={!privacyValues['showDonation']}
|
||||
flagName='showPortfolio'
|
||||
offLabel={t('buttons.public')}
|
||||
onLabel={t('buttons.private')}
|
||||
toggleFlag={toggleFlag('showDonation')}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type='submit'
|
||||
bsSize='lg'
|
||||
@@ -152,7 +154,8 @@ function PrivacySettings({
|
||||
block={true}
|
||||
disabled={!madeChanges}
|
||||
>
|
||||
{t('buttons.save')}
|
||||
{t('buttons.save')}{' '}
|
||||
<span className='sr-only'>{t('settings.headings.privacy')}</span>
|
||||
</Button>
|
||||
</Form>
|
||||
</FullWidthRow>
|
||||
|
||||
@@ -227,6 +227,7 @@ class UsernameSettings extends Component<UsernameProps, UsernameState> {
|
||||
onChange={this.handleChange}
|
||||
value={formValue}
|
||||
data-cy='username-input'
|
||||
id='username-settings'
|
||||
/>
|
||||
</FormGroup>
|
||||
</FullWidthRow>
|
||||
@@ -237,7 +238,10 @@ class UsernameSettings extends Component<UsernameProps, UsernameState> {
|
||||
disabled={
|
||||
!(isValidUsername && valid && !isFormPristine) || submitClicked
|
||||
}
|
||||
/>
|
||||
>
|
||||
{t('buttons.save')}{' '}
|
||||
<span className='sr-only'>{t('settings.labels.username')}</span>
|
||||
</BlockSaveButton>
|
||||
</FullWidthRow>
|
||||
</form>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user