@agilant/toga-blox 1.0.73 → 1.0.74
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/SearchInput/SearchDropdownInput.d.ts +19 -7
- package/dist/components/SearchInput/SearchDropdownInput.js +95 -20
- package/dist/components/SearchInput/SearchInput.js +1 -1
- package/dist/components/SearchInput/SearchInput.stories.js +2 -0
- package/dist/components/SearchInput/SearchInputDatePicker.js +1 -12
- package/package.json +1 -1
|
@@ -1,21 +1,33 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { OptionType } from "../MultiSelect/MultiSelect.types";
|
|
3
|
-
import "./SearchDropdown.scss";
|
|
2
|
+
import { MultiSelectInputProps, OptionType } from "../MultiSelect/MultiSelect.types";
|
|
4
3
|
interface SearchDropdownInputProps {
|
|
5
4
|
options?: OptionType[];
|
|
5
|
+
/** The currently‐selected items (each has { name, value } ) **/
|
|
6
6
|
selectedValue?: OptionType[];
|
|
7
|
+
/** Called whenever the selectedValue changes **/
|
|
7
8
|
onChange: (selected: OptionType[]) => void;
|
|
8
9
|
placeholder?: string;
|
|
9
10
|
disabled?: boolean;
|
|
10
11
|
hasSelectAll?: boolean;
|
|
11
12
|
bgColor?: string;
|
|
12
13
|
textHighlight?: string;
|
|
13
|
-
[key: string]: any;
|
|
14
14
|
handleFilter?: () => void;
|
|
15
|
+
/** The array of “badges” or stored strings for this particular column’s filter(s) **/
|
|
16
|
+
searchItems?: string[];
|
|
17
|
+
setSearchItems?: React.Dispatch<React.SetStateAction<string[]>>;
|
|
18
|
+
/** The full array of all column filters for your table **/
|
|
19
|
+
setSearchCriteria?: React.Dispatch<React.SetStateAction<any[]>>;
|
|
20
|
+
/** The column object must have an .id property */
|
|
21
|
+
column?: {
|
|
22
|
+
id: string;
|
|
23
|
+
[key: string]: any;
|
|
24
|
+
};
|
|
25
|
+
/** Tells the parent to close the UI, etc. */
|
|
26
|
+
setEditingHeader?: React.Dispatch<React.SetStateAction<any>>;
|
|
27
|
+
/** localStorage key used to store the entire “searchCriteria” array */
|
|
28
|
+
localStorageKey?: string;
|
|
29
|
+
otherProps?: MultiSelectInputProps["otherProps"];
|
|
30
|
+
additionalClasses?: string;
|
|
15
31
|
}
|
|
16
|
-
/**
|
|
17
|
-
* A wrapper around MultiSelectInput that appends a special "__footer__" option
|
|
18
|
-
* and uses a custom item renderer to display "Clear" and "Filter" buttons.
|
|
19
|
-
*/
|
|
20
32
|
declare const SearchDropdownInput: React.FC<SearchDropdownInputProps>;
|
|
21
33
|
export default SearchDropdownInput;
|
|
@@ -1,25 +1,104 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from "react";
|
|
2
3
|
import MultiSelectInput from "../MultiSelect/MultiSelect";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*/
|
|
8
|
-
const
|
|
4
|
+
const DEFAULT_STORAGE_KEY = "searchCriteria";
|
|
5
|
+
const SearchDropdownInput = ({ options = [], selectedValue = [], onChange, placeholder = "Select", disabled = false, hasSelectAll = true, bgColor = "bg-sky-500", textHighlight = "text-sky-700", handleFilter, searchItems = [], setSearchItems, setSearchCriteria, column, setEditingHeader, localStorageKey = DEFAULT_STORAGE_KEY, ...rest }) => {
|
|
6
|
+
// Are we able to store filters for this column?
|
|
7
|
+
const canStore = !!column?.id && !!setSearchCriteria;
|
|
8
|
+
/** Helper: write the entire searchCriteria array to localStorage */
|
|
9
|
+
const updateLocalStorage = (criteria) => {
|
|
10
|
+
localStorage.setItem(localStorageKey, JSON.stringify(criteria));
|
|
11
|
+
};
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!canStore)
|
|
14
|
+
return;
|
|
15
|
+
const stored = localStorage.getItem(localStorageKey);
|
|
16
|
+
if (!stored)
|
|
17
|
+
return;
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(stored);
|
|
20
|
+
const existing = parsed.find((c) => c.searchColumn?.id === column.id);
|
|
21
|
+
if (existing && setSearchItems) {
|
|
22
|
+
const raw = existing.submittedSearchText;
|
|
23
|
+
setSearchItems([raw]);
|
|
24
|
+
const storedValues = raw
|
|
25
|
+
.split(",")
|
|
26
|
+
.map((s) => s.trim());
|
|
27
|
+
const matched = options.filter((o) => storedValues.includes(o.value));
|
|
28
|
+
onChange(matched);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
console.error("Error reading stored dropdown filter:", err);
|
|
33
|
+
}
|
|
34
|
+
}, [
|
|
35
|
+
canStore,
|
|
36
|
+
column?.id,
|
|
37
|
+
localStorageKey,
|
|
38
|
+
setSearchItems,
|
|
39
|
+
onChange,
|
|
40
|
+
options,
|
|
41
|
+
]);
|
|
42
|
+
const handleFooterFilter = () => {
|
|
43
|
+
const actualSelections = selectedValue.filter((opt) => opt.value !== "__footer__" && opt.label !== "Select All");
|
|
44
|
+
const selectedLabels = actualSelections
|
|
45
|
+
.map((opt) => opt.value)
|
|
46
|
+
.join(",");
|
|
47
|
+
const isEmpty = !selectedLabels.trim();
|
|
48
|
+
if (!canStore) {
|
|
49
|
+
// If we have no place to store it, just do handleFilter
|
|
50
|
+
handleFilter?.();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (isEmpty) {
|
|
54
|
+
setSearchCriteria?.((prev) => {
|
|
55
|
+
const newCriteria = prev.filter((c) => c.searchColumn.id !== column.id);
|
|
56
|
+
updateLocalStorage(newCriteria);
|
|
57
|
+
return newCriteria;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
if (!searchItems.includes(selectedLabels)) {
|
|
62
|
+
setSearchItems?.([...searchItems, selectedLabels]);
|
|
63
|
+
}
|
|
64
|
+
setSearchCriteria?.((prev) => {
|
|
65
|
+
const newCriteria = [
|
|
66
|
+
...prev,
|
|
67
|
+
{
|
|
68
|
+
searchColumn: column,
|
|
69
|
+
submittedSearchText: selectedLabels,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
updateLocalStorage(newCriteria);
|
|
73
|
+
return newCriteria;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
setEditingHeader?.(null);
|
|
77
|
+
handleFilter?.();
|
|
78
|
+
};
|
|
79
|
+
const handleClear = () => {
|
|
80
|
+
onChange([]);
|
|
81
|
+
if (canStore) {
|
|
82
|
+
setSearchCriteria?.((prev) => {
|
|
83
|
+
const newCriteria = prev.filter((c) => c.searchColumn.id !== column.id);
|
|
84
|
+
updateLocalStorage(newCriteria);
|
|
85
|
+
return newCriteria;
|
|
86
|
+
});
|
|
87
|
+
setSearchItems?.([]);
|
|
88
|
+
setEditingHeader?.(null);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
9
91
|
const footerOption = [{ name: "", value: "__footer__" }];
|
|
10
92
|
const extendedOptions = [...options, ...footerOption];
|
|
11
93
|
const itemRenderer = ({ option, checked, disabled, onClick }) => {
|
|
12
|
-
// If it's our footer option, display the custom footer row
|
|
13
94
|
if (option.value === "__footer__") {
|
|
14
|
-
return (_jsxs("div", { className: "footer px-4 py-2 flex justify-between", children: [_jsx("button", { type: "button", className: `${
|
|
95
|
+
return (_jsxs("div", { className: "footer px-4 py-2 flex justify-between", children: [_jsx("div", { role: "button", className: `${textHighlight}`, onClick: handleClear, children: "Clear" }), _jsx("button", { type: "button", className: `${bgColor} text-white px-3 py-1 rounded`, onClick: (e) => {
|
|
15
96
|
e.stopPropagation();
|
|
16
|
-
|
|
17
|
-
}, children: "Clear" }), _jsx("button", { type: "button", className: `${bgColor} text-white px-3 py-1 rounded`, onClick: (e) => {
|
|
18
|
-
e.stopPropagation();
|
|
19
|
-
handleFilter();
|
|
97
|
+
handleFooterFilter();
|
|
20
98
|
}, children: "Filter" })] }));
|
|
21
99
|
}
|
|
22
|
-
|
|
100
|
+
// If it's the "Select All" item
|
|
101
|
+
if (option.label === "Select All") {
|
|
23
102
|
return (_jsxs("label", { className: "select-all-item", style: {
|
|
24
103
|
display: "flex",
|
|
25
104
|
alignItems: "center",
|
|
@@ -30,20 +109,16 @@ const SearchDropdownInput = ({ options = [], selectedValue = [], onChange, place
|
|
|
30
109
|
zIndex: 10,
|
|
31
110
|
}, onClick: (e) => {
|
|
32
111
|
e.stopPropagation();
|
|
33
|
-
onClick(e);
|
|
112
|
+
onClick(e);
|
|
34
113
|
}, children: [_jsx("input", { type: "checkbox", checked: checked, readOnly: true, disabled: disabled }), _jsx("span", { style: { marginLeft: "0.5rem" }, children: option.label })] }));
|
|
35
114
|
}
|
|
36
|
-
|
|
37
|
-
return (_jsxs("div", { className: "item px-4 py-1 cursor-pointer flex items-center", onClick: (e) => onClick(option, e), children: [_jsx("input", { type: "checkbox", checked: checked, readOnly: true, disabled: disabled }), _jsx("span", { className: "ml-2", children: option.label })] }));
|
|
38
|
-
}
|
|
115
|
+
return (_jsxs("div", { className: "item px-4 py-1 cursor-pointer flex items-center", onClick: (e) => onClick(option, e), children: [_jsx("input", { type: "checkbox", checked: checked, readOnly: true, disabled: disabled }), _jsx("span", { className: "ml-2", children: option.label })] }));
|
|
39
116
|
};
|
|
40
117
|
return (_jsx(MultiSelectInput, { options: extendedOptions, selectedValue: selectedValue, onChange: onChange, placeholder: {
|
|
41
118
|
text: placeholder,
|
|
42
119
|
icon: "magnifyingGlass",
|
|
43
120
|
iconStyle: "regular",
|
|
44
|
-
}, disabled: disabled, hasSelectAll: hasSelectAll,
|
|
45
|
-
/** Provide the custom item renderer and any additional props via otherProps */
|
|
46
|
-
otherProps: {
|
|
121
|
+
}, disabled: disabled, hasSelectAll: hasSelectAll, otherProps: {
|
|
47
122
|
ItemRenderer: itemRenderer,
|
|
48
123
|
...rest.otherProps,
|
|
49
124
|
}, ...rest }));
|
|
@@ -21,7 +21,7 @@ const SearchInput = ({ bgColor = "bg-sky-500", textHighlight = "text-sky-500", i
|
|
|
21
21
|
case "number":
|
|
22
22
|
return (_jsx(SearchNumberInput, { dropdownIconProp: dropdownIconProp, dropdownOptions: dropdownOptions, selectedDropdownOption: selectedDropdownOption, onDropdownOptionSelect: onDropdownOptionSelect, toggleStatus: toggleStatus, setToggleStatus: setToggleStatus, minValue: minValue, maxValue: maxValue, setMinValue: setMinValue, setMaxValue: setMaxValue, handleFilter: handleFilter }));
|
|
23
23
|
case "multiSelect":
|
|
24
|
-
return (_jsx(SearchDropdownInput, { options: dropdownOptions,
|
|
24
|
+
return (_jsx(SearchDropdownInput, { options: dropdownOptions, placeholder: "Search", additionalClasses: "", onChange: onChange, selectedValue: selectedValue, bgColor: bgColor, textHighlight: textHighlight, handleFilter: handleFilter, column: column, setSearchCriteria: setSearchCriteria }));
|
|
25
25
|
case "date":
|
|
26
26
|
return (_jsx(SearchDatePickerInput, { textHighlight: textHighlight, dropdownOptions: dropdownOptions, selectedDropdownOption: selectedDropdownOption, onDropdownOptionSelect: onDropdownOptionSelect, toggleStatus: toggleStatus, setToggleStatus: setToggleStatus, selectedDate: selectedDate, onDateSelect: onDateSelect, selectedStartDate: selectedStartDate, onStartDateSelect: onStartDateSelect, selectedEndDate: selectedEndDate, onEndDateSelect: onEndDateSelect, handleFilter: handleFilter, themeBgColor: dataPickerThemeColor, lightThemeBg: dataPickerThemeColorAccent,
|
|
27
27
|
// pass the same local-storage props:
|
|
@@ -135,6 +135,8 @@ DropdownInput.args = {
|
|
|
135
135
|
disabled: false,
|
|
136
136
|
isLoading: false,
|
|
137
137
|
type: "multiSelect",
|
|
138
|
+
column: { id: "multiSelectColumn" },
|
|
139
|
+
localStorageKey: "searchCriteria",
|
|
138
140
|
};
|
|
139
141
|
export const BooleanInput = Template.bind({});
|
|
140
142
|
BooleanInput.args = {
|
|
@@ -74,18 +74,6 @@ searchItems = [], setSearchItems, handleFilter, setSearchCriteria, column, setEd
|
|
|
74
74
|
const updateLocalStorage = (criteria) => {
|
|
75
75
|
localStorage.setItem(localStorageKey, JSON.stringify(criteria));
|
|
76
76
|
};
|
|
77
|
-
// removing a date "badge"
|
|
78
|
-
const handleSearchBadgeClick = (item) => {
|
|
79
|
-
const newSearchItems = searchItems.filter((x) => x !== item);
|
|
80
|
-
setSearchItems?.(newSearchItems);
|
|
81
|
-
if (canStore) {
|
|
82
|
-
setSearchCriteria?.((prev) => {
|
|
83
|
-
const newCriteria = prev.filter((crit) => crit.submittedSearchText !== item);
|
|
84
|
-
updateLocalStorage(newCriteria);
|
|
85
|
-
return newCriteria;
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
77
|
/**
|
|
90
78
|
* Build a string in 'YYYY-MM-DD' (or range) format,
|
|
91
79
|
* so it won't break in your URL as "mm%2Fdd%2Fyyyy"
|
|
@@ -192,6 +180,7 @@ searchItems = [], setSearchItems, handleFilter, setSearchCriteria, column, setEd
|
|
|
192
180
|
}, modifiersClassNames: modifiersClassNames })) }))
|
|
193
181
|
: isDatePickerOpen && (_jsx("div", { className: "absolute p-4 top-16 w-auto z-50 shadow-lg bg-white", children: _jsx(DayPicker, { mode: "single", selected: selectedDate, onSelect: (day) => {
|
|
194
182
|
onDateSelect?.(day || undefined);
|
|
183
|
+
setIsDatePickerOpen(false);
|
|
195
184
|
}, modifiersClassNames: modifiersClassNames }) })), searchItems?.length ? (_jsx("div", { className: "flex flex-wrap bg-white py-2 px-2 mt-2 rounded-md", children: searchItems.map((item, index) => (_jsx(Badge, { backgroundColor: pillColor, borderRadius: "rounded-full", hasRightIcon: true, icon: _jsx("div", { className: "text-white text-xxs", children: getFontAwesomeIcon("xmark", "solid") }), iconSize: "text-sm", mobileIconLabel: item, onClick: () => {
|
|
196
185
|
const newSearchItems = searchItems.filter((x) => x !== item);
|
|
197
186
|
setSearchItems?.(newSearchItems);
|