mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 08:03:22 +00:00
fix(status-bar): preserve roster menu interactions
This commit is contained in:
@@ -1757,11 +1757,11 @@ export function ProviderDetailsMenu({
|
||||
const usagePercentageDisplay = normalizeUsagePercentageDisplay(
|
||||
useAppStore((s) => s.usagePercentageDisplay)
|
||||
)
|
||||
const skipCloseAutoFocusRef = useRef(false)
|
||||
const menuFocusHandoff = useStatusBarMenuFocusHandoff()
|
||||
|
||||
const handleOpenChange = (nextOpen: boolean): void => {
|
||||
if (nextOpen) {
|
||||
skipCloseAutoFocusRef.current = false
|
||||
menuFocusHandoff.reset()
|
||||
recordFeatureInteraction('usage-tracking')
|
||||
}
|
||||
onOpenChange?.(nextOpen)
|
||||
@@ -1825,17 +1825,8 @@ export function ProviderDetailsMenu({
|
||||
align="start"
|
||||
sideOffset={8}
|
||||
className="w-[260px]"
|
||||
onPointerDownOutside={() => {
|
||||
skipCloseAutoFocusRef.current = true
|
||||
}}
|
||||
onCloseAutoFocus={(event) => {
|
||||
if (!skipCloseAutoFocusRef.current) {
|
||||
return
|
||||
}
|
||||
skipCloseAutoFocusRef.current = false
|
||||
// Why: focus the clicked surface (esp. xterm) — Radix's default trigger restore steals that first click.
|
||||
event.preventDefault()
|
||||
}}
|
||||
onPointerDownOutside={menuFocusHandoff.onPointerDownOutside}
|
||||
onCloseAutoFocus={menuFocusHandoff.onCloseAutoFocus}
|
||||
>
|
||||
{panelBody}
|
||||
</DropdownMenuContent>
|
||||
@@ -1845,6 +1836,30 @@ export function ProviderDetailsMenu({
|
||||
|
||||
const CLOSE_ALL_CONTEXT_MENUS_EVENT = 'orca-close-all-context-menus'
|
||||
|
||||
function useStatusBarMenuFocusHandoff(): {
|
||||
reset: () => void
|
||||
onPointerDownOutside: () => void
|
||||
onCloseAutoFocus: (event: Event) => void
|
||||
} {
|
||||
const skipCloseAutoFocusRef = useRef(false)
|
||||
return {
|
||||
reset: () => {
|
||||
skipCloseAutoFocusRef.current = false
|
||||
},
|
||||
onPointerDownOutside: () => {
|
||||
skipCloseAutoFocusRef.current = true
|
||||
},
|
||||
onCloseAutoFocus: (event) => {
|
||||
if (!skipCloseAutoFocusRef.current) {
|
||||
return
|
||||
}
|
||||
skipCloseAutoFocusRef.current = false
|
||||
// Why: Radix trigger restoration steals the first click from surfaces such as xterm.
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function StatusBarInner({ floatingTerminalOpen }: StatusBarProps): React.JSX.Element | null {
|
||||
const floatingTerminalShortcut = useShortcutLabel('floatingTerminal.toggle')
|
||||
const rateLimits = useAppStore((s) => s.rateLimits)
|
||||
@@ -1856,6 +1871,7 @@ function StatusBarInner({ floatingTerminalOpen }: StatusBarProps): React.JSX.Ele
|
||||
useAppStore((s) => s.usagePercentageDisplay)
|
||||
)
|
||||
const [usageMenuOpen, setUsageMenuOpen] = useState(false)
|
||||
const usageMenuFocusHandoff = useStatusBarMenuFocusHandoff()
|
||||
const statusBarVisible = useAppStore((s) => s.statusBarVisible)
|
||||
const statusBarItems = useAppStore((s) => s.statusBarItems)
|
||||
const recordFeatureInteraction = useAppStore((s) => s.recordFeatureInteraction)
|
||||
@@ -2059,6 +2075,13 @@ function StatusBarInner({ floatingTerminalOpen }: StatusBarProps): React.JSX.Ele
|
||||
openSettingsTarget({ pane: 'accounts', repoId: null, sectionId })
|
||||
openSettingsPage()
|
||||
}
|
||||
const handleUsageMenuOpenChange = (nextOpen: boolean): void => {
|
||||
if (nextOpen) {
|
||||
usageMenuFocusHandoff.reset()
|
||||
recordFeatureInteraction('usage-tracking')
|
||||
}
|
||||
setUsageMenuOpen(nextOpen)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -2084,7 +2107,11 @@ function StatusBarInner({ floatingTerminalOpen }: StatusBarProps): React.JSX.Ele
|
||||
) : hasVisibleUsageMeters ? (
|
||||
// Consolidated roster pill → opens the all-agents Usage popover (mock parity).
|
||||
<UsagePercentageDisplayChangeNotice hasVisibleUsageMeters={hasVisibleUsageMeters}>
|
||||
<DropdownMenu open={usageMenuOpen} onOpenChange={setUsageMenuOpen} modal={false}>
|
||||
<DropdownMenu
|
||||
open={usageMenuOpen}
|
||||
onOpenChange={handleUsageMenuOpenChange}
|
||||
modal={false}
|
||||
>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
@@ -2120,6 +2147,8 @@ function StatusBarInner({ floatingTerminalOpen }: StatusBarProps): React.JSX.Ele
|
||||
// bar instead of overlapping it — bottom padding ≈ footer height.
|
||||
collisionPadding={{ top: 8, bottom: 32, left: 8, right: 8 }}
|
||||
className="w-[360px] p-0"
|
||||
onPointerDownOutside={usageMenuFocusHandoff.onPointerDownOutside}
|
||||
onCloseAutoFocus={usageMenuFocusHandoff.onCloseAutoFocus}
|
||||
>
|
||||
<UsageRosterPanel
|
||||
providers={rosterProviders}
|
||||
|
||||
@@ -9,7 +9,8 @@ describe('formatPlanLabel', () => {
|
||||
})
|
||||
|
||||
it('title-cases multi-token plans across separators', () => {
|
||||
expect(formatPlanLabel('chatgpt_business')).toBe('Chatgpt Business')
|
||||
expect(formatPlanLabel('chatgpt_business')).toBe('ChatGPT Business')
|
||||
expect(formatPlanLabel('CHATGPT_PLUS')).toBe('ChatGPT Plus')
|
||||
expect(formatPlanLabel('team-plus')).toBe('Team Plus')
|
||||
expect(formatPlanLabel('pro trial')).toBe('Pro Trial')
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Pure formatting for the consolidated Usage roster, split out so it can be unit
|
||||
// tested without pulling in React / UI dependencies.
|
||||
|
||||
// "plus" -> "Plus", "chatgpt_business" -> "Chatgpt Business". Codex is the only
|
||||
// "plus" -> "Plus", "chatgpt_business" -> "ChatGPT Business". Codex is the only
|
||||
// provider that reports a plan today; others render just the name.
|
||||
export function formatPlanLabel(planType: string | null | undefined): string | null {
|
||||
const trimmed = planType?.trim()
|
||||
@@ -10,7 +10,12 @@ export function formatPlanLabel(planType: string | null | undefined): string | n
|
||||
}
|
||||
return trimmed
|
||||
.split(/[\s_-]+/)
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.map((word) => {
|
||||
const normalized = word.toLowerCase()
|
||||
return normalized === 'chatgpt'
|
||||
? 'ChatGPT'
|
||||
: normalized.charAt(0).toUpperCase() + normalized.slice(1)
|
||||
})
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user