@moontra/moonui-pro 2.4.3 → 2.4.5
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/dist/index.d.ts +22 -4
- package/dist/index.mjs +1046 -235
- package/package.json +1 -2
- package/src/components/advanced-chart/index.tsx +962 -142
- package/src/components/data-table/data-table-column-toggle.tsx +7 -4
- package/src/components/data-table/data-table-filter-drawer.tsx +48 -42
- package/src/components/data-table/index.tsx +104 -38
- package/src/styles/advanced-chart.css +239 -0
- package/src/styles/index.css +2 -1
- package/src/utils/chart-helpers.ts +100 -0
|
@@ -25,7 +25,12 @@ export function DataTableColumnToggle({ table, trigger }: DataTableColumnToggleP
|
|
|
25
25
|
// Get all columns that can be hidden
|
|
26
26
|
const columns = table
|
|
27
27
|
.getAllColumns()
|
|
28
|
-
.filter((column: any) =>
|
|
28
|
+
.filter((column: any) =>
|
|
29
|
+
column.getCanHide() &&
|
|
30
|
+
column.id !== 'select' &&
|
|
31
|
+
column.id !== 'actions' &&
|
|
32
|
+
column.id !== 'expander'
|
|
33
|
+
)
|
|
29
34
|
|
|
30
35
|
// Filter columns based on search
|
|
31
36
|
const filteredColumns = React.useMemo(() => {
|
|
@@ -45,9 +50,7 @@ export function DataTableColumnToggle({ table, trigger }: DataTableColumnToggleP
|
|
|
45
50
|
const visibleCount = columns.filter((col: any) => col.getIsVisible()).length
|
|
46
51
|
|
|
47
52
|
const handleToggleAll = (visible: boolean) => {
|
|
48
|
-
|
|
49
|
-
column.toggleVisibility(visible)
|
|
50
|
-
})
|
|
53
|
+
table.toggleAllColumnsVisible(visible)
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
return (
|
|
@@ -44,6 +44,8 @@ export interface DataTableFilterDrawerProps<TData> {
|
|
|
44
44
|
filters?: FilterCondition[]
|
|
45
45
|
onFiltersChange?: (filters: FilterCondition[]) => void
|
|
46
46
|
customFilters?: React.ReactNode
|
|
47
|
+
matchAll?: boolean
|
|
48
|
+
onMatchAllChange?: (matchAll: boolean) => void
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
const operatorLabels: Record<FilterOperator, string> = {
|
|
@@ -88,12 +90,16 @@ export function DataTableFilterDrawer<TData>({
|
|
|
88
90
|
filters: externalFilters,
|
|
89
91
|
onFiltersChange,
|
|
90
92
|
customFilters,
|
|
93
|
+
matchAll: externalMatchAll,
|
|
94
|
+
onMatchAllChange,
|
|
91
95
|
}: DataTableFilterDrawerProps<TData>) {
|
|
92
96
|
const [internalFilters, setInternalFilters] = useState<FilterCondition[]>([])
|
|
93
|
-
const [
|
|
97
|
+
const [internalMatchAll, setInternalMatchAll] = useState(true)
|
|
94
98
|
|
|
95
99
|
const filters = externalFilters || internalFilters
|
|
96
100
|
const setFilters = onFiltersChange || setInternalFilters
|
|
101
|
+
const matchAll = externalMatchAll !== undefined ? externalMatchAll : internalMatchAll
|
|
102
|
+
const setMatchAll = onMatchAllChange || setInternalMatchAll
|
|
97
103
|
|
|
98
104
|
// Get filterable columns
|
|
99
105
|
const filterableColumns = useMemo(() => {
|
|
@@ -135,64 +141,64 @@ export function DataTableFilterDrawer<TData>({
|
|
|
135
141
|
}
|
|
136
142
|
|
|
137
143
|
const applyFilters = () => {
|
|
138
|
-
//
|
|
139
|
-
|
|
144
|
+
// Create a custom filter function that handles all our conditions
|
|
145
|
+
const customFilterFn = (row: any, columnId: string, filterValue: any) => {
|
|
146
|
+
// Find the filter condition for this column
|
|
147
|
+
const filterCondition = filters.find(f => f.column === columnId)
|
|
148
|
+
if (!filterCondition) return true
|
|
140
149
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const column = table.getColumn(filter.column)
|
|
144
|
-
if (!column) return
|
|
150
|
+
const cellValue = row.getValue(columnId)
|
|
151
|
+
const filterVal = filterCondition.value
|
|
145
152
|
|
|
146
|
-
|
|
147
|
-
switch (filter.operator) {
|
|
153
|
+
switch (filterCondition.operator) {
|
|
148
154
|
case 'equals':
|
|
149
|
-
|
|
150
|
-
break
|
|
155
|
+
return cellValue === filterVal
|
|
151
156
|
case 'notEquals':
|
|
152
|
-
|
|
153
|
-
break
|
|
157
|
+
return cellValue !== filterVal
|
|
154
158
|
case 'contains':
|
|
155
|
-
|
|
156
|
-
String(value).toLowerCase().includes(String(filter.value).toLowerCase())
|
|
157
|
-
)
|
|
158
|
-
break
|
|
159
|
+
return String(cellValue).toLowerCase().includes(String(filterVal).toLowerCase())
|
|
159
160
|
case 'notContains':
|
|
160
|
-
|
|
161
|
-
!String(value).toLowerCase().includes(String(filter.value).toLowerCase())
|
|
162
|
-
)
|
|
163
|
-
break
|
|
161
|
+
return !String(cellValue).toLowerCase().includes(String(filterVal).toLowerCase())
|
|
164
162
|
case 'startsWith':
|
|
165
|
-
|
|
166
|
-
String(value).toLowerCase().startsWith(String(filter.value).toLowerCase())
|
|
167
|
-
)
|
|
168
|
-
break
|
|
163
|
+
return String(cellValue).toLowerCase().startsWith(String(filterVal).toLowerCase())
|
|
169
164
|
case 'endsWith':
|
|
170
|
-
|
|
171
|
-
String(value).toLowerCase().endsWith(String(filter.value).toLowerCase())
|
|
172
|
-
)
|
|
173
|
-
break
|
|
165
|
+
return String(cellValue).toLowerCase().endsWith(String(filterVal).toLowerCase())
|
|
174
166
|
case 'greaterThan':
|
|
175
|
-
|
|
176
|
-
break
|
|
167
|
+
return Number(cellValue) > Number(filterVal)
|
|
177
168
|
case 'lessThan':
|
|
178
|
-
|
|
179
|
-
break
|
|
169
|
+
return Number(cellValue) < Number(filterVal)
|
|
180
170
|
case 'greaterThanOrEqual':
|
|
181
|
-
|
|
182
|
-
break
|
|
171
|
+
return Number(cellValue) >= Number(filterVal)
|
|
183
172
|
case 'lessThanOrEqual':
|
|
184
|
-
|
|
185
|
-
break
|
|
173
|
+
return Number(cellValue) <= Number(filterVal)
|
|
186
174
|
case 'isNull':
|
|
187
|
-
|
|
188
|
-
break
|
|
175
|
+
return cellValue == null || cellValue === ''
|
|
189
176
|
case 'isNotNull':
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
177
|
+
return cellValue != null && cellValue !== ''
|
|
178
|
+
default:
|
|
179
|
+
return true
|
|
193
180
|
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Reset all column filters first
|
|
184
|
+
table.resetColumnFilters()
|
|
185
|
+
|
|
186
|
+
// Apply filter for each column that has a filter condition
|
|
187
|
+
const columnsWithFilters = [...new Set(filters.map(f => f.column))]
|
|
188
|
+
|
|
189
|
+
columnsWithFilters.forEach(columnId => {
|
|
190
|
+
const column = table.getColumn(columnId)
|
|
191
|
+
if (!column) return
|
|
192
|
+
|
|
193
|
+
// Set filter with custom function
|
|
194
|
+
column.setFilterValue({ custom: true, filters, matchAll })
|
|
194
195
|
})
|
|
195
196
|
|
|
197
|
+
// If no filters, ensure all filters are cleared
|
|
198
|
+
if (filters.length === 0) {
|
|
199
|
+
table.resetColumnFilters()
|
|
200
|
+
}
|
|
201
|
+
|
|
196
202
|
onOpenChange(false)
|
|
197
203
|
}
|
|
198
204
|
|
|
@@ -44,7 +44,7 @@ import { motion, AnimatePresence } from 'framer-motion'
|
|
|
44
44
|
import { DataTableColumnToggle } from './data-table-column-toggle'
|
|
45
45
|
import { DataTableBulkActions, type BulkAction } from './data-table-bulk-actions'
|
|
46
46
|
import { exportData, type ExportFormat, getVisibleColumns } from './data-table-export'
|
|
47
|
-
import { DataTableFilterDrawer, type FilterCondition } from './data-table-filter-drawer'
|
|
47
|
+
import { DataTableFilterDrawer, type FilterCondition, type FilterOperator } from './data-table-filter-drawer'
|
|
48
48
|
import {
|
|
49
49
|
DropdownMenu,
|
|
50
50
|
DropdownMenuContent,
|
|
@@ -153,12 +153,17 @@ export function DataTable<TData, TValue>({
|
|
|
153
153
|
onColumnFiltersChange,
|
|
154
154
|
state: externalState,
|
|
155
155
|
}: DataTableProps<TData, TValue>) {
|
|
156
|
-
// Process columns to ensure they can be hidden
|
|
156
|
+
// Process columns to ensure they can be hidden and use custom filter
|
|
157
157
|
const columns = React.useMemo(() => {
|
|
158
|
-
return originalColumns.map(col =>
|
|
159
|
-
|
|
160
|
-
enableHiding
|
|
161
|
-
|
|
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
|
+
})
|
|
162
167
|
}, [originalColumns])
|
|
163
168
|
// Check if we're in docs mode or have pro access
|
|
164
169
|
const { hasProAccess, isLoading } = useSubscription()
|
|
@@ -226,17 +231,65 @@ export function DataTable<TData, TValue>({
|
|
|
226
231
|
onRowSelectionChange: setRowSelection,
|
|
227
232
|
onGlobalFilterChange: setGlobalFilter,
|
|
228
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
|
+
},
|
|
229
284
|
manualPagination,
|
|
230
285
|
pageCount,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
rowSelection,
|
|
239
|
-
globalFilter,
|
|
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 || {}),
|
|
240
293
|
},
|
|
241
294
|
initialState: {
|
|
242
295
|
pagination: {
|
|
@@ -421,11 +474,9 @@ export function DataTable<TData, TValue>({
|
|
|
421
474
|
<thead className="moonui-data-table-header">
|
|
422
475
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
423
476
|
<tr key={headerGroup.id} className="moonui-data-table-row border-b">
|
|
424
|
-
{headerGroup.headers
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
return (
|
|
477
|
+
{headerGroup.headers
|
|
478
|
+
.filter((header) => header.column.getIsVisible())
|
|
479
|
+
.map((header) => (
|
|
429
480
|
<th
|
|
430
481
|
key={header.id}
|
|
431
482
|
className="moonui-data-table-th h-12 px-4 text-left align-middle font-medium text-muted-foreground"
|
|
@@ -453,8 +504,7 @@ export function DataTable<TData, TValue>({
|
|
|
453
504
|
</div>
|
|
454
505
|
)}
|
|
455
506
|
</th>
|
|
456
|
-
)
|
|
457
|
-
})}
|
|
507
|
+
))}
|
|
458
508
|
</tr>
|
|
459
509
|
))}
|
|
460
510
|
</thead>
|
|
@@ -467,7 +517,7 @@ export function DataTable<TData, TValue>({
|
|
|
467
517
|
exit={{ opacity: 0 }}
|
|
468
518
|
transition={{ duration: 0.2 }}
|
|
469
519
|
>
|
|
470
|
-
<td colSpan={table.
|
|
520
|
+
<td colSpan={table.getAllLeafColumns().filter(col => col.getIsVisible()).length} className="h-24 text-center">
|
|
471
521
|
<div className="flex items-center justify-center space-x-2">
|
|
472
522
|
<span suppressHydrationWarning><Loader2 className="h-4 w-4 animate-spin" /></span>
|
|
473
523
|
<span className="text-sm text-muted-foreground">Loading...</span>
|
|
@@ -488,6 +538,7 @@ export function DataTable<TData, TValue>({
|
|
|
488
538
|
isExpanded={isExpanded}
|
|
489
539
|
enableExpandable={enableExpandable}
|
|
490
540
|
renderSubComponent={renderSubComponent}
|
|
541
|
+
visibilityState={table.getState().columnVisibility}
|
|
491
542
|
/>
|
|
492
543
|
);
|
|
493
544
|
})}
|
|
@@ -500,7 +551,7 @@ export function DataTable<TData, TValue>({
|
|
|
500
551
|
exit={{ opacity: 0 }}
|
|
501
552
|
transition={{ duration: 0.2 }}
|
|
502
553
|
>
|
|
503
|
-
<td colSpan={table.
|
|
554
|
+
<td colSpan={table.getAllLeafColumns().filter(col => col.getIsVisible()).length} className="h-24 text-center">
|
|
504
555
|
No results found.
|
|
505
556
|
</td>
|
|
506
557
|
</motion.tr>
|
|
@@ -709,6 +760,7 @@ interface TableRowProps {
|
|
|
709
760
|
isExpanded: boolean
|
|
710
761
|
enableExpandable: boolean
|
|
711
762
|
renderSubComponent?: (props: { row: { original: any; id: string } }) => React.ReactNode
|
|
763
|
+
visibilityState: Record<string, boolean>
|
|
712
764
|
}
|
|
713
765
|
|
|
714
766
|
const TableRow = React.memo(({
|
|
@@ -716,7 +768,8 @@ const TableRow = React.memo(({
|
|
|
716
768
|
columns,
|
|
717
769
|
isExpanded,
|
|
718
770
|
enableExpandable,
|
|
719
|
-
renderSubComponent
|
|
771
|
+
renderSubComponent,
|
|
772
|
+
visibilityState
|
|
720
773
|
}: TableRowProps) => {
|
|
721
774
|
const rowId = (row.original as any).id || row.id
|
|
722
775
|
|
|
@@ -729,21 +782,26 @@ const TableRow = React.memo(({
|
|
|
729
782
|
isExpanded && "border-b-0"
|
|
730
783
|
)}
|
|
731
784
|
>
|
|
732
|
-
{row.
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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
|
+
})}
|
|
742
798
|
</tr>
|
|
743
799
|
|
|
744
800
|
{isExpanded && renderSubComponent && (
|
|
745
801
|
<tr className="border-b">
|
|
746
|
-
<td colSpan={row.
|
|
802
|
+
<td colSpan={row.getAllCells().filter(cell =>
|
|
803
|
+
visibilityState[cell.column.id] !== false
|
|
804
|
+
).length || 1} className="p-0 overflow-hidden">
|
|
747
805
|
<div
|
|
748
806
|
className="transition-all duration-300 ease-out"
|
|
749
807
|
style={{
|
|
@@ -761,13 +819,21 @@ const TableRow = React.memo(({
|
|
|
761
819
|
</>
|
|
762
820
|
)
|
|
763
821
|
}, (prevProps, nextProps) => {
|
|
764
|
-
// Custom comparison - only re-render if row data
|
|
822
|
+
// Custom comparison - only re-render if row data, expanded state, or visibility changed
|
|
765
823
|
const prevRowId = (prevProps.row.original as any).id || prevProps.row.id
|
|
766
824
|
const nextRowId = (nextProps.row.original as any).id || nextProps.row.id
|
|
767
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
|
+
|
|
768
832
|
return prevRowId === nextRowId &&
|
|
769
833
|
prevProps.isExpanded === nextProps.isExpanded &&
|
|
770
|
-
prevProps.row.getIsSelected() === nextProps.row.getIsSelected()
|
|
834
|
+
prevProps.row.getIsSelected() === nextProps.row.getIsSelected() &&
|
|
835
|
+
prevVisibilityKeys === nextVisibilityKeys &&
|
|
836
|
+
prevVisibilityValues === nextVisibilityValues
|
|
771
837
|
})
|
|
772
838
|
|
|
773
839
|
TableRow.displayName = 'TableRow'
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/* Advanced Chart Styles */
|
|
2
|
+
|
|
3
|
+
/* Chart animation classes */
|
|
4
|
+
.chart-animate-in {
|
|
5
|
+
animation: chartFadeIn 0.5s ease-out;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.chart-point-pulse {
|
|
9
|
+
animation: pointPulse 2s ease-in-out infinite;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.chart-gradient-shift {
|
|
13
|
+
animation: gradientShift 3s ease-in-out infinite;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Custom chart tooltips */
|
|
17
|
+
.recharts-tooltip-wrapper {
|
|
18
|
+
outline: none !important;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.recharts-tooltip-cursor {
|
|
22
|
+
fill: hsl(var(--muted) / 0.1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Legend hover effects */
|
|
26
|
+
.recharts-legend-item {
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
transition: all 0.2s ease;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.recharts-legend-item:hover {
|
|
32
|
+
transform: translateY(-1px);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Smooth chart transitions */
|
|
36
|
+
.recharts-line-curve,
|
|
37
|
+
.recharts-area-curve {
|
|
38
|
+
transition: all 0.3s ease;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.recharts-bar-rectangle {
|
|
42
|
+
transition: all 0.2s ease;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.recharts-bar-rectangle:hover {
|
|
46
|
+
filter: brightness(1.1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Pie chart enhancements */
|
|
50
|
+
.recharts-pie-sector {
|
|
51
|
+
transition: all 0.2s ease;
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.recharts-pie-sector:hover {
|
|
56
|
+
filter: brightness(1.1);
|
|
57
|
+
transform: scale(1.02);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Brush styling */
|
|
61
|
+
.recharts-brush {
|
|
62
|
+
fill: hsl(var(--primary) / 0.1);
|
|
63
|
+
stroke: hsl(var(--primary) / 0.3);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.recharts-brush-slide {
|
|
67
|
+
fill: hsl(var(--primary) / 0.2);
|
|
68
|
+
fill-opacity: 0.5;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Grid styling */
|
|
72
|
+
.recharts-cartesian-grid-horizontal line,
|
|
73
|
+
.recharts-cartesian-grid-vertical line {
|
|
74
|
+
stroke-dasharray: 3 3;
|
|
75
|
+
opacity: 0.3;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Axis styling */
|
|
79
|
+
.recharts-xAxis .recharts-cartesian-axis-tick-value,
|
|
80
|
+
.recharts-yAxis .recharts-cartesian-axis-tick-value {
|
|
81
|
+
fill: hsl(var(--muted-foreground));
|
|
82
|
+
font-size: 11px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Dark mode enhancements */
|
|
86
|
+
.dark .recharts-tooltip-wrapper {
|
|
87
|
+
filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.3));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.dark .recharts-cartesian-grid-horizontal line,
|
|
91
|
+
.dark .recharts-cartesian-grid-vertical line {
|
|
92
|
+
opacity: 0.2;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Sparkline mode */
|
|
96
|
+
.sparkline-chart .recharts-surface {
|
|
97
|
+
overflow: visible !important;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Loading skeleton animation */
|
|
101
|
+
@keyframes chartSkeletonPulse {
|
|
102
|
+
0%, 100% {
|
|
103
|
+
opacity: 0.5;
|
|
104
|
+
transform: scaleY(0.8);
|
|
105
|
+
}
|
|
106
|
+
50% {
|
|
107
|
+
opacity: 1;
|
|
108
|
+
transform: scaleY(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Chart animations */
|
|
113
|
+
@keyframes chartFadeIn {
|
|
114
|
+
from {
|
|
115
|
+
opacity: 0;
|
|
116
|
+
transform: translateY(10px);
|
|
117
|
+
}
|
|
118
|
+
to {
|
|
119
|
+
opacity: 1;
|
|
120
|
+
transform: translateY(0);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@keyframes pointPulse {
|
|
125
|
+
0%, 100% {
|
|
126
|
+
transform: scale(1);
|
|
127
|
+
opacity: 1;
|
|
128
|
+
}
|
|
129
|
+
50% {
|
|
130
|
+
transform: scale(1.2);
|
|
131
|
+
opacity: 0.8;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@keyframes gradientShift {
|
|
136
|
+
0%, 100% {
|
|
137
|
+
stop-opacity: 0.8;
|
|
138
|
+
}
|
|
139
|
+
50% {
|
|
140
|
+
stop-opacity: 0.4;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Interactive legend styles */
|
|
145
|
+
.chart-legend-item {
|
|
146
|
+
position: relative;
|
|
147
|
+
overflow: hidden;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.chart-legend-item::before {
|
|
151
|
+
content: '';
|
|
152
|
+
position: absolute;
|
|
153
|
+
top: 0;
|
|
154
|
+
left: -100%;
|
|
155
|
+
width: 100%;
|
|
156
|
+
height: 100%;
|
|
157
|
+
background: linear-gradient(90deg, transparent, hsl(var(--primary) / 0.1), transparent);
|
|
158
|
+
transition: left 0.5s ease;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.chart-legend-item:hover::before {
|
|
162
|
+
left: 100%;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Crosshair cursor */
|
|
166
|
+
.chart-crosshair {
|
|
167
|
+
stroke: hsl(var(--muted-foreground));
|
|
168
|
+
stroke-width: 1;
|
|
169
|
+
stroke-dasharray: 5 5;
|
|
170
|
+
opacity: 0.5;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* Mini map styles */
|
|
174
|
+
.chart-minimap {
|
|
175
|
+
border: 1px solid hsl(var(--border));
|
|
176
|
+
border-radius: var(--radius-md);
|
|
177
|
+
background: hsl(var(--background) / 0.8);
|
|
178
|
+
backdrop-filter: blur(8px);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Export menu animation */
|
|
182
|
+
.chart-export-menu {
|
|
183
|
+
animation: slideDown 0.2s ease-out;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@keyframes slideDown {
|
|
187
|
+
from {
|
|
188
|
+
opacity: 0;
|
|
189
|
+
transform: translateY(-10px);
|
|
190
|
+
}
|
|
191
|
+
to {
|
|
192
|
+
opacity: 1;
|
|
193
|
+
transform: translateY(0);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* Zoom indicator */
|
|
198
|
+
.chart-zoom-indicator {
|
|
199
|
+
background: hsl(var(--background) / 0.9);
|
|
200
|
+
backdrop-filter: blur(8px);
|
|
201
|
+
border: 1px solid hsl(var(--border));
|
|
202
|
+
border-radius: var(--radius-full);
|
|
203
|
+
padding: 0.25rem 0.5rem;
|
|
204
|
+
font-size: 0.75rem;
|
|
205
|
+
font-weight: 500;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Performance optimizations */
|
|
209
|
+
.recharts-surface {
|
|
210
|
+
will-change: transform;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.recharts-line,
|
|
214
|
+
.recharts-area,
|
|
215
|
+
.recharts-bar {
|
|
216
|
+
will-change: opacity, transform;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* Responsive adjustments */
|
|
220
|
+
@media (max-width: 640px) {
|
|
221
|
+
.recharts-wrapper {
|
|
222
|
+
font-size: 0.875rem;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.recharts-legend-wrapper {
|
|
226
|
+
margin-top: 1rem !important;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* Print styles */
|
|
231
|
+
@media print {
|
|
232
|
+
.chart-controls {
|
|
233
|
+
display: none !important;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.recharts-tooltip-wrapper {
|
|
237
|
+
display: none !important;
|
|
238
|
+
}
|
|
239
|
+
}
|
package/src/styles/index.css
CHANGED