Objectives
A quest and objective tracker for React: a leaning header over a dark band, a slanted mark per objective, and a strike drawn through what is done.
Install
# once per project: where @zeroui items come frompnpm dlx shadcn@latest registry add @zeroui=https://zeroui.catforest.dev/r/{name}.jsonpnpm dlx shadcn@latest add @zeroui/objectivesExamples
A shift's objectives
Night shift
Done: Reach the rail yards
Current: Load the sealed crates2 / 5
To do: Cross the river before the gate shuts
To do: Hand over at the North gate
import { useState } from "react";import { Button } from "@/components/zero/button";import { Objective, ObjectiveCount, ObjectiveDescription, type ObjectiveState, Objectives, ObjectivesHeader, ObjectiveTitle,} from "@/components/zero/objectives";const steps = [ "Reach the rail yards", "Load the sealed crates", "Cross the river before the gate shuts", "Hand over at the North gate",];export default function ObjectivesShift() { const [step, setStep] = useState(1); const [crates, setCrates] = useState(2); const [failed, setFailed] = useState(false); const state = (index: number): ObjectiveState => index < step ? "done" : index === step ? failed ? "failed" : "active" : "pending"; const advance = () => { if (step === 1 && crates < 5) setCrates((n) => n + 1); else setStep((s) => Math.min(steps.length, s + 1)); }; return ( <div className="grid justify-items-start gap-5"> <Objectives aria-label="Night shift"> <ObjectivesHeader>Night shift</ObjectivesHeader> {steps.map((title, index) => ( <Objective key={title} state={state(index)}> <ObjectiveTitle>{title}</ObjectiveTitle> {index === 1 ? <ObjectiveCount>{crates} / 5</ObjectiveCount> : null} {index === 2 && state(index) === "active" ? ( <ObjectiveDescription> The gate shuts at 02:00. </ObjectiveDescription> ) : null} </Objective> ))} </Objectives> <div className="flex flex-wrap gap-3"> <Button size="sm" onClick={advance} disabled={failed}> Next </Button> <Button variant="danger" size="sm" onClick={() => setFailed(true)} disabled={failed || step >= steps.length} > Fail it </Button> <Button variant="ghost" size="sm" onClick={() => { setStep(1); setCrates(2); setFailed(false); }} > Start over </Button> </div> </div> );}Behaviour
Each objective has a slanted mark: an empty frame while it waits, the accent frame while it is the one in hand, the filled accent with a check when done, red with a cross when it failed. A done or failed title is struck through, the line drawn across from the start. The band behind fades out to the right, so it can sit over a map or a scene.
The state is also said in words to screen readers ("Done:", "Current:"). ObjectiveCount sits at the line's end; ObjectiveDescription goes under the title.
Props
Objective
| Prop | Type | Default | Description |
|---|---|---|---|
| state | "pending" | "active" | "done" | "failed" | "pending" | Its mark, and how its title is drawn. |