105 lines
2.7 KiB
TypeScript
105 lines
2.7 KiB
TypeScript
import type {
|
|
InputHTMLAttributes,
|
|
ReactNode,
|
|
SelectHTMLAttributes,
|
|
TextareaHTMLAttributes,
|
|
} from 'react';
|
|
import Icon from '@/components/site/icon';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
const controlClasses =
|
|
'w-full rounded-xl border border-outline-variant/60 bg-surface-container-lowest px-4 py-2.5 text-sm text-on-surface transition-colors placeholder:text-on-surface-variant/50 focus:border-primary focus:outline-hidden';
|
|
|
|
type FieldProps = {
|
|
id: string;
|
|
label: string;
|
|
required?: boolean;
|
|
className?: string;
|
|
children: ReactNode;
|
|
};
|
|
|
|
export function Field({
|
|
id,
|
|
label,
|
|
required,
|
|
className,
|
|
children,
|
|
}: FieldProps) {
|
|
return (
|
|
<div className={cn('flex flex-col gap-1.5', className)}>
|
|
<label
|
|
htmlFor={id}
|
|
className="text-on-surface font-mono text-xs font-medium"
|
|
>
|
|
{label}
|
|
{required && ' *'}
|
|
</label>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function TextInput({
|
|
className,
|
|
...props
|
|
}: InputHTMLAttributes<HTMLInputElement>) {
|
|
return <input className={cn(controlClasses, className)} {...props} />;
|
|
}
|
|
|
|
export function SelectInput({
|
|
options,
|
|
placeholder,
|
|
className,
|
|
...props
|
|
}: SelectHTMLAttributes<HTMLSelectElement> & {
|
|
options: ReadonlyArray<{ value: string; label: string }>;
|
|
placeholder?: string;
|
|
}) {
|
|
return (
|
|
<select
|
|
className={cn(controlClasses, 'cursor-pointer', className)}
|
|
defaultValue={placeholder ? '' : undefined}
|
|
{...props}
|
|
>
|
|
{placeholder && (
|
|
<option value="" disabled>
|
|
{placeholder}
|
|
</option>
|
|
)}
|
|
{options.map((option) => (
|
|
<option key={option.value} value={option.value}>
|
|
{option.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
);
|
|
}
|
|
|
|
export function TextArea({
|
|
className,
|
|
...props
|
|
}: TextareaHTMLAttributes<HTMLTextAreaElement>) {
|
|
return <textarea className={cn(controlClasses, className)} {...props} />;
|
|
}
|
|
|
|
export function FormSuccess({
|
|
title,
|
|
message,
|
|
}: {
|
|
title: string;
|
|
message: string;
|
|
}) {
|
|
return (
|
|
<div
|
|
role="status"
|
|
className="border-primary/40 bg-surface-container-lowest flex flex-col items-center gap-2 rounded-xl border p-6 text-center"
|
|
>
|
|
<Icon name="check_circle" className="text-primary text-4xl" />
|
|
<span className="font-display text-on-surface text-base font-bold">
|
|
{title}
|
|
</span>
|
|
<p className="text-on-surface-variant text-xs">{message}</p>
|
|
</div>
|
|
);
|
|
}
|