aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
342 lines (339 loc) • 13 kB
JavaScript
'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