react-restyle-components 0.4.49 → 0.4.51

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.
Files changed (27) hide show
  1. package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/ProgressBar.d.ts +3 -0
  2. package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/ProgressBar.js +237 -0
  3. package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/index.d.ts +2 -0
  4. package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/index.js +1 -0
  5. package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/types.d.ts +29 -0
  6. package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/types.js +1 -0
  7. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/ProgressStepper.d.ts +3 -0
  8. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/ProgressStepper.js +42 -0
  9. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/StepItem.d.ts +3 -0
  10. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/StepItem.js +349 -0
  11. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/index.d.ts +2 -0
  12. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/index.js +1 -0
  13. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/types.d.ts +75 -0
  14. package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/types.js +1 -0
  15. package/lib/src/core-components/src/components/ProgressStepper/index.d.ts +4 -0
  16. package/lib/src/core-components/src/components/ProgressStepper/index.js +2 -0
  17. package/lib/src/core-components/src/components/Table/Table.js +102 -25
  18. package/lib/src/core-components/src/components/Table/filters.d.ts +203 -10
  19. package/lib/src/core-components/src/components/Table/filters.js +319 -203
  20. package/lib/src/core-components/src/components/Table/index.d.ts +2 -2
  21. package/lib/src/core-components/src/components/Table/index.js +1 -1
  22. package/lib/src/core-components/src/components/Table/types.d.ts +13 -4
  23. package/lib/src/core-components/src/components/index.d.ts +1 -0
  24. package/lib/src/core-components/src/components/index.js +1 -0
  25. package/lib/src/core-components/src/tc.global.css +1 -252
  26. package/lib/src/core-components/src/tc.module.css +1 -1
  27. package/package.json +1 -1
@@ -1,149 +1,28 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState, useCallback, useEffect, useRef } from 'react';
4
- import { styled, css } from 'styled-components';
5
- import { tokens } from '../../utils/designTokens';
6
4
  import { useDebouncedValue } from '../../utils/hooks/useDebouncedValue';
7
- // Styled components for filters
8
- const FilterContainer = styled.div `
9
- display: flex;
10
- align-items: center;
11
- gap: 3px;
12
- `;
13
- const FilterInputBase = styled.input `
14
- width: 100%;
15
- height: 22px;
16
- padding: 0 5px;
17
- font-size: 10px;
18
- font-weight: normal;
19
- color: #000000;
20
- border: 1px solid ${tokens.outline || '#e2e8f0'};
21
- border-radius: 2px;
22
- background: white;
23
- transition: all 0.15s ease;
24
-
25
- &:hover {
26
- border-color: ${tokens.primary || '#94a3b8'};
27
- }
28
-
29
- &:focus {
30
- outline: none;
31
- border-color: ${tokens.primary || '#3b82f6'};
32
- box-shadow: 0 0 0 1px
33
- ${tokens.primary ? `${tokens.primary}20` : '#3b82f620'};
34
- }
35
-
36
- &::placeholder {
37
- color: ${tokens.onSurface ? `${tokens.onSurface}50` : '#94a3af'};
38
- font-size: 9px;
39
- }
40
- `;
41
- const FilterSelectBase = styled.select `
42
- width: 100%;
43
- height: 22px;
44
- padding: 0 5px;
45
- font-size: 10px;
46
- font-weight: normal;
47
- color: #000000;
48
- border: 1px solid ${tokens.outline || '#e2e8f0'};
49
- border-radius: 2px;
50
- background: white;
51
- cursor: pointer;
52
- transition: all 0.15s ease;
53
-
54
- &:hover {
55
- border-color: ${tokens.primary || '#94a3b8'};
56
- }
57
-
58
- &:focus {
59
- outline: none;
60
- border-color: ${tokens.primary || '#3b82f6'};
61
- box-shadow: 0 0 0 1px
62
- ${tokens.primary ? `${tokens.primary}20` : '#3b82f620'};
63
- }
64
- `;
65
- const ComparatorSelect = styled.select `
66
- width: 38px;
67
- height: 22px;
68
- padding: 0 2px;
69
- font-size: 9px;
70
- font-weight: normal;
71
- border: 1px solid ${tokens.outline || '#e2e8f0'};
72
- border-radius: 2px;
73
- background: white;
74
- cursor: pointer;
75
- flex-shrink: 0;
76
- text-align: center;
77
- transition: all 0.15s ease;
78
-
79
- &:hover {
80
- border-color: ${tokens.primary || '#94a3b8'};
81
- }
82
-
83
- &:focus {
84
- outline: none;
85
- border-color: ${tokens.primary || '#3b82f6'};
86
- }
87
- `;
88
- const DateInput = styled.input `
89
- flex: 1;
90
- height: 22px;
91
- padding: 0 4px;
92
- font-size: 10px;
93
- border: 1px solid ${tokens.outline || '#e2e8f0'};
94
- border-radius: 2px;
95
- background: white;
96
- min-width: 80px;
97
- transition: all 0.15s ease;
98
-
99
- &:hover {
100
- border-color: ${tokens.primary || '#94a3b8'};
101
- }
102
-
103
- &:focus {
104
- outline: none;
105
- border-color: ${tokens.primary || '#3b82f6'};
106
- box-shadow: 0 0 0 1px
107
- ${tokens.primary ? `${tokens.primary}20` : '#3b82f620'};
108
- }
109
- `;
110
- const ToggleButton = styled.button `
111
- height: 22px;
112
- padding: 0 5px;
113
- font-size: 9px;
114
- font-weight: normal;
115
- border: 1px solid ${tokens.outline || '#e2e8f0'};
116
- border-radius: 2px;
117
- cursor: pointer;
118
- transition: all 0.15s ease;
119
- white-space: nowrap;
120
-
121
- ${({ $active }) => $active
122
- ? css `
123
- background: ${tokens.primary || '#3b82f6'};
124
- color: white;
125
- border-color: ${tokens.primary || '#3b82f6'};
126
- `
127
- : css `
128
- background: white;
129
- color: ${tokens.onSurface || '#374151'};
130
-
131
- &:hover {
132
- background: #f8fafc;
133
- border-color: ${tokens.primary || '#94a3b8'};
134
- }
135
- `}
136
- `;
5
+ import s from '../../tc.module.css';
6
+ import { cn } from '../../utils';
7
+ // Tailwind CSS class helpers for filter components
8
+ const filterInputClass = 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['w-full'], s['bg-white'], s['placeholder-gray-400'], s['transition-all']);
9
+ const filterSelectClass = 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['w-full'], s['bg-white'], s['cursor-pointer'], s['transition-all']);
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
+ 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
+ const filterContainerClass = cn(s['flex'], s['flex-row'], s['gap-1'], s['items-center'], s['w-full']);
137
13
  /**
138
14
  * Internal Text filter component with options support
139
15
  */
140
- const TextFilterComponent = ({ column, value, onChange, options }) => {
141
- const { placeholder, className, style, defaultValue, delay = 500, getFilter, onFilter, id, disabled, } = options || {};
16
+ const TextFilterComponent = ({ column, value, onChange, onFilter: onFilterProp, options }) => {
17
+ const { placeholder, className, style, defaultValue, delay = 500, getFilter, onFilter: onFilterOption, id, disabled, } = options || {};
18
+ // Support both onChange and onFilter props (onFilter is alias for onChange)
19
+ const onChangeCallback = onChange || onFilterProp || (() => { });
20
+ const onFilter = onFilterOption;
142
21
  // Local state for immediate input updates (maintains focus)
143
22
  const [internalValue, setInternalValue] = useState(value || defaultValue || '');
144
23
  const internalValueRef = useRef(internalValue);
145
24
  const inputRef = useRef(null);
146
- const onChangeRef = useRef(onChange);
25
+ const onChangeRef = useRef(onChangeCallback);
147
26
  const onFilterRef = useRef(onFilter);
148
27
  // Track if the last change was from user input (internal) vs external (e.g., clear all)
149
28
  const lastInternalValueRef = useRef(internalValue);
@@ -152,9 +31,9 @@ const TextFilterComponent = ({ column, value, onChange, options }) => {
152
31
  internalValueRef.current = internalValue;
153
32
  }, [internalValue]);
154
33
  useEffect(() => {
155
- onChangeRef.current = onChange;
34
+ onChangeRef.current = onChangeCallback;
156
35
  onFilterRef.current = onFilter;
157
- }, [onChange, onFilter]);
36
+ }, [onChangeCallback, onFilter]);
158
37
  // Sync internal value when external value changes (e.g., from clear all filters)
159
38
  // Only sync when external value differs from what we last sent to parent
160
39
  useEffect(() => {
@@ -220,19 +99,7 @@ const TextFilterComponent = ({ column, value, onChange, options }) => {
220
99
  });
221
100
  }
222
101
  });
223
- const inputStyle = {
224
- fontWeight: 400,
225
- ...style,
226
- };
227
- // If custom className is provided, use plain input to allow full CSS control
228
- if (className) {
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: {
230
- width: '100%',
231
- fontWeight: 400,
232
- ...style,
233
- }, disabled: disabled }));
234
- }
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 }));
102
+ 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 || filterInputClass, style: style, disabled: disabled }));
236
103
  };
237
104
  export function TextFilter(optionsOrProps) {
238
105
  // Check if it's being used as a factory function (options object without column/value/onChange)
@@ -243,6 +110,9 @@ export function TextFilter(optionsOrProps) {
243
110
  const options = optionsOrProps;
244
111
  const FilterWithOptions = (props) => (_jsx(TextFilterComponent, { ...props, options: options }));
245
112
  FilterWithOptions.displayName = 'TextFilter';
113
+ // Attach options as props for backwards compatibility
114
+ // This allows accessing: column.filter.props.getFilter
115
+ FilterWithOptions.props = options;
246
116
  return FilterWithOptions;
247
117
  }
248
118
  // Direct component usage
@@ -252,15 +122,18 @@ export function TextFilter(optionsOrProps) {
252
122
  /**
253
123
  * Internal Number filter component with options support
254
124
  */
255
- const NumberFilterComponent = ({ column, value, onChange, options }) => {
256
- const { placeholder, className, style, defaultValue, delay = 500, defaultComparator = '=', allowDecimal = true, getFilter, onFilter, id, disabled, hideComparator, comparators = ['=', '!=', '>', '>=', '<', '<='], } = options || {};
125
+ const NumberFilterComponent = ({ column, value, onChange, onFilter: onFilterProp, options }) => {
126
+ const { placeholder, className, style, defaultValue, delay = 500, defaultComparator = '=', allowDecimal = true, getFilter, onFilter: onFilterOption, id, disabled, hideComparator, comparators = ['=', '!=', '>', '>=', '<', '<='], } = options || {};
127
+ // Support both onChange and onFilter props (onFilter is alias for onChange)
128
+ const onChangeCallback = onChange || onFilterProp || (() => { });
129
+ const onFilter = onFilterOption;
257
130
  // Local state for immediate input updates (maintains focus)
258
131
  const [number, setNumber] = useState(value?.number || defaultValue?.number || '');
259
132
  const [comparator, setComparator] = useState(value?.comparator || defaultValue?.comparator || defaultComparator);
260
133
  const numberRef = useRef(number);
261
134
  const comparatorRef = useRef(comparator);
262
135
  const inputRef = useRef(null);
263
- const onChangeRef = useRef(onChange);
136
+ const onChangeRef = useRef(onChangeCallback);
264
137
  const onFilterRef = useRef(onFilter);
265
138
  // Track last value sent to parent to prevent sync loops
266
139
  const lastNumberRef = useRef(number);
@@ -271,9 +144,9 @@ const NumberFilterComponent = ({ column, value, onChange, options }) => {
271
144
  comparatorRef.current = comparator;
272
145
  }, [number, comparator]);
273
146
  useEffect(() => {
274
- onChangeRef.current = onChange;
147
+ onChangeRef.current = onChangeCallback;
275
148
  onFilterRef.current = onFilter;
276
- }, [onChange, onFilter]);
149
+ }, [onChangeCallback, onFilter]);
277
150
  // Sync internal value when external value changes (e.g., from clear all filters)
278
151
  useEffect(() => {
279
152
  const externalNumber = value?.number || '';
@@ -299,7 +172,8 @@ const NumberFilterComponent = ({ column, value, onChange, options }) => {
299
172
  ? { number: debouncedNumber, comparator: comparatorRef.current }
300
173
  : null;
301
174
  onChangeRef.current(newValue);
302
- onFilterRef.current?.(newValue);
175
+ // onFilter passes string value directly
176
+ onFilterRef.current?.(debouncedNumber || null);
303
177
  }, [debouncedNumber]);
304
178
  // Provide filter instance via getFilter callback - only on mount
305
179
  useEffect(() => {
@@ -311,11 +185,13 @@ const NumberFilterComponent = ({ column, value, onChange, options }) => {
311
185
  : null;
312
186
  },
313
187
  setValue: (newValue) => {
314
- if (newValue) {
315
- setNumber(newValue.number);
316
- setComparator(newValue.comparator);
317
- onChangeRef.current(newValue);
318
- onFilterRef.current?.(newValue);
188
+ if (newValue !== null && newValue !== undefined) {
189
+ const num = typeof newValue === 'object' ? newValue.number : String(newValue);
190
+ const comp = typeof newValue === 'object' ? newValue.comparator : comparatorRef.current;
191
+ setNumber(num);
192
+ setComparator(comp);
193
+ onChangeRef.current({ number: num, comparator: comp });
194
+ onFilterRef.current?.(num);
319
195
  }
320
196
  else {
321
197
  setNumber('');
@@ -356,9 +232,8 @@ const NumberFilterComponent = ({ column, value, onChange, options }) => {
356
232
  const handleComparatorChange = useCallback((newComparator) => {
357
233
  setComparator(newComparator);
358
234
  if (number) {
359
- const newValue = { number, comparator: newComparator };
360
- onChangeRef.current(newValue);
361
- onFilterRef.current?.(newValue);
235
+ onChangeRef.current({ number, comparator: newComparator });
236
+ onFilterRef.current?.(number);
362
237
  }
363
238
  }, [number]);
364
239
  const comparatorSymbols = {
@@ -369,29 +244,19 @@ const NumberFilterComponent = ({ column, value, onChange, options }) => {
369
244
  '<': '<',
370
245
  '<=': '≤',
371
246
  };
372
- const inputStyle = {
373
- flex: 1,
374
- fontWeight: 400,
375
- ...style,
376
- };
377
- const inputProps = {
378
- type: 'text',
379
- id,
380
- 'data-filter-field': column.dataField,
381
- value: number,
382
- onChange: (e) => {
383
- const val = e.target.value;
384
- const pattern = allowDecimal ? /^[0-9.,]*$/ : /^[0-9]*$/;
385
- if (pattern.test(val)) {
386
- setNumber(val);
387
- }
388
- },
389
- onFocus: handleFocus,
390
- onBlur: handleBlur,
391
- placeholder: placeholder || column.filterPlaceholder || 'Number...',
392
- disabled,
393
- };
394
- return (_jsxs(FilterContainer, { children: [!hideComparator && (_jsx(ComparatorSelect, { value: comparator, onChange: (e) => handleComparatorChange(e.target.value), disabled: disabled, children: comparators.map((comp) => (_jsx("option", { value: comp, children: comparatorSymbols[comp] || comp }, comp))) })), className ? (_jsx("input", { ref: inputRef, ...inputProps, className: className, style: { flex: 1, width: '100%', fontWeight: 400, ...style } })) : (_jsx(FilterInputBase, { ref: inputRef, ...inputProps, style: inputStyle }))] }));
247
+ // Resolve placeholder: own options > column.filter.props.placeholder > column.filterPlaceholder > default
248
+ const resolvedPlaceholder = placeholder ||
249
+ column?.filter?.props?.placeholder ||
250
+ column.filterPlaceholder ||
251
+ 'Number...';
252
+ const handleInputChange = useCallback((e) => {
253
+ const val = e.target.value;
254
+ const pattern = allowDecimal ? /^[0-9.,]*$/ : /^[0-9]*$/;
255
+ if (pattern.test(val)) {
256
+ setNumber(val);
257
+ }
258
+ }, [allowDecimal]);
259
+ return (_jsxs("div", { className: filterContainerClass, children: [!hideComparator && (_jsx("select", { value: comparator, onChange: (e) => handleComparatorChange(e.target.value), disabled: disabled, className: className || comparatorSelectClass, children: comparators.map((comp) => (_jsx("option", { value: comp, children: comparatorSymbols[comp] || comp }, comp))) })), _jsx("input", { ref: inputRef, type: "text", id: id, "data-filter-field": column.dataField, value: number, onChange: handleInputChange, onFocus: handleFocus, onBlur: handleBlur, placeholder: resolvedPlaceholder, className: className || filterInputClass, style: style, disabled: disabled })] }));
395
260
  };
396
261
  export function NumberFilter(optionsOrProps) {
397
262
  if (!('column' in optionsOrProps) &&
@@ -400,6 +265,8 @@ export function NumberFilter(optionsOrProps) {
400
265
  const options = optionsOrProps;
401
266
  const FilterWithOptions = (props) => (_jsx(NumberFilterComponent, { ...props, options: options }));
402
267
  FilterWithOptions.displayName = 'NumberFilter';
268
+ // Attach options as props for backwards compatibility
269
+ FilterWithOptions.props = options;
403
270
  return FilterWithOptions;
404
271
  }
405
272
  const props = optionsOrProps;
@@ -408,15 +275,18 @@ export function NumberFilter(optionsOrProps) {
408
275
  /**
409
276
  * Internal Date filter component with options support
410
277
  */
411
- const DateFilterComponent = ({ column, value, onChange, options }) => {
412
- const { className, style, defaultValue, defaultComparator = '=', defaultRangeMode = false, getFilter, onFilter, id, disabled, minDate, maxDate, } = options || {};
278
+ const DateFilterComponent = ({ column, value, onChange, onFilter: onFilterProp, options }) => {
279
+ const { className, style, defaultValue, defaultComparator = '=', defaultRangeMode = false, getFilter, onFilter: onFilterOption, id, disabled, minDate, maxDate, } = options || {};
280
+ // Support both onChange and onFilter props (onFilter is alias for onChange)
281
+ const handleChange = onChange || onFilterProp || (() => { });
282
+ const onFilter = onFilterOption;
413
283
  // Local state for immediate updates
414
284
  const [startDate, setStartDate] = useState(value?.startDate || defaultValue?.startDate || '');
415
285
  const [endDate, setEndDate] = useState(value?.endDate || defaultValue?.endDate || '');
416
286
  const [diffFlag, setDiffFlag] = useState(value?.diffFlag ?? defaultValue?.diffFlag ?? defaultRangeMode);
417
287
  const [comparator, setComparator] = useState(value?.comparator || defaultValue?.comparator || defaultComparator);
418
288
  const stateRef = useRef({ startDate, endDate, diffFlag, comparator });
419
- const onChangeRef = useRef(onChange);
289
+ const onChangeRef = useRef(handleChange);
420
290
  const onFilterRef = useRef(onFilter);
421
291
  // Track last values sent to parent to prevent sync loops
422
292
  const lastValuesRef = useRef({
@@ -430,9 +300,9 @@ const DateFilterComponent = ({ column, value, onChange, options }) => {
430
300
  stateRef.current = { startDate, endDate, diffFlag, comparator };
431
301
  }, [startDate, endDate, diffFlag, comparator]);
432
302
  useEffect(() => {
433
- onChangeRef.current = onChange;
303
+ onChangeRef.current = handleChange;
434
304
  onFilterRef.current = onFilter;
435
- }, [onChange, onFilter]);
305
+ }, [handleChange, onFilter]);
436
306
  // Sync internal value when external value changes (e.g., from clear all filters)
437
307
  useEffect(() => {
438
308
  const externalStartDate = value?.startDate || '';
@@ -531,7 +401,25 @@ const DateFilterComponent = ({ column, value, onChange, options }) => {
531
401
  const handleComparatorChange = useCallback((e) => {
532
402
  setComparator(e.target.value);
533
403
  }, []);
534
- return (_jsxs(FilterContainer, { className: className, style: style, children: [_jsx(ToggleButton, { "$active": diffFlag, onClick: handleDiffFlagChange, title: "Date range mode", disabled: disabled, children: diffFlag ? 'Range' : 'Single' }), !diffFlag && (_jsxs(ComparatorSelect, { value: comparator, onChange: handleComparatorChange, disabled: disabled, style: { fontWeight: 400 }, children: [_jsx("option", { value: "=", children: "=" }), _jsx("option", { value: ">=", children: "\u2265" }), _jsx("option", { value: "<", children: "<" })] })), _jsx(DateInput, { type: "date", id: id, value: startDate, onChange: handleStartDateChange, disabled: disabled, min: minDate, max: maxDate, style: { fontWeight: 400 } }), diffFlag && (_jsxs(_Fragment, { children: [_jsx("span", { style: { color: '#6b7280', fontSize: 12, fontWeight: 400 }, children: "to" }), _jsx(DateInput, { type: "date", value: endDate, onChange: handleEndDateChange, disabled: disabled, min: minDate, max: maxDate, style: { fontWeight: 400 } })] }))] }));
404
+ // Show/hide the expanded filter UI - initially collapsed (search icon only)
405
+ const [isExpanded, setIsExpanded] = useState(false);
406
+ // Collapsed view: show column text + search icon
407
+ if (!isExpanded) {
408
+ return (_jsxs("div", { className: cn(s['flex'], s['flex-row'], s['gap-2'], s['items-center'], s['cursor-pointer']), onClick: () => setIsExpanded(true), title: "Click to open date filter", children: [_jsx("span", { className: cn(s['text-white'], s['text-xs'], s['font-normal']), children: column.text }), _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", style: { color: 'white', cursor: 'pointer', flexShrink: 0 }, children: [_jsx("circle", { cx: "11", cy: "11", r: "8" }), _jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })] })] }));
409
+ }
410
+ // Expanded view: full date filter UI
411
+ return (_jsxs("div", { className: cn(filterContainerClass, className), style: style, children: [_jsx("button", { type: "button", onClick: handleDiffFlagChange, title: "Date range mode", disabled: disabled, className: cn(s['text-xs'], s['font-normal'], s['px-2'], s['py-1'], s['border'], s['rounded-md'], s['cursor-pointer'], s['whitespace-nowrap'], s['transition-all'], s['focus:outline-none'], diffFlag
412
+ ? cn(s['bg-blue-500'], s['text-white'], s['border-blue-500'])
413
+ : cn(s['bg-white'], s['text-black'], s['border-gray-300'], s['hover:bg-gray-50'])), children: diffFlag ? 'Range' : 'Single' }), !diffFlag && (_jsxs("select", { value: comparator, onChange: handleComparatorChange, disabled: disabled, className: comparatorSelectClass, children: [_jsx("option", { value: "=", children: "=" }), _jsx("option", { value: ">=", children: "\u2265" }), _jsx("option", { value: "<", children: "<" })] })), _jsx("input", { type: "date", id: id, value: startDate, onChange: handleStartDateChange, disabled: disabled, min: minDate, max: maxDate, className: dateInputClass }), diffFlag && (_jsxs(_Fragment, { children: [_jsx("span", { className: cn(s['text-xs'], s['text-gray-500'], s['font-normal']), children: "to" }), _jsx("input", { type: "date", value: endDate, onChange: handleEndDateChange, disabled: disabled, min: minDate, max: maxDate, className: dateInputClass })] })), _jsx("button", { type: "button", onClick: () => {
414
+ setIsExpanded(false);
415
+ // Clear filter when collapsing
416
+ setStartDate('');
417
+ setEndDate('');
418
+ setDiffFlag(defaultRangeMode);
419
+ setComparator(defaultComparator);
420
+ onChangeRef.current(null);
421
+ onFilterRef.current?.(null);
422
+ }, title: "Close date filter", className: cn(s['text-xs'], s['p-1'], s['cursor-pointer'], s['text-gray-400'], s['hover:text-red-500'], s['focus:outline-none'], s['transition-all']), children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), _jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })] }));
535
423
  };
536
424
  export function DateFilter(optionsOrProps) {
537
425
  if (!('column' in optionsOrProps) &&
@@ -540,6 +428,8 @@ export function DateFilter(optionsOrProps) {
540
428
  const options = optionsOrProps;
541
429
  const FilterWithOptions = (props) => (_jsx(DateFilterComponent, { ...props, options: options }));
542
430
  FilterWithOptions.displayName = 'DateFilter';
431
+ // Attach options as props for backwards compatibility
432
+ FilterWithOptions.props = options;
543
433
  return FilterWithOptions;
544
434
  }
545
435
  const props = optionsOrProps;
@@ -548,12 +438,15 @@ export function DateFilter(optionsOrProps) {
548
438
  /**
549
439
  * Internal Select filter component with options support
550
440
  */
551
- const SelectFilterComponent = ({ column, value, onChange, options }) => {
552
- const { placeholder = 'All', className, style, defaultValue, delay = 300, options: customOptions, getFilter, onFilter, id, disabled, } = options || {};
441
+ const SelectFilterComponent = ({ column, value, onChange, onFilter: onFilterProp, options }) => {
442
+ const { placeholder = 'All', className, style, defaultValue, delay = 300, options: customOptions, getFilter, onFilter: onFilterOption, id, disabled, } = options || {};
443
+ // Support both onChange and onFilter props (onFilter is alias for onChange)
444
+ const onChangeCallback = onChange || onFilterProp || (() => { });
445
+ const onFilter = onFilterOption;
553
446
  // Local state for immediate updates
554
447
  const [selectedValue, setSelectedValue] = useState(value || defaultValue || '');
555
448
  const selectedValueRef = useRef(selectedValue);
556
- const onChangeRef = useRef(onChange);
449
+ const onChangeRef = useRef(onChangeCallback);
557
450
  const onFilterRef = useRef(onFilter);
558
451
  // Track last value sent to parent to prevent sync loops
559
452
  const lastValueRef = useRef(selectedValue);
@@ -562,9 +455,9 @@ const SelectFilterComponent = ({ column, value, onChange, options }) => {
562
455
  selectedValueRef.current = selectedValue;
563
456
  }, [selectedValue]);
564
457
  useEffect(() => {
565
- onChangeRef.current = onChange;
458
+ onChangeRef.current = onChangeCallback;
566
459
  onFilterRef.current = onFilter;
567
- }, [onChange, onFilter]);
460
+ }, [onChangeCallback, onFilter]);
568
461
  // Sync internal value when external value changes (e.g., from clear all filters)
569
462
  useEffect(() => {
570
463
  const externalValue = value || '';
@@ -611,11 +504,7 @@ const SelectFilterComponent = ({ column, value, onChange, options }) => {
611
504
  // Use custom options if provided, otherwise fall back to column.filterOptions
612
505
  const selectOptions = customOptions || column.filterOptions || [];
613
506
  const selectContent = (_jsxs(_Fragment, { children: [_jsx("option", { value: "", children: placeholder }), selectOptions.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }));
614
- // If custom className is provided, use plain select to allow full CSS control
615
- if (className) {
616
- return (_jsx("select", { id: id, value: selectedValue, onChange: handleChange, className: className, style: { width: '100%', fontWeight: 400, ...style }, disabled: disabled, children: selectContent }));
617
- }
618
- return (_jsx(FilterSelectBase, { id: id, value: selectedValue, onChange: handleChange, style: { fontWeight: 400, ...style }, disabled: disabled, children: selectContent }));
507
+ return (_jsx("select", { id: id, value: selectedValue, onChange: handleChange, className: className || filterSelectClass, style: style, disabled: disabled, children: selectContent }));
619
508
  };
620
509
  export function SelectFilter(optionsOrProps) {
621
510
  if (!('column' in optionsOrProps) &&
@@ -624,11 +513,234 @@ export function SelectFilter(optionsOrProps) {
624
513
  const options = optionsOrProps;
625
514
  const FilterWithOptions = (props) => (_jsx(SelectFilterComponent, { ...props, options: options }));
626
515
  FilterWithOptions.displayName = 'SelectFilter';
516
+ // Attach options as props for backwards compatibility
517
+ FilterWithOptions.props = options;
627
518
  return FilterWithOptions;
628
519
  }
629
520
  const props = optionsOrProps;
630
521
  return _jsx(SelectFilterComponent, { ...props });
631
522
  }
523
+ /**
524
+ * Internal Custom filter component with options support
525
+ */
526
+ const CustomFilterComponent = ({ column, value, onChange, onFilter: onFilterProp, options }) => {
527
+ const { render, placeholder, className, style, defaultValue, delay = 300, getFilter, onFilter: onFilterOption, id, disabled, } = options;
528
+ // Support both onChange and onFilter props (onFilter is alias for onChange)
529
+ const onChangeCallback = onChange || onFilterProp || (() => { });
530
+ const onFilter = onFilterOption;
531
+ // Local state for immediate updates
532
+ const [filterValue, setFilterValue] = useState(value ?? defaultValue ?? null);
533
+ const filterValueRef = useRef(filterValue);
534
+ const onChangeRef = useRef(onChangeCallback);
535
+ const onFilterRef = useRef(onFilter);
536
+ // Track last value sent to parent to prevent sync loops
537
+ const lastValueRef = useRef(filterValue);
538
+ // Keep refs in sync with state
539
+ useEffect(() => {
540
+ filterValueRef.current = filterValue;
541
+ }, [filterValue]);
542
+ useEffect(() => {
543
+ onChangeRef.current = onChangeCallback;
544
+ onFilterRef.current = onFilter;
545
+ }, [onChangeCallback, onFilter]);
546
+ // Sync internal value when external value changes (e.g., from clear all filters)
547
+ useEffect(() => {
548
+ const externalValue = value ?? null;
549
+ // Only sync if different from what we last sent to parent
550
+ if (JSON.stringify(externalValue) !== JSON.stringify(lastValueRef.current)) {
551
+ setFilterValue(externalValue);
552
+ lastValueRef.current = externalValue;
553
+ }
554
+ }, [value]);
555
+ // Debounce the filter value
556
+ const [debouncedValue] = useDebouncedValue(filterValue, { wait: delay });
557
+ // Propagate debounced value to parent
558
+ useEffect(() => {
559
+ // Update ref to track what we're sending to parent
560
+ lastValueRef.current = debouncedValue;
561
+ onChangeRef.current(debouncedValue);
562
+ onFilterRef.current?.(debouncedValue);
563
+ }, [debouncedValue]);
564
+ // Provide filter instance via getFilter callback - only on mount
565
+ useEffect(() => {
566
+ if (getFilter) {
567
+ getFilter({
568
+ get value() {
569
+ return filterValueRef.current;
570
+ },
571
+ setValue: (newValue) => {
572
+ setFilterValue(newValue);
573
+ onChangeRef.current(newValue);
574
+ onFilterRef.current?.(newValue);
575
+ },
576
+ clear: () => {
577
+ setFilterValue(null);
578
+ onChangeRef.current(null);
579
+ onFilterRef.current?.(null);
580
+ },
581
+ });
582
+ }
583
+ // eslint-disable-next-line react-hooks/exhaustive-deps
584
+ }, [getFilter]);
585
+ // Handler for value changes from custom render
586
+ const handleChange = useCallback((newValue) => {
587
+ setFilterValue(newValue);
588
+ }, []);
589
+ // Clear handler
590
+ const handleClear = useCallback(() => {
591
+ setFilterValue(null);
592
+ }, []);
593
+ // Build render props
594
+ const renderProps = {
595
+ value: filterValue,
596
+ onChange: handleChange,
597
+ column,
598
+ clear: handleClear,
599
+ placeholder,
600
+ className,
601
+ style,
602
+ id,
603
+ disabled,
604
+ };
605
+ // If no render function provided, render a default text input
606
+ // This allows using CustomFilter with column.filterRenderer
607
+ if (!render) {
608
+ return (_jsx("input", { type: "text", id: id, "data-filter-field": column.dataField, value: filterValue || '', onChange: (e) => handleChange(e.target.value || null), placeholder: placeholder || column.filterPlaceholder || `Filter ${column.text}...`, className: className || filterInputClass, style: style, disabled: disabled }));
609
+ }
610
+ // Render the custom filter UI
611
+ return _jsx(_Fragment, { children: render(renderProps) });
612
+ };
613
+ /**
614
+ * Custom filter - allows rendering any custom filter component
615
+ *
616
+ * @example
617
+ * // Basic custom filter with input
618
+ * filter: CustomFilter({
619
+ * render: ({ value, onChange }) => (
620
+ * <input
621
+ * type="text"
622
+ * value={value || ''}
623
+ * onChange={(e) => onChange(e.target.value || null)}
624
+ * placeholder="Custom filter..."
625
+ * />
626
+ * ),
627
+ * })
628
+ *
629
+ * @example
630
+ * // Using render props (placeholder, className, etc.)
631
+ * filter: CustomFilter({
632
+ * placeholder: 'Search...',
633
+ * className: 'my-custom-input',
634
+ * render: ({ value, onChange, placeholder, className }) => (
635
+ * <input
636
+ * type="text"
637
+ * value={value || ''}
638
+ * onChange={(e) => onChange(e.target.value || null)}
639
+ * placeholder={placeholder}
640
+ * className={className}
641
+ * />
642
+ * ),
643
+ * })
644
+ *
645
+ * @example
646
+ * // Using with column.filterRenderer (simple options pattern)
647
+ * // This allows using existing filter components with custom configuration
648
+ * {
649
+ * dataField: 'picture',
650
+ * text: 'Picture',
651
+ * filter: CustomFilter({
652
+ * placeholder: 'Picture',
653
+ * getFilter: (filter) => {
654
+ * pictureFilterRef.current = filter;
655
+ * },
656
+ * }),
657
+ * filterRenderer: (onFilter, column) => (
658
+ * <NumberFilter onFilter={onFilter} column={column} />
659
+ * ),
660
+ * }
661
+ *
662
+ * @example
663
+ * // Custom range filter
664
+ * filter: CustomFilter({
665
+ * render: ({ value, onChange }) => (
666
+ * <div style={{ display: 'flex', gap: 4 }}>
667
+ * <input
668
+ * type="number"
669
+ * placeholder="Min"
670
+ * value={value?.min || ''}
671
+ * onChange={(e) => onChange({ ...value, min: e.target.value })}
672
+ * />
673
+ * <input
674
+ * type="number"
675
+ * placeholder="Max"
676
+ * value={value?.max || ''}
677
+ * onChange={(e) => onChange({ ...value, max: e.target.value })}
678
+ * />
679
+ * </div>
680
+ * ),
681
+ * filterFunction: (cellValue, filterValue) => {
682
+ * if (!filterValue) return true;
683
+ * const { min, max } = filterValue;
684
+ * const num = Number(cellValue);
685
+ * if (min && num < Number(min)) return false;
686
+ * if (max && num > Number(max)) return false;
687
+ * return true;
688
+ * },
689
+ * })
690
+ *
691
+ * @example
692
+ * // Custom multi-select filter with checkboxes
693
+ * filter: CustomFilter({
694
+ * render: ({ value, onChange }) => {
695
+ * const selected = value || [];
696
+ * const options = ['Active', 'Inactive', 'Pending'];
697
+ * return (
698
+ * <div>
699
+ * {options.map(opt => (
700
+ * <label key={opt}>
701
+ * <input
702
+ * type="checkbox"
703
+ * checked={selected.includes(opt)}
704
+ * onChange={(e) => {
705
+ * if (e.target.checked) {
706
+ * onChange([...selected, opt]);
707
+ * } else {
708
+ * onChange(selected.filter(s => s !== opt));
709
+ * }
710
+ * }}
711
+ * />
712
+ * {opt}
713
+ * </label>
714
+ * ))}
715
+ * </div>
716
+ * );
717
+ * },
718
+ * filterFunction: (cellValue, filterValue) => {
719
+ * if (!filterValue?.length) return true;
720
+ * return filterValue.includes(cellValue);
721
+ * },
722
+ * })
723
+ *
724
+ * @example
725
+ * // External control with getFilter
726
+ * filter: CustomFilter({
727
+ * placeholder: 'Custom...',
728
+ * getFilter: (filter) => {
729
+ * customFilterRef.current = filter;
730
+ * // filter.value - get current value
731
+ * // filter.setValue(newValue) - set value programmatically
732
+ * // filter.clear() - clear the filter
733
+ * },
734
+ * })
735
+ */
736
+ export function CustomFilter(options) {
737
+ const FilterWithOptions = (props) => (_jsx(CustomFilterComponent, { ...props, options: options }));
738
+ FilterWithOptions.displayName = 'CustomFilter';
739
+ // Attach options as props for backwards compatibility with filter-comp.tsx pattern
740
+ // This allows accessing: column.filter.props.getFilter
741
+ FilterWithOptions.props = options;
742
+ return FilterWithOptions;
743
+ }
632
744
  /**
633
745
  * Get filter component based on type
634
746
  */
@@ -641,6 +753,10 @@ export const getFilterComponent = (type) => {
641
753
  return DateFilter;
642
754
  case 'select':
643
755
  return SelectFilter;
756
+ case 'custom':
757
+ // For 'custom' type, return a placeholder - actual custom filters
758
+ // should be created using CustomFilter factory function
759
+ return TextFilter;
644
760
  default:
645
761
  return TextFilter;
646
762
  }