♿️(frontend) improve version history list accessibility
Dynamic aria-label per version, aria-pressed + live region
This commit is contained in:
@@ -9,6 +9,7 @@ and this project adheres to
|
||||
### Changed
|
||||
|
||||
- 💫(frontend) fix the help button to the bottom in tree #2073
|
||||
- ♿️(frontend) improve version history list accessibility #2033
|
||||
|
||||
## [v4.8.2] - 2026-03-19
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ test.describe('Doc Version', () => {
|
||||
await expect(page.getByText('History', { exact: true })).toBeVisible();
|
||||
|
||||
const modal = page.getByRole('dialog', { name: 'Version history' });
|
||||
const panel = modal.getByLabel('version list');
|
||||
const panel = modal.getByLabel('Version list');
|
||||
await expect(panel).toBeVisible();
|
||||
await expect(modal.getByText('No versions')).toBeVisible();
|
||||
|
||||
@@ -155,11 +155,11 @@ test.describe('Doc Version', () => {
|
||||
await getMenuItem(page, 'Version history').click();
|
||||
|
||||
const modal = page.getByRole('dialog', { name: 'Version history' });
|
||||
const panel = modal.getByLabel('version list');
|
||||
const panel = modal.getByLabel('Version list');
|
||||
await expect(panel).toBeVisible();
|
||||
|
||||
await expect(page.getByText('History', { exact: true })).toBeVisible();
|
||||
await panel.getByRole('button', { name: 'version item' }).click();
|
||||
await panel.locator('.version-item').first().click();
|
||||
|
||||
await expect(modal.getByText('World')).toBeHidden();
|
||||
|
||||
|
||||
+39
-23
@@ -1,7 +1,8 @@
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, Text } from '@/components';
|
||||
import { Box, BoxButton, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc } from '@/docs/doc-management';
|
||||
|
||||
@@ -18,54 +19,69 @@ const ModalConfirmationVersion = dynamic(
|
||||
interface VersionItemProps {
|
||||
docId: Doc['id'];
|
||||
text: string;
|
||||
|
||||
versionId?: Versions['version_id'];
|
||||
isActive: boolean;
|
||||
onSelect?: () => void;
|
||||
}
|
||||
|
||||
export const VersionItem = ({
|
||||
docId,
|
||||
versionId,
|
||||
text,
|
||||
|
||||
isActive,
|
||||
onSelect,
|
||||
}: VersionItemProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { colorsTokens, spacingsTokens } = useCunninghamTheme();
|
||||
|
||||
const [isModalVersionOpen, setIsModalVersionOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
<BoxButton
|
||||
aria-label={t('Restore version of {{date}}', { date: text })}
|
||||
aria-pressed={isActive}
|
||||
$width="100%"
|
||||
as="li"
|
||||
$background={isActive ? colorsTokens['gray-100'] : 'transparent'}
|
||||
$radius={spacingsTokens['3xs']}
|
||||
$css={`
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: ${colorsTokens['gray-100']};
|
||||
&:focus-visible {
|
||||
background: var(--c--globals--colors--gray-100);
|
||||
border-radius: var(--c--globals--spacings--st);
|
||||
}
|
||||
`}
|
||||
$hasTransition
|
||||
$minWidth="13rem"
|
||||
className="--docs--version-item"
|
||||
className="version-item"
|
||||
onClick={onSelect}
|
||||
>
|
||||
<Box
|
||||
$padding={{ vertical: '0.7rem', horizontal: 'small' }}
|
||||
$align="center"
|
||||
$direction="row"
|
||||
$justify="space-between"
|
||||
$width="100%"
|
||||
as="span"
|
||||
$background={isActive ? colorsTokens['gray-100'] : 'transparent'}
|
||||
$radius={spacingsTokens['3xs']}
|
||||
$css={`
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: ${colorsTokens['gray-100']};
|
||||
}
|
||||
`}
|
||||
$hasTransition
|
||||
$minWidth="13rem"
|
||||
className="--docs--version-item"
|
||||
>
|
||||
<Box $direction="row" $gap="0.5rem" $align="center">
|
||||
<Text $weight="bold" $size="sm">
|
||||
{text}
|
||||
</Text>
|
||||
<Box
|
||||
$padding={{ vertical: '0.7rem', horizontal: 'small' }}
|
||||
$align="center"
|
||||
$direction="row"
|
||||
$justify="space-between"
|
||||
$width="100%"
|
||||
>
|
||||
<Box $direction="row" $gap="0.5rem" $align="center">
|
||||
<Text $weight="bold" $size="sm">
|
||||
{text}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</BoxButton>
|
||||
{isModalVersionOpen && versionId && (
|
||||
<ModalConfirmationVersion
|
||||
onClose={() => setIsModalVersionOpen(false)}
|
||||
|
||||
+32
-26
@@ -3,14 +3,7 @@ import { DateTime } from 'luxon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { APIError } from '@/api';
|
||||
import {
|
||||
Box,
|
||||
BoxButton,
|
||||
Icon,
|
||||
InfiniteScroll,
|
||||
Text,
|
||||
TextErrors,
|
||||
} from '@/components';
|
||||
import { Box, Icon, InfiniteScroll, Text, TextErrors } from '@/components';
|
||||
import { Doc } from '@/docs/doc-management';
|
||||
import { useDate } from '@/hooks';
|
||||
|
||||
@@ -49,23 +42,24 @@ const VersionListState = ({
|
||||
|
||||
return (
|
||||
<Box $gap="10px" $padding="xs">
|
||||
{versions?.map((version) => (
|
||||
<BoxButton
|
||||
aria-label="version item"
|
||||
className="version-item"
|
||||
key={version.version_id}
|
||||
onClick={() => {
|
||||
onSelectVersion?.(version.version_id);
|
||||
}}
|
||||
>
|
||||
<VersionItem
|
||||
versionId={version.version_id}
|
||||
text={formatDate(version.last_modified, DateTime.DATETIME_MED)}
|
||||
docId={doc.id}
|
||||
isActive={version.version_id === selectedVersionId}
|
||||
/>
|
||||
</BoxButton>
|
||||
))}
|
||||
{versions?.map((version) => {
|
||||
const formattedDate = formatDate(
|
||||
version.last_modified,
|
||||
DateTime.DATETIME_MED,
|
||||
);
|
||||
const isSelected = version.version_id === selectedVersionId;
|
||||
return (
|
||||
<Box as="li" key={version.version_id} $css="list-style: none;">
|
||||
<VersionItem
|
||||
versionId={version.version_id}
|
||||
text={formattedDate}
|
||||
docId={doc.id}
|
||||
isActive={isSelected}
|
||||
onSelect={() => onSelectVersion?.(version.version_id)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
{error && (
|
||||
<Box
|
||||
$justify="center"
|
||||
@@ -97,6 +91,7 @@ export const VersionList = ({
|
||||
selectedVersionId,
|
||||
}: VersionListProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { formatDate } = useDate();
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -112,6 +107,12 @@ export const VersionList = ({
|
||||
const versions = data?.pages.reduce((acc, page) => {
|
||||
return acc.concat(page.versions);
|
||||
}, [] as Versions[]);
|
||||
const selectedVersion = versions?.find(
|
||||
(version) => version.version_id === selectedVersionId,
|
||||
);
|
||||
const selectedVersionDate = selectedVersion
|
||||
? formatDate(selectedVersion.last_modified, DateTime.DATETIME_MED)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -127,7 +128,7 @@ export const VersionList = ({
|
||||
as="ul"
|
||||
$padding="none"
|
||||
$margin={{ top: 'none' }}
|
||||
role="listbox"
|
||||
role="list"
|
||||
>
|
||||
{versions?.length === 0 && (
|
||||
<Box $align="center" $margin="large">
|
||||
@@ -145,6 +146,11 @@ export const VersionList = ({
|
||||
selectedVersionId={selectedVersionId}
|
||||
/>
|
||||
</InfiniteScroll>
|
||||
<Text className="sr-only" aria-live="polite">
|
||||
{selectedVersionDate
|
||||
? t('Selected version {{date}}', { date: selectedVersionDate })
|
||||
: ''}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user