Files
afriadirahman 8c6dad2a31
tests / ci (push) Canceled after 0s
Admin Panel
2026-09-29 14:57:37 +07:00

131 lines
3.3 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>
);
}
/** Validation message under a field. */
export function FieldError({ message }: { message?: string }) {
if (!message) {
return null;
}
return <p className="text-primary text-xs font-medium">{message}</p>;
}
/**
* Hidden field only bots fill in; the server rejects any submission that
* has a value here.
*/
export function Honeypot() {
return (
<input
type="text"
name="website"
tabIndex={-1}
autoComplete="off"
aria-hidden="true"
className="absolute -left-[9999px] h-0 w-0 opacity-0"
/>
);
}
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>
);
}