A11y: Fix fastpass issues for /org/teams pages (#39848)

* A11y: Fix fastpass issues for /org/teams pages
See #39429
This commit is contained in:
kay delaney 2021-09-30 15:46:10 +01:00 committed by GitHub
parent b711bc00b9
commit 451d023c79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 85 additions and 31 deletions

View File

@ -10,9 +10,10 @@ export interface Props {
size?: ComponentSize;
/** Disable button click action */
disabled?: boolean;
'aria-label'?: string;
}
export const DeleteButton: FC<Props> = ({ size, disabled, onConfirm }) => {
export const DeleteButton: FC<Props> = ({ size, disabled, onConfirm, 'aria-label': ariaLabel }) => {
return (
<ConfirmButton
confirmText="Delete"
@ -21,7 +22,7 @@ export const DeleteButton: FC<Props> = ({ size, disabled, onConfirm }) => {
disabled={disabled}
onConfirm={onConfirm}
>
<Button variant="destructive" icon="times" size={size || 'sm'} />
<Button aria-label={ariaLabel} variant="destructive" icon="times" size={size || 'sm'} />
</ConfirmButton>
);
};

View File

@ -308,7 +308,7 @@ $checkboxImageUrl: '../img/checkbox_white.png';
$info-box-border-color: $blue-base;
// footer
$footer-link-color: $gray-3;
$footer-link-color: $gray-1;
$footer-link-hover: $dark-2;
// json explorer

View File

@ -5,11 +5,12 @@ import { GrafanaTheme2 } from '@grafana/data';
type Props = {
onClick: () => void;
'aria-label'?: string;
};
export const CloseButton: React.FC<Props> = ({ onClick }) => {
export const CloseButton: React.FC<Props> = ({ onClick, 'aria-label': ariaLabel }) => {
const styles = useStyles2(getStyles);
return <IconButton className={styles} name="times" onClick={onClick} />;
return <IconButton aria-label={ariaLabel ?? 'Close'} className={styles} name="times" onClick={onClick} />;
};
const getStyles = (theme: GrafanaTheme2) =>

View File

@ -48,9 +48,11 @@ const EmptyListCTA: React.FunctionComponent<Props> = ({
<span key="proTipFooter">
<Icon name="rocket" />
<> ProTip: {proTip} </>
<a href={proTipLink} target={proTipTarget} className="text-link">
{proTipLinkTitle}
</a>
{proTipLink && (
<a href={proTipLink} target={proTipTarget} className="text-link">
{proTipLinkTitle}
</a>
)}
</span>
) : (
''

View File

@ -15,6 +15,7 @@ import { SelectableValue } from '@grafana/data';
export interface Props {
onSelected: (user: SelectableValue<OrgUser['userId']>) => void;
className?: string;
inputId?: string;
}
export interface State {
@ -59,7 +60,7 @@ export class UserPicker extends Component<Props, State> {
}
render() {
const { className, onSelected } = this.props;
const { className, onSelected, inputId } = this.props;
const { isLoading } = this.state;
return (
@ -68,6 +69,7 @@ export class UserPicker extends Component<Props, State> {
menuShouldPortal
isClearable
className={className}
inputId={inputId}
isLoading={isLoading}
defaultOptions={true}
loadOptions={this.debouncedSearch}

View File

@ -135,7 +135,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
<Field
label={
<Label>
<Label htmlFor="home-dashboard-select">
<span className={styles.labelText}>Home Dashboard</span>
<Tooltip content="Not finding the dashboard you want? Star it first, then it should appear in this select box.">
<Icon name="info-circle" />
@ -154,6 +154,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
}
options={dashboards}
placeholder="Choose default dashboard"
inputId="home-dashboard-select"
/>
</Field>

View File

@ -33,7 +33,7 @@ export class CreateTeam extends PureComponent<Props> {
{({ register }) => (
<FieldSet label="New Team">
<Field label="Name">
<Input {...register('name', { required: true })} width={60} />
<Input {...register('name', { required: true })} id="team-name" width={60} />
</Field>
<Field
label={

View File

@ -52,20 +52,27 @@ export class TeamList extends PureComponent<Props, any> {
<tr key={team.id}>
<td className="width-4 text-center link-td">
<a href={teamUrl}>
<img className="filter-table__avatar" src={team.avatarUrl} />
<img className="filter-table__avatar" src={team.avatarUrl} alt="Team avatar" />
</a>
</td>
<td className="link-td">
<a href={teamUrl}>{team.name}</a>
</td>
<td className="link-td">
<a href={teamUrl}>{team.email}</a>
<a href={teamUrl} aria-label={team.email?.length > 0 ? undefined : 'Empty email cell'}>
{team.email}
</a>
</td>
<td className="link-td">
<a href={teamUrl}>{team.memberCount}</a>
</td>
<td className="text-right">
<DeleteButton size="sm" disabled={!canDelete} onConfirm={() => this.deleteTeam(team)} />
<DeleteButton
aria-label="Delete team"
size="sm"
disabled={!canDelete}
onConfirm={() => this.deleteTeam(team)}
/>
</td>
</tr>
);

View File

@ -89,7 +89,11 @@ export class TeamMemberRow extends PureComponent<Props> {
return (
<tr key={member.userId}>
<td className="width-4 text-center">
<img className="filter-table__avatar" src={member.avatarUrl} />
<img
aria-label={`Avatar for team member "${member.name}"`}
className="filter-table__avatar"
src={member.avatarUrl}
/>
</td>
<td>{member.login}</td>
<td>{member.email}</td>
@ -97,7 +101,12 @@ export class TeamMemberRow extends PureComponent<Props> {
{this.renderPermissions(member)}
{syncEnabled && this.renderLabels(member.labels)}
<td className="text-right">
<DeleteButton size="sm" disabled={!signedInUserIsTeamAdmin} onConfirm={() => this.onRemoveMember(member)} />
<DeleteButton
aria-label="Remove team member"
size="sm"
disabled={!signedInUserIsTeamAdmin}
onConfirm={() => this.onRemoveMember(member)}
/>
</td>
</tr>
);

View File

@ -12,7 +12,7 @@ import { contextSrv } from 'app/core/services/context_srv';
import TeamMemberRow from './TeamMemberRow';
import { setSearchMemberQuery } from './state/reducers';
import { CloseButton } from 'app/core/components/CloseButton/CloseButton';
import { Button, FilterInput } from '@grafana/ui';
import { Button, FilterInput, Label } from '@grafana/ui';
import { SelectableValue } from '@grafana/data';
function mapStateToProps(state: any) {
@ -97,10 +97,10 @@ export class TeamMembers extends PureComponent<Props, State> {
<SlideDown in={isAdding}>
<div className="cta-form">
<CloseButton onClick={this.onToggleAdding} />
<h5>Add team member</h5>
<CloseButton aria-label="Close 'Add team member' dialogue" onClick={this.onToggleAdding} />
<Label htmlFor="user-picker">Add team member</Label>
<div className="gf-form-inline">
<UserPicker onSelected={this.onUserSelected} className="min-width-30" />
<UserPicker inputId="user-picker" onSelected={this.onUserSelected} className="min-width-30" />
{this.state.newTeamMember && (
<Button type="submit" onClick={this.onAddUserToTeam}>
Add to team

View File

@ -30,14 +30,14 @@ export const TeamSettings: FC<Props> = ({ team, updateTeam }) => {
{({ register }) => (
<>
<Field label="Name">
<Input {...register('name', { required: true })} />
<Input {...register('name', { required: true })} id="name-input" />
</Field>
<Field
label="Email"
description="This is optional and is primarily used to set the team profile avatar (via gravatar service)."
>
<Input {...register('email')} placeholder="team@email.com" type="email" />
<Input {...register('email')} placeholder="team@email.com" type="email" id="email-input" />
</Field>
<Button type="submit">Update</Button>
</>

View File

@ -92,6 +92,7 @@ exports[`Render should render teams table 1`] = `
href="org/teams/edit/1"
>
<img
alt="Team avatar"
className="filter-table__avatar"
src="some/url/"
/>
@ -128,6 +129,7 @@ exports[`Render should render teams table 1`] = `
className="text-right"
>
<DeleteButton
aria-label="Delete team"
disabled={false}
onConfirm={[Function]}
size="sm"
@ -144,6 +146,7 @@ exports[`Render should render teams table 1`] = `
href="org/teams/edit/2"
>
<img
alt="Team avatar"
className="filter-table__avatar"
src="some/url/"
/>
@ -180,6 +183,7 @@ exports[`Render should render teams table 1`] = `
className="text-right"
>
<DeleteButton
aria-label="Delete team"
disabled={false}
onConfirm={[Function]}
size="sm"
@ -196,6 +200,7 @@ exports[`Render should render teams table 1`] = `
href="org/teams/edit/3"
>
<img
alt="Team avatar"
className="filter-table__avatar"
src="some/url/"
/>
@ -232,6 +237,7 @@ exports[`Render should render teams table 1`] = `
className="text-right"
>
<DeleteButton
aria-label="Delete team"
disabled={false}
onConfirm={[Function]}
size="sm"
@ -248,6 +254,7 @@ exports[`Render should render teams table 1`] = `
href="org/teams/edit/4"
>
<img
alt="Team avatar"
className="filter-table__avatar"
src="some/url/"
/>
@ -284,6 +291,7 @@ exports[`Render should render teams table 1`] = `
className="text-right"
>
<DeleteButton
aria-label="Delete team"
disabled={false}
onConfirm={[Function]}
size="sm"
@ -300,6 +308,7 @@ exports[`Render should render teams table 1`] = `
href="org/teams/edit/5"
>
<img
alt="Team avatar"
className="filter-table__avatar"
src="some/url/"
/>
@ -336,6 +345,7 @@ exports[`Render should render teams table 1`] = `
className="text-right"
>
<DeleteButton
aria-label="Delete team"
disabled={false}
onConfirm={[Function]}
size="sm"
@ -422,6 +432,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned on and signedin us
href="org/teams/edit/1"
>
<img
alt="Team avatar"
className="filter-table__avatar"
src="some/url/"
/>
@ -458,6 +469,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned on and signedin us
className="text-right"
>
<DeleteButton
aria-label="Delete team"
disabled={true}
onConfirm={[Function]}
size="sm"
@ -544,6 +556,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned on and signedin us
href="org/teams/edit/1"
>
<img
alt="Team avatar"
className="filter-table__avatar"
src="some/url/"
/>
@ -580,6 +593,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned on and signedin us
className="text-right"
>
<DeleteButton
aria-label="Delete team"
disabled={true}
onConfirm={[Function]}
size="sm"

View File

@ -8,6 +8,7 @@ exports[`Render should render team members when sync enabled 1`] = `
className="width-4 text-center"
>
<img
aria-label="Avatar for team member \\"testName\\""
className="filter-table__avatar"
src="some/url/"
/>
@ -49,6 +50,7 @@ exports[`Render should render team members when sync enabled 1`] = `
className="text-right"
>
<DeleteButton
aria-label="Remove team member"
disabled={true}
onConfirm={[Function]}
size="sm"
@ -65,6 +67,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned off should not ren
className="width-4 text-center"
>
<img
aria-label="Avatar for team member \\"testName\\""
className="filter-table__avatar"
src="some/url/"
/>
@ -140,6 +143,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned off should not ren
className="text-right"
>
<DeleteButton
aria-label="Remove team member"
disabled={false}
onConfirm={[Function]}
size="sm"
@ -156,6 +160,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned on should render p
className="width-4 text-center"
>
<img
aria-label="Avatar for team member \\"testName\\""
className="filter-table__avatar"
src="some/url/"
/>
@ -231,6 +236,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned on should render p
className="text-right"
>
<DeleteButton
aria-label="Remove team member"
disabled={false}
onConfirm={[Function]}
size="sm"
@ -247,6 +253,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned on should render s
className="width-4 text-center"
>
<img
aria-label="Avatar for team member \\"testName\\""
className="filter-table__avatar"
src="some/url/"
/>
@ -279,6 +286,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned on should render s
className="text-right"
>
<DeleteButton
aria-label="Remove team member"
disabled={true}
onConfirm={[Function]}
size="sm"

View File

@ -29,16 +29,20 @@ exports[`Render should render component 1`] = `
className="cta-form"
>
<CloseButton
aria-label="Close 'Add team member' dialogue"
onClick={[Function]}
/>
<h5>
<Label
htmlFor="user-picker"
>
Add team member
</h5>
</Label>
<div
className="gf-form-inline"
>
<UserPicker
className="min-width-30"
inputId="user-picker"
onSelected={[Function]}
/>
</div>
@ -113,16 +117,20 @@ exports[`Render should render team members 1`] = `
className="cta-form"
>
<CloseButton
aria-label="Close 'Add team member' dialogue"
onClick={[Function]}
/>
<h5>
<Label
htmlFor="user-picker"
>
Add team member
</h5>
</Label>
<div
className="gf-form-inline"
>
<UserPicker
className="min-width-30"
inputId="user-picker"
onSelected={[Function]}
/>
</div>

View File

@ -101,7 +101,7 @@ $text-color-strong: #fff;
$text-color: rgb(204, 204, 220);
$text-color-semi-weak: rgba(204, 204, 220, 0.65);
$text-color-weak: rgba(204, 204, 220, 0.65);
$text-color-faint: rgba(204, 204, 220, 0.40);
$text-color-faint: rgba(204, 204, 220, 0.57);
$text-color-emphasis: #fff;
$text-blue: #6E9FFF;
@ -115,7 +115,7 @@ $brand-gradient-vertical: linear-gradient(#f05a28 30%, #fbca0a 99%);
// Links
// -------------------------
$link-color: rgb(204, 204, 220);
$link-color-disabled: rgba(204, 204, 220, 0.40);
$link-color-disabled: rgba(204, 204, 220, 0.57);
$link-hover-color: #fff;
$external-link-color: #6E9FFF;
@ -216,7 +216,7 @@ $input-border-color: rgba(204, 204, 220, 0.15);
$input-box-shadow: none;
$input-border-focus: #6E9FFF;
$input-box-shadow-focus: #6E9FFF !default;
$input-color-placeholder: rgba(204, 204, 220, 0.40);
$input-color-placeholder: rgba(204, 204, 220, 0.57);
$input-label-bg: #22252b;
$input-color-select-arrow: $white;

View File

@ -310,7 +310,7 @@ $checkboxImageUrl: '../img/checkbox_white.png';
$info-box-border-color: $blue-base;
// footer
$footer-link-color: $gray-3;
$footer-link-color: $gray-1;
$footer-link-hover: $dark-2;
// json explorer

View File

@ -53,7 +53,8 @@
a {
display: block;
padding: $space-inset-squish-md;
padding: 0px $space-sm;
height: 30px;
}
}