{
  "name": "circular-progress",
  "type": "registry:ui",
  "dependencies": [
    "radix-ui"
  ],
  "cssVars": {
    "theme": {
      "--animate-spin-around": "spin-around 0.8s linear infinite"
    }
  },
  "css": {
    "@keyframes spin-around": {
      "0%": {
        "transform": "rotate(-90deg)"
      },
      "100%": {
        "transform": "rotate(270deg)"
      }
    }
  },
  "files": [
    {
      "path": "ui/circular-progress.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { Slot as SlotPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst CIRCULAR_PROGRESS_NAME = \"CircularProgress\";\nconst INDICATOR_NAME = \"CircularProgressIndicator\";\nconst TRACK_NAME = \"CircularProgressTrack\";\nconst RANGE_NAME = \"CircularProgressRange\";\nconst VALUE_TEXT_NAME = \"CircularProgressValueText\";\n\nconst DEFAULT_MAX = 100;\n\ntype ProgressState = \"indeterminate\" | \"complete\" | \"loading\";\n\nfunction getProgressState(\n  value: number | undefined | null,\n  maxValue: number,\n): ProgressState {\n  return value == null\n    ? \"indeterminate\"\n    : value === maxValue\n      ? \"complete\"\n      : \"loading\";\n}\n\nfunction getIsValidNumber(value: unknown): value is number {\n  return typeof value === \"number\" && Number.isFinite(value);\n}\n\nfunction getIsValidMaxNumber(max: unknown): max is number {\n  return getIsValidNumber(max) && max > 0;\n}\n\nfunction getIsValidValueNumber(\n  value: unknown,\n  min: number,\n  max: number,\n): value is number {\n  return getIsValidNumber(value) && value <= max && value >= min;\n}\n\nfunction getDefaultValueText(value: number, min: number, max: number): string {\n  const percentage = max === min ? 100 : ((value - min) / (max - min)) * 100;\n  return `${Math.round(percentage)}%`;\n}\n\nfunction getInvalidValueError(\n  propValue: string,\n  componentName: string,\n): string {\n  return `Invalid prop \\`value\\` of value \\`${propValue}\\` supplied to \\`${componentName}\\`. The \\`value\\` prop must be a number between \\`min\\` and \\`max\\` (inclusive), or \\`null\\`/\\`undefined\\` for indeterminate progress. The value will be clamped to the valid range.`;\n}\n\nfunction getInvalidMaxError(propValue: string, componentName: string): string {\n  return `Invalid prop \\`max\\` of value \\`${propValue}\\` supplied to \\`${componentName}\\`. Only numbers greater than 0 are valid. Defaulting to ${DEFAULT_MAX}.`;\n}\n\ninterface CircularProgressContextValue {\n  value: number | null;\n  valueText: string | undefined;\n  max: number;\n  min: number;\n  state: ProgressState;\n  radius: number;\n  thickness: number;\n  size: number;\n  center: number;\n  circumference: number;\n  percentage: number | null;\n  valueTextId?: string;\n}\n\nconst CircularProgressContext =\n  React.createContext<CircularProgressContextValue | null>(null);\n\nfunction useCircularProgressContext(consumerName: string) {\n  const context = React.useContext(CircularProgressContext);\n  if (!context) {\n    throw new Error(\n      `\\`${consumerName}\\` must be used within \\`${CIRCULAR_PROGRESS_NAME}\\``,\n    );\n  }\n  return context;\n}\n\ninterface CircularProgressProps extends React.ComponentProps<\"div\"> {\n  value?: number | null | undefined;\n  getValueText?(value: number, min: number, max: number): string;\n  min?: number;\n  max?: number;\n  size?: number;\n  thickness?: number;\n  label?: string;\n  asChild?: boolean;\n}\n\nfunction CircularProgress(props: CircularProgressProps) {\n  const {\n    value: valueProp = null,\n    getValueText = getDefaultValueText,\n    min: minProp = 0,\n    max: maxProp,\n    size = 48,\n    thickness = 4,\n    label,\n    asChild,\n    className,\n    children,\n    ...rootProps\n  } = props;\n\n  if ((maxProp || maxProp === 0) && !getIsValidMaxNumber(maxProp)) {\n    if (process.env.NODE_ENV !== \"production\") {\n      console.error(getInvalidMaxError(`${maxProp}`, CIRCULAR_PROGRESS_NAME));\n    }\n  }\n\n  const rawMax = getIsValidMaxNumber(maxProp) ? maxProp : DEFAULT_MAX;\n  const min = getIsValidNumber(minProp) ? minProp : 0;\n  const max = rawMax <= min ? min + 1 : rawMax;\n\n  if (process.env.NODE_ENV !== \"production\" && thickness >= size) {\n    console.warn(\n      `CircularProgress: thickness (${thickness}) should be less than size (${size}) for proper rendering.`,\n    );\n  }\n\n  if (valueProp !== null && !getIsValidValueNumber(valueProp, min, max)) {\n    if (process.env.NODE_ENV !== \"production\") {\n      console.error(\n        getInvalidValueError(`${valueProp}`, CIRCULAR_PROGRESS_NAME),\n      );\n    }\n  }\n\n  const value = getIsValidValueNumber(valueProp, min, max)\n    ? valueProp\n    : getIsValidNumber(valueProp) && valueProp > max\n      ? max\n      : getIsValidNumber(valueProp) && valueProp < min\n        ? min\n        : null;\n\n  const valueText = getIsValidNumber(value)\n    ? getValueText(value, min, max)\n    : undefined;\n  const state = getProgressState(value, max);\n  const radius = Math.max(0, (size - thickness) / 2);\n  const center = size / 2;\n  const circumference = 2 * Math.PI * radius;\n\n  const percentage = getIsValidNumber(value)\n    ? max === min\n      ? 1\n      : (value - min) / (max - min)\n    : null;\n\n  const labelId = React.useId();\n  const valueTextId = React.useId();\n\n  const contextValue = React.useMemo<CircularProgressContextValue>(\n    () => ({\n      value,\n      valueText,\n      max,\n      min,\n      state,\n      radius,\n      thickness,\n      size,\n      center,\n      circumference,\n      percentage,\n      valueTextId,\n    }),\n    [\n      value,\n      valueText,\n      max,\n      min,\n      state,\n      radius,\n      thickness,\n      size,\n      center,\n      circumference,\n      percentage,\n      valueTextId,\n    ],\n  );\n\n  const RootPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <CircularProgressContext.Provider value={contextValue}>\n      <RootPrimitive\n        role=\"progressbar\"\n        aria-describedby={valueText ? valueTextId : undefined}\n        aria-labelledby={label ? labelId : undefined}\n        aria-valuemax={max}\n        aria-valuemin={min}\n        aria-valuenow={getIsValidNumber(value) ? value : undefined}\n        aria-valuetext={valueText}\n        data-state={state}\n        data-value={value ?? undefined}\n        data-max={max}\n        data-min={min}\n        data-percentage={percentage}\n        {...rootProps}\n        className={cn(\n          \"relative inline-flex w-fit items-center justify-center\",\n          className,\n        )}\n      >\n        {children}\n        {label && <div id={labelId}>{label}</div>}\n      </RootPrimitive>\n    </CircularProgressContext.Provider>\n  );\n}\n\nfunction CircularProgressIndicator(props: React.ComponentProps<\"svg\">) {\n  const { className, ...indicatorProps } = props;\n\n  const context = useCircularProgressContext(INDICATOR_NAME);\n\n  return (\n    <svg\n      aria-hidden=\"true\"\n      focusable=\"false\"\n      viewBox={`0 0 ${context.size} ${context.size}`}\n      data-state={context.state}\n      data-value={context.value ?? undefined}\n      data-max={context.max}\n      data-min={context.min}\n      data-percentage={context.percentage}\n      width={context.size}\n      height={context.size}\n      {...indicatorProps}\n      className={cn(\"-rotate-90 transform\", className)}\n    />\n  );\n}\n\nCircularProgressIndicator.displayName = INDICATOR_NAME;\n\nfunction CircularProgressTrack(props: React.ComponentProps<\"circle\">) {\n  const { className, ...trackProps } = props;\n\n  const context = useCircularProgressContext(TRACK_NAME);\n\n  return (\n    <circle\n      data-state={context.state}\n      cx={context.center}\n      cy={context.center}\n      r={context.radius}\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={context.thickness}\n      strokeLinecap=\"round\"\n      vectorEffect=\"non-scaling-stroke\"\n      {...trackProps}\n      className={cn(\"text-muted-foreground/20\", className)}\n    />\n  );\n}\n\nfunction CircularProgressRange(props: React.ComponentProps<\"circle\">) {\n  const { className, ...rangeProps } = props;\n\n  const context = useCircularProgressContext(RANGE_NAME);\n\n  const strokeDasharray = context.circumference;\n  const strokeDashoffset =\n    context.state === \"indeterminate\"\n      ? context.circumference * 0.75\n      : context.percentage !== null\n        ? context.circumference - context.percentage * context.circumference\n        : context.circumference;\n\n  return (\n    <circle\n      data-state={context.state}\n      data-value={context.value ?? undefined}\n      data-max={context.max}\n      data-min={context.min}\n      cx={context.center}\n      cy={context.center}\n      r={context.radius}\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={context.thickness}\n      strokeLinecap=\"round\"\n      strokeDasharray={strokeDasharray}\n      strokeDashoffset={strokeDashoffset}\n      vectorEffect=\"non-scaling-stroke\"\n      {...rangeProps}\n      className={cn(\n        \"origin-center text-primary transition-all duration-300 ease-in-out\",\n        context.state === \"indeterminate\" &&\n          \"motion-safe:[animation:var(--animate-spin-around)] motion-reduce:animate-none\",\n        className,\n      )}\n    />\n  );\n}\n\ninterface CircularProgressValueTextProps extends React.ComponentProps<\"span\"> {\n  asChild?: boolean;\n}\n\nfunction CircularProgressValueText(props: CircularProgressValueTextProps) {\n  const { asChild, className, children, ...valueTextProps } = props;\n\n  const context = useCircularProgressContext(VALUE_TEXT_NAME);\n\n  const ValueTextPrimitive = asChild ? SlotPrimitive.Slot : \"span\";\n\n  return (\n    <ValueTextPrimitive\n      id={context.valueTextId}\n      data-state={context.state}\n      {...valueTextProps}\n      className={cn(\n        \"absolute inset-0 flex items-center justify-center text-sm font-medium\",\n        className,\n      )}\n    >\n      {children ?? context.valueText}\n    </ValueTextPrimitive>\n  );\n}\n\nfunction CircularProgressCombined(props: CircularProgressProps) {\n  return (\n    <CircularProgress {...props}>\n      <CircularProgressIndicator>\n        <CircularProgressTrack />\n        <CircularProgressRange />\n      </CircularProgressIndicator>\n      <CircularProgressValueText />\n    </CircularProgress>\n  );\n}\n\nexport {\n  CircularProgress,\n  CircularProgressCombined,\n  CircularProgressIndicator,\n  type CircularProgressProps,\n  CircularProgressRange,\n  CircularProgressTrack,\n  CircularProgressValueText,\n};\n",
      "target": ""
    }
  ]
}