react-restyle-components 0.4.64 → 0.4.66
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/Modal/BasicModal/modal.component.d.ts +1 -1
- package/lib/src/core-components/src/components/Modal/BasicModal/modal.component.js +62 -23
- package/lib/src/core-components/src/components/Modal/BasicModal/types.d.ts +10 -0
- package/lib/src/core-components/src/components/Table/Table.js +144 -25
- package/lib/src/core-components/src/components/Table/columnResize.js +23 -15
- package/lib/src/core-components/src/components/Table/filters.d.ts +24 -12
- package/lib/src/core-components/src/components/Table/filters.js +65 -39
- 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 -2
- package/lib/src/core-components/src/tc.global.css +13 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
export type { ModalSize, ModalPosition, ModalClassNames, ModalStyles, ModalProps } from './types';
|
|
3
3
|
import type { ModalProps } from './types';
|
|
4
|
-
export declare const Modal: ({ visible, title, className, contentClassName, headerClassName, style, contentStyle, onClose, isAutoClose, autoCloseDelay, isAutoCloseOutside, size, position, showCloseButton, closeButton, overlayOpacity, overlayColor, zIndex, closeOnOverlayClick, closeOnEscape, showHeader, headerBgColor, headerTextColor, borderRadius, maxWidth, contentPadding, headerPadding, animationDuration, classNames, styles, children, }: ModalProps) => React.JSX.Element | null;
|
|
4
|
+
export declare const Modal: ({ visible, title, className, contentClassName, headerClassName, style, contentStyle, onClose, isAutoClose, autoCloseDelay, isAutoCloseOutside, size, position, showCloseButton, closeButton, isExpand, expandButton, onExpand, overlayOpacity, overlayColor, zIndex, closeOnOverlayClick, closeOnEscape, showHeader, headerBgColor, headerTextColor, borderRadius, maxWidth, contentPadding, headerPadding, animationDuration, classNames, styles, children, }: ModalProps) => React.JSX.Element | null;
|
|
@@ -2,11 +2,12 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import s from '../../../tc.module.css';
|
|
4
4
|
import { cn } from '../../../utils';
|
|
5
|
-
export const Modal = ({ visible, title = '', className = '', contentClassName = '', headerClassName = '', style = {}, contentStyle = {}, onClose, isAutoClose = false, autoCloseDelay = 1000, isAutoCloseOutside, size = 'md', position = 'center', showCloseButton = true, closeButton, overlayOpacity = 0.5, overlayColor, zIndex = 11000, closeOnOverlayClick, closeOnEscape = true, showHeader = true, headerBgColor, headerTextColor, borderRadius = '0.5rem', maxWidth, contentPadding = '1rem', headerPadding = '1rem', animationDuration = 300, classNames = {}, styles = {}, children, }) => {
|
|
5
|
+
export const Modal = ({ visible, title = '', className = '', contentClassName = '', headerClassName = '', style = {}, contentStyle = {}, onClose, isAutoClose = false, autoCloseDelay = 1000, isAutoCloseOutside, size = 'md', position = 'center', showCloseButton = true, closeButton, isExpand = false, expandButton, onExpand, overlayOpacity = 0.5, overlayColor, zIndex = 11000, closeOnOverlayClick, closeOnEscape = true, showHeader = true, headerBgColor, headerTextColor, borderRadius = '0.5rem', maxWidth, contentPadding = '1rem', headerPadding = '1rem', animationDuration = 300, classNames = {}, styles = {}, children, }) => {
|
|
6
6
|
// isAutoCloseOutside takes priority, then closeOnOverlayClick, default true
|
|
7
7
|
const shouldCloseOnOutsideClick = isAutoCloseOutside ?? closeOnOverlayClick ?? true;
|
|
8
8
|
const [showModal, setShowModal] = useState(visible);
|
|
9
9
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
10
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
10
11
|
useEffect(() => {
|
|
11
12
|
if (visible) {
|
|
12
13
|
setShowModal(true);
|
|
@@ -75,28 +76,46 @@ export const Modal = ({ visible, title = '', className = '', contentClassName =
|
|
|
75
76
|
...style,
|
|
76
77
|
transition: `opacity ${animationDuration}ms ease-in-out`,
|
|
77
78
|
opacity: isAnimating ? 1 : 0,
|
|
78
|
-
zIndex: containerZIndex,
|
|
79
|
+
zIndex: isExpanded ? 2147483647 : containerZIndex,
|
|
80
|
+
overflow: isExpanded ? 'hidden' : undefined,
|
|
79
81
|
...styles.container,
|
|
80
|
-
}, onClick: handleOverlayClick, children: _jsx("div", { className: cn(s['relative'], s['w-full'], s['my-5'], s['mx-auto'], classNames.wrapper), style:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
}, onClick: handleOverlayClick, children: _jsx("div", { className: cn(s['relative'], s['w-full'], !isExpanded && s['my-5'], !isExpanded && s['mx-auto'], classNames.wrapper), style: isExpanded
|
|
83
|
+
? {
|
|
84
|
+
maxWidth: '100vw',
|
|
85
|
+
width: '100vw',
|
|
86
|
+
height: '100vh',
|
|
87
|
+
margin: 0,
|
|
88
|
+
position: 'fixed',
|
|
89
|
+
top: 0,
|
|
90
|
+
left: 0,
|
|
91
|
+
right: 0,
|
|
92
|
+
bottom: 0,
|
|
93
|
+
zIndex: 2147483647,
|
|
94
|
+
transform: 'none',
|
|
95
|
+
opacity: isAnimating ? 1 : 0,
|
|
96
|
+
transition: `opacity ${animationDuration}ms ease-out`,
|
|
97
|
+
...styles.wrapper,
|
|
98
|
+
}
|
|
99
|
+
: {
|
|
100
|
+
maxWidth: modalMaxWidth,
|
|
101
|
+
marginLeft: size === 'full' ? '1rem' : undefined,
|
|
102
|
+
marginRight: size === 'full' ? '1rem' : undefined,
|
|
103
|
+
transform: isAnimating
|
|
104
|
+
? position === 'center'
|
|
105
|
+
? 'scale(1)'
|
|
89
106
|
: 'translateY(0)'
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
borderRadius,
|
|
107
|
+
: position === 'center'
|
|
108
|
+
? 'scale(0.95)'
|
|
109
|
+
: position === 'top'
|
|
110
|
+
? 'translateY(-20px)'
|
|
111
|
+
: 'translateY(20px)',
|
|
112
|
+
transition: `transform ${animationDuration}ms ease-out, opacity ${animationDuration}ms ease-out`,
|
|
113
|
+
opacity: isAnimating ? 1 : 0,
|
|
114
|
+
...styles.wrapper,
|
|
115
|
+
}, onClick: (e) => e.stopPropagation(), children: _jsxs("div", { className: cn(s['border-0'], s['shadow-xl'], s['relative'], s['flex'], s['flex-col'], s['bg-white'], s['outline-none'], s['focus:outline-none'], contentClassName, classNames.content), style: {
|
|
116
|
+
borderRadius: isExpanded ? 0 : borderRadius,
|
|
117
|
+
height: isExpanded ? '100%' : undefined,
|
|
118
|
+
overflow: isExpanded ? 'hidden' : undefined,
|
|
100
119
|
...contentStyle,
|
|
101
120
|
...styles.content,
|
|
102
121
|
}, children: [showHeader && (_jsxs("div", { className: cn(s['flex'], s['items-center'], s['justify-between'], s['border-b'], s['border-solid'], s['border-gray-200'], headerClassName, classNames.header), style: {
|
|
@@ -106,7 +125,25 @@ export const Modal = ({ visible, title = '', className = '', contentClassName =
|
|
|
106
125
|
borderTopRightRadius: borderRadius,
|
|
107
126
|
borderBottom: title || showCloseButton ? undefined : 'none',
|
|
108
127
|
...styles.header,
|
|
109
|
-
}, children: [title && (_jsx("h3", { className: cn(s['text-xl'], s['font-semibold'], s['m-0'], s['flex-1'], classNames.title), style: { color: headerTextColor, ...styles.title }, children: title })),
|
|
128
|
+
}, children: [title && (_jsx("h3", { className: cn(s['text-xl'], s['font-semibold'], s['m-0'], s['flex-1'], classNames.title), style: { color: headerTextColor, ...styles.title }, children: title })), isExpand && (_jsx("button", { type: "button", className: cn(s['p-1'], s['ml-4'], s['border-0'], s['bg-transparent'], s['cursor-pointer'], s['outline-none'], s['focus:outline-none'], s['transition-colors'], s['hover:bg-gray-100'], s['rounded'], classNames.expandButton), onClick: () => {
|
|
129
|
+
const next = !isExpanded;
|
|
130
|
+
setIsExpanded(next);
|
|
131
|
+
onExpand && onExpand(next);
|
|
132
|
+
}, "aria-label": isExpanded ? 'Collapse modal' : 'Expand modal', style: {
|
|
133
|
+
marginLeft: title ? '1rem' : 0,
|
|
134
|
+
...styles.expandButton,
|
|
135
|
+
}, children: expandButton || (_jsx("span", { style: {
|
|
136
|
+
color: headerTextColor || '#1f2937',
|
|
137
|
+
height: '1.5rem',
|
|
138
|
+
width: '1.5rem',
|
|
139
|
+
display: 'flex',
|
|
140
|
+
alignItems: 'center',
|
|
141
|
+
justifyContent: 'center',
|
|
142
|
+
}, children: isExpanded ? (
|
|
143
|
+
// Collapse icon
|
|
144
|
+
_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("polyline", { points: "4 14 10 14 10 20" }), _jsx("polyline", { points: "20 10 14 10 14 4" }), _jsx("line", { x1: "10", y1: "14", x2: "3", y2: "21" }), _jsx("line", { x1: "21", y1: "3", x2: "14", y2: "10" })] })) : (
|
|
145
|
+
// Expand icon
|
|
146
|
+
_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("polyline", { points: "15 3 21 3 21 9" }), _jsx("polyline", { points: "9 21 3 21 3 15" }), _jsx("line", { x1: "21", y1: "3", x2: "14", y2: "10" }), _jsx("line", { x1: "3", y1: "21", x2: "10", y2: "14" })] })) })) })), showCloseButton && (_jsx("button", { type: "button", className: cn(s['p-1'], s['ml-4'], s['border-0'], s['bg-transparent'], s['cursor-pointer'], s['outline-none'], s['focus:outline-none'], s['transition-colors'], s['hover:bg-gray-100'], s['rounded'], classNames.closeButton), onClick: handleClose, "aria-label": "Close modal", style: {
|
|
110
147
|
marginLeft: title ? '1rem' : 0,
|
|
111
148
|
marginRight: title ? 0 : 'auto',
|
|
112
149
|
...styles.closeButton,
|
|
@@ -118,8 +155,10 @@ export const Modal = ({ visible, title = '', className = '', contentClassName =
|
|
|
118
155
|
alignItems: 'center',
|
|
119
156
|
justifyContent: 'center',
|
|
120
157
|
lineHeight: 1,
|
|
121
|
-
}, children: "\u00D7" })) }))] })), _jsx("div", { className: cn(s['flex'], s['flex-col'], s['w-full'], s['h-auto'], s['overflow-y-auto'], classNames.body), style: {
|
|
158
|
+
}, children: "\u00D7" })) }))] })), _jsx("div", { className: cn(s['flex'], s['flex-col'], s['w-full'], !isExpanded && s['h-auto'], s['overflow-y-auto'], classNames.body), style: {
|
|
122
159
|
padding: contentPadding,
|
|
160
|
+
flex: isExpanded ? '1 1 0' : undefined,
|
|
161
|
+
minHeight: isExpanded ? 0 : undefined,
|
|
123
162
|
...styles.body,
|
|
124
163
|
}, children: children })] }) }) })] }));
|
|
125
164
|
};
|
|
@@ -14,6 +14,8 @@ export interface ModalClassNames {
|
|
|
14
14
|
header?: string;
|
|
15
15
|
/** Custom className for the title */
|
|
16
16
|
title?: string;
|
|
17
|
+
/** Custom className for the expand button */
|
|
18
|
+
expandButton?: string;
|
|
17
19
|
/** Custom className for the close button */
|
|
18
20
|
closeButton?: string;
|
|
19
21
|
/** Custom className for the body/children container */
|
|
@@ -32,6 +34,8 @@ export interface ModalStyles {
|
|
|
32
34
|
header?: CSSProperties;
|
|
33
35
|
/** Custom style for the title */
|
|
34
36
|
title?: CSSProperties;
|
|
37
|
+
/** Custom style for the expand button */
|
|
38
|
+
expandButton?: CSSProperties;
|
|
35
39
|
/** Custom style for the close button */
|
|
36
40
|
closeButton?: CSSProperties;
|
|
37
41
|
/** Custom style for the body/children container */
|
|
@@ -68,6 +72,12 @@ export interface ModalProps {
|
|
|
68
72
|
showCloseButton?: boolean;
|
|
69
73
|
/** Custom close button element */
|
|
70
74
|
closeButton?: React.ReactNode;
|
|
75
|
+
/** Enable expand/collapse fullscreen button (shown before close button) */
|
|
76
|
+
isExpand?: boolean;
|
|
77
|
+
/** Custom expand button element */
|
|
78
|
+
expandButton?: React.ReactNode;
|
|
79
|
+
/** Callback fired when expand/collapse button is clicked; receives the new expanded state */
|
|
80
|
+
onExpand?: (isExpanded: boolean) => void;
|
|
71
81
|
/** Overlay background opacity (0-1) */
|
|
72
82
|
overlayOpacity?: number;
|
|
73
83
|
/** Overlay background color */
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
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, TableCellContent, Checkbox, ExpandButton, ExpandedRow, ExpandedCell, TableFooter, FooterRow, FooterCell, PaginationWrapper, PaginationInfo, PaginationControls, PageButton, PageSizeInputWrapper, PageSizeInput, PageSizeDropdownIcon, PageSizeDropdown, PageSizeDropdownItem, QuickJumper, EmptyState, LoadingOverlay, LoadingSpinner, EditableCell, CellEditor, ColumnTogglePanel, ColumnToggleHeader, ColumnToggleSearch, ColumnToggleList, ColumnToggleItem, ColumnToggleDragHandle, SelectionIndicator, SelectionCount, SelectSortButton, } from './elements';
|
|
5
5
|
import { useSortState, useFilterState, usePaginationState, useRowSelection, useRowExpansion, useColumnVisibility, useTableDebounce, sortData, filterData, paginateData, getNestedValue, exportToCSV, exportToExcel, } from './hooks';
|
|
6
|
-
import { getFilterComponent } from './filters';
|
|
6
|
+
import { getFilterComponent, createCallableFilterHandle } from './filters';
|
|
7
7
|
import { Tooltip } from '../Tooltip';
|
|
8
8
|
import { LoadingAnimateSpin } from '../Loader/loader.component';
|
|
9
9
|
import { useColumnResize, getColumnStyle } from './columnResize';
|
|
@@ -54,6 +54,31 @@ const RefreshIcon = () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", st
|
|
|
54
54
|
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" })] }));
|
|
55
55
|
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" })] }));
|
|
56
56
|
const tableFilterCache = new Map();
|
|
57
|
+
/** Reads `getFilter` from TextFilter / NumberFilter / CustomFilter / DateFilter / SelectFilter factory `.props`. */
|
|
58
|
+
function getColumnFilterGetFilterCallback(filter) {
|
|
59
|
+
if (typeof filter !== 'function')
|
|
60
|
+
return undefined;
|
|
61
|
+
const props = filter.props;
|
|
62
|
+
return typeof props?.getFilter === 'function' ? props.getFilter : undefined;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* When using `filterRenderer`, the factory `filter` component does not mount, so its `getFilter`
|
|
66
|
+
* never runs. Registers the same callable imperative API as built-in filters (via table state).
|
|
67
|
+
*/
|
|
68
|
+
function FilterRendererGetFilterBridge({ getFilter, dataField, filters, handleFilterChange, }) {
|
|
69
|
+
const applyRef = useRef(() => { });
|
|
70
|
+
applyRef.current = (v) => handleFilterChange(dataField, v);
|
|
71
|
+
const getValueRef = useRef(() => filters[dataField]);
|
|
72
|
+
getValueRef.current = () => filters[dataField];
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
getFilter(createCallableFilterHandle({
|
|
75
|
+
getValue: () => getValueRef.current(),
|
|
76
|
+
setValue: (value) => applyRef.current(value),
|
|
77
|
+
clear: () => applyRef.current(null),
|
|
78
|
+
}));
|
|
79
|
+
}, [getFilter]);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
57
82
|
export const Table = forwardRef(function TableComponent(props, ref) {
|
|
58
83
|
const { id, data, columns, keyField = '_id', loading = false, loadingIndicator, isLoading = false, 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 = 'excel', exportSheetName, exportHeaderStyle, exportExtraSheets, exportSummaryRows, exportCharts, columnToggle = false, isFieldSelector = true, 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,
|
|
59
84
|
// Quick configuration props
|
|
@@ -184,8 +209,35 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
184
209
|
const [toggleDragOverColumn, setToggleDragOverColumn] = useState(null);
|
|
185
210
|
// Internal state for reordered columns (used in popup and for rendering)
|
|
186
211
|
const [reorderedColumns, setReorderedColumns] = useState(columns);
|
|
187
|
-
//
|
|
188
|
-
//
|
|
212
|
+
// A stable string fingerprint built from every serialisable column property.
|
|
213
|
+
// Using a primitive (string) as the useEffect dependency means the effect only
|
|
214
|
+
// re-runs when the column *content* meaningfully changes — not merely because
|
|
215
|
+
// the caller re-created the columns array inline on every render.
|
|
216
|
+
// When the fingerprint does change we re-run the effect and update reorderedColumns
|
|
217
|
+
// with the full column objects (picking up formatters, filters, and all functions).
|
|
218
|
+
const columnsFingerprint = useMemo(() => columns
|
|
219
|
+
.map((c) => [
|
|
220
|
+
c.dataField,
|
|
221
|
+
c.text ?? '',
|
|
222
|
+
c.headerText ?? '',
|
|
223
|
+
c.hidden ? '1' : '0',
|
|
224
|
+
c.sort ? '1' : '0',
|
|
225
|
+
String(c.width ?? ''),
|
|
226
|
+
String(c.minWidth ?? ''),
|
|
227
|
+
String(c.maxWidth ?? ''),
|
|
228
|
+
c.align ?? '',
|
|
229
|
+
c.headerAlign ?? '',
|
|
230
|
+
// editable can be boolean or function; stringify the boolean branch only
|
|
231
|
+
typeof c.editable === 'boolean' ? (c.editable ? '1' : '0') : 'fn',
|
|
232
|
+
c.pinned ?? '',
|
|
233
|
+
c.csvExport !== false ? '1' : '0',
|
|
234
|
+
c.editorType ?? '',
|
|
235
|
+
].join('|'))
|
|
236
|
+
.join('~'), [columns]);
|
|
237
|
+
// Sync reorderedColumns when columns prop changes.
|
|
238
|
+
// Depends on columnsFingerprint (a string primitive) rather than the columns
|
|
239
|
+
// array reference so that inline column arrays that get re-created on every
|
|
240
|
+
// render do not trigger an infinite setState → re-render → setState loop.
|
|
189
241
|
useEffect(() => {
|
|
190
242
|
const currentDataFields = reorderedColumns
|
|
191
243
|
.map((c) => c.dataField)
|
|
@@ -196,14 +248,12 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
196
248
|
.sort()
|
|
197
249
|
.join(',');
|
|
198
250
|
if (currentDataFields !== newDataFields) {
|
|
199
|
-
// Columns structure changed
|
|
251
|
+
// Columns structure changed — merge while preserving user-defined order
|
|
200
252
|
const newColumnMap = new Map(columns.map((c) => [c.dataField, c]));
|
|
201
253
|
const existingDataFields = new Set(reorderedColumns.map((c) => c.dataField));
|
|
202
|
-
// Keep existing order for columns that still exist, update their data
|
|
203
254
|
const preserved = reorderedColumns
|
|
204
255
|
.filter((c) => newColumnMap.has(c.dataField))
|
|
205
256
|
.map((c) => newColumnMap.get(c.dataField));
|
|
206
|
-
// Add new columns at the end
|
|
207
257
|
const added = columns.filter((c) => !existingDataFields.has(c.dataField));
|
|
208
258
|
setReorderedColumns([...preserved, ...added]);
|
|
209
259
|
// Clean up filter component cache for removed columns
|
|
@@ -215,11 +265,16 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
215
265
|
}
|
|
216
266
|
}
|
|
217
267
|
else {
|
|
218
|
-
// Same
|
|
268
|
+
// Same column structure — update all column data (including formatter,
|
|
269
|
+
// filter, csvFormatter, etc.) so that reorderable-table rendering always
|
|
270
|
+
// uses the latest column definitions.
|
|
271
|
+
// This branch is only reached when columnsFingerprint actually changed,
|
|
272
|
+
// so there is no risk of an infinite loop here.
|
|
219
273
|
const columnMap = new Map(columns.map((c) => [c.dataField, c]));
|
|
220
|
-
setReorderedColumns(reorderedColumns.map((c) => columnMap.get(c.dataField)
|
|
274
|
+
setReorderedColumns(reorderedColumns.map((c) => columnMap.get(c.dataField) ?? c));
|
|
221
275
|
}
|
|
222
|
-
|
|
276
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
277
|
+
}, [columnsFingerprint]);
|
|
223
278
|
// Close column toggle panel on outside click
|
|
224
279
|
useEffect(() => {
|
|
225
280
|
if (!columnToggleOpen)
|
|
@@ -436,6 +491,28 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
436
491
|
// Reset the flag after processing to prevent duplicate calls
|
|
437
492
|
shouldCallOnFilter.current = false;
|
|
438
493
|
}, [debouncedSearchValue, filters, page, pageSize]);
|
|
494
|
+
/** When remote filtering is used, pagination must re-send current criteria + new page/size. */
|
|
495
|
+
const getActiveRemoteFilterCallArgs = useCallback(() => {
|
|
496
|
+
const cleanFilters = {};
|
|
497
|
+
Object.keys(filters).forEach((key) => {
|
|
498
|
+
const value = filters[key];
|
|
499
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
500
|
+
cleanFilters[key] = value;
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
const hasColumnFilters = Object.keys(cleanFilters).length > 0;
|
|
504
|
+
const hasSearch = Boolean(debouncedSearchValue);
|
|
505
|
+
if (!hasColumnFilters && !hasSearch)
|
|
506
|
+
return null;
|
|
507
|
+
const filterData = { ...cleanFilters };
|
|
508
|
+
if (hasSearch) {
|
|
509
|
+
filterData.srText = debouncedSearchValue;
|
|
510
|
+
}
|
|
511
|
+
const type = hasColumnFilters
|
|
512
|
+
? 'filter'
|
|
513
|
+
: 'search';
|
|
514
|
+
return { type, filterData };
|
|
515
|
+
}, [filters, debouncedSearchValue]);
|
|
439
516
|
// Track selection count changes for animation
|
|
440
517
|
useEffect(() => {
|
|
441
518
|
const currentCount = selectedKeys.size;
|
|
@@ -577,6 +654,15 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
577
654
|
lastOnFilterCallRef.current = null;
|
|
578
655
|
// Clear the cache so remounts also start fresh
|
|
579
656
|
tableFilterCache.delete(id);
|
|
657
|
+
// Remote tables: clear-all must notify like column-filter clears. Search uses debounce and
|
|
658
|
+
// never sets shouldCallOnFilter here, so invoke onFilter immediately with empty criteria.
|
|
659
|
+
if (onFilter) {
|
|
660
|
+
prevSearchRef.current = '';
|
|
661
|
+
prevFiltersRef.current = JSON.stringify({});
|
|
662
|
+
shouldCallOnFilter.current = false;
|
|
663
|
+
filterTypeRef.current = 'filter';
|
|
664
|
+
onFilterRef.current?.('filter', {}, page + 1, pageSize);
|
|
665
|
+
}
|
|
580
666
|
}, [
|
|
581
667
|
id,
|
|
582
668
|
clearFilters,
|
|
@@ -584,6 +670,9 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
584
670
|
onFilterChange,
|
|
585
671
|
onChange,
|
|
586
672
|
resetEditingState,
|
|
673
|
+
onFilter,
|
|
674
|
+
page,
|
|
675
|
+
pageSize,
|
|
587
676
|
]);
|
|
588
677
|
// Handle page change
|
|
589
678
|
const handlePageChange = useCallback((newPage) => {
|
|
@@ -591,13 +680,24 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
591
680
|
goToPage(newPage);
|
|
592
681
|
// Pass 1-indexed page number to callbacks (user-friendly)
|
|
593
682
|
const displayPage = newPage + 1;
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
683
|
+
const filterArgs = onFilter ? getActiveRemoteFilterCallArgs() : null;
|
|
684
|
+
// With active remote criteria, only onFilter should run (avoids double fetch vs onPageSizeChange)
|
|
685
|
+
if (filterArgs) {
|
|
686
|
+
onFilterRef.current?.(filterArgs.type, filterArgs.filterData, displayPage, pageSize);
|
|
687
|
+
// Keep onChange pagination for URL/sync; avoid duplicate fetch callbacks (onPageSizeChange / onPageChange)
|
|
688
|
+
onChange?.({
|
|
689
|
+
type: 'pagination',
|
|
690
|
+
pagination: { page: displayPage, pageSize },
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
onPageSizeChange?.(displayPage, pageSize);
|
|
695
|
+
onPageChange?.(displayPage, pageSize);
|
|
696
|
+
onChange?.({
|
|
697
|
+
type: 'pagination',
|
|
698
|
+
pagination: { page: displayPage, pageSize },
|
|
699
|
+
});
|
|
700
|
+
}
|
|
601
701
|
}, [
|
|
602
702
|
goToPage,
|
|
603
703
|
pageSize,
|
|
@@ -605,24 +705,37 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
605
705
|
onPageChange,
|
|
606
706
|
onChange,
|
|
607
707
|
resetEditingState,
|
|
708
|
+
onFilter,
|
|
709
|
+
getActiveRemoteFilterCallArgs,
|
|
608
710
|
]);
|
|
609
711
|
// Handle page size change
|
|
610
712
|
const handlePageSizeChange = useCallback((newSize) => {
|
|
611
713
|
resetEditingState();
|
|
612
714
|
changePageSize(newSize);
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
715
|
+
const filterArgs = onFilter ? getActiveRemoteFilterCallArgs() : null;
|
|
716
|
+
if (filterArgs) {
|
|
717
|
+
onFilterRef.current?.(filterArgs.type, filterArgs.filterData, 1, newSize);
|
|
718
|
+
onChange?.({
|
|
719
|
+
type: 'pagination',
|
|
720
|
+
pagination: { page: 1, pageSize: newSize },
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
onPageSizeChange?.(1, newSize);
|
|
725
|
+
onPageChange?.(1, newSize);
|
|
726
|
+
onChange?.({
|
|
727
|
+
type: 'pagination',
|
|
728
|
+
pagination: { page: 1, pageSize: newSize },
|
|
729
|
+
});
|
|
730
|
+
}
|
|
620
731
|
}, [
|
|
621
732
|
changePageSize,
|
|
622
733
|
onPageSizeChange,
|
|
623
734
|
onPageChange,
|
|
624
735
|
onChange,
|
|
625
736
|
resetEditingState,
|
|
737
|
+
onFilter,
|
|
738
|
+
getActiveRemoteFilterCallArgs,
|
|
626
739
|
]);
|
|
627
740
|
// Handle row click
|
|
628
741
|
const handleRowClick = useCallback((row, rowIndex, e) => {
|
|
@@ -1064,7 +1177,7 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
1064
1177
|
? JSON.stringify(value)
|
|
1065
1178
|
: String(value)
|
|
1066
1179
|
: '';
|
|
1067
|
-
return (
|
|
1180
|
+
return (_jsx(_Fragment, { children: editorRendererCacheRef.current.get(cacheKey) }));
|
|
1068
1181
|
}
|
|
1069
1182
|
// Default editor
|
|
1070
1183
|
return (_jsx(CellEditor, { type: column.editorType === 'number' ? 'number' : 'text', value: editValue ?? '', onChange: (e) => setEditValue(e.target.value), onBlur: () => handleCellEditComplete(row, rowIndex, column), onKeyDown: (e) => {
|
|
@@ -1444,6 +1557,9 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
1444
1557
|
// Non-null reference for JSX rendering (guarded by showFilter check below)
|
|
1445
1558
|
const ResolvedFilter = FilterComponent;
|
|
1446
1559
|
const onFilter = (value) => handleFilterChange(column.dataField, value);
|
|
1560
|
+
const factoryGetFilter = column.filterRenderer
|
|
1561
|
+
? getColumnFilterGetFilterCallback(column.filter)
|
|
1562
|
+
: undefined;
|
|
1447
1563
|
// Get resized column style
|
|
1448
1564
|
const resizeStyle = resizable
|
|
1449
1565
|
? getColumnStyle(column, columnWidths, resizable, isResizing)
|
|
@@ -1568,7 +1684,10 @@ export const Table = forwardRef(function TableComponent(props, ref) {
|
|
|
1568
1684
|
display: 'flex',
|
|
1569
1685
|
alignItems: 'center',
|
|
1570
1686
|
overflow: 'hidden',
|
|
1571
|
-
}, onClick: (e) => e.stopPropagation(), children: column.filterRenderer ? (
|
|
1687
|
+
}, onClick: (e) => e.stopPropagation(), children: column.filterRenderer ? (_jsxs(_Fragment, { children: [factoryGetFilter ? (_jsx(FilterRendererGetFilterBridge, { getFilter: factoryGetFilter, dataField: column.dataField, filters: filters, handleFilterChange: handleFilterChange })) : null, column.filterRenderer(onFilter, column, {
|
|
1688
|
+
value: filters[column.dataField],
|
|
1689
|
+
onClear: () => handleFilterChange(column.dataField, null),
|
|
1690
|
+
})] })) : (_jsx("div", { onFocusCapture: () => {
|
|
1572
1691
|
focusedFilterFieldRef.current =
|
|
1573
1692
|
column.dataField;
|
|
1574
1693
|
saveFilterCache({
|
|
@@ -175,26 +175,31 @@ export const ResizeLine = styled.div `
|
|
|
175
175
|
// ============================================================================
|
|
176
176
|
// Hook: useColumnResize
|
|
177
177
|
// ============================================================================
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
// Module-level stable defaults so that destructuring in the hook never creates
|
|
179
|
+
// new object references on every render (which would re-trigger useEffect deps).
|
|
180
|
+
const EMPTY_RESIZE_CONFIG = {};
|
|
181
|
+
const EMPTY_WIDTHS = {};
|
|
182
|
+
export function useColumnResize({ columns, config = EMPTY_RESIZE_CONFIG, enabled = true, tableId, }) {
|
|
183
|
+
const { minWidth = 50, maxWidth = 800, autoFit = true, onResize, onResizeStart, onResizeEnd, defaultWidths, handleColor, handleHoverColor, handleWidth = 4, } = config;
|
|
184
|
+
// Stable reference — when the caller doesn't provide defaultWidths, always
|
|
185
|
+
// use the same empty object so that useEffect dependencies remain unchanged.
|
|
186
|
+
const stableDefaultWidths = defaultWidths || EMPTY_WIDTHS;
|
|
180
187
|
// Initialize column widths - Column-level width takes FIRST priority
|
|
181
188
|
const initialWidths = useMemo(() => {
|
|
182
189
|
const widths = {};
|
|
183
190
|
columns.forEach((col) => {
|
|
184
|
-
// Priority 1: Column-level width (user-defined in column)
|
|
185
191
|
if (typeof col.width === 'number') {
|
|
186
192
|
widths[col.dataField] = col.width;
|
|
187
193
|
}
|
|
188
194
|
else if (typeof col.width === 'string' && col.width.endsWith('px')) {
|
|
189
195
|
widths[col.dataField] = parseInt(col.width, 10);
|
|
190
196
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
widths[col.dataField] = defaultWidths[col.dataField];
|
|
197
|
+
else if (stableDefaultWidths[col.dataField]) {
|
|
198
|
+
widths[col.dataField] = stableDefaultWidths[col.dataField];
|
|
194
199
|
}
|
|
195
200
|
});
|
|
196
201
|
return widths;
|
|
197
|
-
}, [columns,
|
|
202
|
+
}, [columns, stableDefaultWidths]);
|
|
198
203
|
const [columnWidths, setColumnWidths] = useState(initialWidths);
|
|
199
204
|
const [isResizing, setIsResizing] = useState(false);
|
|
200
205
|
const [resizingColumn, setResizingColumn] = useState(null);
|
|
@@ -203,29 +208,32 @@ export function useColumnResize({ columns, config = {}, enabled = true, tableId,
|
|
|
203
208
|
const startWidthRef = useRef(0);
|
|
204
209
|
const currentColumnRef = useRef(null);
|
|
205
210
|
const headerRefMap = useRef(new Map());
|
|
206
|
-
// Update widths when columns change - Column-level width takes FIRST priority
|
|
211
|
+
// Update widths when columns change - Column-level width takes FIRST priority.
|
|
212
|
+
// Returns `prev` (same reference) when nothing was added so React skips the
|
|
213
|
+
// re-render and avoids an infinite setState → render → setState loop.
|
|
207
214
|
useEffect(() => {
|
|
208
215
|
setColumnWidths((prev) => {
|
|
216
|
+
let changed = false;
|
|
209
217
|
const newWidths = { ...prev };
|
|
210
218
|
columns.forEach((col) => {
|
|
211
|
-
// Only add if not already set (user hasn't resized it)
|
|
212
219
|
if (!(col.dataField in newWidths)) {
|
|
213
|
-
// Priority 1: Column-level width
|
|
214
220
|
if (typeof col.width === 'number') {
|
|
215
221
|
newWidths[col.dataField] = col.width;
|
|
222
|
+
changed = true;
|
|
216
223
|
}
|
|
217
224
|
else if (typeof col.width === 'string' && col.width.endsWith('px')) {
|
|
218
225
|
newWidths[col.dataField] = parseInt(col.width, 10);
|
|
226
|
+
changed = true;
|
|
219
227
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
228
|
+
else if (stableDefaultWidths[col.dataField]) {
|
|
229
|
+
newWidths[col.dataField] = stableDefaultWidths[col.dataField];
|
|
230
|
+
changed = true;
|
|
223
231
|
}
|
|
224
232
|
}
|
|
225
233
|
});
|
|
226
|
-
return newWidths;
|
|
234
|
+
return changed ? newWidths : prev;
|
|
227
235
|
});
|
|
228
|
-
}, [columns,
|
|
236
|
+
}, [columns, stableDefaultWidths]);
|
|
229
237
|
// Handle mouse move during resize
|
|
230
238
|
const handleMouseMove = useCallback((e) => {
|
|
231
239
|
if (!currentColumnRef.current)
|
|
@@ -175,6 +175,18 @@ export interface DateFilterInstance {
|
|
|
175
175
|
/** Clear the filter */
|
|
176
176
|
clear: () => void;
|
|
177
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Wraps `getFilter` instances so they are callable: `fn()`, `fn('')`, `fn(null)` clear;
|
|
180
|
+
* `fn(value)` sets. Still exposes `.value`, `.setValue`, and `.clear` like TextFilterInstance.
|
|
181
|
+
*/
|
|
182
|
+
export declare function createCallableFilterHandle({ getValue, setValue, clear, }: {
|
|
183
|
+
getValue: () => any;
|
|
184
|
+
setValue: (value: any) => void;
|
|
185
|
+
clear: () => void;
|
|
186
|
+
}): ((v?: any) => void) & {
|
|
187
|
+
setValue: (value: any) => void;
|
|
188
|
+
clear: () => void;
|
|
189
|
+
};
|
|
178
190
|
/**
|
|
179
191
|
* Text filter - can be used as a component or factory function
|
|
180
192
|
*
|
|
@@ -335,21 +347,21 @@ export interface CustomFilterInstance<T = any> {
|
|
|
335
347
|
* })
|
|
336
348
|
*
|
|
337
349
|
* @example
|
|
338
|
-
* //
|
|
339
|
-
* //
|
|
350
|
+
* // With filterRenderer, mount the same factory component as `filter` so getFilter runs.
|
|
351
|
+
* // CustomFilter’s getFilter is not called if you only render a different component in filterRenderer.
|
|
352
|
+
* const PictureFilter = NumberFilter({
|
|
353
|
+
* placeholder: 'Picture',
|
|
354
|
+
* getFilter: (f) => {
|
|
355
|
+
* pictureFilterRef.current = f;
|
|
356
|
+
* },
|
|
357
|
+
* });
|
|
340
358
|
* {
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
* placeholder: 'Picture',
|
|
345
|
-
* getFilter: (filter) => {
|
|
346
|
-
* pictureFilterRef.current = filter;
|
|
347
|
-
* },
|
|
348
|
-
* }),
|
|
349
|
-
* filterRenderer: (onFilter, column) => (
|
|
350
|
-
* <NumberFilter onFilter={onFilter} column={column} />
|
|
359
|
+
* filter: PictureFilter,
|
|
360
|
+
* filterRenderer: (onFilter, column, {value, onClear}) => (
|
|
361
|
+
* <PictureFilter column={column} value={value} onChange={onFilter} />
|
|
351
362
|
* ),
|
|
352
363
|
* }
|
|
364
|
+
* // clearAllFilter: () => pictureFilterRef.current?.clear()
|
|
353
365
|
*
|
|
354
366
|
* @example
|
|
355
367
|
* // Custom range filter
|
|
@@ -10,6 +10,33 @@ const filterSelectClass = cn(s['leading-4'], s['p-1'], s['focus:outline-none'],
|
|
|
10
10
|
const comparatorSelectClass = cn(s['leading-4'], s['p-1'], s['focus:outline-none'], s['focus:ring'], s['shadow-sm'], s['text-xs'], s['font-normal'], s['border'], s['border-gray-300'], s['rounded-md'], s['text-black'], s['bg-white'], s['cursor-pointer'], s['w-12']);
|
|
11
11
|
const dateInputClass = cn(s['leading-4'], s['p-1'], s['focus:outline-none'], s['focus:ring'], s['shadow-sm'], s['text-xs'], s['font-normal'], s['border'], s['border-gray-300'], s['rounded-md'], s['text-black'], s['bg-white'], s['flex-1'], s['transition-all']);
|
|
12
12
|
const filterContainerClass = cn(s['flex'], s['flex-row'], s['gap-1'], s['items-center'], s['w-full']);
|
|
13
|
+
/**
|
|
14
|
+
* Wraps `getFilter` instances so they are callable: `fn()`, `fn('')`, `fn(null)` clear;
|
|
15
|
+
* `fn(value)` sets. Still exposes `.value`, `.setValue`, and `.clear` like TextFilterInstance.
|
|
16
|
+
*/
|
|
17
|
+
export function createCallableFilterHandle({ getValue, setValue, clear, }) {
|
|
18
|
+
const handle = Object.assign(function callFilter(v) {
|
|
19
|
+
if (arguments.length === 0 ||
|
|
20
|
+
v === '' ||
|
|
21
|
+
(typeof v === 'string' && v.trim() === '') ||
|
|
22
|
+
v == null) {
|
|
23
|
+
clear();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
setValue(v);
|
|
27
|
+
}, {
|
|
28
|
+
setValue,
|
|
29
|
+
clear,
|
|
30
|
+
});
|
|
31
|
+
Object.defineProperty(handle, 'value', {
|
|
32
|
+
get() {
|
|
33
|
+
return getValue();
|
|
34
|
+
},
|
|
35
|
+
enumerable: true,
|
|
36
|
+
configurable: true,
|
|
37
|
+
});
|
|
38
|
+
return handle;
|
|
39
|
+
}
|
|
13
40
|
/**
|
|
14
41
|
* Internal Text filter component with options support
|
|
15
42
|
*/
|
|
@@ -57,10 +84,8 @@ const TextFilterComponent = ({ column, value, onChange, onFilter: onFilterProp,
|
|
|
57
84
|
// Provide filter instance via getFilter callback - only on mount
|
|
58
85
|
useEffect(() => {
|
|
59
86
|
if (getFilter) {
|
|
60
|
-
getFilter({
|
|
61
|
-
|
|
62
|
-
return internalValueRef.current;
|
|
63
|
-
},
|
|
87
|
+
getFilter(createCallableFilterHandle({
|
|
88
|
+
getValue: () => internalValueRef.current,
|
|
64
89
|
setValue: (newValue) => {
|
|
65
90
|
setInternalValue(newValue);
|
|
66
91
|
onChangeRef.current(newValue || null);
|
|
@@ -71,7 +96,7 @@ const TextFilterComponent = ({ column, value, onChange, onFilter: onFilterProp,
|
|
|
71
96
|
onChangeRef.current(null);
|
|
72
97
|
onFilterRef.current?.('');
|
|
73
98
|
},
|
|
74
|
-
});
|
|
99
|
+
}));
|
|
75
100
|
}
|
|
76
101
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
77
102
|
}, [getFilter]);
|
|
@@ -178,16 +203,21 @@ const NumberFilterComponent = ({ column, value, onChange, onFilter: onFilterProp
|
|
|
178
203
|
// Provide filter instance via getFilter callback - only on mount
|
|
179
204
|
useEffect(() => {
|
|
180
205
|
if (getFilter) {
|
|
181
|
-
getFilter({
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
:
|
|
186
|
-
|
|
206
|
+
getFilter(createCallableFilterHandle({
|
|
207
|
+
getValue: () => numberRef.current
|
|
208
|
+
? {
|
|
209
|
+
number: numberRef.current,
|
|
210
|
+
comparator: comparatorRef.current,
|
|
211
|
+
}
|
|
212
|
+
: null,
|
|
187
213
|
setValue: (newValue) => {
|
|
188
214
|
if (newValue !== null && newValue !== undefined) {
|
|
189
|
-
const num = typeof newValue === 'object'
|
|
190
|
-
|
|
215
|
+
const num = typeof newValue === 'object'
|
|
216
|
+
? newValue.number
|
|
217
|
+
: String(newValue);
|
|
218
|
+
const comp = typeof newValue === 'object'
|
|
219
|
+
? newValue.comparator
|
|
220
|
+
: comparatorRef.current;
|
|
191
221
|
setNumber(num);
|
|
192
222
|
setComparator(comp);
|
|
193
223
|
onChangeRef.current({ number: num, comparator: comp });
|
|
@@ -205,7 +235,7 @@ const NumberFilterComponent = ({ column, value, onChange, onFilter: onFilterProp
|
|
|
205
235
|
onChangeRef.current(null);
|
|
206
236
|
onFilterRef.current?.(null);
|
|
207
237
|
},
|
|
208
|
-
});
|
|
238
|
+
}));
|
|
209
239
|
}
|
|
210
240
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
211
241
|
}, [getFilter]);
|
|
@@ -353,8 +383,8 @@ const DateFilterComponent = ({ column, value, onChange, onFilter: onFilterProp,
|
|
|
353
383
|
// Provide filter instance via getFilter callback - only on mount
|
|
354
384
|
useEffect(() => {
|
|
355
385
|
if (getFilter) {
|
|
356
|
-
getFilter({
|
|
357
|
-
|
|
386
|
+
getFilter(createCallableFilterHandle({
|
|
387
|
+
getValue: () => {
|
|
358
388
|
const { startDate: s, endDate: e, diffFlag: d, comparator: c, } = stateRef.current;
|
|
359
389
|
return s || e
|
|
360
390
|
? { startDate: s, endDate: e, diffFlag: d, comparator: c }
|
|
@@ -384,7 +414,7 @@ const DateFilterComponent = ({ column, value, onChange, onFilter: onFilterProp,
|
|
|
384
414
|
onChangeRef.current(null);
|
|
385
415
|
onFilterRef.current?.(null);
|
|
386
416
|
},
|
|
387
|
-
});
|
|
417
|
+
}));
|
|
388
418
|
}
|
|
389
419
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
390
420
|
}, [getFilter]);
|
|
@@ -479,10 +509,8 @@ const SelectFilterComponent = ({ column, value, onChange, onFilter: onFilterProp
|
|
|
479
509
|
// Provide filter instance via getFilter callback - only on mount
|
|
480
510
|
useEffect(() => {
|
|
481
511
|
if (getFilter) {
|
|
482
|
-
getFilter({
|
|
483
|
-
|
|
484
|
-
return selectedValueRef.current || null;
|
|
485
|
-
},
|
|
512
|
+
getFilter(createCallableFilterHandle({
|
|
513
|
+
getValue: () => selectedValueRef.current || null,
|
|
486
514
|
setValue: (newValue) => {
|
|
487
515
|
setSelectedValue(newValue || '');
|
|
488
516
|
onChangeRef.current(newValue);
|
|
@@ -493,7 +521,7 @@ const SelectFilterComponent = ({ column, value, onChange, onFilter: onFilterProp
|
|
|
493
521
|
onChangeRef.current(null);
|
|
494
522
|
onFilterRef.current?.(null);
|
|
495
523
|
},
|
|
496
|
-
});
|
|
524
|
+
}));
|
|
497
525
|
}
|
|
498
526
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
499
527
|
}, [getFilter]);
|
|
@@ -564,10 +592,8 @@ const CustomFilterComponent = ({ column, value, onChange, onFilter: onFilterProp
|
|
|
564
592
|
// Provide filter instance via getFilter callback - only on mount
|
|
565
593
|
useEffect(() => {
|
|
566
594
|
if (getFilter) {
|
|
567
|
-
getFilter({
|
|
568
|
-
|
|
569
|
-
return filterValueRef.current;
|
|
570
|
-
},
|
|
595
|
+
getFilter(createCallableFilterHandle({
|
|
596
|
+
getValue: () => filterValueRef.current,
|
|
571
597
|
setValue: (newValue) => {
|
|
572
598
|
setFilterValue(newValue);
|
|
573
599
|
onChangeRef.current(newValue);
|
|
@@ -578,7 +604,7 @@ const CustomFilterComponent = ({ column, value, onChange, onFilter: onFilterProp
|
|
|
578
604
|
onChangeRef.current(null);
|
|
579
605
|
onFilterRef.current?.(null);
|
|
580
606
|
},
|
|
581
|
-
});
|
|
607
|
+
}));
|
|
582
608
|
}
|
|
583
609
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
584
610
|
}, [getFilter]);
|
|
@@ -643,21 +669,21 @@ const CustomFilterComponent = ({ column, value, onChange, onFilter: onFilterProp
|
|
|
643
669
|
* })
|
|
644
670
|
*
|
|
645
671
|
* @example
|
|
646
|
-
* //
|
|
647
|
-
* //
|
|
672
|
+
* // With filterRenderer, mount the same factory component as `filter` so getFilter runs.
|
|
673
|
+
* // CustomFilter’s getFilter is not called if you only render a different component in filterRenderer.
|
|
674
|
+
* const PictureFilter = NumberFilter({
|
|
675
|
+
* placeholder: 'Picture',
|
|
676
|
+
* getFilter: (f) => {
|
|
677
|
+
* pictureFilterRef.current = f;
|
|
678
|
+
* },
|
|
679
|
+
* });
|
|
648
680
|
* {
|
|
649
|
-
*
|
|
650
|
-
*
|
|
651
|
-
*
|
|
652
|
-
* placeholder: 'Picture',
|
|
653
|
-
* getFilter: (filter) => {
|
|
654
|
-
* pictureFilterRef.current = filter;
|
|
655
|
-
* },
|
|
656
|
-
* }),
|
|
657
|
-
* filterRenderer: (onFilter, column) => (
|
|
658
|
-
* <NumberFilter onFilter={onFilter} column={column} />
|
|
681
|
+
* filter: PictureFilter,
|
|
682
|
+
* filterRenderer: (onFilter, column, {value, onClear}) => (
|
|
683
|
+
* <PictureFilter column={column} value={value} onChange={onFilter} />
|
|
659
684
|
* ),
|
|
660
685
|
* }
|
|
686
|
+
* // clearAllFilter: () => pictureFilterRef.current?.clear()
|
|
661
687
|
*
|
|
662
688
|
* @example
|
|
663
689
|
* // Custom range filter
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { Table, default } from './Table';
|
|
2
2
|
export * from './types';
|
|
3
3
|
export { useSortState, useFilterState, usePaginationState, useRowSelection, useRowExpansion, useColumnVisibility, useTableDebounce, sortData, filterData, paginateData, getNestedValue, exportToCSV, exportToExcel, } from './hooks';
|
|
4
|
-
export { TextFilter, NumberFilter, DateFilter, SelectFilter, CustomFilter, getFilterComponent, } from './filters';
|
|
4
|
+
export { TextFilter, NumberFilter, DateFilter, SelectFilter, CustomFilter, getFilterComponent, createCallableFilterHandle, } from './filters';
|
|
5
5
|
export type { TextFilterOptions, TextFilterInstance, NumberFilterOptions, NumberFilterInstance, SelectFilterOptions, SelectFilterInstance, DateFilterOptions, DateFilterInstance, CustomFilterOptions, CustomFilterRenderProps, CustomFilterInstance, } from './filters';
|
|
6
6
|
export { useColumnResize, getColumnStyle, ResizeHandle, ResizeLine, ResizableHeaderCell, } from './columnResize';
|
|
7
7
|
export type { ColumnResizeConfig, UseColumnResizeReturn } from './columnResize';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { Table, default } from './Table';
|
|
2
2
|
export * from './types';
|
|
3
3
|
export { useSortState, useFilterState, usePaginationState, useRowSelection, useRowExpansion, useColumnVisibility, useTableDebounce, sortData, filterData, paginateData, getNestedValue, exportToCSV, exportToExcel, } from './hooks';
|
|
4
|
-
export { TextFilter, NumberFilter, DateFilter, SelectFilter, CustomFilter, getFilterComponent, } from './filters';
|
|
4
|
+
export { TextFilter, NumberFilter, DateFilter, SelectFilter, CustomFilter, getFilterComponent, createCallableFilterHandle, } from './filters';
|
|
5
5
|
export { useColumnResize, getColumnStyle, ResizeHandle, ResizeLine, ResizableHeaderCell, } from './columnResize';
|
|
6
6
|
export { useColumnReorder, DragGhost, DropIndicator, DraggableHeader, ColumnReorderGhost, ColumnDropIndicator, getReorderableHeaderProps, } from './columnReorder';
|
|
@@ -155,6 +155,16 @@ export interface FieldConfig<T = any> {
|
|
|
155
155
|
/** Field type configuration for dynamic rendering */
|
|
156
156
|
export type FieldTypeConfig<T = any> = Record<string, FieldConfig<T>>;
|
|
157
157
|
/** Column definition */
|
|
158
|
+
/**
|
|
159
|
+
* Passed as the third argument to {@link TableColumn.filterRenderer} so custom
|
|
160
|
+
* filters stay controlled with table state (including clear-all).
|
|
161
|
+
*/
|
|
162
|
+
export interface TableFilterRendererContext {
|
|
163
|
+
/** Current value for this column from the table filter state */
|
|
164
|
+
value: any;
|
|
165
|
+
/** Clear this column’s filter only */
|
|
166
|
+
onClear: () => void;
|
|
167
|
+
}
|
|
158
168
|
export interface TableColumn<T = any> {
|
|
159
169
|
/** Unique field key */
|
|
160
170
|
dataField: string;
|
|
@@ -193,8 +203,8 @@ export interface TableColumn<T = any> {
|
|
|
193
203
|
}>;
|
|
194
204
|
/** Custom filter component (alias for filter when using component) */
|
|
195
205
|
filterComponent?: React.ComponentType<TableFilterProps>;
|
|
196
|
-
/** Custom filter renderer */
|
|
197
|
-
filterRenderer?: (onFilter: (value: any) => void, column: TableColumn<T
|
|
206
|
+
/** Custom filter renderer (third arg is always passed when provided by Table; optional in signature for backward compatibility) */
|
|
207
|
+
filterRenderer?: (onFilter: (value: any) => void, column: TableColumn<T>, context?: TableFilterRendererContext) => React.ReactNode;
|
|
198
208
|
/** Filter placeholder */
|
|
199
209
|
filterPlaceholder?: string;
|
|
200
210
|
/** Whether column is editable - boolean or function returning boolean/custom editor */
|
|
@@ -69,4 +69,17 @@
|
|
|
69
69
|
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
70
70
|
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
71
71
|
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
72
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
73
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
74
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
75
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
76
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
77
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
78
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
79
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
80
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
81
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
82
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
83
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
84
|
+
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/
|
|
72
85
|
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/.\!filter,.backdrop-blur-sm,.blur,.ring,.ring-2,.shadow,.shadow-inner,body{font-family:Arima Regular;font-size:14px}.dark\:bg-black:is(.dark *),.dark\:border-gray-600:is(.dark *){--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-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*/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))}.\!visible{visibility:visible!important}.top-1\/2{top:50%}.-translate-x-1\/2,.-translate-x-1\/2,.-translate-y-1\/2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y:-50%}.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}.py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.\!filter,.blur,.ring,.ring-2,.shadow,.shadow-inner,.backdrop-blur-sm,body{font-family:Arima Regular;font-size:14px}.hover\:bg-white\/20:hover{background-color:#fff3}.dark\:border-gray-600:is(.dark *),.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))}.\!visible{visibility:visible!important}.top-1\/2{top:50%}.flex-grow,.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,.-translate-y-1\/2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y:-50%}@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}.py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.shadow,.shadow-inner,.shadow-md,.ring,.ring-2,.blur,.\!filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.backdrop-blur-sm,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,.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){}
|