@agilant/toga-blox 1.0.76 → 1.0.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/MultiSelect/MultiSelect.js +1 -1
- package/dist/components/SearchInput/SearchInput.test.js +16 -13
- package/dist/components/SearchInput/SearchInput.types.d.ts +6 -1
- package/dist/components/SearchInput/SearchInputDatePicker.js +0 -1
- package/dist/components/SearchInput/SearchNumberInput.js +38 -21
- package/package.json +1 -1
|
@@ -27,7 +27,7 @@ const MultiSelectInput = ({ id, name, options = [], selectedValue = [], onChange
|
|
|
27
27
|
return "All items selected";
|
|
28
28
|
}
|
|
29
29
|
// Default behavior: comma-separated labels
|
|
30
|
-
return selected.map((s) => s.label).join("
|
|
30
|
+
return selected.map((s) => s.label).join("; ");
|
|
31
31
|
};
|
|
32
32
|
return (_jsx("div", { className: `${width}`, children: _jsx(MultiSelect, { id: id, name: name, className: className, options: multiSelectOptions, value: multiSelectValue, onChange: (selectedOptions) => {
|
|
33
33
|
// Convert back to OptionType when passing to onChange
|
|
@@ -191,13 +191,13 @@ describe("SearchInput Component", () => {
|
|
|
191
191
|
uuid: "1",
|
|
192
192
|
name: "Option 1",
|
|
193
193
|
value: "option1",
|
|
194
|
-
label: "Option 1",
|
|
194
|
+
// label: "Option 1",
|
|
195
195
|
},
|
|
196
196
|
{
|
|
197
197
|
uuid: "2",
|
|
198
198
|
name: "Option 2",
|
|
199
199
|
value: "option2",
|
|
200
|
-
label: "Option 2",
|
|
200
|
+
// label: "Option 2",
|
|
201
201
|
},
|
|
202
202
|
] }));
|
|
203
203
|
// Open dropdown
|
|
@@ -222,13 +222,13 @@ describe("SearchInput Component", () => {
|
|
|
222
222
|
uuid: "1",
|
|
223
223
|
name: "Option 1",
|
|
224
224
|
value: "option1",
|
|
225
|
-
label: "Option 1",
|
|
225
|
+
// label: "Option 1",
|
|
226
226
|
},
|
|
227
227
|
{
|
|
228
228
|
uuid: "2",
|
|
229
229
|
name: "Option 2",
|
|
230
230
|
value: "option2",
|
|
231
|
-
label: "Option 2",
|
|
231
|
+
// label: "Option 2",
|
|
232
232
|
},
|
|
233
233
|
] }));
|
|
234
234
|
const dropdown = screen.getByText(/Search/i);
|
|
@@ -245,13 +245,13 @@ describe("SearchInput Component", () => {
|
|
|
245
245
|
uuid: "1",
|
|
246
246
|
name: "Option 1",
|
|
247
247
|
value: "option1",
|
|
248
|
-
label: "Option 1",
|
|
248
|
+
// label: "Option 1",
|
|
249
249
|
},
|
|
250
250
|
{
|
|
251
251
|
uuid: "2",
|
|
252
252
|
name: "Option 2",
|
|
253
253
|
value: "option2",
|
|
254
|
-
label: "Option 2",
|
|
254
|
+
// label: "Option 2",
|
|
255
255
|
},
|
|
256
256
|
] }));
|
|
257
257
|
const dropdown = screen.getByText(/Search/i);
|
|
@@ -269,13 +269,13 @@ describe("SearchInput Component", () => {
|
|
|
269
269
|
uuid: "1",
|
|
270
270
|
name: "True",
|
|
271
271
|
value: "true",
|
|
272
|
-
label: "True",
|
|
272
|
+
// label: "True",
|
|
273
273
|
},
|
|
274
274
|
{
|
|
275
275
|
uuid: "2",
|
|
276
276
|
name: "False",
|
|
277
277
|
value: "false",
|
|
278
|
-
label: "False",
|
|
278
|
+
// label: "False",
|
|
279
279
|
},
|
|
280
280
|
] }));
|
|
281
281
|
expect(screen.getByText(/Search/i)).toBeInTheDocument();
|
|
@@ -286,13 +286,13 @@ describe("SearchInput Component", () => {
|
|
|
286
286
|
uuid: "1",
|
|
287
287
|
name: "True",
|
|
288
288
|
value: "true",
|
|
289
|
-
label: "True",
|
|
289
|
+
// label: "True",
|
|
290
290
|
},
|
|
291
291
|
{
|
|
292
292
|
uuid: "2",
|
|
293
293
|
name: "False",
|
|
294
294
|
value: "false",
|
|
295
|
-
label: "False",
|
|
295
|
+
// label: "False",
|
|
296
296
|
},
|
|
297
297
|
] }));
|
|
298
298
|
// Open dropdown
|
|
@@ -308,13 +308,13 @@ describe("SearchInput Component", () => {
|
|
|
308
308
|
uuid: "1",
|
|
309
309
|
name: "True",
|
|
310
310
|
value: "true",
|
|
311
|
-
label: "True",
|
|
311
|
+
// label: "True",
|
|
312
312
|
},
|
|
313
313
|
{
|
|
314
314
|
uuid: "2",
|
|
315
315
|
name: "False",
|
|
316
316
|
value: "false",
|
|
317
|
-
label: "False",
|
|
317
|
+
// label: "False",
|
|
318
318
|
},
|
|
319
319
|
] }));
|
|
320
320
|
// Open dropdown
|
|
@@ -477,10 +477,13 @@ describe("SearchInput Component", () => {
|
|
|
477
477
|
});
|
|
478
478
|
test("renders selectedStartDate if provided, otherwise shows default text", () => {
|
|
479
479
|
const mockStartDate = new Date(2023, 5, 15); // June 15, 2023
|
|
480
|
+
// Helper to format date as YYYY-MM-DD
|
|
481
|
+
const formatDate = (date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
480
482
|
const { rerender } = render(_jsx(SearchInput, { column: undefined, setSearchCriteria: undefined, ...defaultProps, inputType: "date", toggleStatus: true, selectedStartDate: null }));
|
|
481
483
|
expect(screen.getByText("Start Date")).toBeInTheDocument();
|
|
482
484
|
rerender(_jsx(SearchInput, { column: undefined, setSearchCriteria: undefined, ...defaultProps, inputType: "date", toggleStatus: true, selectedStartDate: mockStartDate }));
|
|
483
|
-
|
|
485
|
+
// Use the formatted date string
|
|
486
|
+
expect(screen.getByText(formatDate(mockStartDate))).toBeInTheDocument();
|
|
484
487
|
});
|
|
485
488
|
test("calls onStartDateSelect when a start date is selected", () => {
|
|
486
489
|
render(_jsx(SearchInput, { column: undefined, setSearchCriteria: undefined, ...defaultProps, inputType: "date", toggleStatus: true }));
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Control } from "react-hook-form";
|
|
3
|
+
export type MixedOption = string | {
|
|
4
|
+
uuid: string;
|
|
5
|
+
name: string;
|
|
6
|
+
value: string;
|
|
7
|
+
};
|
|
3
8
|
export type SearchInputProps<T extends object> = {
|
|
4
9
|
onChange(newSelected: OptionType[]): unknown;
|
|
5
10
|
selectedValue: any[];
|
|
@@ -8,7 +13,7 @@ export type SearchInputProps<T extends object> = {
|
|
|
8
13
|
bgColor?: string;
|
|
9
14
|
textHighlight?: string;
|
|
10
15
|
inputType?: "text" | "number" | "date" | "boolean" | "multiSelect";
|
|
11
|
-
dropdownOptions?:
|
|
16
|
+
dropdownOptions?: MixedOption[] | number[];
|
|
12
17
|
selectedDropdownOption?: string | OptionType | number;
|
|
13
18
|
onDropdownOptionSelect?: (option: string) => void;
|
|
14
19
|
dropdownIconProp?: searchDropdownIconProps;
|
|
@@ -51,7 +51,6 @@ searchItems = [], setSearchItems, handleFilter, setSearchCriteria, column, setEd
|
|
|
51
51
|
const parsed = JSON.parse(stored);
|
|
52
52
|
const existing = parsed.find((criterion) => criterion.searchColumn.id === column.id);
|
|
53
53
|
if (existing && setSearchItems) {
|
|
54
|
-
// e.g. "2025-02-27" or "2024-12-01 - 2024-12-28"
|
|
55
54
|
setSearchItems([existing.submittedSearchText]);
|
|
56
55
|
}
|
|
57
56
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useEffect, useRef, useCallback } from "react";
|
|
3
3
|
import { Input } from "../Input";
|
|
4
4
|
import Dropdown from "../Dropdown/Dropdown";
|
|
5
5
|
import ToggleButton from "../ToggleButton/ToggleButton";
|
|
6
6
|
import BaseButton from "../BaseButton";
|
|
7
7
|
import Text from "../Text";
|
|
8
|
-
import updateLocalStorage from "../../utils/updateLocalStorage";
|
|
9
8
|
const DEFAULT_STORAGE_KEY = "searchCriteria";
|
|
10
9
|
/**
|
|
11
10
|
* A numeric filter component that:
|
|
@@ -22,35 +21,41 @@ const SearchNumberInput = ({ textHighlight = "text-sky-500", dropdownIconProp =
|
|
|
22
21
|
searchItems = [], setSearchItems, setSearchCriteria, column, setEditingHeader, localStorageKey = DEFAULT_STORAGE_KEY, }) => {
|
|
23
22
|
const containerRef = useRef(null);
|
|
24
23
|
const inputRef = useRef(null);
|
|
24
|
+
/** Decide if we can store for this column */
|
|
25
25
|
const canStore = !!column?.id && !!setSearchCriteria;
|
|
26
|
+
/** On mount, read localStorage once => if there's a saved filter => parse & set */
|
|
26
27
|
useEffect(() => {
|
|
27
28
|
if (!canStore)
|
|
28
29
|
return;
|
|
30
|
+
// Make sure this runs only once
|
|
29
31
|
const stored = localStorage.getItem(localStorageKey);
|
|
30
32
|
if (!stored)
|
|
31
33
|
return;
|
|
32
34
|
try {
|
|
33
35
|
const parsed = JSON.parse(stored);
|
|
34
|
-
const existing = parsed.find((c) => c.searchColumn?.id === column
|
|
36
|
+
const existing = parsed.find((c) => c.searchColumn?.id === column?.id);
|
|
35
37
|
if (existing) {
|
|
36
38
|
const savedString = existing.submittedSearchText;
|
|
37
39
|
if (savedString && typeof savedString === "string") {
|
|
38
40
|
if (savedString.includes(" - ")) {
|
|
41
|
+
// It's a range: "100 - 200"
|
|
39
42
|
setToggleStatus?.(true);
|
|
40
43
|
const [minPart, maxPart] = savedString.split(" - ");
|
|
41
44
|
setMinValue?.(minPart.trim());
|
|
42
45
|
setMaxValue?.(maxPart.trim());
|
|
43
46
|
}
|
|
44
47
|
else {
|
|
48
|
+
// Single e.g. "Exactly 123" or "default 2" or just "2"
|
|
45
49
|
setToggleStatus?.(false);
|
|
46
50
|
const segments = savedString.split(" ");
|
|
51
|
+
// If the first word is in your dropdown => use it, else treat as pure number
|
|
47
52
|
if (segments.length > 1 &&
|
|
48
53
|
dropdownOptions.includes(segments[0])) {
|
|
49
54
|
onDropdownOptionSelect?.(segments[0]);
|
|
50
55
|
setMinValue?.(segments[1]);
|
|
51
56
|
}
|
|
52
57
|
else {
|
|
53
|
-
// no
|
|
58
|
+
// no prefix => assume it's just the number
|
|
54
59
|
setMinValue?.(savedString);
|
|
55
60
|
}
|
|
56
61
|
}
|
|
@@ -62,13 +67,19 @@ searchItems = [], setSearchItems, setSearchCriteria, column, setEditingHeader, l
|
|
|
62
67
|
catch (err) {
|
|
63
68
|
console.error("Error reading stored number filter:", err);
|
|
64
69
|
}
|
|
70
|
+
// empty dependency array => only once on mount
|
|
65
71
|
}, []);
|
|
66
|
-
|
|
72
|
+
/** Focus on mount if needed */
|
|
67
73
|
useEffect(() => {
|
|
68
74
|
inputRef.current?.focus();
|
|
69
75
|
}, []);
|
|
70
|
-
/**
|
|
71
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Build the final string to store.
|
|
78
|
+
*
|
|
79
|
+
* 1) If toggleStatus => "minValue - maxValue"
|
|
80
|
+
* 2) If single => skip "default" prefix
|
|
81
|
+
*/
|
|
82
|
+
const buildNumberString = useCallback(() => {
|
|
72
83
|
if (toggleStatus) {
|
|
73
84
|
// Range mode => "min - max"
|
|
74
85
|
if (minValue && maxValue) {
|
|
@@ -85,10 +96,12 @@ searchItems = [], setSearchItems, setSearchCriteria, column, setEditingHeader, l
|
|
|
85
96
|
}
|
|
86
97
|
}
|
|
87
98
|
else {
|
|
88
|
-
// Single value mode => e.g. "Exactly 123" or
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
99
|
+
// Single value mode => e.g. "Exactly 123" or "2"
|
|
100
|
+
const prefix = selectedDropdownOption?.toLowerCase() === "default"
|
|
101
|
+
? "" // if your dropdown used "default" for something
|
|
102
|
+
: selectedDropdownOption;
|
|
103
|
+
if (prefix && minValue) {
|
|
104
|
+
return `${prefix} ${minValue}`;
|
|
92
105
|
}
|
|
93
106
|
else if (minValue) {
|
|
94
107
|
return minValue; // fallback
|
|
@@ -97,16 +110,19 @@ searchItems = [], setSearchItems, setSearchCriteria, column, setEditingHeader, l
|
|
|
97
110
|
return "";
|
|
98
111
|
}
|
|
99
112
|
}
|
|
100
|
-
};
|
|
101
|
-
/**
|
|
113
|
+
}, [toggleStatus, minValue, maxValue, selectedDropdownOption]);
|
|
114
|
+
/**
|
|
115
|
+
* Called when user clicks "Filter" button at the bottom.
|
|
116
|
+
* We remove or store the built string, then call handleFilter.
|
|
117
|
+
*/
|
|
102
118
|
const handleFilterClick = () => {
|
|
103
119
|
const finalString = buildNumberString().trim();
|
|
104
120
|
if (!finalString) {
|
|
105
|
-
//
|
|
121
|
+
// remove existing filter for this column
|
|
106
122
|
if (canStore) {
|
|
107
123
|
setSearchCriteria?.((prev) => {
|
|
108
|
-
const newCriteria = prev.filter((c) => c.searchColumn.id !== column
|
|
109
|
-
|
|
124
|
+
const newCriteria = prev.filter((c) => c.searchColumn.id !== column?.id);
|
|
125
|
+
localStorage.setItem(localStorageKey, JSON.stringify(newCriteria));
|
|
110
126
|
return newCriteria;
|
|
111
127
|
});
|
|
112
128
|
}
|
|
@@ -118,8 +134,8 @@ searchItems = [], setSearchItems, setSearchCriteria, column, setEditingHeader, l
|
|
|
118
134
|
}
|
|
119
135
|
if (canStore) {
|
|
120
136
|
setSearchCriteria?.((prev) => {
|
|
121
|
-
// remove old for this column if you want
|
|
122
|
-
const filtered = prev.filter((c) => c.searchColumn.id !== column
|
|
137
|
+
// remove old for this column if you want only one number filter per column
|
|
138
|
+
const filtered = prev.filter((c) => c.searchColumn.id !== column?.id);
|
|
123
139
|
const newCriteria = [
|
|
124
140
|
...filtered,
|
|
125
141
|
{
|
|
@@ -127,7 +143,7 @@ searchItems = [], setSearchItems, setSearchCriteria, column, setEditingHeader, l
|
|
|
127
143
|
submittedSearchText: finalString,
|
|
128
144
|
},
|
|
129
145
|
];
|
|
130
|
-
|
|
146
|
+
localStorage.setItem(localStorageKey, JSON.stringify(newCriteria));
|
|
131
147
|
return newCriteria;
|
|
132
148
|
});
|
|
133
149
|
}
|
|
@@ -137,10 +153,11 @@ searchItems = [], setSearchItems, setSearchCriteria, column, setEditingHeader, l
|
|
|
137
153
|
// Let the parent know a filter was applied
|
|
138
154
|
handleFilter?.();
|
|
139
155
|
};
|
|
140
|
-
return (_jsx("div", { ref: containerRef, className: "w-[425px]", children: _jsxs("div", { className: "flex flex-col p-4 h-[130px] border-2 border-navy-200 rounded-md", children: [_jsx("div", { className: `flex flex-[1] ${toggleStatus ? "" : "border-2"}
|
|
156
|
+
return (_jsx("div", { ref: containerRef, className: "w-[425px]", children: _jsxs("div", { className: "flex flex-col p-4 h-[130px] border-2 border-navy-200 rounded-md", children: [_jsx("div", { className: `flex flex-[1] ${toggleStatus ? "" : "border-2"}
|
|
157
|
+
h-full max-h-11 items-center justify-around`, children: toggleStatus ? (
|
|
141
158
|
// Range mode
|
|
142
159
|
_jsxs(_Fragment, { children: [_jsx(Input, { focusRingColor: "focus:ring-2", hasAutoFocus: true, value: minValue, iconColor: "text-navy-400", required: false, id: "", name: "", type: "number", onChange: (e) => setMinValue?.(e.target.value), additionalClasses: "min-w-[180px] max-w-[180px] h-10 text-gray flex focus:border-l-2 ", placeholder: "Min" }), _jsx(Text, { size: "text-md", tag: "span", text: "to", additionalClasses: "px-2" }), _jsx(Input, { focusRingColor: "focus:ring-2", value: maxValue, iconColor: "text-navy-400", required: false, id: "", name: "", type: "number", onChange: (e) => setMaxValue?.(e.target.value), additionalClasses: "min-w-[180px] max-w-[180px] h-10 text-gray flex border-2 focus:border-l-2 ", placeholder: "Max" })] })) : (
|
|
143
160
|
// Single value mode
|
|
144
|
-
_jsxs(_Fragment, { children: [_jsx(Dropdown, { options: dropdownOptions, selectedOption: selectedDropdownOption, onOptionSelect: onDropdownOptionSelect, optionClasses: "px-4
|
|
161
|
+
_jsxs(_Fragment, { children: [_jsx(Dropdown, { options: dropdownOptions, selectedOption: selectedDropdownOption, onOptionSelect: onDropdownOptionSelect, optionClasses: "px-4 h-full flex items-center", menuClasses: "bg-white w-min-[150px] top-[-8px] left-[-2px]", dropdownClasses: "border-0 w-auto", icon: dropdownIconProp }), _jsx(Input, { ref: inputRef, focusRingColor: "focus:ring-transparent", hasAutoFocus: true, value: minValue, iconColor: "text-navy-400", required: false, id: "", name: "", type: "number", onChange: (e) => setMinValue?.(e.target.value), additionalClasses: "min-w-[200px] h-10 text-gray flex border-l-2 ", placeholder: "Amount", hasIcons: true, iconPosition: "both" })] })) }), _jsxs("div", { className: "flex flex-[1] justify-between items-end bg-white px-2", children: [_jsx(ToggleButton, { initialStatus: toggleStatus, onClick: () => setToggleStatus?.(!toggleStatus), activeColorBackground: "bg-sky-500", activeColorBorder: "border-sky-500", activeLabel: "Range", activeTextColor: "text-sky-500", additionalClasses: "flex items-center", inactiveColorBackground: "bg-gray-300", inactiveColorBorder: "border-gray-300", inactiveLabel: "Range", inactiveTextColor: "text-gray-500", pillHeight: "h-8", textPosition: "right", textSize: "text-sm", smallToggle: false, borderStyle: false }), _jsx(BaseButton, { text: "Filter", backgroundColor: "bg-sky-500", additionalClasses: "py-1.5 px-6 text-white", borderColor: "border-none", onClick: handleFilterClick, shape: "rounded-full" })] })] }) }));
|
|
145
162
|
};
|
|
146
163
|
export default SearchNumberInput;
|