@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,501 @@
1
+ "use client"
2
+ import { Icon, type IconName } from "@skalfa/skalfa-icon";
3
+
4
+
5
+ import { ReactNode, useEffect, useRef } from 'react'
6
+ import { ApiFilterType, cn, conversion, shortcut, useResponsive } from '@utils';
7
+ import { useToggleContext } from '@contexts';
8
+ import { InputCheckboxComponent } from "../input/InputCheckbox.component";
9
+ import { InputComponent } from "../input/Input.component";
10
+ import { SelectComponent } from "../input/Select.component";
11
+ import { OutsideClickComponent } from "../wrap/OutsideClick.component";
12
+ import { ButtonComponent } from "../button/Button.component";
13
+ import { BottomSheetComponent } from "../modal/BottomSheet.component";
14
+ import { FilterComponent, FilterColumnOption } from "./FilterComponent";
15
+
16
+ export type ControlBarOptionType = "SEARCH" | "SEARCHABLE" | "FILTER" | "SELECTABLE" | "REFRESH" | ReactNode;
17
+
18
+ export interface ControlBarProps {
19
+ id ?: string;
20
+ options ?: ControlBarOptionType[];
21
+ className ?: string
22
+ search ?: string,
23
+ onSearch ?: (searchable: string) => void,
24
+ searchableOptions ?: {label: string | ReactNode, selector: string}[]
25
+ searchable ?: string[],
26
+ onSearchable ?: (searchable: string[]) => void,
27
+ selectableOptions ?: {label: string | ReactNode, selector: string}[]
28
+ selectable ?: string[],
29
+ onSelectable ?: (searchable: string[]) => void,
30
+ sortableOptions ?: {label: string | ReactNode, selector: string}[]
31
+ sort ?: string[],
32
+ onSort ?: (sort: string[]) => void,
33
+ filterableColumns ?: FilterColumnOption[],
34
+ onFilter ?: (filters: ApiFilterType[]) => void,
35
+ filter ?: ApiFilterType[],
36
+ onRefresh ?: () => void,
37
+ }
38
+
39
+ export function ControlBarComponent({
40
+ id = "",
41
+ options,
42
+ className,
43
+ search,
44
+ onSearch,
45
+ searchableOptions,
46
+ searchable,
47
+ onSearchable,
48
+ selectableOptions,
49
+ selectable,
50
+ onSelectable,
51
+ sortableOptions,
52
+ sort,
53
+ onSort,
54
+ filterableColumns,
55
+ onFilter,
56
+ filter,
57
+ onRefresh
58
+ }: ControlBarProps) {
59
+ const {toggle, setToggle} = useToggleContext()
60
+ const { isSm } = useResponsive();
61
+
62
+ const searchRef = useRef<HTMLInputElement | null>(null);
63
+
64
+ useEffect(() => {
65
+ if (options?.includes("SEARCH")) {
66
+ shortcut.register("ctrl+s", () => {
67
+ searchRef.current?.focus()
68
+ }, "Cari")
69
+ }
70
+
71
+ if (options?.includes("FILTER")) {
72
+ shortcut.register("ctrl+f", () => {
73
+ setToggle("FILTER")
74
+ }, "Filter")
75
+ }
76
+
77
+ if (options?.includes("SORT")) {
78
+ shortcut.register("ctrl+o", () => {
79
+ setToggle("SORT")
80
+ }, "Urutkan")
81
+ }
82
+
83
+ if (options?.includes("SELECTABLE")) {
84
+ shortcut.register("ctrl+l", () => {
85
+ setToggle("SELECTABLE")
86
+ }, "Kolom Ditampilkan")
87
+ }
88
+
89
+ if (options?.includes("REFRESH")) {
90
+ shortcut.register("ctrl+shift+r", () => {
91
+ onRefresh?.()
92
+ }, "Refresh Tabel")
93
+ }
94
+
95
+ return () => {
96
+ options?.includes("SEARCH") && shortcut.unregister("ctrl+s")
97
+ options?.includes("FILTER") && shortcut.unregister("ctrl+f")
98
+ options?.includes("SORT") && shortcut.unregister("ctrl+o")
99
+ options?.includes("SELECTABLE") && shortcut.unregister("ctrl+l")
100
+ options?.includes("REFRESH") && shortcut.unregister("ctrl+shift+r")
101
+ }
102
+ }, [options])
103
+
104
+ const onChangeSort = (item: string) => {
105
+ if(!!sort?.find((s) => s.split(" ")?.at(0) == item)) {
106
+ const findSort = sort.find((s) => s.split(" ")?.at(0) == item);
107
+
108
+ if (findSort?.split(" ")?.at(1) == "desc") {
109
+ onSort?.(sort.filter((s) => s != findSort));
110
+ } else {
111
+ const newSorts = [...sort];
112
+ newSorts[newSorts?.findIndex((s) => s == findSort)] = `${item} desc`
113
+ onSort?.(newSorts);
114
+ }
115
+ } else {
116
+ onSort?.([...(sort || []), `${item} asc`])
117
+ }
118
+ }
119
+
120
+ return (
121
+ <>
122
+ <div className={cn("control-bar", className)}>
123
+ {(isSm ? [
124
+ ...(options?.filter((op) => op == "SEARCH" || op == "REFRESH") || []),
125
+ ...(options && options?.length > 1 ? ['MOBILE_OPTION'] : [])
126
+ ] : options)?.map((option: ControlBarOptionType, key: number) => {
127
+ {
128
+ // =========================>
129
+ // ## Create button
130
+ // =========================>
131
+ }
132
+ if (option == "CREATE") {
133
+ return (
134
+ <div className="control-bar-create-wrapper" key="button-add">
135
+ <ButtonComponent
136
+ icon="solid/plus"
137
+ label="Tambah Data"
138
+ size="sm"
139
+ onClick={() => setToggle(`MODAL_FORM_${conversion.strSnake(id).toUpperCase()}`)}
140
+ />
141
+ </div>
142
+ );
143
+ }
144
+
145
+ {
146
+ // =========================>
147
+ // ## Search Field
148
+ // =========================>
149
+ }
150
+ if (option == "SEARCH") {
151
+ const searchable = !!options?.find((option) => option == "SEARCHABLE");
152
+
153
+ return (
154
+ <div className={cn("control-bar-search-wrapper", searchable ? "control-bar-search-wrapper-searchable" : "control-bar-search-wrapper-standalone")} key={key}>
155
+ <InputComponent
156
+ ref={searchRef}
157
+ name="search"
158
+ placeholder="Cari disini..."
159
+ rightIcon="solid/magnifying-glass"
160
+ value={search}
161
+ onChange={(e) => onSearch?.(e)}
162
+ className={cn("control-bar-search-input", searchable && "control-bar-search-input-searchable")}
163
+ />
164
+ </div>
165
+ );
166
+ }
167
+
168
+ {
169
+ // =========================>
170
+ // ## Searchable Field
171
+ // =========================>
172
+ }
173
+ if (option == "SEARCHABLE") {
174
+ return searchableOptions?.length ? (
175
+ <div className="control-bar-searchable-wrapper" key={key}>
176
+ <SelectComponent
177
+ name="searchableColumn"
178
+ leftIcon="solid/search"
179
+ options={searchableOptions?.map((column) => {
180
+ return {
181
+ label: column.label,
182
+ value: column.selector,
183
+ };
184
+ }) || []}
185
+ value={searchable}
186
+ onChange={(e) => onSearchable?.(e as string[])}
187
+ className="control-bar-searchable-select"
188
+ multiple
189
+ />
190
+ </div>
191
+ ) : <></>;
192
+ }
193
+
194
+ {
195
+ // =========================>
196
+ // ## Selectable Button
197
+ // =========================>
198
+ }
199
+ if (option == "SELECTABLE") {
200
+ return (
201
+ <div className="control-bar-button-wrapper" key={key}>
202
+ <ButtonComponent
203
+ icon="solid/eye-low-vision"
204
+ variant="outline"
205
+ className="control-bar-icon-button"
206
+ onClick={() => setToggle("SELECTABLE")}
207
+ size="sm"
208
+ />
209
+ <OutsideClickComponent onOutsideClick={() => setToggle("SELECTABLE", false)}>
210
+ <div
211
+ className={cn(
212
+ "control-bar-dropdown",
213
+ !toggle.SELECTABLE && "control-bar-dropdown-hidden"
214
+ )}
215
+ >
216
+ <p className='control-bar-dropdown-title'>Kolom Ditampilkan</p>
217
+ <InputCheckboxComponent
218
+ vertical
219
+ name="show_column"
220
+ options={selectableOptions?.map((option) => {
221
+ return {
222
+ label: option.label as string,
223
+ value: option.selector,
224
+ };
225
+ })}
226
+ onChange={(e) => onSelectable?.(Array().concat(e).map((val) => String(val)))}
227
+ value={selectable}
228
+ className='control-bar-selectable-checkbox-list'
229
+ classNameCheckbox='control-bar-selectable-checkbox label::text-xs'
230
+ />
231
+ </div>
232
+ </OutsideClickComponent>
233
+ </div>
234
+ );
235
+ }
236
+
237
+ {
238
+ // =========================>
239
+ // ## Sort Button
240
+ // =========================>
241
+ }
242
+ if (option == "SORT") {
243
+ return sortableOptions?.length ? (
244
+ <div className="control-bar-button-wrapper" key={key}>
245
+ <ButtonComponent
246
+ icon="solid/sort"
247
+ variant="outline"
248
+ className="control-bar-icon-button"
249
+ onClick={() => setToggle("SORT")}
250
+ size="sm"
251
+ />
252
+ <OutsideClickComponent onOutsideClick={() => setToggle("SORT", false)}>
253
+ <div
254
+ className={cn(
255
+ "control-bar-dropdown",
256
+ !toggle.SORT && "control-bar-dropdown-hidden"
257
+ )}
258
+ >
259
+ <p className='control-bar-dropdown-title'>Urut Berdasarkan</p>
260
+ <div className='control-bar-sort-list'>
261
+ {sortableOptions?.map((option, key) => {
262
+ const sortBy = sort?.find((s) => s.split(" ")?.at(0) == option?.selector)?.split(" ")?.at(1) || "";
263
+ return (
264
+ <div
265
+ key={key}
266
+ className={cn('control-bar-sort-item', !!sortBy && "control-bar-sort-item-active")}
267
+ onClick={() => onChangeSort(option.selector)}
268
+ >
269
+ <p>{option.label}</p>
270
+
271
+ {sortBy && (
272
+ <div className='control-bar-sort-active-icon'>
273
+ <Icon icon={sortBy == "desc" ? "solid/arrow-down-z-a" : "solid/arrow-up-a-z"} className='control-bar-sort-icon' />
274
+ {sort?.length && sort?.length > 1 && <span className='control-bar-sort-badge'>{sort.findIndex((s) => s.split(" ")?.at(0) == option?.selector) + 1}</span>}
275
+ </div>
276
+ )}
277
+ </div>
278
+ )
279
+ })}
280
+ </div>
281
+ </div>
282
+ </OutsideClickComponent>
283
+ </div>
284
+ ) : <></>;
285
+ }
286
+
287
+ {
288
+ // =========================>
289
+ // ## Refresh Button
290
+ // =========================>
291
+ }
292
+ if (option == "REFRESH") {
293
+ return (
294
+ <div className="control-bar-button-wrapper-refresh" key={key}>
295
+ <ButtonComponent
296
+ icon="solid/refresh"
297
+ variant="outline"
298
+ className="control-bar-icon-button"
299
+ onClick={() => onRefresh?.()}
300
+ size="sm"
301
+ />
302
+ </div>
303
+ );
304
+ }
305
+
306
+ {
307
+ // =========================>
308
+ // ## Filter Button
309
+ // =========================>
310
+ }
311
+ if (option == "FILTER") {
312
+ return (
313
+ <div className="control-bar-button-wrapper" key={key}>
314
+ <ButtonComponent
315
+ icon="solid/sliders"
316
+ label="Filter"
317
+ variant="outline"
318
+ className="control-bar-filter-button"
319
+ onClick={() => setToggle("FILTER")}
320
+ size="sm"
321
+ />
322
+ </div>
323
+ );
324
+ }
325
+
326
+ {
327
+ // =========================>
328
+ // ## Mobile option button
329
+ // =========================>
330
+ }
331
+ if (option == "MOBILE_OPTION") {
332
+ return (
333
+ <div className="control-bar-button-wrapper" key={key}>
334
+ <ButtonComponent
335
+ icon="solid/ellipsis-v"
336
+ variant="outline"
337
+ className="control-bar-icon-button"
338
+ onClick={() => setToggle("MOBILE_OPTION")}
339
+ size="sm"
340
+ />
341
+ </div>
342
+ );
343
+ }
344
+
345
+ return option;
346
+ })}
347
+ </div>
348
+
349
+ {!!filterableColumns && !!filterableColumns?.length && (
350
+ <FilterComponent
351
+ className={cn(
352
+ !toggle.FILTER ? "control-bar-filter-panel-hidden" : "control-bar-filter-panel"
353
+ )}
354
+ columns={filterableColumns}
355
+ onChange={onFilter}
356
+ value={filter}
357
+ onMinimize={() => setToggle("FILTER")}
358
+ />
359
+ )}
360
+
361
+ {isSm && (
362
+ <BottomSheetComponent
363
+ show={!!toggle["MOBILE_OPTION"]}
364
+ onClose={() => setToggle("MOBILE_OPTION", false)}
365
+ maxSize="98vh"
366
+ >
367
+ <div className='control-bar-mobile-container'>
368
+ {options?.filter((op, iop) => (iop != 0 && op != "CREATE" && op != "SEARCH" && op != "REFRESH"))?.map((option: ControlBarOptionType, key: number) => {
369
+ {
370
+ // =========================>
371
+ // ## Search Field
372
+ // =========================>
373
+ }
374
+ if (option == "SEARCH") {
375
+ return (
376
+ <div key={key}>
377
+ <InputComponent
378
+ name="search"
379
+ placeholder="Cari disini..."
380
+ rightIcon="solid/magnifying-glass"
381
+ value={search}
382
+ onChange={(e) => onSearch?.(e)}
383
+ />
384
+ </div>
385
+ );
386
+ }
387
+
388
+ {
389
+ // =========================>
390
+ // ## Searchable Field
391
+ // =========================>
392
+ }
393
+ if (option == "SEARCHABLE") {
394
+ return searchableOptions?.length ? (
395
+ <div key={key}>
396
+ <SelectComponent
397
+ name="searchableColumn"
398
+ leftIcon="solid/search"
399
+ options={searchableOptions?.map((column) => {
400
+ return {
401
+ label: column.label,
402
+ value: column.selector,
403
+ };
404
+ }) || []}
405
+ value={searchable}
406
+ onChange={(e) => onSearchable?.(e as string[])}
407
+ multiple
408
+ />
409
+ </div>
410
+ ) : <></>;
411
+ }
412
+
413
+ {
414
+ // =========================>
415
+ // ## Selectable Button
416
+ // =========================>
417
+ }
418
+ if (option == "SELECTABLE") {
419
+ return (
420
+ <div key={key}>
421
+ <p className='control-bar-mobile-title'>Kolom Ditampilkan</p>
422
+ <InputCheckboxComponent
423
+ vertical
424
+ name="show_column"
425
+ options={selectableOptions?.map((option) => {
426
+ return {
427
+ label: option.label as string,
428
+ value: option.selector,
429
+ };
430
+ })}
431
+ onChange={(e) => onSelectable?.(Array().concat(e).map((val) => String(val)))}
432
+ value={selectable}
433
+ className='control-bar-mobile-checkbox-list'
434
+ classNameCheckbox='control-bar-selectable-checkbox label::text-xs'
435
+ />
436
+ </div>
437
+ );
438
+ }
439
+
440
+ {
441
+ // =========================>
442
+ // ## Sort Button
443
+ // =========================>
444
+ }
445
+ if (option == "SORT") {
446
+ return sortableOptions?.length ? (
447
+ <div key={key}>
448
+ <p className='control-bar-mobile-title'>Urut Berdasarkan</p>
449
+ <div className='flex flex-col'>
450
+ {sortableOptions?.map((option, key) => {
451
+ const sortBy = sort?.find((s) => s.split(" ")?.at(0) == option?.selector)?.split(" ")?.at(1) || "";
452
+ return (
453
+ <div
454
+ key={key}
455
+ className={cn('control-bar-mobile-sort-item', !!sortBy && "control-bar-mobile-sort-item-active")}
456
+ onClick={() => onChangeSort(option.selector)}
457
+ >
458
+ <p>{option.label}</p>
459
+
460
+ {sortBy && (
461
+ <div className='text-primary'>
462
+ <Icon icon={sortBy == "desc" ? "solid/arrow-down-z-a" : "solid/arrow-up-a-z"} className='text-xs' />
463
+ {sort?.length && sort?.length > 1 && <span className='text-[9px] ml-1'>{sort.findIndex((s) => s.split(" ")?.at(0) == option?.selector) + 1}</span>}
464
+ </div>
465
+ )}
466
+ </div>
467
+ )
468
+ })}
469
+ </div>
470
+ </div>
471
+ ) : <></>;
472
+ }
473
+
474
+ {
475
+ // =========================>
476
+ // ## Filter
477
+ // =========================>
478
+ }
479
+ if (option == "FILTER") {
480
+ return (
481
+ <div key={key}>
482
+ {filterableColumns && filterableColumns?.length && (
483
+ <FilterComponent
484
+ className='control-bar-mobile-filter title::text-xs'
485
+ columns={filterableColumns}
486
+ onChange={onFilter}
487
+ value={filter}
488
+ />
489
+ )}
490
+ </div>
491
+ );
492
+ }
493
+
494
+ return option;
495
+ })}
496
+ </div>
497
+ </BottomSheetComponent>
498
+ )}
499
+ </>
500
+ )
501
+ }