{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"c-stepper-22","type":"registry:block","title":"Stepper with color-coded bottom bars","description":"4 billing setup steps as colored bars along the panel's bottom edge, the active one thicker, under the current step's icon, name and hint.","dependencies":["cn"],"registryDependencies":["button","@reui/frame","@reui/icon-stack","@reui/stepper"],"files":[{"path":"c-stepper-22.tsx","type":"registry:block","content":"\"use client\"\n\nimport { useRef, useState } from \"react\"\nimport { Frame, FramePanel } from \"@/components/reui/frame\"\nimport { IconStack } from \"@/components/reui/icon-stack\"\nimport {\n  Stepper,\n  StepperContent,\n  StepperIndicator,\n  StepperItem,\n  StepperNav,\n  StepperPanel,\n  StepperTitle,\n  StepperTrigger,\n} from \"@/components/reui/stepper\"\nimport { cn } from \"cn\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { IconPlaceholder } from \"@/app/(create)/components/icon-placeholder\"\n\nconst steps = [\n  {\n    title: \"Company details\",\n    description: \"Legal name and tax ID for your invoices.\",\n    color: \"text-info\",\n    icon: (\n      <IconPlaceholder\n        lucide=\"Building2Icon\"\n        tabler=\"IconBuilding\"\n        hugeicons=\"Building03Icon\"\n        phosphor=\"BuildingsIcon\"\n        remixicon=\"RiBuilding2Line\"\n        className=\"size-4\"\n      />\n    ),\n  },\n  {\n    title: \"Payment method\",\n    description: \"The card charged at each renewal.\",\n    color: \"text-warning\",\n    icon: (\n      <IconPlaceholder\n        lucide=\"CreditCardIcon\"\n        tabler=\"IconCreditCard\"\n        hugeicons=\"CreditCardIcon\"\n        phosphor=\"CreditCardIcon\"\n        remixicon=\"RiBankCardLine\"\n        className=\"size-4\"\n      />\n    ),\n  },\n  {\n    title: \"Billing contact\",\n    description: \"Who receives invoices and receipts.\",\n    // The theme has no blue token, so this step takes the palette color.\n    color: \"text-blue-500\",\n    icon: (\n      <IconPlaceholder\n        lucide=\"MailIcon\"\n        tabler=\"IconMail\"\n        hugeicons=\"Mail01Icon\"\n        phosphor=\"EnvelopeIcon\"\n        remixicon=\"RiMailLine\"\n        className=\"size-4\"\n      />\n    ),\n  },\n  {\n    title: \"Review\",\n    description: \"Confirm the plan and start billing.\",\n    color: \"text-success\",\n    icon: (\n      <IconPlaceholder\n        lucide=\"ClipboardCheckIcon\"\n        tabler=\"IconClipboardCheck\"\n        hugeicons=\"TaskDone01Icon\"\n        phosphor=\"ClipboardTextIcon\"\n        remixicon=\"RiClipboardLine\"\n        className=\"size-4\"\n      />\n    ),\n  },\n]\n\n// The design's 3:1 dashed ring. pathLength counts the circle as 36 units, so\n// the dashes close in nine even segments with no stub at the seam. Loading\n// dims the track and lays a 39% arc over it, resting at the design's angle\n// (11 to 4 o'clock) when motion is reduced.\nfunction Ring({ spin = false }: { spin?: boolean }) {\n  return (\n    <svg\n      viewBox=\"0 0 14 14\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.4\"\n      aria-hidden=\"true\"\n      className=\"size-3.5\"\n    >\n      <circle\n        cx=\"7\"\n        cy=\"7\"\n        r=\"6\"\n        strokeDasharray=\"3 1\"\n        pathLength={36}\n        strokeOpacity={spin ? 0.4 : 1}\n      />\n      {spin && (\n        <circle\n          cx=\"7\"\n          cy=\"7\"\n          r=\"6\"\n          strokeDasharray=\"39 61\"\n          strokeDashoffset={33}\n          pathLength={100}\n          className=\"origin-center motion-safe:animate-spin\"\n        />\n      )}\n    </svg>\n  )\n}\n\nconst indicators = {\n  // The design draws the 14px ring with a 1.4px line: 2.4 on a 24-unit grid.\n  completed: (\n    <IconPlaceholder\n      lucide=\"CircleCheckIcon\"\n      tabler=\"IconCircleCheck\"\n      hugeicons=\"CheckmarkCircle02Icon\"\n      phosphor=\"CheckCircleIcon\"\n      remixicon=\"RiCheckboxCircleLine\"\n      strokeWidth={2.4}\n      className=\"size-3.5\"\n      aria-hidden=\"true\"\n    />\n  ),\n  inactive: <Ring />,\n  loading: <Ring spin />,\n}\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      indicators={indicators}\n      className=\"@container max-w-175\"\n      aria-label=\"Billing setup\"\n    >\n      <Frame className=\"w-full\" variant=\"ghost\">\n        <FramePanel>\n          <StepperPanel className=\"pt-3\">\n            {steps.map((item, index) => (\n              <StepperContent key={item.title} value={index + 1}>\n                <div className=\"flex flex-col items-center text-center\">\n                  <IconStack aria-hidden=\"true\" className=\"mb-2\">\n                    {item.icon}\n                  </IconStack>\n                  <p className=\"text-sm font-medium\">{item.title}</p>\n                  <p className=\"text-muted-foreground mt-0.5 text-sm text-balance\">\n                    {item.description}\n                  </p>\n                </div>\n              </StepperContent>\n            ))}\n            <StepperContent value={steps.length + 1}>\n              <div className=\"flex flex-col items-center text-center\">\n                <IconStack aria-hidden=\"true\" className=\"mb-2\">\n                  <IconPlaceholder\n                    lucide=\"CheckIcon\"\n                    tabler=\"IconCheck\"\n                    hugeicons=\"Tick02Icon\"\n                    phosphor=\"CheckIcon\"\n                    remixicon=\"RiCheckLine\"\n                    className=\"size-4\"\n                  />\n                </IconStack>\n                <p className=\"text-sm font-medium\">Billing ready</p>\n                <p className=\"text-muted-foreground mt-0.5 text-sm text-balance\">\n                  Invoices start with your next renewal.\n                </p>\n              </div>\n            </StepperContent>\n          </StepperPanel>\n\n          <div className=\"mt-3 flex justify-center 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\n          {/* Bleeds over the panel's side and bottom padding, so the bars sit\n              flush with its edges and its overflow-hidden rounds the outer\n              ends to its radius. */}\n          <div className=\"-mx-(--frame-panel-px) mt-4 -mb-(--frame-panel-py) flex\">\n            <StepperNav>\n              {steps.map((item, index) => {\n                const step = index + 1\n                const status =\n                  step < current\n                    ? \", completed\"\n                    : step === current\n                      ? \", in progress\"\n                      : \"\"\n\n                // Each step owns a color, which the bar and indicator take\n                // through currentColor. Off the active step the title turns\n                // muted and the indicator dims to 40%. Every step carries the\n                // loading flag so the spinner follows the selection.\n                return (\n                  <StepperItem\n                    key={item.title}\n                    step={step}\n                    loading\n                    className=\"min-w-0 flex-1\"\n                  >\n                    {/* The panel clips anything drawn past its edges, so the\n                        focus ring is inset. */}\n                    <StepperTrigger\n                      tabIndex={\n                        allDone && step === steps.length ? 0 : undefined\n                      }\n                      aria-label={`Step ${step}: ${item.title}${status}`}\n                      className=\"min-w-0 flex-1 rounded-none focus-visible:ring-inset\"\n                    >\n                      {/* 38px: 8px that keeps the ring off the label, the 14px\n                          label row, a 12px gap and the 4px bar, so the active\n                          bar grows into the gap and the row never moves. */}\n                      <span\n                        className={cn(\n                          \"flex h-9.5 min-w-0 flex-1 flex-col justify-between pt-2\",\n                          item.color\n                        )}\n                      >\n                        <span className=\"flex items-center justify-between gap-1 px-3 whitespace-nowrap\">\n                          {/* Under 28rem a quarter row only fits the number;\n                              the trigger's aria-label keeps the full name. */}\n                          <StepperTitle className=\"text-current/60 data-[state=active]:text-current\">\n                            <span className=\"@max-md:hidden\">Step </span>\n                            {step}\n                          </StepperTitle>\n                          <StepperIndicator className=\"size-3.5 bg-transparent text-current opacity-40 data-[state=active]:bg-transparent data-[state=active]:text-current data-[state=active]:opacity-100 data-[state=completed]:bg-transparent data-[state=completed]:text-current\" />\n                        </span>\n\n                        {/* The active bar doubles to 8px with rounded top\n                            corners, capped at the panel radius so square\n                            styles stay square. */}\n                        <span\n                          aria-hidden=\"true\"\n                          className=\"h-1 bg-current transition-all group-data-[state=active]/step:h-2 group-data-[state=active]/step:rounded-t-[min(calc(var(--radius-sm)-2px),var(--frame-panel-radius))]\"\n                        />\n                      </span>\n                    </StepperTrigger>\n                  </StepperItem>\n                )\n              })}\n            </StepperNav>\n          </div>\n        </FramePanel>\n      </Frame>\n    </Stepper>\n  )\n}","target":"components/examples/c-stepper-22.tsx"}],"meta":{"order":22,"gridSize":1,"label":"Bottom Color Bars"}}