{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "progress",
  "title": "Progress",
  "description": "Shows how far a task has come, as a bar, a hairline, discrete steps or a labelled percent, and admits it when nobody knows yet.",
  "dependencies": [
    "radix-ui",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/progress.tsx",
      "content": "import type * as React from \"react\"\nimport { useId } from \"react\"\nimport { Progress as ProgressPrimitive } from \"radix-ui\"\nimport { useReducedMotion } from \"motion/react\"\nimport { ProgressFill, ProgressSegments, ProgressSweep } from \"@/registry/miseui/ui/progress-parts\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype ProgressVariant = \"plain\" | \"thin\" | \"segmented\" | \"labelled\"\ntype ProgressEffect = \"shimmer\" | \"spring\"\n\nconst DEFAULT_MAX = 100\nconst DEFAULT_STEPS = 5\n\nconst TRACK_CLASS: Record<ProgressVariant, string> = {\n  plain: \"relative h-2 w-full overflow-hidden rounded-full bg-input/70\",\n  thin: \"relative h-0.5 w-full overflow-hidden rounded-full bg-input/70\",\n  // The cells carry the track colour themselves, so the gaps between them read as real breaks.\n  segmented: \"flex h-2 w-full gap-1 rounded-full\",\n  labelled: \"relative h-2 w-full overflow-hidden rounded-full bg-input/70\",\n}\n\ntype ProgressProps = Omit<React.ComponentProps<typeof ProgressPrimitive.Root>, \"value\" | \"max\"> & {\n  /** Current value. `null` or omitted means indeterminate: work is happening and no one knows how much. */\n  value?: number | null\n  max?: number\n  /** Names the bar for assistive tech. `labelled` also shows it above the bar. */\n  label?: string\n  /** `plain` bar, `thin` hairline, `segmented` N discrete cells, `labelled` label and percent above. */\n  variant?: ProgressVariant\n  /** `shimmer` sweeps a band while work runs; `spring` lets the fill settle on a critically damped spring. */\n  effect?: ProgressEffect\n  /** How many cells `segmented` draws. */\n  steps?: number\n}\n\nfunction clampValue(value: number | null | undefined, max: number) {\n  if (value === null || value === undefined || Number.isNaN(value)) return null\n  return Math.min(max, Math.max(0, value))\n}\n\ninterface ProgressMarksProps {\n  percent: number | null\n  variant: ProgressVariant\n  effect: ProgressEffect | undefined\n  steps: number\n}\n\nfunction ProgressMarks({ percent, variant, effect, steps }: ProgressMarksProps) {\n  const reduceMotion = useReducedMotion() ?? false\n  const spring = effect === \"spring\"\n  const shimmer = effect === \"shimmer\"\n  if (variant === \"segmented\") {\n    return <ProgressSegments percent={percent} steps={steps} spring={spring} shimmer={shimmer} reduceMotion={reduceMotion} />\n  }\n  if (percent === null) return <ProgressSweep reduceMotion={reduceMotion} />\n  // The band marks running work, so it stops the moment the bar is full.\n  const band = shimmer && percent < 100 && !reduceMotion\n  return <ProgressFill percent={percent} spring={spring} band={band} reduceMotion={reduceMotion} />\n}\n\nfunction Progress({\n  value,\n  max = DEFAULT_MAX,\n  label,\n  variant = \"plain\",\n  effect,\n  steps = DEFAULT_STEPS,\n  className,\n  ...props\n}: ProgressProps) {\n  const labelId = useId()\n  const clamped = clampValue(value, max)\n  const percent = clamped === null ? null : (clamped / max) * 100\n  const labelled = variant === \"labelled\"\n\n  const bar = (\n    <ProgressPrimitive.Root\n      data-slot={labelled ? \"progress-bar\" : \"progress\"}\n      data-variant={labelled ? undefined : variant}\n      data-effect={labelled ? undefined : effect}\n      value={clamped}\n      max={max}\n      aria-label={labelled ? undefined : label}\n      aria-labelledby={labelled ? labelId : undefined}\n      className={cn(TRACK_CLASS[variant], !labelled && className)}\n      {...props}\n    >\n      <ProgressMarks percent={percent} variant={variant} effect={effect} steps={steps} />\n    </ProgressPrimitive.Root>\n  )\n  if (!labelled) return bar\n  return (\n    <div data-slot=\"progress\" data-variant={variant} data-effect={effect} className={cn(\"grid w-full gap-2\", className)}>\n      <div className=\"flex items-baseline justify-between gap-3 text-sm\">\n        <span id={labelId} className=\"min-w-0 truncate font-medium\">\n          {label}\n        </span>\n        {percent === null ? null : (\n          <span aria-hidden=\"true\" className=\"shrink-0 text-[13px] text-muted-foreground tabular-nums\">\n            {Math.round(percent)}%\n          </span>\n        )}\n      </div>\n      {bar}\n    </div>\n  )\n}\n\nexport { Progress, type ProgressProps, type ProgressVariant, type ProgressEffect }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/progress-parts.tsx",
      "content": "import { motion } from \"motion/react\"\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\n/** A slow breath across the bar: it says \"still working\" without competing with the value itself. */\nconst BAND_SECONDS = 1.6\nconst BAND_REST_SECONDS = 0.4\nconst BAND_GRADIENT =\n  \"linear-gradient(90deg, transparent, color-mix(in oklab, var(--mise-foreground) 35%, transparent), transparent)\"\nconst BAND_OVER_EMPTY_GRADIENT =\n  \"linear-gradient(90deg, transparent, color-mix(in oklab, var(--mise) 55%, transparent), transparent)\"\n\nconst SWEEP_SECONDS = 1.4\nconst SWEEP_BAR_WIDTH_PERCENT = 40\n/** x is a share of the bar's own width: -100% parks it just past the left edge and 250% (100 / 40) past the right. */\nconst SWEEP_KEYFRAMES = [\"-100%\", \"250%\"]\nconst SEGMENT_PULSE_DELAY_SECONDS = 0.14\n\n/** Plain fills follow a value with a short timed transition; the spring is opt-in through `effect`. */\nconst PLAIN_FILL_CLASS = \"transition-[width] duration-(--duration-base) ease-(--ease-mise) motion-reduce:transition-none\"\n\nfunction ProgressBand({ overEmpty = false }: { overEmpty?: boolean }) {\n  return (\n    <motion.span\n      aria-hidden=\"true\"\n      data-slot=\"progress-band\"\n      className=\"absolute inset-0\"\n      style={{ backgroundImage: overEmpty ? BAND_OVER_EMPTY_GRADIENT : BAND_GRADIENT }}\n      initial={{ x: \"-100%\" }}\n      animate={{ x: \"100%\" }}\n      transition={{ duration: BAND_SECONDS, ease: \"easeInOut\", repeat: Infinity, repeatDelay: BAND_REST_SECONDS }}\n    />\n  )\n}\n\ninterface ProgressFillProps {\n  percent: number\n  spring: boolean\n  band: boolean\n  reduceMotion: boolean\n}\n\nfunction ProgressFill({ percent, spring, band, reduceMotion }: ProgressFillProps) {\n  const width = `${percent}%`\n  const className = \"absolute inset-y-0 left-0 overflow-hidden rounded-[inherit] bg-mise\"\n  if (spring && !reduceMotion) {\n    return (\n      <motion.span\n        data-slot=\"progress-fill\"\n        className={className}\n        initial={false}\n        animate={{ width }}\n        transition={SPRING_DEFAULT}\n        style={{ width }}\n      >\n        {band ? <ProgressBand /> : null}\n      </motion.span>\n    )\n  }\n  return (\n    <span data-slot=\"progress-fill\" className={cn(className, PLAIN_FILL_CLASS)} style={{ width }}>\n      {band ? <ProgressBand /> : null}\n    </span>\n  )\n}\n\nfunction ProgressSweep({ reduceMotion }: { reduceMotion: boolean }) {\n  if (reduceMotion) {\n    return (\n      <span\n        data-slot=\"progress-indeterminate\"\n        data-reduced=\"true\"\n        className=\"absolute inset-0 rounded-[inherit] bg-mise/50\"\n      />\n    )\n  }\n  return (\n    <motion.span\n      data-slot=\"progress-indeterminate\"\n      data-reduced=\"false\"\n      className=\"absolute inset-y-0 left-0 rounded-[inherit] bg-mise\"\n      style={{ width: `${SWEEP_BAR_WIDTH_PERCENT}%` }}\n      initial={{ x: SWEEP_KEYFRAMES[0] }}\n      animate={{ x: SWEEP_KEYFRAMES[1] }}\n      transition={{ duration: SWEEP_SECONDS, ease: \"easeInOut\", repeat: Infinity }}\n    />\n  )\n}\n\ninterface ProgressSegmentsProps {\n  /** null means indeterminate. */\n  percent: number | null\n  steps: number\n  spring: boolean\n  shimmer: boolean\n  reduceMotion: boolean\n}\n\nfunction SegmentFill({ filled, animated }: { filled: boolean; animated: boolean }) {\n  const className = \"absolute inset-0 origin-left bg-mise\"\n  if (animated) {\n    return <motion.span className={className} initial={false} animate={{ scaleX: filled ? 1 : 0 }} transition={SPRING_DEFAULT} />\n  }\n  return filled ? <span className={className} /> : null\n}\n\nfunction SegmentPulse({ index, reduceMotion }: { index: number; reduceMotion: boolean }) {\n  if (reduceMotion) {\n    return <span data-slot=\"progress-indeterminate\" data-reduced=\"true\" className=\"absolute inset-0 bg-mise/50\" />\n  }\n  return (\n    <motion.span\n      data-slot=\"progress-indeterminate\"\n      data-reduced=\"false\"\n      className=\"absolute inset-0 bg-mise\"\n      initial={{ opacity: 0.15 }}\n      animate={{ opacity: [0.15, 1, 0.15] }}\n      transition={{\n        duration: SWEEP_SECONDS,\n        ease: \"easeInOut\",\n        repeat: Infinity,\n        delay: index * SEGMENT_PULSE_DELAY_SECONDS,\n      }}\n    />\n  )\n}\n\nfunction ProgressSegments({ percent, steps, spring, shimmer, reduceMotion }: ProgressSegmentsProps) {\n  const indeterminate = percent === null\n  // Floor, not round: a cell lights when its step is finished, so 99 percent never reads as done.\n  const filledCount = indeterminate ? 0 : Math.floor((percent / 100) * steps)\n  return (\n    <>\n      {Array.from({ length: steps }, (_, index) => {\n        const filled = index < filledCount\n        return (\n          <span\n            key={index}\n            data-slot=\"progress-segment\"\n            data-filled={filled ? \"true\" : \"false\"}\n            className=\"relative h-full min-w-0 flex-1 overflow-hidden rounded-[inherit] bg-input/70\"\n          >\n            {indeterminate ? (\n              <SegmentPulse index={index} reduceMotion={reduceMotion} />\n            ) : (\n              <SegmentFill filled={filled} animated={spring && !reduceMotion} />\n            )}\n            {shimmer && !reduceMotion && index === filledCount ? <ProgressBand overEmpty /> : null}\n          </span>\n        )\n      })}\n    </>\n  )\n}\n\nexport { ProgressFill, ProgressSweep, ProgressSegments }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "feedback"
  ],
  "type": "registry:ui"
}