@algodomain/smart-forms 0.1.9 → 0.1.10

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/README.md CHANGED
@@ -220,6 +220,7 @@ Multi-step form with tabs and progress tracking.
220
220
  |------|------|---------|-------------|
221
221
  | `animateTabs` | `boolean` | `false` | Enable tab transition animations |
222
222
  | `tabType` | `'underline' \| 'default'` | `'default'` | Tab styling variant |
223
+ | `disableManualTabSwitch` | `boolean` | `false` | Prevent manual clicking on future tabs. Users can still navigate forward via Next button and go back to previous tabs. |
223
224
 
224
225
  **Tab Component Props:**
225
226
 
@@ -557,6 +558,7 @@ All form components (`SmartForm`, `MultiTabSmartForm`, `BaseSmartForm`) support
557
558
  | `showTabNumbers` | `boolean` | `true` | Show tab numbers (MultiTab only) |
558
559
  | `animateTabs` | `boolean` | `false` | Enable tab animations (MultiTab only) |
559
560
  | `tabType` | `'underline' \| 'default'` | `'default'` | Tab styling variant (MultiTab only) |
561
+ | `disableManualTabSwitch` | `boolean` | `false` | Prevent manual clicking on future tabs (MultiTab only) |
560
562
  | `authentication` | `AuthenticationConfig` | - | Authentication configuration |
561
563
  | `includeQueryParams` | `boolean` | `false` | Include URL query parameters |
562
564
  | `queryParamsToInclude` | `string[]` | - | Filter specific query params |
@@ -678,6 +680,47 @@ All field components support these props:
678
680
 
679
681
  ## 🚀 Advanced Features
680
682
 
683
+ ### Disable Manual Tab Switching
684
+
685
+ Control whether users can manually click on future tabs. When enabled, users must complete each step sequentially using the Next button, but can still go back to previous tabs.
686
+
687
+ **Usage:**
688
+
689
+ ```tsx
690
+ <MultiTabSmartForm
691
+ api="/api/submit"
692
+ disableManualTabSwitch={true}
693
+ >
694
+ <Tab title="Step 1">
695
+ <SmartInput field="name" label="Name" required />
696
+ </Tab>
697
+
698
+ <Tab title="Step 2">
699
+ <SmartInput field="email" label="Email" required />
700
+ </Tab>
701
+
702
+ <Tab title="Step 3">
703
+ <SmartInput field="phone" label="Phone" required />
704
+ </Tab>
705
+ </MultiTabSmartForm>
706
+ ```
707
+
708
+ **Behavior:**
709
+ - When `disableManualTabSwitch={true}`:
710
+ - Users **cannot** click on tabs ahead of the current tab
711
+ - Future tabs appear disabled (grayed out)
712
+ - Users **can** click on previous tabs to go back
713
+ - Users **can** navigate forward using the Next button (after validation)
714
+ - When `disableManualTabSwitch={false}` (default):
715
+ - Users can freely click on any tab
716
+ - All tabs are clickable
717
+
718
+ **Use Cases:**
719
+ - Enforce sequential completion of multi-step forms
720
+ - Ensure users complete each step before proceeding
721
+ - Prevent skipping validation steps
722
+ - Guide users through a structured workflow
723
+
681
724
  ### Tab Callbacks and Processing Overlays
682
725
 
683
726
  In MultiTabSmartForm, you can add custom logic before moving to the next tab using the `onNext` callback. This is useful for async operations like API calls or data processing.
@@ -1732,6 +1775,7 @@ export function JobApplicationForm() {
1732
1775
  showTabNumbers={true}
1733
1776
  animateTabs={true}
1734
1777
  tabType="underline"
1778
+ disableManualTabSwitch={true}
1735
1779
  allowSaveDraft={true}
1736
1780
  enableLocalStorage={true}
1737
1781
  storageKey="job-application"
package/dist/index.cjs CHANGED
@@ -79,7 +79,7 @@ var BaseSmartForm = ({
79
79
  onSuccess,
80
80
  onError,
81
81
  transformData,
82
- className = "max-w-2xl mx-auto p-6 bg-white",
82
+ className = "p-6 bg-white",
83
83
  title,
84
84
  subTitle,
85
85
  logo,
@@ -510,16 +510,16 @@ function MotionHighlightItem({
510
510
  };
511
511
  const commonHandlers = hover ? {
512
512
  onMouseEnter: (e) => {
513
- setActiveValue(childValue);
513
+ if (!isDisabled) setActiveValue(childValue);
514
514
  element.props.onMouseEnter?.(e);
515
515
  },
516
516
  onMouseLeave: (e) => {
517
- setActiveValue(null);
517
+ if (!isDisabled) setActiveValue(null);
518
518
  element.props.onMouseLeave?.(e);
519
519
  }
520
520
  } : {
521
521
  onClick: (e) => {
522
- setActiveValue(childValue);
522
+ if (!isDisabled) setActiveValue(childValue);
523
523
  element.props.onClick?.(e);
524
524
  }
525
525
  };
@@ -879,16 +879,18 @@ function TabsTrigger({
879
879
  onAnimationEnd,
880
880
  onAnimationIteration,
881
881
  onTransitionEnd,
882
+ disabled,
882
883
  ...motionButtonProps
883
884
  } = props;
884
- return /* @__PURE__ */ jsxRuntime.jsx(MotionHighlightItem, { value: props.value, className: "size-full", children: /* @__PURE__ */ jsxRuntime.jsx(
885
+ return /* @__PURE__ */ jsxRuntime.jsx(MotionHighlightItem, { value: props.value, className: "size-full", disabled, children: /* @__PURE__ */ jsxRuntime.jsx(
885
886
  react.motion.button,
886
887
  {
887
888
  ref: localRef,
888
889
  "data-slot": "tabs-trigger",
889
890
  role: "tab",
890
- onClick: () => handleValueChange(props.value),
891
+ onClick: () => !disabled && handleValueChange(props.value),
891
892
  "data-state": activeValue === props.value ? "active" : "inactive",
893
+ disabled,
892
894
  className: chunkWUYS7DMR_cjs.cn(
893
895
  "ring-offset-background focus-visible:ring-ring data-[state=active]:text-foreground z-10 inline-flex size-full cursor-pointer items-center justify-center rounded-sm px-2 py-1 text-sm font-medium whitespace-nowrap transition-transform focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50",
894
896
  className
@@ -1083,6 +1085,7 @@ var MultiTabSmartForm = ({
1083
1085
  const [externalTabFields, setExternalTabFields] = React3.useState({});
1084
1086
  const [processingTab, setProcessingTab] = React3.useState(null);
1085
1087
  const [processingError, setProcessingError] = React3.useState(null);
1088
+ const [maxUnlockedTab, setMaxUnlockedTab] = React3.useState(0);
1086
1089
  const { footerChildren, regularChildren } = React3.useMemo(() => {
1087
1090
  const childArray = React3__namespace.default.Children.toArray(children);
1088
1091
  const footerChildren2 = [];
@@ -1199,6 +1202,8 @@ var MultiTabSmartForm = ({
1199
1202
  processingError,
1200
1203
  setProcessingError,
1201
1204
  disableManualTabSwitch,
1205
+ maxUnlockedTab,
1206
+ setMaxUnlockedTab,
1202
1207
  children: regularChildren
1203
1208
  }
1204
1209
  ) }) });
@@ -1224,7 +1229,9 @@ var MultiTabFormContent = ({
1224
1229
  setProcessingTab,
1225
1230
  processingError,
1226
1231
  setProcessingError,
1227
- disableManualTabSwitch = false
1232
+ disableManualTabSwitch = false,
1233
+ maxUnlockedTab,
1234
+ setMaxUnlockedTab
1228
1235
  }) => {
1229
1236
  const { isLoading, isDraftSaving, submitForm, saveDraft, resetForm, validateFields, formData, validationRegistry, setErrors } = chunkWUYS7DMR_cjs.useSmartForm();
1230
1237
  const debounce = (func, wait) => {
@@ -1288,11 +1295,11 @@ var MultiTabFormContent = ({
1288
1295
  checkTabCompletion();
1289
1296
  }, [formData, validationRegistry, getCombinedTabFields, tabs, setCompletedTabs, setValidationErrorTabs]);
1290
1297
  const handleTabChangeWithErrorCheck = React3.useCallback((index) => {
1291
- if (disableManualTabSwitch && index > activeTab) {
1298
+ if (disableManualTabSwitch && index > maxUnlockedTab) {
1292
1299
  return;
1293
1300
  }
1294
1301
  onTabChange(index);
1295
- }, [onTabChange, disableManualTabSwitch, activeTab]);
1302
+ }, [onTabChange, disableManualTabSwitch, maxUnlockedTab]);
1296
1303
  const handleNextWithValidation = React3.useCallback(async () => {
1297
1304
  const currentTabFields = getCombinedTabFields(activeTab);
1298
1305
  if (currentTabFields.length > 0) {
@@ -1329,6 +1336,7 @@ var MultiTabFormContent = ({
1329
1336
  }
1330
1337
  setProcessingTab(null);
1331
1338
  setProcessingError(null);
1339
+ setMaxUnlockedTab((prev) => Math.max(prev, activeTab + 1));
1332
1340
  onNext();
1333
1341
  } catch (error) {
1334
1342
  setProcessingTab(null);
@@ -1336,6 +1344,7 @@ var MultiTabFormContent = ({
1336
1344
  setProcessingError(errorMessage);
1337
1345
  }
1338
1346
  } else {
1347
+ setMaxUnlockedTab((prev) => Math.max(prev, activeTab + 1));
1339
1348
  onNext();
1340
1349
  }
1341
1350
  } else {
@@ -1369,6 +1378,7 @@ var MultiTabFormContent = ({
1369
1378
  }
1370
1379
  setProcessingTab(null);
1371
1380
  setProcessingError(null);
1381
+ setMaxUnlockedTab((prev) => Math.max(prev, activeTab + 1));
1372
1382
  onNext();
1373
1383
  } catch (error) {
1374
1384
  setProcessingTab(null);
@@ -1376,10 +1386,11 @@ var MultiTabFormContent = ({
1376
1386
  setProcessingError(errorMessage);
1377
1387
  }
1378
1388
  } else {
1389
+ setMaxUnlockedTab((prev) => Math.max(prev, activeTab + 1));
1379
1390
  onNext();
1380
1391
  }
1381
1392
  }
1382
- }, [activeTab, getCombinedTabFields, validateFields, onNext, setCompletedTabs, setValidationErrorTabs, validationRegistry, formData, tabCallbacks, setProcessingTab, setProcessingError]);
1393
+ }, [activeTab, getCombinedTabFields, validateFields, onNext, setCompletedTabs, setValidationErrorTabs, validationRegistry, formData, tabCallbacks, setProcessingTab, setProcessingError, setMaxUnlockedTab]);
1383
1394
  const handleSubmitWithValidation = React3.useCallback(async () => {
1384
1395
  const allErrors = {};
1385
1396
  let isValid = true;
@@ -1443,8 +1454,8 @@ var MultiTabFormContent = ({
1443
1454
  animate: animateTabs,
1444
1455
  type: tabType,
1445
1456
  children: [
1446
- /* @__PURE__ */ jsxRuntime.jsx(TabsList, { className: `grid w-full mb-8`, style: { gridTemplateColumns: `repeat(${tabs.length}, 1fr)` }, children: tabs.map((tab, index) => {
1447
- const isFutureTab = disableManualTabSwitch && index > activeTab;
1457
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full mb-8", children: /* @__PURE__ */ jsxRuntime.jsx(TabsList, { className: `grid w-full`, style: { gridTemplateColumns: `repeat(${tabs.length}, 1fr)` }, children: tabs.map((tab, index) => {
1458
+ const isFutureTab = disableManualTabSwitch && index > maxUnlockedTab;
1448
1459
  return /* @__PURE__ */ jsxRuntime.jsxs(
1449
1460
  TabsTrigger,
1450
1461
  {
@@ -1463,7 +1474,7 @@ var MultiTabFormContent = ({
1463
1474
  },
1464
1475
  tab
1465
1476
  );
1466
- }) }),
1477
+ }) }) }),
1467
1478
  React3__namespace.default.Children.map(children, (child, index) => {
1468
1479
  if (React3__namespace.default.isValidElement(child) && child.type === Tab) {
1469
1480
  const tabProps = child.props;