Tags: Make Tags component more a11y-friendly (#43808)
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user