* Extract reusable league wizard components; wire settings page (#103) Extracts 9 domain components from new.tsx and $leagueId.settings.tsx into app/components/league/ (each with a Storybook story), consolidates wizard form-building into wizard-state.ts, and updates the settings page to use the shared components instead of hand-rolled duplicates. new.tsx shrinks from ~2000 → ~1280 lines. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix sports-season test: add participants to mock data findDraftableSportsSeasons now returns participantCount, which requires participants in the mock db response. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix sports-season test: loosen makeMockDb type to Record<string, unknown>[] typeof mockSeasons became too strict after adding participants to the mock data, breaking inline arrays in other tests that don't include participants. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
import { Fragment } from "react";
|
|
import { Check } from "lucide-react";
|
|
import { cn } from "~/lib/utils";
|
|
|
|
export interface WizardStepperProps {
|
|
currentStep: number;
|
|
steps: string[];
|
|
onStepClick: (n: number) => void;
|
|
}
|
|
|
|
export function WizardStepper({ currentStep, steps, onStepClick }: WizardStepperProps) {
|
|
return (
|
|
<div className="mb-6">
|
|
<div className="flex items-center">
|
|
{steps.map((label, i) => {
|
|
const n = i + 1;
|
|
const done = n < currentStep;
|
|
const active = n === currentStep;
|
|
return (
|
|
<Fragment key={label}>
|
|
{i > 0 && (
|
|
<div className={cn("flex-1 h-0.5 transition-colors", done ? "bg-primary" : "bg-border")} />
|
|
)}
|
|
<button
|
|
type="button"
|
|
onClick={() => done && onStepClick(n)}
|
|
className={cn(
|
|
"shrink-0 w-9 h-9 rounded-full border-2 flex items-center justify-center text-sm font-semibold bg-background transition-colors",
|
|
done && "bg-primary border-primary text-primary-foreground",
|
|
active && !done && "border-primary text-primary",
|
|
!done && !active && "border-border text-muted-foreground",
|
|
done ? "cursor-pointer" : "cursor-default"
|
|
)}
|
|
>
|
|
{done ? <Check className="h-4 w-4" /> : n}
|
|
</button>
|
|
</Fragment>
|
|
);
|
|
})}
|
|
</div>
|
|
<div className="flex mt-1.5">
|
|
{steps.map((label, i) => {
|
|
const n = i + 1;
|
|
const done = n < currentStep;
|
|
const active = n === currentStep;
|
|
return (
|
|
<Fragment key={label}>
|
|
{i > 0 && <div className="flex-1" />}
|
|
<div className="shrink-0 w-9 flex justify-center">
|
|
<span className={cn(
|
|
"text-xs hidden sm:block whitespace-nowrap",
|
|
active ? "text-primary font-medium" : done ? "text-foreground" : "text-muted-foreground"
|
|
)}>
|
|
{label}
|
|
</span>
|
|
</div>
|
|
</Fragment>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|