{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "progressive-flux-loader", "type": "registry:ui", "title": "Progressive Flux Loader", "description": "A glowing progress bar with phase labels that fly in along the Z-axis and reveal letter by letter.", "dependencies": [ "motion" ], "files": [ { "path": "registry/ruixenui/progressive-flux-loader.tsx", "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n AnimatePresence,\n motion,\n useReducedMotion,\n type Transition,\n} from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/* ── types ───────────────────────────────────────────────────── */\n\nexport interface ProgressiveFluxPhase {\n /** Progress threshold (`0`–`100`) at or past which `label` is shown. */\n at: number;\n /** Text revealed once this threshold is reached. */\n label: string;\n}\n\nexport interface ProgressiveFluxLoaderProps {\n /**\n * Controlled progress, `0`–`100`. When set, the loader follows this value and\n * the phase label switches at the configured thresholds. Omit it to let the\n * loader run its own looping sweep.\n */\n value?: number;\n /** Phase thresholds and their labels. Each `at` is a `0`–`100` mark. */\n phases?: ProgressiveFluxPhase[];\n /** Seconds for one full sweep when uncontrolled. Default `12`. */\n duration?: number;\n /** Restart from `0` after reaching `100` (uncontrolled only). Default `true`. */\n loop?: boolean;\n /** Show the animated phase label above the bar. Default `true`. */\n showLabel?: boolean;\n /**\n * CSS background for the bar fill. Defaults to the signature vivid blue → cyan\n * flux gradient. Pass any CSS background to replace it, or recolor the default\n * via the `--flux-from` / `--flux-to` CSS variables (e.g. set them to\n * `hsl(var(--primary))` to follow the theme).\n */\n gradient?: string;\n /** Fires once when progress reaches `100` — in both controlled and uncontrolled modes. */\n onComplete?: () => void;\n /** Classes for the root wrapper. */\n className?: string;\n /** Classes for the bar track. */\n barClassName?: string;\n /** Classes for the phase label. */\n textClassName?: string;\n}\n\n/* ── constants ───────────────────────────────────────────────── */\n\nconst DEFAULT_PHASES: ProgressiveFluxPhase[] = [\n { at: 0, label: \"starting up\" },\n { at: 25, label: \"loading assets\" },\n { at: 55, label: \"preparing magic\" },\n { at: 80, label: \"almost there\" },\n { at: 100, label: \"all done\" },\n];\n\n// Signature \"flux\" palette — a vivid blue → cyan → blue fill. The two end\n// colors are read from CSS variables with built-in defaults, so the bar can be\n// recolored per instance without touching the component: set `--flux-from` /\n// `--flux-to` (e.g. to `hsl(var(--primary))` to follow the theme), or override\n// the whole fill with the `gradient` prop. The surrounding track and label stay\n// on shadcn theme tokens, so the loader still adapts to light and dark. These\n// are component-level custom properties, so the v3 build leaves them untouched\n// and the fill renders identically on Tailwind v3 and v4.\nconst FLUX_FROM = \"var(--flux-from, #1d6ffb)\";\nconst FLUX_TO = \"var(--flux-to, #74e1ff)\";\nconst FLUX_MID = `color-mix(in oklab, ${FLUX_FROM}, ${FLUX_TO})`;\n\nconst DEFAULT_GRADIENT = `linear-gradient(90deg, ${FLUX_FROM} 0%, ${FLUX_MID} 35%, ${FLUX_TO} 55%, ${FLUX_MID} 78%, ${FLUX_FROM} 100%)`;\n\n// Colored glow drawn from the same flux palette, a white top-edge highlight,\n// and a deep-blue inset for depth.\nconst BAR_SHADOW = `0 0 18px color-mix(in oklab, ${FLUX_FROM} 55%, transparent), 0 0 32px color-mix(in oklab, ${FLUX_TO} 40%, transparent), inset 0 1.5px 0 rgba(255, 255, 255, 0.5), inset 0 -2px 3px rgba(0, 40, 120, 0.35)`;\n\n// White sweep over the colored fill (blended with `screen`), so the highlight\n// reads as a bright glide regardless of theme.\nconst SHEEN_GRADIENT =\n \"linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.55) 50%, transparent 100%)\";\n\nconst Z_TRANSITION: Transition = { duration: 0.9, ease: [0.22, 1, 0.36, 1] };\nconst LETTER_TRANSITION: Transition = {\n duration: 0.45,\n ease: [0.22, 1, 0.36, 1],\n};\n\n/* ── helpers ─────────────────────────────────────────────────── */\n\n/** Latest label whose threshold has been crossed. Expects pre-sorted phases. */\nfunction pickLabel(value: number, sortedPhases: ProgressiveFluxPhase[]) {\n let active = sortedPhases[0]?.label ?? \"\";\n for (const phase of sortedPhases) {\n if (value >= phase.at) active = phase.label;\n }\n return active;\n}\n\n/* ── label ───────────────────────────────────────────────────── */\n\ninterface FluxLabelProps {\n label: string;\n /** Render plain, static text instead of the 3D fly-in (reduced motion). */\n reduced: boolean;\n className?: string;\n}\n\n// The label is decorative and `aria-hidden`; the progressbar carries the spoken\n// progress via `aria-valuetext`. Under reduced motion it is plain static text.\nfunction FluxLabel({ label, reduced, className }: FluxLabelProps) {\n const base = cn(\n \"absolute inset-0 flex items-center justify-center text-center text-3xl font-semibold tracking-tight text-muted-foreground sm:text-4xl\",\n className,\n );\n\n if (reduced) {\n return (\n