@skalfa/skalfa-component 1.0.7 → 1.0.9

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 (107) hide show
  1. package/dist/accordion/Accordion.component.js +2 -3
  2. package/dist/breadcrumb/Breadcrumb.component.js +2 -3
  3. package/dist/button/Button.component.d.ts +2 -1
  4. package/dist/button/Button.component.js +2 -2
  5. package/dist/carousel/Carousel.component.js +2 -3
  6. package/dist/chip/Chip.component.js +2 -3
  7. package/dist/contexts-mock.d.ts +2 -0
  8. package/dist/contexts-mock.js +10 -0
  9. package/dist/input/Checkbox.component.js +2 -3
  10. package/dist/input/Input.component.d.ts +3 -2
  11. package/dist/input/Input.component.js +3 -3
  12. package/dist/input/InputCheckbox.component.js +1 -1
  13. package/dist/input/InputCurrency.component.d.ts +3 -2
  14. package/dist/input/InputCurrency.component.js +3 -3
  15. package/dist/input/InputDate.component.js +4 -5
  16. package/dist/input/InputDatetime.component.d.ts +3 -2
  17. package/dist/input/InputDatetime.component.js +3 -3
  18. package/dist/input/InputDocument.component.js +7 -8
  19. package/dist/input/InputImage.component.js +3 -4
  20. package/dist/input/InputNumber.component.js +3 -4
  21. package/dist/input/InputPassword.component.js +3 -3
  22. package/dist/input/InputRadio.component.js +1 -1
  23. package/dist/input/InputTime.component.js +3 -3
  24. package/dist/input/InputValues.component.js +3 -4
  25. package/dist/input/Select.component.js +6 -7
  26. package/dist/modal/FloatingPage.component.d.ts +3 -1
  27. package/dist/modal/FloatingPage.component.js +7 -3
  28. package/dist/modal/Modal.component.js +1 -2
  29. package/dist/modal/ModalConfirm.component.d.ts +2 -2
  30. package/dist/modal/ModalConfirm.component.js +14 -12
  31. package/dist/modal/Toast.component.js +1 -2
  32. package/dist/nav/Bottombar.component.d.ts +2 -2
  33. package/dist/nav/Bottombar.component.js +7 -8
  34. package/dist/nav/Footer.component.d.ts +2 -2
  35. package/dist/nav/Footer.component.js +6 -7
  36. package/dist/nav/Headbar.component.js +2 -3
  37. package/dist/nav/Navbar.component.d.ts +2 -2
  38. package/dist/nav/Navbar.component.js +3 -4
  39. package/dist/nav/Sidebar.component.d.ts +3 -7
  40. package/dist/nav/Sidebar.component.js +14 -17
  41. package/dist/supervision/FormSupervision.component.d.ts +4 -29
  42. package/dist/supervision/FormSupervision.component.js +84 -136
  43. package/dist/supervision/TableSupervision.component.d.ts +17 -26
  44. package/dist/supervision/TableSupervision.component.js +207 -193
  45. package/dist/table/ControlBar.component.js +15 -16
  46. package/dist/table/FilterComponent.js +4 -6
  47. package/dist/table/Pagination.component.js +2 -3
  48. package/dist/table/Table.component.js +3 -4
  49. package/dist/wrap/Swipe.component.js +2 -2
  50. package/package.json +18 -10
  51. package/src/accordion/Accordion.component.tsx +87 -0
  52. package/src/breadcrumb/Breadcrumb.component.tsx +79 -0
  53. package/src/button/Button.component.tsx +89 -0
  54. package/src/card/AlertCard.component.tsx +69 -0
  55. package/src/card/Card.component.tsx +25 -0
  56. package/src/card/DashboardCard.component.tsx +44 -0
  57. package/src/card/GalleryCard.component.tsx +50 -0
  58. package/src/card/ProductCard.component.tsx +65 -0
  59. package/src/card/ProfileCard.component.tsx +71 -0
  60. package/src/carousel/Carousel.component.tsx +111 -0
  61. package/src/chip/Chip.component.tsx +38 -0
  62. package/src/contexts-mock.ts +6 -0
  63. package/src/index.ts +70 -0
  64. package/src/input/Checkbox.component.tsx +102 -0
  65. package/src/input/Input.component.tsx +334 -0
  66. package/src/input/InputCheckbox.component.tsx +174 -0
  67. package/src/input/InputCurrency.component.tsx +165 -0
  68. package/src/input/InputDate.component.tsx +355 -0
  69. package/src/input/InputDatetime.component.tsx +267 -0
  70. package/src/input/InputDocument.component.tsx +360 -0
  71. package/src/input/InputImage.component.tsx +534 -0
  72. package/src/input/InputNumber.component.tsx +193 -0
  73. package/src/input/InputOtp.component.tsx +169 -0
  74. package/src/input/InputPassword.component.tsx +245 -0
  75. package/src/input/InputRadio.component.tsx +174 -0
  76. package/src/input/InputTime.component.tsx +280 -0
  77. package/src/input/InputValues.component.tsx +70 -0
  78. package/src/input/Radio.component.tsx +98 -0
  79. package/src/input/Select.component.tsx +557 -0
  80. package/src/modal/BottomSheet.component.tsx +246 -0
  81. package/src/modal/FloatingPage.component.tsx +133 -0
  82. package/src/modal/Modal.component.tsx +94 -0
  83. package/src/modal/ModalConfirm.component.tsx +204 -0
  84. package/src/modal/Toast.component.tsx +124 -0
  85. package/src/nav/Bottombar.component.tsx +71 -0
  86. package/src/nav/Footer.component.tsx +176 -0
  87. package/src/nav/Headbar.component.tsx +33 -0
  88. package/src/nav/Navbar.component.tsx +137 -0
  89. package/src/nav/Sidebar.component.tsx +307 -0
  90. package/src/nav/Tabbar.component.tsx +61 -0
  91. package/src/nav/Wizard.component.tsx +80 -0
  92. package/src/supervision/FormSupervision.component.tsx +404 -0
  93. package/src/supervision/TableSupervision.component.tsx +653 -0
  94. package/src/table/ControlBar.component.tsx +501 -0
  95. package/src/table/FilterComponent.tsx +517 -0
  96. package/src/table/Pagination.component.tsx +152 -0
  97. package/src/table/Table.component.tsx +436 -0
  98. package/src/typography/TypographyArticle.component.tsx +26 -0
  99. package/src/typography/TypographyColumn.component.tsx +20 -0
  100. package/src/typography/TypographyContent.component.tsx +20 -0
  101. package/src/typography/TypographyTips.component.tsx +20 -0
  102. package/src/wrap/Draggable.component.tsx +303 -0
  103. package/src/wrap/Image.component.tsx +10 -0
  104. package/src/wrap/OutsideClick.component.tsx +48 -0
  105. package/src/wrap/ScrollContainer.component.tsx +107 -0
  106. package/src/wrap/ShortcutProvider.tsx +57 -0
  107. package/src/wrap/Swipe.component.tsx +121 -0
@@ -0,0 +1,404 @@
1
+ "use client"
2
+
3
+ import React, { ReactNode, useEffect, useState } from "react";
4
+ import { ApiType, cn, pcn, FormErrorType, FormRegisterType, FormValueType, useForm, ValidationRules, DBSchema } from "@utils";
5
+ import {
6
+ InputCheckboxComponent,
7
+ InputComponent,
8
+ InputCurrencyComponent,
9
+ InputDateComponent,
10
+ InputNumberComponent,
11
+ InputOtpComponent,
12
+ InputPasswordComponent,
13
+ InputRadioComponent,
14
+ SelectComponent,
15
+ ButtonComponent,
16
+ ModalConfirmComponent,
17
+ InputProps,
18
+ InputCheckboxProps,
19
+ InputCurrencyProps,
20
+ InputDateProps,
21
+ InputNumberProps,
22
+ InputRadioProps,
23
+ SelectProps,
24
+ InputPasswordProps,
25
+ InputOtpProps,
26
+ InputTimeProps,
27
+ InputImageProps,
28
+ InputDateTimeProps,
29
+ InputDatetimeComponent,
30
+ InputTimeComponent,
31
+ InputImageComponent,
32
+ } from "../";
33
+ import { Icon } from "@skalfa/skalfa-icon";
34
+ import { useLang } from "@skalfa/skalfa-lang";
35
+
36
+
37
+
38
+ type CT = "base" | "title" | "submit";
39
+
40
+ type formCustomConstructionProps = ({
41
+ formControl,
42
+ values,
43
+ setValues,
44
+ setRegister,
45
+ errors,
46
+ setErrors,
47
+ }: {
48
+ formControl : (name: string) => {
49
+ register: (regName: string, regValidations?: ValidationRules | undefined) => void;
50
+ onChange: (e: any) => void;
51
+ value: any;
52
+ invalid: any;
53
+ };
54
+ values : { name: string; value?: any }[];
55
+ setValues : (values: FormValueType[]) => void;
56
+ errors : FormErrorType[];
57
+ setErrors : (errors: FormErrorType[]) => void;
58
+ setRegister : (registers: FormRegisterType) => void;
59
+ prefixName ?: string;
60
+ }) => ReactNode;
61
+
62
+ type ClusterConstruction = {
63
+ name : string;
64
+ label : string;
65
+ tip : string;
66
+ fields : FormType[];
67
+ wrap : boolean;
68
+
69
+ /** Use custom class with: "label::", "tip::", "error::", "icon::", "suggest::", "suggest-item::". */
70
+ className : string;
71
+ };
72
+
73
+ type ConstructionMap = {
74
+ default : InputProps;
75
+ check : InputCheckboxProps;
76
+ currency : InputCurrencyProps;
77
+ date : InputDateProps;
78
+ datetime : InputDateTimeProps;
79
+ time : InputTimeProps;
80
+ image : InputImageProps;
81
+ cluster : ClusterConstruction;
82
+ number : InputNumberProps;
83
+ radio : InputRadioProps;
84
+ select : SelectProps;
85
+ "enter-password" : InputPasswordProps;
86
+ otp : InputOtpProps;
87
+ custom : formCustomConstructionProps;
88
+ };
89
+
90
+ type TypeKeys = keyof ConstructionMap;
91
+
92
+ export interface FormType<T extends TypeKeys = keyof ConstructionMap> {
93
+ col ?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | string;
94
+ className ?: string;
95
+ construction ?: ConstructionMap[T];
96
+ type ?: T;
97
+ onHide ?: (values: any) => boolean;
98
+ }
99
+
100
+ export interface formSupervisionProps {
101
+ title ?: string;
102
+ fields : FormType[];
103
+ confirmation ?: boolean;
104
+ defaultValue ?: object | null;
105
+ payload ?: (values: any) => Promise<object> | object;
106
+ submitControl : (ApiType & { idb?: never }) | { idb: { store: string, schema?: DBSchema }};
107
+ footerControl ?: ({ loading }: { loading: boolean }) => ReactNode;
108
+ onSuccess ?: (data: any) => void;
109
+ onError ?: (code: number) => void;
110
+ successMessage ?: string;
111
+ className ?: string;
112
+ id ?: string;
113
+ }
114
+
115
+
116
+
117
+ export function FormSupervisionComponent({
118
+ title,
119
+ fields,
120
+ submitControl,
121
+ confirmation,
122
+ defaultValue,
123
+ onSuccess,
124
+ onError,
125
+ footerControl,
126
+ payload,
127
+ className = "",
128
+ successMessage,
129
+ id,
130
+ }: formSupervisionProps) {
131
+ const l = useLang();
132
+
133
+ const [modal, setModal] = useState<boolean | "success" | "failed">(false);
134
+ const [fresh, setFresh] = useState<boolean>(true);
135
+ const [mapGroups, setMapGroups] = useState<Record<string, number[]>>({});
136
+
137
+ const [
138
+ {
139
+ formControl,
140
+ setRegister,
141
+ values,
142
+ setValues,
143
+ errors,
144
+ setErrors,
145
+ setDefaultValues,
146
+ submit,
147
+ loading,
148
+ confirm,
149
+ },
150
+ ] = useForm(
151
+ submitControl,
152
+ payload,
153
+ confirmation,
154
+ (data: any) => {
155
+ onSuccess?.(data);
156
+ setModal("success");
157
+ setTimeout(() => setModal(false), 1000);
158
+ resetFresh();
159
+ },
160
+ (code: number) => {
161
+ onError?.(code);
162
+ if (code == 422) confirm.onClose();
163
+ else setModal("failed");
164
+ }
165
+ );
166
+
167
+ const GroupsFromDefaults = (defaults: Record<string, any>): Record<string, number[]> => {
168
+ const groups: Record<string, Set<number>> = {};
169
+
170
+ Object.keys(defaults).forEach((key) => {
171
+ const match = key.match(/^(.+?)\[(\d+)\]/);
172
+ if (!match) return;
173
+
174
+ const [, groupKey, indexStr] = match;
175
+ const index = Number(indexStr);
176
+
177
+ if (!groups[groupKey]) {
178
+ groups[groupKey] = new Set();
179
+ }
180
+
181
+ groups[groupKey].add(index);
182
+ });
183
+
184
+ return Object.fromEntries(
185
+ Object.entries(groups).map(([key, indexSet]) => [
186
+ key,
187
+ Array.from(indexSet)
188
+ .sort((a, b) => a - b)
189
+ .map((_, i) => i),
190
+ ])
191
+ );
192
+ };
193
+
194
+ const resetFresh = () => {
195
+ setFresh(false);
196
+ setTimeout(() => setFresh(true), 300);
197
+ };
198
+
199
+ useEffect(() => {
200
+ resetFresh();
201
+ }, [fields]);
202
+
203
+ 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]);
217
+
218
+ const generateColClass = (col: string | number) => String(col).split(" ").map((c) => (c.includes(":") ? `${c.replace(":", ":col-span-")}` : `col-span-${c}`)).join(" ");
219
+
220
+ const inputMap: Record<TypeKeys, React.FC<any>> = {
221
+ default : InputComponent,
222
+ check : InputCheckboxComponent,
223
+ currency : InputCurrencyComponent,
224
+ date : InputDateComponent,
225
+ datetime : InputDatetimeComponent,
226
+ time : InputTimeComponent,
227
+ number : InputNumberComponent,
228
+ radio : InputRadioComponent,
229
+ select : SelectComponent,
230
+ "enter-password" : InputPasswordComponent,
231
+ otp : InputOtpComponent,
232
+ image : InputImageComponent,
233
+ cluster : () => null,
234
+ custom : () => null,
235
+ };
236
+
237
+ const renderInput = (form: FormType, key: number, prefix?: string) => {
238
+ const inputType = form.type || "default";
239
+ const name = prefix ? `${prefix}.${form.construction?.name}` : form.construction?.name || "input_name";
240
+
241
+ if (form?.onHide?.(values)) return null;
242
+
243
+ if (inputType === "cluster") {
244
+ const { name: mapName, fields: innerForms, label, tip, wrap, className } = form.construction as ClusterConstruction;
245
+
246
+ const groupKey = prefix ? `${prefix}.${mapName}` : mapName;
247
+ const group = mapGroups[groupKey] || [0];
248
+
249
+ const addGroup = () => setMapGroups((prev) => ({ ...prev, [groupKey]: [...group, group.length] }));
250
+
251
+ const removeGroup = (index: number) => {
252
+ const filteredGroup = group.filter((_, i) => i !== index);
253
+ const newGroup = filteredGroup.map((_, i) => i);
254
+
255
+ setMapGroups((prev) => ({ ...prev, [groupKey]: newGroup }));
256
+
257
+ let updatedValues = values.filter((v) => {
258
+ const n = String(v?.name || "");
259
+ if (!n) return true;
260
+ if (n.startsWith(`${groupKey}[${index}]`) || n.startsWith(`${groupKey}.${index}.`)) return false;
261
+ return true;
262
+ });
263
+
264
+ const regex = new RegExp(`${groupKey}\\[(\\d+)\\]`, 'g');
265
+ updatedValues = updatedValues.map((v) => {
266
+ let name = v.name;
267
+ name = name.replace(regex, (match: string, oldIdx: string) => {
268
+ const oldIndex = parseInt(oldIdx, 10);
269
+ const newIndex = newGroup.indexOf(oldIndex);
270
+ return newIndex >= 0 ? `${groupKey}[${newIndex}]` : match;
271
+ });
272
+ return { ...v, name };
273
+ });
274
+
275
+ setValues(updatedValues);
276
+ };
277
+
278
+ return (
279
+ <div key={key} className={cn("flex flex-col gap-4", generateColClass(form.col || "12"))}>
280
+ {group.map((gIndex) => (
281
+ <div key={gIndex} className={cn("relative pr-8", wrap && "p-4 rounded border", className)}>
282
+ {label && <p className="input-label">{label} {gIndex + 1}</p>}
283
+ {tip && <small className={cn("input-tip")}>{tip}</small>}
284
+ {(label || tip) && <div className="mb-2"></div>}
285
+
286
+ <div className="w-full grid grid-cols-12 gap-4">
287
+ {innerForms.map((inner, i) => renderInput(inner, i, `${mapName}[${gIndex}]`))}
288
+ </div>
289
+
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
+ />
298
+ </div>
299
+ ))}
300
+
301
+ <div>
302
+ <ButtonComponent
303
+ icon={"solid/plus"}
304
+ label={`Tambah ${label || mapName}`}
305
+ variant="outline"
306
+ size="sm"
307
+ onClick={addGroup}
308
+ />
309
+ </div>
310
+ </div>
311
+ );
312
+ }
313
+
314
+ if (inputType === "custom") {
315
+ const customRender = form.construction as formCustomConstructionProps;
316
+ return (
317
+ <div key={key} className={cn(form.className, generateColClass(form.col || "12"))}>
318
+ {customRender?.({ formControl, values, setValues, errors, setErrors, setRegister, prefixName: prefix })}
319
+ </div>
320
+ );
321
+ }
322
+
323
+ const Component = inputMap[inputType] || InputComponent;
324
+ return (
325
+ <div key={key} className={cn(form.className, generateColClass(form.col || "12"))}>
326
+ <Component
327
+ {...(form.construction as any)}
328
+ {...formControl(name)}
329
+ // autoFocus={key === 0}
330
+ />
331
+ </div>
332
+ );
333
+ };
334
+
335
+ return (
336
+ <>
337
+ {title && <h4 className={cn("text-lg font-semibold mb-4", pcn<CT>(className, "title"))}>{title}</h4>}
338
+
339
+ {modal == "success" ? (
340
+ <div className="flex flex-col items-center justify-center h-full py-6 transition-all duration-300 animate-intro-down">
341
+ <div className="w-12 h-12 rounded-full bg-blue-50 flex items-center justify-center">
342
+ <Icon icon={"solid/check"} className="text-primary text-2xl" />
343
+ </div>
344
+ <p className="text-primary text-lg font-semibold mt-4">{successMessage || "Berhasil disimpan!"}</p>
345
+ </div>
346
+ ) : (
347
+ <form id={id} className={cn("flex flex-col h-full pb-8")} onSubmit={submit}>
348
+ <div className={cn("grid grid-cols-12 gap-4 flex-1 content-start", pcn<CT>(className, "base"))}>
349
+ {fresh && fields.map((f, i) => renderInput(f, i))}
350
+
351
+ {!footerControl && (
352
+ <div className="hidden md:block col-span-12">
353
+ <div className="flex justify-end mt-4">
354
+ <ButtonComponent
355
+ type="submit"
356
+ label={l.base?.save ? l.base.save() : "Save"}
357
+ icon={"solid/save"}
358
+ loading={loading}
359
+ className={pcn<CT>(className, "submit")}
360
+ />
361
+ </div>
362
+ </div>
363
+ )}
364
+
365
+ {!footerControl && (
366
+ <div className={"md:hidden col-span-12 mt-4"}>
367
+ <ButtonComponent
368
+ label={l.base?.save ? l.base.save() : "Save"}
369
+ icon={"solid/save"}
370
+ type="submit"
371
+ loading={loading}
372
+ block
373
+ className={pcn<CT>(className, "submit")}
374
+ />
375
+ </div>
376
+ )}
377
+
378
+ {footerControl && (
379
+ <div className="col-span-12">{footerControl?.({ loading })}</div>
380
+ )}
381
+
382
+ {modal == "failed" && (
383
+ <div className="mt-4 w-full p-4 rounded-sm border border-danger bg-light-danger flex gap-4 items-center">
384
+ <div>
385
+ <div className="w-10 h-10 rounded-full bg-danger/20 flex items-center justify-center">
386
+ <Icon icon={"solid/exclamation-triangle"} className="text-danger text-lg" />
387
+ </div>
388
+ </div>
389
+ <p className="text-danger text-sm font-semibold">{"Terjadi Masalah, Coba ulangi lagi!"}</p>
390
+ </div>
391
+ )}
392
+ </div>
393
+ </form>
394
+ )}
395
+
396
+ <ModalConfirmComponent
397
+ show={confirm.show}
398
+ onClose={() => confirm.onClose()}
399
+ title={l.base?.confirmTitle ? l.base.confirmTitle() : "Yakin"}
400
+ submitControl={{ onSubmit: () => confirm?.onConfirm(), paint: "primary" }}
401
+ />
402
+ </>
403
+ );
404
+ }