add aria-label to action buttons (#1507)

This commit is contained in:
Athou
2024-08-03 11:30:29 +02:00
parent 3a57b68fa3
commit a071b7c265
6 changed files with 51 additions and 31 deletions

View File

@@ -1,3 +1,5 @@
import type { MessageDescriptor } from "@lingui/core"
import { useLingui } from "@lingui/react"
import { ActionIcon, Button, type ButtonVariant, Tooltip, useMantineTheme } from "@mantine/core" import { ActionIcon, Button, type ButtonVariant, Tooltip, useMantineTheme } from "@mantine/core"
import type { ActionIconVariant } from "@mantine/core/lib/components/ActionIcon/ActionIcon" import type { ActionIconVariant } from "@mantine/core/lib/components/ActionIcon/ActionIcon"
import { Constants } from "app/constants" import { Constants } from "app/constants"
@@ -7,7 +9,7 @@ import { type MouseEventHandler, type ReactNode, forwardRef } from "react"
interface ActionButtonProps { interface ActionButtonProps {
className?: string className?: string
icon?: ReactNode icon?: ReactNode
label: ReactNode label?: string | MessageDescriptor
onClick?: MouseEventHandler onClick?: MouseEventHandler
variant?: ActionIconVariant & ButtonVariant variant?: ActionIconVariant & ButtonVariant
hideLabelOnDesktop?: boolean hideLabelOnDesktop?: boolean
@@ -20,17 +22,35 @@ interface ActionButtonProps {
export const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>((props: ActionButtonProps, ref) => { export const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>((props: ActionButtonProps, ref) => {
const { mobile } = useActionButton() const { mobile } = useActionButton()
const theme = useMantineTheme() const theme = useMantineTheme()
const { _ } = useLingui()
const label = typeof props.label === "string" ? props.label : props.label && _(props.label)
const variant = props.variant ?? "subtle" const variant = props.variant ?? "subtle"
const iconOnly = (mobile && !props.showLabelOnMobile) || (!mobile && props.hideLabelOnDesktop) const iconOnly = (mobile && !props.showLabelOnMobile) || (!mobile && props.hideLabelOnDesktop)
return iconOnly ? ( return iconOnly ? (
<Tooltip label={props.label} openDelay={Constants.tooltip.delay}> <Tooltip label={label} openDelay={Constants.tooltip.delay}>
<ActionIcon ref={ref} color={theme.primaryColor} variant={variant} className={props.className} onClick={props.onClick}> <ActionIcon
ref={ref}
color={theme.primaryColor}
variant={variant}
className={props.className}
onClick={props.onClick}
aria-label={label}
>
{props.icon} {props.icon}
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
) : ( ) : (
<Button ref={ref} variant={variant} size="xs" className={props.className} leftSection={props.icon} onClick={props.onClick}> <Button
{props.label} ref={ref}
variant={variant}
size="xs"
className={props.className}
leftSection={props.icon}
onClick={props.onClick}
aria-label={label}
>
{label}
</Button> </Button>
) )
}) })

View File

@@ -1,4 +1,4 @@
import { Trans, t } from "@lingui/macro" import { msg, t } from "@lingui/macro"
import { Group, Indicator, Popover, TagsInput } from "@mantine/core" import { Group, Indicator, Popover, TagsInput } from "@mantine/core"
import { markEntriesUpToEntry, markEntry, starEntry, tagEntry } from "app/entries/thunks" import { markEntriesUpToEntry, markEntry, starEntry, tagEntry } from "app/entries/thunks"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
@@ -40,13 +40,13 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
{props.entry.markable && ( {props.entry.markable && (
<ActionButton <ActionButton
icon={props.entry.read ? <TbMail size={18} /> : <TbMailOpened size={18} />} icon={props.entry.read ? <TbMail size={18} /> : <TbMailOpened size={18} />}
label={props.entry.read ? <Trans>Keep unread</Trans> : <Trans>Mark as read</Trans>} label={props.entry.read ? msg`Keep unread` : msg`Mark as read`}
onClick={readStatusButtonClicked} onClick={readStatusButtonClicked}
/> />
)} )}
<ActionButton <ActionButton
icon={props.entry.starred ? <TbStarOff size={18} /> : <TbStar size={18} />} icon={props.entry.starred ? <TbStarOff size={18} /> : <TbStar size={18} />}
label={props.entry.starred ? <Trans>Unstar</Trans> : <Trans>Star</Trans>} label={props.entry.starred ? msg`Unstar` : msg`Star`}
onClick={async () => onClick={async () =>
await dispatch( await dispatch(
starEntry({ starEntry({
@@ -59,7 +59,7 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
<Popover withArrow withinPortal shadow="md" closeOnClickOutside={!mobile}> <Popover withArrow withinPortal shadow="md" closeOnClickOutside={!mobile}>
<Popover.Target> <Popover.Target>
<ActionButton icon={<TbShare size={18} />} label={<Trans>Share</Trans>} /> <ActionButton icon={<TbShare size={18} />} label={msg`Share`} />
</Popover.Target> </Popover.Target>
<Popover.Dropdown> <Popover.Dropdown>
<ShareButtons url={props.entry.url} description={props.entry.title} /> <ShareButtons url={props.entry.url} description={props.entry.title} />
@@ -70,7 +70,7 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
<Popover withArrow shadow="md" closeOnClickOutside={!mobile}> <Popover withArrow shadow="md" closeOnClickOutside={!mobile}>
<Popover.Target> <Popover.Target>
<Indicator label={props.entry.tags.length} disabled={props.entry.tags.length === 0} inline size={16}> <Indicator label={props.entry.tags.length} disabled={props.entry.tags.length === 0} inline size={16}>
<ActionButton icon={<TbTag size={18} />} label={<Trans>Tags</Trans>} /> <ActionButton icon={<TbTag size={18} />} label={msg`Tags`} />
</Indicator> </Indicator>
</Popover.Target> </Popover.Target>
<Popover.Dropdown> <Popover.Dropdown>
@@ -88,13 +88,13 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
)} )}
<a href={props.entry.url} target="_blank" rel="noreferrer"> <a href={props.entry.url} target="_blank" rel="noreferrer">
<ActionButton icon={<TbExternalLink size={18} />} label={<Trans>Open link</Trans>} /> <ActionButton icon={<TbExternalLink size={18} />} label={msg`Open link`} />
</a> </a>
</Group> </Group>
<ActionButton <ActionButton
icon={<TbArrowBarToDown size={18} />} icon={<TbArrowBarToDown size={18} />}
label={<Trans>Mark as read up to here</Trans>} label={msg`Mark as read up to here`}
onClick={async () => await dispatch(markEntriesUpToEntry(props.entry))} onClick={async () => await dispatch(markEntriesUpToEntry(props.entry))}
/> />
</Group> </Group>

View File

@@ -1,4 +1,4 @@
import { Trans, t } from "@lingui/macro" import { msg, t } from "@lingui/macro"
import { Box, Center, CloseButton, Divider, Group, Indicator, Popover, TextInput } from "@mantine/core" import { Box, Center, CloseButton, Divider, Group, Indicator, Popover, TextInput } from "@mantine/core"
import { useForm } from "@mantine/form" import { useForm } from "@mantine/form"
import { reloadEntries, search, selectNextEntry, selectPreviousEntry } from "app/entries/thunks" import { reloadEntries, search, selectNextEntry, selectPreviousEntry } from "app/entries/thunks"
@@ -77,7 +77,7 @@ export function Header() {
<HeaderToolbar> <HeaderToolbar>
<ActionButton <ActionButton
icon={<TbArrowUp size={iconSize} />} icon={<TbArrowUp size={iconSize} />}
label={<Trans>Previous</Trans>} label={msg`Previous`}
onClick={async () => onClick={async () =>
await dispatch( await dispatch(
selectPreviousEntry({ selectPreviousEntry({
@@ -90,7 +90,7 @@ export function Header() {
/> />
<ActionButton <ActionButton
icon={<TbArrowDown size={iconSize} />} icon={<TbArrowDown size={iconSize} />}
label={<Trans>Next</Trans>} label={msg`Next`}
onClick={async () => onClick={async () =>
await dispatch( await dispatch(
selectNextEntry({ selectNextEntry({
@@ -106,7 +106,7 @@ export function Header() {
<ActionButton <ActionButton
icon={<TbRefresh size={iconSize} />} icon={<TbRefresh size={iconSize} />}
label={<Trans>Refresh</Trans>} label={msg`Refresh`}
onClick={async () => await dispatch(reloadEntries())} onClick={async () => await dispatch(reloadEntries())}
/> />
<MarkAllAsReadButton iconSize={iconSize} /> <MarkAllAsReadButton iconSize={iconSize} />
@@ -115,19 +115,19 @@ export function Header() {
<ActionButton <ActionButton
icon={settings.readingMode === "all" ? <TbEye size={iconSize} /> : <TbEyeOff size={iconSize} />} icon={settings.readingMode === "all" ? <TbEye size={iconSize} /> : <TbEyeOff size={iconSize} />}
label={settings.readingMode === "all" ? <Trans>All</Trans> : <Trans>Unread</Trans>} label={settings.readingMode === "all" ? msg`All` : msg`Unread`}
onClick={async () => await dispatch(changeReadingMode(settings.readingMode === "all" ? "unread" : "all"))} onClick={async () => await dispatch(changeReadingMode(settings.readingMode === "all" ? "unread" : "all"))}
/> />
<ActionButton <ActionButton
icon={settings.readingOrder === "asc" ? <TbSortAscending size={iconSize} /> : <TbSortDescending size={iconSize} />} icon={settings.readingOrder === "asc" ? <TbSortAscending size={iconSize} /> : <TbSortDescending size={iconSize} />}
label={settings.readingOrder === "asc" ? <Trans>Asc</Trans> : <Trans>Desc</Trans>} label={settings.readingOrder === "asc" ? msg`Asc` : msg`Desc`}
onClick={async () => await dispatch(changeReadingOrder(settings.readingOrder === "asc" ? "desc" : "asc"))} onClick={async () => await dispatch(changeReadingOrder(settings.readingOrder === "asc" ? "desc" : "asc"))}
/> />
<Popover> <Popover>
<Popover.Target> <Popover.Target>
<Indicator disabled={!searchFromStore}> <Indicator disabled={!searchFromStore}>
<ActionButton icon={<TbSearch size={iconSize} />} label={<Trans>Search</Trans>} /> <ActionButton icon={<TbSearch size={iconSize} />} label={msg`Search`} />
</Indicator> </Indicator>
</Popover.Target> </Popover.Target>
<Popover.Dropdown> <Popover.Dropdown>
@@ -153,12 +153,12 @@ export function Header() {
<ActionButton <ActionButton
icon={<TbSettings size={iconSize} />} icon={<TbSettings size={iconSize} />}
label={<Trans>Extension options</Trans>} label={msg`Extension options`}
onClick={() => openSettingsPage()} onClick={() => openSettingsPage()}
/> />
<ActionButton <ActionButton
icon={<TbExternalLink size={iconSize} />} icon={<TbExternalLink size={iconSize} />}
label={<Trans>Open CommaFeed</Trans>} label={msg`Open CommaFeed`}
onClick={() => openAppInNewTab()} onClick={() => openAppInNewTab()}
/> />
</> </>

View File

@@ -1,4 +1,4 @@
import { Trans } from "@lingui/macro" import { Trans, msg } from "@lingui/macro"
import { Button, Code, Group, Modal, Slider, Stack, Text } from "@mantine/core" import { Button, Code, Group, Modal, Slider, Stack, Text } from "@mantine/core"
import { markAllEntries } from "app/entries/thunks" import { markAllEntries } from "app/entries/thunks"
@@ -91,7 +91,7 @@ export function MarkAllAsReadButton(props: { iconSize: number }) {
</Group> </Group>
</Stack> </Stack>
</Modal> </Modal>
<ActionButton icon={<TbChecks size={props.iconSize} />} label={<Trans>Mark all as read</Trans>} onClick={buttonClicked} /> <ActionButton icon={<TbChecks size={props.iconSize} />} label={msg`Mark all as read`} onClick={buttonClicked} />
</> </>
) )
} }

View File

@@ -1,4 +1,4 @@
import { Trans } from "@lingui/macro" import { msg } from "@lingui/macro"
import { Anchor, Box, Center, Container, Divider, Group, Image, Space, Title, useMantineColorScheme } from "@mantine/core" import { Anchor, Box, Center, Container, Divider, Group, Image, Space, Title, useMantineColorScheme } from "@mantine/core"
import { client } from "app/client" import { client } from "app/client"
import { redirectToApiDocumentation, redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/redirect/thunks" import { redirectToApiDocumentation, redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/redirect/thunks"
@@ -38,7 +38,7 @@ export function WelcomePage() {
{serverInfos?.demoAccountEnabled && ( {serverInfos?.demoAccountEnabled && (
<Center> <Center>
<ActionButton <ActionButton
label={<Trans>Try the demo!</Trans>} label={msg`Try the demo!`}
icon={<TbClock size={iconSize} />} icon={<TbClock size={iconSize} />}
variant="outline" variant="outline"
onClick={async () => await login.execute({ name: "demo", password: "demo" })} onClick={async () => await login.execute({ name: "demo", password: "demo" })}
@@ -96,7 +96,7 @@ function Buttons() {
return ( return (
<Group gap={14}> <Group gap={14}>
<ActionButton <ActionButton
label={<Trans>Log in</Trans>} label={msg`Log in`}
icon={<TbKey size={iconSize} />} icon={<TbKey size={iconSize} />}
variant="outline" variant="outline"
onClick={async () => await dispatch(redirectToLogin())} onClick={async () => await dispatch(redirectToLogin())}
@@ -104,7 +104,7 @@ function Buttons() {
/> />
{serverInfos?.allowRegistrations && ( {serverInfos?.allowRegistrations && (
<ActionButton <ActionButton
label={<Trans>Sign up</Trans>} label={msg`Sign up`}
icon={<TbUserPlus size={iconSize} />} icon={<TbUserPlus size={iconSize} />}
variant="filled" variant="filled"
onClick={async () => await dispatch(redirectToRegistration())} onClick={async () => await dispatch(redirectToRegistration())}
@@ -113,7 +113,7 @@ function Buttons() {
)} )}
<ActionButton <ActionButton
label={dark ? <Trans>Switch to light theme</Trans> : <Trans>Switch to dark theme</Trans>} label={dark ? msg`Switch to light theme` : msg`Switch to dark theme`}
icon={colorScheme === "dark" ? <TbSun size={18} /> : <TbMoon size={iconSize} />} icon={colorScheme === "dark" ? <TbSun size={18} /> : <TbMoon size={iconSize} />}
onClick={() => toggleColorScheme()} onClick={() => toggleColorScheme()}
hideLabelOnDesktop hideLabelOnDesktop
@@ -121,7 +121,7 @@ function Buttons() {
{isBrowserExtensionPopup && ( {isBrowserExtensionPopup && (
<ActionButton <ActionButton
label={<Trans>Extension options</Trans>} label={msg`Extension options`}
icon={<TbSettings size={iconSize} />} icon={<TbSettings size={iconSize} />}
onClick={() => openSettingsPage()} onClick={() => openSettingsPage()}
hideLabelOnDesktop hideLabelOnDesktop

View File

@@ -1,4 +1,4 @@
import { Trans } from "@lingui/macro" import { msg } from "@lingui/macro"
import { ActionIcon, AppShell, Box, Center, Group, ScrollArea, Title, useMantineTheme } from "@mantine/core" import { ActionIcon, AppShell, Box, Center, Group, ScrollArea, Title, useMantineTheme } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { redirectToAdd, redirectToRootCategory } from "app/redirect/thunks" import { redirectToAdd, redirectToRootCategory } from "app/redirect/thunks"
@@ -101,7 +101,7 @@ export default function Layout(props: LayoutProps) {
const burger = ( const burger = (
<ActionButton <ActionButton
label={mobileMenuOpen ? <Trans>Close menu</Trans> : <Trans>Open menu</Trans>} label={mobileMenuOpen ? msg`Close menu` : msg`Open menu`}
icon={mobileMenuOpen ? <TbX size={18} /> : <TbMenu2 size={18} />} icon={mobileMenuOpen ? <TbX size={18} /> : <TbMenu2 size={18} />}
onClick={() => dispatch(setMobileMenuOpen(!mobileMenuOpen))} onClick={() => dispatch(setMobileMenuOpen(!mobileMenuOpen))}
/> />