From 92bd92b1eb7a7adcd47c26e562f9086dac67ef96 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Thu, 10 Apr 2025 07:05:29 -0300 Subject: [PATCH] fix: Improve Dropdown component handling of custom values and add regression test (#7486) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ (frontend): Add handleOnNewValue prop to Dropdown component to allow selecting a value not in the list 🔧 (frontend): Remove TODO comment and keep handleOnNewValue prop in DropdownComponent ✅ (frontend): Add test for selecting a value not in the list in Dropdown component * 🐛 (dropdownComponent/index.tsx): fix issue where custom value was not being added to validOptions and filteredOptions when pressing Enter ✨ (dropdownComponent/index.tsx): improve functionality to reset filtered options and custom value input when opening the dropdown * 🔧 (dropdownComponent/index.tsx): improve styling and layout of dropdown component for better user experience * ♻️ (dropdownComponent/index.tsx): remove unnecessary comments and improve code readability by removing redundant comments and separating render helper functions from logic blocks. * 📝 (dropdownComponent/index.tsx): add 'no-focus-visible' class to dropdown component to remove focus outline for better accessibility 📝 (applies.css): add styles for 'no-focus-visible' class to remove focus outline for better accessibility --- .../core/dropdownComponent/index.tsx | 80 +++++++++++++------ .../components/dropdownComponent/index.tsx | 2 +- src/frontend/src/style/applies.css | 8 ++ ...l-bugs-dropdown-select-not-in-list.spec.ts | 44 ++++++++++ 4 files changed, 108 insertions(+), 26 deletions(-) create mode 100644 src/frontend/tests/extended/regression/general-bugs-dropdown-select-not-in-list.spec.ts diff --git a/src/frontend/src/components/core/dropdownComponent/index.tsx b/src/frontend/src/components/core/dropdownComponent/index.tsx index a3e9da45e..0cefcaa83 100644 --- a/src/frontend/src/components/core/dropdownComponent/index.tsx +++ b/src/frontend/src/components/core/dropdownComponent/index.tsx @@ -52,9 +52,13 @@ export default function Dropdown({ handleNodeClass, name, dialogInputs, + handleOnNewValue, ...baseInputProps }: BaseInputProps & DropDownComponent): JSX.Element { - const validOptions = useMemo(() => filterNullOptions(options), [options]); + const validOptions = useMemo( + () => filterNullOptions(options), + [options, value], + ); // Initialize state and refs const [open, setOpen] = useState(children ? true : false); @@ -66,11 +70,12 @@ export default function Dropdown({ const refButton = useRef(null); value = useMemo(() => { - if (!options.includes(value)) { + if (!options.includes(value) && !filteredOptions.includes(value)) { return null; } return value; - }, [value, options]); + }, [value, options, filteredOptions]); + // Initialize utilities and constants const placeholderName = name ? formatPlaceholderName(name) @@ -101,14 +106,24 @@ export default function Dropdown({ const searchRoleByTerm = async (event: ChangeEvent) => { const value = event.target.value; + setCustomValue(value); + + if (!value) { + // If search is cleared, show all options + setFilteredOptions(validOptions); + setFilteredMetadata(optionsMetaData); + return; + } + + // Search existing options const searchValues = fuse.search(value); const filtered = searchValues.map((search) => search.item); // Update filteredOptions with the search results - setFilteredOptions(value ? filtered : validOptions); + setFilteredOptions(filtered); // Update filteredMetadata to match the filtered options - if (value && optionsMetaData) { + if (optionsMetaData) { const newMetadata = filtered.map((option) => { const originalIndex = validOptions.indexOf(option); return optionsMetaData[originalIndex]; @@ -117,8 +132,6 @@ export default function Dropdown({ } else { setFilteredMetadata(optionsMetaData); } - - setCustomValue(value); }; const handleRefreshButtonPress = async () => { @@ -166,15 +179,27 @@ export default function Dropdown({ useEffect(() => { if (open) { - const filtered = cloneDeep(validOptions); - if (customValue === value && value && combobox) { - filtered.push(customValue); - } - setFilteredOptions(filtered); + setFilteredOptions(validOptions); + setCustomValue(""); } - }, [open]); + }, [open, validOptions]); - // Render helper functions + const handleInputKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + if (open && customValue) { + const newOptions = [...validOptions]; + if (!newOptions.includes(customValue)) { + newOptions.push(customValue); + } + + setFilteredOptions(newOptions); + + handleOnNewValue?.({ value: customValue }); + onSelect(customValue); + setOpen(false); + } + } + }; const renderLoadingButton = () => (