{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"c-stepper-23","type":"registry:block","title":"Stepper with top color bars and card art","description":"Adding a payment card in 4 steps, each tab with its own color bar on the top edge that thickens when selected, over a card drawing and a caption.","dependencies":["cn"],"registryDependencies":["button","@reui/frame","@reui/stepper"],"files":[{"path":"c-stepper-23.tsx","type":"registry:block","content":"\"use client\"\n\nimport { useRef, useState } from \"react\"\nimport { Frame, FramePanel } from \"@/components/reui/frame\"\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\"\n\n// Each step owns a color. Its column sets it as the text color, and the bar\n// and indicator inherit it through currentColor. The theme has no blue token,\n// so step 3 takes the palette color.\nconst steps = [\n  { color: \"text-info\", caption: \"Enter the card number and expiry\" },\n  { color: \"text-warning\", caption: \"Add the billing address\" },\n  { color: \"text-blue-500\", caption: \"Confirm the card with your bank\" },\n  { color: \"text-success\", caption: \"Review and save the card\" },\n]\n\n// The design's 3:1 dashed ring on a 12px box, its 1.4 stroke overflowing the\n// box. pathLength 36 closes the dashes in nine even segments, turned to start\n// at 1 o'clock. Loading dims the track under a 38.5% arc that rests at 11 to\n// 4 o'clock when motion is reduced.\nfunction Ring({ spin = false }: { spin?: boolean }) {\n  return (\n    <svg\n      viewBox=\"0 0 12 12\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.4\"\n      aria-hidden=\"true\"\n      className=\"size-3 overflow-visible\"\n    >\n      <circle\n        cx=\"6\"\n        cy=\"6\"\n        r=\"6\"\n        strokeDasharray=\"3 1\"\n        pathLength={36}\n        transform=\"rotate(-54 6 6)\"\n        strokeOpacity={spin ? 0.4 : 1}\n      />\n      {spin && (\n        <circle\n          cx=\"6\"\n          cy=\"6\"\n          r=\"6\"\n          strokeDasharray=\"38.5 61.5\"\n          strokeDashoffset={32.7}\n          pathLength={100}\n          className=\"origin-center motion-safe:animate-spin\"\n        />\n      )}\n    </svg>\n  )\n}\n\n// Off the active step the indicator sits at 40% opacity, which dims these\n// too. Completed and loading are spelled out for screen readers as well.\nconst indicators = {\n  completed: (\n    <>\n      <svg\n        viewBox=\"0 0 14 14\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        aria-hidden=\"true\"\n        className=\"size-3.5\"\n      >\n        <circle cx=\"7\" cy=\"7\" r=\"6.3\" strokeWidth=\"1.4\" />\n        <path d=\"m4.33 7.15 1.67 1.54 3.67-3.38\" strokeWidth=\"1.5\" />\n      </svg>\n      <span className=\"sr-only\">Completed</span>\n    </>\n  ),\n  inactive: <Ring />,\n  loading: (\n    <>\n      <Ring spin />\n      <span className=\"sr-only\">In progress</span>\n    </>\n  ),\n}\n\nfunction CardIllustration() {\n  return (\n    <svg\n      width=\"200\"\n      height=\"130\"\n      viewBox=\"0 0 200 130\"\n      fill=\"none\"\n      aria-hidden=\"true\"\n    >\n      <rect\n        x=\"48\"\n        y=\"12\"\n        width=\"120\"\n        height=\"78\"\n        rx=\"10\"\n        transform=\"rotate(6 108 51)\"\n        className=\"fill-muted/50 dark:fill-muted/25\"\n      />\n      <rect\n        x=\"36\"\n        y=\"18\"\n        width=\"128\"\n        height=\"82\"\n        rx=\"10\"\n        strokeWidth=\"1.5\"\n        className=\"fill-background stroke-border\"\n      />\n      <rect\n        x=\"52\"\n        y=\"38\"\n        width=\"22\"\n        height=\"16\"\n        rx=\"3\"\n        className=\"fill-muted-foreground/15 stroke-muted-foreground/20\"\n      />\n      <path\n        d=\"M52 46H74M63 38V54\"\n        strokeWidth=\"0.8\"\n        className=\"stroke-muted-foreground/15\"\n      />\n      <path\n        d=\"M84 42Q87 46 84 50M88 40Q92 46 88 52M92 38Q97 46 92 54\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        className=\"stroke-muted-foreground/20\"\n      />\n      {[56, 64, 72, 80, 94, 102, 110, 118].map((cx) => (\n        <circle\n          key={cx}\n          cx={cx}\n          cy=\"68\"\n          r=\"2\"\n          className={\n            cx < 90 ? \"fill-muted-foreground/20\" : \"fill-muted-foreground/15\"\n          }\n        />\n      ))}\n      <rect\n        x=\"52\"\n        y=\"80\"\n        width=\"40\"\n        height=\"3\"\n        rx=\"1.5\"\n        className=\"fill-muted-foreground/12\"\n      />\n      <rect\n        x=\"120\"\n        y=\"80\"\n        width=\"28\"\n        height=\"3\"\n        rx=\"1.5\"\n        className=\"fill-muted-foreground/12\"\n      />\n      <circle cx=\"140\" cy=\"88\" r=\"6\" className=\"fill-muted-foreground/8\" />\n      <circle cx=\"150\" cy=\"88\" r=\"6\" className=\"fill-muted-foreground/12\" />\n      <circle cx=\"26\" cy=\"50\" r=\"3\" className=\"fill-primary/10\" />\n      <circle cx=\"180\" cy=\"40\" r=\"2\" className=\"fill-primary/10\" />\n      <path d=\"M174 70L178 66V74Z\" className=\"fill-muted-foreground/10\" />\n    </svg>\n  )\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=\"Add a card\"\n    >\n      <Frame className=\"w-full\" variant=\"ghost\">\n        <FramePanel>\n          {/* The nav bleeds over the panel's top and side padding, so the bars\n              sit flush with its edges and the panel's overflow-hidden rounds\n              their ends. Only a block, auto-width nav stretches into those\n              margins, and the tabs' focus ring is inset so the clip spares it. */}\n          <StepperNav className=\"-mx-(--frame-panel-px) -mt-(--frame-panel-py) flex data-[orientation=horizontal]:w-auto\">\n            {steps.map((item, index) => {\n              const step = index + 1\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              return (\n                <StepperItem\n                  key={item.caption}\n                  step={step}\n                  loading\n                  className=\"min-w-0 flex-1\"\n                >\n                  <StepperTrigger\n                    tabIndex={allDone && step === steps.length ? 0 : undefined}\n                    className=\"w-full min-w-0 rounded-none focus-visible:ring-inset\"\n                  >\n                    {/* 30px: the 4px bar, a 12px gap and the 14px label row,\n                        so the active bar grows into the gap and the row\n                        never moves. */}\n                    <span\n                      className={cn(\n                        \"flex h-7.5 min-w-0 flex-1 flex-col justify-between\",\n                        item.color\n                      )}\n                    >\n                      {/* The active bar's corners stop at the panel radius,\n                          so the square styles stay square. */}\n                      <span\n                        aria-hidden=\"true\"\n                        className=\"h-1 bg-current group-data-[state=active]/step:h-2 group-data-[state=active]/step:rounded-b-[min(var(--radius-sm)_-_2px,var(--frame-panel-radius))]\"\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, so\n                            the word moves to sr-only and the tab keeps its\n                            name. */}\n                        <StepperTitle className=\"text-current/60 data-[state=active]:text-current\">\n                          <span className=\"@max-md:sr-only\">Step </span>\n                          {step}\n                        </StepperTitle>\n                        {/* The primitive fills each state with its own color,\n                            so each is cleared for the icon to take the step's. */}\n                        <StepperIndicator className=\"size-3 overflow-visible bg-transparent text-current opacity-40 data-[state=active]:bg-transparent data-[state=active]:text-current data-[state=active]:opacity-100 data-[state=completed]:size-3.5 data-[state=completed]:bg-transparent data-[state=completed]:text-current\" />\n                      </span>\n                    </span>\n                  </StepperTrigger>\n                </StepperItem>\n              )\n            })}\n          </StepperNav>\n\n          {/* The card art is a static empty state; only the caption under\n              it follows the active step. */}\n          <StepperPanel className=\"mt-6 flex flex-col items-center\">\n            <CardIllustration />\n            {steps.map((item, index) => (\n              <StepperContent key={item.caption} value={index + 1}>\n                <p className=\"text-muted-foreground text-center text-sm\">\n                  {item.caption}\n                </p>\n              </StepperContent>\n            ))}\n            <StepperContent value={steps.length + 1}>\n              <p className=\"text-muted-foreground text-center text-sm\">\n                Card saved and ready to use\n              </p>\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        </FramePanel>\n      </Frame>\n    </Stepper>\n  )\n}","target":"components/examples/c-stepper-23.tsx"}],"meta":{"order":23,"gridSize":1,"label":"Top Color Bars"}}