fix: use platform shortcut labels in feature wall (#3811)
This commit is contained in:
parent
17d8d079c9
commit
01a214cc59
|
|
@ -2,6 +2,7 @@
|
|||
import { useEffect, useRef, useState, type JSX, type ReactNode } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ClaudeIcon } from '@/components/status-bar/icons'
|
||||
import { useShortcutLabel } from '@/hooks/useShortcutLabel'
|
||||
import { FeatureWallClickRing } from './FeatureWallClickRing'
|
||||
|
||||
// Why: this animation tells the full Orca story end-to-end — the user opens a
|
||||
|
|
@ -152,6 +153,7 @@ const TERM_ENTRIES: readonly { entry: TermEntry; minPhase: Phase }[] = [
|
|||
|
||||
export function BrowserAnimatedVisual(props: { reducedMotion: boolean }): JSX.Element {
|
||||
const { reducedMotion } = props
|
||||
const newBrowserShortcutLabel = useShortcutLabel('tab.newBrowser')
|
||||
|
||||
const [phase, setPhase] = useState<Phase>('idle')
|
||||
const [typedChars, setTypedChars] = useState(0)
|
||||
|
|
@ -524,7 +526,9 @@ export function BrowserAnimatedVisual(props: { reducedMotion: boolean }): JSX.El
|
|||
<GlobeGlyph />
|
||||
</span>
|
||||
<span className="text-[11.5px] text-foreground">New Browser Tab</span>
|
||||
<span className="font-mono text-[10.5px] text-muted-foreground">⌘⇧B</span>
|
||||
<span className="font-mono text-[10.5px] text-muted-foreground">
|
||||
{newBrowserShortcutLabel}
|
||||
</span>
|
||||
</div>
|
||||
<DropdownSkeletonRow widthPct={52} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { useEffect, useRef } from 'react'
|
||||
import type { JSX, ReactNode } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { getShortcutPlatform } from '@/hooks/useShortcutLabel'
|
||||
|
||||
// Why: the visual leans on direct DOM mutation (typing into a node, swapping
|
||||
// classes, anchoring a floating menu by measured rect) so the loop reads
|
||||
|
|
@ -273,6 +274,9 @@ function SlashRow(props: {
|
|||
|
||||
export function EditorAnimatedVisual(props: { reducedMotion: boolean }): JSX.Element {
|
||||
const { reducedMotion } = props
|
||||
const editorShortcutPrefix = getShortcutPlatform() === 'darwin' ? '⌘' : 'Ctrl+'
|
||||
const boldShortcutLabel = `${editorShortcutPrefix}B`
|
||||
const italicShortcutLabel = `${editorShortcutPrefix}I`
|
||||
|
||||
const docRef = useRef<HTMLDivElement | null>(null)
|
||||
const activeLineRef = useRef<HTMLDivElement | null>(null)
|
||||
|
|
@ -714,8 +718,8 @@ export function EditorAnimatedVisual(props: { reducedMotion: boolean }): JSX.Ele
|
|||
shape. */}
|
||||
<div className="border-t border-border bg-card px-3 py-2 text-[11px] text-muted-foreground">
|
||||
Type <kbd className={KBD_CLASS_DOC}>/</kbd> for blocks ·{' '}
|
||||
<kbd className={KBD_CLASS_DOC}>⌘B</kbd> bold · <kbd className={KBD_CLASS_DOC}>⌘I</kbd>{' '}
|
||||
italic
|
||||
<kbd className={KBD_CLASS_DOC}>{boldShortcutLabel}</kbd> bold ·{' '}
|
||||
<kbd className={KBD_CLASS_DOC}>{italicShortcutLabel}</kbd> italic
|
||||
</div>
|
||||
|
||||
{/* Why: the imperative loop adds .slash-active and toggles
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useEffect, useRef } from 'react'
|
||||
import type { ComponentType, JSX, ReactNode } from 'react'
|
||||
import { Files, GitBranch, ListChecks, MessageSquare, Search } from 'lucide-react'
|
||||
import { useShortcutLabel } from '@/hooks/useShortcutLabel'
|
||||
import { ReviewPRViewVisualStyles } from './review-animated-visual-pr-view-styles'
|
||||
import { CheckTinyIcon, ChevDownIcon, CursorIcon } from './review-animated-visual-shared'
|
||||
|
||||
|
|
@ -18,6 +19,10 @@ const SIDEBAR_TABS: readonly {
|
|||
]
|
||||
|
||||
function SidebarTabs(props: { active: SidebarTabId; interactiveChecks?: boolean }): JSX.Element {
|
||||
const checksShortcutLabel = useShortcutLabel('sidebar.checks.toggle')
|
||||
const checksTooltip =
|
||||
checksShortcutLabel === 'Unassigned' ? 'Checks' : `Checks (${checksShortcutLabel})`
|
||||
|
||||
return (
|
||||
<div className="ravpr-tabs">
|
||||
{SIDEBAR_TABS.map((tab) => {
|
||||
|
|
@ -38,7 +43,7 @@ function SidebarTabs(props: { active: SidebarTabId; interactiveChecks?: boolean
|
|||
})}
|
||||
{props.interactiveChecks ? (
|
||||
<span className="ravpr-tooltip" data-checks-tooltip>
|
||||
Checks (⇧⌘K)
|
||||
{checksTooltip}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
import type { JSX } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useShortcutLabel } from '@/hooks/useShortcutLabel'
|
||||
import { FeatureWallClickRing } from './FeatureWallClickRing'
|
||||
|
||||
// Why: the right-click menu needs the same icons as the real Orca menu so the
|
||||
|
|
@ -126,6 +127,8 @@ const RESPONSE_WIDTHS = [72, 88, 64, 78] as const
|
|||
|
||||
export function WorkbenchAnimatedVisual(props: { reducedMotion: boolean }): JSX.Element {
|
||||
const { reducedMotion } = props
|
||||
const splitRightShortcutLabel = useShortcutLabel('terminal.splitRight')
|
||||
const splitDownShortcutLabel = useShortcutLabel('terminal.splitDown')
|
||||
const panelRef = useRef<HTMLDivElement | null>(null)
|
||||
const leftPaneRef = useRef<HTMLDivElement | null>(null)
|
||||
const splitRowRef = useRef<HTMLDivElement | null>(null)
|
||||
|
|
@ -392,6 +395,8 @@ export function WorkbenchAnimatedVisual(props: { reducedMotion: boolean }): JSX.
|
|||
shown={menuShown}
|
||||
splitRowActive={splitRowActive}
|
||||
splitRowRef={splitRowRef}
|
||||
splitRightShortcutLabel={splitRightShortcutLabel}
|
||||
splitDownShortcutLabel={splitDownShortcutLabel}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -435,8 +440,8 @@ export function WorkbenchAnimatedVisual(props: { reducedMotion: boolean }): JSX.
|
|||
{/* Standalone keyboard hint stays inside the visual so the tour copy can
|
||||
remain a single subheader line. */}
|
||||
<div className="border-t border-border bg-card px-3 py-2 text-[11px] text-muted-foreground">
|
||||
Same pane: <kbd className={KBD_CLASS}>⌘D</kbd> splits right ·{' '}
|
||||
<kbd className={KBD_CLASS}>⌘⇧D</kbd> splits down
|
||||
Same pane: <kbd className={KBD_CLASS}>{splitRightShortcutLabel}</kbd> splits right ·{' '}
|
||||
<kbd className={KBD_CLASS}>{splitDownShortcutLabel}</kbd> splits down
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -494,6 +499,8 @@ function ContextMenu(props: {
|
|||
shown: boolean
|
||||
splitRowActive: boolean
|
||||
splitRowRef: React.RefObject<HTMLDivElement | null>
|
||||
splitRightShortcutLabel: string
|
||||
splitDownShortcutLabel: string
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
|
|
@ -519,14 +526,18 @@ function ContextMenu(props: {
|
|||
<SplitRightIcon />
|
||||
</span>
|
||||
<span className="whitespace-nowrap leading-none">Split Terminal Right</span>
|
||||
<span className="font-mono text-[11px] text-muted-foreground">⌘D</span>
|
||||
<span className="font-mono text-[11px] text-muted-foreground">
|
||||
{props.splitRightShortcutLabel}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid h-[22px] grid-cols-[18px_1fr_auto] items-center gap-2 rounded-[5px] px-1.5 py-1 pl-1.5">
|
||||
<span className="inline-flex items-center justify-center text-muted-foreground">
|
||||
<SplitDownIcon />
|
||||
</span>
|
||||
<span className="whitespace-nowrap leading-none">Split Terminal Down</span>
|
||||
<span className="font-mono text-[11px] text-muted-foreground">⌘⇧D</span>
|
||||
<span className="font-mono text-[11px] text-muted-foreground">
|
||||
{props.splitDownShortcutLabel}
|
||||
</span>
|
||||
</div>
|
||||
<CtxSeparator />
|
||||
<CtxSkeleton width={64} />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { BrowserAnimatedVisual } from './BrowserAnimatedVisual'
|
||||
import { EditorAnimatedVisual } from './EditorAnimatedVisual'
|
||||
import { ReviewPRViewAnimatedVisual } from './ReviewPRViewAnimatedVisual'
|
||||
import { WorkbenchAnimatedVisual } from './WorkbenchAnimatedVisual'
|
||||
|
||||
const originalUserAgent = navigator.userAgent
|
||||
|
||||
function setUserAgent(userAgent: string): void {
|
||||
Object.defineProperty(navigator, 'userAgent', {
|
||||
configurable: true,
|
||||
value: userAgent
|
||||
})
|
||||
}
|
||||
|
||||
describe('feature wall shortcut labels', () => {
|
||||
afterEach(() => {
|
||||
setUserAgent(originalUserAgent)
|
||||
})
|
||||
|
||||
it('renders Windows shortcut copy in workbench and browser visuals', () => {
|
||||
setUserAgent('Windows NT 10.0')
|
||||
|
||||
const html = [
|
||||
renderToStaticMarkup(<BrowserAnimatedVisual reducedMotion />),
|
||||
renderToStaticMarkup(<WorkbenchAnimatedVisual reducedMotion />),
|
||||
renderToStaticMarkup(<EditorAnimatedVisual reducedMotion />),
|
||||
renderToStaticMarkup(<ReviewPRViewAnimatedVisual reducedMotion />)
|
||||
].join('\n')
|
||||
|
||||
expect(html).toContain('Ctrl+Shift+B')
|
||||
expect(html).toContain('Ctrl+Shift+D')
|
||||
expect(html).toContain('Alt+Shift+D')
|
||||
expect(html).toContain('Ctrl+B')
|
||||
expect(html).toContain('Ctrl+I')
|
||||
expect(html).toContain('Checks')
|
||||
expect(html).not.toContain('⌘')
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue