react-restyle-components 0.4.34 → 0.4.36
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/lib/src/core-components/src/components/Table/Table.js +247 -79
- package/lib/src/core-components/src/components/Table/filters.js +130 -2
- package/lib/src/core-components/src/components/Table/hooks.d.ts +16 -0
- package/lib/src/core-components/src/components/Table/hooks.js +87 -2
- package/lib/src/core-components/src/components/Table/index.d.ts +1 -1
- package/lib/src/core-components/src/components/Table/index.js +1 -1
- package/lib/src/core-components/src/components/Table/types.d.ts +12 -5
- package/lib/src/core-components/src/tc.global.css +15 -0
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React, { forwardRef, useState, useCallback, useMemo, useEffect, useRef, } from 'react';
|
|
4
4
|
import { TableRoot, Toolbar, ToolbarGroup, SearchInput, ToolbarButton, TableWrapper, StyledTable, TableHeader, HeaderRow, HeaderCell, TableBody, TableRow, TableCell, Checkbox, ExpandButton, ExpandedRow, ExpandedCell, TableFooter, FooterRow, FooterCell, PaginationWrapper, PaginationInfo, PaginationControls, PageButton, PageSizeSelect, QuickJumper, EmptyState, LoadingOverlay, LoadingSpinner, EditableCell, CellEditor, ColumnTogglePanel, ColumnToggleHeader, ColumnToggleSearch, ColumnToggleList, ColumnToggleItem, SelectionIndicator, SelectionCount, } from './elements';
|
|
5
|
-
import { useSortState, useFilterState, usePaginationState, useRowSelection, useRowExpansion, useColumnVisibility, useTableDebounce, sortData, filterData, paginateData, getNestedValue, exportToCSV, } from './hooks';
|
|
5
|
+
import { useSortState, useFilterState, usePaginationState, useRowSelection, useRowExpansion, useColumnVisibility, useTableDebounce, sortData, filterData, paginateData, getNestedValue, exportToCSV, exportToExcel, } from './hooks';
|
|
6
6
|
import { getFilterComponent } from './filters';
|
|
7
7
|
import { Tooltip } from '../Tooltip';
|
|
8
8
|
// Icons
|
|
@@ -50,9 +50,9 @@ const RefreshIcon = () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", st
|
|
|
50
50
|
const PrintIcon = () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("path", { d: "M6 9V2h12v7M6 18H4a2 2 0 01-2-2v-5a2 2 0 012-2h16a2 2 0 012 2v5a2 2 0 01-2 2h-2", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("rect", { x: "6", y: "14", width: "12", height: "8" })] }));
|
|
51
51
|
const ErrorIcon = () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("circle", { cx: "12", cy: "12", r: "10" }), _jsx("path", { d: "M12 8v4M12 16h.01", strokeLinecap: "round", strokeLinejoin: "round" })] }));
|
|
52
52
|
export const Table = forwardRef(function TableComponent(props, ref) {
|
|
53
|
-
const { id, data, columns, keyField = '_id', loading = false, loadingIndicator, pagination = true, paginationConfig, totalSize, remote = true, defaultSort, sort: controlledSort, filterable = false, defaultFilters, filters: controlledFilters, defaultShowFilters = true, showFilters: controlledShowFilters, onShowFiltersChange, showFilterToggle = true, searchable = true, searchPlaceholder = 'Search...', defaultSearchValue = '', searchValue: controlledSearchValue, searchDebounce = 300, rowSelection, expandable, editMode = 'dblclick', showEditIcon = false, onCellEdit, exportable = true, exportFileName = 'table_export', columnToggle = false, bordered = true, striped = false, hover = true, compact = false, cellPadding, stickyHeader = false, maxHeight, rowClassName, rowStyle, classNames = {}, styles = {}, className, style, emptyText = 'No data available', onChange, onPageChange, onSortChange, onFilterChange, onSearch, onRowClick, onRowDoubleClick, onClearFilters, toolbar, hideToolbar = false, showFooter = false, caption,
|
|
53
|
+
const { id, data, columns, keyField = '_id', loading = false, loadingIndicator, pagination = true, paginationConfig, totalSize, remote = true, defaultSort, sort: controlledSort, filterable = false, defaultFilters, filters: controlledFilters, defaultShowFilters = true, showFilters: controlledShowFilters, onShowFiltersChange, showFilterToggle = true, searchable = true, searchPlaceholder = 'Search...', defaultSearchValue = '', searchValue: controlledSearchValue, searchDebounce = 300, rowSelection, expandable, editMode = 'dblclick', showEditIcon = false, onCellEdit, exportable = true, exportFileName = 'table_export', exportFormat = 'csv', columnToggle = false, bordered = true, striped = false, hover = true, compact = false, cellPadding, stickyHeader = false, maxHeight, rowClassName, rowStyle, classNames = {}, styles = {}, className, style, emptyText = 'No data available', onChange, onPageChange, onSortChange, onFilterChange, onSearch, onRowClick, onRowDoubleClick, onClearFilters, toolbar, hideToolbar = false, showFooter = false, caption,
|
|
54
54
|
// Quick configuration props
|
|
55
|
-
isDelete = false, isEditModify, isUpdate, isExport, isSelectRow, isView = false, fileName, hideExcelSheet = false,
|
|
55
|
+
isDelete = false, isEditModify, isUpdate, isExport, isSelectRow, getNonSelectableRows, nonSelectableStyle, isView = false, fileName, hideExcelSheet = false,
|
|
56
56
|
// Quick callbacks
|
|
57
57
|
onSelectedRow, selectedRowStyle, selectedRowClassName, onUpdateItem, onPageSizeChange, onFilter, clearAllFilter, onDelete, onEdit, onView,
|
|
58
58
|
// Dynamic configuration
|
|
@@ -80,6 +80,17 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
80
80
|
// Resolve aliases
|
|
81
81
|
const resolvedExportable = isExport ?? exportable;
|
|
82
82
|
const resolvedExportFileName = fileName ?? exportFileName;
|
|
83
|
+
// Auto-detect remote mode: if totalSize <= data.length, use client-side (remote=false)
|
|
84
|
+
const resolvedRemote = useMemo(() => {
|
|
85
|
+
// If totalSize is provided and is <= data.length, all data is loaded - use client-side
|
|
86
|
+
if (totalSize !== undefined &&
|
|
87
|
+
totalSize > 0 &&
|
|
88
|
+
totalSize <= data.length) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
// Otherwise use the prop value (default: true)
|
|
92
|
+
return remote;
|
|
93
|
+
}, [remote, totalSize, data.length]);
|
|
83
94
|
// Handle isSelectRow shorthand
|
|
84
95
|
const resolvedRowSelection = isSelectRow
|
|
85
96
|
? {
|
|
@@ -87,6 +98,16 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
87
98
|
selectedRowStyle: selectedRowStyle || rowSelection?.selectedRowStyle,
|
|
88
99
|
selectedRowClassName: selectedRowClassName || rowSelection?.selectedRowClassName,
|
|
89
100
|
...rowSelection,
|
|
101
|
+
// Merge getNonSelectableRows with existing getCheckboxProps
|
|
102
|
+
getCheckboxProps: (row) => {
|
|
103
|
+
const rowKey = row[keyField];
|
|
104
|
+
const isNonSelectable = getNonSelectableRows?.includes(rowKey);
|
|
105
|
+
const existingProps = rowSelection?.getCheckboxProps?.(row);
|
|
106
|
+
return {
|
|
107
|
+
...existingProps,
|
|
108
|
+
disabled: isNonSelectable || existingProps?.disabled,
|
|
109
|
+
};
|
|
110
|
+
},
|
|
90
111
|
onChange: (keys, rows) => {
|
|
91
112
|
rowSelection?.onChange?.(keys, rows);
|
|
92
113
|
// Note: onSelectedRow is only called when user clicks the "X Selected" button
|
|
@@ -156,12 +177,56 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
156
177
|
const { sort, handleSort } = useSortState(defaultSort, controlledSort);
|
|
157
178
|
// Filter state
|
|
158
179
|
const { filters, setFilter, clearFilters } = useFilterState(defaultFilters, controlledFilters);
|
|
159
|
-
// Debounced filters for onFilter callback
|
|
160
|
-
const debouncedFilters = useTableDebounce(filters, 500);
|
|
161
180
|
// Track if onFilter should be called (only after user interaction)
|
|
162
181
|
const shouldCallOnFilter = useRef(false);
|
|
163
182
|
const filterTypeRef = useRef('filter');
|
|
164
183
|
const onFilterRef = useRef(onFilter);
|
|
184
|
+
// Track last called values to prevent duplicate calls
|
|
185
|
+
const lastOnFilterCallRef = useRef(null);
|
|
186
|
+
// Track focused filter field to restore focus after data changes
|
|
187
|
+
const focusedFilterFieldRef = useRef(null);
|
|
188
|
+
const tableContainerRef = useRef(null);
|
|
189
|
+
// Clear focus ref when clicking outside the table
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
const handleClickOutside = (event) => {
|
|
192
|
+
if (tableContainerRef.current &&
|
|
193
|
+
!tableContainerRef.current.contains(event.target)) {
|
|
194
|
+
focusedFilterFieldRef.current = null;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
198
|
+
return () => {
|
|
199
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
200
|
+
};
|
|
201
|
+
}, []);
|
|
202
|
+
// Restore filter focus after data changes
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
const restoreFocus = () => {
|
|
205
|
+
if (focusedFilterFieldRef.current && tableContainerRef.current) {
|
|
206
|
+
const filterInput = tableContainerRef.current.querySelector(`[data-filter-field="${focusedFilterFieldRef.current}"]`);
|
|
207
|
+
if (filterInput && document.activeElement !== filterInput) {
|
|
208
|
+
filterInput.focus();
|
|
209
|
+
// Move cursor to end of input
|
|
210
|
+
const len = filterInput.value?.length || 0;
|
|
211
|
+
filterInput.setSelectionRange(len, len);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
// Try multiple times with increasing delays to handle async renders
|
|
216
|
+
const timers = [
|
|
217
|
+
requestAnimationFrame(() => restoreFocus()),
|
|
218
|
+
setTimeout(() => restoreFocus(), 50),
|
|
219
|
+
setTimeout(() => restoreFocus(), 150),
|
|
220
|
+
];
|
|
221
|
+
return () => {
|
|
222
|
+
timers.forEach((timer) => {
|
|
223
|
+
if (typeof timer === 'number') {
|
|
224
|
+
cancelAnimationFrame(timer);
|
|
225
|
+
clearTimeout(timer);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
}, [data]);
|
|
165
230
|
// Pagination state
|
|
166
231
|
const { page, pageSize, totalPages, goToPage, goToNextPage, goToPrevPage, goToFirstPage, goToLastPage, changePageSize, } = usePaginationState(resolvedPaginationConfig?.page || 0, resolvedPaginationConfig?.pageSize || 10, totalSize ?? data.length);
|
|
167
232
|
// Row selection
|
|
@@ -174,23 +239,48 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
174
239
|
useEffect(() => {
|
|
175
240
|
onFilterRef.current = onFilter;
|
|
176
241
|
}, [onFilter]);
|
|
177
|
-
//
|
|
242
|
+
// Track previous values to detect actual changes
|
|
243
|
+
const prevSearchRef = useRef(debouncedSearchValue);
|
|
244
|
+
const prevFiltersRef = useRef(JSON.stringify(filters));
|
|
245
|
+
// Call onFilter when filter/search values actually change
|
|
246
|
+
// Note: filter components already debounce internally, so we use filters directly
|
|
178
247
|
useEffect(() => {
|
|
179
248
|
if (!shouldCallOnFilter.current)
|
|
180
249
|
return;
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
250
|
+
const currentFiltersStr = JSON.stringify(filters);
|
|
251
|
+
const searchChanged = prevSearchRef.current !== debouncedSearchValue;
|
|
252
|
+
const filtersChanged = prevFiltersRef.current !== currentFiltersStr;
|
|
253
|
+
// Only proceed if search or filters actually changed
|
|
254
|
+
if (!searchChanged && !filtersChanged) {
|
|
255
|
+
shouldCallOnFilter.current = false;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
// Update previous values
|
|
259
|
+
prevSearchRef.current = debouncedSearchValue;
|
|
260
|
+
prevFiltersRef.current = currentFiltersStr;
|
|
261
|
+
// Clean up filters - remove null, undefined, and empty string values
|
|
262
|
+
const cleanFilters = {};
|
|
263
|
+
Object.keys(filters).forEach((key) => {
|
|
264
|
+
const value = filters[key];
|
|
265
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
266
|
+
cleanFilters[key] = value;
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
const hasFilters = Object.keys(cleanFilters).length > 0;
|
|
270
|
+
// Build filter data based on what type of filter changed
|
|
271
|
+
// For 'search' type, include srText if not empty; for 'filter' type, only pass filters
|
|
272
|
+
const filterData = { ...cleanFilters };
|
|
273
|
+
if (filterTypeRef.current === 'search' && debouncedSearchValue) {
|
|
274
|
+
filterData.srText = debouncedSearchValue;
|
|
190
275
|
}
|
|
191
|
-
//
|
|
276
|
+
// Always call onFilter when filters change (including when cleared)
|
|
277
|
+
// Case 1: Global search cleared, column filters exist → filters only
|
|
278
|
+
// Case 2: Global search cleared, no column filters → empty filters (reload all)
|
|
279
|
+
// Case 3: Specific column cleared → remaining filters
|
|
280
|
+
onFilterRef.current?.(filterTypeRef.current, filterData, page, pageSize);
|
|
281
|
+
// Reset the flag after processing to prevent duplicate calls
|
|
192
282
|
shouldCallOnFilter.current = false;
|
|
193
|
-
}, [debouncedSearchValue,
|
|
283
|
+
}, [debouncedSearchValue, filters, page, pageSize]);
|
|
194
284
|
// Track selection count changes for animation
|
|
195
285
|
useEffect(() => {
|
|
196
286
|
const currentCount = selectedKeys.size;
|
|
@@ -210,7 +300,7 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
210
300
|
}, [selectedKeys.size]);
|
|
211
301
|
// Process data (filter, sort, paginate)
|
|
212
302
|
const processedData = useMemo(() => {
|
|
213
|
-
if (
|
|
303
|
+
if (resolvedRemote) {
|
|
214
304
|
// Server-side processing - data is already processed
|
|
215
305
|
return data;
|
|
216
306
|
}
|
|
@@ -231,7 +321,7 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
231
321
|
page,
|
|
232
322
|
pageSize,
|
|
233
323
|
debouncedSearchValue,
|
|
234
|
-
|
|
324
|
+
resolvedRemote,
|
|
235
325
|
pagination,
|
|
236
326
|
columns,
|
|
237
327
|
]);
|
|
@@ -240,10 +330,17 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
240
330
|
// If totalSize is explicitly provided, use it for server-side pagination
|
|
241
331
|
if (totalSize !== undefined && totalSize > 0)
|
|
242
332
|
return totalSize;
|
|
243
|
-
if (
|
|
333
|
+
if (resolvedRemote)
|
|
244
334
|
return data.length;
|
|
245
335
|
return filterData(data, filters, columns, debouncedSearchValue).length;
|
|
246
|
-
}, [
|
|
336
|
+
}, [
|
|
337
|
+
data,
|
|
338
|
+
filters,
|
|
339
|
+
columns,
|
|
340
|
+
debouncedSearchValue,
|
|
341
|
+
resolvedRemote,
|
|
342
|
+
totalSize,
|
|
343
|
+
]);
|
|
247
344
|
// Handle search
|
|
248
345
|
const handleSearchChange = useCallback((value) => {
|
|
249
346
|
setInternalSearchValue(value);
|
|
@@ -279,6 +376,8 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
279
376
|
clearAllFilter?.();
|
|
280
377
|
onFilterChange?.({});
|
|
281
378
|
onChange?.({ type: 'filter', filters: {} });
|
|
379
|
+
// Reset the tracking ref so subsequent filters can trigger onFilter
|
|
380
|
+
lastOnFilterCallRef.current = null;
|
|
282
381
|
}, [clearFilters, onClearFilters, onFilterChange, onChange]);
|
|
283
382
|
// Handle page change
|
|
284
383
|
const handlePageChange = useCallback((newPage) => {
|
|
@@ -288,7 +387,10 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
288
387
|
// onPageSizeChange is the primary callback
|
|
289
388
|
onPageSizeChange?.(displayPage, pageSize);
|
|
290
389
|
onPageChange?.(displayPage, pageSize);
|
|
291
|
-
onChange?.({
|
|
390
|
+
onChange?.({
|
|
391
|
+
type: 'pagination',
|
|
392
|
+
pagination: { page: displayPage, pageSize },
|
|
393
|
+
});
|
|
292
394
|
}, [goToPage, pageSize, onPageSizeChange, onPageChange, onChange]);
|
|
293
395
|
// Handle page size change
|
|
294
396
|
const handlePageSizeChange = useCallback((newSize) => {
|
|
@@ -344,25 +446,32 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
344
446
|
}, []);
|
|
345
447
|
// Handle cell edit
|
|
346
448
|
const handleCellDoubleClick = useCallback((row, rowIndex, column, colIndex, e) => {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
449
|
+
// Call column's onDoubleClick event if defined
|
|
450
|
+
column.events?.onDoubleClick?.(e, row, rowIndex, column, colIndex);
|
|
451
|
+
// Handle cell editing
|
|
452
|
+
if (editMode !== 'none') {
|
|
453
|
+
const { isEditable } = getCellEditableInfo(column, row, rowIndex, colIndex);
|
|
454
|
+
if (isEditable && editMode === 'dblclick') {
|
|
455
|
+
setEditingCell({ row: rowIndex, field: column.dataField });
|
|
456
|
+
setEditValue(getNestedValue(row, column.dataField));
|
|
457
|
+
}
|
|
355
458
|
}
|
|
459
|
+
// Call row double click callback
|
|
356
460
|
onRowDoubleClick?.(row, rowIndex, e);
|
|
357
461
|
}, [editMode, onRowDoubleClick, getCellEditableInfo]);
|
|
358
|
-
const handleCellClick = useCallback((row, rowIndex, column, colIndex) => {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
462
|
+
const handleCellClick = useCallback((row, rowIndex, column, colIndex, e) => {
|
|
463
|
+
// Call column's onClick event if defined
|
|
464
|
+
if (e) {
|
|
465
|
+
column.events?.onClick?.(e, row, rowIndex, column, colIndex);
|
|
466
|
+
}
|
|
467
|
+
// Handle cell editing on click
|
|
468
|
+
if (editMode === 'click') {
|
|
469
|
+
const { isEditable } = getCellEditableInfo(column, row, rowIndex, colIndex);
|
|
470
|
+
if (isEditable) {
|
|
471
|
+
setEditingCell({ row: rowIndex, field: column.dataField });
|
|
472
|
+
setEditValue(getNestedValue(row, column.dataField));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
366
475
|
}, [editMode, getCellEditableInfo]);
|
|
367
476
|
const handleCellEditComplete = useCallback((row, rowIndex, column) => {
|
|
368
477
|
if (editingCell) {
|
|
@@ -387,7 +496,11 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
387
496
|
};
|
|
388
497
|
// Process field value based on fieldTypeConfig (matching reference implementation)
|
|
389
498
|
const processFieldValue = useCallback((value, dataField, row) => {
|
|
390
|
-
const
|
|
499
|
+
const configOrType = fieldTypeConfig?.[dataField];
|
|
500
|
+
// Handle both object config and string shorthand (e.g., 'boolean', 'date')
|
|
501
|
+
const config = typeof configOrType === 'string'
|
|
502
|
+
? { type: configOrType }
|
|
503
|
+
: configOrType;
|
|
391
504
|
const fieldType = config?.type || 'string';
|
|
392
505
|
switch (fieldType) {
|
|
393
506
|
case 'array':
|
|
@@ -475,8 +588,8 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
475
588
|
}
|
|
476
589
|
}, [fieldTypeConfig]);
|
|
477
590
|
// Handle export
|
|
478
|
-
const handleExport = useCallback(() => {
|
|
479
|
-
const exportData =
|
|
591
|
+
const handleExport = useCallback(async () => {
|
|
592
|
+
const exportData = resolvedRemote
|
|
480
593
|
? data
|
|
481
594
|
: filterData(data, filters, columns, debouncedSearchValue);
|
|
482
595
|
// Filter columns based on hideExcelSheet array and csvExport !== false
|
|
@@ -487,7 +600,10 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
487
600
|
// Also filter based on fieldTypeConfig hideFromExport or csvExport
|
|
488
601
|
if (fieldTypeConfig) {
|
|
489
602
|
exportColumns = exportColumns.filter((col) => {
|
|
490
|
-
const
|
|
603
|
+
const configOrType = fieldTypeConfig[col.dataField];
|
|
604
|
+
const config = typeof configOrType === 'string'
|
|
605
|
+
? { type: configOrType }
|
|
606
|
+
: configOrType;
|
|
491
607
|
if (config?.hideFromExport)
|
|
492
608
|
return false;
|
|
493
609
|
if (config?.csvExport === false)
|
|
@@ -497,7 +613,10 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
497
613
|
}
|
|
498
614
|
// Create enhanced columns with export formatters
|
|
499
615
|
const enhancedColumns = exportColumns.map((col) => {
|
|
500
|
-
const
|
|
616
|
+
const configOrType = fieldTypeConfig?.[col.dataField];
|
|
617
|
+
const config = typeof configOrType === 'string'
|
|
618
|
+
? { type: configOrType }
|
|
619
|
+
: configOrType;
|
|
501
620
|
// If column already has csvFormatter, use it
|
|
502
621
|
if (col.csvFormatter) {
|
|
503
622
|
return col;
|
|
@@ -515,7 +634,13 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
515
634
|
}
|
|
516
635
|
return col;
|
|
517
636
|
});
|
|
518
|
-
|
|
637
|
+
// Export based on format
|
|
638
|
+
if (exportFormat === 'excel') {
|
|
639
|
+
await exportToExcel(exportData, enhancedColumns, resolvedExportFileName);
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
exportToCSV(exportData, enhancedColumns, resolvedExportFileName);
|
|
643
|
+
}
|
|
519
644
|
}, [
|
|
520
645
|
data,
|
|
521
646
|
filters,
|
|
@@ -526,7 +651,8 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
526
651
|
fieldTypeConfig,
|
|
527
652
|
processFieldValue,
|
|
528
653
|
resolvedExportFileName,
|
|
529
|
-
|
|
654
|
+
resolvedRemote,
|
|
655
|
+
exportFormat,
|
|
530
656
|
]);
|
|
531
657
|
// Handle checkbox change
|
|
532
658
|
const handleCheckboxChange = useCallback((row, e) => {
|
|
@@ -631,7 +757,10 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
631
757
|
if (toolbar)
|
|
632
758
|
return toolbar;
|
|
633
759
|
const hasFilters = Object.keys(filters).length > 0 || searchValue;
|
|
634
|
-
return (_jsxs(Toolbar, { className: classNames.toolbar, style: styles.toolbar, children: [_jsxs(ToolbarGroup, { children: [toolbarLeft, searchable && (_jsxs(SearchInput, { children: [_jsx(SearchIcon, {}), _jsx("input", { type: "text", value: searchValue, onChange: (e) => handleSearchChange(e.target.value),
|
|
760
|
+
return (_jsxs(Toolbar, { className: classNames.toolbar, style: styles.toolbar, children: [_jsxs(ToolbarGroup, { children: [toolbarLeft, searchable && (_jsxs(SearchInput, { children: [_jsx(SearchIcon, {}), _jsx("input", { type: "text", value: searchValue, onChange: (e) => handleSearchChange(e.target.value), onFocus: () => {
|
|
761
|
+
// Clear column filter focus when global search is focused
|
|
762
|
+
focusedFilterFieldRef.current = null;
|
|
763
|
+
}, placeholder: searchPlaceholder })] })), searchable && (_jsx(ToolbarButton, { onClick: () => handleSearchChange(''), disabled: !searchValue, style: { opacity: searchValue ? 1 : 0.6 }, children: "Clear" })), _jsx(ToolbarButton, { onClick: handleClearFilters, disabled: !hasFilters, style: { opacity: hasFilters ? 1 : 0.6 }, children: "Clear all filters" }), resolvedExportable && hideExcelSheet !== true && (_jsxs(ToolbarButton, { onClick: handleExport, children: [_jsx(DownloadIcon, {}), exportFormat === 'excel' ? 'Export Excel' : 'Export CSV'] })), showFilterToggle && (_jsxs("div", { ref: columnToggleRef, style: { position: 'relative' }, children: [_jsx(Tooltip, { content: "Show/Hide Columns", position: "bottom", children: _jsx(ToolbarButton, { "$active": columnToggleOpen, onClick: () => setColumnToggleOpen(!columnToggleOpen), "aria-label": "Toggle column visibility", style: { padding: '0 8px' }, children: _jsx(FilterIcon, {}) }) }), columnToggleOpen && (_jsxs(ColumnTogglePanel, { children: [_jsxs(ColumnToggleHeader, { children: [_jsx("span", { children: "Show/Hide Columns" }), _jsx("button", { onClick: () => setColumnToggleOpen(false), children: _jsx(CloseIcon, {}) })] }), _jsx(ColumnToggleSearch, { children: _jsx("input", { type: "text", value: columnSearch, onChange: (e) => setColumnSearch(e.target.value), placeholder: "Search columns..." }) }), _jsxs(ColumnToggleList, { children: [_jsxs(ColumnToggleItem, { style: {
|
|
635
764
|
borderBottom: '1px solid #e5e7eb',
|
|
636
765
|
paddingBottom: 8,
|
|
637
766
|
marginBottom: 4,
|
|
@@ -656,8 +785,12 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
656
785
|
if (!pagination)
|
|
657
786
|
return null;
|
|
658
787
|
const actualTotalPages = Math.ceil(calculatedTotal / pageSize) || 1;
|
|
659
|
-
const startItem = page * pageSize + 1;
|
|
660
|
-
|
|
788
|
+
const startItem = calculatedTotal > 0 ? page * pageSize + 1 : 0;
|
|
789
|
+
// Use actual data length for endItem to show correct count
|
|
790
|
+
const actualDataCount = resolvedRemote
|
|
791
|
+
? data.length
|
|
792
|
+
: processedData.length;
|
|
793
|
+
const endItem = Math.min(page * pageSize + actualDataCount, calculatedTotal);
|
|
661
794
|
const showTotal = resolvedPaginationConfig?.showTotal === true
|
|
662
795
|
? `Showing ${startItem} to ${endItem} of ${calculatedTotal} Results`
|
|
663
796
|
: typeof resolvedPaginationConfig?.showTotal === 'function'
|
|
@@ -704,35 +837,38 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
704
837
|
resolvedRowSelection?.onChange?.(Array.from(selectedKeys), selectedRows);
|
|
705
838
|
// Call onSelectedRow only when user clicks this button
|
|
706
839
|
onSelectedRow?.(selectedRows);
|
|
707
|
-
}, children: [_jsx(SelectionCount, { "$animate": selectionAnimation, children: selectedKeys.size }, selectedKeys.size), _jsx("span", { children: "Selected" })] })), resolvedPaginationConfig?.showSizeChanger !== false && (_jsx(PageSizeSelect, { value: pageSize, onChange: (e) => handlePageSizeChange(Number(e.target.value)), children: (resolvedPaginationConfig?.pageSizeOptions || [10, 20, 30, 50]).map((size) => (_jsx("option", { value: size, children: size }, size))) }))] }), _jsxs(PaginationControls, { children: [_jsx(PageButton, { onClick: () => handlePageChange(0), disabled: page === 0, children: _jsx(ChevronsLeftIcon, {}) }), _jsx(PageButton, { onClick: () => handlePageChange(page - 1), disabled: page === 0, children: _jsx(ChevronLeftIcon, {}) }), getPageNumbers().map((p, i) => typeof p === 'string' ? (_jsx("span", { style: { padding: '0 4px', color: 'white' }, children: p }, `ellipsis-${i}`)) : (_jsx(PageButton, { "$active": p === page, onClick: () => handlePageChange(p), children: p + 1 }, p))), _jsx(PageButton, { onClick: () => handlePageChange(page + 1), disabled: page >= actualTotalPages - 1, children: _jsx(ChevronRightIcon, {}) }), _jsx(PageButton, { onClick: () => handlePageChange(actualTotalPages - 1), disabled: page >= actualTotalPages - 1, children: _jsx(ChevronsRightIcon, {}) })] }), showTotal && _jsx(PaginationInfo, { children: showTotal }), resolvedPaginationConfig?.showQuickJumper &&
|
|
708
|
-
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
840
|
+
}, children: [_jsx(SelectionCount, { "$animate": selectionAnimation, children: selectedKeys.size }, selectedKeys.size), _jsx("span", { children: "Selected" })] })), resolvedPaginationConfig?.showSizeChanger !== false && (_jsx(PageSizeSelect, { value: pageSize, onChange: (e) => handlePageSizeChange(Number(e.target.value)), children: (resolvedPaginationConfig?.pageSizeOptions || [10, 20, 30, 50]).map((size) => (_jsx("option", { value: size, children: size }, size))) }))] }), _jsxs(PaginationControls, { children: [_jsx(PageButton, { onClick: () => handlePageChange(0), disabled: page === 0, children: _jsx(ChevronsLeftIcon, {}) }), _jsx(PageButton, { onClick: () => handlePageChange(page - 1), disabled: page === 0, children: _jsx(ChevronLeftIcon, {}) }), getPageNumbers().map((p, i) => typeof p === 'string' ? (_jsx("span", { style: { padding: '0 4px', color: 'white' }, children: p }, `ellipsis-${i}`)) : (_jsx(PageButton, { "$active": p === page, onClick: () => handlePageChange(p), children: p + 1 }, p))), _jsx(PageButton, { onClick: () => handlePageChange(page + 1), disabled: page >= actualTotalPages - 1, children: _jsx(ChevronRightIcon, {}) }), _jsx(PageButton, { onClick: () => handlePageChange(actualTotalPages - 1), disabled: page >= actualTotalPages - 1, children: _jsx(ChevronsRightIcon, {}) })] }), showTotal && _jsx(PaginationInfo, { children: showTotal }), resolvedPaginationConfig?.showQuickJumper &&
|
|
841
|
+
(() => {
|
|
842
|
+
const handleQuickJump = (input) => {
|
|
843
|
+
const pageNum = parseInt(input.value, 10);
|
|
844
|
+
if (!isNaN(pageNum) &&
|
|
845
|
+
pageNum >= 1 &&
|
|
846
|
+
pageNum <= actualTotalPages) {
|
|
847
|
+
handlePageChange(pageNum - 1); // Convert to 0-indexed
|
|
848
|
+
input.value = '';
|
|
849
|
+
return true;
|
|
850
|
+
}
|
|
851
|
+
return false;
|
|
852
|
+
};
|
|
853
|
+
return (_jsxs(QuickJumper, { children: ["Go to", _jsx("input", { type: "number", min: 1, max: actualTotalPages, placeholder: `1-${actualTotalPages}`, onKeyDown: (e) => {
|
|
854
|
+
if (e.key === 'Enter') {
|
|
855
|
+
e.preventDefault();
|
|
856
|
+
const target = e.target;
|
|
857
|
+
if (handleQuickJump(target)) {
|
|
858
|
+
target.blur();
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}, onBlur: (e) => {
|
|
720
862
|
const target = e.target;
|
|
721
|
-
|
|
722
|
-
|
|
863
|
+
handleQuickJump(target);
|
|
864
|
+
} }), _jsx(PageButton, { onClick: () => {
|
|
865
|
+
const input = document.querySelector(`#${id} input[type="number"]`);
|
|
866
|
+
if (input) {
|
|
867
|
+
handleQuickJump(input);
|
|
868
|
+
input.blur();
|
|
723
869
|
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
const target = e.target;
|
|
727
|
-
handleQuickJump(target);
|
|
728
|
-
} }), _jsx(PageButton, { onClick: () => {
|
|
729
|
-
const input = document.querySelector(`#${id} input[type="number"]`);
|
|
730
|
-
if (input) {
|
|
731
|
-
handleQuickJump(input);
|
|
732
|
-
input.blur();
|
|
733
|
-
}
|
|
734
|
-
}, style: { marginLeft: '4px', padding: '2px 8px' }, children: "Go" })] }));
|
|
735
|
-
})()] }));
|
|
870
|
+
}, style: { marginLeft: '4px', padding: '2px 8px' }, children: "Go" })] }));
|
|
871
|
+
})()] }));
|
|
736
872
|
};
|
|
737
873
|
// Check if any columns have filters
|
|
738
874
|
const hasFilterableColumns = filterable ||
|
|
@@ -741,7 +877,17 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
741
877
|
c.filter === true);
|
|
742
878
|
// Should show filter row - check if there are filterable columns AND filters are visible
|
|
743
879
|
const shouldShowFilterRow = hasFilterableColumns && showFilterRow;
|
|
744
|
-
return (_jsxs(TableRoot, { ref:
|
|
880
|
+
return (_jsxs(TableRoot, { ref: (node) => {
|
|
881
|
+
// Handle forwarded ref
|
|
882
|
+
if (typeof ref === 'function') {
|
|
883
|
+
ref(node);
|
|
884
|
+
}
|
|
885
|
+
else if (ref) {
|
|
886
|
+
ref.current = node;
|
|
887
|
+
}
|
|
888
|
+
// Also store in our container ref
|
|
889
|
+
tableContainerRef.current = node;
|
|
890
|
+
}, "$bordered": bordered, "$compact": compact, className: className || classNames.root, style: { ...styles.root, ...style, position: 'relative' }, "aria-label": rest['aria-label'], "aria-labelledby": rest['aria-labelledby'], children: [loading && (_jsx(LoadingOverlay, { className: classNames.loading, style: styles.loading, children: loadingIndicator || _jsx(LoadingSpinner, {}) })), renderToolbar(), _jsx(TableWrapper, { "$maxHeight": maxHeight, "$stickyHeader": stickyHeader, className: classNames.wrapper, style: styles.wrapper, children: _jsxs(StyledTable, { ref: tableRef, "$striped": striped, "$hover": hover, "$compact": compact, role: "grid", children: [caption && _jsx("caption", { className: "sr-only", children: caption }), _jsx(TableHeader, { "$sticky": stickyHeader, className: classNames.header, style: styles.header, children: _jsxs(HeaderRow, { className: classNames.headerRow, style: styles.headerRow, children: [resolvedRowSelection?.mode === 'checkbox' && (_jsx(HeaderCell, { "$align": "center", "$sortable": false, "$compact": compact, "$width": resolvedRowSelection.columnWidth || 40, children: !resolvedRowSelection.hideSelectAll && (_jsx(Checkbox, { checked: isAllSelected, ref: (el) => {
|
|
745
891
|
if (el)
|
|
746
892
|
el.indeterminate = isIndeterminate;
|
|
747
893
|
}, onChange: handleSelectAllChange })) })), expandable && (_jsx(HeaderCell, { "$align": "center", "$sortable": false, "$compact": compact, "$width": expandable.columnWidth || 40 })), showRowNumber && (_jsx(HeaderCell, { "$align": "center", "$sortable": false, "$compact": compact, "$width": rowNumberWidth, children: rowNumberTitle })), visibleColumns.map((column, colIndex) => {
|
|
@@ -839,7 +985,10 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
839
985
|
flexShrink: 1,
|
|
840
986
|
display: 'flex',
|
|
841
987
|
alignItems: 'center',
|
|
842
|
-
}, onClick: (e) => e.stopPropagation(), children: column.filterRenderer ? (column.filterRenderer(onFilter, column)) : (_jsx(
|
|
988
|
+
}, onClick: (e) => e.stopPropagation(), children: column.filterRenderer ? (column.filterRenderer(onFilter, column)) : (_jsx("div", { onFocusCapture: () => {
|
|
989
|
+
focusedFilterFieldRef.current =
|
|
990
|
+
column.dataField;
|
|
991
|
+
}, "data-filter-wrapper": column.dataField, children: _jsx(FilterComponent, { column: column, value: filters[column.dataField], onChange: onFilter, onClear: () => handleFilterChange(column.dataField, null) }, `filter-${column.dataField}`) })) }), showSort && (_jsx("div", { style: {
|
|
843
992
|
width: '20%',
|
|
844
993
|
minWidth: 24,
|
|
845
994
|
height: 24,
|
|
@@ -879,6 +1028,13 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
879
1028
|
const hasCustomBgColor = rowSelected &&
|
|
880
1029
|
selectedStyle &&
|
|
881
1030
|
(selectedStyle.backgroundColor || selectedStyle.background);
|
|
1031
|
+
// Apply non-selectable style if row is disabled
|
|
1032
|
+
const disabledStyle = checkboxProps?.disabled
|
|
1033
|
+
? nonSelectableStyle || {
|
|
1034
|
+
backgroundColor: '#f3f4f6',
|
|
1035
|
+
opacity: 0.7,
|
|
1036
|
+
}
|
|
1037
|
+
: undefined;
|
|
882
1038
|
return (_jsxs(React.Fragment, { children: [_jsxs(TableRow, { "$selected": rowSelected, "$clickable": !!onRowClick ||
|
|
883
1039
|
resolvedRowSelection?.mode === 'single' ||
|
|
884
1040
|
expandable?.expandRowByClick === true, "$disabled": !!checkboxProps?.disabled, "$hasCustomSelectedStyle": !!hasCustomBgColor, "data-custom-selected": hasCustomBgColor ? 'true' : undefined, className: `${classNames.row || ''} ${rowClass || ''} ${rowSelected
|
|
@@ -886,7 +1042,16 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
886
1042
|
'function'
|
|
887
1043
|
? resolvedRowSelection.selectedRowClassName(row)
|
|
888
1044
|
: resolvedRowSelection?.selectedRowClassName || ''
|
|
889
|
-
: ''}`, style: {
|
|
1045
|
+
: ''}`, style: {
|
|
1046
|
+
...styles.row,
|
|
1047
|
+
...rowStyles,
|
|
1048
|
+
...selectedStyle,
|
|
1049
|
+
...disabledStyle,
|
|
1050
|
+
}, onClick: (e) => handleRowClick(row, rowIndex, e), onDoubleClick: (e) => {
|
|
1051
|
+
// Only trigger row-level double click if not handled by a cell
|
|
1052
|
+
// Cells handle their own double-clicks for editing
|
|
1053
|
+
onRowDoubleClick?.(row, rowIndex, e);
|
|
1054
|
+
}, role: "row", "aria-selected": rowSelected, children: [resolvedRowSelection?.mode === 'checkbox' && (_jsx(TableCell, { "$align": "center", "$compact": compact, "$padding": cellPadding, children: _jsx(Checkbox, { checked: rowSelected, disabled: checkboxProps?.disabled, onChange: (e) => handleCheckboxChange(row, e), onClick: (e) => e.stopPropagation() }) })), expandable && (_jsx(TableCell, { "$align": "center", "$compact": compact, "$padding": cellPadding, children: isExpandable && (_jsx(ExpandButton, { "$expanded": rowExpanded, onClick: (e) => handleExpandClick(row, e), children: expandable.expandIcon ? (expandable.expandIcon({
|
|
890
1055
|
expanded: rowExpanded,
|
|
891
1056
|
row,
|
|
892
1057
|
onExpand: () => toggleExpand(row),
|
|
@@ -903,7 +1068,10 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
903
1068
|
const cellStyle = typeof column.style === 'function'
|
|
904
1069
|
? column.style(getNestedValue(row, column.dataField), row, rowIndex, colIndex)
|
|
905
1070
|
: column.style;
|
|
906
|
-
return (_jsx(TableCell, { "$align": column.align || 'left', "$compact": compact, "$padding": cellPadding, "$pinned": column.pinned, "$hasCustomClass": !!cellClass, className: cellClass || classNames.cell, style: { ...styles.cell, ...cellStyle }, onClick: () => handleCellClick(row, rowIndex, column, colIndex
|
|
1071
|
+
return (_jsx(TableCell, { "$align": column.align || 'left', "$compact": compact, "$padding": cellPadding, "$pinned": column.pinned, "$hasCustomClass": !!cellClass, className: cellClass || classNames.cell, style: { ...styles.cell, ...cellStyle }, onClick: (e) => handleCellClick(row, rowIndex, column, colIndex, e), onDoubleClick: (e) => {
|
|
1072
|
+
e.stopPropagation(); // Prevent row-level double-click from interfering
|
|
1073
|
+
handleCellDoubleClick(row, rowIndex, column, colIndex, e);
|
|
1074
|
+
}, role: "gridcell", children: (() => {
|
|
907
1075
|
const editInfo = getCellEditableInfo(column, row, rowIndex, colIndex);
|
|
908
1076
|
if (editInfo.isEditable &&
|
|
909
1077
|
editMode !== 'none') {
|
|
@@ -145,6 +145,8 @@ const TextFilterComponent = ({ column, value, onChange, options }) => {
|
|
|
145
145
|
const inputRef = useRef(null);
|
|
146
146
|
const onChangeRef = useRef(onChange);
|
|
147
147
|
const onFilterRef = useRef(onFilter);
|
|
148
|
+
// Track if the last change was from user input (internal) vs external (e.g., clear all)
|
|
149
|
+
const lastInternalValueRef = useRef(internalValue);
|
|
148
150
|
// Keep refs in sync
|
|
149
151
|
useEffect(() => {
|
|
150
152
|
internalValueRef.current = internalValue;
|
|
@@ -153,10 +155,23 @@ const TextFilterComponent = ({ column, value, onChange, options }) => {
|
|
|
153
155
|
onChangeRef.current = onChange;
|
|
154
156
|
onFilterRef.current = onFilter;
|
|
155
157
|
}, [onChange, onFilter]);
|
|
158
|
+
// Sync internal value when external value changes (e.g., from clear all filters)
|
|
159
|
+
// Only sync when external value differs from what we last sent to parent
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
const externalValue = value || '';
|
|
162
|
+
// Only sync if external value is different from what we last propagated
|
|
163
|
+
// This prevents overwriting user input while they're typing
|
|
164
|
+
if (externalValue !== lastInternalValueRef.current) {
|
|
165
|
+
setInternalValue(externalValue);
|
|
166
|
+
lastInternalValueRef.current = externalValue;
|
|
167
|
+
}
|
|
168
|
+
}, [value]);
|
|
156
169
|
// Debounce the internal value
|
|
157
170
|
const [debouncedValue] = useDebouncedValue(internalValue, { wait: delay });
|
|
158
171
|
// Propagate debounced value to parent
|
|
159
172
|
useEffect(() => {
|
|
173
|
+
// Update the ref to track what we're sending to parent
|
|
174
|
+
lastInternalValueRef.current = debouncedValue || '';
|
|
160
175
|
onChangeRef.current(debouncedValue || null);
|
|
161
176
|
onFilterRef.current?.(debouncedValue);
|
|
162
177
|
}, [debouncedValue]);
|
|
@@ -181,23 +196,43 @@ const TextFilterComponent = ({ column, value, onChange, options }) => {
|
|
|
181
196
|
}
|
|
182
197
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
183
198
|
}, [getFilter]);
|
|
199
|
+
// Track if input should be focused (user is actively typing)
|
|
200
|
+
const hasFocusRef = useRef(false);
|
|
184
201
|
// Simple change handler - just update local state
|
|
185
202
|
const handleChange = useCallback((e) => {
|
|
186
203
|
setInternalValue(e.target.value);
|
|
187
204
|
}, []);
|
|
205
|
+
// Track focus state
|
|
206
|
+
const handleFocus = useCallback(() => {
|
|
207
|
+
hasFocusRef.current = true;
|
|
208
|
+
}, []);
|
|
209
|
+
const handleBlur = useCallback(() => {
|
|
210
|
+
hasFocusRef.current = false;
|
|
211
|
+
}, []);
|
|
212
|
+
// Restore focus after re-renders if it was focused
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
if (hasFocusRef.current && inputRef.current) {
|
|
215
|
+
// Use requestAnimationFrame to ensure DOM is ready
|
|
216
|
+
requestAnimationFrame(() => {
|
|
217
|
+
if (hasFocusRef.current && inputRef.current) {
|
|
218
|
+
inputRef.current.focus();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
});
|
|
188
223
|
const inputStyle = {
|
|
189
224
|
fontWeight: 400,
|
|
190
225
|
...style,
|
|
191
226
|
};
|
|
192
227
|
// If custom className is provided, use plain input to allow full CSS control
|
|
193
228
|
if (className) {
|
|
194
|
-
return (_jsx("input", { ref: inputRef, type: "text", id: id, value: internalValue, onChange: handleChange, placeholder: placeholder || column.filterPlaceholder || `Filter ${column.text}...`, className: className, style: {
|
|
229
|
+
return (_jsx("input", { ref: inputRef, type: "text", id: id, "data-filter-field": column.dataField, value: internalValue, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, placeholder: placeholder || column.filterPlaceholder || `Filter ${column.text}...`, className: className, style: {
|
|
195
230
|
width: '100%',
|
|
196
231
|
fontWeight: 400,
|
|
197
232
|
...style,
|
|
198
233
|
}, disabled: disabled }));
|
|
199
234
|
}
|
|
200
|
-
return (_jsx(FilterInputBase, { ref: inputRef, type: "text", id: id, value: internalValue, onChange: handleChange, placeholder: placeholder || column.filterPlaceholder || `Filter ${column.text}...`, style: inputStyle, disabled: disabled }));
|
|
235
|
+
return (_jsx(FilterInputBase, { ref: inputRef, type: "text", id: id, "data-filter-field": column.dataField, value: internalValue, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, placeholder: placeholder || column.filterPlaceholder || `Filter ${column.text}...`, style: inputStyle, disabled: disabled }));
|
|
201
236
|
};
|
|
202
237
|
export function TextFilter(optionsOrProps) {
|
|
203
238
|
// Check if it's being used as a factory function (options object without column/value/onChange)
|
|
@@ -227,6 +262,9 @@ const NumberFilterComponent = ({ column, value, onChange, options }) => {
|
|
|
227
262
|
const inputRef = useRef(null);
|
|
228
263
|
const onChangeRef = useRef(onChange);
|
|
229
264
|
const onFilterRef = useRef(onFilter);
|
|
265
|
+
// Track last value sent to parent to prevent sync loops
|
|
266
|
+
const lastNumberRef = useRef(number);
|
|
267
|
+
const lastComparatorRef = useRef(comparator);
|
|
230
268
|
// Keep refs in sync with state
|
|
231
269
|
useEffect(() => {
|
|
232
270
|
numberRef.current = number;
|
|
@@ -236,10 +274,27 @@ const NumberFilterComponent = ({ column, value, onChange, options }) => {
|
|
|
236
274
|
onChangeRef.current = onChange;
|
|
237
275
|
onFilterRef.current = onFilter;
|
|
238
276
|
}, [onChange, onFilter]);
|
|
277
|
+
// Sync internal value when external value changes (e.g., from clear all filters)
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
const externalNumber = value?.number || '';
|
|
280
|
+
const externalComparator = value?.comparator || defaultComparator;
|
|
281
|
+
// Only sync if different from what we last sent to parent
|
|
282
|
+
if (externalNumber !== lastNumberRef.current) {
|
|
283
|
+
setNumber(externalNumber);
|
|
284
|
+
lastNumberRef.current = externalNumber;
|
|
285
|
+
}
|
|
286
|
+
if (externalComparator !== lastComparatorRef.current) {
|
|
287
|
+
setComparator(externalComparator);
|
|
288
|
+
lastComparatorRef.current = externalComparator;
|
|
289
|
+
}
|
|
290
|
+
}, [value, defaultComparator]);
|
|
239
291
|
// Debounce the number value
|
|
240
292
|
const [debouncedNumber] = useDebouncedValue(number, { wait: delay });
|
|
241
293
|
// Propagate debounced value to parent
|
|
242
294
|
useEffect(() => {
|
|
295
|
+
// Update refs to track what we're sending to parent
|
|
296
|
+
lastNumberRef.current = debouncedNumber || '';
|
|
297
|
+
lastComparatorRef.current = comparatorRef.current;
|
|
243
298
|
const newValue = debouncedNumber
|
|
244
299
|
? { number: debouncedNumber, comparator: comparatorRef.current }
|
|
245
300
|
: null;
|
|
@@ -278,6 +333,25 @@ const NumberFilterComponent = ({ column, value, onChange, options }) => {
|
|
|
278
333
|
}
|
|
279
334
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
280
335
|
}, [getFilter]);
|
|
336
|
+
// Track if input should be focused
|
|
337
|
+
const hasFocusRef = useRef(false);
|
|
338
|
+
// Track focus state
|
|
339
|
+
const handleFocus = useCallback(() => {
|
|
340
|
+
hasFocusRef.current = true;
|
|
341
|
+
}, []);
|
|
342
|
+
const handleBlur = useCallback(() => {
|
|
343
|
+
hasFocusRef.current = false;
|
|
344
|
+
}, []);
|
|
345
|
+
// Restore focus after re-renders if it was focused
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
if (hasFocusRef.current && inputRef.current) {
|
|
348
|
+
requestAnimationFrame(() => {
|
|
349
|
+
if (hasFocusRef.current && inputRef.current) {
|
|
350
|
+
inputRef.current.focus();
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
});
|
|
281
355
|
// Handle comparator change - trigger immediate filter update
|
|
282
356
|
const handleComparatorChange = useCallback((newComparator) => {
|
|
283
357
|
setComparator(newComparator);
|
|
@@ -303,6 +377,7 @@ const NumberFilterComponent = ({ column, value, onChange, options }) => {
|
|
|
303
377
|
const inputProps = {
|
|
304
378
|
type: 'text',
|
|
305
379
|
id,
|
|
380
|
+
'data-filter-field': column.dataField,
|
|
306
381
|
value: number,
|
|
307
382
|
onChange: (e) => {
|
|
308
383
|
const val = e.target.value;
|
|
@@ -311,6 +386,8 @@ const NumberFilterComponent = ({ column, value, onChange, options }) => {
|
|
|
311
386
|
setNumber(val);
|
|
312
387
|
}
|
|
313
388
|
},
|
|
389
|
+
onFocus: handleFocus,
|
|
390
|
+
onBlur: handleBlur,
|
|
314
391
|
placeholder: placeholder || column.filterPlaceholder || 'Number...',
|
|
315
392
|
disabled,
|
|
316
393
|
};
|
|
@@ -341,6 +418,13 @@ const DateFilterComponent = ({ column, value, onChange, options }) => {
|
|
|
341
418
|
const stateRef = useRef({ startDate, endDate, diffFlag, comparator });
|
|
342
419
|
const onChangeRef = useRef(onChange);
|
|
343
420
|
const onFilterRef = useRef(onFilter);
|
|
421
|
+
// Track last values sent to parent to prevent sync loops
|
|
422
|
+
const lastValuesRef = useRef({
|
|
423
|
+
startDate,
|
|
424
|
+
endDate,
|
|
425
|
+
diffFlag,
|
|
426
|
+
comparator,
|
|
427
|
+
});
|
|
344
428
|
// Keep refs in sync with state
|
|
345
429
|
useEffect(() => {
|
|
346
430
|
stateRef.current = { startDate, endDate, diffFlag, comparator };
|
|
@@ -349,11 +433,42 @@ const DateFilterComponent = ({ column, value, onChange, options }) => {
|
|
|
349
433
|
onChangeRef.current = onChange;
|
|
350
434
|
onFilterRef.current = onFilter;
|
|
351
435
|
}, [onChange, onFilter]);
|
|
436
|
+
// Sync internal value when external value changes (e.g., from clear all filters)
|
|
437
|
+
useEffect(() => {
|
|
438
|
+
const externalStartDate = value?.startDate || '';
|
|
439
|
+
const externalEndDate = value?.endDate || '';
|
|
440
|
+
const externalDiffFlag = value?.diffFlag ?? defaultRangeMode;
|
|
441
|
+
const externalComparator = value?.comparator || defaultComparator;
|
|
442
|
+
// Only sync if different from what we last sent to parent
|
|
443
|
+
if (externalStartDate !== lastValuesRef.current.startDate) {
|
|
444
|
+
setStartDate(externalStartDate);
|
|
445
|
+
lastValuesRef.current.startDate = externalStartDate;
|
|
446
|
+
}
|
|
447
|
+
if (externalEndDate !== lastValuesRef.current.endDate) {
|
|
448
|
+
setEndDate(externalEndDate);
|
|
449
|
+
lastValuesRef.current.endDate = externalEndDate;
|
|
450
|
+
}
|
|
451
|
+
if (externalDiffFlag !== lastValuesRef.current.diffFlag) {
|
|
452
|
+
setDiffFlag(externalDiffFlag);
|
|
453
|
+
lastValuesRef.current.diffFlag = externalDiffFlag;
|
|
454
|
+
}
|
|
455
|
+
if (externalComparator !== lastValuesRef.current.comparator) {
|
|
456
|
+
setComparator(externalComparator);
|
|
457
|
+
lastValuesRef.current.comparator = externalComparator;
|
|
458
|
+
}
|
|
459
|
+
}, [value, defaultRangeMode, defaultComparator]);
|
|
352
460
|
// Debounce the date values
|
|
353
461
|
const [debouncedStartDate] = useDebouncedValue(startDate, { wait: 500 });
|
|
354
462
|
const [debouncedEndDate] = useDebouncedValue(endDate, { wait: 500 });
|
|
355
463
|
// Propagate debounced value to parent
|
|
356
464
|
useEffect(() => {
|
|
465
|
+
// Update refs to track what we're sending to parent
|
|
466
|
+
lastValuesRef.current = {
|
|
467
|
+
startDate: debouncedStartDate,
|
|
468
|
+
endDate: debouncedEndDate,
|
|
469
|
+
diffFlag,
|
|
470
|
+
comparator,
|
|
471
|
+
};
|
|
357
472
|
const newValue = debouncedStartDate || debouncedEndDate
|
|
358
473
|
? {
|
|
359
474
|
startDate: debouncedStartDate,
|
|
@@ -440,6 +555,8 @@ const SelectFilterComponent = ({ column, value, onChange, options }) => {
|
|
|
440
555
|
const selectedValueRef = useRef(selectedValue);
|
|
441
556
|
const onChangeRef = useRef(onChange);
|
|
442
557
|
const onFilterRef = useRef(onFilter);
|
|
558
|
+
// Track last value sent to parent to prevent sync loops
|
|
559
|
+
const lastValueRef = useRef(selectedValue);
|
|
443
560
|
// Keep refs in sync with state
|
|
444
561
|
useEffect(() => {
|
|
445
562
|
selectedValueRef.current = selectedValue;
|
|
@@ -448,10 +565,21 @@ const SelectFilterComponent = ({ column, value, onChange, options }) => {
|
|
|
448
565
|
onChangeRef.current = onChange;
|
|
449
566
|
onFilterRef.current = onFilter;
|
|
450
567
|
}, [onChange, onFilter]);
|
|
568
|
+
// Sync internal value when external value changes (e.g., from clear all filters)
|
|
569
|
+
useEffect(() => {
|
|
570
|
+
const externalValue = value || '';
|
|
571
|
+
// Only sync if different from what we last sent to parent
|
|
572
|
+
if (externalValue !== lastValueRef.current) {
|
|
573
|
+
setSelectedValue(externalValue);
|
|
574
|
+
lastValueRef.current = externalValue;
|
|
575
|
+
}
|
|
576
|
+
}, [value]);
|
|
451
577
|
// Debounce the selected value
|
|
452
578
|
const [debouncedValue] = useDebouncedValue(selectedValue, { wait: delay });
|
|
453
579
|
// Propagate debounced value to parent
|
|
454
580
|
useEffect(() => {
|
|
581
|
+
// Update ref to track what we're sending to parent
|
|
582
|
+
lastValueRef.current = debouncedValue || '';
|
|
455
583
|
onChangeRef.current(debouncedValue || null);
|
|
456
584
|
onFilterRef.current?.(debouncedValue || null);
|
|
457
585
|
}, [debouncedValue]);
|
|
@@ -104,4 +104,20 @@ export declare function getNestedValue(obj: any, path: string): any;
|
|
|
104
104
|
* Export data to CSV
|
|
105
105
|
*/
|
|
106
106
|
export declare function exportToCSV<T>(data: T[], columns: TableColumn<T>[], fileName: string): void;
|
|
107
|
+
/**
|
|
108
|
+
* Export data to Excel (.xlsx) - loads ExcelJS dynamically from CDN
|
|
109
|
+
*/
|
|
110
|
+
export declare function exportToExcel<T>(data: T[], columns: TableColumn<T>[], fileName: string, options?: {
|
|
111
|
+
sheetName?: string;
|
|
112
|
+
headerStyle?: {
|
|
113
|
+
font?: {
|
|
114
|
+
bold?: boolean;
|
|
115
|
+
color?: string;
|
|
116
|
+
size?: number;
|
|
117
|
+
};
|
|
118
|
+
fill?: {
|
|
119
|
+
color?: string;
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
}): Promise<void>;
|
|
107
123
|
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -424,11 +424,11 @@ export function exportToCSV(data, columns, fileName) {
|
|
|
424
424
|
// Header row
|
|
425
425
|
const header = exportColumns.map((c) => `"${c.text}"`).join(',');
|
|
426
426
|
// Data rows
|
|
427
|
-
const rows = data.map((row) => {
|
|
427
|
+
const rows = data.map((row, rowIndex) => {
|
|
428
428
|
return exportColumns
|
|
429
429
|
.map((col) => {
|
|
430
430
|
const value = col.csvFormatter
|
|
431
|
-
? col.csvFormatter(getNestedValue(row, col.dataField), row)
|
|
431
|
+
? col.csvFormatter(getNestedValue(row, col.dataField), row, rowIndex)
|
|
432
432
|
: getNestedValue(row, col.dataField);
|
|
433
433
|
if (value === null || value === undefined)
|
|
434
434
|
return '""';
|
|
@@ -449,3 +449,88 @@ export function exportToCSV(data, columns, fileName) {
|
|
|
449
449
|
link.click();
|
|
450
450
|
URL.revokeObjectURL(url);
|
|
451
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* Dynamically load ExcelJS from CDN (no bundle impact)
|
|
454
|
+
*/
|
|
455
|
+
async function loadExcelJS() {
|
|
456
|
+
// Check if already loaded
|
|
457
|
+
if (window.ExcelJS) {
|
|
458
|
+
return window.ExcelJS;
|
|
459
|
+
}
|
|
460
|
+
return new Promise((resolve, reject) => {
|
|
461
|
+
const script = document.createElement('script');
|
|
462
|
+
script.src =
|
|
463
|
+
'https://cdn.jsdelivr.net/npm/exceljs@4.4.0/dist/exceljs.min.js';
|
|
464
|
+
script.async = true;
|
|
465
|
+
script.onload = () => resolve(window.ExcelJS);
|
|
466
|
+
script.onerror = () => reject(new Error('Failed to load ExcelJS from CDN'));
|
|
467
|
+
document.head.appendChild(script);
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Export data to Excel (.xlsx) - loads ExcelJS dynamically from CDN
|
|
472
|
+
*/
|
|
473
|
+
export async function exportToExcel(data, columns, fileName, options) {
|
|
474
|
+
try {
|
|
475
|
+
const ExcelJS = await loadExcelJS();
|
|
476
|
+
const workbook = new ExcelJS.Workbook();
|
|
477
|
+
const worksheet = workbook.addWorksheet(options?.sheetName || 'Sheet1');
|
|
478
|
+
const exportColumns = columns.filter((c) => c.csvExport !== false);
|
|
479
|
+
// Set up columns with headers
|
|
480
|
+
worksheet.columns = exportColumns.map((col) => ({
|
|
481
|
+
header: col.text,
|
|
482
|
+
key: col.dataField,
|
|
483
|
+
width: Math.max(col.text.length + 5, 15),
|
|
484
|
+
}));
|
|
485
|
+
// Style header row
|
|
486
|
+
const headerRow = worksheet.getRow(1);
|
|
487
|
+
headerRow.font = {
|
|
488
|
+
bold: options?.headerStyle?.font?.bold ?? true,
|
|
489
|
+
size: options?.headerStyle?.font?.size ?? 12,
|
|
490
|
+
};
|
|
491
|
+
if (options?.headerStyle?.fill?.color) {
|
|
492
|
+
headerRow.fill = {
|
|
493
|
+
type: 'pattern',
|
|
494
|
+
pattern: 'solid',
|
|
495
|
+
fgColor: { argb: options.headerStyle.fill.color.replace('#', '') },
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
headerRow.commit();
|
|
499
|
+
// Add data rows
|
|
500
|
+
data.forEach((row, rowIndex) => {
|
|
501
|
+
const rowData = {};
|
|
502
|
+
exportColumns.forEach((col) => {
|
|
503
|
+
const value = col.csvFormatter
|
|
504
|
+
? col.csvFormatter(getNestedValue(row, col.dataField), row, rowIndex)
|
|
505
|
+
: getNestedValue(row, col.dataField);
|
|
506
|
+
if (value === null || value === undefined) {
|
|
507
|
+
rowData[col.dataField] = '';
|
|
508
|
+
}
|
|
509
|
+
else if (typeof value === 'object') {
|
|
510
|
+
rowData[col.dataField] = JSON.stringify(value);
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
rowData[col.dataField] = value;
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
worksheet.addRow(rowData);
|
|
517
|
+
});
|
|
518
|
+
// Generate and download
|
|
519
|
+
const buffer = await workbook.xlsx.writeBuffer();
|
|
520
|
+
const blob = new Blob([buffer], {
|
|
521
|
+
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
522
|
+
});
|
|
523
|
+
const url = URL.createObjectURL(blob);
|
|
524
|
+
const link = document.createElement('a');
|
|
525
|
+
link.href = url;
|
|
526
|
+
link.download = `${fileName}_${new Date().toISOString().slice(0, 10)}.xlsx`;
|
|
527
|
+
link.click();
|
|
528
|
+
URL.revokeObjectURL(url);
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
console.error('Excel export failed:', error);
|
|
532
|
+
// Fallback to CSV if Excel export fails
|
|
533
|
+
console.warn('Falling back to CSV export');
|
|
534
|
+
exportToCSV(data, columns, fileName);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { Table, default } from './Table';
|
|
2
2
|
export * from './types';
|
|
3
|
-
export { useSortState, useFilterState, usePaginationState, useRowSelection, useRowExpansion, useColumnVisibility, useTableDebounce, sortData, filterData, paginateData, getNestedValue, exportToCSV, } from './hooks';
|
|
3
|
+
export { useSortState, useFilterState, usePaginationState, useRowSelection, useRowExpansion, useColumnVisibility, useTableDebounce, sortData, filterData, paginateData, getNestedValue, exportToCSV, exportToExcel, } from './hooks';
|
|
4
4
|
export { TextFilter, NumberFilter, DateFilter, SelectFilter, getFilterComponent, } from './filters';
|
|
5
5
|
export type { TextFilterOptions, TextFilterInstance, NumberFilterOptions, NumberFilterInstance, SelectFilterOptions, SelectFilterInstance, DateFilterOptions, DateFilterInstance, } from './filters';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { Table, default } from './Table';
|
|
2
2
|
export * from './types';
|
|
3
|
-
export { useSortState, useFilterState, usePaginationState, useRowSelection, useRowExpansion, useColumnVisibility, useTableDebounce, sortData, filterData, paginateData, getNestedValue, exportToCSV, } from './hooks';
|
|
3
|
+
export { useSortState, useFilterState, usePaginationState, useRowSelection, useRowExpansion, useColumnVisibility, useTableDebounce, sortData, filterData, paginateData, getNestedValue, exportToCSV, exportToExcel, } from './hooks';
|
|
4
4
|
export { TextFilter, NumberFilter, DateFilter, SelectFilter, getFilterComponent, } from './filters';
|
|
@@ -116,7 +116,7 @@ export interface TableColumn<T = any> {
|
|
|
116
116
|
/** Custom footer renderer */
|
|
117
117
|
footerFormatter?: (column: TableColumn<T>, data: T[]) => React.ReactNode;
|
|
118
118
|
/** CSV export formatter */
|
|
119
|
-
csvFormatter?: (cell: any, row: T) => string;
|
|
119
|
+
csvFormatter?: (cell: any, row: T, rowIndex: number) => string;
|
|
120
120
|
/** Whether to include in CSV export */
|
|
121
121
|
csvExport?: boolean;
|
|
122
122
|
/** Header CSS class */
|
|
@@ -145,8 +145,8 @@ export interface TableColumn<T = any> {
|
|
|
145
145
|
footer?: string | ((column: TableColumn<T>, data: T[]) => React.ReactNode);
|
|
146
146
|
/** Events */
|
|
147
147
|
events?: {
|
|
148
|
-
onClick?: (e: React.MouseEvent,
|
|
149
|
-
onDoubleClick?: (e: React.MouseEvent,
|
|
148
|
+
onClick?: (e: React.MouseEvent, row: T, rowIndex: number, column: TableColumn<T>, columnIndex: number) => void;
|
|
149
|
+
onDoubleClick?: (e: React.MouseEvent, row: T, rowIndex: number, column: TableColumn<T>, columnIndex: number) => void;
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
152
|
/** Filter props passed to custom filter components */
|
|
@@ -334,7 +334,8 @@ export interface TableProps<T = any> {
|
|
|
334
334
|
paginationConfig?: Partial<TablePaginationConfig>;
|
|
335
335
|
/** Total records (for server-side) */
|
|
336
336
|
totalSize?: number;
|
|
337
|
-
/** Server-side mode (default: true) - data is fetched from server via onPageSizeChange
|
|
337
|
+
/** Server-side mode (default: true) - data is fetched from server via onPageSizeChange.
|
|
338
|
+
* Auto-detection: If totalSize <= data.length, automatically uses client-side mode (remote=false) */
|
|
338
339
|
remote?: boolean;
|
|
339
340
|
/** Default sort */
|
|
340
341
|
defaultSort?: TableSortState;
|
|
@@ -376,10 +377,12 @@ export interface TableProps<T = any> {
|
|
|
376
377
|
showEditIcon?: boolean;
|
|
377
378
|
/** On cell edit */
|
|
378
379
|
onCellEdit?: (value: any, dataField: string, row: T, rowIndex: number) => void;
|
|
379
|
-
/** Enable
|
|
380
|
+
/** Enable export button */
|
|
380
381
|
exportable?: boolean;
|
|
381
382
|
/** Export file name */
|
|
382
383
|
exportFileName?: string;
|
|
384
|
+
/** Export format: 'csv' (default) or 'excel' */
|
|
385
|
+
exportFormat?: 'csv' | 'excel';
|
|
383
386
|
/** Enable column toggle */
|
|
384
387
|
columnToggle?: boolean;
|
|
385
388
|
/** Enable column reorder */
|
|
@@ -465,6 +468,10 @@ export interface TableProps<T = any> {
|
|
|
465
468
|
onView?: (row: T, rowIndex: number) => void;
|
|
466
469
|
/** Enable row selection (shorthand) */
|
|
467
470
|
isSelectRow?: boolean;
|
|
471
|
+
/** Array of row IDs (keyField values) that cannot be selected */
|
|
472
|
+
getNonSelectableRows?: string[];
|
|
473
|
+
/** Style for non-selectable rows */
|
|
474
|
+
nonSelectableStyle?: CSSProperties;
|
|
468
475
|
/** Export file name (alias for exportFileName) */
|
|
469
476
|
fileName?: string;
|
|
470
477
|
/** Hide export sheet button or array of fields to exclude from export */
|
|
@@ -199,6 +199,21 @@
|
|
|
199
199
|
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
200
200
|
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
201
201
|
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
202
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
203
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
204
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
205
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
206
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
207
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
208
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
209
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
210
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
211
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
212
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
213
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
214
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
215
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
216
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}
|
|
202
217
|
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}*,.dark\:bg-black:is(.dark *),.dark\:border-gray-600:is(.dark *),:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }
|
|
203
218
|
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}.dark\:bg-black:is(.dark *),.dark\:border-gray-600:is(.dark *),*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }
|
|
204
219
|
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/fieldset,legend{padding:0}.container{width:100%}@media (min-width:0px){.container{max-width:0}}@media (min-width:20rem){.container{max-width:20rem}}@media (min-width:23.4375rem){.container{max-width:23.4375rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:90rem){.container{max-width:90rem}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.form-input,.form-input::-webkit-datetime-edit,.form-input::-webkit-datetime-edit-day-field,.form-input::-webkit-datetime-edit-hour-field,.form-input::-webkit-datetime-edit-meridiem-field,.form-input::-webkit-datetime-edit-millisecond-field,.form-input::-webkit-datetime-edit-minute-field,.form-input::-webkit-datetime-edit-month-field,.form-input::-webkit-datetime-edit-second-field,.form-input::placeholder,.form-input:focus,.form-multiselect,.form-multiselect:focus,.form-select,.form-select:focus,table{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}table:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}table tr:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}select:is(.dark *){--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(243 244 246/var(--tw-divide-opacity,1))}.divide-teal-50>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(240 253 250/var(--tw-divide-opacity,1))}.bg-purple-900\/50{background-color:#581c8780}.p-0\.5{padding:.125rem}.blur,.filter,.ring,.ring-2,.shadow,.shadow-inner,.shadow-md,body{font-family:Arima Regular;font-size:14px}.hover\:bg-white\/20:hover{background-color:#fff3}.dark\:border-gray-600:is(.dark *),.focus\:ring-0:focus,.dark\:bg-black:is(.dark *){--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity,1))}.dark\:bg-boxdark:is(.dark *){--tw-bg-opacity:1;background-color:rgb(36 48 63/var(--tw-bg-opacity,1))}.dark\:bg-gray-700:is(.dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity,1))}.dark\:bg-gray-800:is(.dark *){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity,1))}.dark\:text-black:is(.dark *){--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.dark\:text-gray-100:is(.dark *){--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity,1))}.dark\:text-gray-200:is(.dark *){--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity,1))}.dark\:text-gray-300:is(.dark *){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.dark\:text-gray-400:is(.dark *){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.dark\:text-gray-500:is(.dark *){--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.dark\:text-red-400:is(.dark *){--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.dark\:text-white:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.dark\:placeholder-gray-400:is(.dark *)::placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity,1))}.dark\:ring-offset-gray-800:is(.dark *){--tw-ring-offset-color:#1f2937}.dark\:hover\:bg-blue-900:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(30 58 138/var(--tw-bg-opacity,1))}.dark\:hover\:bg-gray-600:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity,1))}.dark\:hover\:bg-gray-700:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity,1))}.dark\:focus\:ring-blue-600:focus:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgb(37 99 235/var(--tw-ring-opacity,1))}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}@font-face{font-family:ArimaRegular;src:url(library/assets/fonts/arima/arima-regular.ttf)}.container{width:100%}@media (min-width:0px){.container{max-width:0}}@media (min-width:20rem){.container{max-width:20rem}}@media (min-width:23.4375rem){.container{max-width:23.4375rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:90rem){.container{max-width:90rem}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.form-input,.form-multiselect,.form-select,.form-input:focus,.form-multiselect:focus,.form-select:focus,.form-input::placeholder,.form-input::-webkit-datetime-edit,.form-input::-webkit-datetime-edit-day-field,.form-input::-webkit-datetime-edit-hour-field,.form-input::-webkit-datetime-edit-meridiem-field,.form-input::-webkit-datetime-edit-millisecond-field,.form-input::-webkit-datetime-edit-minute-field,.form-input::-webkit-datetime-edit-month-field,.form-input::-webkit-datetime-edit-second-field,table{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}table:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}table tr:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}select:is(.dark *){--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,@keyframes pulse{50%{opacity:.5}}@keyframes spin{to{transform:rotate(1turn)}}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(243 244 246/var(--tw-divide-opacity,1))}.divide-teal-50>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(240 253 250/var(--tw-divide-opacity,1))}.bg-purple-900\/50{background-color:#581c8780}.p-0\.5{padding:.125rem}.shadow,.shadow-inner,.shadow-md,.ring,.ring-2,.blur,.filter,body{font-family:Arima Regular;font-size:14px}.menu ul{list-style:none;margin:0;padding:0}.menu li{border-bottom:1px solid #ddd;padding:10px}.menu li:last-child{border-bottom:none}.hover\:bg-white\/20:hover{background-color:#fff3}.focus\:ring-0:focus,.focus\:ring-1:focus,.dark\:border-gray-600:is(.dark *){--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity,1))}.dark\:bg-black:is(.dark *){--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity,1))}.dark\:bg-boxdark:is(.dark *){--tw-bg-opacity:1;background-color:rgb(36 48 63/var(--tw-bg-opacity,1))}.dark\:bg-gray-700:is(.dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity,1))}.dark\:bg-gray-800:is(.dark *){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity,1))}.dark\:text-black:is(.dark *){--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.dark\:text-gray-100:is(.dark *){--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity,1))}.dark\:text-gray-200:is(.dark *){--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity,1))}.dark\:text-gray-300:is(.dark *){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.dark\:text-gray-400:is(.dark *){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.dark\:text-gray-500:is(.dark *){--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.dark\:text-red-400:is(.dark *){--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.dark\:text-white:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.dark\:placeholder-gray-400:is(.dark *)::placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity,1))}.dark\:ring-offset-gray-800:is(.dark *){--tw-ring-offset-color:#1f2937}.dark\:hover\:bg-blue-900:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(30 58 138/var(--tw-bg-opacity,1))}.dark\:hover\:bg-gray-600:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity,1))}.dark\:hover\:bg-gray-700:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity,1))}.dark\:focus\:ring-blue-600:focus:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgb(37 99 235/var(--tw-ring-opacity,1))}@media (min-width:0px) and (max-width:767px){}
|