{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"c-stepper-24","type":"registry:block","title":"Stepper with icon tile rows","description":"4 incident resolution steps in a vertical list, each row showing an icon, a title, a subtitle and its status, above the selected step's detail line.","dependencies":[],"registryDependencies":["button","@reui/frame","@reui/icon-tile","@reui/stepper"],"files":[{"path":"c-stepper-24.tsx","type":"registry:block","content":"\"use client\"\n\nimport { useRef, useState } from \"react\"\nimport { Frame, FramePanel } from \"@/components/reui/frame\"\nimport { IconTile } from \"@/components/reui/icon-tile\"\nimport {\n  Stepper,\n  StepperContent,\n  StepperDescription,\n  StepperIndicator,\n  StepperItem,\n  StepperNav,\n  StepperPanel,\n  StepperTitle,\n  StepperTrigger,\n} from \"@/components/reui/stepper\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { IconPlaceholder } from \"@/app/(create)/components/icon-placeholder\"\n\nconst steps = [\n  {\n    title: \"Choose Resolution\",\n    description: \"Pick one of the resolution options\",\n    detail: \"Comparing 3 fixes by risk and time to recover.\",\n    icon: (\n      <IconPlaceholder\n        lucide=\"ListChecksIcon\"\n        tabler=\"IconListCheck\"\n        hugeicons=\"CheckListIcon\"\n        phosphor=\"ListChecksIcon\"\n        remixicon=\"RiListCheck\"\n      />\n    ),\n  },\n  {\n    title: \"Execute Resolution\",\n    description: \"Verify details and proceed\",\n    detail: \"Rolling back api-gateway to v2.14.1 on 6 pods.\",\n    icon: (\n      <IconPlaceholder\n        lucide=\"WorkflowIcon\"\n        tabler=\"IconSitemap\"\n        hugeicons=\"WorkflowCircle01Icon\"\n        phosphor=\"TreeStructureIcon\"\n        remixicon=\"RiFlowChart\"\n      />\n    ),\n  },\n  {\n    title: \"Verify Outcome\",\n    description: \"Confirm the issue is resolved\",\n    detail: \"Watching error rate and latency for 10 minutes.\",\n    icon: (\n      <IconPlaceholder\n        lucide=\"ClipboardCheckIcon\"\n        tabler=\"IconClipboardCheck\"\n        hugeicons=\"TaskDone02Icon\"\n        phosphor=\"ClipboardTextIcon\"\n        remixicon=\"RiClipboardLine\"\n      />\n    ),\n  },\n  {\n    title: \"Resolution Summary\",\n    description: \"View actions taken\",\n    detail: \"Writing up the timeline and every action taken.\",\n    icon: (\n      <IconPlaceholder\n        lucide=\"ScrollTextIcon\"\n        tabler=\"IconScript\"\n        hugeicons=\"ScrollIcon\"\n        phosphor=\"ScrollIcon\"\n        remixicon=\"RiFileTextLine\"\n      />\n    ),\n  },\n]\n\nconst completed = (\n  <span className=\"border-success bg-success/10 text-success flex size-4 items-center justify-center rounded-full border\">\n    <IconPlaceholder\n      lucide=\"CheckIcon\"\n      tabler=\"IconCheck\"\n      hugeicons=\"Tick02Icon\"\n      phosphor=\"CheckIcon\"\n      remixicon=\"RiCheckLine\"\n      className=\"size-3\"\n      aria-hidden=\"true\"\n    />\n    <span className=\"sr-only\">Completed</span>\n  </span>\n)\n\n// A repeating conic mask cuts the ring into eleven dashes at the design's 3:1\n// rhythm. The stroke overhangs the 14px box by half its width, so it centers\n// on the box as the design's does. The spinner is a 139deg arc over it that\n// holds still under reduced motion, where it reads as the design's static arc.\nfunction Ring({ spin = false }: { spin?: boolean }) {\n  return (\n    <span className=\"relative block size-3.5\">\n      <span className=\"border-ring/50 absolute -inset-[0.8px] rounded-full border-[1.6px] mask-[repeating-conic-gradient(from_36deg,#000_0_calc(270deg/11),transparent_0_calc(360deg/11))]\" />\n      {spin && (\n        <>\n          <span className=\"absolute -inset-[0.8px] rounded-full border-[1.6px] border-blue-500 mask-[conic-gradient(from_-28deg,#000_139deg,transparent_0)] motion-safe:animate-spin\" />\n          <span className=\"sr-only\">In progress</span>\n        </>\n      )}\n    </span>\n  )\n}\n\nconst indicators = { completed, inactive: <Ring />, loading: <Ring spin /> }\n\nexport function Pattern() {\n  const [current, setCurrent] = useState(2)\n  const prevRef = useRef<HTMLButtonElement>(null)\n  const nextRef = useRef<HTMLButtonElement>(null)\n  // One past the last step marks every step complete. No tab is selected\n  // then, so the last one keeps the tablist's tab stop.\n  const allDone = current > steps.length\n\n  function goTo(step: number) {\n    setCurrent(step)\n    // The pressed button disables itself at either end, so focus moves to\n    // its partner.\n    if (step === 1) nextRef.current?.focus()\n    if (step > steps.length) prevRef.current?.focus()\n  }\n\n  return (\n    <Stepper\n      value={current}\n      onValueChange={setCurrent}\n      orientation=\"vertical\"\n      indicators={indicators}\n      className=\"max-w-115\"\n      aria-label=\"Incident resolution\"\n    >\n      <div className=\"flex flex-col gap-6\">\n        <StepperNav className=\"w-full\">\n          {/* Each row is its own panel: stacked joins them into one list, ghost\n              drops the outer border and xs spacing is the design's 8px row\n              padding. */}\n          <Frame variant=\"ghost\" spacing=\"xs\" stacked>\n            {steps.map((item, index) => (\n              // The trigger sits inside the padding, so the panel paints the\n              // hover, selected and focus states for the whole row.\n              <FramePanel\n                key={item.title}\n                className=\"hover:bg-muted has-data-[state=active]:bg-muted has-focus-visible:ring-ring/50 has-focus-visible:ring-3 has-focus-visible:ring-inset\"\n              >\n                {/* The primitive shows loading on the active step only, so every\n                    step carries the flag and the spinner follows the selection. */}\n                <StepperItem step={index + 1} loading>\n                  {/* Its after layer stretches over the panel, so the padding\n                      takes the click too. */}\n                  <StepperTrigger\n                    tabIndex={\n                      allDone && index + 1 === steps.length ? 0 : undefined\n                    }\n                    className=\"w-full gap-2 text-start after:absolute after:inset-0 focus-visible:ring-0\"\n                  >\n                    {/* The frame tile's 1px border plus its 2px inset form the\n                        3px ring. The border goes transparent so the ring reads\n                        as one fill; the item's data-state picks the tone. */}\n                    <IconTile\n                      variant=\"frame\"\n                      size=\"sm\"\n                      className=\"bg-input/50 text-muted-foreground group-data-[state=active]/step:bg-input group-data-[state=completed]/step:bg-success/20 after:border-ring/30 after:bg-background group-data-[state=completed]/step:after:bg-success border-transparent [--icon-tile-icon-size:--spacing(3.5)] [--icon-tile-size:--spacing(8.5)] group-data-[state=active]/step:text-blue-500 group-data-[state=completed]/step:text-white after:shadow-sm group-data-[state=completed]/step:after:border-white/8\"\n                      aria-hidden=\"true\"\n                    >\n                      {item.icon}\n                    </IconTile>\n\n                    {/* The 14px title, 4px gap and 16px subtitle add up to the\n                        34px tile, so the text adds no height to the row. */}\n                    <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n                      <StepperTitle>{item.title}</StepperTitle>\n                      <StepperDescription className=\"text-xs\">\n                        {item.description}\n                      </StepperDescription>\n                    </div>\n\n                    {/* 16px for the check and 14px for the rings, whose stroke\n                        overhangs the box, so the indicator stops clipping. */}\n                    <StepperIndicator className=\"size-3.5 overflow-visible bg-transparent data-[state=active]:bg-transparent data-[state=completed]:size-4 data-[state=completed]:bg-transparent\" />\n                  </StepperTrigger>\n                </StepperItem>\n              </FramePanel>\n            ))}\n          </Frame>\n        </StepperNav>\n\n        <div className=\"flex flex-col gap-3 text-start\">\n          <StepperPanel>\n            {steps.map((item, index) => (\n              <StepperContent key={item.title} value={index + 1}>\n                <p className=\"text-muted-foreground text-xs\">\n                  Step {index + 1} of {steps.length}\n                </p>\n                <p className=\"text-sm font-medium\">{item.title}</p>\n                <p className=\"text-muted-foreground text-sm\">{item.detail}</p>\n              </StepperContent>\n            ))}\n            <StepperContent value={steps.length + 1}>\n              <p className=\"text-muted-foreground text-xs\">\n                All {steps.length} steps done\n              </p>\n              <p className=\"text-sm font-medium\">Incident resolved</p>\n              <p className=\"text-muted-foreground text-sm\">\n                Every step is complete and the case can close.\n              </p>\n            </StepperContent>\n          </StepperPanel>\n          <div className=\"flex justify-start gap-2\">\n            <Button\n              ref={prevRef}\n              variant=\"outline\"\n              size=\"sm\"\n              disabled={current === 1}\n              onClick={() => goTo(current - 1)}\n            >\n              Previous\n            </Button>\n            <Button\n              ref={nextRef}\n              variant=\"outline\"\n              size=\"sm\"\n              disabled={allDone}\n              onClick={() => goTo(current + 1)}\n            >\n              Next\n            </Button>\n          </div>\n        </div>\n      </div>\n    </Stepper>\n  )\n}","target":"components/examples/c-stepper-24.tsx"}],"meta":{"order":24,"gridSize":1,"label":"Icon Tile Rows"}}