Move settings out of help dropdown into dedicated sidebar button (#5272)

- Pulls the Settings option out of the Help dropdown menu and renders it as a standalone button directly in the sidebar.
- Integrates the `useShortcutKeys` hook and `ShortcutKeyCombo` component to display the settings keyboard shortcut in the button's tooltip.
- Updates associated tests to verify the new button's placement, ARIA attributes, and tooltip content.
This commit is contained in:
Jinjing 2026-06-12 10:56:49 -07:00 committed by GitHub
parent 0021d95796
commit f027a1cb7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 179 additions and 139 deletions

View File

@ -10,7 +10,7 @@ const mocks = vi.hoisted(() => ({
appRestart: vi.fn(),
updaterCheck: vi.fn(),
shellOpenUrl: vi.fn(),
useShortcutLabel: vi.fn(),
useShortcutKeys: vi.fn(),
setupProgress: {
ready: true,
coreDoneCount: 2,
@ -32,7 +32,7 @@ vi.mock('@/store', () => ({
}))
vi.mock('@/hooks/useShortcutLabel', () => ({
useShortcutLabel: mocks.useShortcutLabel
useShortcutKeys: mocks.useShortcutKeys
}))
vi.mock('@/hooks/useMountedRef', () => ({
@ -72,12 +72,14 @@ vi.mock('@/components/ui/tooltip', () => ({
vi.mock('@/components/ui/button', () => ({
Button: ({
children,
onClick
onClick,
'aria-label': ariaLabel
}: {
children: ReactNode
onClick?: (event: React.MouseEvent) => void
'aria-label'?: string
}) => (
<button data-testid="trigger-button" onClick={onClick}>
<button data-testid="trigger-button" aria-label={ariaLabel} onClick={onClick}>
{children}
</button>
)
@ -97,7 +99,7 @@ vi.mock('./SidebarFeedbackDialog', () => ({
describe('SidebarSettingsHelpMenu', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.useShortcutLabel.mockReturnValue('⌘,')
mocks.useShortcutKeys.mockReturnValue(['⌘', ','])
updateStatus = { state: 'idle' }
mocks.setupProgress = {
ready: true,
@ -112,9 +114,17 @@ describe('SidebarSettingsHelpMenu', () => {
expect(html).toContain('Help')
})
it('renders Settings menu item', () => {
it('renders the settings button with correct aria-label', () => {
const html = renderToStaticMarkup(<SidebarSettingsHelpMenu />)
expect(html).toContain('Settings')
expect(html).toContain('aria-label="Settings"')
})
it('renders the settings button before the help button', () => {
const html = renderToStaticMarkup(<SidebarSettingsHelpMenu />)
const settingsIndex = html.indexOf('lucide-settings')
const helpIndex = html.indexOf('lucide-circle-question-mark')
expect(settingsIndex).toBeGreaterThanOrEqual(0)
expect(helpIndex).toBeGreaterThan(settingsIndex)
})
it('renders Send Feedback menu item', () => {
@ -179,8 +189,9 @@ describe('SidebarSettingsHelpMenu', () => {
expect(html).toContain('Check for Updates')
})
it('renders shortcut label next to Settings', () => {
it('renders shortcut keys in the settings tooltip', () => {
const html = renderToStaticMarkup(<SidebarSettingsHelpMenu />)
expect(html).toContain('⌘,')
expect(html).toContain('⌘')
expect(html).toContain('>,</span>')
})
})

View File

@ -26,7 +26,8 @@ import {
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { useMountedRef } from '@/hooks/useMountedRef'
import { useShortcutLabel } from '@/hooks/useShortcutLabel'
import { useShortcutKeys } from '@/hooks/useShortcutLabel'
import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo'
import { showOnboardingFromRenderer } from '../onboarding/show-onboarding-event'
import { SetupGuideProgressRing } from '../setup-guide/SetupGuideProgressRing'
import { useSetupGuideProgress } from '../setup-guide/use-setup-guide-progress'
@ -84,7 +85,7 @@ export function SidebarSettingsHelpMenu(): React.JSX.Element {
const updateStatus = useAppStore((s) => s.updateStatus)
const setupProgress = useSetupGuideProgress(true, false, false)
const settingsShortcut = useShortcutLabel('app.settings')
const settingsShortcutKeys = useShortcutKeys('app.settings')
const [menuOpen, setMenuOpen] = useState(false)
const [feedbackOpen, setFeedbackOpen] = useState(false)
const [showAdminOptions, setShowAdminOptions] = useState(false)
@ -157,145 +158,173 @@ export function SidebarSettingsHelpMenu(): React.JSX.Element {
return (
<>
<DropdownMenu modal={false} open={menuOpen} onOpenChange={handleMenuOpenChange}>
<div className="flex items-center gap-1">
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon-xs"
type="button"
aria-label={translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.2991a0106c',
'Help'
)}
className="text-muted-foreground"
onPointerDown={(event) => revealAdminOptions(event.altKey)}
onClick={(event) => revealAdminOptions(event.altKey)}
>
<CircleHelp className="size-3.5" />
</Button>
</DropdownMenuTrigger>
<Button
variant="ghost"
size="icon-xs"
type="button"
aria-label={translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.a428c25998',
'Settings'
)}
className="text-muted-foreground"
onClick={openSettingsPage}
>
<Settings className="size-3.5" />
</Button>
</TooltipTrigger>
<TooltipContent side="top" sideOffset={4}>
{translate('auto.components.sidebar.SidebarSettingsHelpMenu.2991a0106c', 'Help')}
<TooltipContent side="top" sideOffset={4} className="flex items-center gap-1.5">
{translate('auto.components.sidebar.SidebarSettingsHelpMenu.a428c25998', 'Settings')}
{settingsShortcutKeys.length > 0 ? (
<ShortcutKeyCombo
keys={settingsShortcutKeys}
className="gap-0.5"
keyCapClassName="min-w-0 border-background/20 bg-background/10 px-1 py-0 text-[10px] text-background shadow-none"
separatorClassName="text-[10px] text-background/70"
/>
) : null}
</TooltipContent>
</Tooltip>
<DropdownMenuContent side="top" align="start" sideOffset={8} className="w-52">
<DropdownMenuItem onSelect={openSettingsPage}>
<Settings className="size-3.5" />
{translate('auto.components.sidebar.SidebarSettingsHelpMenu.a428c25998', 'Settings')}
<span className="ml-auto text-xs tracking-wide opacity-60">{settingsShortcut}</span>
</DropdownMenuItem>
<DropdownMenuItem onSelect={openShortcutsSettings}>
<Keyboard className="size-3.5" />
{translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.e565171a7c',
'Keyboard Shortcuts'
)}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onSelect={() => setFeedbackOpen(true)}>
<MessageSquareText className="size-3.5" />
{translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.4cf5b868d7',
'Send Feedback'
)}
</DropdownMenuItem>
{showMilestones ? (
<DropdownMenuItem onSelect={openMilestones}>
<img
src={logo}
alt=""
aria-hidden="true"
className="size-3.5 object-contain invert opacity-55 dark:invert-0"
/>
<DropdownMenu modal={false} open={menuOpen} onOpenChange={handleMenuOpenChange}>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon-xs"
type="button"
aria-label={translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.2991a0106c',
'Help'
)}
className="text-muted-foreground"
onPointerDown={(event) => revealAdminOptions(event.altKey)}
onClick={(event) => revealAdminOptions(event.altKey)}
>
<CircleHelp className="size-3.5" />
</Button>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent side="top" sideOffset={4}>
{translate('auto.components.sidebar.SidebarSettingsHelpMenu.2991a0106c', 'Help')}
</TooltipContent>
</Tooltip>
<DropdownMenuContent side="top" align="start" sideOffset={8} className="w-52">
<DropdownMenuItem onSelect={openShortcutsSettings}>
<Keyboard className="size-3.5" />
{translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.f8a2c91d4e',
'Milestones'
)}
<SetupGuideProgressRing
done={setupProgress.coreDoneCount}
total={setupProgress.coreTotal}
sizeClassName="size-4"
className="ml-auto"
/>
</DropdownMenuItem>
) : null}
{showAdminOptions ? (
<DropdownMenuItem
className="whitespace-nowrap"
onClick={handleShowOnboarding}
onSelect={handleShowOnboarding}
>
<School className="size-3.5" />
{translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.b7e4d2a19c',
'Onboarding'
'auto.components.sidebar.SidebarSettingsHelpMenu.e565171a7c',
'Keyboard Shortcuts'
)}
</DropdownMenuItem>
) : null}
<ExternalMenuItem
label={translate('auto.components.sidebar.SidebarSettingsHelpMenu.cdc87f897e', 'Docs')}
url={DOCS_URL}
icon={<BookOpen className="size-3.5" />}
/>
<ExternalMenuItem
label={translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.5f83d86d92',
'Changelog'
)}
url={CHANGELOG_URL}
icon={<ScrollText className="size-3.5" />}
/>
<DropdownMenuSeparator />
<ExternalMenuItem
label={translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.5687ab246a',
'GitHub'
)}
url={GITHUB_URL}
icon={<Github className="size-3.5" />}
/>
<DropdownMenuItem onSelect={() => openExternalUrl(DISCORD_URL)}>
<DiscordIcon />
{translate('auto.components.sidebar.SidebarSettingsHelpMenu.eb9884e55b', 'Discord')}
<ExternalLink className="ml-auto size-3 text-muted-foreground" />
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => openExternalUrl(X_URL)}>
<XIcon />
{translate('auto.components.sidebar.SidebarSettingsHelpMenu.c4f8e1b72a', 'X')}
<ExternalLink className="ml-auto size-3 text-muted-foreground" />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
disabled={updateStatus.state === 'checking' || updateStatus.state === 'downloading'}
onSelect={handleCheckForUpdates}
>
{updateStatus.state === 'checking' ? (
<Loader2 className="size-3.5 animate-spin" />
) : (
<RefreshCw className="size-3.5" />
)}
{translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.29c56f30ee',
'Check for Updates'
)}
</DropdownMenuItem>
{showAdminOptions ? (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onSelect={handleRestartOrca} disabled={isRestartingOrca}>
<RotateCw className="size-3.5" />
<DropdownMenuSeparator />
<DropdownMenuItem onSelect={() => setFeedbackOpen(true)}>
<MessageSquareText className="size-3.5" />
{translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.4cf5b868d7',
'Send Feedback'
)}
</DropdownMenuItem>
{showMilestones ? (
<DropdownMenuItem onSelect={openMilestones}>
<img
src={logo}
alt=""
aria-hidden="true"
className="size-3.5 object-contain invert opacity-55 dark:invert-0"
/>
{translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.ad3d3ed7f1',
'Restart Orca'
'auto.components.sidebar.SidebarSettingsHelpMenu.f8a2c91d4e',
'Milestones'
)}
<SetupGuideProgressRing
done={setupProgress.coreDoneCount}
total={setupProgress.coreTotal}
sizeClassName="size-4"
className="ml-auto"
/>
</DropdownMenuItem>
) : null}
{showAdminOptions ? (
<DropdownMenuItem
className="whitespace-nowrap"
onClick={handleShowOnboarding}
onSelect={handleShowOnboarding}
>
<School className="size-3.5" />
{translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.b7e4d2a19c',
'Onboarding'
)}
</DropdownMenuItem>
</>
) : null}
</DropdownMenuContent>
</DropdownMenu>
) : null}
<ExternalMenuItem
label={translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.cdc87f897e',
'Docs'
)}
url={DOCS_URL}
icon={<BookOpen className="size-3.5" />}
/>
<ExternalMenuItem
label={translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.5f83d86d92',
'Changelog'
)}
url={CHANGELOG_URL}
icon={<ScrollText className="size-3.5" />}
/>
<DropdownMenuSeparator />
<ExternalMenuItem
label={translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.5687ab246a',
'GitHub'
)}
url={GITHUB_URL}
icon={<Github className="size-3.5" />}
/>
<DropdownMenuItem onSelect={() => openExternalUrl(DISCORD_URL)}>
<DiscordIcon />
{translate('auto.components.sidebar.SidebarSettingsHelpMenu.eb9884e55b', 'Discord')}
<ExternalLink className="ml-auto size-3 text-muted-foreground" />
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => openExternalUrl(X_URL)}>
<XIcon />
{translate('auto.components.sidebar.SidebarSettingsHelpMenu.c4f8e1b72a', 'X')}
<ExternalLink className="ml-auto size-3 text-muted-foreground" />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
disabled={updateStatus.state === 'checking' || updateStatus.state === 'downloading'}
onSelect={handleCheckForUpdates}
>
{updateStatus.state === 'checking' ? (
<Loader2 className="size-3.5 animate-spin" />
) : (
<RefreshCw className="size-3.5" />
)}
{translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.29c56f30ee',
'Check for Updates'
)}
</DropdownMenuItem>
{showAdminOptions ? (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onSelect={handleRestartOrca} disabled={isRestartingOrca}>
<RotateCw className="size-3.5" />
{translate(
'auto.components.sidebar.SidebarSettingsHelpMenu.ad3d3ed7f1',
'Restart Orca'
)}
</DropdownMenuItem>
</>
) : null}
</DropdownMenuContent>
</DropdownMenu>
</div>
<SidebarFeedbackDialog open={feedbackOpen} onOpenChange={setFeedbackOpen} />
</>
)