@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.
@@ -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
- import "./SearchDropdown.scss";
4
- /**
5
- * A wrapper around MultiSelectInput that appends a special "__footer__" option
6
- * and uses a custom item renderer to display "Clear" and "Filter" buttons.
7
- */
8
- const SearchDropdownInput = ({ options = [], selectedValue = [], onChange, placeholder = "Select", disabled = false, hasSelectAll = true, bgColor, textHighlight, handleFilter, ...rest }) => {
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: `${textHighlight}`, onClick: (e) => {
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
- onChange([]); // Clear all selections
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
- else if (option.label === "Select All") {
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); // Let the library handle toggling
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
- else {
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, isSearchable: true, placeholder: "Search", isMulti: true, hideSelectedOptions: true, closeMenuOnSelect: false, inputWidth: "w-full", inputTextSize: "text-sm", additionalClasses: "", customClassNames: {}, onChange: onChange, value: [], selectedValue: selectedValue, bgColor: bgColor, textHighlight: textHighlight, handleFilter: handleFilter }));
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);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agilant/toga-blox",
3
3
  "private": false,
4
- "version": "1.0.73",
4
+ "version": "1.0.74",
5
5
  "description": "",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",