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

pull/43900/head
kay delaney 3 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 onTagClick = (event: React.MouseEvent<HTMLElement>) => {
if (onClick) {
onClick(name, event);
}
event.preventDefault();
event.stopPropagation();
onClick?.(name, event);
};
return (
<span
key={name}
ref={ref}
onClick={onTagClick}
className={cx(styles.wrapper, className, onClick && styles.hover)}
{...rest}
>
const classes = cx(styles.wrapper, className, { [styles.hover]: onClick !== undefined });
return onClick ? (
<button {...rest} className={classes} onClick={onTagClick} ref={ref as React.ForwardedRef<HTMLButtonElement>}>
{name}
</button>
) : (
<span {...rest} className={classes} ref={ref}>
{name}
</span>
);
@ -51,6 +52,8 @@ const getTagStyles = (theme: GrafanaTheme, name: string, colorIndex?: number) =>
}
return {
wrapper: css`
appearance: none;
border-style: none;
font-weight: ${theme.typography.weight.semibold};
font-size: ${theme.typography.size.sm};
line-height: ${theme.typography.lineHeight.xs};

@ -10,21 +10,24 @@ export interface Props {
onClick?: OnTagClick;
/** Custom styles for the wrapper component */
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 styles = getStyles(theme, Boolean(displayMax && displayMax > 0));
const numTags = tags.length;
const tagsToDisplay = displayMax ? tags.slice(0, displayMax) : tags;
return (
<span className={cx(styles.wrapper, className)}>
{tagsToDisplay.map((tag) => (
<Tag key={tag} name={tag} onClick={onClick} />
<ul className={cx(styles.wrapper, className)} aria-label="Tags">
{tagsToDisplay.map((tag, i) => (
<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>}
</span>
</ul>
);
});
@ -33,6 +36,7 @@ TagList.displayName = 'TagList';
const getStyles = (theme: GrafanaTheme2, isTruncated: boolean) => {
return {
wrapper: css`
position: relative;
align-items: ${isTruncated ? 'center' : 'unset'};
display: flex;
flex: 1 1 auto;
@ -45,5 +49,8 @@ const getStyles = (theme: GrafanaTheme2, isTruncated: boolean) => {
color: ${theme.colors.text.secondary};
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.Tags>
<TagList tags={item.tags} onClick={tagSelected} />
<TagList tags={item.tags} onClick={tagSelected} getAriaLabel={(tag) => `Filter by tag "${tag}"`} />
</Card.Tags>
</Card>
);

Loading…
Cancel
Save