{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bento-steps",
  "title": "Bento steps",
  "description": "A how-it-works section: numbered steps in Tiles joined by a connector line, with done, now and up next read out in words.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/manniche/bento-steps/bento-steps.tsx",
      "content": "// Bento steps: a process in three to five tiles. Each tile has a mono number (\"01\"), a title, a sentence and an\n// optional duration chip. On wide boxes the steps sit in one row; on narrow ones they stack in a vertical list with a\n// thin line joining the tiles. `current` marks where the reader is: steps before it say \"Done\" with a check mark, the\n// current one says \"Now\" with the page's one `--primary` dot and ring, and the rest are quiet.\n//\n// Screen readers: one `h2` and an ordered list (`<ol>`), so the count and position are announced. Each step reads\n// its number, title and sentence, then its state in words (\"Done\", \"Now\", \"Up next\"); the current step also has\n// `aria-current=\"step\"`. The check mark, dot and connecting line are hidden from them.\n// Motion: none. Nothing animates, so reduced motion changes nothing.\nimport { useId, type ComponentProps, type ReactNode } from 'react'\nimport { cn } from '@/lib/utils'\n\nexport type BentoStep = {\n  /** A stable key for the step. */\n  id: string\n  /** The step's name, shown as an `h3`. */\n  title: ReactNode\n  /** One sentence about what happens in this step. */\n  description: ReactNode\n  /** How long the step takes, as a short chip: \"1 week\", \"2 days\". */\n  duration?: string\n}\n\nexport type BentoStepsLabels = {\n  /** Shown on steps before the current one. Default \"Done\". */\n  done?: string\n  /** Shown on the current step. Default \"Now\". */\n  now?: string\n  /** Read aloud before each step number. Not shown. Default \"Step\". */\n  step?: string\n  /** Read aloud on the steps after the current one. Not shown. Default \"Up next\". */\n  upcoming?: string\n}\n\nexport type BentoStepsProps = Omit<ComponentProps<'section'>, 'children' | 'title'> & {\n  /** The section's `h2`. */\n  heading: ReactNode\n  /** A short line under the heading. */\n  intro?: ReactNode\n  /** A small mono label above the heading. */\n  eyebrow?: ReactNode\n  /** The steps, three to five. */\n  steps: readonly BentoStep[]\n  /** The index (from 0) of the step that is happening now. Steps before it are done. Leave it out to show no progress. */\n  current?: number\n  /** Every visible and spoken string that is not content. */\n  labels?: BentoStepsLabels\n}\n\nconst COLS: Record<number, string> = {\n  2: '@3xl:grid-cols-2',\n  3: '@3xl:grid-cols-3',\n  4: '@3xl:grid-cols-4',\n  5: '@3xl:grid-cols-5',\n}\n\nconst DEFAULT_LABELS = { step: 'Step', done: 'Done', now: 'Now', upcoming: 'Up next' } satisfies Required<BentoStepsLabels>\n\nexport function BentoSteps({ heading, intro, eyebrow, steps, current, labels, className, ...rest }: BentoStepsProps) {\n  const headingId = useId()\n  const t = { ...DEFAULT_LABELS, ...labels }\n  return (\n    <section aria-labelledby={headingId} className={cn('@container w-full py-12 @3xl:py-20', className)} {...rest}>\n      <div className=\"mx-auto max-w-6xl px-4 sm:px-6\">\n        <header className=\"mb-8 grid max-w-2xl gap-3 @3xl:mb-12\">\n          {eyebrow && <p className=\"font-mono text-xs tracking-[0.04em] text-muted-foreground tabular-nums\">{eyebrow}</p>}\n          <h2\n            id={headingId}\n            className=\"text-[clamp(30px,5.4cqw,52px)] leading-[1.02] font-extrabold tracking-[-0.035em] text-balance text-foreground\"\n            style={{ fontFamily: 'var(--font-display, inherit)', fontStretch: '86%' }}\n          >\n            {heading}\n          </h2>\n          {intro && <p className=\"text-base leading-relaxed text-pretty text-muted-foreground\">{intro}</p>}\n        </header>\n        <ol className={cn('grid list-none grid-cols-1 gap-6 p-0 @3xl:gap-4', COLS[steps.length] ?? '@3xl:grid-cols-4')}>\n          {steps.map((s, i) => {\n            const done = current !== undefined && i < current\n            const now = current === i\n            const state = done ? t.done : now ? t.now : null\n            return (\n              <li\n                key={s.id}\n                aria-current={now ? 'step' : undefined}\n                className=\"relative flex min-w-0 after:absolute after:top-full after:left-[34px] after:h-6 after:w-px after:bg-border last:after:hidden @3xl:after:hidden data-[done]:after:bg-foreground/40\"\n                data-done={done || undefined}\n              >\n                <div\n                  className={cn(\n                    'flex w-full min-w-0 flex-col gap-4 rounded-[calc(var(--radius)*2+2px)] bg-card p-[18px] text-card-foreground @3xl:p-6',\n                    now\n                      ? 'shadow-[0_0_0_2px_var(--primary)]'\n                      : 'shadow-[0_0_0_1px_color-mix(in_oklab,var(--foreground)_9%,transparent),0_1px_2px_rgba(0,0,0,0.03)]',\n                  )}\n                >\n                  <div className=\"flex min-h-7 items-center justify-between gap-3\">\n                    <span className=\"font-mono text-[12px] tracking-[0.04em] text-muted-foreground tabular-nums\">\n                      <span className=\"sr-only\">{t.step} </span>\n                      {String(i + 1).padStart(2, '0')}\n                    </span>\n                    {state ? (\n                      <span className=\"inline-flex items-center gap-1.5 font-mono text-[11px] tracking-[0.04em] text-foreground uppercase\">\n                        {done ? (\n                          <svg\n                            viewBox=\"0 0 12 12\"\n                            aria-hidden\n                            className=\"size-3 fill-none stroke-current stroke-2 [stroke-linecap:round] [stroke-linejoin:round]\"\n                          >\n                            <path d=\"m2.5 6.5 2.5 2.5 4.5-5.5\" />\n                          </svg>\n                        ) : (\n                          <i aria-hidden className=\"size-2 rounded-full bg-primary\" />\n                        )}\n                        {state}\n                      </span>\n                    ) : (\n                      current !== undefined && <span className=\"sr-only\">{t.upcoming}</span>\n                    )}\n                  </div>\n                  <div className=\"grid gap-2\">\n                    <h3 className=\"text-[19px] leading-[1.15] font-semibold tracking-[-0.02em] text-balance\">{s.title}</h3>\n                    <p className=\"text-[15px] leading-normal text-pretty text-muted-foreground\">{s.description}</p>\n                  </div>\n                  {s.duration && (\n                    <span className=\"mt-auto w-fit rounded-full bg-muted px-2.5 py-1 font-mono text-[11.5px] leading-none text-muted-foreground tabular-nums\">\n                      {s.duration}\n                    </span>\n                  )}\n                </div>\n              </li>\n            )\n          })}\n        </ol>\n      </div>\n    </section>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "templates"
  ],
  "type": "registry:block"
}