@skalfa/skalfa-component 1.0.7 → 1.0.8

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 (58) hide show
  1. package/package.json +2 -2
  2. package/src/accordion/Accordion.component.tsx +87 -0
  3. package/src/breadcrumb/Breadcrumb.component.tsx +79 -0
  4. package/src/button/Button.component.tsx +89 -0
  5. package/src/card/AlertCard.component.tsx +69 -0
  6. package/src/card/Card.component.tsx +25 -0
  7. package/src/card/DashboardCard.component.tsx +44 -0
  8. package/src/card/GalleryCard.component.tsx +50 -0
  9. package/src/card/ProductCard.component.tsx +65 -0
  10. package/src/card/ProfileCard.component.tsx +71 -0
  11. package/src/carousel/Carousel.component.tsx +111 -0
  12. package/src/chip/Chip.component.tsx +39 -0
  13. package/src/index.ts +70 -0
  14. package/src/input/Checkbox.component.tsx +102 -0
  15. package/src/input/Input.component.tsx +334 -0
  16. package/src/input/InputCheckbox.component.tsx +174 -0
  17. package/src/input/InputCurrency.component.tsx +165 -0
  18. package/src/input/InputDate.component.tsx +356 -0
  19. package/src/input/InputDatetime.component.tsx +267 -0
  20. package/src/input/InputDocument.component.tsx +360 -0
  21. package/src/input/InputImage.component.tsx +535 -0
  22. package/src/input/InputNumber.component.tsx +194 -0
  23. package/src/input/InputOtp.component.tsx +169 -0
  24. package/src/input/InputPassword.component.tsx +245 -0
  25. package/src/input/InputRadio.component.tsx +174 -0
  26. package/src/input/InputTime.component.tsx +280 -0
  27. package/src/input/InputValues.component.tsx +71 -0
  28. package/src/input/Radio.component.tsx +98 -0
  29. package/src/input/Select.component.tsx +557 -0
  30. package/src/modal/BottomSheet.component.tsx +246 -0
  31. package/src/modal/FloatingPage.component.tsx +103 -0
  32. package/src/modal/Modal.component.tsx +95 -0
  33. package/src/modal/ModalConfirm.component.tsx +219 -0
  34. package/src/modal/Toast.component.tsx +125 -0
  35. package/src/nav/Bottombar.component.tsx +72 -0
  36. package/src/nav/Footer.component.tsx +177 -0
  37. package/src/nav/Headbar.component.tsx +33 -0
  38. package/src/nav/Navbar.component.tsx +138 -0
  39. package/src/nav/Sidebar.component.tsx +298 -0
  40. package/src/nav/Tabbar.component.tsx +61 -0
  41. package/src/nav/Wizard.component.tsx +80 -0
  42. package/src/supervision/FormSupervision.component.tsx +425 -0
  43. package/src/supervision/TableSupervision.component.tsx +688 -0
  44. package/src/table/ControlBar.component.tsx +501 -0
  45. package/src/table/FilterComponent.tsx +519 -0
  46. package/src/table/Pagination.component.tsx +152 -0
  47. package/src/table/Table.component.tsx +436 -0
  48. package/src/types.d.ts +7 -0
  49. package/src/typography/TypographyArticle.component.tsx +26 -0
  50. package/src/typography/TypographyColumn.component.tsx +20 -0
  51. package/src/typography/TypographyContent.component.tsx +20 -0
  52. package/src/typography/TypographyTips.component.tsx +20 -0
  53. package/src/wrap/Draggable.component.tsx +303 -0
  54. package/src/wrap/Image.component.tsx +10 -0
  55. package/src/wrap/OutsideClick.component.tsx +48 -0
  56. package/src/wrap/ScrollContainer.component.tsx +107 -0
  57. package/src/wrap/ShortcutProvider.tsx +57 -0
  58. package/src/wrap/Swipe.component.tsx +121 -0
@@ -0,0 +1,501 @@
1
+ "use client"
2
+
3
+ import { ReactNode, useEffect, useRef } from 'react'
4
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5
+ import { faArrowDownZA, faArrowUpAZ, faEllipsisV, faEyeLowVision, faMagnifyingGlass, faPlus, faRefresh, faSearch, faSliders, faSort } from '@fortawesome/free-solid-svg-icons';
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, iop) => iop == 0 || 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={faPlus}
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={faMagnifyingGlass}
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={faSearch}
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={faEyeLowVision}
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={faSort}
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
+ <FontAwesomeIcon icon={sortBy == "desc" ? faArrowDownZA : faArrowUpAZ} 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={faRefresh}
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={faSliders}
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={faEllipsisV}
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 != "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={faMagnifyingGlass}
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={faSearch}
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
+ <FontAwesomeIcon icon={sortBy == "desc" ? faArrowDownZA : faArrowUpAZ} 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
+ }