@moontra/moonui-pro 2.20.1 → 2.20.3

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 (162) hide show
  1. package/dist/index.d.ts +691 -261
  2. package/dist/index.mjs +7418 -4934
  3. package/package.json +11 -5
  4. package/plugin/index.d.ts +86 -0
  5. package/plugin/index.js +308 -0
  6. package/scripts/postbuild.js +27 -0
  7. package/scripts/postinstall.js +176 -23
  8. package/src/__tests__/use-intersection-observer.test.tsx +0 -216
  9. package/src/__tests__/use-local-storage.test.tsx +0 -174
  10. package/src/__tests__/use-pro-access.test.tsx +0 -183
  11. package/src/components/advanced-chart/advanced-chart.test.tsx +0 -281
  12. package/src/components/advanced-chart/index.tsx +0 -1242
  13. package/src/components/advanced-forms/index.tsx +0 -426
  14. package/src/components/animated-button/index.tsx +0 -385
  15. package/src/components/calendar/event-dialog.tsx +0 -372
  16. package/src/components/calendar/index.tsx +0 -1073
  17. package/src/components/calendar-pro/index.tsx +0 -1697
  18. package/src/components/color-picker/index.tsx +0 -432
  19. package/src/components/credit-card-input/index.tsx +0 -406
  20. package/src/components/dashboard/dashboard-grid.tsx +0 -462
  21. package/src/components/dashboard/demo.tsx +0 -425
  22. package/src/components/dashboard/index.tsx +0 -1046
  23. package/src/components/dashboard/time-range-picker.tsx +0 -336
  24. package/src/components/dashboard/types.ts +0 -222
  25. package/src/components/dashboard/widgets/activity-feed.tsx +0 -344
  26. package/src/components/dashboard/widgets/chart-widget.tsx +0 -418
  27. package/src/components/dashboard/widgets/metric-card.tsx +0 -343
  28. package/src/components/data-table/data-table-bulk-actions.tsx +0 -204
  29. package/src/components/data-table/data-table-column-toggle.tsx +0 -169
  30. package/src/components/data-table/data-table-export.ts +0 -156
  31. package/src/components/data-table/data-table-filter-drawer.tsx +0 -448
  32. package/src/components/data-table/data-table.test.tsx +0 -187
  33. package/src/components/data-table/index.tsx +0 -845
  34. package/src/components/draggable-list/index.tsx +0 -100
  35. package/src/components/enhanced/badge.tsx +0 -191
  36. package/src/components/enhanced/button.tsx +0 -362
  37. package/src/components/enhanced/card.tsx +0 -266
  38. package/src/components/enhanced/dialog.tsx +0 -246
  39. package/src/components/enhanced/index.ts +0 -4
  40. package/src/components/error-boundary/index.tsx +0 -109
  41. package/src/components/file-upload/file-upload.test.tsx +0 -243
  42. package/src/components/file-upload/index.tsx +0 -1660
  43. package/src/components/floating-action-button/index.tsx +0 -206
  44. package/src/components/form-wizard/form-wizard-context.tsx +0 -307
  45. package/src/components/form-wizard/form-wizard-navigation.tsx +0 -118
  46. package/src/components/form-wizard/form-wizard-progress.tsx +0 -298
  47. package/src/components/form-wizard/form-wizard-step.tsx +0 -111
  48. package/src/components/form-wizard/index.tsx +0 -102
  49. package/src/components/form-wizard/types.ts +0 -76
  50. package/src/components/gesture-drawer/index.tsx +0 -551
  51. package/src/components/github-stars/github-api.ts +0 -426
  52. package/src/components/github-stars/hooks.ts +0 -516
  53. package/src/components/github-stars/index.tsx +0 -375
  54. package/src/components/github-stars/types.ts +0 -148
  55. package/src/components/github-stars/variants.tsx +0 -513
  56. package/src/components/health-check/index.tsx +0 -439
  57. package/src/components/hover-card-3d/index.tsx +0 -530
  58. package/src/components/index.ts +0 -128
  59. package/src/components/internal/index.ts +0 -78
  60. package/src/components/kanban/add-card-modal.tsx +0 -502
  61. package/src/components/kanban/card-detail-modal.tsx +0 -761
  62. package/src/components/kanban/index.ts +0 -13
  63. package/src/components/kanban/kanban.tsx +0 -1684
  64. package/src/components/kanban/types.ts +0 -168
  65. package/src/components/lazy-component/index.tsx +0 -823
  66. package/src/components/license-error/index.tsx +0 -29
  67. package/src/components/magnetic-button/index.tsx +0 -167
  68. package/src/components/memory-efficient-data/index.tsx +0 -1016
  69. package/src/components/moonui-quiz-form/index.tsx +0 -817
  70. package/src/components/optimized-image/index.tsx +0 -425
  71. package/src/components/performance-debugger/index.tsx +0 -589
  72. package/src/components/performance-monitor/index.tsx +0 -794
  73. package/src/components/phone-number-input/index.tsx +0 -338
  74. package/src/components/pinch-zoom/index.tsx +0 -566
  75. package/src/components/quiz-form/index.tsx +0 -479
  76. package/src/components/rich-text-editor/index-old-backup.tsx +0 -437
  77. package/src/components/rich-text-editor/index.tsx +0 -2324
  78. package/src/components/rich-text-editor/slash-commands-extension.ts +0 -220
  79. package/src/components/rich-text-editor/slash-commands.css +0 -35
  80. package/src/components/rich-text-editor/table-styles.css +0 -65
  81. package/src/components/sidebar/index.tsx +0 -865
  82. package/src/components/spotlight-card/index.tsx +0 -191
  83. package/src/components/swipeable-card/index.tsx +0 -100
  84. package/src/components/timeline/index.tsx +0 -1148
  85. package/src/components/ui/accordion.tsx +0 -73
  86. package/src/components/ui/alert-dialog.tsx +0 -141
  87. package/src/components/ui/alert.tsx +0 -141
  88. package/src/components/ui/aspect-ratio.tsx +0 -245
  89. package/src/components/ui/avatar.tsx +0 -153
  90. package/src/components/ui/badge.tsx +0 -228
  91. package/src/components/ui/breadcrumb.tsx +0 -214
  92. package/src/components/ui/button.tsx +0 -222
  93. package/src/components/ui/calendar.tsx +0 -387
  94. package/src/components/ui/card.tsx +0 -214
  95. package/src/components/ui/checkbox.tsx +0 -259
  96. package/src/components/ui/collapsible.tsx +0 -135
  97. package/src/components/ui/color-picker.tsx +0 -97
  98. package/src/components/ui/command.tsx +0 -225
  99. package/src/components/ui/dialog.tsx +0 -334
  100. package/src/components/ui/dropdown-menu.tsx +0 -218
  101. package/src/components/ui/gesture-drawer.tsx +0 -11
  102. package/src/components/ui/hover-card.tsx +0 -29
  103. package/src/components/ui/index.ts +0 -190
  104. package/src/components/ui/input.tsx +0 -222
  105. package/src/components/ui/label.tsx +0 -29
  106. package/src/components/ui/lightbox.tsx +0 -606
  107. package/src/components/ui/magnetic-button.tsx +0 -129
  108. package/src/components/ui/media-gallery.tsx +0 -612
  109. package/src/components/ui/pagination.tsx +0 -123
  110. package/src/components/ui/popover.tsx +0 -185
  111. package/src/components/ui/progress.tsx +0 -30
  112. package/src/components/ui/radio-group.tsx +0 -257
  113. package/src/components/ui/scroll-area.tsx +0 -47
  114. package/src/components/ui/select.tsx +0 -374
  115. package/src/components/ui/separator.tsx +0 -145
  116. package/src/components/ui/sheet.tsx +0 -139
  117. package/src/components/ui/skeleton.tsx +0 -20
  118. package/src/components/ui/slider.tsx +0 -354
  119. package/src/components/ui/spotlight-card.tsx +0 -119
  120. package/src/components/ui/switch.tsx +0 -86
  121. package/src/components/ui/table.tsx +0 -329
  122. package/src/components/ui/tabs.tsx +0 -198
  123. package/src/components/ui/textarea.tsx +0 -28
  124. package/src/components/ui/toast.tsx +0 -317
  125. package/src/components/ui/toggle.tsx +0 -119
  126. package/src/components/ui/tooltip.tsx +0 -151
  127. package/src/components/virtual-list/index.tsx +0 -668
  128. package/src/hooks/use-chart.ts +0 -205
  129. package/src/hooks/use-data-table.ts +0 -182
  130. package/src/hooks/use-docs-pro-access.ts +0 -13
  131. package/src/hooks/use-license-check.ts +0 -65
  132. package/src/hooks/use-subscription.ts +0 -19
  133. package/src/hooks/use-toast.ts +0 -15
  134. package/src/index.ts +0 -14
  135. package/src/lib/ai-providers.ts +0 -377
  136. package/src/lib/component-metadata.ts +0 -18
  137. package/src/lib/micro-interactions.ts +0 -255
  138. package/src/lib/paddle.ts +0 -17
  139. package/src/lib/utils.ts +0 -6
  140. package/src/patterns/login-form/index.tsx +0 -276
  141. package/src/patterns/login-form/types.ts +0 -67
  142. package/src/setupTests.ts +0 -41
  143. package/src/styles/advanced-chart.css +0 -239
  144. package/src/styles/calendar.css +0 -35
  145. package/src/styles/design-system.css +0 -363
  146. package/src/styles/index.css +0 -85
  147. package/src/styles/tailwind.css +0 -7
  148. package/src/styles/tokens.css +0 -455
  149. package/src/types/moonui.d.ts +0 -22
  150. package/src/types/next-auth.d.ts +0 -21
  151. package/src/use-intersection-observer.tsx +0 -154
  152. package/src/use-local-storage.tsx +0 -71
  153. package/src/use-paddle.ts +0 -138
  154. package/src/use-performance-optimizer.ts +0 -389
  155. package/src/use-pro-access.ts +0 -141
  156. package/src/use-scroll-animation.ts +0 -219
  157. package/src/use-subscription.ts +0 -37
  158. package/src/use-toast.ts +0 -32
  159. package/src/utils/chart-helpers.ts +0 -357
  160. package/src/utils/cn.ts +0 -6
  161. package/src/utils/data-processing.ts +0 -151
  162. package/src/utils/license-validator.tsx +0 -183
@@ -1,845 +0,0 @@
1
- "use client"
2
-
3
- import React from 'react'
4
- import {
5
- useReactTable,
6
- getCoreRowModel,
7
- getFilteredRowModel,
8
- getPaginationRowModel,
9
- getSortedRowModel,
10
- ColumnDef,
11
- flexRender,
12
- SortingState,
13
- ColumnFiltersState,
14
- VisibilityState,
15
- OnChangeFn,
16
- Row,
17
- } from '@tanstack/react-table'
18
- import { Button } from '../ui/button'
19
- import { Input } from '../ui/input'
20
- import { Card, CardContent } from '../ui/card'
21
- import {
22
- ChevronLeft,
23
- ChevronRight,
24
- ChevronsLeft,
25
- ChevronsRight,
26
- ChevronDown,
27
- ArrowUpDown,
28
- ArrowUp,
29
- ArrowDown,
30
- Search,
31
- Filter,
32
- Download,
33
- Settings,
34
- Lock,
35
- Sparkles,
36
- Loader2,
37
- FileDown,
38
- FileJson,
39
- FileSpreadsheet
40
- } from 'lucide-react'
41
- import { cn } from '../../lib/utils'
42
- import { useSubscription } from '../../hooks/use-subscription'
43
- import { motion, AnimatePresence } from 'framer-motion'
44
- import { DataTableColumnToggle } from './data-table-column-toggle'
45
- import { DataTableBulkActions, type BulkAction } from './data-table-bulk-actions'
46
- import { exportData, type ExportFormat, getVisibleColumns } from './data-table-export'
47
- import { DataTableFilterDrawer, type FilterCondition, type FilterOperator } from './data-table-filter-drawer'
48
- import {
49
- DropdownMenu,
50
- DropdownMenuContent,
51
- DropdownMenuItem,
52
- DropdownMenuTrigger,
53
- } from '../ui/dropdown-menu'
54
-
55
- interface DataTableProps<TData, TValue> {
56
- columns: ColumnDef<TData, TValue>[]
57
- data: TData[]
58
- searchable?: boolean
59
- filterable?: boolean
60
- exportable?: boolean | {
61
- formats?: ExportFormat[]
62
- filename?: string
63
- onExport?: (data: TData[], format: ExportFormat) => void
64
- }
65
- selectable?: boolean
66
- pagination?: boolean
67
- pageSize?: number
68
- className?: string
69
- onRowSelect?: (rows: TData[]) => void
70
- onExport?: (data: TData[]) => void
71
- enableExpandable?: boolean
72
- renderSubComponent?: (props: { row: { original: TData; id: string } }) => React.ReactNode
73
- expandedRows?: Set<string>
74
- onRowExpandChange?: (expandedRows: Set<string>) => void
75
- bulkActions?: BulkAction<TData>[]
76
- // Additional props for compatibility
77
- enableSorting?: boolean
78
- enableFiltering?: boolean
79
- enablePagination?: boolean
80
- enableColumnVisibility?: boolean
81
- enableRowSelection?: boolean
82
- filterPlaceholder?: string
83
- defaultPageSize?: number
84
- manualPagination?: boolean
85
- pageCount?: number
86
- onPaginationChange?: (updater: any) => void
87
- onSortingChange?: OnChangeFn<SortingState>
88
- onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>
89
- state?: any
90
- features?: {
91
- sorting?: boolean
92
- filtering?: boolean
93
- pagination?: boolean
94
- search?: boolean
95
- columnVisibility?: boolean
96
- rowSelection?: boolean
97
- export?: boolean | string[]
98
- density?: boolean
99
- fullscreen?: boolean
100
- print?: boolean
101
- }
102
- theme?: {
103
- headerBg?: string
104
- headerText?: string
105
- borderColor?: string
106
- rowHoverBg?: string
107
- selectedRowBg?: string
108
- }
109
- texts?: {
110
- searchPlaceholder?: string
111
- noResults?: string
112
- rowsPerPage?: string
113
- selectedRows?: string
114
- exportButton?: string
115
- columnsButton?: string
116
- densityButton?: string
117
- filterButton?: string
118
- }
119
- }
120
-
121
- export function DataTable<TData, TValue>({
122
- columns: originalColumns,
123
- data,
124
- searchable = true,
125
- filterable = true,
126
- exportable = true,
127
- selectable = false,
128
- pagination = true,
129
- pageSize = 10,
130
- className,
131
- onRowSelect,
132
- onExport,
133
- enableExpandable = false,
134
- renderSubComponent,
135
- expandedRows: controlledExpandedRows,
136
- onRowExpandChange,
137
- bulkActions = [],
138
- features = {},
139
- theme = {},
140
- texts = {},
141
- // Additional props
142
- enableSorting = true,
143
- enableFiltering = true,
144
- enablePagination = true,
145
- enableColumnVisibility = true,
146
- enableRowSelection,
147
- filterPlaceholder = "Search all columns...",
148
- defaultPageSize,
149
- manualPagination = false,
150
- pageCount,
151
- onPaginationChange,
152
- onSortingChange,
153
- onColumnFiltersChange,
154
- state: externalState,
155
- }: DataTableProps<TData, TValue>) {
156
- // Process columns to ensure they can be hidden and use custom filter
157
- const columns = React.useMemo(() => {
158
- return originalColumns.map(col => {
159
- // Remove any enableHiding: false to avoid conflicts
160
- const { enableHiding, ...restCol } = col as any;
161
- return {
162
- ...restCol,
163
- enableHiding: true, // Force all columns to be hideable
164
- filterFn: 'custom', // Use our custom filter function
165
- }
166
- })
167
- }, [originalColumns])
168
- // Check if we're in docs mode or have pro access
169
- const { hasProAccess, isLoading } = useSubscription()
170
-
171
- // In docs mode, always show the component
172
-
173
- // If not in docs mode and no pro access, show upgrade prompt
174
- if (!isLoading && !hasProAccess) {
175
- return (
176
- <Card className={cn("w-full", className)}>
177
- <CardContent className="py-12 text-center">
178
- <div className="max-w-md mx-auto space-y-4">
179
- <div className="rounded-full bg-purple-100 dark:bg-purple-900/30 p-3 w-fit mx-auto">
180
- <Lock className="h-6 w-6 text-purple-600 dark:text-purple-400" />
181
- </div>
182
- <div>
183
- <h3 className="font-semibold text-lg mb-2">Pro Feature</h3>
184
- <p className="text-muted-foreground text-sm mb-4">
185
- Data Table is available exclusively to MoonUI Pro subscribers.
186
- </p>
187
- <div className="flex gap-3 justify-center">
188
- <a href="/pricing">
189
- <Button size="sm">
190
- <Sparkles className="mr-2 h-4 w-4" />
191
- Upgrade to Pro
192
- </Button>
193
- </a>
194
- </div>
195
- </div>
196
- </div>
197
- </CardContent>
198
- </Card>
199
- )
200
- }
201
- const [sorting, setSorting] = React.useState<SortingState>([])
202
- const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
203
- const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({})
204
- const [rowSelection, setRowSelection] = React.useState({})
205
- const [globalFilter, setGlobalFilter] = React.useState('')
206
- const [isPaginationLoading, setIsPaginationLoading] = React.useState(false)
207
- const [internalExpandedRows, setInternalExpandedRows] = React.useState<Set<string>>(new Set())
208
- const [filterDrawerOpen, setFilterDrawerOpen] = React.useState(false)
209
-
210
- // Use controlled or internal expanded state
211
- const expandedRows = controlledExpandedRows || internalExpandedRows
212
- const setExpandedRows = onRowExpandChange ?
213
- (newExpanded: Set<string>) => onRowExpandChange(newExpanded) :
214
- setInternalExpandedRows
215
-
216
- const actualPageSize = defaultPageSize || pageSize
217
-
218
- // Memoize data to prevent unnecessary re-renders
219
- const stableData = React.useMemo(() => data, [data])
220
-
221
- const table = useReactTable({
222
- data: stableData,
223
- columns,
224
- onSortingChange: onSortingChange !== undefined ? onSortingChange : setSorting,
225
- onColumnFiltersChange: onColumnFiltersChange !== undefined ? onColumnFiltersChange : setColumnFilters,
226
- getCoreRowModel: getCoreRowModel(),
227
- getPaginationRowModel: getPaginationRowModel(),
228
- getSortedRowModel: getSortedRowModel(),
229
- getFilteredRowModel: getFilteredRowModel(),
230
- onColumnVisibilityChange: setColumnVisibility,
231
- onRowSelectionChange: setRowSelection,
232
- onGlobalFilterChange: setGlobalFilter,
233
- globalFilterFn: 'includesString',
234
- filterFns: {
235
- custom: (row, columnId, filterValue) => {
236
- if (!filterValue?.custom || !filterValue?.filters) return true
237
-
238
- const filters = filterValue.filters as FilterCondition[]
239
- const matchAll = filterValue.matchAll !== undefined ? filterValue.matchAll : true
240
-
241
- // Get all filter conditions (not just for this column)
242
- const allFilterResults = filters.map(filterCondition => {
243
- const cellValue = row.getValue(filterCondition.column)
244
- const filterVal = filterCondition.value
245
-
246
- switch (filterCondition.operator) {
247
- case 'equals':
248
- return cellValue === filterVal
249
- case 'notEquals':
250
- return cellValue !== filterVal
251
- case 'contains':
252
- return String(cellValue).toLowerCase().includes(String(filterVal).toLowerCase())
253
- case 'notContains':
254
- return !String(cellValue).toLowerCase().includes(String(filterVal).toLowerCase())
255
- case 'startsWith':
256
- return String(cellValue).toLowerCase().startsWith(String(filterVal).toLowerCase())
257
- case 'endsWith':
258
- return String(cellValue).toLowerCase().endsWith(String(filterVal).toLowerCase())
259
- case 'greaterThan':
260
- return Number(cellValue) > Number(filterVal)
261
- case 'lessThan':
262
- return Number(cellValue) < Number(filterVal)
263
- case 'greaterThanOrEqual':
264
- return Number(cellValue) >= Number(filterVal)
265
- case 'lessThanOrEqual':
266
- return Number(cellValue) <= Number(filterVal)
267
- case 'isNull':
268
- return cellValue == null || cellValue === ''
269
- case 'isNotNull':
270
- return cellValue != null && cellValue !== ''
271
- default:
272
- return true
273
- }
274
- })
275
-
276
- // Apply match logic
277
- if (matchAll) {
278
- return allFilterResults.every(result => result)
279
- } else {
280
- return allFilterResults.some(result => result)
281
- }
282
- }
283
- },
284
- manualPagination,
285
- pageCount,
286
- state: {
287
- sorting: externalState?.sorting ?? sorting,
288
- columnFilters: externalState?.columnFilters ?? columnFilters,
289
- columnVisibility: externalState?.columnVisibility ?? columnVisibility,
290
- rowSelection: externalState?.rowSelection ?? rowSelection,
291
- globalFilter: externalState?.globalFilter ?? globalFilter,
292
- ...(externalState || {}),
293
- },
294
- initialState: {
295
- pagination: {
296
- pageSize: actualPageSize,
297
- },
298
- },
299
- // Prevent re-renders on state changes
300
- autoResetAll: false,
301
- autoResetPageIndex: false,
302
- autoResetExpanded: false,
303
- getRowId: (row: TData) => (row as any).id || (row as any).orderId || Math.random().toString(),
304
- })
305
-
306
- React.useEffect(() => {
307
- if (onRowSelect && selectable) {
308
- const selectedRows = table.getFilteredSelectedRowModel().rows.map(row => row.original)
309
- onRowSelect(selectedRows)
310
- }
311
- }, [rowSelection, onRowSelect, selectable, table])
312
-
313
- // Memoize row model to prevent unnecessary re-renders when only expanded state changes
314
- const tableState = table.getState()
315
- const rowModel = table.getRowModel()
316
-
317
- // Use a ref to track if rows actually changed
318
- const rowsRef = React.useRef(rowModel.rows)
319
- const rowsChanged = React.useMemo(() => {
320
- const changed = rowsRef.current !== rowModel.rows
321
- if (changed) {
322
- rowsRef.current = rowModel.rows
323
- }
324
- return changed
325
- }, [rowModel.rows])
326
-
327
- const rows = rowsRef.current
328
-
329
- // Merge features with defaults
330
- const enabledFeatures = {
331
- sorting: features.sorting !== false,
332
- filtering: features.filtering !== false || filterable,
333
- pagination: features.pagination !== false || pagination,
334
- search: features.search !== false || searchable,
335
- columnVisibility: features.columnVisibility !== false,
336
- rowSelection: features.rowSelection !== false || selectable,
337
- export: features.export !== false || exportable,
338
- }
339
-
340
- const handleExport = async (format: ExportFormat) => {
341
- const selectedRows = table.getFilteredSelectedRowModel().rows
342
- const dataToExport = selectedRows.length > 0
343
- ? selectedRows.map(row => row.original)
344
- : table.getFilteredRowModel().rows.map(row => row.original)
345
-
346
- // Use custom export handler if provided
347
- if (typeof exportable === 'object' && exportable.onExport) {
348
- exportable.onExport(dataToExport, format)
349
- return
350
- }
351
-
352
- // Use legacy onExport if provided
353
- if (onExport) {
354
- onExport(dataToExport)
355
- return
356
- }
357
-
358
- // Default export behavior
359
- const filename = typeof exportable === 'object' && exportable.filename
360
- ? exportable.filename
361
- : 'data-export'
362
-
363
- const visibleColumns = getVisibleColumns(columns as any, columnVisibility)
364
-
365
- await exportData(dataToExport as Record<string, any>[], {
366
- format,
367
- filename,
368
- columns: visibleColumns,
369
- includeHeaders: true
370
- })
371
- }
372
-
373
- // Parse export options
374
- const exportFormats: ExportFormat[] = React.useMemo(() => {
375
- if (!exportable) return []
376
- if (exportable === true) return ['csv', 'json']
377
- if (typeof exportable === 'object' && exportable.formats) {
378
- return exportable.formats
379
- }
380
- return ['csv', 'json']
381
- }, [exportable])
382
-
383
- const clearRowSelection = () => {
384
- table.resetRowSelection()
385
- }
386
-
387
- return (
388
- <div className={cn("moonui-pro-datatable-container flex flex-col gap-4", className)}>
389
- {/* Toolbar */}
390
- <div className="moonui-pro-datatable-toolbar flex items-center justify-between">
391
- <div className="flex items-center space-x-2">
392
- {searchable && (
393
- <div className="relative">
394
- <span suppressHydrationWarning>
395
- <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
396
- </span>
397
- <Input
398
- placeholder={filterPlaceholder}
399
- value={globalFilter}
400
- onChange={(e) => setGlobalFilter(e.target.value)}
401
- className="pl-8 w-64"
402
- />
403
- </div>
404
- )}
405
-
406
- {filterable && (
407
- <Button
408
- variant="outline"
409
- size="sm"
410
- onClick={() => setFilterDrawerOpen(true)}
411
- >
412
- <span suppressHydrationWarning><Filter className="mr-2 h-4 w-4" /></span>
413
- Filters
414
- {columnFilters.length > 0 && (
415
- <span className="ml-2 rounded-full bg-primary px-2 py-0.5 text-xs text-primary-foreground">
416
- {columnFilters.length}
417
- </span>
418
- )}
419
- </Button>
420
- )}
421
-
422
- {/* Bulk actions */}
423
- {selectable && bulkActions.length > 0 && (
424
- <DataTableBulkActions
425
- selectedRows={table.getFilteredSelectedRowModel().rows.map(row => row.original)}
426
- actions={bulkActions}
427
- onClearSelection={clearRowSelection}
428
- />
429
- )}
430
- </div>
431
-
432
- <div className="flex items-center space-x-2">
433
- {/* Export dropdown */}
434
- {exportable && exportFormats.length > 0 && (
435
- <DropdownMenu>
436
- <DropdownMenuTrigger asChild>
437
- <Button variant="outline" size="sm">
438
- <span suppressHydrationWarning><Download className="mr-2 h-4 w-4" /></span>
439
- Export
440
- </Button>
441
- </DropdownMenuTrigger>
442
- <DropdownMenuContent align="end">
443
- {exportFormats.includes('csv') && (
444
- <DropdownMenuItem onClick={() => handleExport('csv')}>
445
- <FileSpreadsheet className="mr-2 h-4 w-4" />
446
- Export as CSV
447
- </DropdownMenuItem>
448
- )}
449
- {exportFormats.includes('json') && (
450
- <DropdownMenuItem onClick={() => handleExport('json')}>
451
- <FileJson className="mr-2 h-4 w-4" />
452
- Export as JSON
453
- </DropdownMenuItem>
454
- )}
455
- {exportFormats.includes('xlsx') && (
456
- <DropdownMenuItem onClick={() => handleExport('xlsx')}>
457
- <FileDown className="mr-2 h-4 w-4" />
458
- Export as Excel
459
- </DropdownMenuItem>
460
- )}
461
- </DropdownMenuContent>
462
- </DropdownMenu>
463
- )}
464
-
465
- {/* Column visibility toggle */}
466
- <DataTableColumnToggle table={table} />
467
- </div>
468
- </div>
469
-
470
- {/* Table */}
471
- <div className="moonui-pro-datatable-wrapper rounded-md border overflow-hidden" style={{ contain: 'layout style' }}>
472
- <div style={{ overflowX: 'auto' }}>
473
- <table className="moonui-pro-datatable" style={{ width: '100%', tableLayout: 'auto' }}>
474
- <thead className="moonui-data-table-header">
475
- {table.getHeaderGroups().map((headerGroup) => (
476
- <tr key={headerGroup.id} className="moonui-data-table-row border-b">
477
- {headerGroup.headers
478
- .filter((header) => header.column.getIsVisible())
479
- .map((header) => (
480
- <th
481
- key={header.id}
482
- className="moonui-data-table-th h-12 px-4 text-left align-middle font-medium text-muted-foreground"
483
- >
484
- {header.isPlaceholder ? null : (
485
- <div
486
- className={cn(
487
- "flex items-center space-x-2",
488
- header.column.getCanSort() && "cursor-pointer select-none"
489
- )}
490
- onClick={header.column.getToggleSortingHandler()}
491
- >
492
- {flexRender(header.column.columnDef.header, header.getContext())}
493
- {header.column.getCanSort() && (
494
- <div className="ml-2">
495
- {header.column.getIsSorted() === 'asc' ? (
496
- <span suppressHydrationWarning><ArrowUp className="h-4 w-4" /></span>
497
- ) : header.column.getIsSorted() === 'desc' ? (
498
- <span suppressHydrationWarning><ArrowDown className="h-4 w-4" /></span>
499
- ) : (
500
- <span suppressHydrationWarning><ArrowUpDown className="h-4 w-4" /></span>
501
- )}
502
- </div>
503
- )}
504
- </div>
505
- )}
506
- </th>
507
- ))}
508
- </tr>
509
- ))}
510
- </thead>
511
- <tbody className="moonui-data-table-body">
512
- {isPaginationLoading ? (
513
- <motion.tr
514
- key="loading"
515
- initial={{ opacity: 0 }}
516
- animate={{ opacity: 1 }}
517
- exit={{ opacity: 0 }}
518
- transition={{ duration: 0.2 }}
519
- >
520
- <td colSpan={table.getAllLeafColumns().filter(col => col.getIsVisible()).length} className="h-24 text-center">
521
- <div className="flex items-center justify-center space-x-2">
522
- <span suppressHydrationWarning><Loader2 className="h-4 w-4 animate-spin" /></span>
523
- <span className="text-sm text-muted-foreground">Loading...</span>
524
- </div>
525
- </td>
526
- </motion.tr>
527
- ) : rows?.length ? (
528
- <>
529
- {rows.map((row, index) => {
530
- const rowId = (row.original as any).id || row.id
531
- const isExpanded = enableExpandable && expandedRows.has(rowId)
532
-
533
- return (
534
- <TableRow
535
- key={rowId}
536
- row={row}
537
- columns={columns}
538
- isExpanded={isExpanded}
539
- enableExpandable={enableExpandable}
540
- renderSubComponent={renderSubComponent}
541
- visibilityState={table.getState().columnVisibility}
542
- />
543
- );
544
- })}
545
- </>
546
- ) : (
547
- <motion.tr
548
- key="no-results"
549
- initial={{ opacity: 0 }}
550
- animate={{ opacity: 1 }}
551
- exit={{ opacity: 0 }}
552
- transition={{ duration: 0.2 }}
553
- >
554
- <td colSpan={table.getAllLeafColumns().filter(col => col.getIsVisible()).length} className="h-24 text-center">
555
- No results found.
556
- </td>
557
- </motion.tr>
558
- )}
559
- </tbody>
560
- </table>
561
- </div>
562
- </div>
563
-
564
- {/* Pagination */}
565
- {pagination && (
566
- <div className="flex items-center justify-between px-2">
567
- <div className="flex-1 text-sm text-muted-foreground">
568
- {selectable && table.getFilteredSelectedRowModel().rows.length > 0 && (
569
- <span>
570
- {table.getFilteredSelectedRowModel().rows.length} of{" "}
571
- {table.getFilteredRowModel().rows.length} row(s) selected.
572
- </span>
573
- )}
574
- </div>
575
- <div className="flex items-center space-x-6 lg:space-x-8">
576
- <div className="flex items-center space-x-2">
577
- <p className="text-sm font-medium">Rows per page</p>
578
- <select
579
- value={table.getState().pagination.pageSize}
580
- onChange={async (e) => {
581
- setIsPaginationLoading(true)
582
- await new Promise(resolve => setTimeout(resolve, 300))
583
- table.setPageSize(Number(e.target.value))
584
- setIsPaginationLoading(false)
585
- }}
586
- className="h-8 w-[70px] rounded border border-input bg-background px-3 py-1 text-sm"
587
- disabled={isPaginationLoading}
588
- >
589
- {[10, 20, 30, 40, 50].map((pageSize) => (
590
- <option key={pageSize} value={pageSize}>
591
- {pageSize}
592
- </option>
593
- ))}
594
- </select>
595
- </div>
596
- <div className="flex w-[100px] items-center justify-center text-sm font-medium">
597
- Page {table.getState().pagination.pageIndex + 1} of{" "}
598
- {table.getPageCount()}
599
- </div>
600
- <div className="flex items-center space-x-2">
601
- <Button
602
- variant="outline"
603
- className="hidden h-8 w-8 p-0 lg:flex"
604
- onClick={async () => {
605
- if (onPaginationChange) {
606
- onPaginationChange({ pageIndex: 0, pageSize: table.getState().pagination.pageSize })
607
- } else {
608
- setIsPaginationLoading(true)
609
- await new Promise(resolve => setTimeout(resolve, 300))
610
- table.setPageIndex(0)
611
- setIsPaginationLoading(false)
612
- }
613
- }}
614
- disabled={!table.getCanPreviousPage() || isPaginationLoading}
615
- >
616
- <span suppressHydrationWarning><ChevronsLeft className="h-4 w-4" /></span>
617
- </Button>
618
- <Button
619
- variant="outline"
620
- className="h-8 w-8 p-0"
621
- onClick={async () => {
622
- if (onPaginationChange) {
623
- const currentIndex = table.getState().pagination.pageIndex
624
- onPaginationChange({ pageIndex: currentIndex - 1, pageSize: table.getState().pagination.pageSize })
625
- } else {
626
- setIsPaginationLoading(true)
627
- await new Promise(resolve => setTimeout(resolve, 300))
628
- table.previousPage()
629
- setIsPaginationLoading(false)
630
- }
631
- }}
632
- disabled={!table.getCanPreviousPage() || isPaginationLoading}
633
- >
634
- <span suppressHydrationWarning><ChevronLeft className="h-4 w-4" /></span>
635
- </Button>
636
- <Button
637
- variant="outline"
638
- className="h-8 w-8 p-0"
639
- onClick={async () => {
640
- setIsPaginationLoading(true)
641
- await new Promise(resolve => setTimeout(resolve, 300))
642
- table.nextPage()
643
- setIsPaginationLoading(false)
644
- }}
645
- disabled={!table.getCanNextPage() || isPaginationLoading}
646
- >
647
- <span suppressHydrationWarning><ChevronRight className="h-4 w-4" /></span>
648
- </Button>
649
- <Button
650
- variant="outline"
651
- className="hidden h-8 w-8 p-0 lg:flex"
652
- onClick={async () => {
653
- setIsPaginationLoading(true)
654
- await new Promise(resolve => setTimeout(resolve, 300))
655
- table.setPageIndex(table.getPageCount() - 1)
656
- setIsPaginationLoading(false)
657
- }}
658
- disabled={!table.getCanNextPage() || isPaginationLoading}
659
- >
660
- <span suppressHydrationWarning><ChevronsRight className="h-4 w-4" /></span>
661
- </Button>
662
- </div>
663
- </div>
664
- </div>
665
- )}
666
-
667
- {/* Filter Drawer */}
668
- {filterable && (
669
- <DataTableFilterDrawer
670
- table={table}
671
- open={filterDrawerOpen}
672
- onOpenChange={setFilterDrawerOpen}
673
- />
674
- )}
675
- </div>
676
- )
677
- }
678
-
679
- /**
680
- * Helper function to create an expandable column
681
- * @param expandedRows - Set of expanded row IDs
682
- * @param onToggle - Function to toggle row expansion
683
- * @returns ColumnDef for expandable rows
684
- */
685
- export function getExpandableColumn<TData>(
686
- expandedRows: Set<string>,
687
- onToggle: (id: string) => void
688
- ): ColumnDef<TData, any> {
689
- return {
690
- id: "expander",
691
- header: () => null,
692
- size: 50,
693
- cell: ({ row }) => {
694
- const rowId = (row.original as any).id || row.id;
695
- const isExpanded = expandedRows.has(rowId);
696
-
697
- return (
698
- <button
699
- onClick={(e) => {
700
- e.stopPropagation();
701
- onToggle(rowId);
702
- }}
703
- className="p-2 hover:bg-muted rounded-md transition-colors"
704
- aria-label={isExpanded ? "Collapse row" : "Expand row"}
705
- >
706
- <span suppressHydrationWarning>
707
- {isExpanded ? (
708
- <ChevronDown className="h-4 w-4 text-muted-foreground" />
709
- ) : (
710
- <ChevronRight className="h-4 w-4 text-muted-foreground" />
711
- )}
712
- </span>
713
- </button>
714
- );
715
- },
716
- };
717
- }
718
-
719
- /**
720
- * Hook for managing expandable rows
721
- * @param initialExpanded - Initial set of expanded row IDs
722
- * @returns Object with expandedRows, toggleRow, and expandAll/collapseAll functions
723
- */
724
- export function useExpandableRows(initialExpanded: Set<string> = new Set()) {
725
- const [expandedRows, setExpandedRows] = React.useState<Set<string>>(initialExpanded);
726
-
727
- const toggleRow = React.useCallback((id: string) => {
728
- setExpandedRows(prev => {
729
- const newExpanded = new Set(prev);
730
- if (newExpanded.has(id)) {
731
- newExpanded.delete(id);
732
- } else {
733
- newExpanded.add(id);
734
- }
735
- return newExpanded;
736
- });
737
- }, []);
738
-
739
- const expandAll = React.useCallback((rowIds: string[]) => {
740
- setExpandedRows(new Set(rowIds));
741
- }, []);
742
-
743
- const collapseAll = React.useCallback(() => {
744
- setExpandedRows(new Set());
745
- }, []);
746
-
747
- return {
748
- expandedRows,
749
- setExpandedRows,
750
- toggleRow,
751
- expandAll,
752
- collapseAll,
753
- };
754
- }
755
-
756
- // Memoized table row component
757
- interface TableRowProps {
758
- row: Row<any>
759
- columns: ColumnDef<any, any>[]
760
- isExpanded: boolean
761
- enableExpandable: boolean
762
- renderSubComponent?: (props: { row: { original: any; id: string } }) => React.ReactNode
763
- visibilityState: Record<string, boolean>
764
- }
765
-
766
- const TableRow = React.memo(({
767
- row,
768
- columns,
769
- isExpanded,
770
- enableExpandable,
771
- renderSubComponent,
772
- visibilityState
773
- }: TableRowProps) => {
774
- const rowId = (row.original as any).id || row.id
775
-
776
- return (
777
- <>
778
- <tr
779
- className={cn(
780
- "border-b transition-colors hover:bg-muted/50",
781
- row.getIsSelected() && "bg-muted",
782
- isExpanded && "border-b-0"
783
- )}
784
- >
785
- {row.getAllCells()
786
- .filter((cell) => {
787
- // Manual visibility check
788
- const isVisible = visibilityState[cell.column.id] !== false;
789
- return isVisible;
790
- })
791
- .map((cell) => {
792
- return (
793
- <td key={cell.id} className="moonui-data-table-td p-4 align-middle">
794
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
795
- </td>
796
- );
797
- })}
798
- </tr>
799
-
800
- {isExpanded && renderSubComponent && (
801
- <tr className="border-b">
802
- <td colSpan={row.getAllCells().filter(cell =>
803
- visibilityState[cell.column.id] !== false
804
- ).length || 1} className="p-0 overflow-hidden">
805
- <div
806
- className="transition-all duration-300 ease-out"
807
- style={{
808
- maxHeight: isExpanded ? '1000px' : '0',
809
- opacity: isExpanded ? 1 : 0,
810
- }}
811
- >
812
- <div className="border-t border-border/50">
813
- {renderSubComponent({ row: { original: row.original, id: rowId } })}
814
- </div>
815
- </div>
816
- </td>
817
- </tr>
818
- )}
819
- </>
820
- )
821
- }, (prevProps, nextProps) => {
822
- // Custom comparison - only re-render if row data, expanded state, or visibility changed
823
- const prevRowId = (prevProps.row.original as any).id || prevProps.row.id
824
- const nextRowId = (nextProps.row.original as any).id || nextProps.row.id
825
-
826
- // Include visibility state in comparison
827
- const prevVisibilityKeys = Object.keys(prevProps.visibilityState).sort().join(',')
828
- const nextVisibilityKeys = Object.keys(nextProps.visibilityState).sort().join(',')
829
- const prevVisibilityValues = Object.values(prevProps.visibilityState).join(',')
830
- const nextVisibilityValues = Object.values(nextProps.visibilityState).join(',')
831
-
832
- return prevRowId === nextRowId &&
833
- prevProps.isExpanded === nextProps.isExpanded &&
834
- prevProps.row.getIsSelected() === nextProps.row.getIsSelected() &&
835
- prevVisibilityKeys === nextVisibilityKeys &&
836
- prevVisibilityValues === nextVisibilityValues
837
- })
838
-
839
- TableRow.displayName = 'TableRow'
840
-
841
- // Re-export types for convenience
842
- export { type ColumnDef } from "@tanstack/react-table";
843
- export type { BulkAction } from './data-table-bulk-actions';
844
- export type { ExportFormat } from './data-table-export';
845
- export type { FilterCondition, FilterOperator } from './data-table-filter-drawer';