{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "circular-progress-glass", "type": "registry:ui", "title": "Circular Progress Glass", "description": "SVG-based circular progress indicator with gradient colors and glow effects.\n * Supports both determinate (0-100%) and indeterminate (spinner) variants.\n *\n * ## Features", "dependencies": [ "class-variance-authority", "react", "shadcn-glass-ui" ], "registryDependencies": [ "cn" ], "files": [ { "path": "components/glass/ui/circular-progress-glass.tsx", "type": "registry:component", "content": "/**\n * CircularProgressGlass Component\n *\n * SVG-based circular progress indicator with gradient colors and glow effects.\n * Supports both determinate (0-100%) and indeterminate (spinner) variants.\n *\n * ## Features\n * - Determinate (0-100%) and indeterminate (spinner) variants\n * - 4 sizes: sm (64px), md (96px), lg (128px), xl (160px)\n * - 6 gradient colors: violet, blue, cyan, amber, emerald, rose\n * - Configurable stroke thickness and track width\n * - Optional center label (percentage or custom text)\n * - SVG glow filters with 3 intensity levels (low, medium, high)\n * - Smooth transitions with configurable animation duration\n * - Theme-aware styling via CSS variables\n * - Screen reader accessible with ARIA progressbar\n *\n * ## CSS Variables\n * Customize colors via theme CSS variables:\n * - `--progress-bg`: Progress bar background color\n * - `--progress-glow`: Default glow color\n * - `--progress-glow-violet`: Glow for violet gradient\n * - `--progress-glow-blue`: Glow for blue gradient\n * - `--progress-glow-cyan`: Glow for cyan gradient\n * - `--progress-glow-amber`: Glow for amber gradient\n * - `--progress-glow-emerald`: Glow for emerald gradient\n * - `--progress-glow-rose`: Glow for rose gradient\n *\n * @example Determinate progress with default settings\n * ```tsx\n * import { CircularProgressGlass } from 'shadcn-glass-ui'\n *\n * function UploadProgress() {\n * const [progress, setProgress] = React.useState(0)\n *\n * return (\n * \n * )\n * }\n * ```\n *\n * @example Indeterminate spinner\n * ```tsx\n * \n * ```\n *\n * @example With custom label and color\n * ```tsx\n * \n * ```\n *\n * @example Large size with high glow intensity\n * ```tsx\n * \n * ```\n *\n * @accessibility\n * - Uses ARIA `role=\"progressbar\"` for screen reader support\n * - Provides `aria-valuenow`, `aria-valuemin`, `aria-valuemax` for determinate variant\n * - Includes `aria-label` and `aria-valuetext` for context\n * - Visual indicator uses `aria-hidden=\"true\"` (screen readers use hidden progressbar)\n * - Custom label text overrides default percentage announcement\n *\n * @since v1.0.0\n */\n\nimport { forwardRef, useMemo, useId } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// VARIANTS\n// ========================================\n\nconst circularProgressVariants = cva('relative inline-flex items-center justify-center p-4', {\n variants: {\n size: {\n sm: 'w-20 h-20',\n md: 'w-28 h-28',\n lg: 'w-36 h-36',\n xl: 'w-44 h-44',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n});\n\n// ========================================\n// TYPES\n// ========================================\n\nexport type CircularProgressGradient = 'violet' | 'blue' | 'cyan' | 'amber' | 'emerald' | 'rose';\n\n/**\n * Props for CircularProgressGlass component.\n *\n * Extends standard div attributes (excluding `children`) with progress-specific props.\n *\n * @example\n * ```tsx\n * const props: CircularProgressGlassProps = {\n * value: 75,\n * variant: 'determinate',\n * size: 'lg',\n * color: 'violet',\n * showLabel: true,\n * glowIntensity: 'medium',\n * };\n * ```\n */\nexport interface CircularProgressGlassProps\n extends\n Omit, 'children'>,\n VariantProps {\n /**\n * Progress value (0-100) for determinate variant.\n * Values outside this range are clamped automatically.\n *\n * @default 0\n * @example\n * ```tsx\n * \n * ```\n */\n readonly value?: number;\n\n /**\n * Variant type.\n * - `determinate`: Shows specific progress from 0-100%\n * - `indeterminate`: Spinning animation for unknown duration\n *\n * @default 'determinate'\n * @example\n * ```tsx\n * \n * ```\n */\n readonly variant?: 'determinate' | 'indeterminate';\n\n /**\n * Stroke width in pixels for the progress circle.\n *\n * @default 8\n * @example\n * ```tsx\n * \n * ```\n */\n readonly thickness?: number;\n\n /**\n * Background track width in pixels.\n *\n * @default 8\n * @example\n * ```tsx\n * \n * ```\n */\n readonly trackWidth?: number;\n\n /**\n * Progress color gradient.\n *\n * @default 'violet'\n * @example\n * ```tsx\n * \n * ```\n */\n readonly color?: CircularProgressGradient;\n\n /**\n * Track color (background circle).\n *\n * @default 'oklch(100% 0 0 / 0.1)'\n * @example\n * ```tsx\n * \n * ```\n */\n readonly trackColor?: string;\n\n /**\n * Show percentage label in center.\n *\n * @default true\n * @example\n * ```tsx\n * \n * ```\n */\n readonly showLabel?: boolean;\n\n /**\n * Custom label text (overrides percentage).\n *\n * @default undefined (shows percentage if showLabel is true)\n * @example\n * ```tsx\n * \n * ```\n */\n readonly label?: string;\n\n /**\n * Custom color for the center label text.\n *\n * @default undefined (uses gradient 'to' color)\n * @example\n * ```tsx\n * \n * ```\n */\n readonly labelColor?: string;\n\n /**\n * Show glow effect using SVG filters.\n *\n * @default true\n * @example\n * ```tsx\n * \n * ```\n */\n readonly showGlow?: boolean;\n\n /**\n * Glow intensity level.\n *\n * @default 'medium'\n * @example\n * ```tsx\n * \n * ```\n */\n readonly glowIntensity?: 'low' | 'medium' | 'high';\n\n /**\n * Stroke linecap style.\n *\n * @default 'round'\n * @example\n * ```tsx\n * \n * ```\n */\n readonly strokeLinecap?: 'round' | 'butt' | 'square';\n\n /**\n * Animation duration in seconds for value transitions.\n *\n * @default 1\n * @example\n * ```tsx\n * \n * ```\n */\n readonly animationDuration?: number;\n}\n\n// ========================================\n// HELPERS\n// ========================================\n\nconst getGradientColors = (gradient: CircularProgressGradient) => {\n const gradients: Record =\n {\n violet: { from: '#8b5cf6', to: '#a855f7', glowVar: '--progress-glow-violet' },\n blue: { from: '#3b82f6', to: '#60a5fa', glowVar: '--progress-glow-blue' },\n cyan: { from: '#06b6d4', to: '#22d3ee', glowVar: '--progress-glow-cyan' },\n amber: { from: '#f59e0b', to: '#fbbf24', glowVar: '--progress-glow-amber' },\n emerald: { from: '#10b981', to: '#34d399', glowVar: '--progress-glow-emerald' },\n rose: { from: '#f43f5e', to: '#fb7185', glowVar: '--progress-glow-rose' },\n };\n return gradients[gradient];\n};\n\nconst getGlowStdDeviation = (intensity: 'low' | 'medium' | 'high'): number => {\n const intensities = { low: 2, medium: 4, high: 6 };\n return intensities[intensity];\n};\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const CircularProgressGlass = forwardRef(\n (\n {\n className,\n size = 'md',\n value = 0,\n variant = 'determinate',\n thickness = 8,\n trackWidth = 8,\n color = 'violet',\n trackColor = 'oklch(100% 0 0 / 0.1)',\n showLabel = true,\n label,\n labelColor,\n showGlow = true,\n glowIntensity = 'medium',\n strokeLinecap = 'round',\n animationDuration = 1,\n ...props\n },\n ref\n ) => {\n const clampedValue = Math.min(100, Math.max(0, value));\n const gradientColors = getGradientColors(color);\n\n // SVG dimensions\n const sizeMap = { sm: 64, md: 96, lg: 128, xl: 160 };\n const svgSize = sizeMap[size || 'md'];\n const radius = (svgSize - Math.max(thickness, trackWidth)) / 2;\n const circumference = 2 * Math.PI * radius;\n const center = svgSize / 2;\n\n // Calculate stroke dash offset for determinate progress\n const dashOffset = useMemo(() => {\n if (variant === 'indeterminate') return circumference * 0.75;\n return circumference * ((100 - clampedValue) / 100);\n }, [variant, clampedValue, circumference]);\n\n // Generate unique IDs for SVG elements (using useId for stable IDs)\n const uniqueId = useId();\n const gradientId = `circular-gradient-${uniqueId}`;\n const glowId = `circular-glow-${uniqueId}`;\n\n return (\n \n \n \n {/* Gradient definition */}\n \n \n \n \n\n {/* Glow filter */}\n {showGlow && (\n \n \n \n \n \n \n \n )}\n \n\n {/* Background track */}\n \n\n {/* Progress circle */}\n \n \n\n {/* Center label */}\n {showLabel && (\n
\n \n {label || (variant === 'determinate' ? `${clampedValue}%` : '')}\n \n
\n )}\n\n {/* Accessibility */}\n \n {label || (variant === 'determinate' ? `${clampedValue}%` : 'Loading...')}\n \n \n );\n }\n);\n\nCircularProgressGlass.displayName = 'CircularProgressGlass';\n" } ], "categories": [ "ui" ], "cssVars": { "light": { "--glass-bg": "rgba(255, 255, 255, 0.1)", "--glass-border": "rgba(255, 255, 255, 0.2)", "--blur-sm": "8px", "--blur-md": "16px", "--blur-lg": "24px" }, "dark": { "--glass-bg": "rgba(255, 255, 255, 0.05)", "--glass-border": "rgba(255, 255, 255, 0.1)", "--blur-sm": "8px", "--blur-md": "16px", "--blur-lg": "24px" } } }