From f08ad95c59f854d30a21778756e48936314666d5 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Thu, 5 Oct 2023 09:14:26 +0000 Subject: [PATCH] BrowseDashboards: Improve screen reader announcements (#75970) * use aria-labelledby to describe the whole row * change * fix typo * i18n --- .../components/CheckboxCell.tsx | 2 ++ .../components/CheckboxHeaderCell.tsx | 2 ++ .../components/DashboardsTree.tsx | 14 +++++++---- .../browse-dashboards/components/NameCell.tsx | 24 ++++++++++++++----- .../browse-dashboards/components/utils.ts | 5 ++++ .../app/features/browse-dashboards/types.ts | 1 + public/locales/de-DE/grafana.json | 4 ++++ public/locales/en-US/grafana.json | 4 ++++ public/locales/es-ES/grafana.json | 4 ++++ public/locales/fr-FR/grafana.json | 4 ++++ public/locales/pseudo-LOCALE/grafana.json | 4 ++++ public/locales/zh-Hans/grafana.json | 4 ++++ 12 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 public/app/features/browse-dashboards/components/utils.ts diff --git a/public/app/features/browse-dashboards/components/CheckboxCell.tsx b/public/app/features/browse-dashboards/components/CheckboxCell.tsx index 3338f0436e5..83f84c9e039 100644 --- a/public/app/features/browse-dashboards/components/CheckboxCell.tsx +++ b/public/app/features/browse-dashboards/components/CheckboxCell.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Checkbox, useStyles2 } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; import { DashboardsTreeCellProps, SelectionState } from '../types'; @@ -32,6 +33,7 @@ export default function CheckboxCell({ return ( onItemSelectionChange?.(item, ev.currentTarget.checked)} diff --git a/public/app/features/browse-dashboards/components/CheckboxHeaderCell.tsx b/public/app/features/browse-dashboards/components/CheckboxHeaderCell.tsx index b476b803f3d..d31dbdce500 100644 --- a/public/app/features/browse-dashboards/components/CheckboxHeaderCell.tsx +++ b/public/app/features/browse-dashboards/components/CheckboxHeaderCell.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Checkbox } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; import { DashboardTreeHeaderProps, SelectionState } from '../types'; @@ -11,6 +12,7 @@ export default function CheckboxHeaderCell({ isSelected, onAllSelectionChange }: { if (state === SelectionState.Mixed) { // Ensure clicking an indeterminate checkbox always clears the selection diff --git a/public/app/features/browse-dashboards/components/DashboardsTree.tsx b/public/app/features/browse-dashboards/components/DashboardsTree.tsx index ccf37dcd1f6..be3b5bee1c7 100644 --- a/public/app/features/browse-dashboards/components/DashboardsTree.tsx +++ b/public/app/features/browse-dashboards/components/DashboardsTree.tsx @@ -1,5 +1,5 @@ import { css, cx } from '@emotion/css'; -import React, { useCallback, useEffect, useMemo, useRef } from 'react'; +import React, { useCallback, useEffect, useId, useMemo, useRef } from 'react'; import { TableInstance, useTable } from 'react-table'; import { FixedSizeList as List } from 'react-window'; import InfiniteLoader from 'react-window-infinite-loader'; @@ -17,6 +17,7 @@ import CheckboxHeaderCell from './CheckboxHeaderCell'; import { NameCell } from './NameCell'; import { TagsCell } from './TagsCell'; import { useCustomFlexLayout } from './customFlexTableLayout'; +import { makeRowID } from './utils'; interface DashboardsTreeProps { items: DashboardsTreeItem[]; @@ -47,6 +48,8 @@ export function DashboardsTree({ requestLoadMore, canSelect = false, }: DashboardsTreeProps) { + const treeID = useId(); + const infiniteLoaderRef = useRef(null); const styles = useStyles2(getStyles); @@ -98,10 +101,11 @@ export function DashboardsTree({ isSelected, onAllSelectionChange, onItemSelectionChange, + treeID, }), // we need this to rerender if items changes // eslint-disable-next-line react-hooks/exhaustive-deps - [table, isSelected, onAllSelectionChange, onItemSelectionChange, items] + [table, isSelected, onAllSelectionChange, onItemSelectionChange, items, treeID] ); const handleIsItemLoaded = useCallback( @@ -175,12 +179,13 @@ interface VirtualListRowProps { isSelected: DashboardsTreeCellProps['isSelected']; onAllSelectionChange: DashboardsTreeCellProps['onAllSelectionChange']; onItemSelectionChange: DashboardsTreeCellProps['onItemSelectionChange']; + treeID: string; }; } function VirtualListRow({ index, style, data }: VirtualListRowProps) { const styles = useStyles2(getStyles); - const { table, isSelected, onItemSelectionChange } = data; + const { table, isSelected, onItemSelectionChange, treeID } = data; const { rows, prepareRow } = table; const row = rows[index]; @@ -190,6 +195,7 @@ function VirtualListRow({ index, style, data }: VirtualListRowProps) {
- {cell.render('Cell', { isSelected, onItemSelectionChange })} + {cell.render('Cell', { isSelected, onItemSelectionChange, treeID })}
); })} diff --git a/public/app/features/browse-dashboards/components/NameCell.tsx b/public/app/features/browse-dashboards/components/NameCell.tsx index 69ee47be2f2..11bbd5af231 100644 --- a/public/app/features/browse-dashboards/components/NameCell.tsx +++ b/public/app/features/browse-dashboards/components/NameCell.tsx @@ -1,26 +1,28 @@ import { css } from '@emotion/css'; import React from 'react'; import Skeleton from 'react-loading-skeleton'; -import { CellProps } from 'react-table'; import { GrafanaTheme2 } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; import { Icon, IconButton, Link, Spinner, useStyles2, Text } from '@grafana/ui'; import { getSvgSize } from '@grafana/ui/src/components/Icon/utils'; +import { t } from 'app/core/internationalization'; import { getIconForKind } from 'app/features/search/service/utils'; import { Indent } from '../../../core/components/Indent/Indent'; import { useChildrenByParentUIDState } from '../state'; -import { DashboardsTreeItem } from '../types'; +import { DashboardsTreeCellProps } from '../types'; + +import { makeRowID } from './utils'; const CHEVRON_SIZE = 'md'; const ICON_SIZE = 'sm'; -type NameCellProps = CellProps & { +type NameCellProps = DashboardsTreeCellProps & { onFolderClick: (uid: string, newOpenState: boolean) => void; }; -export function NameCell({ row: { original: data }, onFolderClick }: NameCellProps) { +export function NameCell({ row: { original: data }, onFolderClick, treeID }: NameCellProps) { const styles = useStyles2(getStyles); const { item, level, isOpen } = data; const childrenByParentUID = useChildrenByParentUIDState(); @@ -69,14 +71,24 @@ export function NameCell({ row: { original: data }, onFolderClick }: NameCellPro onFolderClick(item.uid, !isOpen); }} name={isOpen ? 'angle-down' : 'angle-right'} - aria-label={isOpen ? `Collapse folder ${item.title}` : `Expand folder ${item.title}`} + aria-label={ + isOpen + ? t('browse-dashboards.dashboards-tree.collapse-folder-button', 'Collapse folder {{title}}', { + title: item.title, + }) + : t('browse-dashboards.dashboards-tree.expand-folder-button', 'Expand folder {{title}}', { + title: item.title, + }) + } /> ) : ( )} +
{isLoading ? : } - + + {item.url ? ( { diff --git a/public/app/features/browse-dashboards/components/utils.ts b/public/app/features/browse-dashboards/components/utils.ts new file mode 100644 index 00000000000..56613b82e14 --- /dev/null +++ b/public/app/features/browse-dashboards/components/utils.ts @@ -0,0 +1,5 @@ +import { DashboardViewItemWithUIItems } from '../types'; + +export function makeRowID(baseId: string, item: DashboardViewItemWithUIItems) { + return baseId + item.uid; +} diff --git a/public/app/features/browse-dashboards/types.ts b/public/app/features/browse-dashboards/types.ts index b4827710eb0..8b0c0dc1ed5 100644 --- a/public/app/features/browse-dashboards/types.ts +++ b/public/app/features/browse-dashboards/types.ts @@ -48,6 +48,7 @@ interface RendererUserProps { isSelected?: (kind: DashboardViewItem | '$all') => SelectionState; onAllSelectionChange?: (newState: boolean) => void; onItemSelectionChange?: (item: DashboardViewItem, newState: boolean) => void; + treeID?: string; } export type DashboardsTreeColumn = Column; diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 9983480cfb8..2769f2a1910 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -55,7 +55,11 @@ "total__other": "" }, "dashboards-tree": { + "collapse-folder-button": "", + "expand-folder-button": "", "name-column": "", + "select-all-header-checkbox": "", + "select-checkbox": "", "tags-column": "" }, "folder-actions-button": { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index d7321924793..bec9c51efd4 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -55,7 +55,11 @@ "total__other": "{{count}} items" }, "dashboards-tree": { + "collapse-folder-button": "Collapse folder {{title}}", + "expand-folder-button": "Expand folder {{title}}", "name-column": "Name", + "select-all-header-checkbox": "Select all", + "select-checkbox": "Select", "tags-column": "Tags" }, "folder-actions-button": { diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 2b3213c2267..3e3b900771f 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -60,7 +60,11 @@ "total__other": "" }, "dashboards-tree": { + "collapse-folder-button": "", + "expand-folder-button": "", "name-column": "", + "select-all-header-checkbox": "", + "select-checkbox": "", "tags-column": "" }, "folder-actions-button": { diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 1e46a7f9328..bbd91cf05b7 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -60,7 +60,11 @@ "total__other": "" }, "dashboards-tree": { + "collapse-folder-button": "", + "expand-folder-button": "", "name-column": "", + "select-all-header-checkbox": "", + "select-checkbox": "", "tags-column": "" }, "folder-actions-button": { diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 567fd3a94bc..de9e4b4abb7 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -55,7 +55,11 @@ "total__other": "{{count}} įŧęmş" }, "dashboards-tree": { + "collapse-folder-button": "Cőľľäpşę ƒőľđęř {{title}}", + "expand-folder-button": "Ēχpäʼnđ ƒőľđęř {{title}}", "name-column": "Ńämę", + "select-all-header-checkbox": "Ŝęľęčŧ äľľ", + "select-checkbox": "Ŝęľęčŧ", "tags-column": "Ŧäģş" }, "folder-actions-button": { diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 3935c235763..62dd47f8d02 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -50,7 +50,11 @@ "total__other": "" }, "dashboards-tree": { + "collapse-folder-button": "", + "expand-folder-button": "", "name-column": "", + "select-all-header-checkbox": "", + "select-checkbox": "", "tags-column": "" }, "folder-actions-button": {