@moontra/moonui-pro 2.3.8 → 2.4.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.3.8",
3
+ "version": "2.4.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",
@@ -32,7 +32,7 @@
32
32
  "postinstall": "node scripts/postinstall.js",
33
33
  "build": "tsup",
34
34
  "build:dts": "tsup --dts",
35
- "dev": "tsup --watch",
35
+ "dev": "tsup --watch --sourcemap",
36
36
  "clean": "rm -rf dist",
37
37
  "lint": "eslint \"src/**/*.{ts,tsx}\"",
38
38
  "test": "jest",
@@ -75,7 +75,8 @@
75
75
  "react-dom": ">=18.0.0 || ^19.0.0"
76
76
  },
77
77
  "dependencies": {
78
- "@moontra/moonui-pro": "^2.3.4",
78
+ "@moontra/moonui": "^2.1.9",
79
+ "@moontra/moonui-pro": "^2.3.8",
79
80
  "@radix-ui/react-accordion": "^1.2.11",
80
81
  "@radix-ui/react-avatar": "^1.1.10",
81
82
  "@radix-ui/react-checkbox": "^1.3.2",
@@ -171,6 +171,32 @@ const main = async () => {
171
171
  log('🌙 MoonUI Pro Installation', colors.cyan + colors.bright);
172
172
  log('═'.repeat(50), colors.gray);
173
173
 
174
+ // Check for development bypass
175
+ if (process.env.MOONUI_DEV_MODE === 'true' || process.env.MOONUI_SKIP_AUTH === 'true') {
176
+ log('🔧 Development Mode Enabled', colors.yellow + colors.bright);
177
+ log(' Authentication bypassed for local development', colors.yellow);
178
+ log(' This mode should only be used during development', colors.gray);
179
+ log('', '');
180
+ log('✅ MoonUI Pro ready for development!', colors.green);
181
+ log('═'.repeat(50), colors.gray);
182
+ return;
183
+ }
184
+
185
+ // Check if running on localhost (development)
186
+ const isLocalDev = process.env.NODE_ENV === 'development' ||
187
+ process.cwd().includes('moonui') ||
188
+ fs.existsSync(path.join(process.cwd(), '.moonui-dev'));
189
+
190
+ if (isLocalDev && !process.env.MOONUI_FORCE_AUTH) {
191
+ log('🚀 Local Development Detected', colors.blue + colors.bright);
192
+ log(' Running in development mode without auth', colors.blue);
193
+ log(' To enable auth checks: MOONUI_FORCE_AUTH=true npm install', colors.gray);
194
+ log('', '');
195
+ log('✅ MoonUI Pro ready for development!', colors.green);
196
+ log('═'.repeat(50), colors.gray);
197
+ return;
198
+ }
199
+
174
200
  // Check if this is a CI environment
175
201
  const isCI = process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.GITHUB_ACTIONS;
176
202
 
@@ -0,0 +1,204 @@
1
+ "use client"
2
+
3
+ import React from 'react'
4
+ import { Loader2, MoreHorizontal, AlertTriangle } from 'lucide-react'
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuSeparator,
10
+ DropdownMenuTrigger,
11
+ } from '../ui/dropdown-menu'
12
+ import {
13
+ AlertDialog,
14
+ AlertDialogAction,
15
+ AlertDialogCancel,
16
+ AlertDialogContent,
17
+ AlertDialogDescription,
18
+ AlertDialogFooter,
19
+ AlertDialogHeader,
20
+ AlertDialogTitle,
21
+ } from '../ui/alert-dialog'
22
+ import { Button } from '../ui/button'
23
+ import { Badge } from '../ui/badge'
24
+ import { cn } from '../../lib/utils'
25
+
26
+ export interface BulkAction<T = any> {
27
+ label: string
28
+ icon?: React.ReactNode
29
+ action: (selectedRows: T[]) => void | Promise<void>
30
+ confirmMessage?: string
31
+ confirmTitle?: string
32
+ variant?: 'default' | 'destructive'
33
+ disabled?: boolean | ((selectedRows: T[]) => boolean)
34
+ }
35
+
36
+ interface DataTableBulkActionsProps<T = any> {
37
+ selectedRows: T[]
38
+ actions: BulkAction<T>[]
39
+ onClearSelection?: () => void
40
+ className?: string
41
+ }
42
+
43
+ export function DataTableBulkActions<T>({
44
+ selectedRows,
45
+ actions,
46
+ onClearSelection,
47
+ className
48
+ }: DataTableBulkActionsProps<T>) {
49
+ const [isLoading, setIsLoading] = React.useState(false)
50
+ const [pendingAction, setPendingAction] = React.useState<BulkAction<T> | null>(null)
51
+
52
+ const selectedCount = selectedRows.length
53
+
54
+ const handleAction = async (action: BulkAction<T>) => {
55
+ if (action.confirmMessage) {
56
+ setPendingAction(action)
57
+ return
58
+ }
59
+
60
+ await executeAction(action)
61
+ }
62
+
63
+ const executeAction = async (action: BulkAction<T>) => {
64
+ setIsLoading(true)
65
+ try {
66
+ await action.action(selectedRows)
67
+
68
+ // Clear selection after successful action
69
+ if (onClearSelection) {
70
+ onClearSelection()
71
+ }
72
+ } catch (error) {
73
+ console.error('Bulk action failed:', error)
74
+ // You might want to show an error toast here
75
+ } finally {
76
+ setIsLoading(false)
77
+ setPendingAction(null)
78
+ }
79
+ }
80
+
81
+ const handleConfirm = async () => {
82
+ if (pendingAction) {
83
+ await executeAction(pendingAction)
84
+ }
85
+ }
86
+
87
+ if (selectedCount === 0) {
88
+ return null
89
+ }
90
+
91
+ return (
92
+ <>
93
+ <div className={cn("flex items-center gap-2", className)}>
94
+ {/* Selected count badge */}
95
+ <Badge variant="secondary" className="gap-1">
96
+ <span className="font-semibold">{selectedCount}</span>
97
+ <span>selected</span>
98
+ </Badge>
99
+
100
+ {/* Bulk actions dropdown */}
101
+ <DropdownMenu>
102
+ <DropdownMenuTrigger asChild>
103
+ <Button
104
+ variant="outline"
105
+ size="sm"
106
+ disabled={isLoading}
107
+ className="gap-2"
108
+ >
109
+ {isLoading ? (
110
+ <>
111
+ <Loader2 className="h-4 w-4 animate-spin" />
112
+ Processing...
113
+ </>
114
+ ) : (
115
+ <>
116
+ <MoreHorizontal className="h-4 w-4" />
117
+ Bulk Actions
118
+ </>
119
+ )}
120
+ </Button>
121
+ </DropdownMenuTrigger>
122
+ <DropdownMenuContent align="end" className="w-48">
123
+ {actions.map((action, index) => {
124
+ const isDisabled = typeof action.disabled === 'function'
125
+ ? action.disabled(selectedRows)
126
+ : action.disabled
127
+
128
+ return (
129
+ <DropdownMenuItem
130
+ key={index}
131
+ disabled={isDisabled || isLoading}
132
+ onSelect={() => handleAction(action)}
133
+ className={cn(
134
+ "cursor-pointer",
135
+ action.variant === 'destructive' && "text-destructive focus:text-destructive"
136
+ )}
137
+ >
138
+ {action.icon && (
139
+ <span className="mr-2 h-4 w-4">{action.icon}</span>
140
+ )}
141
+ {action.label}
142
+ </DropdownMenuItem>
143
+ )
144
+ })}
145
+
146
+ {onClearSelection && (
147
+ <>
148
+ <DropdownMenuSeparator />
149
+ <DropdownMenuItem
150
+ onSelect={onClearSelection}
151
+ className="cursor-pointer text-muted-foreground"
152
+ >
153
+ Clear selection
154
+ </DropdownMenuItem>
155
+ </>
156
+ )}
157
+ </DropdownMenuContent>
158
+ </DropdownMenu>
159
+ </div>
160
+
161
+ {/* Confirmation dialog */}
162
+ <AlertDialog
163
+ open={!!pendingAction}
164
+ onOpenChange={(open) => !open && setPendingAction(null)}
165
+ >
166
+ <AlertDialogContent>
167
+ <AlertDialogHeader>
168
+ <AlertDialogTitle className="flex items-center gap-2">
169
+ {pendingAction?.variant === 'destructive' && (
170
+ <AlertTriangle className="h-5 w-5 text-destructive" />
171
+ )}
172
+ {pendingAction?.confirmTitle || 'Confirm Action'}
173
+ </AlertDialogTitle>
174
+ <AlertDialogDescription>
175
+ {pendingAction?.confirmMessage ||
176
+ `This action will affect ${selectedCount} selected item${selectedCount > 1 ? 's' : ''}. This action cannot be undone.`}
177
+ </AlertDialogDescription>
178
+ </AlertDialogHeader>
179
+ <AlertDialogFooter>
180
+ <AlertDialogCancel disabled={isLoading}>
181
+ Cancel
182
+ </AlertDialogCancel>
183
+ <AlertDialogAction
184
+ onClick={handleConfirm}
185
+ disabled={isLoading}
186
+ className={cn(
187
+ pendingAction?.variant === 'destructive' && "bg-destructive text-destructive-foreground hover:bg-destructive/90"
188
+ )}
189
+ >
190
+ {isLoading ? (
191
+ <>
192
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
193
+ Processing...
194
+ </>
195
+ ) : (
196
+ 'Confirm'
197
+ )}
198
+ </AlertDialogAction>
199
+ </AlertDialogFooter>
200
+ </AlertDialogContent>
201
+ </AlertDialog>
202
+ </>
203
+ )
204
+ }
@@ -0,0 +1,166 @@
1
+ "use client"
2
+
3
+ import React from 'react'
4
+ import { Check, Columns, Search } from 'lucide-react'
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuLabel,
10
+ DropdownMenuSeparator,
11
+ DropdownMenuTrigger,
12
+ } from '../ui/dropdown-menu'
13
+ import { Button } from '../ui/button'
14
+ import { Input } from '../ui/input'
15
+ import { cn } from '../../lib/utils'
16
+
17
+ interface DataTableColumnToggleProps {
18
+ table: any // Table instance from @tanstack/react-table
19
+ trigger?: React.ReactNode
20
+ }
21
+
22
+ export function DataTableColumnToggle({ table, trigger }: DataTableColumnToggleProps) {
23
+ const [search, setSearch] = React.useState('')
24
+
25
+ // Get all columns that can be hidden
26
+ const columns = table
27
+ .getAllColumns()
28
+ .filter((column: any) => column.getCanHide())
29
+
30
+ // Filter columns based on search
31
+ const filteredColumns = React.useMemo(() => {
32
+ if (!search) return columns
33
+
34
+ return columns.filter((column: any) => {
35
+ const header = column.columnDef.header
36
+ const headerText = typeof header === 'string'
37
+ ? header
38
+ : column.id
39
+
40
+ return headerText.toLowerCase().includes(search.toLowerCase())
41
+ })
42
+ }, [columns, search])
43
+
44
+ // Count visible columns
45
+ const visibleCount = columns.filter((col: any) => col.getIsVisible()).length
46
+
47
+ const handleToggleAll = (visible: boolean) => {
48
+ columns.forEach((column: any) => {
49
+ column.toggleVisibility(visible)
50
+ })
51
+ }
52
+
53
+ return (
54
+ <DropdownMenu>
55
+ <DropdownMenuTrigger asChild>
56
+ {trigger || (
57
+ <Button variant="outline" size="sm" className="gap-2">
58
+ <Columns className="h-4 w-4" />
59
+ Columns
60
+ {visibleCount < columns.length && (
61
+ <span className="ml-1 rounded-full bg-primary/10 px-1.5 py-0.5 text-xs">
62
+ {visibleCount}/{columns.length}
63
+ </span>
64
+ )}
65
+ </Button>
66
+ )}
67
+ </DropdownMenuTrigger>
68
+ <DropdownMenuContent align="end" className="w-56">
69
+ <DropdownMenuLabel>Toggle Columns</DropdownMenuLabel>
70
+
71
+ {/* Search input for many columns */}
72
+ {columns.length > 5 && (
73
+ <>
74
+ <div className="px-2 py-1.5">
75
+ <div className="relative">
76
+ <Search className="absolute left-2 top-2.5 h-3 w-3 text-muted-foreground" />
77
+ <Input
78
+ placeholder="Search columns..."
79
+ value={search}
80
+ onChange={(e) => setSearch(e.target.value)}
81
+ className="h-8 pl-7 text-xs"
82
+ />
83
+ </div>
84
+ </div>
85
+ <DropdownMenuSeparator />
86
+ </>
87
+ )}
88
+
89
+ {/* Quick actions */}
90
+ <div className="flex items-center justify-between px-2 py-1.5">
91
+ <Button
92
+ variant="ghost"
93
+ size="sm"
94
+ className="h-7 text-xs"
95
+ onClick={() => handleToggleAll(true)}
96
+ >
97
+ Show all
98
+ </Button>
99
+ <Button
100
+ variant="ghost"
101
+ size="sm"
102
+ className="h-7 text-xs"
103
+ onClick={() => handleToggleAll(false)}
104
+ >
105
+ Hide all
106
+ </Button>
107
+ </div>
108
+
109
+ <DropdownMenuSeparator />
110
+
111
+ {/* Column list */}
112
+ <div className="max-h-64 overflow-y-auto">
113
+ {filteredColumns.length === 0 ? (
114
+ <div className="px-2 py-4 text-center text-xs text-muted-foreground">
115
+ No columns found
116
+ </div>
117
+ ) : (
118
+ filteredColumns.map((column: any) => {
119
+ const isVisible = column.getIsVisible()
120
+ const header = column.columnDef.header
121
+ const headerText = typeof header === 'string'
122
+ ? header
123
+ : column.id
124
+
125
+ return (
126
+ <DropdownMenuItem
127
+ key={column.id}
128
+ className={cn(
129
+ "cursor-pointer",
130
+ !isVisible && "text-muted-foreground"
131
+ )}
132
+ onSelect={(e) => {
133
+ e.preventDefault()
134
+ column.toggleVisibility()
135
+ }}
136
+ >
137
+ <div className="flex items-center gap-2 flex-1">
138
+ <div className={cn(
139
+ "flex h-4 w-4 items-center justify-center rounded-sm border",
140
+ isVisible
141
+ ? "border-primary bg-primary text-primary-foreground"
142
+ : "border-muted-foreground"
143
+ )}>
144
+ {isVisible && <Check className="h-3 w-3" />}
145
+ </div>
146
+ <span className="truncate">{headerText}</span>
147
+ </div>
148
+ </DropdownMenuItem>
149
+ )
150
+ })
151
+ )}
152
+ </div>
153
+
154
+ {/* Summary */}
155
+ {columns.length > 0 && (
156
+ <>
157
+ <DropdownMenuSeparator />
158
+ <div className="px-2 py-1.5 text-xs text-muted-foreground text-center">
159
+ {visibleCount} of {columns.length} columns visible
160
+ </div>
161
+ </>
162
+ )}
163
+ </DropdownMenuContent>
164
+ </DropdownMenu>
165
+ )
166
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Export utilities for DataTable component
3
+ */
4
+
5
+ export type ExportFormat = 'csv' | 'json' | 'xlsx';
6
+
7
+ export interface ExportOptions {
8
+ filename?: string;
9
+ format: ExportFormat;
10
+ columns?: string[];
11
+ includeHeaders?: boolean;
12
+ }
13
+
14
+ /**
15
+ * Convert data to CSV format
16
+ */
17
+ export function dataToCSV<T extends Record<string, any>>(
18
+ data: T[],
19
+ columns?: string[],
20
+ includeHeaders = true
21
+ ): string {
22
+ if (data.length === 0) return '';
23
+
24
+ // Get columns from first item if not provided
25
+ const cols = columns || Object.keys(data[0]);
26
+
27
+ // Build CSV content
28
+ const rows: string[] = [];
29
+
30
+ // Add headers
31
+ if (includeHeaders) {
32
+ const headers = cols.map(col => {
33
+ // Handle column names with special characters
34
+ const value = String(col);
35
+ return value.includes(',') || value.includes('"') || value.includes('\n')
36
+ ? `"${value.replace(/"/g, '""')}"`
37
+ : value;
38
+ });
39
+ rows.push(headers.join(','));
40
+ }
41
+
42
+ // Add data rows
43
+ data.forEach(item => {
44
+ const row = cols.map(col => {
45
+ const value = item[col];
46
+
47
+ // Handle different value types
48
+ if (value === null || value === undefined) return '';
49
+ if (value instanceof Date) return value.toISOString();
50
+ if (typeof value === 'object') return JSON.stringify(value);
51
+
52
+ const stringValue = String(value);
53
+ return stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')
54
+ ? `"${stringValue.replace(/"/g, '""')}"`
55
+ : stringValue;
56
+ });
57
+ rows.push(row.join(','));
58
+ });
59
+
60
+ return rows.join('\n');
61
+ }
62
+
63
+ /**
64
+ * Convert data to JSON format
65
+ */
66
+ export function dataToJSON<T>(data: T[], columns?: string[]): string {
67
+ if (!columns || columns.length === 0) {
68
+ return JSON.stringify(data, null, 2);
69
+ }
70
+
71
+ // Filter data to include only specified columns
72
+ const filteredData = data.map(item => {
73
+ const filtered: Record<string, any> = {};
74
+ columns.forEach(col => {
75
+ if (col in (item as any)) {
76
+ filtered[col] = (item as any)[col];
77
+ }
78
+ });
79
+ return filtered;
80
+ });
81
+
82
+ return JSON.stringify(filteredData, null, 2);
83
+ }
84
+
85
+ /**
86
+ * Download data as a file
87
+ */
88
+ export function downloadFile(content: string, filename: string, mimeType: string): void {
89
+ const blob = new Blob([content], { type: mimeType });
90
+ const url = URL.createObjectURL(blob);
91
+ const link = document.createElement('a');
92
+
93
+ link.href = url;
94
+ link.download = filename;
95
+ document.body.appendChild(link);
96
+ link.click();
97
+
98
+ // Cleanup
99
+ document.body.removeChild(link);
100
+ URL.revokeObjectURL(url);
101
+ }
102
+
103
+ /**
104
+ * Export data to the specified format
105
+ */
106
+ export async function exportData<T extends Record<string, any>>(
107
+ data: T[],
108
+ options: ExportOptions
109
+ ): Promise<void> {
110
+ const { format, filename = 'data-export', columns, includeHeaders = true } = options;
111
+
112
+ let content: string;
113
+ let mimeType: string;
114
+ let extension: string;
115
+
116
+ switch (format) {
117
+ case 'csv':
118
+ content = dataToCSV(data, columns, includeHeaders);
119
+ mimeType = 'text/csv;charset=utf-8;';
120
+ extension = 'csv';
121
+ break;
122
+
123
+ case 'json':
124
+ content = dataToJSON(data, columns);
125
+ mimeType = 'application/json;charset=utf-8;';
126
+ extension = 'json';
127
+ break;
128
+
129
+ case 'xlsx':
130
+ // For XLSX, we'll need to use a library like xlsx or exceljs
131
+ // For now, we'll throw an error indicating it's not implemented
132
+ throw new Error('XLSX export requires additional dependencies. Use CSV format instead.');
133
+
134
+ default:
135
+ throw new Error(`Unsupported export format: ${format}`);
136
+ }
137
+
138
+ const finalFilename = `${filename}-${new Date().toISOString().split('T')[0]}.${extension}`;
139
+ downloadFile(content, finalFilename, mimeType);
140
+ }
141
+
142
+ /**
143
+ * Get visible columns from column definitions
144
+ */
145
+ export function getVisibleColumns<T>(
146
+ columns: Array<{ id?: string; accessorKey?: string; header?: any }>,
147
+ columnVisibility: Record<string, boolean>
148
+ ): string[] {
149
+ return columns
150
+ .filter(col => {
151
+ const key = col.id || col.accessorKey;
152
+ return key && columnVisibility[key] !== false;
153
+ })
154
+ .map(col => col.id || col.accessorKey!)
155
+ .filter(Boolean);
156
+ }