Field
Label, control and hint composed as one row.
18 digits, no spaces
npx shadcn@latest add https://ui.thjsecurity.tech/r/field.jsonPulls the component into components/ui/field.tsx and its token dependencies. The app owns the copy from then on.
(registry) label(registry) select
"use client"
import * as React from "react"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { cn } from "@/lib/utils"
/* Form rows. A Field is a label over a control with an optional hint; a
SelectField is a Field around the console's Select, fed a list of options.
Forms compose these instead of repeating the label-control markup. */
export function Field({
id,
label,
hint,
className,
children,
}: {
id?: string
label: React.ReactNode
hint?: React.ReactNode
className?: string
children: React.ReactNode
}) {
return (
<div className={cn("space-y-2", className)}>
<Label htmlFor={id}>{label}</Label>
{children}
{hint && <p className="text-[12px] text-paper-faint">{hint}</p>}
</div>
)
}
export interface Option<V extends string | number = string> {
value: V
label: React.ReactNode
}
/** A labelled select over a list of options; the value round-trips in its own type. */
export function SelectField<V extends string | number>({
id,
label,
hint,
value,
options,
onChange,
placeholder,
disabled,
className,
}: {
id: string
label: React.ReactNode
hint?: React.ReactNode
value: V
options: readonly Option<V>[]
onChange: (value: V) => void
placeholder?: string
disabled?: boolean
className?: string
}) {
const numeric = typeof value === "number"
return (
<Field id={id} label={label} hint={hint} className={className}>
<Select
value={String(value)}
onValueChange={(v) => onChange((numeric ? Number(v) : v) as V)}
disabled={disabled}
>
<SelectTrigger id={id}>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
{options.map((o) => (
<SelectItem key={String(o.value)} value={String(o.value)}>
{o.label}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
)
}