azamat-ui-kit-cli 0.2.2

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 (213) hide show
  1. package/README.md +8 -0
  2. package/dist/index.js +432 -0
  3. package/package.json +34 -0
  4. package/vendor/package.json +4 -0
  5. package/vendor/src/components/actions/action-bar.tsx +35 -0
  6. package/vendor/src/components/actions/action-menu.tsx +120 -0
  7. package/vendor/src/components/actions/button-group.tsx +47 -0
  8. package/vendor/src/components/actions/copy-button.tsx +91 -0
  9. package/vendor/src/components/actions/copy-field.tsx +31 -0
  10. package/vendor/src/components/actions/floating-action-button.tsx +33 -0
  11. package/vendor/src/components/actions/index.ts +7 -0
  12. package/vendor/src/components/actions/public.ts +5 -0
  13. package/vendor/src/components/actions/quick-action-grid.tsx +162 -0
  14. package/vendor/src/components/calendar/calendar.tsx +328 -0
  15. package/vendor/src/components/calendar/date-picker.tsx +78 -0
  16. package/vendor/src/components/calendar/date-range-picker.tsx +96 -0
  17. package/vendor/src/components/calendar/date-utils.ts +89 -0
  18. package/vendor/src/components/calendar/index.ts +4 -0
  19. package/vendor/src/components/charts/charts.tsx +275 -0
  20. package/vendor/src/components/charts/horizontal-bar-chart.tsx +46 -0
  21. package/vendor/src/components/charts/index.ts +4 -0
  22. package/vendor/src/components/charts/kpi.tsx +68 -0
  23. package/vendor/src/components/charts/progress-ring.tsx +45 -0
  24. package/vendor/src/components/charts/public.ts +1 -0
  25. package/vendor/src/components/command/command-palette.tsx +375 -0
  26. package/vendor/src/components/command/index.ts +1 -0
  27. package/vendor/src/components/data-table/data-table-actions-column.tsx +58 -0
  28. package/vendor/src/components/data-table/data-table-bulk-actions.tsx +84 -0
  29. package/vendor/src/components/data-table/data-table-column-visibility-menu.tsx +79 -0
  30. package/vendor/src/components/data-table/data-table-pagination.tsx +91 -0
  31. package/vendor/src/components/data-table/data-table-row-actions.tsx +48 -0
  32. package/vendor/src/components/data-table/data-table-select-column.tsx +59 -0
  33. package/vendor/src/components/data-table/data-table-sortable-header.tsx +45 -0
  34. package/vendor/src/components/data-table/data-table-toolbar.tsx +76 -0
  35. package/vendor/src/components/data-table/data-table-view-presets.tsx +128 -0
  36. package/vendor/src/components/data-table/data-table.tsx +507 -0
  37. package/vendor/src/components/data-table/index.ts +12 -0
  38. package/vendor/src/components/data-table/public.ts +10 -0
  39. package/vendor/src/components/data-table/table-export-menu.tsx +56 -0
  40. package/vendor/src/components/data-table/table-import-button.tsx +43 -0
  41. package/vendor/src/components/display/activity-feed.tsx +97 -0
  42. package/vendor/src/components/display/avatar.tsx +131 -0
  43. package/vendor/src/components/display/code-block.tsx +33 -0
  44. package/vendor/src/components/display/data-state.tsx +63 -0
  45. package/vendor/src/components/display/description-list.tsx +119 -0
  46. package/vendor/src/components/display/descriptions.tsx +83 -0
  47. package/vendor/src/components/display/entity-card.tsx +53 -0
  48. package/vendor/src/components/display/file-card.tsx +54 -0
  49. package/vendor/src/components/display/index.ts +30 -0
  50. package/vendor/src/components/display/kanban.tsx +104 -0
  51. package/vendor/src/components/display/keyboard-shortcut.tsx +31 -0
  52. package/vendor/src/components/display/list.tsx +100 -0
  53. package/vendor/src/components/display/metric-grid.tsx +86 -0
  54. package/vendor/src/components/display/progress.tsx +162 -0
  55. package/vendor/src/components/display/property-grid.tsx +54 -0
  56. package/vendor/src/components/display/result.tsx +90 -0
  57. package/vendor/src/components/display/smart-card.tsx +168 -0
  58. package/vendor/src/components/display/statistic.tsx +107 -0
  59. package/vendor/src/components/display/status-legend.tsx +108 -0
  60. package/vendor/src/components/display/tag-list.tsx +52 -0
  61. package/vendor/src/components/display/timeline.tsx +132 -0
  62. package/vendor/src/components/display/tree-view.tsx +116 -0
  63. package/vendor/src/components/feedback/alert.tsx +69 -0
  64. package/vendor/src/components/feedback/empty-state.tsx +56 -0
  65. package/vendor/src/components/feedback/index.ts +5 -0
  66. package/vendor/src/components/feedback/loading-state.tsx +39 -0
  67. package/vendor/src/components/feedback/page-state.tsx +69 -0
  68. package/vendor/src/components/feedback/status-badge.tsx +62 -0
  69. package/vendor/src/components/filters/filter-bar.tsx +89 -0
  70. package/vendor/src/components/filters/filter-chips.tsx +69 -0
  71. package/vendor/src/components/filters/index.ts +2 -0
  72. package/vendor/src/components/form/form-actions.tsx +53 -0
  73. package/vendor/src/components/form/form-async-select.tsx +26 -0
  74. package/vendor/src/components/form/form-date-input.tsx +19 -0
  75. package/vendor/src/components/form/form-date-picker.tsx +54 -0
  76. package/vendor/src/components/form/form-date-range-input.tsx +79 -0
  77. package/vendor/src/components/form/form-date-range-picker.tsx +57 -0
  78. package/vendor/src/components/form/form-field-shell.tsx +191 -0
  79. package/vendor/src/components/form/form-input.tsx +480 -0
  80. package/vendor/src/components/form/form-number-input.tsx +19 -0
  81. package/vendor/src/components/form/form-password-input.tsx +19 -0
  82. package/vendor/src/components/form/form-phone-input.tsx +22 -0
  83. package/vendor/src/components/form/form-search-input.tsx +19 -0
  84. package/vendor/src/components/form/form-section.tsx +29 -0
  85. package/vendor/src/components/form/form-select.tsx +194 -0
  86. package/vendor/src/components/form/form-switch.tsx +145 -0
  87. package/vendor/src/components/form/form-textarea.tsx +103 -0
  88. package/vendor/src/components/form/index.ts +17 -0
  89. package/vendor/src/components/form/public.ts +14 -0
  90. package/vendor/src/components/form/smart-form-shell.tsx +59 -0
  91. package/vendor/src/components/inputs/async-select.tsx +1143 -0
  92. package/vendor/src/components/inputs/clearable-input.tsx +78 -0
  93. package/vendor/src/components/inputs/color-input.tsx +47 -0
  94. package/vendor/src/components/inputs/combobox.tsx +89 -0
  95. package/vendor/src/components/inputs/date-input.tsx +32 -0
  96. package/vendor/src/components/inputs/date-range-input.tsx +67 -0
  97. package/vendor/src/components/inputs/index.ts +19 -0
  98. package/vendor/src/components/inputs/input-chrome.tsx +37 -0
  99. package/vendor/src/components/inputs/input-decorator.tsx +64 -0
  100. package/vendor/src/components/inputs/input-value.ts +42 -0
  101. package/vendor/src/components/inputs/masked-input.tsx +51 -0
  102. package/vendor/src/components/inputs/money-input.tsx +73 -0
  103. package/vendor/src/components/inputs/number-input.tsx +87 -0
  104. package/vendor/src/components/inputs/numeric-value.ts +39 -0
  105. package/vendor/src/components/inputs/otp-input.tsx +102 -0
  106. package/vendor/src/components/inputs/password-input.tsx +85 -0
  107. package/vendor/src/components/inputs/phone-input.tsx +46 -0
  108. package/vendor/src/components/inputs/quantity-input.tsx +116 -0
  109. package/vendor/src/components/inputs/quantity-stepper.tsx +49 -0
  110. package/vendor/src/components/inputs/rating.tsx +98 -0
  111. package/vendor/src/components/inputs/search-input.tsx +26 -0
  112. package/vendor/src/components/inputs/simple-select.tsx +72 -0
  113. package/vendor/src/components/inputs/slider.tsx +149 -0
  114. package/vendor/src/components/inputs/tag-input.tsx +104 -0
  115. package/vendor/src/components/layout/app-header.tsx +46 -0
  116. package/vendor/src/components/layout/app-shell.tsx +243 -0
  117. package/vendor/src/components/layout/app-sidebar.tsx +179 -0
  118. package/vendor/src/components/layout/breadcrumbs.tsx +72 -0
  119. package/vendor/src/components/layout/index.ts +11 -0
  120. package/vendor/src/components/layout/page-container.tsx +30 -0
  121. package/vendor/src/components/layout/page-header.tsx +60 -0
  122. package/vendor/src/components/layout/public.ts +10 -0
  123. package/vendor/src/components/layout/section.tsx +76 -0
  124. package/vendor/src/components/layout/sidebar-nav.tsx +147 -0
  125. package/vendor/src/components/layout/stat-card.tsx +88 -0
  126. package/vendor/src/components/layout/sticky-footer-bar.tsx +23 -0
  127. package/vendor/src/components/layout/workspace-shell.tsx +50 -0
  128. package/vendor/src/components/navigation/anchor-nav.tsx +44 -0
  129. package/vendor/src/components/navigation/index.ts +4 -0
  130. package/vendor/src/components/navigation/page-tabs.tsx +67 -0
  131. package/vendor/src/components/navigation/pagination.tsx +179 -0
  132. package/vendor/src/components/navigation/stepper-tabs.tsx +67 -0
  133. package/vendor/src/components/notifications/index.ts +1 -0
  134. package/vendor/src/components/notifications/toast.tsx +259 -0
  135. package/vendor/src/components/overlay/confirm-dialog.tsx +66 -0
  136. package/vendor/src/components/overlay/dialog-actions.tsx +68 -0
  137. package/vendor/src/components/overlay/index.ts +4 -0
  138. package/vendor/src/components/overlay/modal-shell.tsx +93 -0
  139. package/vendor/src/components/overlay/sheet-shell.tsx +212 -0
  140. package/vendor/src/components/patterns/action-system.tsx +116 -0
  141. package/vendor/src/components/patterns/crud-system.tsx +53 -0
  142. package/vendor/src/components/patterns/data-view.tsx +84 -0
  143. package/vendor/src/components/patterns/entity-details.tsx +66 -0
  144. package/vendor/src/components/patterns/filter-builder.tsx +113 -0
  145. package/vendor/src/components/patterns/form-builder-presets.ts +131 -0
  146. package/vendor/src/components/patterns/form-builder.tsx +334 -0
  147. package/vendor/src/components/patterns/index.ts +12 -0
  148. package/vendor/src/components/patterns/public.ts +4 -0
  149. package/vendor/src/components/patterns/resource-detail-page.tsx +160 -0
  150. package/vendor/src/components/patterns/resource-page.tsx +159 -0
  151. package/vendor/src/components/patterns/resource-system.tsx +61 -0
  152. package/vendor/src/components/patterns/settings-section.tsx +46 -0
  153. package/vendor/src/components/patterns/status-system.tsx +89 -0
  154. package/vendor/src/components/theme-provider.tsx +51 -0
  155. package/vendor/src/components/ui/badge.tsx +52 -0
  156. package/vendor/src/components/ui/button.tsx +61 -0
  157. package/vendor/src/components/ui/card.tsx +103 -0
  158. package/vendor/src/components/ui/checkbox.tsx +82 -0
  159. package/vendor/src/components/ui/collapse.tsx +126 -0
  160. package/vendor/src/components/ui/command.tsx +194 -0
  161. package/vendor/src/components/ui/dialog.tsx +160 -0
  162. package/vendor/src/components/ui/divider.tsx +46 -0
  163. package/vendor/src/components/ui/dropdown-menu.tsx +266 -0
  164. package/vendor/src/components/ui/input-group.tsx +158 -0
  165. package/vendor/src/components/ui/input.tsx +20 -0
  166. package/vendor/src/components/ui/popover.tsx +90 -0
  167. package/vendor/src/components/ui/segmented-control.tsx +78 -0
  168. package/vendor/src/components/ui/select.tsx +201 -0
  169. package/vendor/src/components/ui/skeleton.tsx +75 -0
  170. package/vendor/src/components/ui/spinner.tsx +50 -0
  171. package/vendor/src/components/ui/switch.tsx +71 -0
  172. package/vendor/src/components/ui/table.tsx +114 -0
  173. package/vendor/src/components/ui/tabs.tsx +55 -0
  174. package/vendor/src/components/ui/textarea.tsx +18 -0
  175. package/vendor/src/components/ui/tooltip.tsx +38 -0
  176. package/vendor/src/components/upload/file-upload.tsx +483 -0
  177. package/vendor/src/components/upload/image-upload.tsx +118 -0
  178. package/vendor/src/components/upload/index.ts +2 -0
  179. package/vendor/src/components/wizard/index.ts +2 -0
  180. package/vendor/src/components/wizard/stepper.tsx +53 -0
  181. package/vendor/src/components/wizard/wizard.tsx +60 -0
  182. package/vendor/src/families/card-family.ts +28 -0
  183. package/vendor/src/families/catalog.ts +96 -0
  184. package/vendor/src/families/data-table-family.ts +31 -0
  185. package/vendor/src/families/docs-adoption.ts +103 -0
  186. package/vendor/src/families/docs-groups.ts +209 -0
  187. package/vendor/src/families/docs-queries.ts +84 -0
  188. package/vendor/src/families/docs-routing.ts +89 -0
  189. package/vendor/src/families/form-family.ts +45 -0
  190. package/vendor/src/families/index.ts +17 -0
  191. package/vendor/src/families/input-family.ts +61 -0
  192. package/vendor/src/families/member-metadata.ts +466 -0
  193. package/vendor/src/families/member-queries.ts +28 -0
  194. package/vendor/src/families/member-snippet-queries.ts +54 -0
  195. package/vendor/src/families/member-snippets.ts +673 -0
  196. package/vendor/src/families/migration-map.ts +79 -0
  197. package/vendor/src/families/queries.ts +63 -0
  198. package/vendor/src/families/select-family.ts +33 -0
  199. package/vendor/src/families/views.ts +81 -0
  200. package/vendor/src/hooks/index.ts +6 -0
  201. package/vendor/src/hooks/use-before-unload-when-dirty.ts +21 -0
  202. package/vendor/src/hooks/use-data-table-view-state.ts +122 -0
  203. package/vendor/src/hooks/use-debounce.ts +52 -0
  204. package/vendor/src/hooks/use-disclosure.ts +38 -0
  205. package/vendor/src/hooks/use-is-mobile.ts +28 -0
  206. package/vendor/src/hooks/use-session-storage-state.ts +85 -0
  207. package/vendor/src/index.ts +38 -0
  208. package/vendor/src/lib/utils.ts +6 -0
  209. package/vendor/templates/components/button.tsx +0 -0
  210. package/vendor/templates/components/data-table.tsx +0 -0
  211. package/vendor/templates/components/input.tsx +0 -0
  212. package/vendor/templates/lib/utils.ts +0 -0
  213. package/vendor/templates/styles/globals.css +0 -0
@@ -0,0 +1,507 @@
1
+ import * as React from "react"
2
+ import {
3
+ flexRender,
4
+ getCoreRowModel,
5
+ getPaginationRowModel,
6
+ useReactTable,
7
+ type Cell,
8
+ type ColumnDef,
9
+ type Header,
10
+ type OnChangeFn,
11
+ type Row,
12
+ type RowSelectionState,
13
+ type SortingState,
14
+ type Table as TanStackTable,
15
+ type VisibilityState,
16
+ } from "@tanstack/react-table"
17
+
18
+ import { createDataTableActionsColumn } from "@/components/data-table/data-table-actions-column"
19
+ import { DataTableBulkActions, type DataTableBulkAction } from "@/components/data-table/data-table-bulk-actions"
20
+ import { DataTableColumnVisibilityMenu } from "@/components/data-table/data-table-column-visibility-menu"
21
+ import { DataTablePagination, type DataTablePaginationProps } from "@/components/data-table/data-table-pagination"
22
+ import { type DataTableRowAction } from "@/components/data-table/data-table-row-actions"
23
+ import { DataTableToolbar, type DataTableToolbarProps } from "@/components/data-table/data-table-toolbar"
24
+ import { EmptyState, type EmptyStateProps } from "@/components/feedback/empty-state"
25
+ import { LoadingState, type LoadingStateProps } from "@/components/feedback/loading-state"
26
+ import { SearchInput, type SearchInputProps } from "@/components/inputs/search-input"
27
+ import { Button } from "@/components/ui/button"
28
+ import {
29
+ Table,
30
+ TableBody,
31
+ TableCell,
32
+ TableHead,
33
+ TableHeader,
34
+ TableRow,
35
+ } from "@/components/ui/table"
36
+ import { cn } from "@/lib/utils"
37
+
38
+ export type DataTableDensity = "compact" | "default" | "comfortable"
39
+ export type DataTableLoadingVariant = "skeleton" | "state"
40
+
41
+ export type DataTableFeatureConfig = {
42
+ search?: boolean
43
+ columnVisibility?: boolean
44
+ rowActions?: boolean
45
+ bulkActions?: boolean
46
+ refresh?: boolean
47
+ export?: boolean
48
+ }
49
+
50
+ export type DataTableSearchConfig = Pick<
51
+ SearchInputProps,
52
+ | "value"
53
+ | "onValueChange"
54
+ | "placeholder"
55
+ | "inputClassName"
56
+ | "disabled"
57
+ | "clearable"
58
+ | "clearLabel"
59
+ | "searchIcon"
60
+ > & {
61
+ className?: string
62
+ wrapperClassName?: string
63
+ }
64
+
65
+ export type DataTableActionContext<TData> = {
66
+ table: TanStackTable<TData>
67
+ data: TData[]
68
+ selectedRows: TData[]
69
+ }
70
+
71
+ export type DataTablePaginationConfig = Pick<
72
+ DataTablePaginationProps,
73
+ | "pageIndex"
74
+ | "pageSize"
75
+ | "pageCount"
76
+ | "rowCount"
77
+ | "pageSizeOptions"
78
+ | "labels"
79
+ | "showPageSize"
80
+ > & {
81
+ manual?: boolean
82
+ hidden?: boolean
83
+ onPageChange?: (pageIndex: number) => void
84
+ onPageSizeChange?: (pageSize: number) => void
85
+ }
86
+
87
+ export type DataTableProps<TData, TValue = unknown> = Omit<
88
+ React.ComponentProps<"div">,
89
+ "children"
90
+ > & {
91
+ columns: ColumnDef<TData, TValue>[]
92
+ data: TData[]
93
+ title?: React.ReactNode
94
+ description?: React.ReactNode
95
+ features?: DataTableFeatureConfig
96
+ search?: DataTableSearchConfig
97
+ toolbarActions?: React.ReactNode | ((context: DataTableActionContext<TData>) => React.ReactNode)
98
+ rowActions?: (row: Row<TData>, original: TData) => DataTableRowAction<TData>[]
99
+ bulkActions?: DataTableBulkAction<TData>[]
100
+ onRefresh?: (context: DataTableActionContext<TData>) => void
101
+ onExport?: (context: DataTableActionContext<TData>) => void
102
+ refreshLabel?: React.ReactNode
103
+ exportLabel?: React.ReactNode
104
+ getRowId?: (originalRow: TData, index: number, parent?: Row<TData>) => string
105
+ isLoading?: boolean
106
+ isError?: boolean
107
+ emptyState?: EmptyStateProps
108
+ errorState?: EmptyStateProps
109
+ loadingState?: LoadingStateProps
110
+ loadingVariant?: DataTableLoadingVariant
111
+ toolbar?: React.ReactNode | ((table: TanStackTable<TData>) => React.ReactNode)
112
+ toolbarProps?: DataTableToolbarProps | ((table: TanStackTable<TData>) => DataTableToolbarProps)
113
+ pagination?: DataTablePaginationConfig | false
114
+ sorting?: SortingState
115
+ onSortingChange?: OnChangeFn<SortingState>
116
+ columnVisibility?: VisibilityState
117
+ onColumnVisibilityChange?: OnChangeFn<VisibilityState>
118
+ rowSelection?: RowSelectionState
119
+ onRowSelectionChange?: OnChangeFn<RowSelectionState>
120
+ enableRowSelection?: boolean | ((row: Row<TData>) => boolean)
121
+ renderMobileCard?: (row: Row<TData>) => React.ReactNode
122
+ onRowClick?: (row: Row<TData>) => void
123
+ onRowDoubleClick?: (row: Row<TData>) => void
124
+ getRowDisabled?: (row: Row<TData>) => boolean
125
+ density?: DataTableDensity
126
+ striped?: boolean
127
+ bordered?: boolean
128
+ stickyHeader?: boolean
129
+ skeletonRows?: number
130
+ skeletonCellClassName?: string
131
+ cellFallback?: React.ReactNode
132
+ tableClassName?: string
133
+ tableWrapperClassName?: string
134
+ headerCellClassName?: string | ((header: Header<TData, unknown>) => string)
135
+ cellClassName?: string | ((cell: Cell<TData, unknown>) => string)
136
+ rowClassName?: string | ((row: Row<TData>) => string)
137
+ }
138
+
139
+ const densityHeadClassName: Record<DataTableDensity, string> = {
140
+ compact: "h-8 px-2 py-1.5",
141
+ default: "h-10 px-2 py-2",
142
+ comfortable: "h-12 px-3 py-3",
143
+ }
144
+
145
+ const densityCellClassName: Record<DataTableDensity, string> = {
146
+ compact: "px-2 py-1.5",
147
+ default: "p-2",
148
+ comfortable: "px-3 py-3",
149
+ }
150
+
151
+ function getRowClassName<TData>(
152
+ row: Row<TData>,
153
+ rowClassName?: string | ((row: Row<TData>) => string)
154
+ ) {
155
+ return typeof rowClassName === "function" ? rowClassName(row) : rowClassName
156
+ }
157
+
158
+ function getHeaderCellClassName<TData>(
159
+ header: Header<TData, unknown>,
160
+ headerCellClassName?: string | ((header: Header<TData, unknown>) => string)
161
+ ) {
162
+ return typeof headerCellClassName === "function"
163
+ ? headerCellClassName(header)
164
+ : headerCellClassName
165
+ }
166
+
167
+ function getCellClassName<TData>(
168
+ cell: Cell<TData, unknown>,
169
+ cellClassName?: string | ((cell: Cell<TData, unknown>) => string)
170
+ ) {
171
+ return typeof cellClassName === "function" ? cellClassName(cell) : cellClassName
172
+ }
173
+
174
+ function isEmptyCellContent(content: React.ReactNode) {
175
+ return content === null || content === undefined || content === ""
176
+ }
177
+
178
+ function DataTable<TData, TValue = unknown>({
179
+ className,
180
+ columns,
181
+ data,
182
+ title,
183
+ description,
184
+ features,
185
+ search,
186
+ toolbarActions,
187
+ rowActions,
188
+ bulkActions,
189
+ onRefresh,
190
+ onExport,
191
+ refreshLabel = "Refresh",
192
+ exportLabel = "Export",
193
+ getRowId,
194
+ isLoading = false,
195
+ isError = false,
196
+ emptyState,
197
+ errorState,
198
+ loadingState,
199
+ loadingVariant = "skeleton",
200
+ toolbar,
201
+ toolbarProps,
202
+ pagination,
203
+ sorting,
204
+ onSortingChange,
205
+ columnVisibility,
206
+ onColumnVisibilityChange,
207
+ rowSelection,
208
+ onRowSelectionChange,
209
+ enableRowSelection,
210
+ renderMobileCard,
211
+ onRowClick,
212
+ onRowDoubleClick,
213
+ getRowDisabled,
214
+ density = "default",
215
+ striped = false,
216
+ bordered = false,
217
+ stickyHeader = false,
218
+ skeletonRows = 6,
219
+ skeletonCellClassName,
220
+ cellFallback = "-",
221
+ tableClassName,
222
+ tableWrapperClassName,
223
+ headerCellClassName,
224
+ cellClassName,
225
+ rowClassName,
226
+ ...props
227
+ }: DataTableProps<TData, TValue>) {
228
+ const resolvedColumns = React.useMemo<ColumnDef<TData, TValue | unknown>[]>(() => {
229
+ if (!rowActions || features?.rowActions === false) return columns
230
+
231
+ return [
232
+ ...columns,
233
+ createDataTableActionsColumn<TData>({
234
+ getActions: rowActions,
235
+ }) as ColumnDef<TData, TValue | unknown>,
236
+ ]
237
+ }, [columns, features?.rowActions, rowActions])
238
+
239
+ const paginationConfig = pagination === false ? undefined : pagination
240
+ const controlledPagination = paginationConfig
241
+ ? {
242
+ pageIndex: paginationConfig.pageIndex,
243
+ pageSize: paginationConfig.pageSize,
244
+ }
245
+ : undefined
246
+ const manualPagination = Boolean(paginationConfig && paginationConfig.manual !== false)
247
+ const selectedRowCount = rowSelection ? Object.keys(rowSelection).length : 0
248
+
249
+ // TanStack Table returns imperative helpers that React Compiler flags by design.
250
+ // eslint-disable-next-line react-hooks/incompatible-library
251
+ const table = useReactTable({
252
+ data,
253
+ columns: resolvedColumns,
254
+ getRowId,
255
+ getCoreRowModel: getCoreRowModel(),
256
+ getPaginationRowModel: paginationConfig && !manualPagination ? getPaginationRowModel() : undefined,
257
+ manualPagination,
258
+ pageCount: paginationConfig?.pageCount,
259
+ state: {
260
+ sorting,
261
+ columnVisibility,
262
+ rowSelection,
263
+ pagination: controlledPagination,
264
+ },
265
+ onSortingChange,
266
+ onColumnVisibilityChange,
267
+ onRowSelectionChange,
268
+ enableRowSelection,
269
+ })
270
+
271
+ const rows = table.getRowModel().rows
272
+ const selectedRows = table.getSelectedRowModel().rows.map((row) => row.original)
273
+ const actionContext = React.useMemo<DataTableActionContext<TData>>(
274
+ () => ({ table, data, selectedRows }),
275
+ [data, selectedRows, table]
276
+ )
277
+ const visibleColumns = table.getVisibleLeafColumns()
278
+ const visibleColumnCount = Math.max(visibleColumns.length, 1)
279
+ const resolvedToolbar = typeof toolbar === "function" ? toolbar(table) : toolbar
280
+ const resolvedToolbarProps = typeof toolbarProps === "function" ? toolbarProps(table) : toolbarProps
281
+ const shouldShowSearch = Boolean(search && features?.search !== false)
282
+ const shouldShowColumnVisibility = Boolean(features?.columnVisibility && table.getAllLeafColumns().some((column) => column.getCanHide()))
283
+ const shouldShowRefresh = Boolean(features?.refresh && onRefresh)
284
+ const shouldShowExport = Boolean(features?.export && onExport)
285
+ const shouldShowBulkActions = Boolean(features?.bulkActions !== false && bulkActions?.length)
286
+ const defaultSearch = shouldShowSearch && search ? (
287
+ <SearchInput
288
+ value={search.value}
289
+ onValueChange={search.onValueChange}
290
+ placeholder={search.placeholder ?? "Search..."}
291
+ wrapperClassName={search.wrapperClassName ?? search.className}
292
+ inputClassName={search.inputClassName}
293
+ disabled={search.disabled}
294
+ clearable={search.clearable}
295
+ clearLabel={search.clearLabel}
296
+ searchIcon={search.searchIcon}
297
+ />
298
+ ) : undefined
299
+ const defaultActions = (
300
+ <>
301
+ {typeof toolbarActions === "function" ? toolbarActions(actionContext) : toolbarActions}
302
+ {shouldShowColumnVisibility && <DataTableColumnVisibilityMenu table={table} />}
303
+ {shouldShowRefresh && (
304
+ <Button type="button" variant="outline" size="sm" disabled={isLoading} onClick={() => onRefresh?.(actionContext)}>
305
+ {refreshLabel}
306
+ </Button>
307
+ )}
308
+ {shouldShowExport && (
309
+ <Button type="button" variant="outline" size="sm" onClick={() => onExport?.(actionContext)}>
310
+ {exportLabel}
311
+ </Button>
312
+ )}
313
+ </>
314
+ )
315
+ const defaultSelectionActions = shouldShowBulkActions ? (
316
+ <DataTableBulkActions
317
+ rows={selectedRows}
318
+ actions={bulkActions ?? []}
319
+ onClearSelection={() => table.resetRowSelection()}
320
+ hideWhenEmpty={false}
321
+ />
322
+ ) : undefined
323
+ const hasDefaultToolbarContent = Boolean(
324
+ title ||
325
+ description ||
326
+ defaultSearch ||
327
+ toolbarActions ||
328
+ shouldShowColumnVisibility ||
329
+ shouldShowRefresh ||
330
+ shouldShowExport ||
331
+ defaultSelectionActions
332
+ )
333
+ const hasToolbar = Boolean(resolvedToolbar || resolvedToolbarProps || hasDefaultToolbarContent)
334
+ const showPagination = Boolean(paginationConfig && !paginationConfig.hidden)
335
+ const shouldRenderSkeleton = isLoading && loadingVariant === "skeleton"
336
+
337
+ const renderStateRow = (children: React.ReactNode) => (
338
+ <TableRow>
339
+ <TableCell colSpan={visibleColumnCount} className="p-0">
340
+ {children}
341
+ </TableCell>
342
+ </TableRow>
343
+ )
344
+
345
+ const renderSkeletonRows = () =>
346
+ Array.from({ length: Math.max(skeletonRows, 1) }, (_, rowIndex) => (
347
+ <TableRow key={`skeleton-${rowIndex}`} aria-hidden="true">
348
+ {visibleColumns.map((column) => (
349
+ <TableCell
350
+ key={`${column.id}-${rowIndex}`}
351
+ className={cn(
352
+ densityCellClassName[density],
353
+ bordered && "border-r last:border-r-0"
354
+ )}
355
+ >
356
+ <div
357
+ className={cn(
358
+ "h-4 w-full max-w-40 animate-pulse rounded-md bg-muted",
359
+ rowIndex % 3 === 1 && "max-w-24",
360
+ rowIndex % 3 === 2 && "max-w-32",
361
+ skeletonCellClassName
362
+ )}
363
+ />
364
+ </TableCell>
365
+ ))}
366
+ </TableRow>
367
+ ))
368
+
369
+ const stateContent = shouldRenderSkeleton ? null : isLoading ? (
370
+ <LoadingState label="Loading data..." {...loadingState} />
371
+ ) : isError ? (
372
+ <EmptyState title="Could not load data" description="Please try again." {...errorState} />
373
+ ) : rows.length === 0 ? (
374
+ <EmptyState {...emptyState} />
375
+ ) : null
376
+
377
+ return (
378
+ <div data-slot="data-table" className={cn("grid gap-3", className)} {...props}>
379
+ {hasToolbar &&
380
+ (resolvedToolbar ?? (
381
+ <DataTableToolbar
382
+ title={title}
383
+ description={description}
384
+ search={defaultSearch}
385
+ actions={defaultActions}
386
+ selectionActions={defaultSelectionActions}
387
+ selectedCount={selectedRowCount}
388
+ totalCount={paginationConfig ? paginationConfig.rowCount ?? data.length : data.length}
389
+ {...resolvedToolbarProps}
390
+ />
391
+ ))}
392
+
393
+ {renderMobileCard && (
394
+ <div className="grid gap-3 md:hidden">
395
+ {stateContent ??
396
+ (shouldRenderSkeleton ? (
397
+ <LoadingState label="Loading data..." {...loadingState} />
398
+ ) : (
399
+ rows.map((row) => <React.Fragment key={row.id}>{renderMobileCard(row)}</React.Fragment>)
400
+ ))}
401
+ </div>
402
+ )}
403
+
404
+ <div
405
+ data-slot="data-table-wrapper"
406
+ data-density={density}
407
+ data-striped={striped || undefined}
408
+ data-bordered={bordered || undefined}
409
+ className={cn(
410
+ "overflow-auto rounded-[var(--radius-2xl)] border bg-card/96 shadow-sm backdrop-blur",
411
+ !bordered && "border-border",
412
+ renderMobileCard && "hidden md:block",
413
+ tableWrapperClassName
414
+ )}
415
+ >
416
+ <Table className={cn("text-[0.95rem]", tableClassName)}>
417
+ <TableHeader className={cn(stickyHeader && "sticky top-0 z-10 bg-background shadow-sm")}>
418
+ {table.getHeaderGroups().map((headerGroup) => (
419
+ <TableRow key={headerGroup.id}>
420
+ {headerGroup.headers.map((header) => (
421
+ <TableHead
422
+ key={header.id}
423
+ style={{ width: header.getSize() }}
424
+ className={cn(
425
+ densityHeadClassName[density],
426
+ stickyHeader && "bg-background",
427
+ bordered && "border-r last:border-r-0",
428
+ getHeaderCellClassName(header, headerCellClassName)
429
+ )}
430
+ >
431
+ {header.isPlaceholder
432
+ ? null
433
+ : flexRender(header.column.columnDef.header, header.getContext())}
434
+ </TableHead>
435
+ ))}
436
+ </TableRow>
437
+ ))}
438
+ </TableHeader>
439
+ <TableBody>
440
+ {stateContent
441
+ ? renderStateRow(stateContent)
442
+ : shouldRenderSkeleton
443
+ ? renderSkeletonRows()
444
+ : rows.map((row, rowIndex) => {
445
+ const rowDisabled = getRowDisabled?.(row) ?? false
446
+
447
+ return (
448
+ <TableRow
449
+ key={row.id}
450
+ data-state={row.getIsSelected() ? "selected" : undefined}
451
+ data-striped={striped && rowIndex % 2 === 1 ? "true" : undefined}
452
+ data-disabled={rowDisabled || undefined}
453
+ className={cn(
454
+ onRowClick && !rowDisabled && "cursor-pointer",
455
+ striped && rowIndex % 2 === 1 && "bg-muted/20",
456
+ rowDisabled && "pointer-events-none opacity-55",
457
+ getRowClassName(row, rowClassName)
458
+ )}
459
+ onClick={() => {
460
+ if (!rowDisabled) onRowClick?.(row)
461
+ }}
462
+ onDoubleClick={() => {
463
+ if (!rowDisabled) onRowDoubleClick?.(row)
464
+ }}
465
+ >
466
+ {row.getVisibleCells().map((cell) => {
467
+ const renderedCell = flexRender(cell.column.columnDef.cell, cell.getContext())
468
+
469
+ return (
470
+ <TableCell
471
+ key={cell.id}
472
+ className={cn(
473
+ densityCellClassName[density],
474
+ bordered && "border-r last:border-r-0",
475
+ getCellClassName(cell, cellClassName)
476
+ )}
477
+ >
478
+ {isEmptyCellContent(renderedCell) ? cellFallback : renderedCell}
479
+ </TableCell>
480
+ )
481
+ })}
482
+ </TableRow>
483
+ )
484
+ })}
485
+ </TableBody>
486
+ </Table>
487
+
488
+ {showPagination && paginationConfig && (
489
+ <DataTablePagination
490
+ pageIndex={paginationConfig.pageIndex}
491
+ pageSize={paginationConfig.pageSize}
492
+ pageCount={paginationConfig.pageCount}
493
+ rowCount={paginationConfig.rowCount}
494
+ pageSizeOptions={paginationConfig.pageSizeOptions}
495
+ showPageSize={paginationConfig.showPageSize}
496
+ labels={paginationConfig.labels}
497
+ disabled={isLoading}
498
+ onPageChange={paginationConfig.onPageChange}
499
+ onPageSizeChange={paginationConfig.onPageSizeChange}
500
+ />
501
+ )}
502
+ </div>
503
+ </div>
504
+ )
505
+ }
506
+
507
+ export { DataTable }
@@ -0,0 +1,12 @@
1
+ export * from './data-table'
2
+ export * from './data-table-pagination'
3
+ export * from './data-table-toolbar'
4
+ export * from './data-table-column-visibility-menu'
5
+ export * from './data-table-select-column'
6
+ export * from './data-table-sortable-header'
7
+ export * from './data-table-row-actions'
8
+ export * from './data-table-actions-column'
9
+ export * from './data-table-bulk-actions'
10
+ export * from './data-table-view-presets'
11
+ export * from './table-export-menu'
12
+ export * from './table-import-button'
@@ -0,0 +1,10 @@
1
+ export * from "./data-table"
2
+ export * from "./data-table-pagination"
3
+ export * from "./data-table-toolbar"
4
+ export * from "./data-table-column-visibility-menu"
5
+ export * from "./data-table-select-column"
6
+ export * from "./data-table-sortable-header"
7
+ export * from "./data-table-row-actions"
8
+ export * from "./data-table-actions-column"
9
+ export * from "./data-table-bulk-actions"
10
+ export * from "./data-table-view-presets"
@@ -0,0 +1,56 @@
1
+ import * as React from "react"
2
+ import { DownloadIcon } from "lucide-react"
3
+
4
+ import { Button, type ButtonProps } from "@/components/ui/button"
5
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
6
+
7
+ export type TableExportOption = {
8
+ key: string
9
+ label: React.ReactNode
10
+ description?: React.ReactNode
11
+ icon?: React.ReactNode
12
+ disabled?: boolean
13
+ }
14
+
15
+ export type TableExportMenuProps = Omit<ButtonProps, "onSelect"> & {
16
+ options?: TableExportOption[]
17
+ label?: React.ReactNode
18
+ menuLabel?: React.ReactNode
19
+ onExport?: (option: TableExportOption) => void
20
+ renderOption?: (option: TableExportOption) => React.ReactNode
21
+ }
22
+
23
+ const defaultExportOptions: TableExportOption[] = [
24
+ { key: "csv", label: "CSV" },
25
+ { key: "xlsx", label: "Excel" },
26
+ { key: "pdf", label: "PDF" },
27
+ ]
28
+
29
+ function TableExportMenu({ options = defaultExportOptions, label = "Export", menuLabel = "Export as", onExport, renderOption, children, ...props }: TableExportMenuProps) {
30
+ return (
31
+ <DropdownMenu>
32
+ <DropdownMenuTrigger render={<Button type="button" variant="outline" size="sm" {...props} />}>
33
+ <DownloadIcon data-icon="inline-start" />
34
+ {children ?? label}
35
+ </DropdownMenuTrigger>
36
+ <DropdownMenuContent align="end" className="min-w-44">
37
+ {menuLabel && <DropdownMenuLabel>{menuLabel}</DropdownMenuLabel>}
38
+ {options.map((option) => (
39
+ <DropdownMenuItem key={option.key} disabled={option.disabled} onClick={() => onExport?.(option)}>
40
+ {renderOption?.(option) ?? (
41
+ <>
42
+ {option.icon}
43
+ <span className="grid gap-0.5">
44
+ <span>{option.label}</span>
45
+ {option.description && <span className="text-xs text-muted-foreground">{option.description}</span>}
46
+ </span>
47
+ </>
48
+ )}
49
+ </DropdownMenuItem>
50
+ ))}
51
+ </DropdownMenuContent>
52
+ </DropdownMenu>
53
+ )
54
+ }
55
+
56
+ export { TableExportMenu }
@@ -0,0 +1,43 @@
1
+ import * as React from "react"
2
+ import { UploadIcon } from "lucide-react"
3
+
4
+ import { Button, type ButtonProps } from "@/components/ui/button"
5
+
6
+ export type TableImportButtonProps = Omit<ButtonProps, "onChange"> & {
7
+ accept?: string
8
+ multiple?: boolean
9
+ label?: React.ReactNode
10
+ inputName?: string
11
+ onFilesSelect?: (files: File[]) => void
12
+ onFileSelect?: (file: File) => void
13
+ }
14
+
15
+ function TableImportButton({ accept = ".csv,.xlsx,.xls", multiple = false, label = "Import", inputName, onFilesSelect, onFileSelect, disabled, children, ...props }: TableImportButtonProps) {
16
+ const inputRef = React.useRef<HTMLInputElement>(null)
17
+
18
+ return (
19
+ <>
20
+ <Button type="button" variant="outline" size="sm" disabled={disabled} onClick={() => inputRef.current?.click()} {...props}>
21
+ <UploadIcon data-icon="inline-start" />
22
+ {children ?? label}
23
+ </Button>
24
+ <input
25
+ ref={inputRef}
26
+ name={inputName}
27
+ type="file"
28
+ accept={accept}
29
+ multiple={multiple}
30
+ disabled={disabled}
31
+ className="sr-only"
32
+ onChange={(event) => {
33
+ const files = Array.from(event.currentTarget.files ?? [])
34
+ onFilesSelect?.(files)
35
+ if (files[0]) onFileSelect?.(files[0])
36
+ event.currentTarget.value = ""
37
+ }}
38
+ />
39
+ </>
40
+ )
41
+ }
42
+
43
+ export { TableImportButton }