mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 06:30:10 +08:00
fix(a11y): improve screen reader (NVDA) support with aria-label attributes (#11678)
* fix(a11y): improve screen reader support with aria-label attributes Add aria-label attributes to all interactive buttons and toolbar elements to improve accessibility for screen reader users (NVDA, etc.). Changes: - Add aria-label with i18n translations to all ActionIconButton components - Add role="button", tabIndex, and keyboard handlers for non-semantic elements - Fix hardcoded English aria-labels in WindowControls to use i18n - Add aria-pressed for toggle buttons to indicate state - Add aria-expanded for expandable menus - Add aria-disabled for disabled buttons Components updated: - SendMessageButton, CopyButton, SelectionToolbar - CodeToolbar, RichEditor toolbar, MinimalToolbar - WindowControls - 12 Inputbar tool buttons (WebSearch, Attachment, KnowledgeBase, etc.) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(a11y): enhance accessibility in CodeToolbar snapshot Added aria-label, role, and tabindex attributes to improve screen reader support for interactive elements in the CodeToolbar component. This change aligns with ongoing efforts to enhance accessibility across the application. --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
fb20173194
commit
9637fb8a43
@ -64,7 +64,11 @@ exports[`CodeToolbar > basic rendering > should match snapshot with mixed tools
|
||||
data-title="code_block.more"
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-label="code_block.more"
|
||||
class="c2"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="tool-icon"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { ActionTool } from '@renderer/components/ActionTools'
|
||||
import { Dropdown, Tooltip } from 'antd'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { memo, useCallback, useMemo } from 'react'
|
||||
|
||||
import { ToolWrapper } from './styles'
|
||||
|
||||
@ -9,13 +9,30 @@ interface CodeToolButtonProps {
|
||||
}
|
||||
|
||||
const CodeToolButton = ({ tool }: CodeToolButtonProps) => {
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
tool.onClick?.()
|
||||
}
|
||||
},
|
||||
[tool]
|
||||
)
|
||||
|
||||
const mainTool = useMemo(
|
||||
() => (
|
||||
<Tooltip key={tool.id} title={tool.tooltip} mouseEnterDelay={0.5} mouseLeaveDelay={0}>
|
||||
<ToolWrapper onClick={tool.onClick}>{tool.icon}</ToolWrapper>
|
||||
<ToolWrapper
|
||||
onClick={tool.onClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
role="button"
|
||||
aria-label={tool.tooltip}
|
||||
tabIndex={0}>
|
||||
{tool.icon}
|
||||
</ToolWrapper>
|
||||
</Tooltip>
|
||||
),
|
||||
[tool]
|
||||
[tool, handleKeyDown]
|
||||
)
|
||||
|
||||
if (tool.children?.length && tool.children.length > 0) {
|
||||
|
||||
@ -40,7 +40,19 @@ const CodeToolbar = ({ tools }: { tools: ActionTool[] }) => {
|
||||
{quickToolButtons}
|
||||
{quickTools.length > 1 && (
|
||||
<Tooltip title={t('code_block.more')} mouseEnterDelay={0.5}>
|
||||
<ToolWrapper onClick={() => setShowQuickTools(!showQuickTools)} className={showQuickTools ? 'active' : ''}>
|
||||
<ToolWrapper
|
||||
onClick={() => setShowQuickTools(!showQuickTools)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
setShowQuickTools(!showQuickTools)
|
||||
}
|
||||
}}
|
||||
className={showQuickTools ? 'active' : ''}
|
||||
role="button"
|
||||
aria-label={t('code_block.more')}
|
||||
aria-expanded={showQuickTools}
|
||||
tabIndex={0}>
|
||||
<EllipsisVertical className="tool-icon" />
|
||||
</ToolWrapper>
|
||||
</Tooltip>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Tooltip } from 'antd'
|
||||
import { Copy } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
import type { FC, KeyboardEvent } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@ -39,8 +39,24 @@ const CopyButton: FC<CopyButtonProps> = ({
|
||||
})
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
handleCopy()
|
||||
}
|
||||
}
|
||||
|
||||
const ariaLabel = tooltip || t('common.copy')
|
||||
|
||||
const button = (
|
||||
<ButtonContainer $color={color} $hoverColor={hoverColor} onClick={handleCopy}>
|
||||
<ButtonContainer
|
||||
$color={color}
|
||||
$hoverColor={hoverColor}
|
||||
onClick={handleCopy}
|
||||
onKeyDown={handleKeyDown}
|
||||
role="button"
|
||||
aria-label={ariaLabel}
|
||||
tabIndex={0}>
|
||||
<Copy size={size} className="copy-icon" />
|
||||
{label && <RightText size={size}>{label}</RightText>}
|
||||
</ButtonContainer>
|
||||
|
||||
@ -171,7 +171,9 @@ export const Toolbar: React.FC<ToolbarProps> = ({ editor, formattingState, onCom
|
||||
data-active={isActive}
|
||||
disabled={isDisabled}
|
||||
onClick={() => handleCommand(command)}
|
||||
data-testid={`toolbar-${command}`}>
|
||||
data-testid={`toolbar-${command}`}
|
||||
aria-label={tooltipText}
|
||||
aria-pressed={isActive}>
|
||||
<Icon color={isActive ? 'var(--color-primary)' : 'var(--color-text)'} />
|
||||
</ToolbarButton>
|
||||
)
|
||||
|
||||
@ -86,7 +86,7 @@ const WindowControls: React.FC = () => {
|
||||
return (
|
||||
<WindowControlsContainer>
|
||||
<Tooltip title={t('navbar.window.minimize')} placement="bottom" mouseEnterDelay={DEFAULT_DELAY}>
|
||||
<ControlButton onClick={handleMinimize} aria-label="Minimize">
|
||||
<ControlButton onClick={handleMinimize} aria-label={t('navbar.window.minimize')}>
|
||||
<Minus size={14} />
|
||||
</ControlButton>
|
||||
</Tooltip>
|
||||
@ -94,12 +94,14 @@ const WindowControls: React.FC = () => {
|
||||
title={isMaximized ? t('navbar.window.restore') : t('navbar.window.maximize')}
|
||||
placement="bottom"
|
||||
mouseEnterDelay={DEFAULT_DELAY}>
|
||||
<ControlButton onClick={handleMaximize} aria-label={isMaximized ? 'Restore' : 'Maximize'}>
|
||||
<ControlButton
|
||||
onClick={handleMaximize}
|
||||
aria-label={isMaximized ? t('navbar.window.restore') : t('navbar.window.maximize')}>
|
||||
{isMaximized ? <WindowRestoreIcon size={14} /> : <Square size={14} />}
|
||||
</ControlButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('navbar.window.close')} placement="bottom" mouseEnterDelay={DEFAULT_DELAY}>
|
||||
<ControlButton $isClose onClick={handleClose} aria-label="Close">
|
||||
<ControlButton $isClose onClick={handleClose} aria-label={t('navbar.window.close')}>
|
||||
<X size={17} />
|
||||
</ControlButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import type { FC, KeyboardEvent } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface Props {
|
||||
disabled: boolean
|
||||
@ -6,10 +7,24 @@ interface Props {
|
||||
}
|
||||
|
||||
const SendMessageButton: FC<Props> = ({ disabled, sendMessage }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLElement>) => {
|
||||
if (!disabled && (e.key === 'Enter' || e.key === ' ')) {
|
||||
e.preventDefault()
|
||||
sendMessage()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<i
|
||||
className="iconfont icon-ic_send"
|
||||
onClick={sendMessage}
|
||||
onClick={disabled ? undefined : sendMessage}
|
||||
onKeyDown={handleKeyDown}
|
||||
role="button"
|
||||
aria-label={t('chat.input.send')}
|
||||
aria-disabled={disabled}
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
style={{
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
color: disabled ? 'var(--color-text-3)' : 'var(--color-primary)',
|
||||
|
||||
@ -31,7 +31,7 @@ const ActivityDirectoryButton: FC<Props> = ({ quickPanel, quickPanelController,
|
||||
|
||||
return (
|
||||
<Tooltip placement="top" title={t('chat.input.activity_directory.title')} mouseLeaveDelay={0} arrow>
|
||||
<ActionIconButton onClick={handleOpenQuickPanel}>
|
||||
<ActionIconButton onClick={handleOpenQuickPanel} aria-label={t('chat.input.activity_directory.title')}>
|
||||
<FolderOpen size={18} />
|
||||
</ActionIconButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -152,13 +152,15 @@ const AttachmentButton: FC<Props> = ({ quickPanel, couldAddImageFile, extensions
|
||||
}
|
||||
}, [couldAddImageFile, openQuickPanel, quickPanel, t])
|
||||
|
||||
const ariaLabel = couldAddImageFile ? t('chat.input.upload.image_or_document') : t('chat.input.upload.document')
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={couldAddImageFile ? t('chat.input.upload.image_or_document') : t('chat.input.upload.document')}
|
||||
mouseLeaveDelay={0}
|
||||
arrow>
|
||||
<ActionIconButton onClick={openFileSelectDialog} active={files.length > 0} disabled={disabled}>
|
||||
<Tooltip placement="top" title={ariaLabel} mouseLeaveDelay={0} arrow>
|
||||
<ActionIconButton
|
||||
onClick={openFileSelectDialog}
|
||||
active={files.length > 0}
|
||||
disabled={disabled}
|
||||
aria-label={ariaLabel}>
|
||||
<Paperclip size={18} />
|
||||
</ActionIconButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -15,18 +15,18 @@ interface Props {
|
||||
const GenerateImageButton: FC<Props> = ({ model, assistant, onEnableGenerateImage }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const ariaLabel = isGenerateImageModel(model)
|
||||
? t('chat.input.generate_image')
|
||||
: t('chat.input.generate_image_not_supported')
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={
|
||||
isGenerateImageModel(model) ? t('chat.input.generate_image') : t('chat.input.generate_image_not_supported')
|
||||
}
|
||||
mouseLeaveDelay={0}
|
||||
arrow>
|
||||
<Tooltip placement="top" title={ariaLabel} mouseLeaveDelay={0} arrow>
|
||||
<ActionIconButton
|
||||
onClick={onEnableGenerateImage}
|
||||
active={assistant.enableGenerateImage}
|
||||
disabled={!isGenerateImageModel(model)}>
|
||||
disabled={!isGenerateImageModel(model)}
|
||||
aria-label={ariaLabel}
|
||||
aria-pressed={assistant.enableGenerateImage}>
|
||||
<Image size={18} />
|
||||
</ActionIconButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -124,7 +124,8 @@ const KnowledgeBaseButton: FC<Props> = ({ quickPanel, selectedBases, onSelect, d
|
||||
<ActionIconButton
|
||||
onClick={handleOpenQuickPanel}
|
||||
active={selectedBases && selectedBases.length > 0}
|
||||
disabled={disabled}>
|
||||
disabled={disabled}
|
||||
aria-label={t('chat.input.knowledge_base')}>
|
||||
<FileSearch size={18} />
|
||||
</ActionIconButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -516,7 +516,10 @@ const MCPToolsButton: FC<Props> = ({ quickPanel, setInputValue, resizeTextArea,
|
||||
|
||||
return (
|
||||
<Tooltip placement="top" title={t('settings.mcp.title')} mouseLeaveDelay={0} arrow>
|
||||
<ActionIconButton onClick={handleOpenQuickPanel} active={assistant.mcpServers && assistant.mcpServers.length > 0}>
|
||||
<ActionIconButton
|
||||
onClick={handleOpenQuickPanel}
|
||||
active={assistant.mcpServers && assistant.mcpServers.length > 0}
|
||||
aria-label={t('settings.mcp.title')}>
|
||||
<Hammer size={18} />
|
||||
</ActionIconButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -46,7 +46,10 @@ const MentionModelsButton: FC<Props> = ({
|
||||
|
||||
return (
|
||||
<Tooltip placement="top" title={t('assistants.presets.edit.model.select.title')} mouseLeaveDelay={0} arrow>
|
||||
<ActionIconButton onClick={handleOpenQuickPanel} active={mentionedModels.length > 0}>
|
||||
<ActionIconButton
|
||||
onClick={handleOpenQuickPanel}
|
||||
active={mentionedModels.length > 0}
|
||||
aria-label={t('assistants.presets.edit.model.select.title')}>
|
||||
<AtSign size={18} />
|
||||
</ActionIconButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -20,7 +20,9 @@ const NewContextButton: FC<Props> = ({ onNewContext }) => {
|
||||
title={t('chat.input.new.context', { Command: newContextShortcut })}
|
||||
mouseLeaveDelay={0}
|
||||
arrow>
|
||||
<ActionIconButton onClick={onNewContext}>
|
||||
<ActionIconButton
|
||||
onClick={onNewContext}
|
||||
aria-label={t('chat.input.new.context', { Command: newContextShortcut })}>
|
||||
<Eraser size={18} />
|
||||
</ActionIconButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -250,7 +250,7 @@ const QuickPhrasesButton = ({ quickPanel, setInputValue, resizeTextArea, assista
|
||||
return (
|
||||
<>
|
||||
<Tooltip placement="top" title={t('settings.quickPhrase.title')} mouseLeaveDelay={0} arrow>
|
||||
<ActionIconButton onClick={handleOpenQuickPanel}>
|
||||
<ActionIconButton onClick={handleOpenQuickPanel} aria-label={t('settings.quickPhrase.title')}>
|
||||
<Zap size={18} />
|
||||
</ActionIconButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -37,7 +37,11 @@ const SlashCommandsButton: FC<Props> = ({ quickPanelController, session, openPan
|
||||
|
||||
return (
|
||||
<Tooltip placement="top" title={t('chat.input.slash_commands.title')} mouseLeaveDelay={0} arrow>
|
||||
<ActionIconButton onClick={handleOpenQuickPanel} active={isActive} disabled={!hasCommands}>
|
||||
<ActionIconButton
|
||||
onClick={handleOpenQuickPanel}
|
||||
active={isActive}
|
||||
disabled={!hasCommands}
|
||||
aria-label={t('chat.input.slash_commands.title')}>
|
||||
<Terminal size={18} />
|
||||
</ActionIconButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -142,17 +142,18 @@ const ThinkingButton: FC<Props> = ({ quickPanel, model, assistantId }): ReactEle
|
||||
}
|
||||
}, [currentReasoningEffort, openQuickPanel, quickPanel, t])
|
||||
|
||||
const ariaLabel =
|
||||
isThinkingEnabled && supportedOptions.includes('none')
|
||||
? t('common.close')
|
||||
: t('assistants.settings.reasoning_effort.label')
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={
|
||||
isThinkingEnabled && supportedOptions.includes('none')
|
||||
? t('common.close')
|
||||
: t('assistants.settings.reasoning_effort.label')
|
||||
}
|
||||
mouseLeaveDelay={0}
|
||||
arrow>
|
||||
<ActionIconButton onClick={handleOpenQuickPanel} active={currentReasoningEffort !== 'none'}>
|
||||
<Tooltip placement="top" title={ariaLabel} mouseLeaveDelay={0} arrow>
|
||||
<ActionIconButton
|
||||
onClick={handleOpenQuickPanel}
|
||||
active={currentReasoningEffort !== 'none'}
|
||||
aria-label={ariaLabel}
|
||||
aria-pressed={currentReasoningEffort !== 'none'}>
|
||||
{ThinkingIcon(currentReasoningEffort)}
|
||||
</ActionIconButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -48,7 +48,11 @@ const UrlContextButton: FC<Props> = ({ assistantId }) => {
|
||||
|
||||
return (
|
||||
<Tooltip placement="top" title={t('chat.input.url_context')} arrow>
|
||||
<ActionIconButton onClick={handleToggle} active={assistant.enableUrlContext}>
|
||||
<ActionIconButton
|
||||
onClick={handleToggle}
|
||||
active={assistant.enableUrlContext}
|
||||
aria-label={t('chat.input.url_context')}
|
||||
aria-pressed={assistant.enableUrlContext}>
|
||||
<Link size={18} />
|
||||
</ActionIconButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -25,13 +25,15 @@ const WebSearchButton: FC<Props> = ({ quickPanelController, assistantId }) => {
|
||||
}
|
||||
}, [enableWebSearch, toggleQuickPanel, updateWebSearchProvider])
|
||||
|
||||
const ariaLabel = enableWebSearch ? t('common.close') : t('chat.input.web_search.label')
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={enableWebSearch ? t('common.close') : t('chat.input.web_search.label')}
|
||||
mouseLeaveDelay={0}
|
||||
arrow>
|
||||
<ActionIconButton onClick={onClick} active={!!enableWebSearch}>
|
||||
<Tooltip placement="top" title={ariaLabel} mouseLeaveDelay={0} arrow>
|
||||
<ActionIconButton
|
||||
onClick={onClick}
|
||||
active={!!enableWebSearch}
|
||||
aria-label={ariaLabel}
|
||||
aria-pressed={!!enableWebSearch}>
|
||||
<WebSearchProviderIcon pid={selectedProviderId} />
|
||||
</ActionIconButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -238,19 +238,27 @@ const MinimalToolbar: FC<Props> = ({ app, webviewRef, currentUrl, onReload, onOp
|
||||
<LeftSection>
|
||||
<ButtonGroup>
|
||||
<Tooltip title={t('minapp.popup.goBack')} placement="bottom">
|
||||
<ToolbarButton onClick={handleGoBack} $disabled={!canGoBack}>
|
||||
<ToolbarButton
|
||||
onClick={handleGoBack}
|
||||
$disabled={!canGoBack}
|
||||
aria-label={t('minapp.popup.goBack')}
|
||||
aria-disabled={!canGoBack}>
|
||||
<ArrowLeftOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title={t('minapp.popup.goForward')} placement="bottom">
|
||||
<ToolbarButton onClick={handleGoForward} $disabled={!canGoForward}>
|
||||
<ToolbarButton
|
||||
onClick={handleGoForward}
|
||||
$disabled={!canGoForward}
|
||||
aria-label={t('minapp.popup.goForward')}
|
||||
aria-disabled={!canGoForward}>
|
||||
<ArrowRightOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title={t('minapp.popup.refresh')} placement="bottom">
|
||||
<ToolbarButton onClick={onReload}>
|
||||
<ToolbarButton onClick={onReload} aria-label={t('minapp.popup.refresh')}>
|
||||
<ReloadOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
@ -261,7 +269,7 @@ const MinimalToolbar: FC<Props> = ({ app, webviewRef, currentUrl, onReload, onOp
|
||||
<ButtonGroup>
|
||||
{canOpenExternalLink && (
|
||||
<Tooltip title={t('minapp.popup.openExternal')} placement="bottom">
|
||||
<ToolbarButton onClick={handleOpenLink}>
|
||||
<ToolbarButton onClick={handleOpenLink} aria-label={t('minapp.popup.openExternal')}>
|
||||
<ExportOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
@ -271,7 +279,11 @@ const MinimalToolbar: FC<Props> = ({ app, webviewRef, currentUrl, onReload, onOp
|
||||
<Tooltip
|
||||
title={isPinned ? t('minapp.remove_from_launchpad') : t('minapp.add_to_launchpad')}
|
||||
placement="bottom">
|
||||
<ToolbarButton onClick={handleTogglePin} $active={isPinned}>
|
||||
<ToolbarButton
|
||||
onClick={handleTogglePin}
|
||||
$active={isPinned}
|
||||
aria-label={isPinned ? t('minapp.remove_from_launchpad') : t('minapp.add_to_launchpad')}
|
||||
aria-pressed={isPinned}>
|
||||
<PushpinOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
@ -284,21 +296,29 @@ const MinimalToolbar: FC<Props> = ({ app, webviewRef, currentUrl, onReload, onOp
|
||||
: t('minapp.popup.open_link_external_off')
|
||||
}
|
||||
placement="bottom">
|
||||
<ToolbarButton onClick={handleToggleOpenExternal} $active={minappsOpenLinkExternal}>
|
||||
<ToolbarButton
|
||||
onClick={handleToggleOpenExternal}
|
||||
$active={minappsOpenLinkExternal}
|
||||
aria-label={
|
||||
minappsOpenLinkExternal
|
||||
? t('minapp.popup.open_link_external_on')
|
||||
: t('minapp.popup.open_link_external_off')
|
||||
}
|
||||
aria-pressed={minappsOpenLinkExternal}>
|
||||
<LinkOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
|
||||
{isDev && (
|
||||
<Tooltip title={t('minapp.popup.devtools')} placement="bottom">
|
||||
<ToolbarButton onClick={onOpenDevTools}>
|
||||
<ToolbarButton onClick={onOpenDevTools} aria-label={t('minapp.popup.devtools')}>
|
||||
<CodeOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Tooltip title={t('minapp.popup.minimize')} placement="bottom">
|
||||
<ToolbarButton onClick={handleMinimize}>
|
||||
<ToolbarButton onClick={handleMinimize} aria-label={t('minapp.popup.minimize')}>
|
||||
<MinusOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
|
||||
@ -72,8 +72,22 @@ const ActionIcons: FC<{
|
||||
(action: ActionItem) => {
|
||||
const displayName = action.isBuiltIn ? t(action.name) : action.name
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
handleAction(action)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionButton key={action.id} onClick={() => handleAction(action)} title={isCompact ? displayName : undefined}>
|
||||
<ActionButton
|
||||
key={action.id}
|
||||
onClick={() => handleAction(action)}
|
||||
onKeyDown={handleKeyDown}
|
||||
title={isCompact ? displayName : undefined}
|
||||
role="button"
|
||||
aria-label={displayName}
|
||||
tabIndex={0}>
|
||||
<ActionIcon>
|
||||
{action.id === 'copy' ? (
|
||||
renderCopyIcon()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user