fixup! (frontend) make components accessible to screen readers

This commit is contained in:
Cyril
2025-09-04 11:44:37 +02:00
parent 4456e8b1d6
commit ef9411d5e9
4 changed files with 162 additions and 125 deletions
@@ -0,0 +1,76 @@
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { BoxButton, Icon } from '@/components';
type ButtonAddChildDocProps = {
onCreateChild: (params: { parentId: string }) => void;
parentId: string;
title?: string | null;
};
export const ButtonAddChildDoc = ({
onCreateChild,
parentId,
title,
}: ButtonAddChildDocProps) => {
const { t } = useTranslation();
const preventDefaultAndStopPropagation = useCallback(
(e: React.MouseEvent | React.KeyboardEvent) => {
e.stopPropagation();
e.preventDefault();
},
[],
);
const isValidKeyEvent = useCallback((e: React.KeyboardEvent) => {
return e.key === 'Enter' || e.key === ' ';
}, []);
const handleClick = useCallback(
(e: React.MouseEvent) => {
preventDefaultAndStopPropagation(e);
void onCreateChild({ parentId });
},
[onCreateChild, parentId, preventDefaultAndStopPropagation],
);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (isValidKeyEvent(e)) {
preventDefaultAndStopPropagation(e);
void onCreateChild({ parentId });
}
},
[
onCreateChild,
parentId,
preventDefaultAndStopPropagation,
isValidKeyEvent,
],
);
return (
<BoxButton
as="button"
tabIndex={-1}
data-testid="add-child-doc"
onClick={handleClick}
onKeyDown={handleKeyDown}
color="primary"
aria-label={t('Add child document to {{title}}', {
title: title || t('Untitled document'),
})}
$hasTransition={false}
>
<Icon
variant="filled"
$variation="800"
$theme="primary"
iconName="add_box"
aria-hidden="true"
/>
</BoxButton>
);
};
@@ -0,0 +1,73 @@
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Icon } from '@/components';
type ButtonMoreOptionsProps = {
isOpen?: boolean;
onOpenChange?: (isOpen: boolean) => void;
title?: string | null;
className?: string;
};
export const ButtonMoreOptions = ({
isOpen,
onOpenChange,
title,
className = 'icon-button',
}: ButtonMoreOptionsProps) => {
const { t } = useTranslation();
const preventDefaultAndStopPropagation = useCallback(
(e: React.MouseEvent | React.KeyboardEvent) => {
e.stopPropagation();
e.preventDefault();
},
[],
);
const isValidKeyEvent = useCallback((e: React.KeyboardEvent) => {
return e.key === 'Enter' || e.key === ' ';
}, []);
const handleClick = useCallback(
(e: React.MouseEvent) => {
preventDefaultAndStopPropagation(e);
onOpenChange?.(!isOpen);
},
[isOpen, onOpenChange, preventDefaultAndStopPropagation],
);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (isValidKeyEvent(e)) {
preventDefaultAndStopPropagation(e);
onOpenChange?.(!isOpen);
}
},
[isOpen, onOpenChange, preventDefaultAndStopPropagation, isValidKeyEvent],
);
return (
<Icon
onClick={handleClick}
iconName="more_horiz"
variant="filled"
$theme="primary"
$variation="600"
className={className}
tabIndex={-1}
role="button"
aria-label={t('More options for {{title}}', {
title: title || t('Untitled document'),
})}
aria-haspopup="true"
aria-expanded={isOpen}
onKeyDown={handleKeyDown}
$css={css`
cursor: pointer;
`}
/>
);
};
@@ -8,9 +8,7 @@ import { useRouter } from 'next/router';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, BoxButton, Icon } from '@/components';
import { useDocTreeItemHandlers } from '@/features/docs/doc-tree/hooks/useDocTreeItemHandlers';
import { useDropdownFocusManagement } from '@/features/docs/doc-tree/hooks/useDropdownFocusManagement';
import { Box, Icon } from '@/components';
import {
Doc,
ModalRemoveDoc,
@@ -19,6 +17,9 @@ import {
useCreateChildDoc,
useDuplicateDoc,
} from '@/docs/doc-management';
import { ButtonAddChildDoc } from '@/features/docs/doc-tree/components/ButtonAddChildDoc';
import { ButtonMoreOptions } from '@/features/docs/doc-tree/components/ButtonMoreOptions';
import { useDropdownFocusManagement } from '@/features/docs/doc-tree/hooks/useDropdownFocusManagement';
import { useDetachDoc } from '../api/useDetach';
import MoveDocIcon from '../assets/doc-extract-bold.svg';
@@ -149,18 +150,6 @@ export const DocTreeItemActions = ({
}
};
const {
handleMoreOptionsClick,
handleMoreOptionsKeyDown,
handleAddChildClick,
handleAddChildKeyDown,
} = useDocTreeItemHandlers({
isOpen,
onOpenChange,
createChildDoc,
docId: doc.id,
});
useDropdownFocusManagement({
isOpen: isOpen || false,
docId: doc.id,
@@ -202,45 +191,18 @@ export const DocTreeItemActions = ({
isOpen={isOpen}
onOpenChange={onOpenChange}
>
<Icon
onClick={handleMoreOptionsClick}
iconName="more_horiz"
variant="filled"
$theme="primary"
$variation="600"
className="icon-button"
tabIndex={0}
role="button"
aria-label={
t('More options for') + ` ${doc.title || t('Untitled document')}`
}
aria-haspopup="true"
aria-expanded={isOpen}
onKeyDown={handleMoreOptionsKeyDown}
<ButtonMoreOptions
isOpen={isOpen}
onOpenChange={onOpenChange}
title={doc.title}
/>
</DropdownMenu>
{doc.abilities.children_create && (
<BoxButton
as="button"
tabIndex={0}
data-testid="add-child-doc"
onClick={handleAddChildClick}
onKeyDown={handleAddChildKeyDown}
color="primary"
aria-label={
t('Add child document to') +
` ${doc.title || t('Untitled document')}`
}
$hasTransition={false}
>
<Icon
variant="filled"
$variation="800"
$theme="primary"
iconName="add_box"
aria-hidden="true"
/>
</BoxButton>
<ButtonAddChildDoc
onCreateChild={createChildDoc}
parentId={doc.id}
title={doc.title}
/>
)}
</Box>
{deleteModal.isOpen && (
@@ -1,74 +0,0 @@
import { useCallback } from 'react';
interface UseDocTreeItemHandlersProps {
isOpen?: boolean;
onOpenChange?: (isOpen: boolean) => void;
createChildDoc: (params: { parentId: string }) => void;
docId: string;
}
export const useDocTreeItemHandlers = ({
isOpen,
onOpenChange,
createChildDoc,
docId,
}: UseDocTreeItemHandlersProps) => {
const preventDefaultAndStopPropagation = useCallback(
(e: React.MouseEvent | React.KeyboardEvent) => {
e.stopPropagation();
e.preventDefault();
},
[],
);
const isValidKeyEvent = useCallback((e: React.KeyboardEvent) => {
return e.key === 'Enter' || e.key === ' ';
}, []);
const handleMoreOptionsClick = useCallback(
(e: React.MouseEvent) => {
preventDefaultAndStopPropagation(e);
onOpenChange?.(!isOpen);
},
[isOpen, onOpenChange, preventDefaultAndStopPropagation],
);
const handleMoreOptionsKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (isValidKeyEvent(e)) {
preventDefaultAndStopPropagation(e);
onOpenChange?.(!isOpen);
}
},
[isOpen, onOpenChange, preventDefaultAndStopPropagation, isValidKeyEvent],
);
const handleAddChildClick = useCallback(
(e: React.MouseEvent) => {
preventDefaultAndStopPropagation(e);
void createChildDoc({
parentId: docId,
});
},
[createChildDoc, docId, preventDefaultAndStopPropagation],
);
const handleAddChildKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (isValidKeyEvent(e)) {
preventDefaultAndStopPropagation(e);
void createChildDoc({
parentId: docId,
});
}
},
[createChildDoc, docId, preventDefaultAndStopPropagation, isValidKeyEvent],
);
return {
handleMoreOptionsClick,
handleMoreOptionsKeyDown,
handleAddChildClick,
handleAddChildKeyDown,
};
};