️(frontend) shortcuts table: semantic structure and kbd badge

caption, th scope, <kbd> for keys, no Tab stops on rows
This commit is contained in:
Cyril
2026-03-02 11:02:36 +01:00
parent f1ddd7fa2f
commit 8362ac0e24
6 changed files with 36 additions and 13 deletions

View File

@@ -41,6 +41,7 @@ and this project adheres to
- ♿️(frontend) improve accessibility of the IntroSlider carousel #1026 - ♿️(frontend) improve accessibility of the IntroSlider carousel #1026
- ♿️(frontend) add skip link component for keyboard navigation #1019 - ♿️(frontend) add skip link component for keyboard navigation #1019
- ♿️(frontend) announce mic/camera state to SR on shortcut toggle #1052 - ♿️(frontend) announce mic/camera state to SR on shortcut toggle #1052
- ✨(frontend) add Ctrl+Shift+/ to open shortcuts settings #1050
### Fixed ### Fixed

View File

@@ -32,7 +32,7 @@ import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
import { useConnectionObserver } from '../hooks/useConnectionObserver' import { useConnectionObserver } from '../hooks/useConnectionObserver'
import { useNoiseReduction } from '../hooks/useNoiseReduction' import { useNoiseReduction } from '../hooks/useNoiseReduction'
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut' import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
import { settingsStore } from '@/stores/settings' import { useSettingsDialog } from '@/features/settings'
import { SettingsDialogExtendedKey } from '@/features/settings/type' import { SettingsDialogExtendedKey } from '@/features/settings/type'
import { useVideoResolutionSubscription } from '../hooks/useVideoResolutionSubscription' import { useVideoResolutionSubscription } from '../hooks/useVideoResolutionSubscription'
import { SettingsDialogProvider } from '@/features/settings/components/SettingsDialogProvider' import { SettingsDialogProvider } from '@/features/settings/components/SettingsDialogProvider'
@@ -100,6 +100,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
const { t: tRooms } = useTranslation('rooms') const { t: tRooms } = useTranslation('rooms')
const room = useRoomContext() const room = useRoomContext()
const announce = useScreenReaderAnnounce() const announce = useScreenReaderAnnounce()
const { toggleSettingsDialog } = useSettingsDialog()
const getAnnouncementName = useCallback( const getAnnouncementName = useCallback(
(participant?: Participant | null) => { (participant?: Participant | null) => {
@@ -117,9 +118,8 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
useRegisterKeyboardShortcut({ useRegisterKeyboardShortcut({
id: 'open-shortcuts', id: 'open-shortcuts',
handler: useCallback(() => { handler: useCallback(() => {
settingsStore.defaultSelectedTab = SettingsDialogExtendedKey.SHORTCUTS toggleSettingsDialog(SettingsDialogExtendedKey.SHORTCUTS)
settingsStore.areSettingsOpen = true }, [toggleSettingsDialog]),
}, []),
}) })
const tracks = useTracks( const tracks = useTracks(

View File

@@ -3,15 +3,20 @@ import { ShortcutRow } from '@/features/shortcuts/components/ShortcutRow'
import { css } from '@/styled-system/css' import { css } from '@/styled-system/css'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { TabPanel, type TabPanelProps } from '@/primitives/Tabs' import { TabPanel, type TabPanelProps } from '@/primitives/Tabs'
import { H } from '@/primitives'
const tableStyle = css({ const tableStyle = css({
width: '100%', width: '100%',
borderCollapse: 'collapse', borderCollapse: 'collapse',
overflowY: 'auto', overflowY: 'auto',
'& caption': {
fontWeight: 'bold',
marginBottom: '0.75rem',
textAlign: 'left',
},
'& th, & td': { '& th, & td': {
padding: '0.65rem 0', padding: '0.65rem 0',
textAlign: 'left', textAlign: 'left',
fontWeight: 'normal',
}, },
'& tbody tr': { '& tbody tr': {
borderBottom: '1px solid rgba(255,255,255,0.08)', borderBottom: '1px solid rgba(255,255,255,0.08)',
@@ -29,12 +34,11 @@ export const ShortcutTab = ({ id }: Pick<TabPanelProps, 'id'>) => {
className={css({ className={css({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: '0.75rem',
})} })}
> >
<H lvl={2}>{t('shortcuts.listLabel')}</H>
<table className={tableStyle}> <table className={tableStyle}>
<thead className="sr-only"> <caption>{t('shortcuts.listLabel')}</caption>
<thead>
<tr> <tr>
<th scope="col">{t('shortcuts.columnAction')}</th> <th scope="col">{t('shortcuts.columnAction')}</th>
<th scope="col">{t('shortcuts.columnShortcut')}</th> <th scope="col">{t('shortcuts.columnShortcut')}</th>

View File

@@ -14,7 +14,25 @@ export const useSettingsDialog = () => {
settingsStore.areSettingsOpen = true settingsStore.areSettingsOpen = true
} }
const closeSettingsDialog = () => {
settingsStore.areSettingsOpen = false
}
const toggleSettingsDialog = (
defaultSelectedTab?: SettingsDialogExtendedKey
) => {
if (areSettingsOpen) {
closeSettingsDialog()
} else {
if (defaultSelectedTab)
settingsStore.defaultSelectedTab = defaultSelectedTab
settingsStore.areSettingsOpen = true
}
}
return { return {
openSettingsDialog, openSettingsDialog,
closeSettingsDialog,
toggleSettingsDialog,
} }
} }

View File

@@ -25,9 +25,9 @@ export const ShortcutBadge: React.FC<ShortcutBadgeProps> = ({
}) => { }) => {
return ( return (
<> <>
<div className={cx(badgeStyle, className)} aria-hidden="true"> <kbd className={cx(badgeStyle, className)} aria-hidden="true">
<span>{visualLabel}</span> {visualLabel}
</div> </kbd>
{srLabel && <span className="sr-only">{srLabel}</span>} {srLabel && <span className="sr-only">{srLabel}</span>}
</> </>
) )

View File

@@ -31,9 +31,9 @@ export const ShortcutRow: React.FC<ShortcutRowProps> = ({ descriptor }) => {
return ( return (
<tr> <tr>
<td className={text({ variant: 'body' })}> <th scope="row" className={text({ variant: 'body' })}>
{t(`actions.${descriptor.id}`)} {t(`actions.${descriptor.id}`)}
</td> </th>
<td className={shortcutCellStyle}> <td className={shortcutCellStyle}>
<ShortcutBadge visualLabel={visualShortcut} srLabel={srShortcut} /> <ShortcutBadge visualLabel={visualShortcut} srLabel={srShortcut} />
</td> </td>