{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"progress","title":"Progress component","type":"registry:ui","description":"Progress component","dependencies":["@radix-ui/react-progress","class-variance-authority","lucide-react"],"registryDependencies":["utils","https://fysk.dev/r/fysk-provider.json","https://fysk.dev/r/fysk-hooks.json"],"files":[{"path":"fysk/progress.tsx","type":"registry:ui","content":"\"use client\"\r\n\r\nimport * as React from \"react\";\r\nimport * as ProgressPrimitive from \"@radix-ui/react-progress\";\r\nimport { cva, type VariantProps } from \"class-variance-authority\";\r\nimport { CheckCircle2, XCircle, Loader2 } from \"lucide-react\";\r\n\r\nimport { cn } from \"@/lib/utils\";\r\nimport { useFyskConfig } from \"@/components/fysk-provider\";\r\nimport { useFyskAnimation } from \"@/components/hooks/useFyskAnimation\";\r\n\r\nconst progressVariants = cva(\r\n    \"relative w-full overflow-hidden bg-secondary transition-all duration-500\",\r\n    {\r\n        variants: {\r\n            size: {\r\n                xs: \"h-1\",\r\n                sm: \"h-2\",\r\n                md: \"h-4\",\r\n                lg: \"h-6\",\r\n                xl: \"h-8\",\r\n            },\r\n            shape: {\r\n                rounded: \"rounded-full\",\r\n                square: \"rounded-none\",\r\n                soft: \"rounded-md\",\r\n            },\r\n            variant: {\r\n                default: \"\",\r\n                gradient: \"\",\r\n                striped: \"\",\r\n                glow: \"\",\r\n                glass: \"backdrop-blur-sm bg-background/20 border border-border/50\",\r\n                neon: \"shadow-lg\",\r\n            },\r\n            state: {\r\n                idle: \"bg-secondary\",\r\n                loading: \"bg-secondary\",\r\n                success: \"bg-green-500/20\",\r\n                error: \"bg-destructive/20\",\r\n            },\r\n        },\r\n        defaultVariants: {\r\n            size: \"md\",\r\n            shape: \"rounded\",\r\n            variant: \"default\",\r\n            state: \"idle\",\r\n        },\r\n    }\r\n)\r\n\r\nconst indicatorVariants = cva(\r\n    \"h-full w-full flex-1 transition-all duration-1000\",\r\n    {\r\n        variants: {\r\n            variant: {\r\n                default: \"bg-primary\",\r\n                gradient: \"bg-gradient-to-r from-primary via-primary/80 to-primary\",\r\n                striped: \"bg-primary bg-striped-indicator animate-stripe-move\",\r\n                glow: \"bg-primary shadow-[0_0_10px_rgba(var(--primary),0.5)]\",\r\n                glass: \"bg-primary/60 backdrop-blur-sm\",\r\n                neon: \"bg-gradient-to-r from-primary to-secondary shadow-[0_0_15px_rgba(var(--primary),0.8)]\",\r\n            },\r\n            state: {\r\n                idle: \"\",\r\n                loading: \"\",\r\n                success: \"bg-green-500\",\r\n                error: \"bg-destructive\",\r\n            },\r\n        },\r\n        defaultVariants: {\r\n            variant: \"default\",\r\n            state: \"idle\",\r\n        },\r\n    }\r\n)\r\n\r\nexport interface ProgressSegment {\r\n    value: number\r\n    color?: string\r\n    label?: string\r\n    className?: string\r\n}\r\n\r\nexport interface ProgressMilestone {\r\n    value: number\r\n    label?: string\r\n    color?: string\r\n}\r\n\r\nexport interface ProgressProps\r\n    extends Omit<React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>, 'value'>,\r\n    VariantProps<typeof progressVariants> {\r\n    /**\r\n     * Current progress value (0-100)\r\n     */\r\n    value?: number\r\n    /**\r\n     * Buffer/cached value for dual-layer progress (0-100)\r\n     */\r\n    bufferValue?: number\r\n    /**\r\n     * Multiple segments for stacked progress\r\n     */\r\n    segments?: ProgressSegment[]\r\n    /**\r\n     * Show percentage/value text\r\n     */\r\n    showValue?: boolean\r\n    /**\r\n     * Custom label text\r\n     */\r\n    label?: string\r\n    /**\r\n     * Position of the label\r\n     * - \"top\": Label displayed above the progress bar\r\n     * - \"bottom\": Label displayed below the progress bar\r\n     * - \"inside\": Label displayed inside the progress indicator (only visible when value > 15%)\r\n     */\r\n    labelPosition?: \"top\" | \"bottom\" | \"inside\"\r\n    /**\r\n     * Custom value formatter\r\n     */\r\n    formatValue?: (value: number) => string\r\n    /**\r\n     * Striped indicator size : stripe size in pixels\r\n     * Remember to also set variant to \"striped\" for this to take effect\r\n     * Default: 20\r\n     * Note: Stripe animation distance is automatically derived from stripedIndicatorSize. Don't forgot to check the @keyframes strip-move in your global.css file.\r\n     */\r\n    stripedIndicatorSize?: number\r\n    /**\r\n     * Show indeterminate loading animation (unknown progress)\r\n     * When true, displays an animated bar that moves back and forth\r\n     */\r\n    indeterminate?: boolean\r\n    /**\r\n     * Icon to display next to the value\r\n     * - \"auto\": Automatically shows appropriate icon based on state (success, error, loading)\r\n     * - ReactNode: Custom icon to display\r\n     */\r\n    icon?: React.ReactNode | \"auto\"\r\n    /**\r\n     * Milestones to mark on the progress bar\r\n     */\r\n    milestones?: ProgressMilestone[]\r\n    /**\r\n     * Show milestone markers\r\n     */\r\n    showMilestones?: boolean\r\n    /**\r\n     * Enable interactive mode (hover effects, clickable)\r\n     */\r\n    interactive?: boolean\r\n    /**\r\n     * Show tooltip on hover\r\n     */\r\n    showTooltip?: boolean\r\n    /**\r\n     * Custom className for the indicator\r\n     */\r\n    indicatorClassName?: string\r\n    /**\r\n     * Callback when progress bar is clicked\r\n     */\r\n    onProgressClick?: (percentage: number) => void\r\n    /**\r\n     * Custom colors for the gradient variant\r\n     */\r\n    colors?: string[]\r\n    /**\r\n     * Direction of the gradient\r\n     * Default: \"to right\"\r\n     */\r\n    gradientDirection?:\r\n    | \"to top\"\r\n    | \"to right\"\r\n    | \"to bottom\"\r\n    | \"to left\"\r\n    | \"to top right\"\r\n    | \"to bottom right\"\r\n    | \"to bottom left\"\r\n    | \"to top left\"\r\n    | (string & {})\r\n}\r\n\r\n// A tiny helper to generate striped background styles\r\nconst createStripedBg = (colorVar: string, size = 20, opacity?: number) => ({\r\n    opacity,\r\n    backgroundImage: `linear-gradient(\r\n    45deg,\r\n    var(${colorVar}) 25%,\r\n    transparent 25%,\r\n    transparent 50%,\r\n    var(${colorVar}) 50%,\r\n    var(${colorVar}) 75%,\r\n    transparent 75%,\r\n    transparent\r\n  )`,\r\n    backgroundSize: `${size}px ${size}px`,\r\n    backgroundRepeat: \"repeat\",\r\n})\r\n\r\n\r\nconst Progress = React.forwardRef<\r\n    React.ComponentRef<typeof ProgressPrimitive.Root>,\r\n    ProgressProps\r\n>(\r\n    (\r\n        {\r\n            value = 0,\r\n            variant,\r\n            size,\r\n            shape,\r\n            state,\r\n            bufferValue,\r\n            label,\r\n            labelPosition = \"bottom\",\r\n            formatValue,\r\n            showValue,\r\n            icon,\r\n            indeterminate,\r\n            segments,\r\n            milestones,\r\n            showMilestones,\r\n            colors,\r\n            gradientDirection = \"to right\",\r\n            stripedIndicatorSize = 20,\r\n            interactive,\r\n            showTooltip,\r\n            onProgressClick,\r\n            indicatorClassName,\r\n            className,\r\n            ...props\r\n        },\r\n        ref\r\n    ) => {\r\n        const [hoverValue, setHoverValue] = React.useState<number | null>(null)\r\n        const containerRef = React.useRef<HTMLDivElement>(null)\r\n        const fyskConfig = useFyskConfig();\r\n        const { isEnabled, motion, AnimatePresence } = useFyskAnimation()\r\n\r\n        // Generate unique ID for tooltip accessibility\r\n        const tooltipId = React.useId()\r\n\r\n        // Auto-determine icon based on state\r\n        const getStateIcon = () => {\r\n            if (icon === \"auto\") {\r\n                if (state === \"success\") return fyskConfig.icons?.success || <CheckCircle2 className=\"h-4 w-4\" />\r\n                if (state === \"error\") return fyskConfig.icons?.error || <XCircle className=\"h-4 w-4\" />\r\n                if (state === \"loading\" || indeterminate) return fyskConfig.icons?.loading || <Loader2 className=\"h-4 w-4 animate-spin\" />\r\n                return null\r\n            }\r\n            return icon\r\n        }\r\n\r\n        const displayIcon = getStateIcon()\r\n\r\n        // Format the displayed value\r\n        const formattedValue = formatValue\r\n            ? formatValue(hoverValue ?? value)\r\n            : `${Math.round(hoverValue ?? value)}%`\r\n\r\n        // generate striped background styles if needed\r\n        const stripedBg = React.useMemo(() => variant === \"striped\" ? {\r\n            ...createStripedBg(\"--color-primary\", stripedIndicatorSize, 0.5),\r\n            [\"--stripe-size\" as string]: `${stripedIndicatorSize}px`,\r\n        } : undefined, [stripedIndicatorSize, variant])\r\n\r\n        // Handle click for interactive mode\r\n        const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {\r\n            console.log(\"click\")\r\n            console.log(interactive, onProgressClick, containerRef.current)\r\n            if (!interactive || !onProgressClick || !containerRef.current) return\r\n\r\n            const rect = containerRef.current.getBoundingClientRect()\r\n            const x = e.clientX - rect.left\r\n            const percentage = Math.round((x / rect.width) * 100)\r\n            console.log(percentage)\r\n            onProgressClick(Math.min(100, Math.max(0, percentage)))\r\n        }\r\n\r\n        // Handle hover for tooltip\r\n        const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {\r\n            if (!showTooltip || !containerRef.current) return\r\n\r\n            const rect = containerRef.current.getBoundingClientRect()\r\n            const x = e.clientX - rect.left\r\n            const percentage = Math.round((x / rect.width) * 100)\r\n            setHoverValue(Math.min(100, Math.max(0, percentage)))\r\n        }\r\n\r\n        const handleMouseLeave = () => {\r\n            setHoverValue(null)\r\n        }\r\n\r\n\r\n        return (\r\n            <div className=\"w-full space-y-2\">\r\n                {/* Top Label */}\r\n                {(label || showValue) && labelPosition === \"top\" && (\r\n                    <div className=\"flex items-center justify-between text-sm\">\r\n                        {label && <span className=\"text-foreground font-medium\">{label}</span>}\r\n                        {showValue && (\r\n                            <div className=\"flex items-center gap-2\">\r\n                                <span className=\"text-muted-foreground font-mono\">\r\n                                    {formattedValue}\r\n                                </span>\r\n                                {displayIcon && <span className=\"text-muted-foreground\">{displayIcon}</span>}\r\n                            </div>\r\n                        )}\r\n                    </div>\r\n                )}\r\n\r\n                {/* Progress Bar Container */}\r\n                <div\r\n                    ref={containerRef}\r\n                    className={cn(\r\n                        \"relative\",\r\n                        interactive && \"cursor-pointer\"\r\n                    )}\r\n                    onClick={handleClick}\r\n                    onMouseMove={handleMouseMove}\r\n                    onMouseLeave={handleMouseLeave}\r\n                >\r\n                    <ProgressPrimitive.Root\r\n                        ref={ref}\r\n                        value={indeterminate ? null : value}\r\n                        aria-describedby={showTooltip && hoverValue !== null ? tooltipId : undefined}\r\n                        className={cn(\r\n                            progressVariants({ size, shape, variant, state }),\r\n                            interactive && \"hover:opacity-90 transition-opacity\",\r\n                            className\r\n                        )}\r\n                        {...props}\r\n                    >\r\n                        <div className=\"relative h-full w-full\">\r\n                            {/* Buffer Layer */}\r\n                            {bufferValue !== undefined && (\r\n                                <motion.div\r\n                                    {...(isEnabled ? {\r\n                                        initial: { width: 0 },\r\n                                        animate: { width: `${Math.min(100, Math.max(0, bufferValue))}%` },\r\n                                        transition: { duration: 0.8, ease: \"easeOut\" }\r\n                                    } : {\r\n                                        style: { width: `${Math.min(100, Math.max(0, bufferValue))}%` }\r\n                                    })}\r\n                                    className=\"absolute inset-y-0 left-0 bg-primary/30 transition-all duration-1000\"\r\n                                />\r\n                            )}\r\n\r\n                            {/* Segments or Single Indicator */}\r\n                            {segments && segments.length > 0 ? (\r\n                                <div className=\"flex h-full\">\r\n                                    {segments.map((segment, index) => (\r\n                                        <motion.div\r\n                                            key={index}\r\n                                            {...(isEnabled ? {\r\n                                                initial: { width: 0 },\r\n                                                animate: { width: `${segment.value}%` },\r\n                                                transition: { duration: 0.8, ease: \"easeOut\", delay: index * 0.1 }\r\n                                            } : {\r\n                                                style: { width: `${segment.value}%` }\r\n                                            })}\r\n                                            className={cn(\r\n                                                \"h-full transition-all duration-1000\",\r\n                                                segment.className\r\n                                            )}\r\n                                            style={{\r\n                                                backgroundColor: segment.color,\r\n                                            }}\r\n                                            title={segment.label}\r\n                                        />\r\n                                    ))}\r\n                                </div>\r\n                            ) : indeterminate ? (\r\n                                <div\r\n                                    className={cn(\r\n                                        \"absolute h-full w-1/3\",\r\n                                        \"animate-indeterminate\",\r\n                                        indicatorVariants({ variant, state: \"idle\" }) // Force idle state to avoid animation conflicts\r\n                                    )}\r\n                                    style={stripedBg}\r\n                                />\r\n                            ) : (\r\n                                <ProgressPrimitive.Indicator asChild={isEnabled}>\r\n                                    <motion.div\r\n                                        {...(isEnabled ? {\r\n                                            initial: { width: 0 },\r\n                                            animate: { width: `${Math.min(100, Math.max(0, value))}%` },\r\n                                            transition: { type: \"easeOut\", stiffness: 100, damping: 20 }\r\n                                        } : {\r\n                                            style: { transform: `translateX(-${100 - Math.min(100, Math.max(0, value))}%)` }\r\n                                        })}\r\n                                        className={cn(\r\n                                            indicatorVariants({ variant, state }),\r\n                                            indicatorClassName\r\n                                        )}\r\n                                        style={{\r\n                                            ...(variant === \"gradient\" && colors && colors.length > 0\r\n                                                ? { background: `linear-gradient(${gradientDirection}, ${colors.join(\", \")})` }\r\n                                                : {}),\r\n                                            ...(stripedBg)\r\n                                        }}\r\n                                    >\r\n                                        {/* Inside Label */}\r\n                                        {showValue && labelPosition === \"inside\" && value > 15 && (\r\n                                            <div className=\"flex h-full items-center justify-end pr-2\">\r\n                                                <span className=\"text-xs font-bold text-primary-foreground drop-shadow\">\r\n                                                    {formattedValue}\r\n                                                </span>\r\n                                            </div>\r\n                                        )}\r\n                                    </motion.div>\r\n                                </ProgressPrimitive.Indicator>\r\n                            )}\r\n\r\n                            {/* Milestones */}\r\n                            {showMilestones && milestones && milestones.length > 0 && (\r\n                                <>\r\n                                    {milestones.map((milestone, index) => (\r\n                                        <div\r\n                                            key={index}\r\n                                            className=\"absolute top-0 h-full w-0.5 bg-border pointer-events-none\"\r\n                                            style={{ left: `${milestone.value}%` }}\r\n                                            title={milestone.label || `${milestone.value}%`}\r\n                                        >\r\n                                            {milestone.label && (\r\n                                                <span className=\"absolute -top-5 left-1/2 -translate-x-1/2 text-xs text-muted-foreground whitespace-nowrap\">\r\n                                                    {milestone.label}\r\n                                                </span>\r\n                                            )}\r\n                                        </div>\r\n                                    ))}\r\n                                </>\r\n                            )}\r\n                        </div>\r\n                    </ProgressPrimitive.Root>\r\n\r\n                    {/* Tooltip with accessibility */}\r\n                    <AnimatePresence>\r\n                        {showTooltip && hoverValue !== null && (\r\n                            <motion.div\r\n                                id={tooltipId}\r\n                                role=\"tooltip\"\r\n                                aria-live=\"polite\"\r\n                                {...(isEnabled ? {\r\n                                    initial: { opacity: 0, y: -5, scale: 0.9 },\r\n                                    animate: { opacity: 1, y: 0, scale: 1 },\r\n                                    exit: { opacity: 0, y: -5, scale: 0.9 }\r\n                                } : {})}\r\n                                className=\"absolute -top-10 bg-popover text-popover-foreground px-2 py-1 rounded-md text-xs shadow-lg border border-border pointer-events-none z-50\"\r\n                                style={{ left: `${hoverValue}%`, x: \"-50%\" }}\r\n                            >\r\n                                {formatValue ? formatValue(hoverValue) : `${hoverValue}%`}\r\n                            </motion.div>\r\n                        )}\r\n                    </AnimatePresence>\r\n                </div>\r\n\r\n                {/* Bottom Label */}\r\n                {(label || showValue) && labelPosition === \"bottom\" && (\r\n                    <div className=\"flex items-center justify-between text-sm\">\r\n                        {label && <span className=\"text-muted-foreground\">{label}</span>}\r\n                        {showValue && (\r\n                            <div className=\"flex items-center gap-2\">\r\n                                <span className=\"text-muted-foreground font-mono text-xs\">\r\n                                    {formattedValue}\r\n                                </span>\r\n                                {displayIcon && <span className=\"text-muted-foreground\">{displayIcon}</span>}\r\n                            </div>\r\n                        )}\r\n                    </div>\r\n                )}\r\n            </div>\r\n        )\r\n    }\r\n)\r\nProgress.displayName = ProgressPrimitive.Root.displayName\r\n\r\nexport { Progress }\r\n"}],"cssVars":{"theme":{"animate-shimmer":"shimmer 2s infinite","animate-indeterminate":"indeterminate 1.5s ease-in-out infinite","animate-stripe-move":"stripe-move 1s linear infinite","animate-blink":"blink 1.5s ease-in-out infinite"}},"tailwind":{"config":{"theme":{"extend":{"keyframes":{"shimmer":{"0%":{"transform":"translateX(-100%)"},"100%":{"transform":"translateX(100%)"}},"blink":{"0%, 100%":{"opacity":"1"},"50%":{"opacity":"0"}},"indeterminate":{"0%":{"transform":"translateX(-100%)"},"100%":{"transform":"translateX(400%)"}},"stripe-move":{"0%":{"background-position":"0 0"},"100%":{"background-position":"calc(var(--stripe-size, 20px) * 2) 0"}}},"animation":{"shimmer":"shimmer 2s infinite","blink":"blink 1.5s ease-in-out infinite","indeterminate":"indeterminate 1.5s ease-in-out infinite","stripe-move":"stripe-move 1s linear infinite"}}}}}}