@moontra/moonui-pro 2.2.19 → 2.3.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moontra/moonui-pro",
3
- "version": "2.2.19",
3
+ "version": "2.3.1",
4
4
  "description": "Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -313,13 +313,13 @@ export function Calendar({
313
313
  </div>
314
314
  </div>
315
315
  </CardHeader>
316
- <CardContent className="overflow-x-auto">
316
+ <CardContent>
317
317
  <div className="space-y-4">
318
318
  {/* Calendar Grid */}
319
- <div className="grid grid-cols-7 gap-1 w-full" style={{ minWidth: "500px" }}>
319
+ <div className="grid grid-cols-7 gap-1 w-full">
320
320
  {/* Day Headers */}
321
321
  {visibleDaysOfWeek.map((day) => (
322
- <div key={day} className="p-1 text-center text-xs font-medium text-muted-foreground min-w-[70px]">
322
+ <div key={day} className="p-1 text-center text-xs font-medium text-muted-foreground">
323
323
  {day}
324
324
  </div>
325
325
  ))}
@@ -336,7 +336,7 @@ export function Calendar({
336
336
  <div
337
337
  key={index}
338
338
  className={cn(
339
- "min-h-[80px] min-w-[70px] p-1 border border-border/50 cursor-pointer hover:bg-muted/50 transition-colors text-xs flex flex-col",
339
+ "min-h-[80px] p-1 border border-border/50 cursor-pointer hover:bg-muted/50 transition-colors text-xs flex flex-col",
340
340
  !isCurrentMonthDate && "text-muted-foreground bg-muted/20",
341
341
  isTodayDate && highlightToday && "bg-primary/10 border-primary/50",
342
342
  isSelectedDate && "bg-primary/20 border-primary",
@@ -0,0 +1,54 @@
1
+ /* MoonUI DataTable Component Styles */
2
+ .moonui-data-table-container {
3
+ /* Container specific styles */
4
+ }
5
+
6
+ .moonui-data-table-wrapper {
7
+ /* Wrapper specific styles */
8
+ }
9
+
10
+ .moonui-data-table {
11
+ /* Table specific styles */
12
+ border-collapse: collapse;
13
+ table-layout: fixed;
14
+ }
15
+
16
+ .moonui-data-table-header {
17
+ /* Header specific styles */
18
+ }
19
+
20
+ .moonui-data-table-body {
21
+ /* Body specific styles */
22
+ }
23
+
24
+ .moonui-data-table-row {
25
+ /* Row specific styles */
26
+ }
27
+
28
+ .moonui-data-table-th {
29
+ /* Table header cell specific styles */
30
+ }
31
+
32
+ .moonui-data-table-td {
33
+ /* Table data cell specific styles */
34
+ }
35
+
36
+ .moonui-data-table-toolbar {
37
+ /* Toolbar specific styles */
38
+ }
39
+
40
+ /* Prevent style leakage */
41
+ .moonui-data-table-container * {
42
+ box-sizing: border-box;
43
+ }
44
+
45
+ /* Ensure DataTable styles don't affect global styles */
46
+ .moonui-data-table-container table {
47
+ margin: 0;
48
+ padding: 0;
49
+ }
50
+
51
+ .moonui-data-table-container th,
52
+ .moonui-data-table-container td {
53
+ margin: 0;
54
+ }
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import React from 'react'
4
+ import './data-table.css'
4
5
  import {
5
6
  useReactTable,
6
7
  getCoreRowModel,
@@ -21,6 +22,7 @@ import {
21
22
  ChevronRight,
22
23
  ChevronsLeft,
23
24
  ChevronsRight,
25
+ ChevronDown,
24
26
  ArrowUpDown,
25
27
  ArrowUp,
26
28
  ArrowDown,
@@ -29,10 +31,12 @@ import {
29
31
  Download,
30
32
  Settings,
31
33
  Lock,
32
- Sparkles
34
+ Sparkles,
35
+ Loader2
33
36
  } from 'lucide-react'
34
37
  import { cn } from '../../lib/utils'
35
38
  import { useSubscription } from '../../hooks/use-subscription'
39
+ import { motion, AnimatePresence } from 'framer-motion'
36
40
 
37
41
  interface DataTableProps<TData, TValue> {
38
42
  columns: ColumnDef<TData, TValue>[]
@@ -46,6 +50,10 @@ interface DataTableProps<TData, TValue> {
46
50
  className?: string
47
51
  onRowSelect?: (rows: TData[]) => void
48
52
  onExport?: (data: TData[]) => void
53
+ enableExpandable?: boolean
54
+ renderSubComponent?: (props: { row: { original: TData; id: string } }) => React.ReactNode
55
+ expandedRows?: Set<string>
56
+ onRowExpandChange?: (expandedRows: Set<string>) => void
49
57
  features?: {
50
58
  sorting?: boolean
51
59
  filtering?: boolean
@@ -89,6 +97,10 @@ export function DataTable<TData, TValue>({
89
97
  className,
90
98
  onRowSelect,
91
99
  onExport,
100
+ enableExpandable = false,
101
+ renderSubComponent,
102
+ expandedRows: controlledExpandedRows,
103
+ onRowExpandChange,
92
104
  features = {},
93
105
  theme = {},
94
106
  texts = {},
@@ -131,6 +143,14 @@ export function DataTable<TData, TValue>({
131
143
  const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({})
132
144
  const [rowSelection, setRowSelection] = React.useState({})
133
145
  const [globalFilter, setGlobalFilter] = React.useState('')
146
+ const [isPaginationLoading, setIsPaginationLoading] = React.useState(false)
147
+ const [internalExpandedRows, setInternalExpandedRows] = React.useState<Set<string>>(new Set())
148
+
149
+ // Use controlled or internal expanded state
150
+ const expandedRows = controlledExpandedRows || internalExpandedRows
151
+ const setExpandedRows = onRowExpandChange ?
152
+ (newExpanded: Set<string>) => onRowExpandChange(newExpanded) :
153
+ setInternalExpandedRows
134
154
 
135
155
  const table = useReactTable({
136
156
  data,
@@ -188,9 +208,9 @@ export function DataTable<TData, TValue>({
188
208
  }
189
209
 
190
210
  return (
191
- <div className={cn("space-y-4", className)}>
211
+ <div className={cn("moonui-data-table-container space-y-4", className)}>
192
212
  {/* Toolbar */}
193
- <div className="flex items-center justify-between">
213
+ <div className="moonui-data-table-toolbar flex items-center justify-between">
194
214
  <div className="flex items-center space-x-2">
195
215
  {searchable && (
196
216
  <div className="relative">
@@ -228,15 +248,15 @@ export function DataTable<TData, TValue>({
228
248
  </div>
229
249
 
230
250
  {/* Table */}
231
- <div className="rounded-md border">
232
- <table className="w-full">
233
- <thead>
251
+ <div className="moonui-data-table-wrapper rounded-md border overflow-hidden">
252
+ <table className="moonui-data-table w-full">
253
+ <thead className="moonui-data-table-header">
234
254
  {table.getHeaderGroups().map((headerGroup) => (
235
- <tr key={headerGroup.id} className="border-b">
255
+ <tr key={headerGroup.id} className="moonui-data-table-row border-b">
236
256
  {headerGroup.headers.map((header) => (
237
257
  <th
238
258
  key={header.id}
239
- className="h-12 px-4 text-left align-middle font-medium text-muted-foreground"
259
+ className="moonui-data-table-th h-12 px-4 text-left align-middle font-medium text-muted-foreground"
240
260
  >
241
261
  {header.isPlaceholder ? null : (
242
262
  <div
@@ -265,30 +285,86 @@ export function DataTable<TData, TValue>({
265
285
  </tr>
266
286
  ))}
267
287
  </thead>
268
- <tbody>
269
- {table.getRowModel().rows?.length ? (
270
- table.getRowModel().rows.map((row) => (
271
- <tr
272
- key={row.id}
273
- className={cn(
274
- "border-b transition-colors hover:bg-muted/50",
275
- row.getIsSelected() && "bg-muted"
276
- )}
288
+ <tbody className="moonui-data-table-body">
289
+ <AnimatePresence mode="wait">
290
+ {isPaginationLoading ? (
291
+ <motion.tr
292
+ key="loading"
293
+ initial={{ opacity: 0 }}
294
+ animate={{ opacity: 1 }}
295
+ exit={{ opacity: 0 }}
296
+ transition={{ duration: 0.2 }}
277
297
  >
278
- {row.getVisibleCells().map((cell) => (
279
- <td key={cell.id} className="p-4 align-middle">
280
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
281
- </td>
282
- ))}
283
- </tr>
284
- ))
285
- ) : (
286
- <tr>
287
- <td colSpan={columns.length} className="h-24 text-center">
288
- No results found.
289
- </td>
290
- </tr>
291
- )}
298
+ <td colSpan={columns.length} className="h-24 text-center">
299
+ <div className="flex items-center justify-center space-x-2">
300
+ <Loader2 className="h-4 w-4 animate-spin" />
301
+ <span className="text-sm text-muted-foreground">Loading...</span>
302
+ </div>
303
+ </td>
304
+ </motion.tr>
305
+ ) : table.getRowModel().rows?.length ? (
306
+ table.getRowModel().rows.flatMap((row, index) => {
307
+ const rowId = (row.original as any).id || row.id
308
+ const isExpanded = enableExpandable && expandedRows.has(rowId)
309
+
310
+ const mainRow = (
311
+ <motion.tr
312
+ key={row.id}
313
+ initial={{ opacity: 0, y: 20 }}
314
+ animate={{ opacity: 1, y: 0 }}
315
+ exit={{ opacity: 0, y: -20 }}
316
+ transition={{
317
+ duration: 0.3,
318
+ delay: index * 0.05,
319
+ ease: "easeOut"
320
+ }}
321
+ className={cn(
322
+ "border-b transition-colors hover:bg-muted/50",
323
+ row.getIsSelected() && "bg-muted",
324
+ isExpanded && "border-b-0"
325
+ )}
326
+ >
327
+ {row.getVisibleCells().map((cell) => (
328
+ <td key={cell.id} className="moonui-data-table-td p-4 align-middle">
329
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
330
+ </td>
331
+ ))}
332
+ </motion.tr>
333
+ )
334
+
335
+ const expandedRow = isExpanded && renderSubComponent ? (
336
+ <motion.tr
337
+ key={`${row.id}-expanded`}
338
+ initial={{ opacity: 0, height: 0 }}
339
+ animate={{ opacity: 1, height: "auto" }}
340
+ exit={{ opacity: 0, height: 0 }}
341
+ transition={{ duration: 0.3, ease: "easeOut" }}
342
+ className="border-b"
343
+ >
344
+ <td colSpan={columns.length} className="p-0">
345
+ <div className="border-t border-border/50">
346
+ {renderSubComponent({ row: { original: row.original, id: rowId } })}
347
+ </div>
348
+ </td>
349
+ </motion.tr>
350
+ ) : null;
351
+
352
+ return expandedRow ? [mainRow, expandedRow] : [mainRow];
353
+ })
354
+ ) : (
355
+ <motion.tr
356
+ key="no-results"
357
+ initial={{ opacity: 0 }}
358
+ animate={{ opacity: 1 }}
359
+ exit={{ opacity: 0 }}
360
+ transition={{ duration: 0.2 }}
361
+ >
362
+ <td colSpan={columns.length} className="h-24 text-center">
363
+ No results found.
364
+ </td>
365
+ </motion.tr>
366
+ )}
367
+ </AnimatePresence>
292
368
  </tbody>
293
369
  </table>
294
370
  </div>
@@ -309,8 +385,14 @@ export function DataTable<TData, TValue>({
309
385
  <p className="text-sm font-medium">Rows per page</p>
310
386
  <select
311
387
  value={table.getState().pagination.pageSize}
312
- onChange={(e) => table.setPageSize(Number(e.target.value))}
388
+ onChange={async (e) => {
389
+ setIsPaginationLoading(true)
390
+ await new Promise(resolve => setTimeout(resolve, 300))
391
+ table.setPageSize(Number(e.target.value))
392
+ setIsPaginationLoading(false)
393
+ }}
313
394
  className="h-8 w-[70px] rounded border border-input bg-background px-3 py-1 text-sm"
395
+ disabled={isPaginationLoading}
314
396
  >
315
397
  {[10, 20, 30, 40, 50].map((pageSize) => (
316
398
  <option key={pageSize} value={pageSize}>
@@ -327,32 +409,52 @@ export function DataTable<TData, TValue>({
327
409
  <Button
328
410
  variant="outline"
329
411
  className="hidden h-8 w-8 p-0 lg:flex"
330
- onClick={() => table.setPageIndex(0)}
331
- disabled={!table.getCanPreviousPage()}
412
+ onClick={async () => {
413
+ setIsPaginationLoading(true)
414
+ await new Promise(resolve => setTimeout(resolve, 300))
415
+ table.setPageIndex(0)
416
+ setIsPaginationLoading(false)
417
+ }}
418
+ disabled={!table.getCanPreviousPage() || isPaginationLoading}
332
419
  >
333
420
  <ChevronsLeft className="h-4 w-4" />
334
421
  </Button>
335
422
  <Button
336
423
  variant="outline"
337
424
  className="h-8 w-8 p-0"
338
- onClick={() => table.previousPage()}
339
- disabled={!table.getCanPreviousPage()}
425
+ onClick={async () => {
426
+ setIsPaginationLoading(true)
427
+ await new Promise(resolve => setTimeout(resolve, 300))
428
+ table.previousPage()
429
+ setIsPaginationLoading(false)
430
+ }}
431
+ disabled={!table.getCanPreviousPage() || isPaginationLoading}
340
432
  >
341
433
  <ChevronLeft className="h-4 w-4" />
342
434
  </Button>
343
435
  <Button
344
436
  variant="outline"
345
437
  className="h-8 w-8 p-0"
346
- onClick={() => table.nextPage()}
347
- disabled={!table.getCanNextPage()}
438
+ onClick={async () => {
439
+ setIsPaginationLoading(true)
440
+ await new Promise(resolve => setTimeout(resolve, 300))
441
+ table.nextPage()
442
+ setIsPaginationLoading(false)
443
+ }}
444
+ disabled={!table.getCanNextPage() || isPaginationLoading}
348
445
  >
349
446
  <ChevronRight className="h-4 w-4" />
350
447
  </Button>
351
448
  <Button
352
449
  variant="outline"
353
450
  className="hidden h-8 w-8 p-0 lg:flex"
354
- onClick={() => table.setPageIndex(table.getPageCount() - 1)}
355
- disabled={!table.getCanNextPage()}
451
+ onClick={async () => {
452
+ setIsPaginationLoading(true)
453
+ await new Promise(resolve => setTimeout(resolve, 300))
454
+ table.setPageIndex(table.getPageCount() - 1)
455
+ setIsPaginationLoading(false)
456
+ }}
457
+ disabled={!table.getCanNextPage() || isPaginationLoading}
356
458
  >
357
459
  <ChevronsRight className="h-4 w-4" />
358
460
  </Button>
@@ -363,3 +465,81 @@ export function DataTable<TData, TValue>({
363
465
  </div>
364
466
  )
365
467
  }
468
+
469
+ /**
470
+ * Helper function to create an expandable column
471
+ * @param expandedRows - Set of expanded row IDs
472
+ * @param onToggle - Function to toggle row expansion
473
+ * @returns ColumnDef for expandable rows
474
+ */
475
+ export function getExpandableColumn<TData>(
476
+ expandedRows: Set<string>,
477
+ onToggle: (id: string) => void
478
+ ): ColumnDef<TData, any> {
479
+ return {
480
+ id: "expander",
481
+ header: () => null,
482
+ size: 50,
483
+ cell: ({ row }) => {
484
+ const rowId = (row.original as any).id || row.id;
485
+ const isExpanded = expandedRows.has(rowId);
486
+
487
+ return (
488
+ <button
489
+ onClick={(e) => {
490
+ e.stopPropagation();
491
+ onToggle(rowId);
492
+ }}
493
+ className="p-2 hover:bg-muted rounded-md transition-colors"
494
+ aria-label={isExpanded ? "Collapse row" : "Expand row"}
495
+ >
496
+ {isExpanded ? (
497
+ <ChevronDown className="h-4 w-4 text-muted-foreground" />
498
+ ) : (
499
+ <ChevronRight className="h-4 w-4 text-muted-foreground" />
500
+ )}
501
+ </button>
502
+ );
503
+ },
504
+ };
505
+ }
506
+
507
+ /**
508
+ * Hook for managing expandable rows
509
+ * @param initialExpanded - Initial set of expanded row IDs
510
+ * @returns Object with expandedRows, toggleRow, and expandAll/collapseAll functions
511
+ */
512
+ export function useExpandableRows(initialExpanded: Set<string> = new Set()) {
513
+ const [expandedRows, setExpandedRows] = React.useState<Set<string>>(initialExpanded);
514
+
515
+ const toggleRow = React.useCallback((id: string) => {
516
+ setExpandedRows(prev => {
517
+ const newExpanded = new Set(prev);
518
+ if (newExpanded.has(id)) {
519
+ newExpanded.delete(id);
520
+ } else {
521
+ newExpanded.add(id);
522
+ }
523
+ return newExpanded;
524
+ });
525
+ }, []);
526
+
527
+ const expandAll = React.useCallback((rowIds: string[]) => {
528
+ setExpandedRows(new Set(rowIds));
529
+ }, []);
530
+
531
+ const collapseAll = React.useCallback(() => {
532
+ setExpandedRows(new Set());
533
+ }, []);
534
+
535
+ return {
536
+ expandedRows,
537
+ setExpandedRows,
538
+ toggleRow,
539
+ expandAll,
540
+ collapseAll,
541
+ };
542
+ }
543
+
544
+ // Re-export types for convenience
545
+ export { type ColumnDef } from "@tanstack/react-table";