{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "progress-numbered",
  "type": "registry:component",
  "title": "Progress (Numbered)",
  "description": "Numbered step progress with collapse/ellipsis.",
  "registryDependencies": [
    "tooltip"
  ],
  "files": [
    {
      "path": "registry/turbopills/ui/progress/progress.tsx",
      "content": "\"use client\";\r\n\r\nimport * as React from \"react\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport {\r\n  Tooltip,\r\n  TooltipContent,\r\n  TooltipTrigger,\r\n} from \"@/components/ui/tooltip\";\r\n\r\nexport interface ProgressStep {\r\n  label?: string;\r\n  tooltip?: string;\r\n}\r\n\r\nexport interface ProgressProps {\r\n  value?: number;\r\n  animated?: boolean;\r\n  filledClassName?: string;\r\n  unfilledClassName?: string;\r\n  currentClassName?: string;\r\n  className?: string;\r\n}\r\n\r\nexport function useProgressHelpers({\r\n  value = 0,\r\n  stepCount,\r\n  highlightCurrent = false,\r\n  animated = true,\r\n  filledClassName,\r\n  unfilledClassName,\r\n  currentClassName,\r\n}: {\r\n  value?: number;\r\n  stepCount: number;\r\n  highlightCurrent?: boolean;\r\n  animated?: boolean;\r\n  filledClassName?: string;\r\n  unfilledClassName?: string;\r\n  currentClassName?: string;\r\n}) {\r\n  const transitionClass = animated ? \"transition-all duration-500\" : \"\";\r\n  const clampedValue = Math.min(Math.max(value, 0), 100);\r\n\r\n  const getStepState = (index: number) => {\r\n    const midpoint = ((index + 0.5) / stepCount) * 100;\r\n    const isFilled = value >= midpoint;\r\n    const nextMidpoint = ((index + 1.5) / stepCount) * 100;\r\n    const isCurrent = isFilled && value < nextMidpoint;\r\n    return { isFilled, isCurrent };\r\n  };\r\n\r\n  const getStateClasses = (isFilled: boolean, isCurrent: boolean) => {\r\n    if (isCurrent) {\r\n      return cn(\r\n        \"bg-primary\",\r\n        highlightCurrent ? \"ring-2 ring-primary/70\" : \"\",\r\n        currentClassName,\r\n      );\r\n    }\r\n    return isFilled\r\n      ? cn(\"bg-primary\", filledClassName)\r\n      : cn(\"bg-muted\", unfilledClassName);\r\n  };\r\n\r\n  return {\r\n    transitionClass,\r\n    clampedValue,\r\n    getStepState,\r\n    getStateClasses,\r\n  };\r\n}\r\n\r\nexport function StepLabel({ label }: { label?: string }) {\r\n  if (!label) return null;\r\n  return (\r\n    <span className=\"absolute top-full mt-2 text-xs text-muted-foreground whitespace-nowrap\">\r\n      {label}\r\n    </span>\r\n  );\r\n}\r\n\r\nexport function WithTooltip({\r\n  children,\r\n  tooltip,\r\n}: {\r\n  children: React.ReactElement;\r\n  tooltip?: string;\r\n}) {\r\n  if (!tooltip) return children;\r\n  return (\r\n    <Tooltip>\r\n      <TooltipTrigger asChild>{children}</TooltipTrigger>\r\n      <TooltipContent>{tooltip}</TooltipContent>\r\n    </Tooltip>\r\n  );\r\n}\r\n\r\nexport const ProgressContainer = React.forwardRef<\r\n  HTMLDivElement,\r\n  {\r\n    children: React.ReactNode;\r\n    hasLabels: boolean;\r\n  }\r\n>(({ children, hasLabels }, ref) => {\r\n  return (\r\n    <div ref={ref} className={cn(\"relative w-full\", hasLabels && \"pb-6\")}>\r\n      {children}\r\n    </div>\r\n  );\r\n});\r\n\r\nProgressContainer.displayName = \"ProgressContainer\";\r\n\r\nexport function renderStepsArray(\r\n  count: number,\r\n  steps: ProgressStep[] | undefined,\r\n  renderItem: (index: number, step?: ProgressStep) => React.ReactNode,\r\n) {\r\n  return Array.from({ length: count }, (_, index) =>\r\n    renderItem(index, steps?.[index]),\r\n  );\r\n}\r\n",
      "type": "registry:component",
      "target": "components/turbopills/ui/progress/progress.tsx"
    },
    {
      "path": "registry/turbopills/ui/progress/progress-numbered.tsx",
      "content": "\"use client\";\r\n\r\nimport { forwardRef, useRef, useState, useEffect, useMemo } from \"react\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport {\r\n  type ProgressProps,\r\n  type ProgressStep,\r\n  useProgressHelpers,\r\n  ProgressContainer,\r\n  StepLabel,\r\n  WithTooltip,\r\n} from \"./progress\";\r\n\r\nconst STEP_SIZE = 32;\r\nconst STEP_GAP = 4;\r\nconst CONNECTOR_HEIGHT = 4;\r\n\r\nexport type ProgressNumberedProps = ProgressProps & {\r\n  highlightCurrent?: boolean;\r\n  steps?: ProgressStep[] | number;\r\n};\r\n\r\nfunction getVisibleIndices(\r\n  total: number,\r\n  maxSlots: number,\r\n  activeIdx: number,\r\n): number[] | null {\r\n  if (total <= maxSlots) return null;\r\n\r\n  const active = Math.max(0, Math.min(total - 1, activeIdx));\r\n  const pinned = new Set<number>([0, total - 1, active]);\r\n\r\n  for (let d = 1; pinned.size < maxSlots; d++) {\r\n    if (active - d >= 0) pinned.add(active - d);\r\n    if (pinned.size < maxSlots && active + d < total) pinned.add(active + d);\r\n    if (active - d < 0 && active + d >= total) break;\r\n  }\r\n\r\n  const sorted = Array.from(pinned).sort((a, b) => a - b);\r\n\r\n  const countEllipses = (arr: number[]) =>\r\n    arr.reduce(\r\n      (count, value, i) =>\r\n        i > 0 && value - arr[i - 1] > 1 ? count + 1 : count,\r\n      0,\r\n    );\r\n\r\n  while (sorted.length + countEllipses(sorted) > maxSlots && sorted.length > 2) {\r\n    let worstIndex = -1;\r\n    let worstDistance = -1;\r\n\r\n    for (let i = 1; i < sorted.length - 1; i++) {\r\n      const distance = Math.abs(sorted[i] - active);\r\n\r\n      if (distance > worstDistance) {\r\n        worstDistance = distance;\r\n        worstIndex = i;\r\n      }\r\n    }\r\n\r\n    if (worstIndex === -1) break;\r\n    sorted.splice(worstIndex, 1);\r\n  }\r\n\r\n  return sorted;\r\n}\r\n\r\nexport const ProgressNumbered = forwardRef<HTMLDivElement, ProgressNumberedProps>(\r\n  (\r\n    {\r\n      className,\r\n      value = 0,\r\n      animated = true,\r\n      highlightCurrent = false,\r\n      filledClassName,\r\n      unfilledClassName,\r\n      currentClassName,\r\n      steps,\r\n    },\r\n    ref,\r\n  ) => {\r\n    const stepCount = Array.isArray(steps)\r\n      ? steps.length\r\n      : typeof steps === \"number\"\r\n        ? steps\r\n        : 4;\r\n    const stepsArray = Array.isArray(steps) ? steps : undefined;\r\n    const hasLabels = stepsArray?.some((s) => s.label) ?? false;\r\n\r\n    const innerRef = useRef<HTMLDivElement>(null);\r\n    const [containerWidth, setContainerWidth] = useState<number | null>(null);\r\n\r\n    useEffect(() => {\r\n      const el = innerRef.current;\r\n\r\n      if (!el) return;\r\n\r\n      const update = () => setContainerWidth(el.offsetWidth);\r\n      update();\r\n\r\n      const observer = new ResizeObserver(update);\r\n      observer.observe(el);\r\n\r\n      return () => observer.disconnect();\r\n    }, []);\r\n\r\n    const { transitionClass, getStepState, getStateClasses } =\r\n      useProgressHelpers({\r\n        value,\r\n        stepCount,\r\n        highlightCurrent,\r\n        animated,\r\n        filledClassName,\r\n        unfilledClassName,\r\n        currentClassName,\r\n      });\r\n\r\n    const activeIdx = Math.max(\r\n      0,\r\n      Math.min(stepCount - 1, Math.round((value * stepCount) / 100) - 1),\r\n    );\r\n\r\n    const maxSlots =\r\n      containerWidth !== null\r\n        ? Math.floor((containerWidth + STEP_GAP) / (STEP_SIZE + STEP_GAP))\r\n        : stepCount;\r\n\r\n    const visibleIndices = useMemo(\r\n      () => getVisibleIndices(stepCount, maxSlots, activeIdx),\r\n      [stepCount, maxSlots, activeIdx],\r\n    );\r\n\r\n    const isCollapsed = visibleIndices !== null;\r\n\r\n    const renderConnector = (fromIndex: number) => {\r\n      const { isFilled } = getStepState(fromIndex + 1);\r\n      return (\r\n        <div\r\n          key={`connector-${fromIndex}`}\r\n          className={cn(\r\n            \"rounded-full self-center\",\r\n            transitionClass,\r\n            isFilled ? \"bg-primary\" : \"bg-muted\",\r\n          )}\r\n          style={{ flex: \"1 1 0\", minWidth: 0, height: CONNECTOR_HEIGHT }}\r\n        />\r\n      );\r\n    };\r\n\r\n    const renderStep = (index: number, step?: ProgressStep) => {\r\n      const { isFilled, isCurrent } = getStepState(index);\r\n\r\n      return (\r\n        <div key={index} className=\"relative flex flex-col items-center\">\r\n          <WithTooltip tooltip={step?.tooltip}>\r\n            <div\r\n              className={cn(\r\n                \"flex items-center justify-center rounded-full w-8 h-8 text-sm font-medium\",\r\n                transitionClass,\r\n                getStateClasses(isFilled, isCurrent),\r\n                isFilled || isCurrent\r\n                  ? \"text-primary-foreground\"\r\n                  : \"text-muted-foreground\",\r\n              )}\r\n            >\r\n              {index + 1}\r\n            </div>\r\n          </WithTooltip>\r\n\r\n          {!isCollapsed && <StepLabel label={step?.label} />}\r\n        </div>\r\n      );\r\n    };\r\n\r\n    const renderContent = () => {\r\n      if (!isCollapsed) {\r\n        const elements = [];\r\n        for (let i = 0; i < stepCount; i++) {\r\n          elements.push(renderStep(i, stepsArray?.[i]));\r\n          if (i < stepCount - 1) {\r\n            elements.push(renderConnector(i));\r\n          }\r\n        }\r\n        return elements;\r\n      }\r\n\r\n      return visibleIndices.flatMap((idx, i) => {\r\n        const prev = visibleIndices[i - 1];\r\n        const hasGap = i > 0 && idx - prev > 1;\r\n        const elements = [];\r\n\r\n        if (i > 0) {\r\n          if (hasGap) {\r\n            elements.push(\r\n              <div\r\n                key={`ellipsis-${i}`}\r\n                className=\"flex items-center justify-center w-8 h-8 text-sm text-muted-foreground\"\r\n              >\r\n                …\r\n              </div>,\r\n            );\r\n          } else {\r\n            elements.push(renderConnector(prev));\r\n          }\r\n        }\r\n\r\n        elements.push(renderStep(idx, stepsArray?.[idx]));\r\n        return elements;\r\n      });\r\n    };\r\n\r\n    return (\r\n      <ProgressContainer ref={ref} hasLabels={hasLabels && !isCollapsed}>\r\n        <div\r\n          ref={innerRef}\r\n          className={cn(\"relative flex items-center gap-1\", className)}\r\n        >\r\n          {renderContent()}\r\n        </div>\r\n      </ProgressContainer>\r\n    );\r\n  },\r\n);\r\n\r\nProgressNumbered.displayName = \"ProgressNumbered\";",
      "type": "registry:component",
      "target": "components/turbopills/ui/progress/progress-numbered.tsx"
    }
  ]
}