aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
316 lines (313 loc) • 10.9 kB
JavaScript
'use client';
import { jsxs, jsx } from 'react/jsx-runtime';
import { forwardRef, useState, useCallback, useEffect } from 'react';
import { GlassButton } from '../../button/GlassButton.js';
import '../../button/GlassFab.js';
import '../../button/GlassMagneticButton.js';
import { cn } from '../../../lib/utilsComprehensive.js';
import { GlassCard } from '../../card/GlassCard.js';
import { GlassFormBuilder } from '../../interactive/GlassFormBuilder.js';
import '../../layout/GlassBox.js';
import '../../layout/GlassContainer.js';
import '../../layout/GlassFlex.js';
import '../../layout/GlassGrid.js';
import '../../layout/GlassMasonry.js';
import '../../layout/GlassScrollArea.js';
import '../../layout/GlassSplitPane.js';
import { VStack, HStack } from '../../layout/GlassStack.js';
import '../../layout/OptimizedGlassContainer.js';
import '../../layout/Box.js';
import { GlassCore } from '../../../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 { GlassFormWizardSteps } from './GlassFormWizardSteps.js';
/**
* GlassWizardTemplate component
* Multi-step wizard template with advanced navigation and validation
*/
const GlassWizardTemplate = /*#__PURE__*/forwardRef(({
title,
description,
steps,
currentStep = 0,
values = {},
errors = {},
onStepChange,
onSubmit,
onCancel,
onChange,
onStepValidate,
loading = false,
allowDraft = false,
onSaveDraft,
autoSave = false,
showStepIndicator = true,
allowSkipping = false,
layout = "default",
submitText = "Submit",
cancelText = "Cancel",
nextText = "Next",
previousText = "Previous",
skipText = "Skip",
finishText = "Finish",
className,
...props
}, ref) => {
const [internalValues, setInternalValues] = useState(values);
const [internalErrors, setInternalErrors] = useState(errors);
const [stepValidation, setStepValidation] = useState({});
const [completedSteps, setCompletedSteps] = useState(new Set());
const totalSteps = steps?.length || 0;
const currentStepData = steps?.[currentStep];
const isFirstStep = currentStep === 0;
const isLastStep = currentStep === totalSteps - 1;
// Handle value change
const handleValueChange = useCallback(newValues => {
setInternalValues(newValues);
onChange?.(newValues);
}, [onChange]);
// Handle step validation
const handleStepValidation = useCallback(async (stepIndex, stepValues) => {
const step = steps?.[stepIndex];
let errors = {};
// Step-specific validation
if (step.validation) {
errors = await step.validation(stepValues);
}
// Custom validation handler
if (onStepValidate) {
const customErrors = await onStepValidate(stepIndex, stepValues);
errors = {
...errors,
...customErrors
};
}
setInternalErrors(errors);
const isValid = Object.keys(errors).length === 0;
setStepValidation(prev => ({
...prev,
[stepIndex]: isValid
}));
return isValid;
}, [steps, onStepValidate]);
// Handle next step
const handleNext = useCallback(async () => {
if (isLastStep) return;
const isValid = await handleStepValidation(currentStep, internalValues);
if (isValid) {
setCompletedSteps(prev => new Set([...prev, currentStep]));
onStepChange?.(currentStep + 1);
}
}, [currentStep, isLastStep, handleStepValidation, internalValues, onStepChange]);
// Handle previous step
const handlePrevious = useCallback(() => {
if (isFirstStep) return;
onStepChange?.(currentStep - 1);
}, [currentStep, isFirstStep, onStepChange]);
// Handle skip step
const handleSkip = useCallback(() => {
if (isLastStep || !currentStepData.canSkip) return;
onStepChange?.(currentStep + 1);
}, [currentStep, isLastStep, currentStepData?.canSkip, onStepChange]);
// Handle step click (direct navigation)
const handleStepClick = useCallback(stepIndex => {
// Only allow navigation to completed steps or the next step
if (stepIndex <= currentStep || completedSteps.has(stepIndex - 1)) {
onStepChange?.(stepIndex);
}
}, [currentStep, completedSteps, onStepChange]);
// Handle form submission
const handleSubmit = useCallback(async () => {
const isValid = await handleStepValidation(currentStep, internalValues);
if (isValid) {
if (isLastStep) {
await onSubmit?.(internalValues);
} else {
handleNext();
}
}
}, [currentStep, internalValues, isLastStep, handleStepValidation, onSubmit, handleNext]);
// Handle draft save
const handleSaveDraft = useCallback(() => {
onSaveDraft?.(internalValues);
}, [internalValues, onSaveDraft]);
// Auto-save effect
useEffect(() => {
if (autoSave && onSaveDraft) {
const timeoutId = setTimeout(() => {
onSaveDraft(internalValues);
}, 2000);
return () => clearTimeout(timeoutId);
}
}, [internalValues, autoSave, onSaveDraft]);
// Render wizard header
const renderHeader = () => jsxs(VStack, {
space: "md",
children: [jsxs(VStack, {
space: "sm",
children: [jsx("h1", {
className: 'glass-text-2xl font-bold text-primary',
children: title
}), description && jsx("p", {
className: "glass-text-secondary",
children: description
})]
}), showStepIndicator && jsx(GlassFormWizardSteps, {
steps: steps,
currentStep: currentStep,
completedSteps: completedSteps,
onStepClick: handleStepClick,
layout: layout === "compact" ? "compact" : "default"
})]
});
// Render step content
const renderStepContent = () => jsxs(VStack, {
space: "lg",
children: [jsxs(VStack, {
space: "sm",
children: [jsxs(HStack, {
space: "sm",
align: "center",
children: [jsx("h2", {
className: 'glass-text-xl font-semibold text-primary',
children: currentStepData.title
}), currentStepData.optional && jsx("span", {
className: "glass-text-sm glass-text-secondary glass-surface-subtle glass-px-2 glass-py-1 glass-radius-md",
children: "Optional"
})]
}), currentStepData.description && jsx("p", {
className: "glass-text-secondary",
children: currentStepData.description
})]
}), jsx("div", {
className: "glass-flex-1",
children: currentStepData.component ? currentStepData.component : jsx(GlassFormBuilder, {
schema: currentStepData.schema || [],
values: internalValues,
errors: internalErrors,
onChange: handleValueChange,
onSubmit: handleSubmit,
validateOnChange: true,
autoSave: false,
showProgress: false,
submitText: "",
showCancel: false
})
})]
});
// Render navigation buttons
const renderNavigation = () => jsxs(HStack, {
space: "sm",
align: "center",
justify: "between",
children: [jsxs(HStack, {
space: "sm",
children: [onCancel && jsx(GlassButton, {
variant: "ghost",
onClick: onCancel,
disabled: loading,
children: cancelText
}), allowDraft && jsx(GlassButton, {
variant: "outline",
onClick: handleSaveDraft,
disabled: loading,
children: "Save Draft"
})]
}), jsxs(HStack, {
space: "sm",
children: [!isFirstStep && jsx(GlassButton, {
variant: "ghost",
onClick: handlePrevious,
disabled: loading,
children: previousText
}), allowSkipping && currentStepData.canSkip && !isLastStep && jsx(GlassButton, {
variant: "outline",
onClick: handleSkip,
disabled: loading,
children: skipText
}), jsx(GlassButton, {
variant: "default",
onClick: handleSubmit,
loading: loading,
children: isLastStep ? finishText : nextText
})]
})]
});
// Render layout
const renderLayout = () => {
const content = jsxs(VStack, {
space: "lg",
children: [renderStepContent(), renderNavigation()]
});
switch (layout) {
case "compact":
return jsx("div", {
"data-glass-component": true,
className: 'max-w-2xl glass-mx-auto',
children: jsx(GlassCard, {
variant: "default",
className: "glass-p-6",
children: content
})
});
case "sidebar":
return jsxs("div", {
className: "glass-grid glass-grid-cols-12 glass-gap-8",
children: [jsx("div", {
className: 'col-span-4',
children: jsx(GlassCore, {
className: 'glass-p-6 sticky top-8',
children: jsxs(VStack, {
space: "md",
children: [jsx("h3", {
className: 'font-semibold text-primary',
children: "Steps"
}), jsx(VStack, {
space: "sm",
children: steps.map((step, index) => jsxs(GlassButton, {
onClick: e => handleStepClick(index),
className: cn("text-left glass-p-3 glass-radius-lg transition-colors", index === currentStep ? "bg-primary/10 text-primary border border-primary/20" : completedSteps.has(index) ? "bg-success/10 text-success border border-success/20" : "glass-text-secondary hover:bg-muted/50"),
disabled: index > currentStep && !completedSteps.has(index - 1),
children: [jsx("div", {
className: 'font-medium',
children: step.title
}), step.description && jsx("div", {
className: 'glass-text-sm opacity-75 glass-mt-1',
children: step.description
})]
}, step.id))
})]
})
})
}), jsx("div", {
className: 'col-span-8',
children: jsx(GlassCard, {
variant: "default",
className: "glass-p-6",
children: content
})
})]
});
default:
return jsx(GlassCard, {
variant: "default",
className: "glass-p-6",
children: content
});
}
};
return jsxs("div", {
ref: ref,
className: cn("w-full glass-auto-gap glass-auto-gap-3xl", className),
...props,
children: [layout !== "sidebar" && renderHeader(), jsx(MotionFramer, {
children: renderLayout()
})]
});
});
GlassWizardTemplate.displayName = "GlassWizardTemplate";
export { GlassWizardTemplate };
//# sourceMappingURL=GlassWizardTemplate.js.map