openreader/src/components/ui/field.tsx
2026-05-31 22:02:28 -06:00

107 lines
2.8 KiB
TypeScript

'use client';
import { useId, type ReactNode } from 'react';
import { cn } from './cn';
import { Switch } from './switch';
export function Field({
label,
hint,
className,
children,
}: {
label?: string;
hint?: string;
className?: string;
children: ReactNode;
}) {
return (
<div className={cn('space-y-1', className)}>
{label ? <label className="block text-[11px] font-semibold uppercase tracking-wide text-faint">{label}</label> : null}
{children}
{hint ? <p className="text-[11px] text-faint">{hint}</p> : null}
</div>
);
}
export function ToggleRow({
label,
description,
checked,
onChange,
disabled = false,
right,
variant = 'card',
}: {
label: string;
description: string;
checked: boolean;
onChange: (checked: boolean) => void;
disabled?: boolean;
right?: ReactNode;
variant?: 'card' | 'flat';
}) {
const labelId = useId();
const descId = useId();
const rowClass =
variant === 'flat'
? 'px-0.5 pt-1 pb-2 border-b border-line-soft last:border-b-0 transition-colors duration-fast ease-standard'
: 'rounded-md border border-line bg-surface px-2.5 py-1.5 transition-colors duration-fast ease-standard';
const handleTextToggle = () => {
if (!disabled) onChange(!checked);
};
return (
<div className={rowClass}>
<div className="flex items-start gap-2.5">
<div
className={cn('flex-1 min-w-0 space-y-0.5', disabled ? '' : 'cursor-pointer')}
onClick={handleTextToggle}
>
<span id={labelId} className="block text-sm font-medium leading-5 text-foreground">{label}</span>
<span id={descId} className="block text-xs leading-4 text-soft">{description}</span>
</div>
{right ? <div className="shrink-0 self-start pl-1.5">{right}</div> : null}
<Switch
checked={checked}
onChange={onChange}
disabled={disabled}
size="md"
ariaLabelledBy={labelId}
ariaDescribedBy={descId}
/>
</div>
</div>
);
}
export function CheckItem({
label,
checked,
onChange,
disabled = false,
}: {
label: string;
checked: boolean;
onChange: (checked: boolean) => void;
disabled?: boolean;
}) {
const labelId = useId();
const handleTextToggle = () => {
if (!disabled) onChange(!checked);
};
return (
<div className="flex items-center justify-between gap-2 py-0.5 group">
<span
id={labelId}
onClick={handleTextToggle}
className={cn(
'flex-1 min-w-0 truncate text-xs leading-4 text-foreground select-none transition-colors duration-fast ease-standard group-hover:text-accent',
disabled ? '' : 'cursor-pointer',
)}
>
{label}
</span>
<Switch checked={checked} onChange={onChange} disabled={disabled} size="sm" ariaLabelledBy={labelId} />
</div>
);
}