From 6f3200d4f01d4208a2bfa57046c003ef6b1f9a47 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Wed, 7 May 2025 15:43:48 +0100 Subject: [PATCH] Re-enable `jsx-a11y` recommended rules (#104637) * Re-enable `jsx-a11y` recommended rules * apply rule in correct place, couple of fixes * fix up some a11y issues * add ignore for keyboard a11y for now * readd testid * close carousel on backdrop click * use type="button" --------- Co-authored-by: Ashley Harrison --- eslint.config.js | 4 +- .../src/components/Carousel/Carousel.tsx | 174 +++++++++--------- .../Table/TableNG/Cells/HeaderCell.tsx | 1 + .../Table/TableNG/Cells/RowExpander.tsx | 2 +- .../ThemeSelector/ThemeSelectorDrawer.tsx | 2 + .../edit-pane/DashboardEditPane.tsx | 10 +- .../edit-pane/DashboardOutline.tsx | 3 +- .../scene/layout-rows/RowItemRenderer.tsx | 1 - .../scene/layout-tabs/TabItemRenderer.tsx | 1 - .../new-toolbar/actions/ToolbarSwitch.tsx | 7 +- .../variables/VariableSetEditableElement.tsx | 2 + .../logs/components/LogRowMessage.tsx | 5 +- .../logs/components/panel/LogLine.tsx | 1 + 13 files changed, 114 insertions(+), 99 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index d33fae54961..4611b52e9ac 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -173,9 +173,7 @@ module.exports = [ files: ['**/*.tsx'], ignores: ['**/*.{spec,test}.tsx'], rules: { - // rules marked "off" are those left in the recommended preset we need to fix - // we should remove the corresponding line and fix them one by one - // any marked "error" contain specific overrides we'll need to keep + ...jsxA11yPlugin.configs.recommended.rules, 'jsx-a11y/no-autofocus': [ 'error', { diff --git a/packages/grafana-ui/src/components/Carousel/Carousel.tsx b/packages/grafana-ui/src/components/Carousel/Carousel.tsx index 17cf1efbf09..f6ce2b239a2 100644 --- a/packages/grafana-ui/src/components/Carousel/Carousel.tsx +++ b/packages/grafana-ui/src/components/Carousel/Carousel.tsx @@ -1,11 +1,15 @@ import { css, cx } from '@emotion/css'; -import { useState, useEffect } from 'react'; +import { useDialog } from '@react-aria/dialog'; +import { FocusScope } from '@react-aria/focus'; +import { OverlayContainer, useOverlay } from '@react-aria/overlays'; +import { useState, useEffect, useRef } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '../../themes'; import { t } from '../../utils/i18n'; import { Alert } from '../Alert/Alert'; +import { clearButtonStyles } from '../Button'; import { IconButton } from '../IconButton/IconButton'; // Define the image item interface @@ -23,7 +27,8 @@ export const Carousel: React.FC = ({ images }) => { const [imageErrors, setImageErrors] = useState>({}); const [validImages, setValidImages] = useState(images); - const styles = useStyles2(getStyles()); + const styles = useStyles2(getStyles); + const resetButtonStyles = useStyles2(clearButtonStyles); const handleImageError = (path: string) => { setImageErrors((prev) => ({ @@ -77,6 +82,11 @@ export const Carousel: React.FC = ({ images }) => { } }; + const ref = useRef(null); + + const { overlayProps, underlayProps } = useOverlay({ isOpen: selectedIndex !== null, onClose: closePreview }, ref); + const { dialogProps } = useDialog({}, ref); + if (validImages.length === 0) { return ( = ({ images }) => { } return ( -
+ <>
{validImages.map((image, index) => ( -
openPreview(index)} style={{ cursor: 'pointer' }}> +
+ ))}
{selectedIndex !== null && ( -
- + +
+ + {/* convenience method for keyboard users */} + {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} +
+ - { - e.stopPropagation(); - goToPrevious(); - }} - className={cx(styles.navigationButton, styles.previousButton)} - data-testid="previous-button" - /> + -
e.stopPropagation()} - data-testid="carousel-full-image" - > - {validImages[selectedIndex].name} handleImageError(validImages[selectedIndex].path)} - /> -
+
+ {validImages[selectedIndex].name} handleImageError(validImages[selectedIndex].path)} + /> +
- { - e.stopPropagation(); - goToNext(); - }} - className={cx(styles.navigationButton, styles.nextButton)} - data-testid="next-button" - /> -
+ +
+ +
)} -
+ ); }; -const getStyles = () => (theme: GrafanaTheme2) => ({ +const getStyles = (theme: GrafanaTheme2) => ({ + imageButton: css({ + textAlign: 'left', + }), + imagePreview: css({ + maxWidth: '100%', + maxHeight: '80vh', + objectFit: 'contain', + }), imageGrid: css({ display: 'grid', gridTemplateColumns: `repeat(auto-fill, minmax(200px, 1fr))`, - gap: '16px', + gap: theme.spacing(2), marginBottom: '20px', '& img': { @@ -165,49 +191,33 @@ const getStyles = () => (theme: GrafanaTheme2) => ({ boxShadow: theme.shadows.z1, }, '& p': { - margin: '4px 0', + margin: theme.spacing(0.5, 0), fontWeight: theme.typography.fontWeightMedium, color: theme.colors.text.primary, }, }), - fullScreenDiv: css({ + underlay: css({ position: 'fixed', zIndex: theme.zIndex.modalBackdrop, - top: 0, - right: 0, - bottom: 0, - left: 0, + inset: 0, backgroundColor: theme.components.overlay.background, + }), + overlay: css({ alignItems: 'center', - justifyContent: 'center', display: 'flex', - - '& img': { - maxWidth: '100%', - maxHeight: '80vh', - objectFit: 'contain', - }, + gap: theme.spacing(1), + height: 'fit-content', + marginBottom: 'auto', + marginTop: 'auto', + padding: theme.spacing(2), + position: 'fixed', + inset: 0, + zIndex: theme.zIndex.modal, }), closeButton: css({ - position: 'absolute', - top: '20px', - right: '20px', - backgroundColor: 'transparent', color: theme.colors.text.primary, - }), - navigationButton: css({ - position: 'absolute', - backgroundColor: 'transparent', - color: theme.colors.text.primary, - cursor: 'pointer', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }), - nextButton: css({ - right: '20px', - }), - previousButton: css({ - left: '20px', + position: 'fixed', + top: theme.spacing(2), + right: theme.spacing(2), }), }); diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/HeaderCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/HeaderCell.tsx index 5c53de65886..9537c502f09 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/HeaderCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/HeaderCell.tsx @@ -99,6 +99,7 @@ const HeaderCell: React.FC = ({ }, [column]); // eslint-disable-line react-hooks/exhaustive-deps return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions
+
-
setOutlineCollapsed(!outlineCollapsed)} - className={styles.outlineCollapseButton} + className={cx(clearButton, styles.outlineCollapseButton)} data-testid={selectors.components.PanelEditor.Outline.section} > Outline -
+ {!outlineCollapsed && (
diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx index 725714df3d8..aa31caefaf2 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx @@ -90,6 +90,8 @@ function DashboardOutlineNode({ > {elementInfo.isContainer && (
+ ); }; diff --git a/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx b/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx index b52127ad95b..75dd1fce2d0 100644 --- a/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx +++ b/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx @@ -76,6 +76,8 @@ function VariableList({ set }: { set: SceneVariableSet }) { return ( {variables.map((variable) => ( + // TODO fix keyboard a11y here + // eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events
onEditVariable(variable)}> {/* eslint-disable-next-line @grafana/no-untranslated-strings */} ${variable.state.name} diff --git a/public/app/features/logs/components/LogRowMessage.tsx b/public/app/features/logs/components/LogRowMessage.tsx index d3788b3c7cf..f05fc96decd 100644 --- a/public/app/features/logs/components/LogRowMessage.tsx +++ b/public/app/features/logs/components/LogRowMessage.tsx @@ -88,15 +88,16 @@ const Ellipsis = ({ toggle, diff }: EllipsisProps) => { return ( <> - + ); }; const getEllipsisStyles = (theme: GrafanaTheme2) => ({ showMore: css({ + backgroundColor: 'transparent', display: 'inline-flex', fontWeight: theme.typography.fontWeightMedium, fontSize: theme.typography.size.sm, diff --git a/public/app/features/logs/components/panel/LogLine.tsx b/public/app/features/logs/components/panel/LogLine.tsx index 53972f6ff7f..f39c97b2924 100644 --- a/public/app/features/logs/components/panel/LogLine.tsx +++ b/public/app/features/logs/components/panel/LogLine.tsx @@ -76,6 +76,7 @@ export const LogLine = ({ className={`${styles.logLine} ${variant ?? ''} ${pinned ? styles.pinnedLogLine : ''}`} ref={onOverflow ? logLineRef : undefined} onMouseEnter={handleMouseOver} + onFocus={handleMouseOver} >