@skalfa/skalfa-app 1.0.0

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 (112) hide show
  1. package/.env.example +44 -0
  2. package/README.md +28 -0
  3. package/app/auth/edit/page.tsx +65 -0
  4. package/app/auth/login/page.tsx +63 -0
  5. package/app/auth/me/page.tsx +58 -0
  6. package/app/auth/register/page.tsx +69 -0
  7. package/app/auth/verify/page.tsx +53 -0
  8. package/app/dashboard/layout.tsx +47 -0
  9. package/app/dashboard/page.tsx +9 -0
  10. package/app/dashboard/user/page.tsx +77 -0
  11. package/app/index.ts +14 -0
  12. package/app/layout.tsx +38 -0
  13. package/app/page.tsx +13 -0
  14. package/barrels.json +6 -0
  15. package/blueprints/starter.blueprint.json +103 -0
  16. package/components/base.components/accordion/Accordion.component.tsx +82 -0
  17. package/components/base.components/breadcrumb/Breadcrumb.component.tsx +80 -0
  18. package/components/base.components/button/Button.component.tsx +91 -0
  19. package/components/base.components/button/IconButton.component.tsx +88 -0
  20. package/components/base.components/button/button.decorate.ts +82 -0
  21. package/components/base.components/card/AlertCard.component.tsx +69 -0
  22. package/components/base.components/card/Card.component.tsx +25 -0
  23. package/components/base.components/card/DashboardCard.component.tsx +44 -0
  24. package/components/base.components/card/GalleryCard.component.tsx +50 -0
  25. package/components/base.components/card/ProductCard.component.tsx +65 -0
  26. package/components/base.components/card/ProfileCard.component.tsx +71 -0
  27. package/components/base.components/carousel/Carousel.component.tsx +113 -0
  28. package/components/base.components/chip/Chip.component.tsx +39 -0
  29. package/components/base.components/document/DocumentViewer.component.tsx +164 -0
  30. package/components/base.components/document/ExportExcel.component.tsx +340 -0
  31. package/components/base.components/document/ImportExcel.component.tsx +315 -0
  32. package/components/base.components/document/PrintTable.component.tsx +204 -0
  33. package/components/base.components/document/RenderPDF.component.tsx +416 -0
  34. package/components/base.components/index.ts +85 -0
  35. package/components/base.components/input/Checkbox.component.tsx +109 -0
  36. package/components/base.components/input/Input.component.tsx +332 -0
  37. package/components/base.components/input/InputCheckbox.component.tsx +174 -0
  38. package/components/base.components/input/InputCurrency.component.tsx +163 -0
  39. package/components/base.components/input/InputDate.component.tsx +352 -0
  40. package/components/base.components/input/InputDatetime.component.tsx +260 -0
  41. package/components/base.components/input/InputDocument.component.tsx +352 -0
  42. package/components/base.components/input/InputImage.component.tsx +533 -0
  43. package/components/base.components/input/InputMap.component.tsx +318 -0
  44. package/components/base.components/input/InputNumber.component.tsx +192 -0
  45. package/components/base.components/input/InputOtp.component.tsx +169 -0
  46. package/components/base.components/input/InputPassword.component.tsx +236 -0
  47. package/components/base.components/input/InputRadio.component.tsx +175 -0
  48. package/components/base.components/input/InputTime.component.tsx +276 -0
  49. package/components/base.components/input/InputValues.component.tsx +68 -0
  50. package/components/base.components/input/Radio.component.tsx +102 -0
  51. package/components/base.components/input/Select.component.tsx +541 -0
  52. package/components/base.components/modal/BottomSheet.component.tsx +246 -0
  53. package/components/base.components/modal/FloatingPage.component.tsx +104 -0
  54. package/components/base.components/modal/Modal.component.tsx +96 -0
  55. package/components/base.components/modal/ModalConfirm.component.tsx +218 -0
  56. package/components/base.components/modal/Toast.component.tsx +126 -0
  57. package/components/base.components/nav/Bottombar.component.tsx +116 -0
  58. package/components/base.components/nav/Footer.component.tsx +144 -0
  59. package/components/base.components/nav/Headbar.component.tsx +104 -0
  60. package/components/base.components/nav/Navbar.component.tsx +100 -0
  61. package/components/base.components/nav/Sidebar.component.tsx +301 -0
  62. package/components/base.components/nav/Tabbar.component.tsx +60 -0
  63. package/components/base.components/nav/Wizard.component.tsx +73 -0
  64. package/components/base.components/supervision/FormSupervision.component.tsx +434 -0
  65. package/components/base.components/supervision/TableSupervision.component.tsx +697 -0
  66. package/components/base.components/table/ControlBar.component.tsx +497 -0
  67. package/components/base.components/table/FilterComponent.tsx +518 -0
  68. package/components/base.components/table/Pagination.component.tsx +159 -0
  69. package/components/base.components/table/Table.component.tsx +469 -0
  70. package/components/base.components/typography/TypographyArticle.component.tsx +26 -0
  71. package/components/base.components/typography/TypographyColumn.component.tsx +20 -0
  72. package/components/base.components/typography/TypographyContent.component.tsx +20 -0
  73. package/components/base.components/typography/TypographyTips.component.tsx +20 -0
  74. package/components/base.components/wrap/Draggable.component.tsx +303 -0
  75. package/components/base.components/wrap/IDBProvider.tsx +12 -0
  76. package/components/base.components/wrap/Image.component.tsx +10 -0
  77. package/components/base.components/wrap/OutsideClick.component.tsx +48 -0
  78. package/components/base.components/wrap/ScrollContainer.component.tsx +104 -0
  79. package/components/base.components/wrap/ShortcutProvider.tsx +57 -0
  80. package/components/base.components/wrap/Swipe.component.tsx +93 -0
  81. package/components/construct.components/example.tsx +1 -0
  82. package/components/construct.components/index.ts +5 -0
  83. package/components/index.ts +3 -0
  84. package/components/structure.components/example.tsx +1 -0
  85. package/components/structure.components/index.ts +5 -0
  86. package/contexts/AppProvider.tsx +12 -0
  87. package/contexts/Auth.context.tsx +64 -0
  88. package/contexts/Toggle.context.tsx +44 -0
  89. package/contexts/index.ts +7 -0
  90. package/eslint.config.mjs +34 -0
  91. package/langs/index.ts +1 -0
  92. package/langs/validation.langs.ts +17 -0
  93. package/next.config.ts +17 -0
  94. package/package.json +43 -0
  95. package/postcss.config.mjs +12 -0
  96. package/public/204.svg +19 -0
  97. package/public/500.svg +39 -0
  98. package/public/images/avatar.jpg +0 -0
  99. package/public/images/example.png +0 -0
  100. package/schema/idb/app.schema.ts +9 -0
  101. package/schema/index.ts +5 -0
  102. package/styles/globals.css +231 -0
  103. package/styles/tailwind.safelist +69 -0
  104. package/tailwind.config.ts +10 -0
  105. package/tsconfig.json +35 -0
  106. package/utils/commands/barrels.ts +28 -0
  107. package/utils/commands/blueprint.ts +421 -0
  108. package/utils/commands/light.ts +21 -0
  109. package/utils/commands/logger.ts +42 -0
  110. package/utils/commands/stubs/table-blueprint.stub +13 -0
  111. package/utils/commands/use-pdf.ts +29 -0
  112. package/utils/index.ts +3 -0
@@ -0,0 +1,469 @@
1
+ "use client"
2
+
3
+ import { isValidElement, ReactNode, useEffect, useMemo, useRef, useState } from "react";
4
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5
+ import { faArrowDownZA, faArrowUpAZ, faChevronLeft, faChevronRight } from "@fortawesome/free-solid-svg-icons";
6
+ import { ApiFilterType, cn, pcn, useLazySearch, useResponsive } from "@utils";
7
+ import { ControlBarComponent, ControlBarOptionType, PaginationComponent, PaginationProps, ScrollContainerComponent, FilterColumnOption, CheckboxComponent, SwipeComponent, SwipeActionType } from "@components";
8
+
9
+
10
+
11
+ type CT = "controller-bar" | "head-column" | "column" | "row" | "floating-action" | "base";
12
+
13
+ export interface TableColumnType {
14
+ selector : string;
15
+ label : string | ReactNode;
16
+ width ?: string;
17
+ sortable ?: boolean;
18
+ searchable ?: boolean;
19
+ filterable ?: boolean | {
20
+ type : "text" | "number" | "currency" | "date";
21
+ } | {
22
+ type : "select";
23
+ options : { label: string; value: any }[];
24
+ };
25
+ className ?: string;
26
+ item ?: (data: any) => string | ReactNode;
27
+ tip ?: string | ((data: any) => string);
28
+ };
29
+
30
+ export interface TableProps {
31
+ id ?: string;
32
+
33
+ controlBar ?: false | ControlBarOptionType[];
34
+
35
+ columns : TableColumnType[];
36
+ data : Record<string, any>[];
37
+ pagination ?: PaginationProps | false;
38
+
39
+ loading ?: boolean;
40
+ sortBy ?: string[];
41
+ onChangeSortBy ?: (sort: string[]) => void;
42
+ search ?: string;
43
+ onChangeSearch ?: (search: string) => void;
44
+ searchableColumn ?: string[];
45
+ onChangeSearchableColumn ?: (column: string) => void;
46
+ filter ?: ApiFilterType[];
47
+ onChangeFilter ?: (filters: ApiFilterType[]) => void;
48
+ checks ?: (string | number)[];
49
+ onChangeChecks ?: (checks: (string | number)[]) => void;
50
+ actionBulking ?: ((checks: (string | number)[]) => ReactNode) | false
51
+ focus ?: number | null,
52
+ setFocus ?: (focus: number | null) => void,
53
+
54
+ onRowClick ?: (data: Record<string, any>, key: number) => void;
55
+ onRefresh ?: () => void;
56
+
57
+ block ?: boolean;
58
+ noIndex ?: boolean;
59
+ responsiveControl ?: {
60
+ mobile ?: {
61
+ item ?: (item: Record<string, any>, key: number) => ReactNode,
62
+ leftActionControl ?: Omit<SwipeActionType, "onAction"> & { onAction?: (item: Record<string, any>, key?: number) => void },
63
+ rightActionControl ?: Omit<SwipeActionType, "onAction"> & { onAction?: (item: Record<string, any>, key?: number) => void },
64
+ }
65
+ }
66
+
67
+ /** Use custom class with: "controller-bar::", "head-column::", "column::", "floating-action::", "row::". */
68
+ className?: string;
69
+ };
70
+
71
+
72
+
73
+ export function TableComponent({
74
+ id,
75
+ controlBar,
76
+ columns,
77
+ data,
78
+ pagination,
79
+ loading,
80
+
81
+ sortBy,
82
+ onChangeSortBy,
83
+ search,
84
+ onChangeSearch,
85
+ searchableColumn,
86
+ onChangeSearchableColumn,
87
+ filter,
88
+ onChangeFilter,
89
+ checks,
90
+ onChangeChecks,
91
+ actionBulking,
92
+ focus,
93
+
94
+ onRowClick,
95
+ onRefresh,
96
+
97
+ block,
98
+ noIndex,
99
+ responsiveControl,
100
+
101
+ className = "",
102
+ }: TableProps) {
103
+ const [displayColumns, setDisplayColumns] = useState<string[]>([]);
104
+ const [showFloatingAction, setShowFloatingAction] = useState(false);
105
+ const [floatingActionActive, setFloatingActionActive] = useState<false | number>(false);
106
+ const [keyword, setKeyword] = useState<string>("");
107
+ const [keywordSearch] = useLazySearch(keyword);
108
+ const { isSm } = useResponsive ();
109
+
110
+ const actionColumnRef = useRef<HTMLDivElement>(null);
111
+
112
+ useEffect(() => {
113
+ if (columns) setDisplayColumns([...columns.map((column) => column.selector)]);
114
+ }, [columns]);
115
+
116
+
117
+ useEffect(() => {
118
+ setKeyword(search || "");
119
+ }, [search]);
120
+
121
+ useEffect(() => {
122
+ keywordSearch ? onChangeSearch?.(keywordSearch) : onChangeSearch?.("");
123
+
124
+ if(pagination != false) {
125
+ pagination?.onChange?.(pagination.totalRow, pagination.paginate, 1);
126
+ }
127
+ }, [keywordSearch]);
128
+
129
+ const columnMapping = useMemo(() => {
130
+ return ( columns?.filter((column) => displayColumns.includes(column.selector)) || []);
131
+ }, [columns, displayColumns]);
132
+
133
+
134
+
135
+ const styles = {
136
+ head : "px-4 py-2.5 font-bold w-full flex justify-between gap-2 items-center text-sm text-foreground capitalize",
137
+ column : cn("px-4 py-4 font-medium", pcn<CT>(className, "column")),
138
+ row : "flex items-center gap-4 rounded-[4px] relative opacity-0 animate-intro-fade border-b hover:bg-light-primary/30",
139
+ floatingAction : cn("sticky bg-background -right-5 z-30 cursor-pointer flex items-center shadow rounded-l-lg", pcn<CT>(className, "floating-action")),
140
+ };
141
+
142
+
143
+ const numberOfRow = (key: number) => pagination && (pagination?.page || 1) != 1 ? pagination?.paginate * ((pagination?.page || 1) - 1) + key + 1 : key + 1;
144
+
145
+
146
+ function renderHead() {
147
+ return (
148
+ <>
149
+ {columnMapping?.map((column, key) => {
150
+ const sortColumn = sortBy?.find((e) => e.split(" ")?.at(0) == column.selector)?.split(" ")?.at(0) || "";
151
+ const sortDirection = sortBy?.find((e) => e.split(" ")?.at(0) == column.selector)?.split(" ")?.at(1) || "";
152
+
153
+ return (
154
+ <div
155
+ key={key}
156
+ className={cn(
157
+ styles?.head,
158
+ column.sortable && "cursor-pointer",
159
+ pcn<CT>(className, "head-column")
160
+ )}
161
+ style={{ width: column.width ? column.width : 200 }}
162
+ onClick={() => column.sortable && onChangeSortBy?.([`${column.selector} ${sortDirection == "desc" ? "asc" : "desc"}`])}
163
+ >
164
+ {column.label}
165
+
166
+ {!!sortColumn && (
167
+ <FontAwesomeIcon
168
+ icon={sortDirection == "desc" ? faArrowDownZA : faArrowUpAZ}
169
+ className="text-light-foreground/70"
170
+ />
171
+ )}
172
+ </div>
173
+ );
174
+ })}
175
+ </>
176
+ );
177
+ }
178
+
179
+
180
+ function renderItem(item: Record<string, any>, itemKey: number) {
181
+ const itemMapping = columnMapping.map((column) => {
182
+ if (column?.item) {
183
+ return column.item(item);
184
+ }
185
+
186
+ const value = item[column.selector];
187
+
188
+ if (isValidElement(value)) {
189
+ return value;
190
+ }
191
+
192
+ if (value === null || value === undefined) {
193
+ return "-";
194
+ }
195
+
196
+ if (typeof value === "object") {
197
+ return JSON.stringify(value);
198
+ }
199
+
200
+ return value;
201
+ });
202
+
203
+ if(!isSm || !responsiveControl?.mobile) {
204
+ return (
205
+ <>
206
+ {itemMapping?.map((one, key) => {
207
+ const column = columnMapping?.[key];
208
+
209
+ let title = one as string;
210
+ if (column?.tip) {
211
+ if (typeof column.tip === "string") {
212
+ title = (item[column.tip as keyof object] as any)?.toString() || "-";
213
+ } else if (typeof column.tip === "function") {
214
+ title = column.tip(item);
215
+ }
216
+ }
217
+ return (
218
+ <div
219
+ key={key}
220
+ className={cn(styles.column , onRowClick && "cursor-pointer")}
221
+ style={{ width: columnMapping?.at(key)?.width || 200 }}
222
+ onClick={() => onRowClick?.(item, itemKey) }
223
+ title={title}
224
+ >
225
+ {one}
226
+ </div>
227
+ );
228
+ })}
229
+ </>
230
+ );
231
+ } else {
232
+
233
+ const { onAction: onLeftAction, ...restLeftAction } = responsiveControl?.mobile?.leftActionControl || {};
234
+ const { onAction: onRightAction, ...restRightAction } = responsiveControl?.mobile?.rightActionControl || {};
235
+
236
+ return (
237
+ <SwipeComponent
238
+ className="border-b py-2 px-2 bg-white"
239
+ leftActionControl={!!responsiveControl?.mobile?.leftActionControl ? {
240
+ ...restLeftAction,
241
+ ...(onLeftAction ? { onAction: () => onLeftAction?.(item, itemKey)} : {})
242
+ } : undefined}
243
+ rightActionControl={!!responsiveControl?.mobile?.rightActionControl ? {
244
+ ...restRightAction,
245
+ ...(onRightAction ? { onAction: () => onRightAction?.(item, itemKey)} : {})
246
+ } : undefined}
247
+ >
248
+ {responsiveControl?.mobile?.item ? responsiveControl?.mobile?.item(item, itemKey) : (
249
+ <div onClick={() => onRowClick?.(item, itemKey) }>
250
+ <p className="font-semibold">{Object.values(itemMapping)[0]}</p>
251
+ <p className="text-sm">{Object.values(itemMapping)[1]}</p>
252
+ </div>
253
+ )}
254
+ </SwipeComponent>
255
+ )
256
+ }
257
+ }
258
+
259
+
260
+
261
+
262
+
263
+
264
+
265
+
266
+
267
+
268
+
269
+ return (
270
+ <div className={cn("relative", pcn<CT>(className, "base"))}>
271
+ {controlBar != false && (
272
+ <ControlBarComponent
273
+ id={id}
274
+ options={!controlBar ? ["SEARCH", "SELECTABLE", "REFRESH"] : controlBar}
275
+ searchableOptions={columns?.filter((c: TableColumnType) => c.searchable)}
276
+ onSearchable={(e) => onChangeSearchableColumn?.(String(e))}
277
+ searchable={searchableColumn || []}
278
+ onSearch={(e) => setKeyword(e)}
279
+ search={keyword}
280
+ selectableOptions={columns}
281
+ onSelectable={(e) => setDisplayColumns(e)}
282
+ selectable={displayColumns}
283
+ sortableOptions={columns?.filter((c: TableColumnType) => c.sortable)}
284
+ sort={sortBy}
285
+ onSort={(sort) => onChangeSortBy?.(sort)}
286
+ onRefresh={() => onRefresh?.()}
287
+ filterableColumns={columns?.filter((c) => !!c?.filterable)?.map((c) => ({
288
+ label: c.label,
289
+ selector: c.selector,
290
+ type: typeof c?.filterable == "object" ? c?.filterable?.type : "text",
291
+ options: typeof c?.filterable == "object" && c?.filterable?.type == "select" ? c?.filterable?.options : undefined
292
+ })) as FilterColumnOption[]}
293
+ onFilter={(filters) => onChangeFilter?.(filters)}
294
+ filter={filter}
295
+ className={pcn<CT>(className, "controller-bar") || ""}
296
+ />
297
+ )}
298
+
299
+ <div className="relative">
300
+ <ScrollContainerComponent
301
+ scrollFloating={!isSm && block}
302
+ className="w-full"
303
+ onScroll={(e) => {
304
+ actionColumnRef.current?.clientWidth && e.scrollLeft &&
305
+ setShowFloatingAction(e.scrollLeft + e.clientWidth <= e.scrollWidth - actionColumnRef.current?.clientWidth);
306
+ }}
307
+ footer={
308
+ <>
309
+ {block && pagination && (
310
+ <>
311
+ <div className="py-6"></div>
312
+ <div className="my-2 absolute bottom-0 w-full">
313
+ <PaginationComponent {...pagination} />
314
+ </div>
315
+ </>
316
+ )}
317
+ </>
318
+ }
319
+ >
320
+ {loading ? (
321
+ <>
322
+ {
323
+ // =========================>
324
+ // ## When Loading
325
+ // =========================>
326
+ }
327
+ <div className="w-max min-w-full">
328
+ <div className="flex flex-col items-center justify-center gap-8 p-20">
329
+ <h1 className="text-lg text-light-foreground animate-pulse">
330
+ Memuat data...
331
+ </h1>
332
+ </div>
333
+ </div>
334
+ </>
335
+ ) : !data || !data.length ? (
336
+ <>
337
+ {
338
+ // =========================>
339
+ // ## When Empty
340
+ // =========================>
341
+ }
342
+ <div className="flex flex-col items-center justify-center gap-8 p-20 opacity-50">
343
+ <h1 className="text-lg text-light-foreground">
344
+ Belum Ada Data
345
+ </h1>
346
+ </div>
347
+ </>
348
+ ) : (
349
+ <>
350
+ {!isSm || !responsiveControl?.mobile ? (
351
+ <>
352
+ <div className="w-max min-w-full">
353
+ {
354
+ // =========================>
355
+ // ## Head Column
356
+ // =========================>
357
+ }
358
+ <div className={cn("flex gap-4", pcn<CT>(className, "row"))}>
359
+ {!!actionBulking && (
360
+ <div className={cn(styles.head, "w-max")}>
361
+ <CheckboxComponent
362
+ name="selected_table"
363
+ className="w-5 h-5"
364
+ checked={data.length > 0 && checks?.length === data.length}
365
+ onChange={() => data.length > 0 && checks?.length === data.length ? onChangeChecks?.([]) : onChangeChecks?.(data.map((d) => d.id))}
366
+ />
367
+ </div>
368
+ )}
369
+ {!noIndex && <div className={cn(styles.head, "w-8", pcn<CT>(className, "head-column"))}>#</div>}
370
+ {renderHead()}
371
+ </div>
372
+
373
+ {
374
+ // =========================>
375
+ // ## Body Column
376
+ // =========================>
377
+ }
378
+ <div className={`flex flex-col gap-y-0.5`}>
379
+ {data.map((item: Record<string, any>, key) => {
380
+ return (
381
+ <div
382
+ style={{ animationDelay: `${(key + 1) * 0.05}s` }}
383
+ className={cn(
384
+ styles.row,
385
+ key % 2 ? "bg-light-primary/10" : "bg-white",
386
+ focus == key && "bg-light-primary/30",
387
+ pcn<CT>(className, "row")
388
+ )}
389
+ key={key}
390
+ >
391
+ {!!actionBulking && (
392
+ <div className={cn("w-max", styles.column)}>
393
+ <CheckboxComponent
394
+ name="selected_table"
395
+ className="w-5 h-5"
396
+ checked={checks?.includes(item?.id)}
397
+ onChange={() => checks?.includes(item?.id) ? onChangeChecks?.(checks.filter((i) => i !== item?.id)) : onChangeChecks?.([...(checks || []), item?.id])}
398
+ />
399
+ </div>
400
+ )}
401
+ {!noIndex && <div className={cn("w-8", styles?.column)}>{numberOfRow(key)}</div>}
402
+ {renderItem(item, key)}
403
+ <div ref={actionColumnRef} className={cn(`flex-1 flex justify-end gap-2 px-4 py-2`)}>
404
+ {item["action" as keyof object]}
405
+ </div>
406
+
407
+ {item["action" as keyof object] &&
408
+ showFloatingAction && (
409
+ <div
410
+ className={styles.floatingAction}
411
+ onClick={() =>
412
+ floatingActionActive !== false &&
413
+ floatingActionActive == key ? setFloatingActionActive(false) : setFloatingActionActive(key)
414
+ }
415
+ >
416
+ <div className="pl-4 pr-7 py-2">
417
+ <FontAwesomeIcon icon={floatingActionActive === false || floatingActionActive != key ? faChevronLeft : faChevronRight}/>
418
+ </div>
419
+
420
+ <div className={`py-1 flex gap-2 ${floatingActionActive !== false && floatingActionActive == key ? "w-max pl-2 pr-8" : "w-0"}`}>
421
+ {item["action" as keyof object]}
422
+ </div>
423
+ </div>
424
+ )}
425
+ </div>
426
+ );
427
+ })}
428
+ </div>
429
+ </div>
430
+ </>
431
+ ) : (
432
+ <>
433
+ <div className={`w-full flex flex-col gap-y-0.5`}>
434
+ {data.map((item: Record<string, any>, key) => {
435
+ return (
436
+ <div
437
+ style={{ animationDelay: `${(key + 1) * 0.05}s` }}
438
+ key={key}
439
+ >
440
+ {renderItem(item, key)}
441
+ </div>
442
+ )
443
+ })}
444
+ </div>
445
+ </>
446
+ )}
447
+
448
+ </>
449
+ )}
450
+ </ScrollContainerComponent>
451
+ </div>
452
+
453
+ {!!actionBulking && !!checks?.length && (
454
+ <div className="rounded-[6px] bg-white mt-4 w-full px-4 py-2 border flex justify-between items-center">
455
+ <div className="text-sm font-semibold">{checks?.length} Data Terpilih</div>
456
+ <div className="flex justify-end items-center gap-2">
457
+ {actionBulking?.(checks)}
458
+ </div>
459
+ </div>
460
+ )}
461
+
462
+ {!block && pagination && (
463
+ <div className="mt-4">
464
+ <PaginationComponent {...pagination} />
465
+ </div>
466
+ )}
467
+ </div>
468
+ );
469
+ }
@@ -0,0 +1,26 @@
1
+ import { ReactNode } from 'react'
2
+
3
+ export interface TypographyArticleProps {
4
+ title : string | ReactNode;
5
+ content : string | ReactNode;
6
+ header ?: string | ReactNode;
7
+ footer ?: string | ReactNode;
8
+ }
9
+
10
+ export function TypographyArticleComponent({
11
+ title,
12
+ content,
13
+ header,
14
+ footer,
15
+ } : TypographyArticleProps) {
16
+ return (
17
+ <>
18
+ <h4 className="text-light-foreground">{header}</h4>
19
+ <h1 className="text-2xl font-bold mt-2">{title}</h1>
20
+ <div className="text-justify mt-2">{content}</div>
21
+ <div className="text-sm text-light-foreground mt-4">
22
+ {footer}
23
+ </div>
24
+ </>
25
+ )
26
+ }
@@ -0,0 +1,20 @@
1
+ import { ReactNode } from 'react'
2
+
3
+ export interface TypographyColumnProps {
4
+ title : string | ReactNode;
5
+ content : string | ReactNode;
6
+ }
7
+
8
+ export function TypographyColumnComponent({
9
+ title,
10
+ content,
11
+ } : TypographyColumnProps) {
12
+ return (
13
+ <>
14
+ <div>
15
+ <div className="text-xs font-semibold text-light-foreground">{title}</div>
16
+ <div>{content}</div>
17
+ </div>
18
+ </>
19
+ )
20
+ }
@@ -0,0 +1,20 @@
1
+ import { ReactNode } from 'react'
2
+
3
+ export interface TypographyContentProps {
4
+ title : string | ReactNode;
5
+ content : string | ReactNode;
6
+ }
7
+
8
+ export function TypographyContentComponent({
9
+ title,
10
+ content,
11
+ } : TypographyContentProps) {
12
+ return (
13
+ <>
14
+ <div>
15
+ <p className="font-semibold">{title}</p>
16
+ <p className="text-sm text-light-foreground">{content}</p>
17
+ </div>
18
+ </>
19
+ )
20
+ }
@@ -0,0 +1,20 @@
1
+ import { ReactNode } from 'react'
2
+
3
+ export interface TypographyTipsProps {
4
+ title : string | ReactNode;
5
+ content : string | ReactNode;
6
+ }
7
+
8
+ export function TypographyTipsComponent({
9
+ title,
10
+ content,
11
+ } : TypographyTipsProps) {
12
+ return (
13
+ <>
14
+ <div className="pl-3 py-1 border-l-2 border-light-primary">
15
+ <p className="font-semibold">{title}</p>
16
+ <p className="text-sm text-light-foreground">{content}</p>
17
+ </div>
18
+ </>
19
+ )
20
+ }