{ "name": "radial-dots-spinner", "type": "registry:ui", "registryDependencies": [], "dependencies": [], "devDependencies": [], "tailwind": {}, "cssVars": { "light": {}, "dark": {} }, "files": [ { "path": "radial-dots-spinner.tsx", "content": "'use client';\n\nimport * as React from 'react';\nimport { cn } from '@/lib/utils';\n\nexport type RadialDotsSpinnerProps = {\n /** Overall square size (px) */\n size?: number;\n /** Number of radial spokes */\n spokes?: number;\n /** Dots per spoke (excluding center rings) */\n dotsPerSpoke?: number;\n /** Inner radius where spokes start (px) */\n innerRadius?: number;\n /** Outer radius (px). Default: size/2 - 6 */\n outerRadius?: number;\n /** Dot radius across spokes. Either fixed number or { inner, outer } to taper. */\n dotRadius?: number | { inner: number; outer: number };\n /** How many dotted rings around the center (0–3 works well) */\n centerRings?: number;\n /** Radius of the first ring (px) */\n centerRingRadius?: number;\n /** Gap between center rings (px) */\n centerRingGap?: number;\n /** Dots on each center ring */\n centerRingDots?: number;\n /** Seconds per full rotation (set 0 or falsey to disable spin) */\n speed?: number;\n /** Extra classes (use to set color: text-white, text-zinc-900, etc.) */\n className?: string;\n /** Extra classes applied to each dot (e.g. drop-shadow) */\n dotClassName?: string;\n /** Accessible label */\n label?: string;\n};\n\nfunction lerp(a: number, b: number, t: number) {\n return a + (b - a) * t;\n}\n\nexport function RadialDotsSpinner({\n size = 160,\n spokes = 12,\n dotsPerSpoke = 10,\n innerRadius = 14,\n outerRadius,\n dotRadius = { inner: 2, outer: 3.5 },\n centerRings = 2,\n centerRingRadius = 10,\n centerRingGap = 6,\n centerRingDots = 16,\n speed = 1.6,\n className,\n dotClassName,\n label = 'Loading…',\n}: RadialDotsSpinnerProps) {\n const w = size;\n const h = size;\n const cx = w / 2;\n const cy = h / 2;\n const R = outerRadius ?? Math.floor(size / 2) - 6;\n const step = dotsPerSpoke > 1 ? (R - innerRadius) / (dotsPerSpoke - 1) : 0;\n\n const getDotR = (idx: number) => {\n if (typeof dotRadius === 'number') return dotRadius;\n const t = dotsPerSpoke <= 1 ? 0 : idx / (dotsPerSpoke - 1);\n return lerp(dotRadius.inner, dotRadius.outer, t);\n };\n\n // build center rings (array of arrays of points)\n const rings = Array.from({ length: Math.max(0, centerRings) }, (_, r) => {\n const radius = centerRingRadius + r * centerRingGap;\n return Array.from({ length: centerRingDots }, (_, i) => {\n const a = (i / centerRingDots) * Math.PI * 2;\n return {\n x: cx + Math.cos(a) * radius,\n y: cy + Math.sin(a) * radius,\n };\n });\n });\n\n // styles for rotation that work inside SVG everywhere\n const spinStyle: React.CSSProperties | undefined =\n speed && speed > 0\n ? {\n transformOrigin: '50% 50%',\n animation: `rds-rotate ${speed}s linear infinite`,\n }\n : undefined;\n\n return (\n \n {/* local keyframes so we don't depend on Tailwind's animate-spin */}\n \n\n {/* spin the whole group */}\n \n {/* center dotted rings */}\n {rings.map((pts, ringIdx) =>\n pts.map((p, i) => (\n \n ))\n )}\n\n {/* spokes */}\n {Array.from({ length: spokes }, (_, s) => {\n const angle = (s / spokes) * Math.PI * 2;\n const dx = Math.cos(angle);\n const dy = Math.sin(angle);\n\n return Array.from({ length: dotsPerSpoke }, (_, j) => {\n const r = innerRadius + j * step;\n const x = cx + dx * r;\n const y = cy + dy * r;\n const rr = getDotR(j);\n return (\n \n );\n });\n })}\n \n \n );\n}\n", "type": "registry:ui" } ] }