nicolium: make hotkey modal more screen reader-readable
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -1282,6 +1282,7 @@
|
||||
"join_event.request_success": "Requested to join the event",
|
||||
"join_event.success": "Joined the event",
|
||||
"join_event.title": "Join event",
|
||||
"keyboard_shortcuts.action": "Action",
|
||||
"keyboard_shortcuts.back": "to navigate back",
|
||||
"keyboard_shortcuts.blocked": "to open blocked users list",
|
||||
"keyboard_shortcuts.boost": "to repost",
|
||||
@ -1293,6 +1294,17 @@
|
||||
"keyboard_shortcuts.heading": "Keyboard shortcuts",
|
||||
"keyboard_shortcuts.home": "to open home timeline",
|
||||
"keyboard_shortcuts.hotkey": "Hotkey",
|
||||
"keyboard_shortcuts.joiners.or": "or",
|
||||
"keyboard_shortcuts.joiners.plus": "plus",
|
||||
"keyboard_shortcuts.joiners.then": "then",
|
||||
"keyboard_shortcuts.key_names.alt": "Alt",
|
||||
"keyboard_shortcuts.key_names.backspace": "Backspace",
|
||||
"keyboard_shortcuts.key_names.down": "Arrow down",
|
||||
"keyboard_shortcuts.key_names.enter": "Enter",
|
||||
"keyboard_shortcuts.key_names.esc": "Escape",
|
||||
"keyboard_shortcuts.key_names.question_mark": "Question mark",
|
||||
"keyboard_shortcuts.key_names.slash": "Slash",
|
||||
"keyboard_shortcuts.key_names.up": "Arrow up",
|
||||
"keyboard_shortcuts.legend": "to display this legend",
|
||||
"keyboard_shortcuts.mention": "to mention author",
|
||||
"keyboard_shortcuts.muted": "to open muted users list",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, FormattedMessage, type MessageDescriptor, useIntl } from 'react-intl';
|
||||
|
||||
import Modal from '@/components/ui/modal';
|
||||
import { useFeatures } from '@/hooks/use-features';
|
||||
@ -8,12 +8,88 @@ import { useLoggedIn } from '@/hooks/use-logged-in';
|
||||
|
||||
import type { BaseModalProps } from '@/features/ui/components/modal-root';
|
||||
|
||||
const messages = defineMessages({
|
||||
keyNameSlash: { id: 'keyboard_shortcuts.key_names.slash', defaultMessage: 'Slash' },
|
||||
keyNameQuestionMark: {
|
||||
id: 'keyboard_shortcuts.key_names.question_mark',
|
||||
defaultMessage: 'Question mark',
|
||||
},
|
||||
keyNameAlt: { id: 'keyboard_shortcuts.key_names.alt', defaultMessage: 'Alt' },
|
||||
keyNameBackspace: { id: 'keyboard_shortcuts.key_names.backspace', defaultMessage: 'Backspace' },
|
||||
keyNameDown: { id: 'keyboard_shortcuts.key_names.down', defaultMessage: 'Arrow down' },
|
||||
keyNameEnter: { id: 'keyboard_shortcuts.key_names.enter', defaultMessage: 'Enter' },
|
||||
keyNameEsc: { id: 'keyboard_shortcuts.key_names.esc', defaultMessage: 'Escape' },
|
||||
keyNameUp: { id: 'keyboard_shortcuts.key_names.up', defaultMessage: 'Arrow up' },
|
||||
joinerOr: { id: 'keyboard_shortcuts.joiners.or', defaultMessage: 'or' },
|
||||
joinerPlus: { id: 'keyboard_shortcuts.joiners.plus', defaultMessage: 'plus' },
|
||||
joinerThen: { id: 'keyboard_shortcuts.joiners.then', defaultMessage: 'then' },
|
||||
});
|
||||
|
||||
const Hotkey: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<kbd className='rounded-md border border-solid border-primary-200 bg-primary-50 px-1.5 py-1 font-sans text-xs dark:border-gray-700 dark:bg-gray-800'>
|
||||
{children}
|
||||
</kbd>
|
||||
);
|
||||
|
||||
const spokenKeyNames: Record<string, MessageDescriptor> = {
|
||||
'/': messages.keyNameSlash,
|
||||
'?': messages.keyNameQuestionMark,
|
||||
alt: messages.keyNameAlt,
|
||||
backspace: messages.keyNameBackspace,
|
||||
down: messages.keyNameDown,
|
||||
enter: messages.keyNameEnter,
|
||||
esc: messages.keyNameEsc,
|
||||
up: messages.keyNameUp,
|
||||
};
|
||||
|
||||
const getSpokenKeyName = (keyName: string) => {
|
||||
if (spokenKeyNames[keyName]) return spokenKeyNames[keyName];
|
||||
if (/^[a-z]$/i.test(keyName)) return keyName.toUpperCase();
|
||||
return keyName;
|
||||
};
|
||||
|
||||
type KeyJoiner = 'or' | 'plus' | 'then';
|
||||
|
||||
const visualJoiners: Record<KeyJoiner, string> = {
|
||||
or: ', ',
|
||||
plus: ' + ',
|
||||
then: ' + ',
|
||||
};
|
||||
|
||||
const spokenJoiners: Record<KeyJoiner, MessageDescriptor> = {
|
||||
or: messages.joinerOr,
|
||||
plus: messages.joinerPlus,
|
||||
then: messages.joinerThen,
|
||||
};
|
||||
|
||||
const HotkeyBinding: React.FC<{ keys: string[]; joiner?: KeyJoiner }> = ({
|
||||
keys,
|
||||
joiner = 'or',
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const spokenBinding = keys
|
||||
.map((keyName) => {
|
||||
const spokenKey = getSpokenKeyName(keyName);
|
||||
return typeof spokenKey === 'string' ? spokenKey : intl.formatMessage(spokenKey);
|
||||
})
|
||||
.join(` ${intl.formatMessage(spokenJoiners[joiner])} `);
|
||||
|
||||
return (
|
||||
<span>
|
||||
<span aria-hidden='true'>
|
||||
{keys.map((keyName, idx) => (
|
||||
<React.Fragment key={keyName}>
|
||||
{idx > 0 && visualJoiners[joiner]}
|
||||
<Hotkey>{keyName}</Hotkey>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</span>
|
||||
<span className='sr-only'>{spokenBinding}</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const TableCell: React.FC<{ className?: string; children: React.ReactNode }> = ({
|
||||
className,
|
||||
children,
|
||||
@ -41,17 +117,17 @@ const HotkeysModal: React.FC<BaseModalProps> = ({ onClose }) => {
|
||||
|
||||
const hotkeys = [
|
||||
isLoggedIn && {
|
||||
key: <Hotkey>r</Hotkey>,
|
||||
key: <HotkeyBinding keys={['r']} />,
|
||||
label: <FormattedMessage id='keyboard_shortcuts.reply' defaultMessage='to reply' />,
|
||||
},
|
||||
isLoggedIn && {
|
||||
key: <Hotkey>m</Hotkey>,
|
||||
key: <HotkeyBinding keys={['m']} />,
|
||||
label: (
|
||||
<FormattedMessage id='keyboard_shortcuts.mention' defaultMessage='to mention author' />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: <Hotkey>p</Hotkey>,
|
||||
key: <HotkeyBinding keys={['p']} />,
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='keyboard_shortcuts.profile'
|
||||
@ -60,32 +136,28 @@ const HotkeysModal: React.FC<BaseModalProps> = ({ onClose }) => {
|
||||
),
|
||||
},
|
||||
isLoggedIn && {
|
||||
key: <Hotkey>f</Hotkey>,
|
||||
key: <HotkeyBinding keys={['f']} />,
|
||||
label: <FormattedMessage id='keyboard_shortcuts.favourite' defaultMessage='to like' />,
|
||||
},
|
||||
isLoggedIn &&
|
||||
features.emojiReacts && {
|
||||
key: <Hotkey>e</Hotkey>,
|
||||
key: <HotkeyBinding keys={['e']} />,
|
||||
label: <FormattedMessage id='keyboard_shortcuts.react' defaultMessage='to react' />,
|
||||
},
|
||||
isLoggedIn && {
|
||||
key: <Hotkey>b</Hotkey>,
|
||||
key: <HotkeyBinding keys={['b']} />,
|
||||
label: <FormattedMessage id='keyboard_shortcuts.boost' defaultMessage='to repost' />,
|
||||
},
|
||||
{
|
||||
key: (
|
||||
<>
|
||||
<Hotkey>enter</Hotkey>, <Hotkey>o</Hotkey>
|
||||
</>
|
||||
),
|
||||
key: <HotkeyBinding keys={['enter', 'o']} joiner='or' />,
|
||||
label: <FormattedMessage id='keyboard_shortcuts.enter' defaultMessage='to open post' />,
|
||||
},
|
||||
{
|
||||
key: <Hotkey>a</Hotkey>,
|
||||
key: <HotkeyBinding keys={['a']} />,
|
||||
label: <FormattedMessage id='keyboard_shortcuts.open_media' defaultMessage='to open media' />,
|
||||
},
|
||||
features.spoilers && {
|
||||
key: <Hotkey>x</Hotkey>,
|
||||
key: <HotkeyBinding keys={['x']} />,
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='keyboard_shortcuts.toggle_hidden'
|
||||
@ -94,7 +166,7 @@ const HotkeysModal: React.FC<BaseModalProps> = ({ onClose }) => {
|
||||
),
|
||||
},
|
||||
features.spoilers && {
|
||||
key: <Hotkey>h</Hotkey>,
|
||||
key: <HotkeyBinding keys={['h']} />,
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='keyboard_shortcuts.toggle_sensitivity'
|
||||
@ -103,27 +175,19 @@ const HotkeysModal: React.FC<BaseModalProps> = ({ onClose }) => {
|
||||
),
|
||||
},
|
||||
{
|
||||
key: (
|
||||
<>
|
||||
<Hotkey>up</Hotkey>, <Hotkey>k</Hotkey>
|
||||
</>
|
||||
),
|
||||
key: <HotkeyBinding keys={['up', 'k']} joiner='or' />,
|
||||
label: (
|
||||
<FormattedMessage id='keyboard_shortcuts.up' defaultMessage='to move up in the list' />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: (
|
||||
<>
|
||||
<Hotkey>down</Hotkey>, <Hotkey>j</Hotkey>
|
||||
</>
|
||||
),
|
||||
key: <HotkeyBinding keys={['down', 'j']} joiner='or' />,
|
||||
label: (
|
||||
<FormattedMessage id='keyboard_shortcuts.down' defaultMessage='to move down in the list' />
|
||||
),
|
||||
},
|
||||
isLoggedIn && {
|
||||
key: <Hotkey>n</Hotkey>,
|
||||
key: <HotkeyBinding keys={['n']} />,
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='keyboard_shortcuts.compose'
|
||||
@ -132,23 +196,15 @@ const HotkeysModal: React.FC<BaseModalProps> = ({ onClose }) => {
|
||||
),
|
||||
},
|
||||
isLoggedIn && {
|
||||
key: (
|
||||
<>
|
||||
<Hotkey>alt</Hotkey> + <Hotkey>n</Hotkey>
|
||||
</>
|
||||
),
|
||||
key: <HotkeyBinding keys={['alt', 'n']} joiner='plus' />,
|
||||
label: <FormattedMessage id='keyboard_shortcuts.toot' defaultMessage='to start a new post' />,
|
||||
},
|
||||
{
|
||||
key: <Hotkey>backspace</Hotkey>,
|
||||
key: <HotkeyBinding keys={['backspace']} />,
|
||||
label: <FormattedMessage id='keyboard_shortcuts.back' defaultMessage='to navigate back' />,
|
||||
},
|
||||
isLoggedIn && {
|
||||
key: (
|
||||
<>
|
||||
<Hotkey>s</Hotkey>, <Hotkey>/</Hotkey>
|
||||
</>
|
||||
),
|
||||
key: <HotkeyBinding keys={['s', '/']} joiner='or' />,
|
||||
label: document.querySelector('#search') ? (
|
||||
<FormattedMessage id='keyboard_shortcuts.search' defaultMessage='to focus search' />
|
||||
) : (
|
||||
@ -159,7 +215,7 @@ const HotkeysModal: React.FC<BaseModalProps> = ({ onClose }) => {
|
||||
),
|
||||
},
|
||||
{
|
||||
key: <Hotkey>esc</Hotkey>,
|
||||
key: <HotkeyBinding keys={['esc']} />,
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='keyboard_shortcuts.unfocus'
|
||||
@ -168,21 +224,13 @@ const HotkeysModal: React.FC<BaseModalProps> = ({ onClose }) => {
|
||||
),
|
||||
},
|
||||
isLoggedIn && {
|
||||
key: (
|
||||
<>
|
||||
<Hotkey>g</Hotkey> + <Hotkey>h</Hotkey>
|
||||
</>
|
||||
),
|
||||
key: <HotkeyBinding keys={['g', 'h']} joiner='then' />,
|
||||
label: (
|
||||
<FormattedMessage id='keyboard_shortcuts.home' defaultMessage='to open home timeline' />
|
||||
),
|
||||
},
|
||||
isLoggedIn && {
|
||||
key: (
|
||||
<>
|
||||
<Hotkey>g</Hotkey> + <Hotkey>n</Hotkey>
|
||||
</>
|
||||
),
|
||||
key: <HotkeyBinding keys={['g', 'n']} joiner='then' />,
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='keyboard_shortcuts.notifications'
|
||||
@ -191,21 +239,13 @@ const HotkeysModal: React.FC<BaseModalProps> = ({ onClose }) => {
|
||||
),
|
||||
},
|
||||
isLoggedIn && {
|
||||
key: (
|
||||
<>
|
||||
<Hotkey>g</Hotkey> + <Hotkey>f</Hotkey>
|
||||
</>
|
||||
),
|
||||
key: <HotkeyBinding keys={['g', 'f']} joiner='then' />,
|
||||
label: (
|
||||
<FormattedMessage id='keyboard_shortcuts.favourites' defaultMessage='to open likes list' />
|
||||
),
|
||||
},
|
||||
isLoggedIn && {
|
||||
key: (
|
||||
<>
|
||||
<Hotkey>g</Hotkey> + <Hotkey>u</Hotkey>
|
||||
</>
|
||||
),
|
||||
key: <HotkeyBinding keys={['g', 'u']} joiner='then' />,
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='keyboard_shortcuts.my_profile'
|
||||
@ -214,11 +254,7 @@ const HotkeysModal: React.FC<BaseModalProps> = ({ onClose }) => {
|
||||
),
|
||||
},
|
||||
isLoggedIn && {
|
||||
key: (
|
||||
<>
|
||||
<Hotkey>g</Hotkey> + <Hotkey>b</Hotkey>
|
||||
</>
|
||||
),
|
||||
key: <HotkeyBinding keys={['g', 'b']} joiner='then' />,
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='keyboard_shortcuts.blocked'
|
||||
@ -227,22 +263,14 @@ const HotkeysModal: React.FC<BaseModalProps> = ({ onClose }) => {
|
||||
),
|
||||
},
|
||||
isLoggedIn && {
|
||||
key: (
|
||||
<>
|
||||
<Hotkey>g</Hotkey> + <Hotkey>m</Hotkey>
|
||||
</>
|
||||
),
|
||||
key: <HotkeyBinding keys={['g', 'm']} joiner='then' />,
|
||||
label: (
|
||||
<FormattedMessage id='keyboard_shortcuts.muted' defaultMessage='to open muted users list' />
|
||||
),
|
||||
},
|
||||
isLoggedIn &&
|
||||
features.followRequests && {
|
||||
key: (
|
||||
<>
|
||||
<Hotkey>g</Hotkey> + <Hotkey>r</Hotkey>
|
||||
</>
|
||||
),
|
||||
key: <HotkeyBinding keys={['g', 'r']} joiner='then' />,
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='keyboard_shortcuts.requests'
|
||||
@ -251,7 +279,7 @@ const HotkeysModal: React.FC<BaseModalProps> = ({ onClose }) => {
|
||||
),
|
||||
},
|
||||
{
|
||||
key: <Hotkey>?</Hotkey>,
|
||||
key: <HotkeyBinding keys={['?']} />,
|
||||
label: (
|
||||
<FormattedMessage id='keyboard_shortcuts.legend' defaultMessage='to display this legend' />
|
||||
),
|
||||
@ -291,6 +319,9 @@ const HotkeysModal: React.FC<BaseModalProps> = ({ onClose }) => {
|
||||
<th className='pb-2 font-bold'>
|
||||
<FormattedMessage id='keyboard_shortcuts.hotkey' defaultMessage='Hotkey' />
|
||||
</th>
|
||||
<th className='pb-2 font-bold'>
|
||||
<FormattedMessage id='keyboard_shortcuts.action' defaultMessage='Action' />
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
Reference in New Issue
Block a user