@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 +44 -0
- package/dist/index.cjs +24 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +24 -13
- package/dist/index.js.map +1 -1
- package/dist/style.css +2 -2
- package/package.json +1 -1
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 = "
|
|
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 >
|
|
1298
|
+
if (disableManualTabSwitch && index > maxUnlockedTab) {
|
|
1292
1299
|
return;
|
|
1293
1300
|
}
|
|
1294
1301
|
onTabChange(index);
|
|
1295
|
-
}, [onTabChange, disableManualTabSwitch,
|
|
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
|
|
1447
|
-
const isFutureTab = disableManualTabSwitch && index >
|
|
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;
|