Files
hermes-agent/apps/desktop/src/app/settings/primitives.tsx
T

116 lines
3.1 KiB
TypeScript

import type { ReactNode } from 'react'
import { PageLoader } from '@/components/page-loader'
import { Button } from '@/components/ui/button'
import type { LucideIcon } from '@/lib/icons'
import { cn } from '@/lib/utils'
export function SettingsContent({ children }: { children: ReactNode }) {
return (
<section className="min-h-0 overflow-hidden">
<div className="h-full min-h-0 overflow-y-auto px-8 py-6 pb-24">
<div className="mx-auto w-full max-w-5xl">{children}</div>
</div>
</section>
)
}
export function Pill({ tone = 'muted', children }: { tone?: 'muted' | 'primary'; children: ReactNode }) {
return (
<span
className={cn(
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[0.66rem]',
tone === 'primary' ? 'bg-primary/10 text-primary' : 'bg-muted text-muted-foreground'
)}
>
{children}
</span>
)
}
export function SectionHeading({ icon: Icon, title, meta }: { icon: LucideIcon; title: string; meta?: string }) {
return (
<div className="mb-3 flex items-center gap-2 pt-3.5 text-sm font-medium">
<Icon className="size-4 text-muted-foreground" />
<span>{title}</span>
{meta && <Pill>{meta}</Pill>}
</div>
)
}
export function NavLink({
icon: Icon,
label,
active,
onClick
}: {
icon: LucideIcon
label: string
active: boolean
onClick: () => void
}) {
return (
<Button
className={cn(
'flex min-h-8 w-full justify-start gap-2 rounded-lg px-2.5 text-left text-sm transition',
active ? 'bg-muted text-foreground' : 'text-foreground/80 hover:bg-muted/70'
)}
onClick={onClick}
size="sm"
type="button"
variant="ghost"
>
<Icon className="size-4 shrink-0" />
<span className="min-w-0 flex-1 truncate">{label}</span>
</Button>
)
}
export function ListRow({
title,
description,
hint,
action,
below,
wide = false
}: {
title: ReactNode
description?: ReactNode
hint?: ReactNode
action?: ReactNode
below?: ReactNode
wide?: boolean
}) {
return (
<div
className={cn(
'grid gap-4 py-3.5 sm:grid-cols-[minmax(0,1fr)_minmax(15rem,22rem)] sm:items-center',
wide && 'sm:grid-cols-1 sm:items-start'
)}
>
<div className="min-w-0">
<div className="text-sm font-medium text-foreground">{title}</div>
{description && <div className="mt-1 text-xs leading-5 text-muted-foreground">{description}</div>}
{hint && <div className="mt-1 block font-mono text-[0.68rem] text-muted-foreground/45">{hint}</div>}
{below}
</div>
{action && <div className={cn('min-w-0', !wide && 'sm:justify-self-end')}>{action}</div>}
</div>
)
}
export function LoadingState({ label }: { label: string }) {
return <PageLoader label={label} />
}
export function EmptyState({ title, description }: { title: string; description: string }) {
return (
<div className="grid min-h-48 place-items-center text-center">
<div>
<div className="text-sm font-medium">{title}</div>
<div className="mt-1 text-xs text-muted-foreground">{description}</div>
</div>
</div>
)
}