Skip to content
File

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

typescript48 lines
1import { type InputHTMLAttributes, type Ref, useId } from "react";
2 
3export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
4 label?: string;
5 helper?: string;
6 error?: string;
7 ref?: Ref<HTMLInputElement>;
8}
9 
10export const Input = ({ ref, label, helper, error, id, className = "", ...rest }: InputProps) => {
11 const generated = useId();
12 const inputId = id ?? generated;
13 const helperId = helper ? `${inputId}-helper` : undefined;
14 const errorId = error ? `${inputId}-error` : undefined;
15 const describedBy = [helperId, errorId].filter(Boolean).join(" ") || undefined;
16 
17 return (
18 <div className="flex flex-col gap-1.5">
19 {label ? (
20 <label htmlFor={inputId} className="text-sm font-medium text-zinc-200">
21 {label}
22 </label>
23 ) : null}
24 <input
25 ref={ref}
26 id={inputId}
27 aria-describedby={describedBy}
28 aria-invalid={error ? "true" : undefined}
29 className={`w-full rounded-xl border bg-zinc-800/80 px-4 py-2.5 text-zinc-100 placeholder:text-zinc-500 focus:outline-none focus:ring-1 ${
30 error
31 ? "border-red-500/40 focus:border-red-500/60 focus:ring-red-500/30"
32 : "border-zinc-700/60 focus:border-accent-500/50 focus:ring-accent-500/30"
33 } ${className}`}
34 {...rest}
35 />
36 {error ? (
37 <p id={errorId} className="text-xs text-red-400">
38 {error}
39 </p>
40 ) : helper ? (
41 <p id={helperId} className="text-xs text-zinc-500">
42 {helper}
43 </p>
44 ) : null}
45 </div>
46 );
47};