@skalfa/skalfa-component 1.0.22 → 1.0.24

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.
Files changed (32) hide show
  1. package/dist/input/Input.component.js +1 -1
  2. package/dist/input/InputCheckbox.component.js +1 -1
  3. package/dist/input/InputCurrency.component.js +1 -1
  4. package/dist/input/InputDate.component.js +1 -1
  5. package/dist/input/InputDatetime.component.js +1 -1
  6. package/dist/input/InputDocument.component.js +1 -1
  7. package/dist/input/InputImage.component.js +1 -1
  8. package/dist/input/InputNumber.component.js +1 -1
  9. package/dist/input/InputPassword.component.js +1 -1
  10. package/dist/input/InputRadio.component.js +1 -1
  11. package/dist/input/InputTime.component.js +1 -1
  12. package/dist/input/Select.component.js +1 -1
  13. package/dist/supervision/FormSupervision.component.d.ts +16 -1
  14. package/dist/supervision/FormSupervision.component.js +129 -10
  15. package/dist/supervision/TableSupervision.component.js +2 -2
  16. package/dist/table/Table.component.js +1 -1
  17. package/package.json +1 -1
  18. package/src/input/Input.component.tsx +1 -1
  19. package/src/input/InputCheckbox.component.tsx +1 -1
  20. package/src/input/InputCurrency.component.tsx +1 -1
  21. package/src/input/InputDate.component.tsx +1 -1
  22. package/src/input/InputDatetime.component.tsx +1 -1
  23. package/src/input/InputDocument.component.tsx +1 -1
  24. package/src/input/InputImage.component.tsx +1 -1
  25. package/src/input/InputNumber.component.tsx +1 -1
  26. package/src/input/InputPassword.component.tsx +1 -1
  27. package/src/input/InputRadio.component.tsx +1 -1
  28. package/src/input/InputTime.component.tsx +1 -1
  29. package/src/input/Select.component.tsx +1 -1
  30. package/src/supervision/FormSupervision.component.tsx +196 -32
  31. package/src/supervision/TableSupervision.component.tsx +2 -2
  32. package/src/table/Table.component.tsx +1 -1
@@ -15,7 +15,7 @@ function InputComponent({ label, tip, leftIcon, rightIcon, className = "", value
15
15
  // =========================>
16
16
  // ## Initial
17
17
  // =========================>
18
- const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, props.type == "file");
18
+ const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, props.type == "file", unregister);
19
19
  const randomId = (0, _utils_1.useInputRandomId)();
20
20
  // =========================>
21
21
  // ## Invalid handler
@@ -14,7 +14,7 @@ function InputCheckboxComponent({ name, label, tip, vertical, className = "", cl
14
14
  // =========================>
15
15
  // ## initial
16
16
  // =========================>
17
- const inputHandler = (0, _utils_1.useInputHandler)(name, value, validations, register, false);
17
+ const inputHandler = (0, _utils_1.useInputHandler)(name, value, validations, register, false, unregister);
18
18
  // =========================>
19
19
  // ## Invalid handler
20
20
  // =========================>
@@ -9,7 +9,7 @@ function InputCurrencyComponent({ label, tip, leftIcon, rightIcon, value, invali
9
9
  // =========================>
10
10
  // ## Initial
11
11
  // =========================>
12
- const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, false);
12
+ const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, false, unregister);
13
13
  const randomId = (0, _utils_1.useInputRandomId)();
14
14
  // =========================>
15
15
  // ## Invalid handler
@@ -19,7 +19,7 @@ function InputDateComponent({ label, tip, leftIcon, rightIcon, value, invalid, v
19
19
  // =========================>
20
20
  // ## Initial
21
21
  // =========================>
22
- const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, false);
22
+ const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, false, unregister);
23
23
  const randomId = (0, _utils_1.useInputRandomId)();
24
24
  // =========================>
25
25
  // ## Invalid handler
@@ -20,7 +20,7 @@ function InputDatetimeComponent({ label, tip, leftIcon, rightIcon, value, invali
20
20
  // =========================>
21
21
  // ## Initial
22
22
  // =========================>
23
- const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, false);
23
+ const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, false, unregister);
24
24
  const randomId = (0, _utils_1.useInputRandomId)();
25
25
  // =========================>
26
26
  // ## Invalid handler
@@ -23,7 +23,7 @@ function InputDocumentComponent({ label, tip, leftIcon, rightIcon, className = "
23
23
  // =========================>
24
24
  // ## Initial
25
25
  // =========================>
26
- const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, props.type == "file");
26
+ const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, props.type == "file", unregister);
27
27
  const randomId = (0, _utils_1.useInputRandomId)();
28
28
  // =========================>
29
29
  // ## Invalid handler
@@ -17,7 +17,7 @@ const InputImageComponent = ({ name, label, tip, value, disabled, aspect = "1/1"
17
17
  const [drag, setDrag] = (0, react_1.useState)(false);
18
18
  const [cropSrc, setCropSrc] = (0, react_1.useState)(null);
19
19
  const [openCrop, setOpenCrop] = (0, react_1.useState)(false);
20
- const inputHandler = (0, _utils_1.useInputHandler)(name, value, validations, register, true);
20
+ const inputHandler = (0, _utils_1.useInputHandler)(name, value, validations, register, true, unregister);
21
21
  const [invalidMessage, setInvalidMessage] = (0, _utils_1.useValidation)(inputHandler.value, validations, invalid, inputHandler.idle);
22
22
  (0, react_1.useEffect)(() => {
23
23
  if (value) {
@@ -9,7 +9,7 @@ function InputNumberComponent({ label, tip, leftIcon, rightIcon, value, invalid,
9
9
  // =========================>
10
10
  // ## Initial
11
11
  // =========================>
12
- const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, false);
12
+ const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, false, unregister);
13
13
  const randomId = (0, _utils_1.useInputRandomId)();
14
14
  // =========================>
15
15
  // ## Invalid handler
@@ -15,7 +15,7 @@ function InputPasswordComponent({ label, tip, leftIcon, rightIcon, value, invali
15
15
  // =========================>
16
16
  // ## Initial
17
17
  // =========================>
18
- const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, false);
18
+ const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, false, unregister);
19
19
  const randomId = (0, _utils_1.useInputRandomId)();
20
20
  const randomConfirmId = (0, _utils_1.useInputRandomId)();
21
21
  // =========================>
@@ -12,7 +12,7 @@ function InputRadioComponent({ name, label, tip, vertical, value, disabled, inva
12
12
  // =========================>
13
13
  // ## Initial
14
14
  // =========================>
15
- const inputHandler = (0, _utils_1.useInputHandler)(name, value, validations, register, false);
15
+ const inputHandler = (0, _utils_1.useInputHandler)(name, value, validations, register, false, unregister);
16
16
  // =========================>
17
17
  // ## Invalid handler
18
18
  // =========================>
@@ -15,7 +15,7 @@ function InputTimeComponent({ label, tip, leftIcon, rightIcon, value, invalid, v
15
15
  // =========================>
16
16
  // ## Initial
17
17
  // =========================>
18
- const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, false);
18
+ const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, false, unregister);
19
19
  const randomId = (0, _utils_1.useInputRandomId)();
20
20
  // =========================>
21
21
  // ## Invalid handler
@@ -21,7 +21,7 @@ function SelectComponent({ name, label, placeholder, tip, leftIcon, rightIcon, v
21
21
  // =========================>
22
22
  // ## Initial
23
23
  // =========================>
24
- const inputHandler = (0, _utils_1.useInputHandler)(name, value, validations, register, false);
24
+ const inputHandler = (0, _utils_1.useInputHandler)(name, value, validations, register, false, unregister);
25
25
  const randomId = (0, _utils_1.useInputRandomId)();
26
26
  // =========================>
27
27
  // ## Invalid handler
@@ -24,6 +24,7 @@ type ClusterConstruction = {
24
24
  tip: string;
25
25
  fields: FormType[];
26
26
  wrap: boolean;
27
+ min?: number;
27
28
  /** Use custom class with: "label::", "tip::", "error::", "icon::", "suggest::", "suggest-item::". */
28
29
  className: string;
29
30
  };
@@ -44,12 +45,26 @@ type ConstructionMap = {
44
45
  custom: formCustomConstructionProps;
45
46
  };
46
47
  type TypeKeys = keyof ConstructionMap;
48
+ export type WatchContext = {
49
+ values: Record<string, any>;
50
+ self: string;
51
+ prev: WatchAction;
52
+ };
53
+ export type WatchAction = {
54
+ disabled?: boolean;
55
+ hidden?: boolean;
56
+ value?: any;
57
+ required?: boolean;
58
+ readonly?: boolean;
59
+ reset?: boolean;
60
+ };
47
61
  export interface FormType<T extends TypeKeys = keyof ConstructionMap> {
48
62
  col?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | string;
49
63
  className?: string;
50
64
  construction?: ConstructionMap[T];
51
65
  type?: T;
52
- onHide?: (values: any) => boolean;
66
+ onHide?: (values: Record<string, any>) => boolean;
67
+ onWatch?: (ctx: WatchContext) => WatchAction | undefined;
53
68
  }
54
69
  export interface formSupervisionProps {
55
70
  title?: string;
@@ -13,7 +13,9 @@ function FormSupervisionComponent({ title, fields, submitControl, confirmation,
13
13
  const [modal, setModal] = (0, react_1.useState)(false);
14
14
  const [fresh, setFresh] = (0, react_1.useState)(true);
15
15
  const [mapGroups, setMapGroups] = (0, react_1.useState)({});
16
- const [{ formControl, setRegister, values, setValues, errors, setErrors, setDefaultValues, submit, loading, confirm, },] = (0, _utils_1.useForm)(submitControl, payload, confirmation, (data) => {
16
+ const [watchState, setWatchState] = (0, react_1.useState)({});
17
+ const watchRef = (0, react_1.useRef)({});
18
+ const [{ formControl, setRegister, setUnregister, values, setValues, errors, setErrors, setDefaultValues, submit, loading, confirm, },] = (0, _utils_1.useForm)(submitControl, payload, confirmation, (data) => {
17
19
  onSuccess?.(data);
18
20
  setModal("success");
19
21
  setTimeout(() => setModal(false), 1000);
@@ -25,6 +27,82 @@ function FormSupervisionComponent({ title, fields, submitControl, confirmation,
25
27
  else
26
28
  setModal("failed");
27
29
  });
30
+ // ==============================>
31
+ // ## Watch: collect watchers from fields
32
+ // ==============================>
33
+ const collectWatchers = (fieldList, prefix) => {
34
+ const result = [];
35
+ for (const f of fieldList) {
36
+ const inputType = f.type || "default";
37
+ const name = prefix ? `${prefix}.${f.construction?.name}` : f.construction?.name || "";
38
+ if (inputType === "cluster") {
39
+ const cluster = f.construction;
40
+ const groupKey = prefix ? `${prefix}.${cluster.name}` : cluster.name;
41
+ const group = mapGroups[groupKey] || [];
42
+ for (const gIndex of group) {
43
+ result.push(...collectWatchers(cluster.fields, `${cluster.name}[${gIndex}]`));
44
+ }
45
+ }
46
+ else if (f.onWatch) {
47
+ result.push({ name, onWatch: f.onWatch, construction: f.construction });
48
+ }
49
+ }
50
+ return result;
51
+ };
52
+ // ==============================>
53
+ // ## Watch: execute watchers on value change
54
+ // ==============================>
55
+ (0, react_1.useEffect)(() => {
56
+ const watchers = collectWatchers(fields);
57
+ if (watchers.length === 0) {
58
+ if (Object.keys(watchRef.current).length > 0) {
59
+ watchRef.current = {};
60
+ setWatchState({});
61
+ }
62
+ return;
63
+ }
64
+ const valMap = {};
65
+ values.forEach((v) => { valMap[v.name] = v.value; });
66
+ const nextState = {};
67
+ const valueUpdates = [];
68
+ for (const { name, onWatch, construction } of watchers) {
69
+ const prev = watchRef.current[name] || {};
70
+ const action = onWatch({ values: valMap, self: name, prev });
71
+ if (!action)
72
+ continue;
73
+ nextState[name] = action;
74
+ if (action.hidden && !prev.hidden)
75
+ setUnregister(name);
76
+ if (action.required !== prev.required) {
77
+ const baseValidations = Array.isArray(construction?.validations) ? [...construction.validations] : [];
78
+ const newValidations = action.required ? (baseValidations.includes("required") ? baseValidations : [...baseValidations, "required"]) : baseValidations.filter((v) => v !== "required");
79
+ setRegister({ name, validations: newValidations });
80
+ }
81
+ if (action.reset) {
82
+ const cur = valMap[name];
83
+ if (cur != null && cur !== "")
84
+ valueUpdates.push({ name, value: "" });
85
+ }
86
+ else if (action.value !== undefined && action.value !== valMap[name]) {
87
+ valueUpdates.push({ name, value: action.value });
88
+ }
89
+ }
90
+ if (JSON.stringify(watchRef.current) !== JSON.stringify(nextState)) {
91
+ watchRef.current = nextState;
92
+ setWatchState(nextState);
93
+ }
94
+ if (valueUpdates.length > 0) {
95
+ const merged = [...values];
96
+ for (const upd of valueUpdates) {
97
+ const idx = merged.findIndex(v => v.name === upd.name);
98
+ if (idx >= 0)
99
+ merged[idx] = upd;
100
+ else
101
+ merged.push(upd);
102
+ }
103
+ setValues(merged);
104
+ }
105
+ }, [values, fields, mapGroups]);
28
106
  const GroupsFromDefaults = (defaults) => {
29
107
  const groups = {};
30
108
  Object.keys(defaults).forEach((key) => {
@@ -53,18 +131,42 @@ function FormSupervisionComponent({ title, fields, submitControl, confirmation,
53
131
  resetFresh();
54
132
  }, [fields]);
55
133
  (0, react_1.useEffect)(() => {
134
+ const minGroups = {};
135
+ const initialValues = [];
136
+ const processMinClusters = (formList, prefix) => {
137
+ formList.forEach((form) => {
138
+ if (form.type === "cluster" && form.construction) {
139
+ const { name: mapName, fields: innerForms, min } = form.construction;
140
+ const groupKey = prefix ? `${prefix}.${mapName}` : mapName;
141
+ const minCount = Math.max(0, min || 0);
142
+ if (minCount > 0) {
143
+ minGroups[groupKey] = Array.from({ length: minCount }, (_, i) => i);
144
+ for (let gIndex = 0; gIndex < minCount; gIndex++) {
145
+ innerForms.forEach((inner) => {
146
+ const fieldName = `${groupKey}[${gIndex}].${inner.construction?.name}`;
147
+ initialValues.push({ name: fieldName, value: "" });
148
+ });
149
+ }
150
+ }
151
+ }
152
+ });
153
+ };
154
+ processMinClusters(fields);
56
155
  if (defaultValue) {
57
156
  setDefaultValues(defaultValue);
58
157
  const derivedGroups = GroupsFromDefaults(defaultValue);
59
- setMapGroups(derivedGroups);
158
+ setMapGroups({ ...minGroups, ...derivedGroups });
60
159
  resetFresh();
61
160
  }
62
161
  else {
63
162
  setDefaultValues(null);
64
- setMapGroups({});
163
+ setMapGroups(minGroups);
164
+ if (initialValues.length > 0) {
165
+ setValues(initialValues);
166
+ }
65
167
  resetFresh();
66
168
  }
67
- }, [defaultValue]);
169
+ }, [defaultValue, fields]);
68
170
  const generateColClass = (col) => String(col).split(" ").map((c) => (c.includes(":") ? `${c.replace(":", ":col-span-")}` : `col-span-${c}`)).join(" ");
69
171
  const inputMap = {
70
172
  default: __1.InputComponent,
@@ -85,13 +187,29 @@ function FormSupervisionComponent({ title, fields, submitControl, confirmation,
85
187
  const renderInput = (form, key, prefix) => {
86
188
  const inputType = form.type || "default";
87
189
  const name = prefix ? `${prefix}.${form.construction?.name}` : form.construction?.name || "input_name";
88
- if (form?.onHide?.(values))
190
+ const valMap = {};
191
+ values.forEach((v) => { valMap[v.name] = v.value; });
192
+ if (form?.onHide?.(valMap))
193
+ return null;
194
+ const ws = watchState[name];
195
+ if (ws?.hidden)
89
196
  return null;
90
197
  if (inputType === "cluster") {
91
- const { name: mapName, fields: innerForms, label, tip, wrap, className } = form.construction;
198
+ const { name: mapName, fields: innerForms, label, tip, wrap, className, min } = form.construction;
199
+ const minCount = Math.max(0, min || 0);
92
200
  const groupKey = prefix ? `${prefix}.${mapName}` : mapName;
93
- const group = mapGroups[groupKey] || [0];
94
- const addGroup = () => setMapGroups((prev) => ({ ...prev, [groupKey]: [...group, group.length] }));
201
+ const defaultGroup = minCount > 0 ? Array.from({ length: minCount }, (_, i) => i) : [];
202
+ const group = mapGroups[groupKey] ?? defaultGroup;
203
+ const showDeleteButton = group.length > minCount;
204
+ const addGroup = () => {
205
+ const nextIndex = group.length;
206
+ setMapGroups((prev) => ({ ...prev, [groupKey]: [...group, nextIndex] }));
207
+ const newGroupValues = innerForms.map((inner) => ({
208
+ name: `${groupKey}[${nextIndex}].${inner.construction?.name}`,
209
+ value: "",
210
+ }));
211
+ setValues([...values, ...newGroupValues]);
212
+ };
95
213
  const removeGroup = (index) => {
96
214
  const filteredGroup = group.filter((_, i) => i !== index);
97
215
  const newGroup = filteredGroup.map((_, i) => i);
@@ -116,14 +234,15 @@ function FormSupervisionComponent({ title, fields, submitControl, confirmation,
116
234
  });
117
235
  setValues(updatedValues);
118
236
  };
119
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, _utils_1.cn)("flex flex-col gap-4", generateColClass(form.col || "12")), children: [group.map((gIndex) => ((0, jsx_runtime_1.jsxs)("div", { className: (0, _utils_1.cn)("relative pr-8", wrap && "p-4 rounded border", className), children: [label && (0, jsx_runtime_1.jsxs)("p", { className: "input-label", children: [label, " ", gIndex + 1] }), tip && (0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-tip"), children: tip }), (label || tip) && (0, jsx_runtime_1.jsx)("div", { className: "mb-2" }), (0, jsx_runtime_1.jsx)("div", { className: "w-full grid grid-cols-12 gap-4", children: innerForms.map((inner, i) => renderInput(inner, i, `${mapName}[${gIndex}]`)) }), (0, jsx_runtime_1.jsx)(__1.ButtonComponent, { icon: "solid/times", paint: "danger", variant: "outline", size: "xs", className: (0, _utils_1.cn)("absolute top-10 right-2 translate-x-[50%] -translate-y-[50%]", wrap && "translate-x-0 -translate-y-0 top-1 right-1"), onClick: () => removeGroup(gIndex) })] }, gIndex))), (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(__1.ButtonComponent, { icon: "solid/plus", label: `${l.base.add ? l.base.add() : "Add"} ${label || mapName}`, variant: "outline", size: "sm", onClick: addGroup }) })] }, key));
237
+ const clusterError = errors?.find((err) => err.name === groupKey || err.name === mapName)?.error;
238
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, _utils_1.cn)("flex flex-col gap-4", generateColClass(form.col || "12")), children: [label && (0, jsx_runtime_1.jsx)("p", { className: "input-label", children: label }), clusterError && (0, jsx_runtime_1.jsx)("small", { className: "input-error-message", children: clusterError }), group.map((gIndex) => ((0, jsx_runtime_1.jsxs)("div", { className: (0, _utils_1.cn)("relative pr-8", wrap && "p-4 rounded border", className), children: [label && (0, jsx_runtime_1.jsxs)("p", { className: "input-label", children: [label, " ", gIndex + 1] }), tip && (0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-tip"), children: tip }), (label || tip) && (0, jsx_runtime_1.jsx)("div", { className: "mb-2" }), (0, jsx_runtime_1.jsx)("div", { className: "w-full grid grid-cols-12 gap-4", children: innerForms.map((inner, i) => renderInput(inner, i, `${groupKey}[${gIndex}]`)) }), showDeleteButton && ((0, jsx_runtime_1.jsx)(__1.ButtonComponent, { icon: "solid/times", paint: "danger", variant: "outline", size: "xs", className: (0, _utils_1.cn)("absolute top-10 right-2 translate-x-[50%] -translate-y-[50%]", wrap && "translate-x-0 -translate-y-0 top-1 right-1"), onClick: () => removeGroup(gIndex) }))] }, gIndex))), (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(__1.ButtonComponent, { icon: "solid/plus", label: `${l.base.add ? l.base.add() : "Add"} ${label || mapName}`, variant: "outline", size: "sm", onClick: addGroup }) })] }, key));
120
239
  }
121
240
  if (inputType === "custom") {
122
241
  const customRender = form.construction;
123
242
  return ((0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)(form.className, generateColClass(form.col || "12")), children: customRender?.({ formControl, values, setValues, errors, setErrors, setRegister, prefixName: prefix }) }, key));
124
243
  }
125
244
  const Component = inputMap[inputType] || __1.InputComponent;
126
- return ((0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)(form.className, generateColClass(form.col || "12")), children: (0, jsx_runtime_1.jsx)(Component, { ...form.construction, ...formControl(name) }) }, key));
245
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)(form.className, generateColClass(form.col || "12")), children: (0, jsx_runtime_1.jsx)(Component, { ...form.construction, ...formControl(name), disabled: ws?.disabled, readOnly: ws?.readonly, name: name }) }, key));
127
246
  };
128
247
  (0, react_1.useEffect)(() => {
129
248
  (modal == "failed") && setModal(false);
@@ -213,10 +213,10 @@ function TableSupervisionComponent({ title, id, fetchControl, columnControl, for
213
213
  } }) }, "button-add"));
214
214
  }
215
215
  if (cb == "IMPORT") {
216
- return ((0, jsx_runtime_1.jsx)("div", { className: "px-1.5 rounded-md relative", children: (0, jsx_runtime_1.jsx)(__1.ButtonComponent, { icon: "solid/file-excel", variant: "outline", className: "!text-foreground", onClick: () => setToggle(`MODAL_IMPORT_${toggleKey}`), size: "sm" }) }, "import"));
216
+ return ((0, jsx_runtime_1.jsx)("div", { className: "px-1.5 rounded-md relative", children: (0, jsx_runtime_1.jsx)(__1.ButtonComponent, { icon: "solid/import", variant: "outline", className: "!text-foreground", onClick: () => setToggle(`MODAL_IMPORT_${toggleKey}`), size: "sm" }) }, "import"));
217
217
  }
218
218
  if (cb == "EXPORT") {
219
- return ((0, jsx_runtime_1.jsx)("div", { className: "px-1.5 rounded-md relative", children: (0, jsx_runtime_1.jsx)(__1.ButtonComponent, { icon: "solid/file-excel", variant: "outline", className: "!text-foreground", onClick: () => setToggle(`MODAL_EXPORT_${toggleKey}`), size: "sm" }) }, "export-excel"));
219
+ return ((0, jsx_runtime_1.jsx)("div", { className: "px-1.5 rounded-md relative", children: (0, jsx_runtime_1.jsx)(__1.ButtonComponent, { icon: "solid/export", variant: "outline", className: "!text-foreground", onClick: () => setToggle(`MODAL_EXPORT_${toggleKey}`), size: "sm" }) }, "export-excel"));
220
220
  }
221
221
  if (cb == "PRINT") {
222
222
  return ((0, jsx_runtime_1.jsx)("div", { className: "px-1.5 rounded-md relative", children: (0, jsx_runtime_1.jsx)(__1.ButtonComponent, { icon: "solid/file-pdf", label: l.base.print ? l.base.print() : "Print", variant: "outline", className: "!text-foreground", onClick: () => setToggle(`MODAL_PRINT_${toggleKey}`), size: "sm" }) }, "export-pdf"));
@@ -97,7 +97,7 @@ function TableComponent({ id, controlBar, columns, data, pagination, loading, so
97
97
  selector: c.selector,
98
98
  type: typeof c?.filterable == "object" ? c?.filterable?.type : "text",
99
99
  options: typeof c?.filterable == "object" && c?.filterable?.type == "select" ? c?.filterable?.options : undefined
100
- })), onFilter: (filters) => onChangeFilter?.(filters), filter: filter, className: (0, _utils_1.pcn)(className, "controller-bar") || "" })), (0, jsx_runtime_1.jsx)("div", { className: "relative", children: (0, jsx_runtime_1.jsx)(ScrollContainer_component_1.ScrollContainerComponent, { scrollFloating: !isSm && block, className: "w-full", onScroll: (e) => {
100
+ })), onFilter: (filters) => onChangeFilter?.(filters), filter: filter, className: (0, _utils_1.pcn)(className, "controller-bar") || "" })), (0, jsx_runtime_1.jsx)("div", { className: "relative", children: (0, jsx_runtime_1.jsx)(ScrollContainer_component_1.ScrollContainerComponent, { scrollFloating: !isSm && block, className: `w-full ${block ? "pb-12" : ""}`, onScroll: (e) => {
101
101
  actionColumnRef.current?.clientWidth && e.scrollLeft &&
102
102
  setShowFloatingAction(e.scrollLeft + e.clientWidth <= e.scrollWidth - actionColumnRef.current?.clientWidth);
103
103
  }, footer: (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: block && pagination && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "py-6" }), (0, jsx_runtime_1.jsx)("div", { className: "my-2 absolute bottom-0 w-full", children: (0, jsx_runtime_1.jsx)(Pagination_component_1.PaginationComponent, { ...pagination }) })] })) }), children: loading ? ((0, jsx_runtime_1.jsx)("div", { className: "w-max min-w-full", children: (0, jsx_runtime_1.jsx)("div", { className: "table-loading-container", children: (0, jsx_runtime_1.jsx)("h1", { className: "table-loading-text", children: l.base.tableLoading ? l.base.tableLoading() : "" }) }) })) : !data || !data.length ? ((0, jsx_runtime_1.jsx)("div", { className: "table-empty-container", children: (0, jsx_runtime_1.jsx)("h1", { className: "table-empty-text", children: l.base.tableEmpty ? l.base.tableEmpty() : "Data empty" }) })) : ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: !isSm || !responsiveControl?.mobile ? ((0, jsx_runtime_1.jsxs)("div", { className: "w-max min-w-full", children: [(0, jsx_runtime_1.jsxs)("div", { className: (0, _utils_1.cn)("table-head-row", (0, _utils_1.pcn)(className, "row")), children: [!!actionBulking && ((0, jsx_runtime_1.jsx)("div", { className: "table-head-column w-max", children: (0, jsx_runtime_1.jsx)(Checkbox_component_1.CheckboxComponent, { name: "selected_table", className: "w-5 h-5", checked: data.length > 0 && checks?.length === data.length, onChange: () => data.length > 0 && checks?.length === data.length ? onChangeChecks?.([]) : onChangeChecks?.(data.map((d) => d.id)) }) })), !noIndex && (0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)("table-head-column w-8", (0, _utils_1.pcn)(className, "head-column")), children: "#" }), renderHead()] }), (0, jsx_runtime_1.jsx)("div", { className: "table-body", children: data.map((item, key) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skalfa/skalfa-component",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "Reusable UI components for Skalfa App.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -72,7 +72,7 @@ export function InputComponent({
72
72
  // =========================>
73
73
  // ## Initial
74
74
  // =========================>
75
- const inputHandler = useInputHandler(props.name, value, validations, register, props.type == "file")
75
+ const inputHandler = useInputHandler(props.name, value, validations, register, props.type == "file", unregister)
76
76
  const randomId = useInputRandomId()
77
77
 
78
78
 
@@ -70,7 +70,7 @@ export function InputCheckboxComponent({
70
70
  // =========================>
71
71
  // ## initial
72
72
  // =========================>
73
- const inputHandler = useInputHandler(name, value, validations, register, false)
73
+ const inputHandler = useInputHandler(name, value, validations, register, false, unregister)
74
74
 
75
75
 
76
76
  // =========================>
@@ -55,7 +55,7 @@ export function InputCurrencyComponent({
55
55
  // =========================>
56
56
  // ## Initial
57
57
  // =========================>
58
- const inputHandler = useInputHandler(props.name, value, validations, register, false)
58
+ const inputHandler = useInputHandler(props.name, value, validations, register, false, unregister)
59
59
  const randomId = useInputRandomId()
60
60
 
61
61
 
@@ -54,7 +54,7 @@ export function InputDateComponent({
54
54
  // =========================>
55
55
  // ## Initial
56
56
  // =========================>
57
- const inputHandler = useInputHandler(props.name, value, validations, register, false)
57
+ const inputHandler = useInputHandler(props.name, value, validations, register, false, unregister)
58
58
  const randomId = useInputRandomId()
59
59
 
60
60
 
@@ -61,7 +61,7 @@ export function InputDatetimeComponent({
61
61
  // =========================>
62
62
  // ## Initial
63
63
  // =========================>
64
- const inputHandler = useInputHandler(props.name, value, validations, register, false)
64
+ const inputHandler = useInputHandler(props.name, value, validations, register, false, unregister)
65
65
  const randomId = useInputRandomId()
66
66
 
67
67
 
@@ -73,7 +73,7 @@ export function InputDocumentComponent({
73
73
  // =========================>
74
74
  // ## Initial
75
75
  // =========================>
76
- const inputHandler = useInputHandler(props.name, value, validations, register, props.type == "file")
76
+ const inputHandler = useInputHandler(props.name, value, validations, register, props.type == "file", unregister)
77
77
  const randomId = useInputRandomId()
78
78
 
79
79
  // =========================>
@@ -56,7 +56,7 @@ export const InputImageComponent: React.FC<InputImageProps> = ({
56
56
  const [cropSrc, setCropSrc] = useState<string | null>(null);
57
57
  const [openCrop, setOpenCrop] = useState(false);
58
58
 
59
- const inputHandler = useInputHandler(name, value, validations, register, true);
59
+ const inputHandler = useInputHandler(name, value, validations, register, true, unregister);
60
60
  const [invalidMessage, setInvalidMessage] = useValidation(inputHandler.value, validations, invalid, inputHandler.idle);
61
61
 
62
62
  useEffect(() => {
@@ -53,7 +53,7 @@ export function InputNumberComponent({
53
53
  // =========================>
54
54
  // ## Initial
55
55
  // =========================>
56
- const inputHandler = useInputHandler(props.name, value, validations, register, false)
56
+ const inputHandler = useInputHandler(props.name, value, validations, register, false, unregister)
57
57
  const randomId = useInputRandomId()
58
58
 
59
59
 
@@ -55,7 +55,7 @@ export function InputPasswordComponent({
55
55
  // =========================>
56
56
  // ## Initial
57
57
  // =========================>
58
- const inputHandler = useInputHandler(props.name, value, validations, register, false)
58
+ const inputHandler = useInputHandler(props.name, value, validations, register, false, unregister)
59
59
  const randomId = useInputRandomId()
60
60
  const randomConfirmId = useInputRandomId()
61
61
 
@@ -68,7 +68,7 @@ export function InputRadioComponent({
68
68
  // =========================>
69
69
  // ## Initial
70
70
  // =========================>
71
- const inputHandler = useInputHandler(name, value, validations, register, false)
71
+ const inputHandler = useInputHandler(name, value, validations, register, false, unregister)
72
72
 
73
73
 
74
74
  // =========================>
@@ -53,7 +53,7 @@ export function InputTimeComponent({
53
53
  // =========================>
54
54
  // ## Initial
55
55
  // =========================>
56
- const inputHandler = useInputHandler(props.name, value, validations, register, false)
56
+ const inputHandler = useInputHandler(props.name, value, validations, register, false, unregister)
57
57
  const randomId = useInputRandomId()
58
58
 
59
59
 
@@ -105,7 +105,7 @@ export function SelectComponent({
105
105
  // =========================>
106
106
  // ## Initial
107
107
  // =========================>
108
- const inputHandler = useInputHandler(name, value, validations, register, false)
108
+ const inputHandler = useInputHandler(name, value, validations, register, false, unregister)
109
109
  const randomId = useInputRandomId()
110
110
 
111
111
 
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import React, { ReactNode, useEffect, useState } from "react";
3
+ import React, { ReactNode, useEffect, useRef, useState } from "react";
4
4
  import { ApiType, cn, pcn, FormErrorType, FormRegisterType, FormValueType, useForm, ValidationRules, DBSchema } from "@utils";
5
5
  import {
6
6
  InputCheckboxComponent,
@@ -65,6 +65,7 @@ type ClusterConstruction = {
65
65
  tip : string;
66
66
  fields : FormType[];
67
67
  wrap : boolean;
68
+ min ?: number;
68
69
 
69
70
  /** Use custom class with: "label::", "tip::", "error::", "icon::", "suggest::", "suggest-item::". */
70
71
  className : string;
@@ -89,12 +90,28 @@ type ConstructionMap = {
89
90
 
90
91
  type TypeKeys = keyof ConstructionMap;
91
92
 
93
+ export type WatchContext = {
94
+ values : Record<string, any>
95
+ self : string
96
+ prev : WatchAction
97
+ }
98
+
99
+ export type WatchAction = {
100
+ disabled ?: boolean
101
+ hidden ?: boolean
102
+ value ?: any
103
+ required ?: boolean
104
+ readonly ?: boolean
105
+ reset ?: boolean
106
+ }
107
+
92
108
  export interface FormType<T extends TypeKeys = keyof ConstructionMap> {
93
109
  col ?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | string;
94
110
  className ?: string;
95
111
  construction ?: ConstructionMap[T];
96
112
  type ?: T;
97
- onHide ?: (values: any) => boolean;
113
+ onHide ?: (values: Record<string, any>) => boolean;
114
+ onWatch ?: (ctx: WatchContext) => WatchAction | undefined;
98
115
  }
99
116
 
100
117
  export interface formSupervisionProps {
@@ -130,14 +147,17 @@ export function FormSupervisionComponent({
130
147
  }: formSupervisionProps) {
131
148
  const l = useLang();
132
149
 
133
- const [modal, setModal] = useState<boolean | "success" | "failed">(false);
134
- const [fresh, setFresh] = useState<boolean>(true);
135
- const [mapGroups, setMapGroups] = useState<Record<string, number[]>>({});
150
+ const [modal, setModal] = useState<boolean | "success" | "failed">(false);
151
+ const [fresh, setFresh] = useState<boolean>(true);
152
+ const [mapGroups, setMapGroups] = useState<Record<string, number[]>>({});
153
+ const [watchState, setWatchState] = useState<Record<string, WatchAction>>({});
154
+ const watchRef = useRef<Record<string, WatchAction>>({});
136
155
 
137
156
  const [
138
157
  {
139
158
  formControl,
140
159
  setRegister,
160
+ setUnregister,
141
161
  values,
142
162
  setValues,
143
163
  errors,
@@ -164,6 +184,95 @@ export function FormSupervisionComponent({
164
184
  }
165
185
  );
166
186
 
187
+ // ==============================>
188
+ // ## Watch: collect watchers from fields
189
+ // ==============================>
190
+ const collectWatchers = (fieldList: FormType[], prefix?: string): { name: string, onWatch: NonNullable<FormType['onWatch']>, construction: any }[] => {
191
+ const result: { name: string, onWatch: NonNullable<FormType['onWatch']>, construction: any }[] = [];
192
+
193
+ for (const f of fieldList) {
194
+ const inputType = f.type || "default";
195
+ const name = prefix ? `${prefix}.${f.construction?.name}` : f.construction?.name || "";
196
+
197
+ if (inputType === "cluster") {
198
+ const cluster = f.construction as ClusterConstruction;
199
+ const groupKey = prefix ? `${prefix}.${cluster.name}` : cluster.name;
200
+ const group = mapGroups[groupKey] || [];
201
+
202
+ for (const gIndex of group) {
203
+ result.push(...collectWatchers(cluster.fields, `${cluster.name}[${gIndex}]`));
204
+ }
205
+ } else if (f.onWatch) {
206
+ result.push({ name, onWatch: f.onWatch, construction: f.construction });
207
+ }
208
+ }
209
+
210
+ return result;
211
+ };
212
+
213
+ // ==============================>
214
+ // ## Watch: execute watchers on value change
215
+ // ==============================>
216
+ useEffect(() => {
217
+ const watchers = collectWatchers(fields);
218
+ if (watchers.length === 0) {
219
+ if (Object.keys(watchRef.current).length > 0) {
220
+ watchRef.current = {};
221
+ setWatchState({});
222
+ }
223
+ return;
224
+ }
225
+
226
+ const valMap: Record<string, any> = {};
227
+ values.forEach((v) => { valMap[v.name] = v.value; });
228
+
229
+ const nextState : Record<string, WatchAction> = {};
230
+ const valueUpdates : FormValueType[] = [];
231
+
232
+ for (const { name, onWatch, construction } of watchers) {
233
+ const prev = watchRef.current[name] || {};
234
+ const action = onWatch({ values: valMap, self: name, prev });
235
+
236
+ if (!action) continue;
237
+
238
+ nextState[name] = action;
239
+
240
+ if (action.hidden && !prev.hidden) setUnregister(name);
241
+
242
+ if (action.required !== prev.required) {
243
+ const baseValidations = Array.isArray(construction?.validations) ? [...construction.validations] : [];
244
+ const newValidations = action.required ? (baseValidations.includes("required") ? baseValidations : [...baseValidations, "required"]) : baseValidations.filter((v: string) => v !== "required");
245
+
246
+ setRegister({ name, validations: newValidations });
247
+ }
248
+
249
+ if (action.reset) {
250
+ const cur = valMap[name];
251
+
252
+ if (cur != null && cur !== "") valueUpdates.push({ name, value: "" });
253
+ } else if (action.value !== undefined && action.value !== valMap[name]) {
254
+ valueUpdates.push({ name, value: action.value });
255
+ }
256
+ }
257
+
258
+ if (JSON.stringify(watchRef.current) !== JSON.stringify(nextState)) {
259
+ watchRef.current = nextState;
260
+ setWatchState(nextState);
261
+ }
262
+
263
+ if (valueUpdates.length > 0) {
264
+ const merged = [...values];
265
+
266
+ for (const upd of valueUpdates) {
267
+ const idx = merged.findIndex(v => v.name === upd.name);
268
+ if (idx >= 0) merged[idx] = upd;
269
+ else merged.push(upd);
270
+ }
271
+
272
+ setValues(merged);
273
+ }
274
+ }, [values, fields, mapGroups]);
275
+
167
276
  const GroupsFromDefaults = (defaults: Record<string, any>): Record<string, number[]> => {
168
277
  const groups: Record<string, Set<number>> = {};
169
278
 
@@ -201,19 +310,46 @@ export function FormSupervisionComponent({
201
310
  }, [fields]);
202
311
 
203
312
  useEffect(() => {
204
- if (defaultValue) {
205
- setDefaultValues(defaultValue);
206
-
207
- const derivedGroups = GroupsFromDefaults(defaultValue);
208
- setMapGroups(derivedGroups);
209
-
210
- resetFresh();
211
- } else {
212
- setDefaultValues(null);
213
- setMapGroups({});
214
- resetFresh();
215
- }
216
- }, [defaultValue]);
313
+ const minGroups: Record<string, number[]> = {};
314
+ const initialValues: FormValueType[] = [];
315
+
316
+ const processMinClusters = (formList: FormType[], prefix?: string) => {
317
+ formList.forEach((form) => {
318
+ if (form.type === "cluster" && form.construction) {
319
+ const { name: mapName, fields: innerForms, min } = form.construction as ClusterConstruction;
320
+ const groupKey = prefix ? `${prefix}.${mapName}` : mapName;
321
+ const minCount = Math.max(0, min || 0);
322
+
323
+ if (minCount > 0) {
324
+ minGroups[groupKey] = Array.from({ length: minCount }, (_, i) => i);
325
+
326
+ for (let gIndex = 0; gIndex < minCount; gIndex++) {
327
+ innerForms.forEach((inner) => {
328
+ const fieldName = `${groupKey}[${gIndex}].${inner.construction?.name}`;
329
+ initialValues.push({ name: fieldName, value: "" });
330
+ });
331
+ }
332
+ }
333
+ }
334
+ });
335
+ };
336
+
337
+ processMinClusters(fields);
338
+
339
+ if (defaultValue) {
340
+ setDefaultValues(defaultValue);
341
+ const derivedGroups = GroupsFromDefaults(defaultValue);
342
+ setMapGroups({ ...minGroups, ...derivedGroups });
343
+ resetFresh();
344
+ } else {
345
+ setDefaultValues(null);
346
+ setMapGroups(minGroups);
347
+ if (initialValues.length > 0) {
348
+ setValues(initialValues);
349
+ }
350
+ resetFresh();
351
+ }
352
+ }, [defaultValue, fields]);
217
353
 
218
354
  const generateColClass = (col: string | number) => String(col).split(" ").map((c) => (c.includes(":") ? `${c.replace(":", ":col-span-")}` : `col-span-${c}`)).join(" ");
219
355
 
@@ -238,15 +374,35 @@ export function FormSupervisionComponent({
238
374
  const inputType = form.type || "default";
239
375
  const name = prefix ? `${prefix}.${form.construction?.name}` : form.construction?.name || "input_name";
240
376
 
241
- if (form?.onHide?.(values)) return null;
377
+ const valMap: Record<string, any> = {};
378
+ values.forEach((v) => { valMap[v.name] = v.value; });
379
+
380
+ if (form?.onHide?.(valMap)) return null;
381
+
382
+ const ws = watchState[name];
383
+ if (ws?.hidden) return null;
242
384
 
243
385
  if (inputType === "cluster") {
244
- const { name: mapName, fields: innerForms, label, tip, wrap, className } = form.construction as ClusterConstruction;
386
+ const { name: mapName, fields: innerForms, label, tip, wrap, className, min } = form.construction as ClusterConstruction;
387
+ const minCount = Math.max(0, min || 0);
245
388
 
246
389
  const groupKey = prefix ? `${prefix}.${mapName}` : mapName;
247
- const group = mapGroups[groupKey] || [0];
390
+ const defaultGroup = minCount > 0 ? Array.from({ length: minCount }, (_, i) => i) : [];
391
+ const group = mapGroups[groupKey] ?? defaultGroup;
392
+
393
+ const showDeleteButton = group.length > minCount;
248
394
 
249
- const addGroup = () => setMapGroups((prev) => ({ ...prev, [groupKey]: [...group, group.length] }));
395
+ const addGroup = () => {
396
+ const nextIndex = group.length;
397
+ setMapGroups((prev) => ({ ...prev, [groupKey]: [...group, nextIndex] }));
398
+
399
+ const newGroupValues = innerForms.map((inner) => ({
400
+ name: `${groupKey}[${nextIndex}].${inner.construction?.name}`,
401
+ value: "",
402
+ }));
403
+
404
+ setValues([...values, ...newGroupValues]);
405
+ };
250
406
 
251
407
  const removeGroup = (index: number) => {
252
408
  const filteredGroup = group.filter((_, i) => i !== index);
@@ -275,8 +431,12 @@ export function FormSupervisionComponent({
275
431
  setValues(updatedValues);
276
432
  };
277
433
 
434
+ const clusterError = errors?.find((err: FormErrorType) => err.name === groupKey || err.name === mapName)?.error;
435
+
278
436
  return (
279
437
  <div key={key} className={cn("flex flex-col gap-4", generateColClass(form.col || "12"))}>
438
+ {label && <p className="input-label">{label}</p>}
439
+ {clusterError && <small className="input-error-message">{clusterError}</small>}
280
440
  {group.map((gIndex) => (
281
441
  <div key={gIndex} className={cn("relative pr-8", wrap && "p-4 rounded border", className)}>
282
442
  {label && <p className="input-label">{label} {gIndex + 1}</p>}
@@ -284,17 +444,19 @@ export function FormSupervisionComponent({
284
444
  {(label || tip) && <div className="mb-2"></div>}
285
445
 
286
446
  <div className="w-full grid grid-cols-12 gap-4">
287
- {innerForms.map((inner, i) => renderInput(inner, i, `${mapName}[${gIndex}]`))}
447
+ {innerForms.map((inner, i) => renderInput(inner, i, `${groupKey}[${gIndex}]`))}
288
448
  </div>
289
449
 
290
- <ButtonComponent
291
- icon={"solid/times"}
292
- paint="danger"
293
- variant="outline"
294
- size="xs"
295
- className={cn("absolute top-10 right-2 translate-x-[50%] -translate-y-[50%]", wrap && "translate-x-0 -translate-y-0 top-1 right-1")}
296
- onClick={() => removeGroup(gIndex)}
297
- />
450
+ {showDeleteButton && (
451
+ <ButtonComponent
452
+ icon={"solid/times"}
453
+ paint="danger"
454
+ variant="outline"
455
+ size="xs"
456
+ className={cn("absolute top-10 right-2 translate-x-[50%] -translate-y-[50%]", wrap && "translate-x-0 -translate-y-0 top-1 right-1")}
457
+ onClick={() => removeGroup(gIndex)}
458
+ />
459
+ )}
298
460
  </div>
299
461
  ))}
300
462
 
@@ -326,7 +488,9 @@ export function FormSupervisionComponent({
326
488
  <Component
327
489
  {...(form.construction as any)}
328
490
  {...formControl(name)}
329
- // autoFocus={key === 0}
491
+ disabled={ws?.disabled}
492
+ readOnly={ws?.readonly}
493
+ name={name}
330
494
  />
331
495
  </div>
332
496
  );
@@ -431,7 +431,7 @@ export function TableSupervisionComponent({
431
431
  return (
432
432
  <div className="px-1.5 rounded-md relative" key={"import"}>
433
433
  <ButtonComponent
434
- icon={"solid/file-excel"}
434
+ icon={"solid/import"}
435
435
  variant="outline"
436
436
  className="!text-foreground"
437
437
  onClick={() => setToggle(`MODAL_IMPORT_${toggleKey}`)}
@@ -445,7 +445,7 @@ export function TableSupervisionComponent({
445
445
  return (
446
446
  <div className="px-1.5 rounded-md relative" key={"export-excel"}>
447
447
  <ButtonComponent
448
- icon={"solid/file-excel"}
448
+ icon={"solid/export"}
449
449
  variant="outline"
450
450
  className="!text-foreground"
451
451
  onClick={() => setToggle(`MODAL_EXPORT_${toggleKey}`)}
@@ -293,7 +293,7 @@ export function TableComponent({
293
293
  <div className="relative">
294
294
  <ScrollContainerComponent
295
295
  scrollFloating={!isSm && block}
296
- className="w-full"
296
+ className={`w-full ${block ? "pb-12" : ""}`}
297
297
  onScroll={(e) => {
298
298
  actionColumnRef.current?.clientWidth && e.scrollLeft &&
299
299
  setShowFloatingAction(e.scrollLeft + e.clientWidth <= e.scrollWidth - actionColumnRef.current?.clientWidth);