Tags: Make Tags component more a11y-friendly (#43808)

pull/43900/head
kay delaney 4 years ago committed by GitHub
parent d1a94c1fa2
commit cc9e70be5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      packages/grafana-ui/src/components/Tags/Tag.tsx
  2. 19
      packages/grafana-ui/src/components/Tags/TagList.tsx
  3. 2
      public/app/features/search/components/SearchItem.tsx

@ -22,19 +22,20 @@ export const Tag = forwardRef<HTMLElement, Props>(({ name, onClick, className, c
const styles = getTagStyles(theme, name, colorIndex); const styles = getTagStyles(theme, name, colorIndex);
const onTagClick = (event: React.MouseEvent<HTMLElement>) => { const onTagClick = (event: React.MouseEvent<HTMLElement>) => {
if (onClick) { event.preventDefault();
onClick(name, event); event.stopPropagation();
}
onClick?.(name, event);
}; };
return ( const classes = cx(styles.wrapper, className, { [styles.hover]: onClick !== undefined });
<span
key={name} return onClick ? (
ref={ref} <button {...rest} className={classes} onClick={onTagClick} ref={ref as React.ForwardedRef<HTMLButtonElement>}>
onClick={onTagClick} {name}
className={cx(styles.wrapper, className, onClick && styles.hover)} </button>
{...rest} ) : (
> <span {...rest} className={classes} ref={ref}>
{name} {name}
</span> </span>
); );
@ -51,6 +52,8 @@ const getTagStyles = (theme: GrafanaTheme, name: string, colorIndex?: number) =>
} }
return { return {
wrapper: css` wrapper: css`
appearance: none;
border-style: none;
font-weight: ${theme.typography.weight.semibold}; font-weight: ${theme.typography.weight.semibold};
font-size: ${theme.typography.size.sm}; font-size: ${theme.typography.size.sm};
line-height: ${theme.typography.lineHeight.xs}; line-height: ${theme.typography.lineHeight.xs};

@ -10,21 +10,24 @@ export interface Props {
onClick?: OnTagClick; onClick?: OnTagClick;
/** Custom styles for the wrapper component */ /** Custom styles for the wrapper component */
className?: string; className?: string;
/** aria-label for the `i`-th Tag component */
getAriaLabel?: (name: string, i: number) => string;
} }
export const TagList: FC<Props> = memo(({ displayMax, tags, onClick, className }) => { export const TagList: FC<Props> = memo(({ displayMax, tags, onClick, className, getAriaLabel }) => {
const theme = useTheme2(); const theme = useTheme2();
const styles = getStyles(theme, Boolean(displayMax && displayMax > 0)); const styles = getStyles(theme, Boolean(displayMax && displayMax > 0));
const numTags = tags.length; const numTags = tags.length;
const tagsToDisplay = displayMax ? tags.slice(0, displayMax) : tags; const tagsToDisplay = displayMax ? tags.slice(0, displayMax) : tags;
return ( return (
<span className={cx(styles.wrapper, className)}> <ul className={cx(styles.wrapper, className)} aria-label="Tags">
{tagsToDisplay.map((tag) => ( {tagsToDisplay.map((tag, i) => (
<Tag key={tag} name={tag} onClick={onClick} /> <li className={styles.li} key={tag}>
<Tag name={tag} onClick={onClick} aria-label={getAriaLabel?.(tag, i)} />
</li>
))} ))}
{displayMax && displayMax > 0 && numTags - 1 > 0 && <span className={styles.moreTagsLabel}>+ {numTags - 1}</span>} {displayMax && displayMax > 0 && numTags - 1 > 0 && <span className={styles.moreTagsLabel}>+ {numTags - 1}</span>}
</span> </ul>
); );
}); });
@ -33,6 +36,7 @@ TagList.displayName = 'TagList';
const getStyles = (theme: GrafanaTheme2, isTruncated: boolean) => { const getStyles = (theme: GrafanaTheme2, isTruncated: boolean) => {
return { return {
wrapper: css` wrapper: css`
position: relative;
align-items: ${isTruncated ? 'center' : 'unset'}; align-items: ${isTruncated ? 'center' : 'unset'};
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
@ -45,5 +49,8 @@ const getStyles = (theme: GrafanaTheme2, isTruncated: boolean) => {
color: ${theme.colors.text.secondary}; color: ${theme.colors.text.secondary};
font-size: ${theme.typography.size.sm}; font-size: ${theme.typography.size.sm};
`, `,
li: css({
listStyle: 'none',
}),
}; };
}; };

@ -78,7 +78,7 @@ export const SearchItem: FC<Props> = ({ item, editable, onToggleChecked, onTagSe
)} )}
</Card.Meta> </Card.Meta>
<Card.Tags> <Card.Tags>
<TagList tags={item.tags} onClick={tagSelected} /> <TagList tags={item.tags} onClick={tagSelected} getAriaLabel={(tag) => `Filter by tag "${tag}"`} />
</Card.Tags> </Card.Tags>
</Card> </Card>
); );

Loading…
Cancel
Save