@agilant/toga-blox 1.0.319-beta.82 → 1.0.319-beta.83
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.
|
@@ -10,14 +10,21 @@ const FILTER_MODE_LABELS = {
|
|
|
10
10
|
includes: "Includes",
|
|
11
11
|
excludes: "Excludes",
|
|
12
12
|
};
|
|
13
|
+
// Keep in sync with .headerFilterMenu width in toga.module.css
|
|
14
|
+
const HEADER_FILTER_MENU_WIDTH = 329;
|
|
13
15
|
export const HeaderFilterSearch = ({ columnId, filterValue, filterMode: filterModeProp, filterIndex, onFilterChange, onClear, skin, }) => {
|
|
14
16
|
const [isOpen, setIsOpen] = useState(false);
|
|
15
17
|
const [portalStyles, setPortalStyles] = useState({});
|
|
16
18
|
const [isModeOpen, setIsModeOpen] = useState(false);
|
|
17
19
|
const [filterMode, setFilterMode] = useState("includes");
|
|
18
20
|
const [inputValue, setInputValue] = useState(filterValue);
|
|
21
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
19
22
|
const [activeTags, setActiveTags] = useState(filterValue ? [filterValue] : []);
|
|
20
23
|
const [modeDropdownPos, setModeDropdownPos] = useState(null);
|
|
24
|
+
// When centering would clip the menu off the left edge of the scroll
|
|
25
|
+
// container (e.g. the first column), left-align it so it extends right.
|
|
26
|
+
const [alignLeft, setAlignLeft] = useState(false);
|
|
27
|
+
const triggerRef = useRef(null);
|
|
21
28
|
const inputRef = useRef(null);
|
|
22
29
|
const modeButtonRef = useRef(null);
|
|
23
30
|
const isActive = activeTags.length > 0;
|
|
@@ -65,6 +72,22 @@ export const HeaderFilterSearch = ({ columnId, filterValue, filterMode: filterMo
|
|
|
65
72
|
setActiveTags([filterValue]);
|
|
66
73
|
}
|
|
67
74
|
}, [filterValue]);
|
|
75
|
+
const handleTriggerClick = (e) => {
|
|
76
|
+
e.stopPropagation();
|
|
77
|
+
// Only recompute alignment when opening.
|
|
78
|
+
if (!isOpen) {
|
|
79
|
+
const btnRect = triggerRef.current?.getBoundingClientRect();
|
|
80
|
+
const scroller = triggerRef.current?.closest('[class*="scrollWrapper"]');
|
|
81
|
+
const containerLeft = scroller?.getBoundingClientRect().left ?? 0;
|
|
82
|
+
if (btnRect) {
|
|
83
|
+
const centeredLeft = btnRect.left +
|
|
84
|
+
btnRect.width / 2 -
|
|
85
|
+
HEADER_FILTER_MENU_WIDTH / 2;
|
|
86
|
+
setAlignLeft(centeredLeft < containerLeft);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
setIsOpen((prev) => !prev);
|
|
90
|
+
};
|
|
68
91
|
const handleModeButtonClick = () => {
|
|
69
92
|
if (isModeOpen) {
|
|
70
93
|
setIsModeOpen(false);
|
|
@@ -109,14 +132,13 @@ export const HeaderFilterSearch = ({ columnId, filterValue, filterMode: filterMo
|
|
|
109
132
|
else
|
|
110
133
|
onFilterChange(next.join(","), filterMode);
|
|
111
134
|
};
|
|
112
|
-
return (_jsxs("div", { className: styles.headerFilter, children: [_jsx("button", { onClick: (
|
|
113
|
-
e.stopPropagation();
|
|
114
|
-
setIsOpen((prev) => !prev);
|
|
115
|
-
}, className: styles.headerFilterButton, "data-active": isActive, "data-open": isOpen, children: _jsx("span", { className: styles.headerFilterButtonIcon, "data-active": isActive, children: getFontAwesomeIcon("search", isActive ? "solid" : "regular") }) }), isOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.headerFilterBackdrop, onClick: () => {
|
|
135
|
+
return (_jsxs("div", { className: styles.headerFilter, children: [_jsx("button", { ref: triggerRef, onClick: handleTriggerClick, className: styles.headerFilterButton, "data-active": isActive, "data-open": isOpen, children: _jsx("span", { className: styles.headerFilterButtonIcon, "data-active": isActive, children: getFontAwesomeIcon("search", isActive ? "solid" : "regular") }) }), isOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.headerFilterBackdrop, onClick: () => {
|
|
116
136
|
setIsOpen(false);
|
|
117
137
|
setIsModeOpen(false);
|
|
118
138
|
setModeDropdownPos(null);
|
|
119
|
-
} }), _jsxs("div", { ref: popoverRef, className: styles.headerFilterMenu, "data-mode-open": isModeOpen, onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: styles.headerFilterInputRow, children: [_jsx("div", { className: styles.headerFilterModeWrapper, children: _jsxs("button", { ref: modeButtonRef, className: styles.headerFilterModeButton, onClick: handleModeButtonClick, children: [isModeOpen && (_jsx("span", { children: getFontAwesomeIcon("check", "regular") })), FILTER_MODE_LABELS[filterMode], !isModeOpen && (_jsx("span", { className: styles.headerFilterModeButtonIcon, children: getFontAwesomeIcon("chevronDown", "regular") }))] }) }),
|
|
139
|
+
} }), _jsxs("div", { ref: popoverRef, className: styles.headerFilterMenu, "data-mode-open": isModeOpen, "data-align": alignLeft ? "left" : "center", onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: styles.headerFilterInputRow, children: [_jsx("div", { className: styles.headerFilterModeWrapper, children: _jsxs("button", { ref: modeButtonRef, className: styles.headerFilterModeButton, onClick: handleModeButtonClick, children: [isModeOpen && (_jsx("span", { className: styles.headerFilterModeButtonIcon, children: getFontAwesomeIcon("check", "regular") })), FILTER_MODE_LABELS[filterMode], !isModeOpen && (_jsx("span", { className: styles.headerFilterModeButtonIcon, children: getFontAwesomeIcon("chevronDown", "regular") }))] }) }), activeTags.length === 0 &&
|
|
140
|
+
!inputValue &&
|
|
141
|
+
!isFocused && (_jsx("span", { className: styles.headerFilterSearchIcon, children: getFontAwesomeIcon("search", "regular") })), _jsx("label", { htmlFor: "search", className: "sr-only" }), _jsx("input", { ref: inputRef, id: "search", className: styles.headerFilterInput, placeholder: "Search", value: inputValue, onChange: (e) => setInputValue(e.target.value), onKeyDown: handleKeyDown, onFocus: () => setIsFocused(true), onBlur: () => setIsFocused(false) }), inputValue && (_jsx("button", { className: styles.headerFilterIconButton, onClick: () => setInputValue(""), children: getFontAwesomeIcon("xmark", "regular") })), inputValue && (_jsx("button", { className: styles.headerFilterSearchButton, onClick: handleConfirm, children: getFontAwesomeIcon("search", "regular") }))] }), activeTags.length > 0 && (_jsx("div", { className: styles.headerFilterTags, children: activeTags.map((tag) => (_jsxs("span", { className: styles.headerFilterTag, children: [tag, _jsx("button", { className: styles.headerFilterTagRemove, onClick: () => removeTag(tag), children: getFontAwesomeIcon("xmark", "regular") })] }, tag))) }))] })] })), isModeOpen &&
|
|
120
142
|
modeDropdownPos &&
|
|
121
143
|
createPortal(_jsxs(_Fragment, { children: [_jsx("div", { className: styles.headerFilterBackdrop, onClick: () => {
|
|
122
144
|
setIsModeOpen(false);
|
|
@@ -946,6 +946,13 @@
|
|
|
946
946
|
font-size: 14px;
|
|
947
947
|
}
|
|
948
948
|
|
|
949
|
+
/* First column: centering clips the menu off the scroll container's left
|
|
950
|
+
edge, so anchor it to the button's left edge and let it extend right. */
|
|
951
|
+
.headerFilterMenu[data-align="left"] {
|
|
952
|
+
left: -100px;
|
|
953
|
+
transform: none;
|
|
954
|
+
}
|
|
955
|
+
|
|
949
956
|
/* Square off the bottom-left corner so the mode dropdown visually connects */
|
|
950
957
|
.headerFilterMenu[data-mode-open="true"] {
|
|
951
958
|
border-radius: 4px 4px 4px 0px;
|
|
@@ -973,6 +980,7 @@
|
|
|
973
980
|
color: var(--filterDropdown-text-default);
|
|
974
981
|
white-space: nowrap;
|
|
975
982
|
width: 103px;
|
|
983
|
+
font-weight: 400;
|
|
976
984
|
}
|
|
977
985
|
|
|
978
986
|
.headerFilterModeButtonIcon {
|
|
@@ -1001,7 +1009,7 @@
|
|
|
1001
1009
|
gap: 6px;
|
|
1002
1010
|
width: 103px;
|
|
1003
1011
|
padding: 7px 12px;
|
|
1004
|
-
color:
|
|
1012
|
+
color: var(--filterDropdown-itemText-color-default);
|
|
1005
1013
|
text-align: right;
|
|
1006
1014
|
font-family: var(--filterDropdown-fontFamily-default);
|
|
1007
1015
|
font-size: 14px;
|
|
@@ -1023,20 +1031,45 @@
|
|
|
1023
1031
|
flex-shrink: 0;
|
|
1024
1032
|
}
|
|
1025
1033
|
|
|
1034
|
+
/* Search icon (leading, inside input row) */
|
|
1035
|
+
.headerFilterSearchIcon {
|
|
1036
|
+
display: flex;
|
|
1037
|
+
align-items: center;
|
|
1038
|
+
justify-content: center;
|
|
1039
|
+
padding-left: 8px;
|
|
1040
|
+
color: var(--headerFilterInput-icon-default);
|
|
1041
|
+
flex-shrink: 0;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/* Hide the leading search icon while the input is focused */
|
|
1045
|
+
.headerFilterInputRow:focus-within .headerFilterSearchIcon {
|
|
1046
|
+
display: none;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1026
1049
|
/* Text input */
|
|
1027
1050
|
.headerFilterInput {
|
|
1028
1051
|
flex: 1;
|
|
1029
1052
|
height: 100%;
|
|
1030
1053
|
padding: 0 8px;
|
|
1031
|
-
color:
|
|
1054
|
+
color: var(--headerFilterInput-text-default);
|
|
1055
|
+
font-weight: var(--headerFilterInput-font-weight);
|
|
1032
1056
|
background: transparent;
|
|
1033
1057
|
border: none;
|
|
1034
1058
|
outline: none;
|
|
1035
1059
|
min-width: 0;
|
|
1060
|
+
|
|
1061
|
+
&::placeholder {
|
|
1062
|
+
font-weight: 400;
|
|
1063
|
+
color: #9ca3af;
|
|
1064
|
+
}
|
|
1036
1065
|
}
|
|
1037
1066
|
|
|
1038
1067
|
.headerFilterInput::placeholder {
|
|
1039
|
-
color:
|
|
1068
|
+
color: var(--headerFilterInput-placeholder-text-default);
|
|
1069
|
+
}
|
|
1070
|
+
.headerFilterSearchIcon {
|
|
1071
|
+
color: var(--filterDropdown-text-default);
|
|
1072
|
+
padding-left: 10px;
|
|
1040
1073
|
}
|
|
1041
1074
|
|
|
1042
1075
|
/* Icon buttons (clear X) */
|
|
@@ -1046,13 +1079,13 @@
|
|
|
1046
1079
|
justify-content: center;
|
|
1047
1080
|
padding: 0 6px;
|
|
1048
1081
|
height: 100%;
|
|
1049
|
-
color:
|
|
1082
|
+
color: var(--headerFilterInput-icon-btn-text-default);
|
|
1050
1083
|
flex-shrink: 0;
|
|
1051
1084
|
font-size: 20px;
|
|
1052
1085
|
}
|
|
1053
1086
|
|
|
1054
1087
|
.headerFilterIconButton:hover {
|
|
1055
|
-
color:
|
|
1088
|
+
color: var(--headerFilterInput-icon-btn-text-hover);
|
|
1056
1089
|
}
|
|
1057
1090
|
|
|
1058
1091
|
/* Blue search confirm button */
|
|
@@ -1077,7 +1110,6 @@
|
|
|
1077
1110
|
gap: 4px;
|
|
1078
1111
|
padding: 6px 8px;
|
|
1079
1112
|
border-top: 1px solid #e5e7eb;
|
|
1080
|
-
background-color: #f9fafb;
|
|
1081
1113
|
}
|
|
1082
1114
|
|
|
1083
1115
|
.headerFilterTag {
|
package/package.json
CHANGED