Primitives
Input states and FormField
Error (shakes once, announced), warning, success; leading and trailing slots; one message line, so the form never jumps.
Enter all 11 digits, e.g. 01712-345678.
Verified
৳per day
Higher than every other car park in Gulshan.
Plate, phone or ticket number.
43/60
input-states.tsx
"use client";
import { Button, FormField, Input, Textarea } from "@waveinno/ui";
import { CircleCheck, Mail, Phone, Search } from "lucide-react";
import * as React from "react";
export default function InputStates() {
const [phone, setPhone] = React.useState("01712-34");
const [note, setNote] = React.useState("Leave the car with the attendant at lane 2.");
const short = phone.replace(/\D/g, "").length < 11;
return (
<div className="grid max-w-2xl gap-4 sm:grid-cols-2">
<FormField label="Mobile number" htmlFor="pg-phone" required error={short ? "Enter all 11 digits, e.g. 01712-345678." : undefined} hint="We send the receipt here.">
<Input id="pg-phone" value={phone} onChange={(e) => setPhone(e.target.value)} state={short ? "error" : "success"} leading={<Phone />} trailing={short ? null : <CircleCheck className="text-success" />} />
</FormField>
<FormField label="Email" htmlFor="pg-mail" success="Verified">
<Input id="pg-mail" defaultValue="karim@gulshanparking.com" state="success" leading={<Mail />} />
</FormField>
<FormField label="Daily cap" htmlFor="pg-cap" warning="Higher than every other car park in Gulshan.">
<Input id="pg-cap" defaultValue="1,200" state="warning" leading="৳" trailing="per day" />
</FormField>
<FormField label="Search" htmlFor="pg-search" hint="Plate, phone or ticket number.">
<Input id="pg-search" placeholder="DHAKA METRO-GA 11-4821" leading={<Search />} />
</FormField>
<FormField label="Note for the team" htmlFor="pg-note2" count={{ value: note.length, max: 60 }} className="sm:col-span-2" error={note.length > 60 ? "Keep it under 60 characters." : undefined}>
<Textarea id="pg-note2" value={note} onChange={(e) => setNote(e.target.value)} state={note.length > 60 ? "error" : "default"} />
</FormField>
<Button variant="primary" className="w-fit">Save</Button>
</div>
);
}Import#
Everything comes from the package root. The example above uses:
your-component.tsx
import { Button, FormField, Input, Textarea } from "@waveinno/ui";Props#
Read from the source of this version, so they match the code exactly.
<Button>
src/components/ui/button.tsx| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | |
loading | boolean | — |
Also takes ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants>.
<FormField>
src/components/ui/form-field.tsx| Prop | Type | Default | Description |
|---|---|---|---|
label | React.ReactNode | — | |
htmlFor | string | — | The control's id, so clicking the label focuses it. |
required | boolean | — | |
hint | React.ReactNode | — | |
error | React.ReactNode | — | |
warning | React.ReactNode | — | |
success | React.ReactNode | — | |
count | { value: number; max: number } | — | Character counter, e.g. { value: note.length, max: 240 }. |
className | string | — | |
childrenrequired | React.ReactNode | — |
<Input>
src/components/ui/input.tsx| Prop | Type | Default | Description |
|---|---|---|---|
state | "default" | "error" | "warning" | "success" | "default" | |
leading | React.ReactNode | — | Icon or text inside the box, before the value (a search icon, "৳", "+880"). |
trailing | React.ReactNode | — | After the value: a unit, a clear button, a status icon. |
Also takes InputHTMLAttributes<HTMLInputElement>.
<Textarea>
src/components/ui/input.tsx| Prop | Type | Default | Description |
|---|---|---|---|
state | "default" | "error" | "warning" | "success" | "default" |
Also takes TextareaHTMLAttributes<HTMLTextAreaElement>.
Install#
If the project does not have the package yet, add it, then follow the setup for Next.js or Vite once.
$ npm install @waveinno/ui