@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,360 @@
1
+ "use client"
2
+ import { Icon, type IconName } from "@skalfa/skalfa-icon";
3
+
4
+
5
+ import { useState, useRef, InputHTMLAttributes, ReactNode } from "react";
6
+ import { cn, pcn, registry, useInputHandler, useInputRandomId, useResponsive, useValidation, validation, ValidationRules } from "@utils";
7
+ import { ButtonComponent } from "../button/Button.component";
8
+ import { FloatingPageComponent } from "../modal/FloatingPage.component";
9
+ import { BottomSheetComponent } from "../modal/BottomSheet.component";
10
+
11
+ const DocumentViewerComponent = (props: any) => {
12
+ const Comp = registry.get("DocumentViewerComponent");
13
+ return Comp ? <Comp {...props} /> : null;
14
+ };
15
+
16
+ const DocumentViewerIcon = (ext: string) => {
17
+ const getIcon = registry.get("DocumentViewerIcon");
18
+ return getIcon ? getIcon(ext) : null;
19
+ };
20
+
21
+
22
+
23
+ type DocFile = {
24
+ id: string;
25
+ file: File;
26
+ url: string;
27
+ type: string;
28
+ };
29
+
30
+ type CT = "label" | "tip" | "error" | "base" | "icon";
31
+
32
+ export interface InputDocumentProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "onChange"> {
33
+ label ?: string;
34
+ tip ?: string | ReactNode;
35
+ leftIcon ?: any;
36
+ rightIcon ?: any;
37
+
38
+ value ?: any;
39
+ invalid ?: string;
40
+
41
+ validations ?: ValidationRules;
42
+
43
+ onChange ?: (value: any) => any;
44
+ register ?: (name: string, validations ?: ValidationRules) => void;
45
+ unregister ?: (name: string) => void;
46
+
47
+ /** Use custom class with: "label::", "tip::", "error::", "icon::", "suggest::", "suggest-item::". */
48
+ className ?: string;
49
+ }
50
+
51
+
52
+
53
+ export function InputDocumentComponent({
54
+ label,
55
+ tip,
56
+ leftIcon,
57
+ rightIcon,
58
+ className = "",
59
+
60
+ value,
61
+ invalid,
62
+
63
+ validations,
64
+
65
+ register,
66
+ unregister,
67
+ onChange,
68
+
69
+ ...props
70
+ } : InputDocumentProps) {
71
+ const { isSm } = useResponsive();
72
+
73
+ // =========================>
74
+ // ## Initial
75
+ // =========================>
76
+ const inputHandler = useInputHandler(props.name, value, validations, register, props.type == "file")
77
+ const randomId = useInputRandomId()
78
+
79
+ // =========================>
80
+ // ## Invalid handler
81
+ // =========================>
82
+ const [invalidMessage] = useValidation(inputHandler.value, validations, invalid, inputHandler.idle);
83
+
84
+
85
+ return (
86
+ <>
87
+ <div className="input-container">
88
+ <label
89
+ htmlFor={randomId}
90
+ className={cn(
91
+ "input-label",
92
+ props.disabled && "input-label-disabled",
93
+ inputHandler.focus && "input-label-focus",
94
+ !!invalidMessage && "input-label-error",
95
+ pcn<CT>(className, "label"),
96
+ props.disabled && pcn<CT>(className, "label", "disabled"),
97
+ inputHandler.focus && pcn<CT>(className, "label", "focus"),
98
+ !!invalidMessage && pcn<CT>(className, "label", "focus"),
99
+ )}
100
+ >
101
+ {label}
102
+ {validations && validation.hasRules(validations, "required") && <span className="text-danger ml-1">*</span>}
103
+ </label>
104
+
105
+ {tip && (
106
+ <small
107
+ className={cn(
108
+ "input-tip",
109
+ props.disabled && "input-tip-disabled",
110
+ pcn<CT>(className, "tip"),
111
+ props.disabled && pcn<CT>(className, "tip", "disabled"),
112
+ )}
113
+ >{tip}</small>
114
+ )}
115
+
116
+ <div className="relative">
117
+ <input
118
+ {...props}
119
+ id={randomId}
120
+ placeholder={!inputHandler.value ? props.placeholder : ""}
121
+ className={cn(
122
+ "input cursor-pointer",
123
+ props.type == "file" && "input-file",
124
+ leftIcon && "input-with-left-icon",
125
+ rightIcon && "input-with-right-icon",
126
+ pcn<CT>(className, "base"),
127
+ !!invalidMessage && "input-error",
128
+ !!invalidMessage && pcn<CT>(className, "base", "error"),
129
+ )}
130
+ onFocus={(e) => {
131
+ props.onFocus?.(e);
132
+ inputHandler.setFocus(true);
133
+ }}
134
+ inputMode="none"
135
+ />
136
+
137
+
138
+ {(inputHandler.value) && (
139
+ <div
140
+ className={cn(
141
+ "input-document-values",
142
+ leftIcon ? "input-document-values-left-icon" : "input-document-values-no-icon"
143
+ )}
144
+ style={{ maxWidth: `calc(100% - ${leftIcon ? "5.2rem" : "2rem"})` }}
145
+ onClick={() => {
146
+ inputHandler.setFocus(true);
147
+ }}
148
+ >
149
+ {(Array.isArray(inputHandler.value) ? inputHandler.value : []).map((f) => {
150
+ return (
151
+ <span
152
+ key={f.id}
153
+ className="input-document-value-item"
154
+ >
155
+ <Icon icon={DocumentViewerIcon(f.file.name.split(".").pop()?.toLowerCase())} className="text-light-foreground" />
156
+ <span className="line-clamp-1">{f.file.name}</span>
157
+ </span>
158
+ )
159
+ })}
160
+ </div>
161
+ )}
162
+
163
+ {leftIcon && (
164
+ <Icon
165
+ className={cn(
166
+ "input-icon",
167
+ "input-icon-left",
168
+ props.disabled && "input-icon-disabled",
169
+ inputHandler.focus && "input-icon-focus",
170
+ pcn<CT>(className, "icon"),
171
+ props.disabled && pcn<CT>(className, "icon", "disabled"),
172
+ inputHandler.focus && pcn<CT>(className, "icon", "focus"),
173
+ )}
174
+ icon={leftIcon}
175
+ />
176
+ )}
177
+
178
+ {rightIcon && (
179
+ <Icon
180
+ className={cn(
181
+ "input-icon",
182
+ "input-icon-right",
183
+ props.disabled && "input-icon-disabled",
184
+ inputHandler.focus && "input-icon-focus",
185
+ pcn<CT>(className, "icon"),
186
+ props.disabled && pcn<CT>(className, "icon", "disabled"),
187
+ inputHandler.focus && pcn<CT>(className, "icon", "focus"),
188
+ )}
189
+ icon={rightIcon}
190
+ />
191
+ )}
192
+ </div>
193
+
194
+ {invalidMessage && <small className={cn("input-error-message", pcn<CT>(className, "error"))}>{invalidMessage}</small>}
195
+ </div>
196
+
197
+ {!isSm ? (
198
+ <FloatingPageComponent
199
+ show={inputHandler.focus}
200
+ onClose={() => inputHandler.setFocus(false)}
201
+ title={label}
202
+ footer={
203
+ <ButtonComponent
204
+ label="Selesai"
205
+ variant="outline"
206
+ onClick={() => inputHandler.setFocus(false)}
207
+ block
208
+ />
209
+ }
210
+ >
211
+ <InputDocumentPicker
212
+ value={inputHandler.value}
213
+ onChange={(e) => {
214
+ inputHandler.setValue(e);
215
+ if (inputHandler.idle) inputHandler.setIdle(false);
216
+ onChange?.(e)
217
+ }}
218
+ />
219
+
220
+ </FloatingPageComponent>
221
+ ) : (
222
+ <BottomSheetComponent
223
+ show={inputHandler.focus}
224
+ onClose={() => inputHandler.setFocus(false)}
225
+ size={'98vh'}
226
+ footer={
227
+ <ButtonComponent
228
+ label="Selesai"
229
+ variant="outline"
230
+ onClick={() => inputHandler.setFocus(false)}
231
+ block
232
+ />
233
+ }
234
+ >
235
+ <InputDocumentPicker
236
+ value={inputHandler.value}
237
+ onChange={(e) => {
238
+ inputHandler.setValue(e);
239
+ if (inputHandler.idle) inputHandler.setIdle(false);
240
+ onChange?.(e)
241
+ }}
242
+ />
243
+
244
+ </BottomSheetComponent>
245
+ )}
246
+ </>
247
+ );
248
+ }
249
+
250
+
251
+ export interface InputDocumentPickerProps {
252
+ value ?: any[];
253
+ onChange ?: (value: any[]) => void;
254
+ }
255
+
256
+ export const InputDocumentPicker: React.FC<InputDocumentPickerProps> = ({ value, onChange }) => {
257
+ const { isSm } = useResponsive();
258
+ const fileInputRef = useRef<HTMLInputElement>(null);
259
+
260
+ const [previewActive, setPreviewActive] = useState<string | null>(null);
261
+
262
+ const files: DocFile[] = Array.isArray(value) ? value : [];
263
+
264
+ function updateFiles(next: DocFile[]) {
265
+ onChange?.(next);
266
+ }
267
+
268
+ function handleFilePick(e: React.ChangeEvent<HTMLInputElement>) {
269
+ const picked = e.target.files;
270
+ if (!picked) return;
271
+
272
+ const next = [...files];
273
+
274
+ Array.from(picked).forEach((f) => {
275
+ const id = Math.random().toString(36).substring(7);
276
+ next.push({
277
+ id,
278
+ file: f,
279
+ url: URL.createObjectURL(f),
280
+ type: f.type,
281
+ });
282
+
283
+ if (!previewActive) setPreviewActive(id);
284
+ });
285
+
286
+ updateFiles(next);
287
+ e.target.value = "";
288
+ }
289
+
290
+ function removeFile(id: string) {
291
+ const next = files.filter((x) => x.id !== id);
292
+
293
+ updateFiles(next);
294
+
295
+ if (previewActive === id) {
296
+ setPreviewActive(next[0]?.id || null);
297
+ }
298
+ }
299
+
300
+ return (<>
301
+ <div className="p-4 flex flex-col gap-4">
302
+ <div className="input-document-dropzone">
303
+ {files.find((x) => x.id === previewActive)?.file ? (
304
+ <DocumentViewerComponent
305
+ file={files.find((x) => x.id === previewActive)?.file}
306
+ />
307
+ ) : (
308
+ <div className="input-document-empty-box" onClick={() => fileInputRef.current?.click()}>
309
+ <Icon icon="solid/plus" className="text-3xl" />
310
+ <p className="text-lg">Tambah Dokumen</p>
311
+ </div>
312
+ )}
313
+ </div>
314
+
315
+ <input
316
+ ref={fileInputRef}
317
+ type="file"
318
+ multiple
319
+ className="hidden"
320
+ onChange={handleFilePick}
321
+ />
322
+
323
+ <div className="input-document-thumbs-container">
324
+ <div className="input-document-thumbs-grid">
325
+ {files.map((f) => {
326
+ return (
327
+ <div
328
+ key={f.id}
329
+ className={cn(
330
+ "input-document-thumb-wrapper",
331
+ previewActive === f.id && "input-document-thumb-wrapper-active"
332
+ )}
333
+ onClick={() => setPreviewActive(f.id)}
334
+ >
335
+ <DocumentViewerComponent
336
+ file={f.file}
337
+ mode="thumb"
338
+ />
339
+
340
+ <ButtonComponent
341
+ icon="solid/times"
342
+ onClick={() => removeFile(f.id)}
343
+ variant="light"
344
+ paint="danger"
345
+ className="absolute top-2 right-2"
346
+ size={isSm ? "xs" : "sm"}
347
+ />
348
+ </div>
349
+ );
350
+ })}
351
+
352
+ <div className="input-document-upload-box" onClick={() => fileInputRef.current?.click()}>
353
+ <Icon icon="solid/plus" />
354
+ <p className="text-[10px] text-center">Tambah Dokumen</p>
355
+ </div>
356
+ </div>
357
+ </div>
358
+ </div>
359
+ </>)
360
+ }