{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "progress-bar",
  "title": "Progress Bar",
  "description": "Progress of a known-size task as a bar or segments, with indeterminate start and status.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "@ikas/motion",
    "@ikas/step-kit",
    "@ikas/swap-text",
    "@ikas/theme",
    "@ikas/utils"
  ],
  "files": [
    {
      "path": "registry/ikas/progress-bar.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, animate, motion, useMotionValue, useMotionValueEvent, useReducedMotion, useTransform } from \"motion/react\"\n\nimport { EASE_OUT, ICON_SPRING, INSTANT, SPRING } from \"@/lib/motion\"\nimport { cn } from \"@/lib/utils\"\nimport { SwapText } from \"@/components/ikas/swap-text\"\nimport { CheckGlyph, useStepTravel } from \"@/components/ikas/step-kit\"\n\ntype ProgressBarStatus = \"neutral\" | \"success\" | \"warning\" | \"danger\"\ntype ProgressSegmentType = \"skew\" | \"block\" | \"pill\" | \"notch\" | \"dot\" | \"diamond\"\n\n/** Fill and track from one ramp, like Meter, so the state reads across the whole bar. */\nconst tone: Record<ProgressBarStatus, string> = {\n  neutral: \"[--fill:var(--chart-ink)] [--track:color-mix(in_oklab,var(--chart-ink)_9%,var(--card))]\",\n  success: \"[--fill:var(--success)] [--track:color-mix(in_oklab,var(--success)_16%,var(--card))]\",\n  warning: \"[--fill:var(--warning)] [--track:color-mix(in_oklab,var(--warning)_22%,var(--card))]\",\n  danger: \"[--fill:var(--danger)] [--track:color-mix(in_oklab,var(--danger)_16%,var(--card))]\",\n}\n\ntype ProgressBarProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** Work done so far. Clamped to 0…max. In the segments variant, the number of finished steps. */\n  value?: number\n  /** Total work. In the segments variant, the number of segments (3–8 read well). */\n  max?: number\n  /** Shown above the bar and used as its accessible name. A new label rises in. */\n  label?: string\n  /** Text for the value on the right. Defaults to \"%NN\" (bar) or \"02/05\" (segments). */\n  valueFormat?: (value: number, max: number) => string\n  /** Hides the value on the right. */\n  hideValue?: boolean\n  /** neutral while running, warning when paused, danger on error. `auto` turns success at the end. */\n  status?: ProgressBarStatus | \"auto\"\n  /** No total yet: a bar travels the track. When it turns false, the fill starts where that bar is. */\n  indeterminate?: boolean\n  /** One line under the bar: time left, the error and its retry. A new string rises in. */\n  hint?: React.ReactNode\n  /** bar for continuous work, segments for a few equal steps. */\n  variant?: \"bar\" | \"segments\"\n  /** Segment look. skew matches Launchpad's header. */\n  segmentType?: ProgressSegmentType\n}\n\nconst percentFormat = (value: number, max: number) => `%${Math.round((value / max) * 100)}`\nconst stepFormat = (value: number, max: number) => `${String(Math.round(value)).padStart(2, \"0\")}/${String(max).padStart(2, \"0\")}`\n\nconst segmentShape: Record<ProgressSegmentType, string> = {\n  skew: \"h-2 flex-1 -skew-x-[20deg] rounded-[2px]\",\n  block: \"h-2 flex-1 rounded-[2px]\",\n  pill: \"h-2 flex-1 rounded-full\",\n  notch: \"h-1.5 flex-1 first:rounded-l-full last:rounded-r-full\",\n  dot: \"size-2.5 rounded-full\",\n  diamond: \"size-2.5 rotate-45 rounded-[2px]\",\n}\nconst segmentGap: Record<ProgressSegmentType, string> = { skew: \"gap-[3px]\", block: \"gap-[3px]\", pill: \"gap-[3px]\", notch: \"gap-0.5\", dot: \"gap-3\", diamond: \"gap-3\" }\n\n/**\n * Progress of a task with a known size: an import, a sync, a few setup steps. One spring drives the\n * fill and the number, so they never disagree; the fill slides (not scales) so its rounded end holds.\n * At the end it turns green and a check draws in. For quotas use Meter; for ordered flows use Steps.\n */\nfunction ProgressBar({\n  value = 0,\n  max = 100,\n  label,\n  valueFormat,\n  hideValue = false,\n  status = \"auto\",\n  indeterminate = false,\n  hint,\n  variant = \"bar\",\n  segmentType = \"skew\",\n  className,\n  ...props\n}: ProgressBarProps) {\n  const reduce = useReducedMotion()\n  const safeMax = max > 0 ? max : 100\n  const clamped = Math.min(Math.max(value, 0), safeMax)\n  const complete = !indeterminate && clamped >= safeMax\n  const resolved: ProgressBarStatus = status === \"auto\" ? (complete ? \"success\" : \"neutral\") : status\n  const format = valueFormat ?? (variant === \"segments\" ? stepFormat : percentFormat)\n  const text = indeterminate ? \"\" : format(clamped, safeMax)\n  const labelId = React.useId()\n  // One spring drives the fill and the number, so they never disagree.\n  const trackRef = React.useRef<HTMLDivElement>(null)\n  const indeterminateRef = React.useRef<HTMLSpanElement>(null)\n  const progress = useFill(clamped, safeMax, indeterminate, indeterminateRef, trackRef)\n\n  return (\n    <div\n      data-slot=\"progress-bar\"\n      data-status={resolved}\n      data-variant={variant}\n      role=\"progressbar\"\n      aria-labelledby={label ? labelId : undefined}\n      aria-label={label ? undefined : \"İlerleme\"}\n      aria-valuemin={0}\n      aria-valuemax={safeMax}\n      aria-valuenow={indeterminate ? undefined : clamped}\n      aria-valuetext={indeterminate ? \"Hazırlanıyor\" : text}\n      className={cn(\"flex flex-col gap-2\", tone[resolved], className)}\n      {...props}\n    >\n      {(label || !hideValue) && (\n        <div className=\"flex items-baseline justify-between gap-3 text-[13px]\">\n          <span id={labelId} className=\"min-w-0 truncate text-sm font-semibold text-foreground\">\n            {label && <SwapText>{label}</SwapText>}\n          </span>\n          {!hideValue && (\n            <span className=\"inline-flex shrink-0 items-center gap-1 text-muted-foreground tabular-nums\" aria-hidden>\n              {/* Completion lands with the fill: the check settles in beside the final count. */}\n              <AnimatePresence initial={false}>\n                {complete && (\n                  <motion.span\n                    key=\"done\"\n                    className=\"flex text-success\"\n                    initial={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.6 }}\n                    animate={{ opacity: 1, scale: 1, transition: reduce ? { duration: 0.12 } : { scale: { ...ICON_SPRING, delay: 0.2 }, opacity: { duration: 0.16, delay: 0.2 } } }}\n                    exit={{ opacity: 0, transition: { duration: 0.1 } }}\n                  >\n                    <CheckGlyph delay={0.2} />\n                  </motion.span>\n                )}\n              </AnimatePresence>\n              {variant === \"segments\" ? (\n                <SwapText swapKey={text}>{text}</SwapText>\n              ) : (\n                <BarCount progress={progress} value={clamped} max={safeMax} format={format} hidden={indeterminate} />\n              )}\n            </span>\n          )}\n        </div>\n      )}\n      {variant === \"segments\" ? (\n        // A new type starts fresh: bar types fill with clip-path, point types with scale, and neither may inherit the other's state.\n        <Segments key={segmentType} value={Math.round(clamped)} count={safeMax} type={segmentType} status={resolved} />\n      ) : (\n        <Bar progress={progress} indeterminate={indeterminate} trackRef={trackRef} indeterminateRef={indeterminateRef} />\n      )}\n      {hint && (\n        <div className=\"flex min-h-5 items-center text-[12.5px] text-muted-foreground [&_svg]:size-3.5 [&_svg]:shrink-0\">\n          <SwapText swapKey={typeof hint === \"string\" ? hint : undefined}>{hint}</SwapText>\n        </div>\n      )}\n    </div>\n  )\n}\n\n/** Shared spring for the fill and the number; reads the travelling bar's edge when the total arrives. */\nfunction useFill(value: number, max: number, indeterminate: boolean, indeterminateRef: React.RefObject<HTMLSpanElement | null>, trackRef: React.RefObject<HTMLDivElement | null>) {\n  const reduce = useReducedMotion()\n  const pct = (value / max) * 100\n  const progress = useMotionValue(indeterminate ? 0 : pct)\n  const wasIndeterminate = React.useRef(indeterminate)\n\n  React.useLayoutEffect(() => {\n    if (wasIndeterminate.current && !indeterminate && !reduce && indeterminateRef.current && trackRef.current) {\n      const track = trackRef.current.getBoundingClientRect()\n      const bar = indeterminateRef.current.getBoundingClientRect()\n      // Start where the eye already is, but never ahead of the real value.\n      progress.jump(Math.min(pct, Math.max(0, ((bar.right - track.left) / track.width) * 100)))\n    }\n    wasIndeterminate.current = indeterminate\n  }, [indeterminate, pct, progress, reduce, indeterminateRef, trackRef])\n\n  React.useEffect(() => {\n    if (indeterminate) return\n    if (reduce) return progress.jump(pct)\n    const controls = animate(progress, pct, SPRING)\n    return () => controls.stop()\n  }, [pct, indeterminate, progress, reduce])\n\n  return progress\n}\n\ntype Progress = ReturnType<typeof useMotionValue<number>>\n\nfunction Bar({\n  progress,\n  indeterminate,\n  trackRef,\n  indeterminateRef,\n}: {\n  progress: Progress\n  indeterminate: boolean\n  trackRef: React.RefObject<HTMLDivElement | null>\n  indeterminateRef: React.RefObject<HTMLSpanElement | null>\n}) {\n  const reduce = useReducedMotion()\n  const x = useTransform(progress, (p) => `${Math.min(Math.max(p, 0), 100) - 100}%`)\n\n  return (\n    <div ref={trackRef} className=\"relative h-[7px] overflow-hidden rounded-full bg-(--track) transition-colors duration-200 ease-out\">\n      {/* The fill slides in from the left instead of scaling, so its rounded end keeps its shape. */}\n      <motion.span\n        className=\"absolute inset-0 rounded-[inherit] bg-(--fill) transition-colors delay-150 duration-200 ease-out\"\n        style={{ x }}\n        animate={{ opacity: indeterminate ? 0 : 1 }}\n        transition={reduce ? INSTANT : { duration: 0.16, ease: EASE_OUT }}\n      />\n      <AnimatePresence initial={false}>\n        {indeterminate && (\n          <motion.span\n            ref={indeterminateRef}\n            key=\"indeterminate\"\n            className=\"absolute inset-y-0 left-0 animate-progress-indeterminate rounded-[inherit] bg-(--fill)\"\n            exit={{ opacity: 0, transition: { duration: 0.12 } }}\n          />\n        )}\n      </AnimatePresence>\n    </div>\n  )\n}\n\n/** The number on the right reads the fill's spring. A hidden \"max\" row reserves its width. */\nfunction BarCount({\n  progress,\n  value,\n  max,\n  format,\n  hidden,\n}: {\n  progress: Progress\n  value: number\n  max: number\n  format: (value: number, max: number) => string\n  hidden: boolean\n}) {\n  const [text, setText] = React.useState(() => format(value, max))\n  useMotionValueEvent(progress, \"change\", (p) => setText(format((Math.min(Math.max(p, 0), 100) / 100) * max, max)))\n\n  return (\n    // The visible number comes first: the grid takes its baseline from its first item, and the\n    // zero-height width reserve would pull the number below the label.\n    <span className=\"inline-grid justify-items-end\">\n      <span className=\"col-start-1 row-start-1\">{hidden ? \"…\" : text}</span>\n      <span className=\"invisible col-start-1 row-start-1 h-0 overflow-hidden\">{format(max, max)}</span>\n    </span>\n  )\n}\n\nfunction Segments({ value, count, type, status }: { value: number; count: number; type: ProgressSegmentType; status: ProgressBarStatus }) {\n  const reduce = useReducedMotion()\n  const travel = useStepTravel(value)\n  const point = type === \"dot\" || type === \"diamond\"\n  // When the last segment's fill ends (its travel delay plus the fill itself), in ms.\n  const lastLanding = Math.round((travel.delay(count - 1) + 0.24) * 1000)\n\n  return (\n    <div className={cn(\"flex min-h-3 items-center\", segmentGap[type])}>\n      {Array.from({ length: count }, (_, i) => {\n        const on = i < value\n        const isCurrent = i === value && value < count\n        const delay = travel.delay(i)\n        return (\n          <span\n            key={i}\n            data-state={on ? \"done\" : isCurrent ? \"current\" : \"upcoming\"}\n            className={cn(\n              \"relative bg-border-strong transition-[box-shadow,background-color] duration-200 ease-out\",\n              point ? \"overflow-visible\" : \"overflow-hidden\",\n              segmentShape[type],\n              // Bars mark the current step with a half tone; points with a halo.\n              isCurrent && !point && (status === \"danger\" ? \"bg-danger\" : status === \"warning\" ? \"bg-warning\" : \"bg-[color-mix(in_oklab,var(--chart-ink)_35%,var(--card))]\"),\n              isCurrent && point && (status === \"danger\" ? \"shadow-[0_0_0_3px_color-mix(in_oklab,var(--danger)_22%,transparent)]\" : \"shadow-[0_0_0_3px_color-mix(in_oklab,var(--chart-ink)_14%,transparent)]\")\n            )}\n          >\n            <motion.i\n              className={cn(\n                \"absolute inset-0 rounded-[inherit] transition-[background-color] duration-200 ease-out motion-reduce:transition-none\",\n                isCurrent && point && status === \"danger\" ? \"bg-danger\" : status === \"success\" ? \"bg-success\" : \"bg-chart-ink\"\n              )}\n              // Done turns green as a wave from the left once the last segment has landed; undoing turns back at once.\n              style={{ transitionDelay: status === \"success\" && !reduce ? `${lastLanding + i * 40}ms` : \"0ms\" }}\n              initial={false}\n              animate={\n                point\n                  ? { opacity: on || (isCurrent && status === \"danger\") ? 1 : 0, scale: on || (isCurrent && status === \"danger\") ? 1 : 0.6 }\n                  : { clipPath: on ? \"inset(0 0% 0 0)\" : \"inset(0 100% 0 0)\" }\n              }\n              transition={\n                reduce ? INSTANT\n                : point ? { scale: { ...ICON_SPRING, delay }, opacity: { duration: 0.16, delay } }\n                : { duration: 0.24, ease: EASE_OUT, delay }\n              }\n            />\n          </span>\n        )\n      })}\n    </div>\n  )\n}\n\nexport { ProgressBar, type ProgressBarProps, type ProgressBarStatus, type ProgressSegmentType }\n",
      "type": "registry:component",
      "target": "@components/ikas/progress-bar.tsx"
    }
  ],
  "docs": "https://builders.ikas.com/tr/docs/app-development/ui-kit/components/progress-bar",
  "categories": [
    "feedback",
    "motion"
  ],
  "type": "registry:component"
}