diff --git a/changes/14102-fix-label-filter-select b/changes/14102-fix-label-filter-select new file mode 100644 index 0000000000..53543c9bfc --- /dev/null +++ b/changes/14102-fix-label-filter-select @@ -0,0 +1,2 @@ +- Fix a bug in which the manage page's label filter selection menu did not close when open and + clicked. Added some additional UX improvements around this component. diff --git a/frontend/pages/hosts/ManageHostsPage/components/LabelFilterSelect/LabelFilterSelect.tsx b/frontend/pages/hosts/ManageHostsPage/components/LabelFilterSelect/LabelFilterSelect.tsx index d6ce79e341..943e403ee2 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/LabelFilterSelect/LabelFilterSelect.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/LabelFilterSelect/LabelFilterSelect.tsx @@ -10,7 +10,6 @@ import CustomLabelGroupHeading from "../CustomLabelGroupHeading"; import { PLATFORM_TYPE_ICONS } from "./constants"; import { createDropdownOptions, IEmptyOption, IGroupOption } from "./helpers"; import CustomDropdownIndicator from "../CustomDropdownIndicator"; -import CustomValueContainer from "../CustomValueContainer"; // Extending the react-select module to add custom props we need for our custom // group heading. More info here: @@ -36,7 +35,7 @@ const baseClass = "label-filter-select"; * component. You will find focus and blur handlers in this component to help * solve the problem of changing focus between the select dropdown and the * label search input. */ -const OptionLabel = (data: ILabel | IEmptyOption) => { +const formatOptionLabel = (data: ILabel | IEmptyOption) => { const isLabel = "display_text" in data; const isPlatform = isLabel && data.type === "platform"; @@ -81,9 +80,9 @@ const LabelFilterSelect = ({ const [labelQuery, setLabelQuery] = useState(""); // we need the Select to be a controlled component to enable our label input - // to work correctly. shouldOpenMenu now becomes our single source of truth if + // to work correctly. menuIsOpen now becomes our single source of truth if // we want the menu to render open or closed. - const [shouldOpenMenu, setShouldOpenMenu] = useState(false); + const [menuIsOpen, setMenuIsOpen] = useState(false); const isLabelSearchInputFocusedRef = useRef(false); const selectRef = useRef< SelectInstance @@ -97,40 +96,47 @@ const LabelFilterSelect = ({ const handleChange = (option: ILabel | IEmptyOption | null) => { if (option === null) return; if ("type" in option) { - setShouldOpenMenu(false); + // typeof option === "ILabel" setLabelQuery(""); selectRef.current?.blur(); onChange(option); } }; - const handleLabelQueryChange = ( - event: React.ChangeEvent - ) => { + const toggleMenu = () => { + menuIsOpen && selectRef.current?.blur(); + setMenuIsOpen(!menuIsOpen); + }; + const onChangeLabelQuery = (event: React.ChangeEvent) => { // We need to stop the key presses propagation to prevent the dropdown from // picking up keypresses. event.stopPropagation(); setLabelQuery(event.target.value); }; - const handleBlurSelect = () => { + const onBlur = () => { if (!isLabelSearchInputFocusedRef.current) { isLabelSearchInputFocusedRef.current = false; - setShouldOpenMenu(false); + setMenuIsOpen(false); } }; - const handleFocusSelect = () => { - setShouldOpenMenu(true); + const onKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Escape") { + setMenuIsOpen(false); + selectRef.current?.blur(); + } else { + setMenuIsOpen(true); + } }; - const handleClickLabelSearchInput = () => { + const onClickLabelSearchInput = () => { isLabelSearchInputFocusedRef.current = true; }; - const handleBlurLabelSearchInput = () => { + const onBlurLabelSearchInput = () => { isLabelSearchInputFocusedRef.current = false; - setShouldOpenMenu(false); + setMenuIsOpen(false); }; const getOptionLabel = (option: ILabel | IEmptyOption) => { @@ -161,35 +167,40 @@ const LabelFilterSelect = ({ }; return ( - - ref={selectRef} - name="input-filter-select" - options={options} - className={classes} - classNamePrefix={baseClass} - defaultMenuIsOpen={false} - placeholder={"Filter by platform or label"} - formatOptionLabel={OptionLabel} - menuIsOpen={shouldOpenMenu} - value={selectedLabel} - isSearchable={false} - getOptionLabel={getOptionLabel} - getOptionValue={getOptionValue} - components={{ - GroupHeading: CustomLabelGroupHeading, - DropdownIndicator: CustomDropdownIndicator, - ValueContainer, - }} - labelQuery={labelQuery} - canAddNewLabels={canAddNewLabels} - onChange={handleChange} - onBlur={handleBlurSelect} - onFocus={handleFocusSelect} - onAddLabel={onAddLabel} - onChangeLabelQuery={handleLabelQueryChange} - onClickLabelSearchInput={handleClickLabelSearchInput} - onBlurLabelSearchInput={handleBlurLabelSearchInput} - /> +
+ + ref={selectRef} + name="input-filter-select" + className={classes} + classNamePrefix={baseClass} + defaultMenuIsOpen={false} + placeholder="Filter by platform or label" + value={selectedLabel} + isSearchable={false} + components={{ + GroupHeading: CustomLabelGroupHeading, + DropdownIndicator: CustomDropdownIndicator, + ValueContainer, + }} + onChange={handleChange} + closeMenuOnSelect + {...{ + menuIsOpen, + options, + formatOptionLabel, + getOptionLabel, + getOptionValue, + labelQuery, + canAddNewLabels, + onKeyDown, + onAddLabel, + onBlur, + onChangeLabelQuery, + onClickLabelSearchInput, + onBlurLabelSearchInput, + }} + /> +
); }; diff --git a/frontend/pages/hosts/ManageHostsPage/components/LabelFilterSelect/_styles.scss b/frontend/pages/hosts/ManageHostsPage/components/LabelFilterSelect/_styles.scss index 9fbc3357ba..a619168e3a 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/LabelFilterSelect/_styles.scss +++ b/frontend/pages/hosts/ManageHostsPage/components/LabelFilterSelect/_styles.scss @@ -32,7 +32,6 @@ box-shadow: none; border: 1px solid $core-vibrant-blue; } - &--is-focused, &--menu-is-open { .custom-dropdown-indicator { svg { @@ -101,6 +100,10 @@ .label-filter-select__option { padding: 10px 1rem; + &:hover { + cursor: pointer; + } + &--is-selected, &:active { background-color: $ui-vibrant-blue-25;