Files
NextChair/src/components/ui/textarea.tsx
T
Stefan Lazic 090646fdfd Phase 2
Base app architecture:

    Route groups for customer, staff and owner.
* Reusable design system with around 20 UI components.
* Complete Prisma schema.
* Authentication with email/password and Google.
* Role and permission model.
* Protected routes.
* Clean separation between features, services, components and shared utilities.
2026-08-02 17:47:23 +01:00

58 lines
1.8 KiB
TypeScript

import { forwardRef, useId } from "react";
import type { TextareaHTMLAttributes } from "react";
import { cn } from "@/lib/utils/cn";
export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
label?: string;
error?: string;
hint?: string;
}
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, label, error, hint, id, required, rows = 4, ...props }, ref) => {
const generatedId = useId();
const textareaId = id ?? generatedId;
const hintId = hint ? `${textareaId}-hint` : undefined;
const errorId = error ? `${textareaId}-error` : undefined;
return (
<div className="w-full">
{label && (
<label
htmlFor={textareaId}
className="mb-2 ml-1 block text-[10px] font-bold uppercase tracking-widest text-muted"
>
{label}
{required && <span aria-hidden="true"> *</span>}
</label>
)}
<textarea
ref={ref}
id={textareaId}
rows={rows}
required={required}
aria-invalid={!!error || undefined}
aria-describedby={cn(hintId, errorId) || undefined}
className={cn(
"w-full resize-none rounded-xl border border-line bg-white p-4 text-sm outline-none transition-all placeholder:text-muted focus:ring-2 focus:ring-ink",
error && "border-red-300 focus:ring-red-400",
className,
)}
{...props}
/>
{hint && !error && (
<p id={hintId} className="mt-1.5 ml-1 text-xs text-muted">
{hint}
</p>
)}
{error && (
<p id={errorId} role="alert" className="mt-1.5 ml-1 text-xs font-medium text-red-500">
{error}
</p>
)}
</div>
);
},
);
Textarea.displayName = "Textarea";