hermes-agent/apps/desktop/src/components/ui/segmented-control.tsx
Brooklyn Nicholson ac9de2e80c feat(desktop): global Cmd+K palette + UI consistency overhaul
Builds on the clarify/needs-input work with a cross-cutting pass to make
the desktop surfaces feel like one app.

- Global Cmd+K command palette (cmdk): nav, settings deep-links, async
  API-key / MCP-server / archived-session groups, reusable theme sub-page
  (light/dark groups, stays open on pick), loop nav, fuzzy match. Replaces
  per-page settings search.
- Shared SearchField: borderless, underline-on-focus, `field-sizing`
  auto-width. Unifies sessions sidebar, pages, overlays, command center,
  cron; drops bespoke OverlaySearchInput.
- Cron & Profiles converted to OverlayView; flat token-driven panels
  (no card-in-card / divider borders) matching command center.
- `r` refresh hotkey via useRefreshHotkey; drop the visible refresh buttons.
- Button text/textStrong link variants applied across settings & views;
  shared PAGE_INSET_X content gutters.
- Math/ascii loaders replace "Loading…" text placeholders; x-icon close
  over text "Close"; cursor-pointer at the dropdown/select primitive level.
2026-06-03 23:45:45 -05:00

51 lines
1.5 KiB
TypeScript

import type { IconComponent } from '@/lib/icons'
import { cn } from '@/lib/utils'
export interface SegmentedControlOption<T extends string> {
id: T
label: string
icon?: IconComponent
}
interface SegmentedControlProps<T extends string> {
options: readonly SegmentedControlOption<T>[]
value: T
onChange: (id: T) => void
className?: string
}
/**
* Grouped one-row toggle used for small mutually-exclusive choices
* (color mode, tool-call display, usage period, etc.). Flat by design —
* no per-option borders, just a tinted track with a raised active pill.
*/
export function SegmentedControl<T extends string>({ options, value, onChange, className }: SegmentedControlProps<T>) {
return (
<div
className={cn(
'inline-grid w-fit auto-cols-fr grid-flow-col gap-0.5 rounded-[5px] bg-(--ui-bg-tertiary) p-0.5',
className
)}
>
{options.map(({ id, label, icon: Icon }) => {
const active = value === id
return (
<button
aria-pressed={active}
className={cn(
'flex items-center justify-center gap-1 rounded-[3px] px-2.5 py-0.5 text-[0.6875rem] font-medium transition-colors',
active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'
)}
key={id}
onClick={() => onChange(id)}
type="button"
>
{Icon && <Icon className="size-3" />}
{label}
</button>
)
})}
</div>
)
}