{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "progress",
  "title": "Progress",
  "description": "Progress from the Get IT Done design system.",
  "dependencies": [
    "class-variance-authority@^0.7.0"
  ],
  "registryDependencies": [
    "https://zero-design.itdone.de/r/labels-context.json",
    "https://zero-design.itdone.de/r/utils.json",
    "https://zero-design.itdone.de/r/theme.json"
  ],
  "files": [
    {
      "path": "registry/ui/progress.tsx",
      "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\nimport { usePreset } from '@/components/ui/labels-context';\n\nconst progressVariants = cva('relative w-full overflow-hidden rounded-full bg-background-neutral-default', {\n  variants: {\n    size: {\n      sm: 'h-1',\n      md: 'h-progress-md',\n    },\n  },\n  defaultVariants: { size: 'md' },\n});\n\nconst progressIndicatorVariants = cva('h-full rounded-full transition-[width] duration-150 motion-reduce:transition-none', {\n  variants: {\n    variant: {\n      // Ring token: bg-primary has no dark value, so it vanished on dark surfaces. Contrast 3:1 or more against track and surface in every theme, light and dark.\n      default: 'bg-general-ring-default',\n      destructive: 'bg-text-destructive',\n      success: 'bg-text-success',\n      warning: 'bg-text-warning',\n    },\n  },\n  defaultVariants: { variant: 'default' },\n});\n\nexport interface ProgressSegment {\n  value: number;\n  // Segment colour, same set as the Progress variants. Every tone keeps 3:1 against the track in all themes.\n  tone?: 'default' | 'success' | 'warning' | 'destructive';\n  // Names the segment for assistive tech, e.g. \"Done\".\n  label?: string;\n  // Custom colour that wins over `tone`: a CSS colour (e.g. a project colour from data) or a bg- token utility such as \"bg-data-project-1\". Utilities must appear literally in your source so Tailwind generates them. Keep 3:1 against the track and name the segment with `label`, colour alone carries no meaning.\n  color?: string;\n}\n\nconst isUtilityColor = (color: string) => /^bg-[a-z0-9/-]+$/.test(color);\n\nconst segmentTone = {\n  default: 'bg-general-ring-default',\n  success: 'bg-text-success',\n  warning: 'bg-text-warning',\n  destructive: 'bg-text-destructive',\n} as const;\n\nexport interface ProgressProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>,\n    VariantProps<typeof progressVariants>,\n    VariantProps<typeof progressIndicatorVariants> {\n  /** Current value. `null`/`undefined` renders the indeterminate state (no `aria-valuenow`). */\n  value?: number | null;\n  // Stacked multi-segment mode: segments fill the bar left to right and replace `value`. The bar reports their sum.\n  segments?: ProgressSegment[];\n  /** Upper bound, default 100. */\n  max?: number;\n  // Shows the percentage next to the bar (Figma Label): right of it, under it, or in a bubble above/below the indicator end.\n  label?: 'right' | 'bottom' | 'top-floating' | 'bottom-floating';\n}\n\n/**\n * Linear progress bar (`role=\"progressbar\"`). Provide an accessible name via `aria-label` or\n * `aria-labelledby`. Values are clamped to `[0, max]`.\n */\nconst Progress = React.forwardRef<HTMLDivElement, ProgressProps>(\n  ({ className, size, variant, value: valueProp, segments, max = 100, label, ...props }, ref) => {\n    const zero = usePreset() === 'zero';\n    const stacked = segments != null && segments.length > 0;\n    const value = stacked ? segments.reduce((sum, seg) => sum + Math.max(seg.value, 0), 0) : valueProp;\n    const determinate = value != null;\n    const clamped = determinate ? Math.min(Math.max(value, 0), max) : 0;\n    const percent = determinate && max > 0 ? (clamped / max) * 100 : 0;\n    const bar = (\n      <div\n        ref={ref}\n        role=\"progressbar\"\n        aria-valuemin={0}\n        aria-valuemax={max}\n        aria-valuenow={determinate ? clamped : undefined}\n        aria-valuetext={stacked && segments.some((seg) => seg.label) ? segments.map((seg) => `${seg.label ?? ''} ${seg.value}`.trim()).join(', ') : undefined}\n        data-state={determinate ? (clamped >= max ? 'complete' : 'loading') : 'indeterminate'}\n        className={cn(progressVariants({ size }), zero && 'rounded-tight', zero && size === 'sm' && 'h-progress-md', !label && className)}\n        {...props}\n      >\n        {stacked ? (\n          <div className=\"flex h-full w-full\">\n            {segments.map((seg, i) => (\n              <div\n                key={i}\n                data-progress-segment=\"\"\n                className={cn(\n                  'h-full shrink-0 transition-[width] duration-150 motion-reduce:transition-none',\n                  seg.color ? (isUtilityColor(seg.color) ? seg.color : undefined) : segmentTone[seg.tone ?? 'default'],\n                  i < segments.length - 1 && 'border-r border-solid border-background',\n                )}\n                style={{\n                  width: `${max > 0 ? Math.min(Math.max(seg.value, 0) / max, 1) * 100 : 0}%`,\n                  ...(seg.color && !isUtilityColor(seg.color) ? { backgroundColor: seg.color } : null),\n                }}\n              />\n            ))}\n          </div>\n        ) : (\n          <div\n            data-progress-indicator=\"\"\n            className={cn(progressIndicatorVariants({ variant }), zero && 'rounded-tight', !determinate && 'w-2/5 animate-pulse motion-reduce:animate-none')}\n            style={determinate ? { width: `${percent}%` } : undefined}\n          />\n        )}\n      </div>\n    );\n    if (!label) return bar;\n    const text = (\n      <span aria-hidden=\"true\" data-progress-label=\"\" className=\"typography-ui tabular-nums text-foreground\">\n        {Math.round(percent)}%\n      </span>\n    );\n    if (label === 'right') {\n      return (\n        <div className={cn('flex w-full items-center gap-3', className)}>\n          <div className=\"min-w-0 flex-1\">{bar}</div>\n          {text}\n        </div>\n      );\n    }\n    if (label === 'bottom') {\n      return (\n        <div className={cn('flex w-full flex-col gap-2', className)}>\n          {bar}\n          <span className=\"self-end\">{text}</span>\n        </div>\n      );\n    }\n    const floating = (\n      <span\n        aria-hidden=\"true\"\n        data-progress-label=\"\"\n        style={{ left: `${percent}%` }}\n        className={cn(\n          'absolute -translate-x-1/2 whitespace-nowrap rounded-item border border-solid border-border-default bg-background px-2 py-1 typography-caption tabular-nums text-foreground shadow-xs',\n          label === 'top-floating' ? 'top-0' : 'top-4',\n        )}\n      >\n        {Math.round(percent)}%\n      </span>\n    );\n    return (\n      <div className={cn('relative w-full', label === 'top-floating' ? 'pt-9' : 'pb-9', className)}>\n        {bar}\n        {floating}\n      </div>\n    );\n  },\n);\nProgress.displayName = 'Progress';\n\nexport { Progress, progressIndicatorVariants, progressVariants };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}