This repository has no description
1<script module lang="ts">
2 import { tv, type VariantProps } from "tailwind-variants";
3
4 // the numbered rail from Figma node 191:6514 — same shape as the appview's
5 // onboarding/fragments/steps, restyled onto the design-system tokens. the three
6 // states differ only in the marker fill and the label colour, so they are one
7 // variant across two slots rather than three hand-written class strings.
8 export const steps = tv({
9 slots: {
10 root: "flex w-full items-center gap-2",
11 item: "flex shrink-0 items-center gap-2",
12 marker:
13 "flex size-5 shrink-0 items-center justify-center rounded-full typography-paragraph-small",
14 // labels are desktop-only, but sr-only rather than hidden so the rail still
15 // reads as a named list of steps on a narrow screen
16 label: "sr-only typography-paragraph-small whitespace-nowrap sm:not-sr-only",
17 connector: "h-px min-w-4 flex-1 bg-border-default"
18 },
19 variants: {
20 state: {
21 done: {
22 marker: "bg-background-inset text-foreground-default",
23 label: "text-foreground-subtle"
24 },
25 current: {
26 marker: "bg-background-muted text-foreground-default",
27 label: "text-foreground-default"
28 },
29 upcoming: {
30 marker: "bg-background-inset text-foreground-subtle",
31 label: "text-foreground-subtle"
32 }
33 }
34 },
35 defaultVariants: {
36 state: "upcoming"
37 }
38 });
39
40 export type StepsVariants = VariantProps<typeof steps>;
41 export type StepState = NonNullable<StepsVariants["state"]>;
42</script>
43
44<script lang="ts">
45 import Check from "$icon/check";
46
47 interface Props {
48 items: readonly { key: string; label: string }[];
49 current: number;
50 }
51
52 let { items, current }: Props = $props();
53
54 // root and connector take no variant, so they resolve once
55 const shell = steps();
56
57 const stateOf = (index: number): StepState =>
58 index < current ? "done" : index === current ? "current" : "upcoming";
59</script>
60
61<ol class={shell.root()}>
62 {#each items as item, index (item.key)}
63 {#if index > 0}
64 <li aria-hidden="true" class={shell.connector()}></li>
65 {/if}
66 {@const state = stateOf(index)}
67 {@const slot = steps({ state })}
68 <li class={slot.item()} aria-current={state === "current" ? "step" : undefined}>
69 <span class={slot.marker()} aria-label={state === "done" ? "done" : undefined}>
70 {#if state === "done"}
71 <Check class="size-3.5" aria-hidden="true" />
72 {:else}
73 {index + 1}
74 {/if}
75 </span>
76 <span class={slot.label()}>{item.label}</span>
77 </li>
78 {/each}
79</ol>