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:
Bruce Blaser
2023-01-26 09:28:01 -08:00
committed by GitHub
parent d96600f1f7
commit 849e2c041d
5 changed files with 238 additions and 211 deletions
+49 -42
View File
@@ -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 />
+34 -26
View File
@@ -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 />
+62 -57
View File
@@ -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>
</>
+88 -85
View File
@@ -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>
+5 -1
View File
@@ -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>
);