mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
The status bar shipped every affordance it had, so approvals, the terminal toggle, agents, cron and webhooks sat there permanently for users who never touched them. Those five now start hidden and the bar owns a context menu that turns them back on, persisted per install. Items opt in by naming themselves with `toggleLabel`, so a plugin contribution that doesn't opt in always shows; the system icon and the version/update pills are listed but locked on, since hiding the way back into settings strands the user. Preferences store the hidden set rather than the visible one, so an item added to the bar in a later version appears for existing users instead of staying silently off.
180 lines
7.5 KiB
TypeScript
180 lines
7.5 KiB
TypeScript
import { ContextMenu as ContextMenuPrimitive } from 'radix-ui'
|
|
import * as React from 'react'
|
|
|
|
import { Codicon } from '@/components/ui/codicon'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
function ContextMenu({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
|
|
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
|
|
}
|
|
|
|
function ContextMenuPortal({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
|
|
return <ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
|
|
}
|
|
|
|
function ContextMenuTrigger({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
|
|
return <ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
|
|
}
|
|
|
|
function ContextMenuGroup({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
|
|
return <ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
|
|
}
|
|
|
|
function ContextMenuContent({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
|
|
return (
|
|
<ContextMenuPrimitive.Portal>
|
|
<ContextMenuPrimitive.Content
|
|
className={cn(
|
|
'z-50 max-h-(--radix-context-menu-content-available-height) min-w-36 origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg border border-(--ui-stroke-secondary) bg-[color-mix(in_srgb,var(--ui-bg-elevated)_96%,transparent)] p-1 text-[length:var(--conversation-text-font-size)] text-popover-foreground shadow-md backdrop-blur-md data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
className
|
|
)}
|
|
data-slot="context-menu-content"
|
|
{...props}
|
|
/>
|
|
</ContextMenuPrimitive.Portal>
|
|
)
|
|
}
|
|
|
|
function ContextMenuItem({
|
|
className,
|
|
inset,
|
|
variant = 'default',
|
|
...props
|
|
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
|
|
inset?: boolean
|
|
variant?: 'default' | 'destructive'
|
|
}) {
|
|
return (
|
|
<ContextMenuPrimitive.Item
|
|
className={cn(
|
|
"relative flex cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs outline-hidden select-none focus:bg-(--ui-control-active-background) focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 [&_svg:not([class*='text-'])]:text-(--ui-text-tertiary) data-[variant=destructive]:*:[svg]:text-destructive!",
|
|
className
|
|
)}
|
|
data-inset={inset}
|
|
data-slot="context-menu-item"
|
|
data-variant={variant}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function ContextMenuCheckboxItem({
|
|
className,
|
|
children,
|
|
checked,
|
|
...props
|
|
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
|
|
return (
|
|
<ContextMenuPrimitive.CheckboxItem
|
|
checked={checked}
|
|
className={cn(
|
|
"relative flex cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs outline-hidden select-none focus:bg-(--ui-control-active-background) focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
|
className
|
|
)}
|
|
data-slot="context-menu-checkbox-item"
|
|
{...props}
|
|
>
|
|
{children}
|
|
<ContextMenuPrimitive.ItemIndicator className="ml-auto flex items-center pl-2 text-foreground">
|
|
<Codicon name="check" size="0.75rem" />
|
|
</ContextMenuPrimitive.ItemIndicator>
|
|
</ContextMenuPrimitive.CheckboxItem>
|
|
)
|
|
}
|
|
|
|
function ContextMenuLabel({
|
|
className,
|
|
inset,
|
|
...props
|
|
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
|
|
inset?: boolean
|
|
}) {
|
|
return (
|
|
<ContextMenuPrimitive.Label
|
|
className={cn('px-2 py-1 text-xs font-medium text-(--ui-text-tertiary) data-[inset]:pl-7', className)}
|
|
data-inset={inset}
|
|
data-slot="context-menu-label"
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function ContextMenuSeparator({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
|
|
return (
|
|
<ContextMenuPrimitive.Separator
|
|
className={cn('-mx-1 my-1 h-px bg-(--ui-stroke-tertiary)', className)}
|
|
data-slot="context-menu-separator"
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function ContextMenuSub({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
|
|
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />
|
|
}
|
|
|
|
function ContextMenuSubTrigger({
|
|
className,
|
|
inset,
|
|
children,
|
|
...props
|
|
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
|
|
inset?: boolean
|
|
}) {
|
|
return (
|
|
<ContextMenuPrimitive.SubTrigger
|
|
className={cn(
|
|
"flex cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs outline-hidden select-none focus:bg-(--ui-control-active-background) focus:text-foreground data-[inset]:pl-7 data-[state=open]:bg-(--ui-control-active-background) data-[state=open]:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 [&_svg:not([class*='text-'])]:text-(--ui-text-tertiary)",
|
|
className
|
|
)}
|
|
data-inset={inset}
|
|
data-slot="context-menu-sub-trigger"
|
|
{...props}
|
|
>
|
|
{children}
|
|
<Codicon className="ml-auto text-(--ui-text-tertiary)" name="chevron-right" size="1rem" />
|
|
</ContextMenuPrimitive.SubTrigger>
|
|
)
|
|
}
|
|
|
|
function ContextMenuSubContent({
|
|
className,
|
|
collisionPadding = 8,
|
|
...props
|
|
}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
|
|
return (
|
|
// Portal the submenu out of the parent Content so it escapes that Content's
|
|
// `overflow` clip — without this a submenu opening from a scrollable /
|
|
// overflow-hidden menu gets visually cut off at the parent's edges. Radix
|
|
// Popper still anchors it to the SubTrigger, so portaling is safe. Mirrors
|
|
// DropdownMenuSubContent.
|
|
<ContextMenuPrimitive.Portal>
|
|
<ContextMenuPrimitive.SubContent
|
|
className={cn(
|
|
// `max-h-80` (not the Radix available-height var, which is published
|
|
// only on Content) so a long submenu scrolls instead of collapsing.
|
|
'dt-portal-scrollbar z-50 max-h-80 min-w-36 origin-(--radix-context-menu-content-transform-origin) overflow-y-auto rounded-lg border border-(--ui-stroke-secondary) bg-[color-mix(in_srgb,var(--ui-bg-elevated)_96%,transparent)] p-1 text-[length:var(--conversation-text-font-size)] text-popover-foreground shadow-md backdrop-blur-md data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
className
|
|
)}
|
|
collisionPadding={collisionPadding}
|
|
data-slot="context-menu-sub-content"
|
|
{...props}
|
|
/>
|
|
</ContextMenuPrimitive.Portal>
|
|
)
|
|
}
|
|
|
|
export {
|
|
ContextMenu,
|
|
ContextMenuCheckboxItem,
|
|
ContextMenuContent,
|
|
ContextMenuGroup,
|
|
ContextMenuItem,
|
|
ContextMenuLabel,
|
|
ContextMenuPortal,
|
|
ContextMenuSeparator,
|
|
ContextMenuSub,
|
|
ContextMenuSubContent,
|
|
ContextMenuSubTrigger,
|
|
ContextMenuTrigger
|
|
}
|