Install ReUI (MCP, skill, @reui registry, Tailwind v4, shadcn base-nova) and move the shared UI onto it: Button, Tag, Check, Input/Textarea, Segmented, Alert, Panel, Dialog, Tooltip, Menu and Select (Popover), Toast (sonner), the Table frame (DataGrid), the Dashboard KPI strip, the shnt Ops palette, Geist and the type scale. Design only: behaviour, texts and roles are unchanged. Tests that asserted the old DOM were updated; the tests now format numbers in English wherever they run. docs/reui-migration-plan.md records the plan and its decisions. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
69 lines
2.5 KiB
TypeScript
69 lines
2.5 KiB
TypeScript
import { useId, type ComponentProps, type InputHTMLAttributes, type ReactNode, type Ref, type TextareaHTMLAttributes } from 'react'
|
|
import { Input as ReuiInput } from '@/components/ui/input'
|
|
import { Textarea as ReuiTextarea } from '@/components/ui/textarea'
|
|
import { cn } from '@/lib/utils'
|
|
import textStyles from './text.module.css'
|
|
|
|
export function Field({ label, children }: { label: string; children: (id: string) => ReactNode }) {
|
|
const id = useId()
|
|
return (
|
|
<div className="flex flex-col gap-1.5">
|
|
<label className="text-xs font-medium text-muted-foreground" htmlFor={id}>
|
|
{label}
|
|
</label>
|
|
{children(id)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export type InputProps = InputHTMLAttributes<HTMLInputElement> & {
|
|
mono?: boolean
|
|
/** React 19 passes `ref` as a normal prop, but it still has to be declared. */
|
|
ref?: Ref<HTMLInputElement>
|
|
}
|
|
|
|
export function Input({ mono, className, ...rest }: InputProps) {
|
|
return (
|
|
<ReuiInput className={cn('text-sm', mono && textStyles.mono, className)} {...rest} />
|
|
)
|
|
}
|
|
|
|
/** A field with its label associated via `htmlFor`, which is what lets the tests
|
|
* query it with `getByLabelText` exactly as a screen reader would. */
|
|
export function LabeledInput({ label, mono, ...rest }: InputProps & { label: string }) {
|
|
return <Field label={label}>{(id) => <Input id={id} mono={mono} {...rest} />}</Field>
|
|
}
|
|
|
|
export type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|
mono?: boolean
|
|
ref?: Ref<HTMLTextAreaElement>
|
|
}
|
|
|
|
export function Textarea({ mono, className, ...rest }: TextareaProps) {
|
|
return (
|
|
<ReuiTextarea className={cn('text-sm', mono && textStyles.mono, className)} {...rest} />
|
|
)
|
|
}
|
|
|
|
/** A textarea with an associated label, so it can be queried by its name. */
|
|
export function LabeledTextarea({ label, mono, ...rest }: TextareaProps & { label: string }) {
|
|
return <Field label={label}>{(id) => <Textarea id={id} mono={mono} {...rest} />}</Field>
|
|
}
|
|
|
|
/*
|
|
The dropdown lives in `ui/Select`: it is no longer a native `<select>`, so it does
|
|
not take `SelectHTMLAttributes` but its list of options. It is re-exported from
|
|
here because the screens ask for it alongside the rest of the fields.
|
|
*/
|
|
import { Select, type Option } from './Select'
|
|
|
|
export { Select, type Option }
|
|
|
|
/** A dropdown with an associated label. Phases 6 and 7 asked for it. */
|
|
export function LabeledSelect({
|
|
label,
|
|
...rest
|
|
}: Omit<ComponentProps<typeof Select>, 'id'> & { label: string }) {
|
|
return <Field label={label}>{(id) => <Select id={id} {...rest} />}</Field>
|
|
}
|