refactor(desktop): DRY/elegance pass over PR-touched files

- Shared useDeepLinkHighlight hook collapses 3 near-identical settings
  deep-link effects (keys/mcp); config kept inline (distinct bail-clear).
- command-center: table-driven SECTION_ICONS + single errorText helper.
- clarify-tool: OPTION_ROW_CLASS + RadioDot extracted from option rows.
- desktop-controller: merge Cmd+K / Cmd+. into one keydown handler.
- statusbar-controls: hoist shared action class.
- Misc: drop redundant cn()/cursor-pointer/dead fields; tidy switch.
This commit is contained in:
Brooklyn Nicholson
2026-06-04 00:28:57 -05:00
parent 38acced687
commit bc9e33d66b
13 changed files with 181 additions and 197 deletions

View File

@ -657,11 +657,7 @@ interface ArtifactImageCardProps {
function ArtifactImageCard({ artifact, failedImage, onImageError, onOpenChat }: ArtifactImageCardProps) {
return (
<article
className={cn(
'group/artifact overflow-hidden rounded-lg border border-(--ui-stroke-tertiary) bg-(--ui-chat-bubble-background)'
)}
>
<article className="group/artifact overflow-hidden rounded-lg border border-(--ui-stroke-tertiary) bg-(--ui-chat-bubble-background)">
<div
className={cn(
'relative flex h-40 w-full items-center justify-center overflow-hidden border-b border-(--ui-stroke-tertiary) bg-(--ui-bg-quinary) p-1.5',
@ -738,9 +734,7 @@ function ArtifactCellAction({
return (
<button
className={cn(
'flex h-full w-full min-w-0 items-center gap-2 px-2.5 py-1.5 text-left text-[length:var(--conversation-caption-font-size)] leading-(--conversation-caption-line-height) font-normal text-(--ui-text-secondary) no-underline underline-offset-4 decoration-current/20 transition-colors hover:text-foreground hover:underline'
)}
className="flex h-full w-full min-w-0 items-center gap-2 px-2.5 py-1.5 text-left text-[length:var(--conversation-caption-font-size)] leading-(--conversation-caption-line-height) font-normal text-(--ui-text-secondary) no-underline underline-offset-4 decoration-current/20 transition-colors hover:text-foreground hover:underline"
onClick={onClick}
title={title}
type="button"

View File

@ -16,7 +16,7 @@ import {
} from '@/hermes'
import type { ActionStatusResponse, AnalyticsResponse, StatusResponse } from '@/hermes'
import { sessionTitle } from '@/lib/chat-runtime'
import { Activity, AlertCircle, BarChart3, Pin } from '@/lib/icons'
import { Activity, AlertCircle, BarChart3, type IconComponent, Pin } from '@/lib/icons'
import { exportSession } from '@/lib/session-export'
import { cn } from '@/lib/utils'
import { upsertDesktopActionTask } from '@/store/activity'
@ -54,6 +54,14 @@ const SECTION_DESCRIPTIONS: Record<CommandCenterSection, string> = {
usage: 'Token, cost, and skill activity over time'
}
const SECTION_ICONS: Record<CommandCenterSection, IconComponent> = {
sessions: Pin,
system: Activity,
usage: BarChart3
}
const errorText = (error: unknown): string => (error instanceof Error ? error.message : String(error))
function formatTimestamp(value?: number | null): string {
if (!value) {
return ''
@ -182,7 +190,7 @@ export function CommandCenterView({
setStatus(nextStatus)
setLogs(nextLogs.lines)
} catch (error) {
setSystemError(error instanceof Error ? error.message : String(error))
setSystemError(errorText(error))
} finally {
setSystemLoading(false)
}
@ -202,7 +210,7 @@ export function CommandCenterView({
}
} catch (error) {
if (usageRequestRef.current === requestId) {
setUsageError(error instanceof Error ? error.message : String(error))
setUsageError(errorText(error))
}
} finally {
if (usageRequestRef.current === requestId) {
@ -266,7 +274,7 @@ export function CommandCenterView({
upsertDesktopActionTask(pendingStatus)
}
} catch (error) {
setSystemError(error instanceof Error ? error.message : String(error))
setSystemError(errorText(error))
} finally {
void refreshSystem()
}
@ -281,7 +289,7 @@ export function CommandCenterView({
{SECTIONS.map(value => (
<OverlayNavItem
active={section === value}
icon={value === 'sessions' ? Pin : value === 'system' ? Activity : BarChart3}
icon={SECTION_ICONS[value]}
key={value}
label={SECTION_LABELS[value]}
onClick={() => setSection(value)}

View File

@ -201,25 +201,21 @@ export function DesktopController() {
}
}, [])
// Global command palette: Cmd/Ctrl+K from anywhere. Plain Cmd+K is reserved
// for the palette; the composer's "drain next queued" moved to Cmd+Shift+K.
// Global chrome shortcuts (plain Cmd/Ctrl, no alt/shift): Cmd+K → command
// palette (the composer's "drain next queued" moved to Cmd+Shift+K), Cmd+. →
// command center (sessions / system / usage).
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if ((event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === 'k') {
if (!(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey) {
return
}
const key = event.key.toLowerCase()
if (key === 'k') {
event.preventDefault()
toggleCommandPalette()
}
}
window.addEventListener('keydown', onKeyDown)
return () => window.removeEventListener('keydown', onKeyDown)
}, [])
// Cmd/Ctrl+. toggles the command center (sessions / system / usage).
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if ((event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key === '.') {
} else if (key === '.') {
event.preventDefault()
toggleCommandCenter()
}

View File

@ -126,7 +126,7 @@ export function AboutSettings() {
size="sm"
variant="textStrong"
>
{checking ? <Loader2 className="size-3 animate-spin" /> : null}
{checking && <Loader2 className="size-3 animate-spin" />}
{checking ? 'Checking…' : 'Check now'}
</Button>

View File

@ -289,12 +289,11 @@ export const SECTIONS: DesktopConfigSection[] = [
export interface ModeOption {
id: ThemeMode
label: string
description: string
icon: IconComponent
}
export const MODE_OPTIONS: ModeOption[] = [
{ id: 'light', label: 'Light', description: 'Bright desktop surfaces', icon: Sun },
{ id: 'dark', label: 'Dark', description: 'Low-glare workspace', icon: Moon },
{ id: 'system', label: 'System', description: 'Follow OS appearance', icon: Monitor }
{ id: 'light', label: 'Light', icon: Sun },
{ id: 'dark', label: 'Dark', icon: Moon },
{ id: 'system', label: 'System', icon: Monitor }
]

View File

@ -1,5 +1,4 @@
import { useEffect, useMemo, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
@ -13,6 +12,7 @@ import { CONTROL_TEXT } from './constants'
import { asText, prettyName, providerGroup, providerPriority, redactedValue, withoutKey } from './helpers'
import { LoadingState, Pill, SectionHeading, SettingsContent } from './primitives'
import type { EnvPatch, EnvRowProps, ProviderGroup } from './types'
import { useDeepLinkHighlight } from './use-deep-link-highlight'
interface EnvActionsProps {
varKey: string
@ -224,10 +224,13 @@ export function KeysSettings() {
const [revealed, setRevealed] = useState<Record<string, string>>({})
const [saving, setSaving] = useState<string | null>(null)
// Deep-link target from the command palette (?key=<ENV_VAR>): force-expand
// the matching provider group, scroll the row in, and flash it.
const [searchParams, setSearchParams] = useSearchParams()
const highlightKey = searchParams.get('key')
// Deep-link from the command palette (?key=<ENV_VAR>): force-expand the
// matching provider group, scroll the row in, and flash it.
const highlightKey = useDeepLinkHighlight({
elementId: key => `env-var-${key}`,
param: 'key',
ready: key => Boolean(vars?.[key])
})
// We used to hide ~80% of rows behind a global "Show advanced" toggle, but
// everything in this view is configuration-level — "advanced" was a poor
@ -259,38 +262,6 @@ export function KeysSettings() {
return () => void (cancelled = true)
}, [])
useEffect(() => {
if (!highlightKey || !vars || !vars[highlightKey]) {
return
}
// Group expansion is async (state), so defer the scroll a frame to let the
// target row mount before we look it up.
const scrollTimeout = window.setTimeout(() => {
const element = document.getElementById(`env-var-${highlightKey}`)
if (!element) {
return
}
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
element.classList.add('setting-field-highlight')
window.setTimeout(() => element.classList.remove('setting-field-highlight'), 1600)
}, 80)
setSearchParams(
previous => {
const next = new URLSearchParams(previous)
next.delete('key')
return next
},
{ replace: true }
)
return () => window.clearTimeout(scrollTimeout)
}, [highlightKey, setSearchParams, vars])
const providerGroups = useMemo<ProviderGroup[]>(() => {
if (!vars) {
return []

View File

@ -1,6 +1,5 @@
import { useStore } from '@nanostores/react'
import { useEffect, useMemo, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
@ -13,6 +12,7 @@ import { $activeSessionId } from '@/store/session'
import type { HermesConfigRecord } from '@/types/hermes'
import { EmptyState, LoadingState, Pill, SettingsContent } from './primitives'
import { useDeepLinkHighlight } from './use-deep-link-highlight'
interface McpSettingsProps {
gateway?: HermesGateway | null
@ -46,7 +46,6 @@ export function McpSettings({ gateway, onConfigSaved }: McpSettingsProps) {
const activeSessionId = useStore($activeSessionId)
const [config, setConfig] = useState<HermesConfigRecord | null>(null)
const [selected, setSelected] = useState<string | null>(null)
const [searchParams, setSearchParams] = useSearchParams()
const [name, setName] = useState('')
const [body, setBody] = useState('')
const [saving, setSaving] = useState(false)
@ -73,36 +72,13 @@ export function McpSettings({ gateway, onConfigSaved }: McpSettingsProps) {
const servers = useMemo(() => getServers(config), [config])
const names = useMemo(() => Object.keys(servers).sort(), [servers])
// Deep-link target from the command palette (?server=<name>): select it and
// scroll the list entry into view.
const targetServer = searchParams.get('server')
useEffect(() => {
if (!targetServer || !config || !(targetServer in servers)) {
return
}
setSelected(targetServer)
const scrollTimeout = window.setTimeout(() => {
const element = document.getElementById(`mcp-server-${targetServer}`)
element?.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
element?.classList.add('setting-field-highlight')
window.setTimeout(() => element?.classList.remove('setting-field-highlight'), 1600)
}, 80)
setSearchParams(
previous => {
const next = new URLSearchParams(previous)
next.delete('server')
return next
},
{ replace: true }
)
return () => window.clearTimeout(scrollTimeout)
}, [config, servers, setSearchParams, targetServer])
useDeepLinkHighlight({
block: 'nearest',
elementId: serverName => `mcp-server-${serverName}`,
onResolve: setSelected,
param: 'server',
ready: serverName => Boolean(config) && serverName in servers
})
useEffect(() => {
const server = selected ? servers[selected] : null

View File

@ -1,5 +1,4 @@
import { useCallback, useEffect, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { Button } from '@/components/ui/button'
import { deleteSession, listSessions, setSessionArchived } from '@/hermes'
@ -11,6 +10,7 @@ import { setSessions } from '@/store/session'
import type { SessionInfo } from '@/types/hermes'
import { EmptyState, ListRow, LoadingState, SectionHeading, SettingsContent } from './primitives'
import { useDeepLinkHighlight } from './use-deep-link-highlight'
const ARCHIVED_FETCH_LIMIT = 200
@ -34,7 +34,6 @@ export function SessionsSettings() {
const [sessions, setLocalSessions] = useState<SessionInfo[]>([])
const [loading, setLoading] = useState(true)
const [busyId, setBusyId] = useState<string | null>(null)
const [searchParams, setSearchParams] = useSearchParams()
const load = useCallback(async () => {
setLoading(true)
@ -88,39 +87,11 @@ export function SessionsSettings() {
}
}, [])
// Deep-link target from the command palette (?session=<id>): scroll the row
// into view and flash it.
const targetSession = searchParams.get('session')
useEffect(() => {
if (!targetSession || loading || !sessions.some(session => session.id === targetSession)) {
return
}
const scrollTimeout = window.setTimeout(() => {
const element = document.getElementById(`archived-session-${targetSession}`)
if (!element) {
return
}
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
element.classList.add('setting-field-highlight')
window.setTimeout(() => element.classList.remove('setting-field-highlight'), 1600)
}, 80)
setSearchParams(
previous => {
const next = new URLSearchParams(previous)
next.delete('session')
return next
},
{ replace: true }
)
return () => window.clearTimeout(scrollTimeout)
}, [loading, sessions, setSearchParams, targetSession])
useDeepLinkHighlight({
elementId: id => `archived-session-${id}`,
param: 'session',
ready: id => !loading && sessions.some(session => session.id === id)
})
if (loading) {
return <LoadingState label="Loading archived sessions…" />
@ -152,31 +123,31 @@ export function SessionsSettings() {
<div className="scroll-mt-6 rounded-lg" id={`archived-session-${session.id}`} key={session.id}>
<ListRow
action={
<div className="flex items-center gap-1.5">
<Button
disabled={busy}
onClick={() => void unarchive(session)}
size="sm"
type="button"
variant="textStrong"
>
{busy ? <Loader2 className="size-3.5 animate-spin" /> : <ArchiveOff className="size-3.5" />}
<span>Unarchive</span>
</Button>
<Button
aria-label="Delete permanently"
className="text-muted-foreground hover:text-destructive"
disabled={busy}
onClick={() => void remove(session)}
size="icon"
title="Delete permanently"
type="button"
variant="ghost"
>
<Trash2 className="size-3.5" />
</Button>
</div>
}
<div className="flex items-center gap-1.5">
<Button
disabled={busy}
onClick={() => void unarchive(session)}
size="sm"
type="button"
variant="textStrong"
>
{busy ? <Loader2 className="size-3.5 animate-spin" /> : <ArchiveOff className="size-3.5" />}
<span>Unarchive</span>
</Button>
<Button
aria-label="Delete permanently"
className="text-muted-foreground hover:text-destructive"
disabled={busy}
onClick={() => void remove(session)}
size="icon"
title="Delete permanently"
type="button"
variant="ghost"
>
<Trash2 className="size-3.5" />
</Button>
</div>
}
description={session.preview || undefined}
hint={label ? `${label} · ${session.message_count} messages` : `${session.message_count} messages`}
title={sessionTitle(session)}
@ -213,7 +184,10 @@ function DefaultProjectDirSetting() {
let alive = true
void settings.getDefaultProjectDir().then(result => {
if (!alive) {return}
if (!alive) {
return
}
setDir(result.dir)
setFallback(result.defaultLabel)
})
@ -226,7 +200,9 @@ function DefaultProjectDirSetting() {
const choose = useCallback(async () => {
const settings = window.hermesDesktop?.settings
if (!settings) {return}
if (!settings) {
return
}
setBusy(true)
@ -250,7 +226,9 @@ function DefaultProjectDirSetting() {
const clear = useCallback(async () => {
const settings = window.hermesDesktop?.settings
if (!settings) {return}
if (!settings) {
return
}
setBusy(true)

View File

@ -0,0 +1,60 @@
import { useEffect } from 'react'
import { useSearchParams } from 'react-router-dom'
interface DeepLinkHighlightOptions {
param: string
ready: (target: string) => boolean
elementId: (target: string) => string
onResolve?: (target: string) => void
block?: ScrollLogicalPosition
}
// Deep-link from the command palette (?<param>=<id>): once the target row is
// renderable, scroll it into view and flash it, then drop the param so it
// doesn't re-fire. Returns the pending target (null once consumed) so callers
// can force the row open before it mounts.
export function useDeepLinkHighlight({
param,
ready,
elementId,
onResolve,
block = 'center'
}: DeepLinkHighlightOptions): null | string {
const [searchParams, setSearchParams] = useSearchParams()
const target = searchParams.get(param)
useEffect(() => {
if (!target || !ready(target)) {
return
}
onResolve?.(target)
// Defer a frame so async state (expansion, selection) mounts the row first.
const scrollTimeout = window.setTimeout(() => {
const element = document.getElementById(elementId(target))
if (!element) {
return
}
element.scrollIntoView({ behavior: 'smooth', block })
element.classList.add('setting-field-highlight')
window.setTimeout(() => element.classList.remove('setting-field-highlight'), 1600)
}, 80)
setSearchParams(
previous => {
const next = new URLSearchParams(previous)
next.delete(param)
return next
},
{ replace: true }
)
return () => window.clearTimeout(scrollTimeout)
}, [block, elementId, onResolve, param, ready, setSearchParams, target])
return target
}

View File

@ -4,6 +4,11 @@ import { useNavigate } from 'react-router-dom'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
import { cn } from '@/lib/utils'
// Shared chrome styling for interactive statusbar items (button / link / menu
// trigger). The 'text' variant intentionally omits hover/transition/disabled.
const STATUSBAR_ACTION_CLASS =
'inline-flex h-full items-center gap-1 rounded-none px-1.5 text-[0.6875rem] text-(--ui-text-tertiary) transition-colors hover:bg-(--chrome-action-hover) hover:text-foreground disabled:cursor-default disabled:opacity-45'
export interface StatusbarMenuItem {
id: string
icon?: ReactNode
@ -93,10 +98,7 @@ function StatusbarItemView({ item, navigate }: { item: StatusbarItem; navigate:
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
className={cn(
'inline-flex h-full items-center gap-1 rounded-none px-1.5 text-[0.6875rem] text-(--ui-text-tertiary) transition-colors hover:bg-(--chrome-action-hover) hover:text-foreground disabled:cursor-default disabled:opacity-45',
item.className
)}
className={cn(STATUSBAR_ACTION_CLASS, item.className)}
disabled={item.disabled}
title={title}
type="button"
@ -167,10 +169,7 @@ function StatusbarItemView({ item, navigate }: { item: StatusbarItem; navigate:
if (item.href || item.variant === 'link') {
return (
<a
className={cn(
'inline-flex h-full items-center gap-1 rounded-none px-1.5 text-[0.6875rem] text-(--ui-text-tertiary) transition-colors hover:bg-(--chrome-action-hover) hover:text-foreground disabled:cursor-default disabled:opacity-45',
item.className
)}
className={cn(STATUSBAR_ACTION_CLASS, item.className)}
href={item.href}
rel="noreferrer"
target="_blank"
@ -183,10 +182,7 @@ function StatusbarItemView({ item, navigate }: { item: StatusbarItem; navigate:
return (
<button
className={cn(
'inline-flex h-full items-center gap-1 rounded-none px-1.5 text-[0.6875rem] text-(--ui-text-tertiary) transition-colors hover:bg-(--chrome-action-hover) hover:text-foreground disabled:cursor-default disabled:opacity-45',
item.className
)}
className={cn(STATUSBAR_ACTION_CLASS, item.className)}
disabled={item.disabled}
onClick={() => {
if (item.to) {

View File

@ -33,6 +33,23 @@ function readClarifyArgs(args: unknown): ClarifyArgs {
}
}
// Choice and "Other" rows share a layout; only color/hover differs.
const OPTION_ROW_CLASS = 'flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm transition-colors'
function RadioDot({ selected }: { selected: boolean }) {
return (
<span
aria-hidden
className={cn(
'grid size-3.5 shrink-0 place-items-center rounded-full border transition-colors',
selected ? 'border-primary' : 'border-muted-foreground/40'
)}
>
{selected && <span className="size-1.5 rounded-full bg-primary" />}
</span>
)
}
export const ClarifyTool = (props: ToolCallMessagePartProps) => {
const isPending = props.result === undefined
@ -164,8 +181,8 @@ function ClarifyToolPending({ args }: ToolCallMessagePartProps) {
{choices.map((choice, index) => (
<button
className={cn(
'flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm text-foreground/95',
'transition-colors hover:bg-accent/60 disabled:cursor-not-allowed disabled:opacity-55',
OPTION_ROW_CLASS,
'text-foreground/95 hover:bg-accent/60 disabled:cursor-not-allowed disabled:opacity-55',
selectedChoice === choice && 'bg-accent/60'
)}
data-choice
@ -177,24 +194,13 @@ function ClarifyToolPending({ args }: ToolCallMessagePartProps) {
}}
type="button"
>
<span
aria-hidden
className={cn(
'grid size-3.5 shrink-0 place-items-center rounded-full border transition-colors',
selectedChoice === choice ? 'border-primary' : 'border-muted-foreground/40'
)}
>
{selectedChoice === choice && <span className="size-1.5 rounded-full bg-primary" />}
</span>
<RadioDot selected={selectedChoice === choice} />
<span className="flex-1 wrap-anywhere">{choice}</span>
{selectedChoice === choice && <Check aria-hidden className="size-4 shrink-0 text-primary" />}
</button>
))}
<button
className={cn(
'flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm text-muted-foreground',
'transition-colors hover:bg-accent/40 hover:text-foreground'
)}
className={cn(OPTION_ROW_CLASS, 'text-muted-foreground hover:bg-accent/40 hover:text-foreground')}
disabled={submitting}
onClick={() => {
setTyping(true)
@ -202,7 +208,7 @@ function ClarifyToolPending({ args }: ToolCallMessagePartProps) {
}}
type="button"
>
<span aria-hidden className="size-3.5 shrink-0 rounded-full border border-muted-foreground/40" />
<RadioDot selected={false} />
<span className="flex-1">Other (type your answer)</span>
</button>
</div>

View File

@ -110,7 +110,7 @@ function DropdownMenuItem({
return (
<DropdownMenuPrimitive.Item
className={cn(
"relative flex cursor-pointer 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!",
"relative flex 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}
@ -131,7 +131,7 @@ function DropdownMenuCheckboxItem({
<DropdownMenuPrimitive.CheckboxItem
checked={checked}
className={cn(
"relative flex cursor-pointer 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",
"relative flex 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="dropdown-menu-checkbox-item"
@ -157,7 +157,7 @@ function DropdownMenuRadioItem({
return (
<DropdownMenuPrimitive.RadioItem
className={cn(
"relative flex cursor-pointer 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",
"relative flex 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="dropdown-menu-radio-item"

View File

@ -41,7 +41,7 @@ function Switch({
}: React.ComponentProps<typeof SwitchPrimitive.Root> & VariantProps<typeof switchVariants>) {
return (
<SwitchPrimitive.Root className={cn(switchVariants({ size }), className)} data-slot="switch" {...props}>
<SwitchPrimitive.Thumb className={cn(switchThumbVariants({ size }))} data-slot="switch-thumb" />
<SwitchPrimitive.Thumb className={switchThumbVariants({ size })} data-slot="switch-thumb" />
</SwitchPrimitive.Root>
)
}