UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

342 lines (339 loc) 13 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { cn } from '../../lib/utilsComprehensive.js'; import { Check, Loader2, AlertCircle, ChevronLeft, ChevronRight } from 'lucide-react'; import { useState, useEffect, createContext } from 'react'; import '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import { MotionFramer } from '../../primitives/motion/MotionFramer.js'; import { GlassButton } from '../button/GlassButton.js'; import '../button/GlassFab.js'; import '../button/GlassMagneticButton.js'; import { CardHeader, CardTitle, CardContent } from '../card/index.js'; import '../data-display/GlassAccordion.js'; import '../data-display/GlassAlert.js'; import '../data-display/GlassAvatar.js'; import '../data-display/GlassBadge.js'; import '../data-display/GlassBadgeLine.js'; import '../data-display/GlassDataGrid.js'; import '../data-display/GlassDataTable.js'; import '../data-display/GlassHeatmap.js'; import '../data-display/GlassLoadingSkeleton.js'; import { GlassProgress } from '../data-display/GlassProgress.js'; import '../data-display/GlassTimeline.js'; import '../data-display/GlassSkeleton.js'; import '../data-display/GlassNotificationCenter.js'; import '../data-display/GlassAnimatedNumber.js'; import { GlassCard } from '../card/GlassCard.js'; const WizardContext = /*#__PURE__*/createContext(null); /** * GlassWizard component * A multi-step form wizard with glassmorphism styling and comprehensive features */ const GlassWizard = ({ steps, currentStep: controlledCurrentStep, onStepChange, onComplete, onCancel, title, description, showStepNavigation = true, showProgress = true, allowSkip = false, nextButtonText = "Next", previousButtonText = "Previous", completeButtonText = "Complete", cancelButtonText = "Cancel", loading = false, validationMode = "onNext", className, ...props }) => { const [internalCurrentStep, setInternalCurrentStep] = useState(0); const [completedSteps, setCompletedSteps] = useState(new Set()); const [wizardData, setWizardData] = useState({}); const [validatingStep, setValidatingStep] = useState(null); const [stepErrors, setStepErrors] = useState({}); const currentStep = controlledCurrentStep ?? internalCurrentStep; const currentStepData = steps[currentStep]; const isFirstStep = currentStep === 0; const isLastStep = currentStep === steps.length - 1; const progress = (currentStep + 1) / steps.length * 100; // Update internal step when controlled step changes useEffect(() => { if (controlledCurrentStep !== undefined) { setInternalCurrentStep(controlledCurrentStep); } }, [controlledCurrentStep]); // Validate step const validateStep = async stepIndex => { const step = steps[stepIndex]; if (!step?.validation) return true; try { setValidatingStep(stepIndex); const isValid = await step.validation(); setValidatingStep(null); if (!isValid) { setStepErrors(prev => ({ ...prev, [stepIndex]: "Please complete all required fields" })); } else { setStepErrors(prev => { const newErrors = { ...prev }; delete newErrors[stepIndex]; return newErrors; }); } return isValid; } catch (error) { setValidatingStep(null); setStepErrors(prev => ({ ...prev, [stepIndex]: "Validation failed" })); return false; } }; // Check if step is valid const isStepValid = stepIndex => { return !stepErrors[stepIndex]; }; // Check if step is completed const isStepCompleted = stepIndex => { return completedSteps.has(stepIndex); }; // Navigate to step const goToStep = async stepIndex => { if (stepIndex < 0 || stepIndex >= steps.length) return; // Validate current step if validation mode is onChange if (validationMode === "onChange" && !isStepValid(currentStep)) { return; } // Validate current step before moving to next if (stepIndex > currentStep) { const isValid = await validateStep(currentStep); if (!isValid) return; // Mark current step as completed setCompletedSteps(prev => new Set([...prev, currentStep])); } if (controlledCurrentStep === undefined) { setInternalCurrentStep(stepIndex); } onStepChange?.(stepIndex); }; // Go to next step const goToNext = async () => { if (isLastStep) { await completeWizard(); } else { await goToStep(currentStep + 1); } }; // Go to previous step const goToPrevious = () => { if (!isFirstStep) { goToStep(currentStep - 1); } }; // Complete wizard const completeWizard = async () => { // Validate final step const isValid = await validateStep(currentStep); if (!isValid) return; // Mark final step as completed setCompletedSteps(prev => new Set([...prev, currentStep])); onComplete?.(wizardData); }; // Cancel wizard const cancelWizard = () => { onCancel?.(); }; // Set wizard data const setData = data => { setWizardData(prev => ({ ...prev, ...data })); }; const contextValue = { currentStep, steps, goToStep, goToNext, goToPrevious, isStepValid, isStepCompleted, completeWizard, cancelWizard, data: wizardData, setData }; return jsx(WizardContext.Provider, { "data-glass-component": true, value: contextValue, children: jsx(MotionFramer, { preset: "fadeIn", className: 'glass-w-full max-w-4xl glass-mx-auto', children: jsxs(GlassCard, { className: cn("overflow-hidden", className), ...props, children: [jsxs(CardHeader, { className: "glass-border-b glass-border-white/10", children: [jsxs("div", { className: "glass-flex glass-items-center glass-justify-between", children: [jsxs("div", { children: [title && jsx(CardTitle, { className: 'glass-text-xl font-semibold text-primary mb-1', children: title }), description && jsx("p", { className: 'glass-text-sm text-primary/70', children: description })] }), showProgress && jsxs("div", { className: "glass-flex glass-items-center glass-gap-3", children: [jsxs("span", { className: 'glass-text-sm text-primary/60', children: ["Step ", currentStep + 1, " of ", steps.length] }), jsx("div", { className: 'w-24', children: jsx(GlassProgress, { value: progress, size: "sm" }) })] })] }), showStepNavigation && jsx("nav", { "aria-label": "Wizard steps", className: 'glass-flex glass-items-center glass-gap-2 mt-6 overflow-x-auto pb-2', children: steps.map((step, index) => { const isActive = index === currentStep; const isCompleted = completedSteps.has(index); isStepValid(index); const isDisabled = step.disabled; return jsxs("button", { onClick: e => !isDisabled && goToStep(index), onKeyDown: e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); if (!isDisabled) goToStep(index); } }, disabled: isDisabled, "aria-label": `${step.title}${isActive ? " (current step)" : ""}${isCompleted ? " (completed)" : ""}`, "aria-current": isActive ? "step" : undefined, "aria-disabled": isDisabled, className: cn("flex items-center glass-gap-2 glass-px-3 glass-py-2 glass-radius-lg glass-text-sm font-medium transition-all duration-200 whitespace-nowrap", "border border-white/20", "glass-focus glass-touch-target glass-contrast-guard", { "bg-primary/20 text-primary-foreground border-primary/40": isActive, "bg-green-500/20 text-green-400 border-green-500/40": isCompleted && !isActive, "bg-white/5 glass-text-primary/60": !isActive && !isCompleted, "opacity-50 cursor-not-allowed": isDisabled, "hover:bg-white/10": !isDisabled && !isActive }), children: [jsx("div", { className: 'glass-flex glass-items-center glass-justify-center w-6 h-6 glass-radius-full glass-text-xs', children: isCompleted ? jsx(Check, { className: 'w-3 h-3' }) : isActive && validatingStep === index ? jsx(Loader2, { className: 'w-3 h-3 animate-spin' }) : jsx("span", { children: index + 1 }) }), jsx("span", { className: 'hidden sm:inline', children: step.title })] }, step.id); }) })] }), jsx(CardContent, { className: "glass-p-6", children: jsxs(MotionFramer, { preset: "slideIn", className: 'min-h-[300px]', children: [jsxs("div", { className: 'glass-flex glass-items-start glass-gap-4 mb-6', children: [currentStepData.icon && jsx("div", { className: 'glass-flex glass-items-center glass-justify-center w-12 h-12 glass-radius-lg glass-surface-subtle/10', children: currentStepData.icon }), jsxs("div", { className: "glass-flex-1", children: [jsx("h2", { className: 'glass-text-lg font-semibold text-primary mb-1', children: currentStepData.title }), currentStepData.description && jsx("p", { className: 'glass-text-sm text-primary/70', children: currentStepData.description })] }), stepErrors[currentStep] && jsxs("div", { role: "alert", "aria-live": "assertive", className: 'glass-flex glass-items-center glass-gap-2 text-primary', children: [jsx(AlertCircle, { className: 'w-4 h-4', "aria-hidden": "true" }), jsx("span", { className: "glass-text-sm", children: stepErrors[currentStep] })] })] }), jsx("div", { className: "glass-flex-1", children: currentStepData.content })] }, currentStep) }), jsx("div", { className: "glass-border-t glass-border-white/10 glass-p-6", children: jsxs("div", { className: "glass-flex glass-items-center glass-justify-between", children: [jsxs("div", { className: "glass-flex glass-gap-3", children: [!isFirstStep && jsxs(GlassButton, { variant: "outline", onClick: goToPrevious, disabled: loading, className: "glass-flex glass-items-center glass-gap-2", children: [jsx(ChevronLeft, { className: 'w-4 h-4' }), previousButtonText] }), allowSkip && !isLastStep && jsx(GlassButton, { variant: "ghost", onClick: e => goToStep(currentStep + 1), disabled: loading, children: "Skip" })] }), jsxs("div", { className: "glass-flex glass-gap-3", children: [jsx(GlassButton, { variant: "ghost", onClick: cancelWizard, disabled: loading, children: cancelButtonText }), jsxs(GlassButton, { variant: "primary", onClick: goToNext, disabled: loading || validatingStep === currentStep, className: "glass-flex glass-items-center glass-gap-2", children: [validatingStep === currentStep ? jsx(Loader2, { className: 'w-4 h-4 animate-spin' }) : isLastStep ? jsx(Check, { className: 'w-4 h-4' }) : jsx(ChevronRight, { className: 'w-4 h-4' }), isLastStep ? completeButtonText : nextButtonText] })] })] }) })] }) }) }); }; export { GlassWizard, GlassWizard as default }; //# sourceMappingURL=GlassWizard.js.map