Skip to content
File

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

typescript46 lines
1import type { ComponentProps } from "react";
2 
3type InputProps = {
4 label?: string;
5 helperText?: string;
6 error?: string;
7} & Omit<ComponentProps<"input">, "className">;
8 
9const inputClasses =
10 "w-full rounded-xl border border-zinc-300 dark:border-zinc-700/60 bg-white dark:bg-zinc-800/80 px-4 py-2.5 text-zinc-900 dark:text-zinc-100 placeholder:text-zinc-500 focus:border-accent-500/50 focus:ring-1 focus:ring-accent-500/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-500/50 focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
11 
12const errorInputClasses =
13 "w-full rounded-xl border border-red-500/40 bg-white dark:bg-zinc-800/80 px-4 py-2.5 text-zinc-900 dark:text-zinc-100 placeholder:text-zinc-500 focus:border-red-500/50 focus:ring-1 focus:ring-red-500/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500/50 focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
14 
15export function Input({ label, helperText, error, id, ...rest }: InputProps) {
16 const inputId = id || (label ? label.toLowerCase().replace(/\s+/g, "-") : undefined);
17 const errorId = error && inputId ? `${inputId}-error` : undefined;
18 
19 return (
20 <div>
21 {label ? (
22 <label
23 htmlFor={inputId}
24 className="mb-1.5 block text-sm font-medium text-zinc-700 dark:text-zinc-300"
25 >
26 {label}
27 </label>
28 ) : null}
29 <input
30 id={inputId}
31 className={error ? errorInputClasses : inputClasses}
32 aria-describedby={errorId}
33 aria-invalid={error ? true : undefined}
34 {...rest}
35 />
36 {error ? (
37 <p id={errorId} className="mt-1.5 text-sm text-red-600 dark:text-red-400">
38 {error}
39 </p>
40 ) : helperText ? (
41 <p className="mt-1.5 text-sm text-zinc-500">{helperText}</p>
42 ) : null}
43 </div>
44 );
45}