@moontra/moonui-pro 2.8.0 → 2.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -60056,16 +60056,61 @@ var FormWizardProvider = ({
60056
60056
  }, [stepData, currentStep, completedSteps, persistData, storageKey]);
60057
60057
  const validateCurrentStep = useCallback(async () => {
60058
60058
  const step = steps[currentStep];
60059
+ if (typeof window !== "undefined") {
60060
+ const stepElement = document.querySelector("[data-wizard-step-content]");
60061
+ if (stepElement) {
60062
+ const requiredInputs = stepElement.querySelectorAll("input[required], select[required], textarea[required]");
60063
+ const emptyFields = [];
60064
+ requiredInputs.forEach((input) => {
60065
+ const htmlInput = input;
60066
+ if (!htmlInput.value || htmlInput.value.trim() === "") {
60067
+ const label = document.querySelector(`label[for="${htmlInput.id}"]`);
60068
+ const fieldName = label ? label.textContent?.replace(" *", "") || "Field" : htmlInput.name || htmlInput.id || "Field";
60069
+ emptyFields.push(fieldName);
60070
+ }
60071
+ });
60072
+ const requiredCheckboxes = stepElement.querySelectorAll('input[type="checkbox"][required]');
60073
+ requiredCheckboxes.forEach((checkbox) => {
60074
+ const htmlCheckbox = checkbox;
60075
+ if (!htmlCheckbox.checked) {
60076
+ const label = document.querySelector(`label[for="${htmlCheckbox.id}"]`);
60077
+ const fieldName = label ? label.textContent?.replace(" *", "") || "Checkbox" : htmlCheckbox.name || htmlCheckbox.id || "Checkbox";
60078
+ emptyFields.push(fieldName);
60079
+ }
60080
+ });
60081
+ if (emptyFields.length > 0) {
60082
+ setError(`Please fill in the following required fields: ${emptyFields.join(", ")}`);
60083
+ return false;
60084
+ }
60085
+ }
60086
+ }
60059
60087
  if (!step.validation)
60060
60088
  return true;
60061
60089
  setIsLoading(true);
60062
60090
  setError(null);
60063
60091
  try {
60064
- const isValid = await step.validation();
60065
- if (!isValid) {
60066
- setError("Please complete all required fields before proceeding");
60092
+ const result = await step.validation();
60093
+ if (typeof result === "boolean") {
60094
+ if (!result) {
60095
+ setError("Please complete all required fields before proceeding");
60096
+ }
60097
+ return result;
60098
+ }
60099
+ if (typeof result === "object" && result !== null && "isValid" in result) {
60100
+ const validationResult = result;
60101
+ if (!validationResult.isValid) {
60102
+ if (validationResult.error) {
60103
+ setError(validationResult.error);
60104
+ } else if (validationResult.errors && Array.isArray(validationResult.errors)) {
60105
+ setError(validationResult.errors.join(", "));
60106
+ } else {
60107
+ setError("Please complete all required fields before proceeding");
60108
+ }
60109
+ }
60110
+ return validationResult.isValid;
60067
60111
  }
60068
- return isValid;
60112
+ setError("Invalid validation response");
60113
+ return false;
60069
60114
  } catch (err) {
60070
60115
  setError(err instanceof Error ? err.message : "Validation failed");
60071
60116
  return false;
@@ -60193,55 +60238,129 @@ var FormWizardProgress = ({
60193
60238
  const { steps, currentStep, isStepCompleted, isStepAccessible, error: error42 } = useFormWizard();
60194
60239
  if (progressType === "linear") {
60195
60240
  return /* @__PURE__ */ jsx("div", { className: cn(
60196
- "relative",
60197
- orientation === "vertical" ? "flex flex-col space-y-8" : "flex items-center justify-between",
60241
+ "relative w-full",
60242
+ orientation === "vertical" ? "flex flex-col space-y-8" : "",
60198
60243
  className
60199
- ), children: steps.map((step, index2) => {
60244
+ ), children: orientation === "horizontal" ? /* @__PURE__ */ jsx("div", { className: "relative w-full", children: /* @__PURE__ */ jsx("div", { className: "relative flex items-start justify-between w-full", children: steps.map((step, index2) => {
60200
60245
  const isActive2 = index2 === currentStep;
60201
60246
  const isCompleted = isStepCompleted(index2);
60202
60247
  const isAccessible = isStepAccessible(index2);
60203
60248
  const hasError = isActive2 && error42;
60204
60249
  const StepIcon = typeof step.icon === "function" ? step.icon({ isActive: isActive2, isCompleted }) : step.icon;
60205
- return /* @__PURE__ */ jsx(t__default.Fragment, { children: /* @__PURE__ */ jsxs("div", { className: cn(
60206
- "relative flex items-center",
60207
- orientation === "vertical" ? "w-full" : "flex-1"
60208
- ), children: [
60209
- /* @__PURE__ */ jsx(
60210
- motion.div,
60211
- {
60212
- initial: { scale: 0.8 },
60213
- animate: { scale: isActive2 ? 1.1 : 1 },
60214
- className: cn(
60215
- "relative z-20 flex items-center justify-center rounded-full border-2 transition-all duration-300",
60216
- stepIconPosition === "inside" ? "w-12 h-12" : "w-10 h-10",
60217
- isActive2 && "border-primary bg-primary text-primary-foreground shadow-lg",
60218
- isCompleted && !isActive2 && "border-primary bg-primary text-primary-foreground",
60219
- !isActive2 && !isCompleted && isAccessible && "border-muted-foreground/50 bg-background text-muted-foreground hover:border-muted-foreground",
60220
- !isAccessible && "border-muted bg-muted text-muted-foreground cursor-not-allowed opacity-50",
60221
- hasError && "border-destructive bg-destructive text-destructive-foreground"
60222
- ),
60223
- children: hasError ? errorStepIcon : isCompleted && !isActive2 ? completedStepIcon : isActive2 && activeStepIcon ? activeStepIcon : StepIcon ? /* @__PURE__ */ jsx("span", { className: "w-5 h-5 flex items-center justify-center", children: StepIcon }) : showStepNumbers ? /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: index2 + 1 }) : /* @__PURE__ */ jsx(Circle, { className: "w-4 h-4" })
60224
- }
60225
- ),
60226
- showStepTitles && /* @__PURE__ */ jsxs("div", { className: cn(
60227
- "absolute whitespace-nowrap",
60228
- orientation === "vertical" ? "left-16 top-1/2 -translate-y-1/2" : "top-full mt-2 left-1/2 -translate-x-1/2 text-center"
60229
- ), children: [
60250
+ return /* @__PURE__ */ jsxs("div", { className: "relative flex-1 flex flex-col items-center", children: [
60251
+ showStepTitles && /* @__PURE__ */ jsxs("div", { className: "text-center mb-3 min-h-[40px] px-2", children: [
60230
60252
  /* @__PURE__ */ jsx("p", { className: cn(
60231
- "text-sm font-medium transition-colors",
60253
+ "text-sm font-semibold transition-colors",
60232
60254
  isActive2 && "text-primary",
60233
60255
  isCompleted && !isActive2 && "text-primary",
60234
60256
  !isActive2 && !isCompleted && "text-muted-foreground"
60235
60257
  ), children: step.title }),
60236
60258
  step.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: step.description })
60237
60259
  ] }),
60238
- index2 < steps.length - 1 && /* @__PURE__ */ jsx("div", { className: cn(
60239
- "absolute transition-all duration-500",
60240
- orientation === "vertical" ? "top-12 left-5 w-0.5 h-8" : "left-12 right-0 top-1/2 -translate-y-1/2 h-0.5",
60241
- isCompleted ? "bg-primary" : "bg-muted"
60242
- ) })
60243
- ] }) }, step.id);
60244
- }) });
60260
+ /* @__PURE__ */ jsxs("div", { className: "relative flex items-center w-full", children: [
60261
+ index2 < steps.length - 1 && /* @__PURE__ */ jsxs(
60262
+ "div",
60263
+ {
60264
+ className: "absolute left-1/2 w-full h-[2px]",
60265
+ style: {
60266
+ width: "calc(100% + 24px)",
60267
+ marginLeft: "24px"
60268
+ },
60269
+ children: [
60270
+ /* @__PURE__ */ jsx("div", { className: "h-full bg-gray-200 dark:bg-gray-700" }),
60271
+ index2 < currentStep && /* @__PURE__ */ jsx(
60272
+ motion.div,
60273
+ {
60274
+ className: "absolute inset-0 bg-primary",
60275
+ initial: { scaleX: 0 },
60276
+ animate: { scaleX: 1 },
60277
+ transition: { duration: 0.5, delay: index2 * 0.1 },
60278
+ style: { transformOrigin: "left" }
60279
+ }
60280
+ )
60281
+ ]
60282
+ }
60283
+ ),
60284
+ /* @__PURE__ */ jsx(
60285
+ motion.div,
60286
+ {
60287
+ initial: { scale: 0.8 },
60288
+ animate: { scale: isActive2 ? 1.05 : 1 },
60289
+ transition: { duration: 0.2 },
60290
+ className: cn(
60291
+ "relative z-10 flex items-center justify-center rounded-full transition-all duration-300 mx-auto",
60292
+ "w-12 h-12 border-2",
60293
+ isActive2 && "border-primary bg-primary text-primary-foreground shadow-lg shadow-primary/20",
60294
+ isCompleted && !isActive2 && "border-primary bg-primary text-primary-foreground",
60295
+ !isActive2 && !isCompleted && isAccessible && "border-gray-300 bg-background text-gray-500 hover:border-gray-400 dark:border-gray-600 dark:text-gray-400",
60296
+ !isAccessible && "border-gray-200 bg-gray-50 text-gray-400 cursor-not-allowed dark:border-gray-700 dark:bg-gray-900 dark:text-gray-600",
60297
+ hasError && "border-destructive bg-destructive text-destructive-foreground"
60298
+ ),
60299
+ children: hasError ? errorStepIcon : isCompleted && !isActive2 ? completedStepIcon : isActive2 && activeStepIcon ? activeStepIcon : StepIcon ? /* @__PURE__ */ jsx("span", { className: "w-5 h-5 flex items-center justify-center", children: StepIcon }) : showStepNumbers ? /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: index2 + 1 }) : /* @__PURE__ */ jsx(Circle, { className: "w-4 h-4" })
60300
+ }
60301
+ )
60302
+ ] }),
60303
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground font-medium", children: [
60304
+ "Step ",
60305
+ index2 + 1,
60306
+ " of ",
60307
+ steps.length
60308
+ ] }) })
60309
+ ] }, step.id);
60310
+ }) }) }) : (
60311
+ // Vertical orientation
60312
+ /* @__PURE__ */ jsxs("div", { className: "relative pl-12", children: [
60313
+ steps.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
60314
+ /* @__PURE__ */ jsx("div", { className: "absolute left-6 top-8 bottom-8 w-[2px] bg-gray-200 dark:bg-gray-700" }),
60315
+ /* @__PURE__ */ jsx(
60316
+ motion.div,
60317
+ {
60318
+ className: "absolute left-6 top-8 w-[2px] bg-primary",
60319
+ initial: { height: 0 },
60320
+ animate: {
60321
+ height: `${currentStep / (steps.length - 1) * (100 - 16)}%`
60322
+ },
60323
+ transition: { duration: 0.5 }
60324
+ }
60325
+ )
60326
+ ] }),
60327
+ steps.map((step, index2) => {
60328
+ const isActive2 = index2 === currentStep;
60329
+ const isCompleted = isStepCompleted(index2);
60330
+ const isAccessible = isStepAccessible(index2);
60331
+ const hasError = isActive2 && error42;
60332
+ const StepIcon = typeof step.icon === "function" ? step.icon({ isActive: isActive2, isCompleted }) : step.icon;
60333
+ return /* @__PURE__ */ jsxs("div", { className: "relative flex items-center mb-8 last:mb-0", children: [
60334
+ /* @__PURE__ */ jsx(
60335
+ motion.div,
60336
+ {
60337
+ initial: { scale: 0.8 },
60338
+ animate: { scale: isActive2 ? 1.05 : 1 },
60339
+ className: cn(
60340
+ "absolute left-0 z-20 flex items-center justify-center rounded-full border-2 transition-all duration-300",
60341
+ "w-12 h-12",
60342
+ isActive2 && "border-primary bg-primary text-primary-foreground shadow-lg",
60343
+ isCompleted && !isActive2 && "border-primary bg-primary text-primary-foreground",
60344
+ !isActive2 && !isCompleted && isAccessible && "border-gray-300 bg-background text-gray-500 hover:border-gray-400",
60345
+ !isAccessible && "border-gray-200 bg-gray-50 text-gray-400 cursor-not-allowed",
60346
+ hasError && "border-destructive bg-destructive text-destructive-foreground"
60347
+ ),
60348
+ children: hasError ? errorStepIcon : isCompleted && !isActive2 ? completedStepIcon : isActive2 && activeStepIcon ? activeStepIcon : StepIcon ? /* @__PURE__ */ jsx("span", { className: "w-5 h-5 flex items-center justify-center", children: StepIcon }) : showStepNumbers ? /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: index2 + 1 }) : /* @__PURE__ */ jsx(Circle, { className: "w-4 h-4" })
60349
+ }
60350
+ ),
60351
+ /* @__PURE__ */ jsx("div", { className: "ml-16", children: showStepTitles && /* @__PURE__ */ jsxs(Fragment, { children: [
60352
+ /* @__PURE__ */ jsx("p", { className: cn(
60353
+ "text-sm font-semibold transition-colors",
60354
+ isActive2 && "text-primary",
60355
+ isCompleted && !isActive2 && "text-primary",
60356
+ !isActive2 && !isCompleted && "text-muted-foreground"
60357
+ ), children: step.title }),
60358
+ step.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: step.description })
60359
+ ] }) })
60360
+ ] }, step.id);
60361
+ })
60362
+ ] })
60363
+ ) });
60245
60364
  }
60246
60365
  if (progressType === "dots") {
60247
60366
  return /* @__PURE__ */ jsx("div", { className: cn("flex items-center justify-center space-x-2", className), children: steps.map((_, index2) => {
@@ -60265,28 +60384,28 @@ var FormWizardProgress = ({
60265
60384
  }
60266
60385
  if (progressType === "circular") {
60267
60386
  const progress = (currentStep + 1) / steps.length * 100;
60268
- const circumference = 2 * Math.PI * 40;
60387
+ const circumference = 2 * Math.PI * 45;
60269
60388
  const strokeDashoffset = circumference - progress / 100 * circumference;
60270
- return /* @__PURE__ */ jsxs("div", { className: cn("relative w-32 h-32", className), children: [
60271
- /* @__PURE__ */ jsxs("svg", { className: "w-full h-full -rotate-90", children: [
60389
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative w-36 h-36 mx-auto", className), children: [
60390
+ /* @__PURE__ */ jsxs("svg", { className: "w-full h-full -rotate-90", viewBox: "0 0 144 144", children: [
60272
60391
  /* @__PURE__ */ jsx(
60273
60392
  "circle",
60274
60393
  {
60275
- cx: "64",
60276
- cy: "64",
60277
- r: "40",
60394
+ cx: "72",
60395
+ cy: "72",
60396
+ r: "45",
60278
60397
  fill: "none",
60279
60398
  stroke: "currentColor",
60280
60399
  strokeWidth: "8",
60281
- className: "text-muted"
60400
+ className: "text-gray-200 dark:text-gray-700"
60282
60401
  }
60283
60402
  ),
60284
60403
  /* @__PURE__ */ jsx(
60285
60404
  motion.circle,
60286
60405
  {
60287
- cx: "64",
60288
- cy: "64",
60289
- r: "40",
60406
+ cx: "72",
60407
+ cy: "72",
60408
+ r: "45",
60290
60409
  fill: "none",
60291
60410
  stroke: "currentColor",
60292
60411
  strokeWidth: "8",
@@ -60299,7 +60418,7 @@ var FormWizardProgress = ({
60299
60418
  )
60300
60419
  ] }),
60301
60420
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
60302
- /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold", children: currentStep + 1 }),
60421
+ /* @__PURE__ */ jsx("p", { className: "text-3xl font-bold", children: currentStep + 1 }),
60303
60422
  /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
60304
60423
  "of ",
60305
60424
  steps.length
@@ -60345,7 +60464,7 @@ var FormWizardStep = ({
60345
60464
  animationType = "slide",
60346
60465
  animationDuration = 0.3
60347
60466
  }) => {
60348
- const { steps, currentStep, goToNext, goToPrevious, goToStep, updateStepData, stepData } = useFormWizard();
60467
+ const { steps, currentStep, goToNext, goToPrevious, goToStep, updateStepData, stepData, error: error42 } = useFormWizard();
60349
60468
  const [direction, setDirection] = t__default.useState(0);
60350
60469
  const previousStep = t__default.useRef(currentStep);
60351
60470
  t__default.useEffect(() => {
@@ -60380,7 +60499,14 @@ var FormWizardStep = ({
60380
60499
  ease: "easeInOut"
60381
60500
  },
60382
60501
  className: cn("w-full", className),
60383
- children: content
60502
+ "data-wizard-step-content": true,
60503
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
60504
+ error42 && /* @__PURE__ */ jsxs(MoonUIAlertPro, { variant: "error", children: [
60505
+ /* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }),
60506
+ /* @__PURE__ */ jsx(MoonUIAlertDescriptionPro, { children: error42 })
60507
+ ] }),
60508
+ content
60509
+ ] })
60384
60510
  },
60385
60511
  currentStep
60386
60512
  ) });
@@ -60523,23 +60649,20 @@ var MoonUIFormWizardPro = t__default.forwardRef(({
60523
60649
  persistData,
60524
60650
  storageKey,
60525
60651
  children: /* @__PURE__ */ jsxs("div", { ref, className: cn("w-full", className), children: [
60526
- showProgressBar && /* @__PURE__ */ jsxs(Fragment, { children: [
60527
- /* @__PURE__ */ jsx(
60528
- FormWizardProgress,
60529
- {
60530
- className: progressClassName,
60531
- progressType,
60532
- orientation,
60533
- showStepNumbers,
60534
- showStepTitles,
60535
- stepIconPosition,
60536
- completedStepIcon,
60537
- activeStepIcon,
60538
- errorStepIcon
60539
- }
60540
- ),
60541
- /* @__PURE__ */ jsx(MoonUISeparatorPro, { className: "my-8" })
60542
- ] }),
60652
+ showProgressBar && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
60653
+ FormWizardProgress,
60654
+ {
60655
+ className: cn("mb-12", progressClassName),
60656
+ progressType,
60657
+ orientation,
60658
+ showStepNumbers,
60659
+ showStepTitles,
60660
+ stepIconPosition,
60661
+ completedStepIcon,
60662
+ activeStepIcon,
60663
+ errorStepIcon
60664
+ }
60665
+ ) }),
60543
60666
  /* @__PURE__ */ jsx(MoonUICardPro, { className: cn("border-0 shadow-none", contentClassName), children: /* @__PURE__ */ jsx(MoonUICardContentPro, { className: "p-0", children: /* @__PURE__ */ jsx(
60544
60667
  FormWizardStep,
60545
60668
  {
@@ -60548,11 +60671,10 @@ var MoonUIFormWizardPro = t__default.forwardRef(({
60548
60671
  animationDuration
60549
60672
  }
60550
60673
  ) }) }),
60551
- /* @__PURE__ */ jsx(MoonUISeparatorPro, { className: "my-8" }),
60552
60674
  /* @__PURE__ */ jsx(
60553
60675
  FormWizardNavigation,
60554
60676
  {
60555
- className: navigationClassName
60677
+ className: cn("mt-8", navigationClassName)
60556
60678
  }
60557
60679
  )
60558
60680
  ] })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moontra/moonui-pro",
3
- "version": "2.8.0",
3
+ "version": "2.8.2",
4
4
  "description": "Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -95,17 +95,76 @@ export const FormWizardProvider: React.FC<FormWizardProviderProps> = ({
95
95
 
96
96
  const validateCurrentStep = useCallback(async (): Promise<boolean> => {
97
97
  const step = steps[currentStep]
98
+
99
+ // First check HTML5 validation for required fields
100
+ if (typeof window !== 'undefined') {
101
+ const stepElement = document.querySelector('[data-wizard-step-content]')
102
+ if (stepElement) {
103
+ const requiredInputs = stepElement.querySelectorAll('input[required], select[required], textarea[required]')
104
+ const emptyFields: string[] = []
105
+
106
+ requiredInputs.forEach((input: Element) => {
107
+ const htmlInput = input as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
108
+ if (!htmlInput.value || htmlInput.value.trim() === '') {
109
+ const label = document.querySelector(`label[for="${htmlInput.id}"]`)
110
+ const fieldName = label ? (label.textContent?.replace(' *', '') || 'Field') : htmlInput.name || htmlInput.id || 'Field'
111
+ emptyFields.push(fieldName)
112
+ }
113
+ })
114
+
115
+ // Check for required checkboxes
116
+ const requiredCheckboxes = stepElement.querySelectorAll('input[type="checkbox"][required]')
117
+ requiredCheckboxes.forEach((checkbox: Element) => {
118
+ const htmlCheckbox = checkbox as HTMLInputElement
119
+ if (!htmlCheckbox.checked) {
120
+ const label = document.querySelector(`label[for="${htmlCheckbox.id}"]`)
121
+ const fieldName = label ? (label.textContent?.replace(' *', '') || 'Checkbox') : htmlCheckbox.name || htmlCheckbox.id || 'Checkbox'
122
+ emptyFields.push(fieldName)
123
+ }
124
+ })
125
+
126
+ if (emptyFields.length > 0) {
127
+ setError(`Please fill in the following required fields: ${emptyFields.join(', ')}`)
128
+ return false
129
+ }
130
+ }
131
+ }
132
+
133
+ // Then run custom validation if provided
98
134
  if (!step.validation) return true
99
135
 
100
136
  setIsLoading(true)
101
137
  setError(null)
102
138
 
103
139
  try {
104
- const isValid = await step.validation()
105
- if (!isValid) {
106
- setError("Please complete all required fields before proceeding")
140
+ const result = await step.validation()
141
+
142
+ // Handle boolean return
143
+ if (typeof result === 'boolean') {
144
+ if (!result) {
145
+ setError("Please complete all required fields before proceeding")
146
+ }
147
+ return result
107
148
  }
108
- return isValid
149
+
150
+ // Handle object return {isValid: boolean, error?: string, errors?: string[]}
151
+ if (typeof result === 'object' && result !== null && 'isValid' in result) {
152
+ const validationResult = result as { isValid: boolean; error?: string; errors?: string[] }
153
+ if (!validationResult.isValid) {
154
+ if (validationResult.error) {
155
+ setError(validationResult.error)
156
+ } else if (validationResult.errors && Array.isArray(validationResult.errors)) {
157
+ setError(validationResult.errors.join(', '))
158
+ } else {
159
+ setError("Please complete all required fields before proceeding")
160
+ }
161
+ }
162
+ return validationResult.isValid
163
+ }
164
+
165
+ // Invalid return type
166
+ setError("Invalid validation response")
167
+ return false
109
168
  } catch (err) {
110
169
  setError(err instanceof Error ? err.message : "Validation failed")
111
170
  return false
@@ -35,90 +35,195 @@ export const FormWizardProgress: React.FC<FormWizardProgressProps> = ({
35
35
  if (progressType === 'linear') {
36
36
  return (
37
37
  <div className={cn(
38
- "relative",
39
- orientation === 'vertical' ? "flex flex-col space-y-8" : "flex items-center justify-between",
38
+ "relative w-full",
39
+ orientation === 'vertical' ? "flex flex-col space-y-8" : "",
40
40
  className
41
41
  )}>
42
- {steps.map((step, index) => {
43
- const isActive = index === currentStep
44
- const isCompleted = isStepCompleted(index)
45
- const isAccessible = isStepAccessible(index)
46
- const hasError = isActive && error
47
-
48
- const StepIcon = typeof step.icon === 'function'
49
- ? step.icon({ isActive, isCompleted })
50
- : step.icon
42
+ {orientation === 'horizontal' ? (
43
+ <div className="relative w-full">
44
+ {/* Steps Container with connecting lines */}
45
+ <div className="relative flex items-start justify-between w-full">
46
+ {steps.map((step, index) => {
47
+ const isActive = index === currentStep
48
+ const isCompleted = isStepCompleted(index)
49
+ const isAccessible = isStepAccessible(index)
50
+ const hasError = isActive && error
51
+
52
+ const StepIcon = typeof step.icon === 'function'
53
+ ? step.icon({ isActive, isCompleted })
54
+ : step.icon
51
55
 
52
- return (
53
- <React.Fragment key={step.id}>
54
- <div className={cn(
55
- "relative flex items-center",
56
- orientation === 'vertical' ? "w-full" : "flex-1"
57
- )}>
58
- <motion.div
59
- initial={{ scale: 0.8 }}
60
- animate={{ scale: isActive ? 1.1 : 1 }}
61
- className={cn(
62
- "relative z-20 flex items-center justify-center rounded-full border-2 transition-all duration-300",
63
- stepIconPosition === 'inside' ? "w-12 h-12" : "w-10 h-10",
64
- isActive && "border-primary bg-primary text-primary-foreground shadow-lg",
65
- isCompleted && !isActive && "border-primary bg-primary text-primary-foreground",
66
- !isActive && !isCompleted && isAccessible && "border-muted-foreground/50 bg-background text-muted-foreground hover:border-muted-foreground",
67
- !isAccessible && "border-muted bg-muted text-muted-foreground cursor-not-allowed opacity-50",
68
- hasError && "border-destructive bg-destructive text-destructive-foreground"
69
- )}
70
- >
71
- {hasError ? (
72
- errorStepIcon
73
- ) : isCompleted && !isActive ? (
74
- completedStepIcon
75
- ) : isActive && activeStepIcon ? (
76
- activeStepIcon
77
- ) : StepIcon ? (
78
- <span className="w-5 h-5 flex items-center justify-center">{StepIcon}</span>
79
- ) : showStepNumbers ? (
80
- <span className="text-sm font-medium">{index + 1}</span>
81
- ) : (
82
- <Circle className="w-4 h-4" />
83
- )}
84
- </motion.div>
56
+ return (
57
+ <div key={step.id} className="relative flex-1 flex flex-col items-center">
58
+ {/* Step Title & Description - Above the circle */}
59
+ {showStepTitles && (
60
+ <div className="text-center mb-3 min-h-[40px] px-2">
61
+ <p className={cn(
62
+ "text-sm font-semibold transition-colors",
63
+ isActive && "text-primary",
64
+ isCompleted && !isActive && "text-primary",
65
+ !isActive && !isCompleted && "text-muted-foreground"
66
+ )}>
67
+ {step.title}
68
+ </p>
69
+ {step.description && (
70
+ <p className="text-xs text-muted-foreground mt-1">
71
+ {step.description}
72
+ </p>
73
+ )}
74
+ </div>
75
+ )}
76
+
77
+ {/* Step Circle Container with line */}
78
+ <div className="relative flex items-center w-full">
79
+ {/* Connecting Line - Positioned after each circle except the last */}
80
+ {index < steps.length - 1 && (
81
+ <div
82
+ className="absolute left-1/2 w-full h-[2px]"
83
+ style={{
84
+ width: 'calc(100% + 24px)',
85
+ marginLeft: '24px'
86
+ }}
87
+ >
88
+ <div className="h-full bg-gray-200 dark:bg-gray-700" />
89
+ {index < currentStep && (
90
+ <motion.div
91
+ className="absolute inset-0 bg-primary"
92
+ initial={{ scaleX: 0 }}
93
+ animate={{ scaleX: 1 }}
94
+ transition={{ duration: 0.5, delay: index * 0.1 }}
95
+ style={{ transformOrigin: 'left' }}
96
+ />
97
+ )}
98
+ </div>
99
+ )}
100
+
101
+ {/* Step Circle */}
102
+ <motion.div
103
+ initial={{ scale: 0.8 }}
104
+ animate={{ scale: isActive ? 1.05 : 1 }}
105
+ transition={{ duration: 0.2 }}
106
+ className={cn(
107
+ "relative z-10 flex items-center justify-center rounded-full transition-all duration-300 mx-auto",
108
+ "w-12 h-12 border-2",
109
+ isActive && "border-primary bg-primary text-primary-foreground shadow-lg shadow-primary/20",
110
+ isCompleted && !isActive && "border-primary bg-primary text-primary-foreground",
111
+ !isActive && !isCompleted && isAccessible && "border-gray-300 bg-background text-gray-500 hover:border-gray-400 dark:border-gray-600 dark:text-gray-400",
112
+ !isAccessible && "border-gray-200 bg-gray-50 text-gray-400 cursor-not-allowed dark:border-gray-700 dark:bg-gray-900 dark:text-gray-600",
113
+ hasError && "border-destructive bg-destructive text-destructive-foreground"
114
+ )}
115
+ >
116
+ {hasError ? (
117
+ errorStepIcon
118
+ ) : isCompleted && !isActive ? (
119
+ completedStepIcon
120
+ ) : isActive && activeStepIcon ? (
121
+ activeStepIcon
122
+ ) : StepIcon ? (
123
+ <span className="w-5 h-5 flex items-center justify-center">{StepIcon}</span>
124
+ ) : showStepNumbers ? (
125
+ <span className="text-sm font-semibold">{index + 1}</span>
126
+ ) : (
127
+ <Circle className="w-4 h-4" />
128
+ )}
129
+ </motion.div>
130
+ </div>
85
131
 
86
- {showStepTitles && (
87
- <div className={cn(
88
- "absolute whitespace-nowrap",
89
- orientation === 'vertical'
90
- ? "left-16 top-1/2 -translate-y-1/2"
91
- : "top-full mt-2 left-1/2 -translate-x-1/2 text-center"
92
- )}>
93
- <p className={cn(
94
- "text-sm font-medium transition-colors",
95
- isActive && "text-primary",
96
- isCompleted && !isActive && "text-primary",
97
- !isActive && !isCompleted && "text-muted-foreground"
98
- )}>
99
- {step.title}
100
- </p>
101
- {step.description && (
102
- <p className="text-xs text-muted-foreground mt-1">
103
- {step.description}
132
+ {/* Step Label Below Circle */}
133
+ <div className="mt-3">
134
+ <p className="text-xs text-muted-foreground font-medium">
135
+ Step {index + 1} of {steps.length}
104
136
  </p>
105
- )}
137
+ </div>
106
138
  </div>
107
- )}
139
+ )
140
+ })}
141
+ </div>
142
+ </div>
143
+ ) : (
144
+ // Vertical orientation
145
+ <div className="relative pl-12">
146
+ {/* Vertical Progress Line */}
147
+ {steps.length > 1 && (
148
+ <>
149
+ <div className="absolute left-6 top-8 bottom-8 w-[2px] bg-gray-200 dark:bg-gray-700" />
150
+ <motion.div
151
+ className="absolute left-6 top-8 w-[2px] bg-primary"
152
+ initial={{ height: 0 }}
153
+ animate={{
154
+ height: `${(currentStep / (steps.length - 1)) * (100 - 16)}%`
155
+ }}
156
+ transition={{ duration: 0.5 }}
157
+ />
158
+ </>
159
+ )}
108
160
 
109
- {index < steps.length - 1 && (
110
- <div className={cn(
111
- "absolute transition-all duration-500",
112
- orientation === 'vertical'
113
- ? "top-12 left-5 w-0.5 h-8"
114
- : "left-12 right-0 top-1/2 -translate-y-1/2 h-0.5",
115
- isCompleted ? "bg-primary" : "bg-muted"
116
- )} />
117
- )}
118
- </div>
119
- </React.Fragment>
120
- )
121
- })}
161
+ {steps.map((step, index) => {
162
+ const isActive = index === currentStep
163
+ const isCompleted = isStepCompleted(index)
164
+ const isAccessible = isStepAccessible(index)
165
+ const hasError = isActive && error
166
+
167
+ const StepIcon = typeof step.icon === 'function'
168
+ ? step.icon({ isActive, isCompleted })
169
+ : step.icon
170
+
171
+ return (
172
+ <div key={step.id} className="relative flex items-center mb-8 last:mb-0">
173
+ {/* Step Circle */}
174
+ <motion.div
175
+ initial={{ scale: 0.8 }}
176
+ animate={{ scale: isActive ? 1.05 : 1 }}
177
+ className={cn(
178
+ "absolute left-0 z-20 flex items-center justify-center rounded-full border-2 transition-all duration-300",
179
+ "w-12 h-12",
180
+ isActive && "border-primary bg-primary text-primary-foreground shadow-lg",
181
+ isCompleted && !isActive && "border-primary bg-primary text-primary-foreground",
182
+ !isActive && !isCompleted && isAccessible && "border-gray-300 bg-background text-gray-500 hover:border-gray-400",
183
+ !isAccessible && "border-gray-200 bg-gray-50 text-gray-400 cursor-not-allowed",
184
+ hasError && "border-destructive bg-destructive text-destructive-foreground"
185
+ )}
186
+ >
187
+ {hasError ? (
188
+ errorStepIcon
189
+ ) : isCompleted && !isActive ? (
190
+ completedStepIcon
191
+ ) : isActive && activeStepIcon ? (
192
+ activeStepIcon
193
+ ) : StepIcon ? (
194
+ <span className="w-5 h-5 flex items-center justify-center">{StepIcon}</span>
195
+ ) : showStepNumbers ? (
196
+ <span className="text-sm font-semibold">{index + 1}</span>
197
+ ) : (
198
+ <Circle className="w-4 h-4" />
199
+ )}
200
+ </motion.div>
201
+
202
+ {/* Step Content */}
203
+ <div className="ml-16">
204
+ {showStepTitles && (
205
+ <>
206
+ <p className={cn(
207
+ "text-sm font-semibold transition-colors",
208
+ isActive && "text-primary",
209
+ isCompleted && !isActive && "text-primary",
210
+ !isActive && !isCompleted && "text-muted-foreground"
211
+ )}>
212
+ {step.title}
213
+ </p>
214
+ {step.description && (
215
+ <p className="text-xs text-muted-foreground mt-1">
216
+ {step.description}
217
+ </p>
218
+ )}
219
+ </>
220
+ )}
221
+ </div>
222
+ </div>
223
+ )
224
+ })}
225
+ </div>
226
+ )}
122
227
  </div>
123
228
  )
124
229
  }
@@ -150,25 +255,25 @@ export const FormWizardProgress: React.FC<FormWizardProgressProps> = ({
150
255
 
151
256
  if (progressType === 'circular') {
152
257
  const progress = ((currentStep + 1) / steps.length) * 100
153
- const circumference = 2 * Math.PI * 40 // radius = 40
258
+ const circumference = 2 * Math.PI * 45 // radius = 45
154
259
  const strokeDashoffset = circumference - (progress / 100) * circumference
155
260
 
156
261
  return (
157
- <div className={cn("relative w-32 h-32", className)}>
158
- <svg className="w-full h-full -rotate-90">
262
+ <div className={cn("relative w-36 h-36 mx-auto", className)}>
263
+ <svg className="w-full h-full -rotate-90" viewBox="0 0 144 144">
159
264
  <circle
160
- cx="64"
161
- cy="64"
162
- r="40"
265
+ cx="72"
266
+ cy="72"
267
+ r="45"
163
268
  fill="none"
164
269
  stroke="currentColor"
165
270
  strokeWidth="8"
166
- className="text-muted"
271
+ className="text-gray-200 dark:text-gray-700"
167
272
  />
168
273
  <motion.circle
169
- cx="64"
170
- cy="64"
171
- r="40"
274
+ cx="72"
275
+ cy="72"
276
+ r="45"
172
277
  fill="none"
173
278
  stroke="currentColor"
174
279
  strokeWidth="8"
@@ -181,7 +286,7 @@ export const FormWizardProgress: React.FC<FormWizardProgressProps> = ({
181
286
  </svg>
182
287
  <div className="absolute inset-0 flex items-center justify-center">
183
288
  <div className="text-center">
184
- <p className="text-2xl font-bold">{currentStep + 1}</p>
289
+ <p className="text-3xl font-bold">{currentStep + 1}</p>
185
290
  <p className="text-sm text-muted-foreground">of {steps.length}</p>
186
291
  </div>
187
292
  </div>
@@ -5,6 +5,8 @@ import { motion, AnimatePresence } from "framer-motion"
5
5
  import { cn } from "../../lib/utils"
6
6
  import { useFormWizard } from "./form-wizard-context"
7
7
  import { FormWizardProps } from "./types"
8
+ import { Alert, AlertDescription } from "../ui/alert"
9
+ import { AlertCircle } from "lucide-react"
8
10
 
9
11
  interface FormWizardStepProps {
10
12
  className?: string
@@ -49,7 +51,7 @@ export const FormWizardStep: React.FC<FormWizardStepProps> = ({
49
51
  animationType = 'slide',
50
52
  animationDuration = 0.3
51
53
  }) => {
52
- const { steps, currentStep, goToNext, goToPrevious, goToStep, updateStepData, stepData } = useFormWizard()
54
+ const { steps, currentStep, goToNext, goToPrevious, goToStep, updateStepData, stepData, error } = useFormWizard()
53
55
  const [direction, setDirection] = React.useState(0)
54
56
  const previousStep = React.useRef(currentStep)
55
57
 
@@ -92,8 +94,17 @@ export const FormWizardStep: React.FC<FormWizardStepProps> = ({
92
94
  ease: "easeInOut"
93
95
  }}
94
96
  className={cn("w-full", className)}
97
+ data-wizard-step-content
95
98
  >
96
- {content}
99
+ <div className="space-y-4">
100
+ {error && (
101
+ <Alert variant="error">
102
+ <AlertCircle className="h-4 w-4" />
103
+ <AlertDescription>{error}</AlertDescription>
104
+ </Alert>
105
+ )}
106
+ {content}
107
+ </div>
97
108
  </motion.div>
98
109
  </AnimatePresence>
99
110
  )
@@ -59,7 +59,7 @@ export const MoonUIFormWizardPro = React.forwardRef<HTMLDivElement, FormWizardPr
59
59
  {showProgressBar && (
60
60
  <>
61
61
  <FormWizardProgress
62
- className={progressClassName}
62
+ className={cn("mb-12", progressClassName)}
63
63
  progressType={progressType}
64
64
  orientation={orientation}
65
65
  showStepNumbers={showStepNumbers}
@@ -69,7 +69,6 @@ export const MoonUIFormWizardPro = React.forwardRef<HTMLDivElement, FormWizardPr
69
69
  activeStepIcon={activeStepIcon}
70
70
  errorStepIcon={errorStepIcon}
71
71
  />
72
- <Separator className="my-8" />
73
72
  </>
74
73
  )}
75
74
 
@@ -83,10 +82,8 @@ export const MoonUIFormWizardPro = React.forwardRef<HTMLDivElement, FormWizardPr
83
82
  </CardContent>
84
83
  </Card>
85
84
 
86
- <Separator className="my-8" />
87
-
88
85
  <FormWizardNavigation
89
- className={navigationClassName}
86
+ className={cn("mt-8", navigationClassName)}
90
87
  />
91
88
  </div>
92
89
  </FormWizardProvider>