Skip to content
File

Blob: src/client/components/ui/input.tsx

typescript44 lines
1import type { InputHTMLAttributes } from "react";
2 
3interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
4 label?: string;
5 helperText?: string;
6 error?: string;
7}
8 
9export const Input = ({ label, helperText, error, className, id, ...rest }: InputProps) => {
10 const inputId = id ?? (label ? `input-${label.toLowerCase().replace(/\s+/g, "-")}` : undefined);
11 const errorId = inputId ? `${inputId}-error` : undefined;
12 const helperId = inputId ? `${inputId}-helper` : undefined;
13 
14 return (
15 <label className="block" htmlFor={inputId}>
16 {label ? <span className="mb-2 block text-sm font-medium text-zinc-300">{label}</span> : null}
17 <input
18 id={inputId}
19 className={[
20 "w-full rounded-xl border bg-zinc-800/80 px-4 py-2.5 text-zinc-100 transition-colors placeholder:text-zinc-500 focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-500/50 focus-visible:ring-offset-2 focus-visible:ring-offset-canvas",
21 error
22 ? "border-red-500/50 focus:border-red-500/70 focus:ring-1 focus:ring-red-500/30"
23 : "border-zinc-700/60 focus:border-accent-500/50 focus:ring-1 focus:ring-accent-500/30",
24 className,
25 ]
26 .filter(Boolean)
27 .join(" ")}
28 {...rest}
29 aria-invalid={error ? "true" : undefined}
30 aria-describedby={error ? errorId : helperText ? helperId : undefined}
31 />
32 {error ? (
33 <span id={errorId} className="mt-2 block text-xs text-red-400">
34 {error}
35 </span>
36 ) : helperText ? (
37 <span id={helperId} className="mt-2 block text-xs text-zinc-500">
38 {helperText}
39 </span>
40 ) : null}
41 </label>
42 );
43};