@agilant/toga-blox 1.0.73 → 1.0.75

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,101 @@
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
+ handleFilter?.();
50
+ return;
51
+ }
52
+ if (isEmpty) {
53
+ setSearchCriteria?.((prev) => {
54
+ const newCriteria = prev.filter((c) => c.searchColumn.id !== column.id);
55
+ updateLocalStorage(newCriteria);
56
+ return newCriteria;
57
+ });
58
+ }
59
+ else {
60
+ setSearchCriteria?.((prev) => {
61
+ const filtered = prev.filter((c) => c.searchColumn.id !== column.id);
62
+ const newCriteria = [
63
+ ...filtered,
64
+ {
65
+ searchColumn: column,
66
+ submittedSearchText: selectedLabels,
67
+ },
68
+ ];
69
+ updateLocalStorage(newCriteria);
70
+ return newCriteria;
71
+ });
72
+ }
73
+ setEditingHeader?.(null);
74
+ handleFilter?.();
75
+ };
76
+ const handleClear = () => {
77
+ onChange([]);
78
+ if (canStore) {
79
+ setSearchCriteria?.((prev) => {
80
+ const newCriteria = prev.filter((c) => c.searchColumn.id !== column.id);
81
+ updateLocalStorage(newCriteria);
82
+ return newCriteria;
83
+ });
84
+ setSearchItems?.([]);
85
+ setEditingHeader?.(null);
86
+ }
87
+ };
9
88
  const footerOption = [{ name: "", value: "__footer__" }];
10
89
  const extendedOptions = [...options, ...footerOption];
11
90
  const itemRenderer = ({ option, checked, disabled, onClick }) => {
12
- // If it's our footer option, display the custom footer row
13
91
  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) => {
92
+ 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
93
  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();
94
+ handleFooterFilter();
20
95
  }, children: "Filter" })] }));
21
96
  }
22
- else if (option.label === "Select All") {
97
+ // If it's the "Select All" item
98
+ if (option.label === "Select All") {
23
99
  return (_jsxs("label", { className: "select-all-item", style: {
24
100
  display: "flex",
25
101
  alignItems: "center",
@@ -30,20 +106,16 @@ const SearchDropdownInput = ({ options = [], selectedValue = [], onChange, place
30
106
  zIndex: 10,
31
107
  }, onClick: (e) => {
32
108
  e.stopPropagation();
33
- onClick(e); // Let the library handle toggling
109
+ onClick(e);
34
110
  }, children: [_jsx("input", { type: "checkbox", checked: checked, readOnly: true, disabled: disabled }), _jsx("span", { style: { marginLeft: "0.5rem" }, children: option.label })] }));
35
111
  }
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
- }
112
+ 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
113
  };
40
114
  return (_jsx(MultiSelectInput, { options: extendedOptions, selectedValue: selectedValue, onChange: onChange, placeholder: {
41
115
  text: placeholder,
42
116
  icon: "magnifyingGlass",
43
117
  iconStyle: "regular",
44
- }, disabled: disabled, hasSelectAll: hasSelectAll,
45
- /** Provide the custom item renderer and any additional props via otherProps */
46
- otherProps: {
118
+ }, disabled: disabled, hasSelectAll: hasSelectAll, otherProps: {
47
119
  ItemRenderer: itemRenderer,
48
120
  ...rest.otherProps,
49
121
  }, ...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.75",
5
5
  "description": "",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",