♿️(a11y) improve accessibility
fix global accessibility
This commit is contained in:
@@ -45,6 +45,7 @@ and this project adheres to
|
||||
|
||||
### Added
|
||||
|
||||
- ♿️(a11y) improve accessibility #135
|
||||
- 🌐(i18n) add dutch language #117
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -28,10 +28,16 @@ const StyledButton = styled(Button)<StyledButtonProps>`
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
transition: all 0.2s ease-in-out;
|
||||
transition: background 0.2s ease-in-out;
|
||||
font-weight: 500;
|
||||
font-size: 0.938rem;
|
||||
padding: 0;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid #3e5de7;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
${({ $css }) => $css};
|
||||
`;
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ export const DropdownMenu = ({
|
||||
data-testid={option.testId}
|
||||
$direction="row"
|
||||
disabled={isDisabled}
|
||||
tabIndex={isDisabled ? -1 : 0}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -134,9 +135,15 @@ export const DropdownMenu = ({
|
||||
cursor: ${isDisabled ? 'not-allowed' : 'pointer'};
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--c--theme--colors--greyscale-050);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid #3e5de7;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Box
|
||||
|
||||
@@ -15,6 +15,7 @@ export const Icon = ({
|
||||
return (
|
||||
<Text
|
||||
{...textProps}
|
||||
aria-hidden="true"
|
||||
className={clsx('--docs--icon-bg', textProps.className, {
|
||||
'material-symbols': variant === 'filled',
|
||||
'material-symbols-outlined': variant === 'outlined',
|
||||
@@ -33,6 +34,7 @@ export const IconOptions = ({ isHorizontal, ...props }: IconOptionsProps) => {
|
||||
return (
|
||||
<Icon
|
||||
{...props}
|
||||
aria-hidden="true"
|
||||
iconName={isHorizontal ? 'more_horiz' : 'more_vert'}
|
||||
$css={css`
|
||||
user-select: none;
|
||||
|
||||
@@ -69,6 +69,7 @@ export const ToastProvider = ({ children }: ToastProviderProps) => {
|
||||
{typeof window !== 'undefined' &&
|
||||
createPortal(
|
||||
<Box
|
||||
aria-live="polite"
|
||||
$css={`
|
||||
position: fixed;
|
||||
top: 8px;
|
||||
|
||||
@@ -603,6 +603,7 @@ export const Chat = ({
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
margin-bottom: ${isLastAssistantMessageInConversation ? '30px' : '0px'};
|
||||
color: var(--c--theme--colors--greyscale-850);
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
@@ -643,6 +644,11 @@ export const Chat = ({
|
||||
className="mainContent-chat"
|
||||
$padding={{ all: 'xxs' }}
|
||||
>
|
||||
<p className="sr-only">
|
||||
{message.role === 'user'
|
||||
? t('You said: ')
|
||||
: t('Assistant IA replied: ')}
|
||||
</p>
|
||||
<MarkdownHooks
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[
|
||||
@@ -659,6 +665,7 @@ export const Chat = ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
p: ({ node, ...props }) => (
|
||||
<Text
|
||||
as="p"
|
||||
$css="display: block"
|
||||
$theme="greyscale"
|
||||
$variation="850"
|
||||
@@ -909,7 +916,6 @@ export const Chat = ({
|
||||
{status === 'error' && (
|
||||
<Box
|
||||
$direction={isMobile ? 'column' : 'row'}
|
||||
$align="center"
|
||||
$gap="6px"
|
||||
$width="100%"
|
||||
$maxWidth="750px"
|
||||
|
||||
@@ -411,6 +411,7 @@ export const InputChat = ({
|
||||
)}
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
aria-label={t('Enter your message or a question')}
|
||||
value={input ?? ''}
|
||||
name="inputchat-textarea"
|
||||
onChange={(e) => {
|
||||
|
||||
+4
-2
@@ -6,13 +6,15 @@ import { useChatPreferencesStore } from '@/features/chat/stores/useChatPreferenc
|
||||
|
||||
export const ButtonToggleLeftPanel = () => {
|
||||
const { t } = useTranslation();
|
||||
const { togglePanel } = useChatPreferencesStore();
|
||||
const { isPanelOpen, togglePanel } = useChatPreferencesStore();
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="medium"
|
||||
onClick={() => togglePanel()}
|
||||
aria-label={t('Open the header menu')}
|
||||
aria-label={
|
||||
isPanelOpen ? t('Close the left panel') : t('Open the left panel')
|
||||
}
|
||||
color="primary-text"
|
||||
icon={<LeftPanelIcon />}
|
||||
className="--docs--button-toggle-panel"
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ export const ButtonTogglePanel = () => {
|
||||
<Button
|
||||
size="medium"
|
||||
onClick={() => togglePanel()}
|
||||
aria-label={t('Open the header menu')}
|
||||
aria-label={isPanelOpen ? t('Close the menu') : t('Open the menu')}
|
||||
color="primary-text"
|
||||
icon={<Icon $theme="primary" iconName={isPanelOpen ? 'close' : 'menu'} />}
|
||||
className="mobile-no-focus"
|
||||
|
||||
@@ -41,6 +41,7 @@ export const LanguagePicker = () => {
|
||||
<DropdownMenu
|
||||
options={optionsPicker}
|
||||
showArrow={isDesktop}
|
||||
label={t('Language')}
|
||||
buttonCss={css`
|
||||
&:hover {
|
||||
background-color: var(
|
||||
@@ -61,7 +62,6 @@ export const LanguagePicker = () => {
|
||||
<Text
|
||||
$theme="primary"
|
||||
$size="md"
|
||||
aria-label={t('Language')}
|
||||
$direction="row"
|
||||
$gap="0.5rem"
|
||||
className="--docs--language-picker-text"
|
||||
|
||||
+32
-40
@@ -2,7 +2,7 @@ import { Button as _Button, useModal } from '@openfun/cunningham-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, DropdownMenu, DropdownMenuOption, Icon } from '@/components';
|
||||
import { DropdownMenu, DropdownMenuOption, Icon } from '@/components';
|
||||
import { ChatConversation } from '@/features/chat/types';
|
||||
|
||||
import { ModalRemoveConversation } from './ModalRemoveConversation';
|
||||
@@ -30,47 +30,39 @@ export const ConversationItemActions = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu options={options}>
|
||||
<Box
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={t('Conversation actions')}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
// Le DropdownMenu gère l'ouverture
|
||||
}
|
||||
}}
|
||||
<DropdownMenu
|
||||
options={options}
|
||||
label={t('Actions list for conversation {{title}}', {
|
||||
title: conversation.title || t('Untitled conversation'),
|
||||
})}
|
||||
buttonCss={css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
background-color: #e1e3e7 !important;
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid #3e5de7;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Icon
|
||||
data-testid={`conversation-item-actions-button-${conversation.id}`}
|
||||
iconName="more_horiz"
|
||||
$theme="primary"
|
||||
$variation="600"
|
||||
$css={css`
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #e1e3e7 !important;
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid #3e5de7;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
font-size: 1rem;
|
||||
color: var(--c--theme--colors--primary-text-text);
|
||||
pointer-events: none;
|
||||
`}
|
||||
>
|
||||
<Icon
|
||||
data-testid={`conversation-item-actions-button-${conversation.id}`}
|
||||
iconName="more_horiz"
|
||||
$theme="primary"
|
||||
$variation="600"
|
||||
$css={css`
|
||||
font-size: 1rem;
|
||||
color: var(--c--theme--colors--primary-text-text);
|
||||
pointer-events: none;
|
||||
`}
|
||||
/>
|
||||
</Box>
|
||||
/>
|
||||
</DropdownMenu>
|
||||
|
||||
{deleteModal.isOpen && (
|
||||
|
||||
+4
-1
@@ -44,12 +44,15 @@ export const LeftPanelConversationItem = ({
|
||||
background-color: transparent
|
||||
transition: all 0.3s cubic-bezier(1, 0, 0, 1);
|
||||
}
|
||||
&:hover, &:focus {
|
||||
&:hover, &:focus, &:focus-within {
|
||||
background-color: #ebedf1;
|
||||
.pinned-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.pinned-actions:focus-within {
|
||||
opacity: 1;
|
||||
}
|
||||
`}
|
||||
className="--docs--left-panel-favorite-item"
|
||||
>
|
||||
|
||||
@@ -38,6 +38,7 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
|
||||
>
|
||||
<Box $direction="row" $gap="2px">
|
||||
<Button
|
||||
aria-label={t('New chat')}
|
||||
color="primary"
|
||||
icon={<NewChatIcon />}
|
||||
onClick={goToHome}
|
||||
|
||||
+7
-17
@@ -3,7 +3,7 @@ import { t } from 'i18next';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import { Box, Text, TextErrors, useToast } from '@/components';
|
||||
import { Box, Text, useToast } from '@/components';
|
||||
import { useRemoveConversation } from '@/features/chat/api/useRemoveConversation';
|
||||
import { ChatConversation } from '@/features/chat/types';
|
||||
|
||||
@@ -20,11 +20,7 @@ export const ModalRemoveConversation = ({
|
||||
const { push } = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const {
|
||||
mutate: removeDoc,
|
||||
isError,
|
||||
error,
|
||||
} = useRemoveConversation({
|
||||
const { mutate: removeDoc } = useRemoveConversation({
|
||||
onSuccess: () => {
|
||||
showToast(
|
||||
'success',
|
||||
@@ -45,6 +41,7 @@ export const ModalRemoveConversation = ({
|
||||
isOpen
|
||||
closeOnClickOutside
|
||||
onClose={() => onClose()}
|
||||
aria-label={t('Content modal to delete conversation')}
|
||||
rightActions={
|
||||
<>
|
||||
<Button
|
||||
@@ -82,17 +79,10 @@ export const ModalRemoveConversation = ({
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
aria-label={t('Content modal to delete conversation')}
|
||||
className="--docs--modal-remove-doc"
|
||||
>
|
||||
{!isError && (
|
||||
<Text $size="sm" $variation="600">
|
||||
{t('Are you sure you want to delete this conversation ?')}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{isError && <TextErrors causes={error.cause} />}
|
||||
<Box className="--converstions--modal-remove-chat">
|
||||
<Text $size="sm" $variation="600">
|
||||
{t('Are you sure you want to delete this conversation ?')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,13 @@ body {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button:focus-visible,
|
||||
a:focus-visible,
|
||||
[role='button']:focus-visible {
|
||||
outline: 2px solid #3e5de7;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
main ::-webkit-scrollbar,
|
||||
.ReactModalPortal ::-webkit-scrollbar {
|
||||
width: 20px;
|
||||
@@ -153,6 +160,10 @@ ul a:hover {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.mainContent-chat p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mainContent-chat a {
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
@@ -184,7 +195,7 @@ ul a:hover {
|
||||
border-color: #b7d7ff !important;
|
||||
}
|
||||
|
||||
.research-web-button:focus {
|
||||
.research-web-button:focus:not(:focus-visible) {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
@@ -237,3 +248,15 @@ figure[data-rehype-pretty-code-figure] > pre {
|
||||
figure[data-rehype-pretty-code-figure] > pre > code {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ test.describe('Header mobile', () => {
|
||||
|
||||
const header = page.locator('header').first();
|
||||
|
||||
await expect(header.getByLabel('Open the header menu')).toBeVisible();
|
||||
await expect(header.getByLabel('Open the menu')).toBeVisible();
|
||||
await expect(
|
||||
header.getByRole('link', { name: 'Assistant Logo' }),
|
||||
).toBeVisible();
|
||||
|
||||
Reference in New Issue
Block a user