{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "progress-circle",
  "title": "Progress Circle",
  "description": "Circular and half-circular progress indicator with an optional caption.",
  "registryDependencies": [
    "https://indiacn.in/r/theme.json",
    "https://indiacn.in/r/typography.json"
  ],
  "files": [
    {
      "path": "components/ui/progress-circle.tsx",
      "content": "import { ComponentProps } from 'react';\n\nimport { Body2, Label1 } from '@/components/ui/typography';\nimport { cn } from '@/lib/utils';\n\n/*\n * UX4G progress circle: 64/160/200/240/280px, stroke a flat 10% of the\n * diameter, neutral-50 track and a primary arc with a round cap. The circle\n * starts at twelve o'clock; the half circle spans the top 180 degrees from\n * nine o'clock.\n */\nconst DIAMETERS = { xxs: 64, xs: 160, sm: 200, md: 240, lg: 280 } as const;\n\n/** Cap heights measured off the Figma symbols resolve to this ladder. */\nconst VALUE_CLASSES = {\n  xxs: 'text-sm',\n  xs: 'text-2xl',\n  sm: 'text-3xl',\n  md: 'text-4xl',\n  lg: 'text-5xl',\n} as const;\n\nconst LABEL_CLASSES = {\n  xxs: 'text-xs',\n  xs: 'text-sm',\n  sm: 'text-sm',\n  md: 'text-base',\n  lg: 'text-base',\n} as const;\n\ntype TProgressCircleSize = keyof typeof DIAMETERS;\ntype TProgressCircleShape = 'circle' | 'half';\n\n/*\n * UX4G's ladder starts at 64px and runs to 280px — dashboard scale. A number is\n * accepted too, for the inline rings the kit has no size for. Font tracks the\n * measured ratio for those, since the named sizes are not on one curve.\n */\nconst NUMERIC_FONT_RATIO = 0.16;\n\ninterface IProgressCircleProps extends Omit<ComponentProps<'div'>, 'children'> {\n  value?: number;\n  max?: number;\n  size?: TProgressCircleSize | number;\n  shape?: TProgressCircleShape;\n  /** Caption shown with the value. Below the ring at `xxs`, inside it otherwise. */\n  label?: string;\n  showValue?: boolean;\n}\n\n/** Circular or half-circular progress indicator. */\nfunction ProgressCircle({\n  className,\n  value = 0,\n  max = 100,\n  size = 'xxs',\n  shape = 'circle',\n  label,\n  showValue = true,\n  ...props\n}: IProgressCircleProps) {\n  const custom = typeof size === 'number';\n  const diameter = custom ? size : DIAMETERS[size];\n  const stroke = Math.max(2, Math.round(diameter * 0.1));\n  const radius = (diameter - stroke) / 2;\n  const centre = diameter / 2;\n  const isHalf = shape === 'half';\n\n  const percent = Math.min(Math.max((value / max) * 100, 0), 100);\n  const length = (isHalf ? Math.PI : 2 * Math.PI) * radius;\n  const height = isHalf ? centre + stroke / 2 : diameter;\n\n  // Half sweeps left-to-right over the top; full starts at twelve o'clock.\n  const track = isHalf\n    ? `M ${stroke / 2} ${centre} A ${radius} ${radius} 0 0 1 ${diameter - stroke / 2} ${centre}`\n    : undefined;\n\n  const shared = {\n    fill: 'none',\n    strokeWidth: stroke,\n    strokeLinecap: 'round' as const,\n  };\n\n  // Nothing legible fits inside a small ring beside the number.\n  const captionInside = Boolean(label) && diameter >= 120;\n\n  return (\n    <div className={cn('inline-flex flex-col items-center', className)} {...props}>\n      <div className='relative' style={{ width: diameter, height }}>\n        <svg width={diameter} height={height} viewBox={`0 0 ${diameter} ${height}`} aria-hidden>\n          {isHalf ? (\n            <>\n              <path d={track} className='stroke-neutral-50' {...shared} />\n              <path\n                d={track}\n                className='stroke-primary transition-[stroke-dashoffset] duration-600 ease-in-out'\n                strokeDasharray={length}\n                strokeDashoffset={length * (1 - percent / 100)}\n                {...shared}\n              />\n            </>\n          ) : (\n            <>\n              <circle cx={centre} cy={centre} r={radius} className='stroke-neutral-50' {...shared} />\n              <circle\n                cx={centre}\n                cy={centre}\n                r={radius}\n                className='stroke-primary transition-[stroke-dashoffset] duration-600 ease-in-out'\n                strokeDasharray={length}\n                strokeDashoffset={length * (1 - percent / 100)}\n                transform={`rotate(-90 ${centre} ${centre})`}\n                {...shared}\n              />\n            </>\n          )}\n        </svg>\n\n        <div\n          role='progressbar'\n          aria-valuenow={Math.round(percent)}\n          aria-valuemin={0}\n          aria-valuemax={100}\n          aria-label={label ?? 'Progress'}\n          className={cn(\n            'absolute inset-0 flex flex-col justify-center text-center',\n            isHalf ? 'justify-end pb-[6%]' : 'justify-center',\n          )}\n        >\n          {captionInside && (\n            <Body2\n              className={cn('text-neutral-600', !custom && LABEL_CLASSES[size])}\n              style={custom ? { fontSize: Math.round(diameter * NUMERIC_FONT_RATIO * 0.6) } : undefined}\n            >\n              {label}\n            </Body2>\n          )}\n          {showValue && (\n            <Label1\n              className={cn('text-neutral font-semibold', !custom && VALUE_CLASSES[size])}\n              style={custom ? { fontSize: Math.round(diameter * NUMERIC_FONT_RATIO) } : undefined}\n            >\n              {Math.round(percent)}%\n            </Label1>\n          )}\n        </div>\n      </div>\n\n      {label && !captionInside && (\n        <Body2 className='mt-1 text-xs text-neutral-600'>{label}</Body2>\n      )}\n    </div>\n  );\n}\n\nexport type { TProgressCircleSize, TProgressCircleShape };\n\nexport { ProgressCircle };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}
