Stepper
WhereUIแสดงขั้นตอนเป็นแนวนอนหรือแนวตั้ง พร้อมสถานะเสร็จแล้ว ขั้นปัจจุบัน รอ และผิดพลาด เหมาะกับฟอร์มหลายขั้น เช่น การสมัครงาน หรือการส่งคำขอ
- ข้อมูลผู้สมัคร
- ประวัติการทำงาน
- แนบเอกสาร
- ยืนยันส่งคำขอ
ติดตั้ง
Terminal
pnpm dlx shadcn@latest add @whereui/stepperต้องผูก registry และติดตั้ง @whereui/theme นำหน้าเสมอเมื่อติดตั้งครั้งแรก
การใช้งาน
import { Stepper } from "@/components/ui/stepper"<Stepper steps={[{ label: "กรอกข้อมูล" }, { label: "แนบเอกสาร" }, { label: "ยืนยัน" }]} current={1}/>ตัวอย่างเพิ่มเติม
แนวตั้งพร้อมคำอธิบาย
- ข้อมูลผู้สมัครชื่อ ตำแหน่งที่สมัคร และช่องทางติดต่อ
- ประวัติการทำงานประสบการณ์ย้อนหลังไม่เกิน 3 แห่ง
- แนบเอกสารใบรับรองและผลงานเป็น PDF
ขั้นปัจจุบันผิดพลาด
- ข้อมูลพนักงาน
- ระยะเวลาลา
- เหตุผล
- ยืนยัน
Props
| prop | type | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
| steps | { label: string; description?: string }[] | — | รายการขั้นตอนเรียงจากซ้ายไปขวา (หรือบนลงล่าง) |
| current | number | 0 | ดัชนีขั้นปัจจุบัน เริ่มที่ 0 — ขั้นก่อนหน้าถือว่าเสร็จแล้ว |
| error | boolean | false | ขั้นปัจจุบันผิดพลาด เช่น ตรวจฟอร์มไม่ผ่าน วงกลมจะเป็นสีแดงพร้อมเครื่องหมายกากบาท |
| orientation | "horizontal" | "vertical" | "horizontal" | ทิศทางการเรียงขั้นตอน |
ข้อควรรู้
- บนจอ 375px ควรใช้ไม่เกิน 4 ขั้นในแนวนอน หรือเปลี่ยนเป็นแนวตั้งเมื่อป้ายยาว
- ขั้นปัจจุบันมี aria-current="step" เพื่อการอ่านด้วยโปรแกรมอ่านหน้าจอ
ซอร์สโค้ด
components/ui/stepper.tsx
import * as React from "react"import { CheckIcon, XIcon } from "lucide-react"import { cn } from "cn"export type StepperStep = { label: string description?: string}// สถานะของแต่ละขั้น คำนวณจาก current + error (error ใช้กับขั้นปัจจุบัน)function getStepStatus( index: number, current: number, error: boolean): "complete" | "current" | "error" | "upcoming" { if (index < current) return "complete" if (index === current) return error ? "error" : "current" return "upcoming"}function Stepper({ className, steps, current = 0, error = false, orientation = "horizontal", ...props}: React.ComponentProps<"ol"> & { steps: StepperStep[] /** ดัชนีขั้นปัจจุบัน (เริ่มที่ 0) */ current?: number /** ขั้นปัจจุบันผิดพลาด เช่น ตรวจฟอร์มไม่ผ่าน */ error?: boolean orientation?: "horizontal" | "vertical"}) { return ( <ol data-slot="stepper" data-orientation={orientation} className={cn( "flex w-full", orientation === "horizontal" ? "items-start" : "flex-col", className )} {...props} > {steps.map((step, i) => { const status = getStepStatus(i, current, error) const circle = ( <span aria-hidden="true" className={cn( "grid size-7 shrink-0 place-items-center rounded-full text-xs font-semibold [&_svg]:size-4", status === "upcoming" && "bg-muted text-muted-foreground", (status === "complete" || status === "current") && "bg-primary text-primary-foreground", status === "error" && "bg-destructive text-primary-foreground" )} > {status === "complete" ? ( <CheckIcon /> ) : status === "error" ? ( <XIcon /> ) : ( i + 1 )} </span> ) const labelBlock = ( <div className={cn("min-w-0", orientation === "horizontal" && "max-w-44")}> <div className={cn( "text-sm leading-snug font-medium", status === "upcoming" && "text-muted-foreground" )} > {step.label} </div> {step.description ? ( <div className="mt-0.5 text-xs text-muted-foreground"> {step.description} </div> ) : null} </div> ) return ( <li key={i} aria-current={status === "current" ? "step" : undefined} className={cn( orientation === "horizontal" ? "flex min-w-0 flex-1 flex-col items-center gap-1.5 text-center" : "relative flex gap-3 pb-6 last:pb-0" )} > {orientation === "horizontal" ? ( <> <div className="flex w-full items-center" aria-hidden="true"> <span className={cn( "h-px flex-1", i === 0 && "invisible", i <= current ? "bg-primary" : "bg-border" )} /> {circle} <span className={cn( "h-px flex-1", i === steps.length - 1 && "invisible", i < current ? "bg-primary" : "bg-border" )} /> </div> {labelBlock} </> ) : ( <> <div className="flex flex-col items-center self-stretch"> {circle} {i < steps.length - 1 ? ( <span aria-hidden="true" className={cn( "mt-1 w-px flex-1", i < current ? "bg-primary" : "bg-border" )} /> ) : null} </div> <div className="pt-0.5">{labelBlock}</div> </> )} </li> ) })} </ol> )}export { Stepper }