diff --git a/CHANGELOG.md b/CHANGELOG.md index be383b28..5b0b71cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ # CHANGELOG +## 1.11.9 +- Allow empty `options` object for `ListBox` + ## 1.11.8 -- All select all and clear all options for `OptionCardsGroup` +- All select all and clear all methods for `OptionCardsGroup` - Fix label padding for `RadioTabGroup` ## 1.11.7 diff --git a/package.json b/package.json index 7fcc1b8d..801a4f13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fictoan-react", - "version": "1.11.9-alpha.6", + "version": "1.11.9-alpha.7", "private": false, "description": "A full-featured, designer-friendly, yet performant framework with plain-English props and focus on rapid iteration.", "repository": { diff --git a/src/components/Form/ListBox/ListBox.tsx b/src/components/Form/ListBox/ListBox.tsx index 25a3f77f..9ab32867 100644 --- a/src/components/Form/ListBox/ListBox.tsx +++ b/src/components/Form/ListBox/ListBox.tsx @@ -29,7 +29,7 @@ import { ListBoxProps, OptionForListBoxProps, ListBoxElementType, ListBoxCustomP const ListBoxWithOptions = ( { - options, + options = [], label, placeholder = "Select an option", id, @@ -48,14 +48,20 @@ const ListBoxWithOptions = ( isFullWidth, className, ...props - }: ListBoxCustomProps & { className ? : string }) => { + }: ListBoxCustomProps & { className?: string }) => { // STATES ========================================================================================================== - const [ isOpen, setIsOpen ] = useState(false); - const [ searchValue, setSearchValue ] = useState(""); - const [ activeIndex, setActiveIndex ] = useState(-1); - const [ selectedOption, setSelectedOption ] = useState(null); - const [ selectedOptions, setSelectedOptions ] = useState([]); + const [isOpen, setIsOpen] = useState(false); + const [searchValue, setSearchValue] = useState(""); + const [activeIndex, setActiveIndex] = useState(-1); + const [selectedOption, setSelectedOption] = useState(null); + const [selectedOptions, setSelectedOptions] = useState([]); + const [internalOptions, setInternalOptions] = useState(options); + + // Update internal options when external options change + useEffect(() => { + setInternalOptions(options); + }, [options]); // Set initial value =============================================================================================== useEffect(() => { @@ -70,7 +76,7 @@ const ListBoxWithOptions = ( // CONSTANTS ======================================================================================================= const listboxId = id || `listbox-${Math.random().toString(36).substring(2, 9)}`; - const filteredOptions = searchOptions(options, searchValue); + const filteredOptions = searchOptions(internalOptions, searchValue); // SELECT AN OPTION ================================================================================================ const handleSelectOption = (option: OptionForListBoxProps) => { @@ -119,6 +125,11 @@ const ListBoxWithOptions = ( label: customValue, }; + // Add to internal options if it doesn't exist + if (!internalOptions.some(opt => opt.value === customValue)) { + setInternalOptions(prev => [...prev, customOption]); + } + handleSelectOption(customOption); }; @@ -181,7 +192,7 @@ const ListBoxWithOptions = ( setActiveIndex(-1); break; - case " ": // Space key + case " ": // Space key if (!isOpen) { event.preventDefault(); setIsOpen(true); @@ -189,19 +200,19 @@ const ListBoxWithOptions = ( } break; - case "Home": - if (isOpen) { - event.preventDefault(); - setActiveIndex(0); - } - break; + case "Home": + if (isOpen) { + event.preventDefault(); + setActiveIndex(0); + } + break; - case "End": - if (isOpen) { - event.preventDefault(); - setActiveIndex(filteredOptions.length - 1); - } - break; + case "End": + if (isOpen) { + event.preventDefault(); + setActiveIndex(filteredOptions.length - 1); + } + break; } }; @@ -216,13 +227,13 @@ const ListBoxWithOptions = ( if (isOpen && searchInputRef.current) { searchInputRef.current.focus(); } - }, [ isOpen ]); + }, [isOpen]); // SCROLL ACTIVE OPTION INTO VIEW ================================================================================== useEffect(() => { if (activeIndex >= 0) { const activeOption = document.querySelector(`[data-index="${activeIndex}"]`); - activeOption?.scrollIntoView({ block: "nearest" }); + activeOption?.scrollIntoView({block : "nearest"}); } }, [activeIndex]); @@ -310,7 +321,7 @@ const ListBoxWithOptions = ( type="text" ref={searchInputRef} className="list-box-search" - placeholder="Search" + placeholder={allowCustomEntries ? "Type to search or add new" : "Search"} value={searchValue} onChange={handleSearchChange} onKeyDown={handleKeyDown} @@ -360,7 +371,10 @@ const ListBoxWithOptions = ( role="alert" aria-live="polite" > - No matches found + {allowCustomEntries + ? "Type and press Enter to add new option" + : "No matches found" + } )} diff --git a/src/components/Form/ListBox/constants.ts b/src/components/Form/ListBox/constants.ts index 5c84092a..9b997003 100644 --- a/src/components/Form/ListBox/constants.ts +++ b/src/components/Form/ListBox/constants.ts @@ -13,7 +13,7 @@ export interface OptionForListBoxProps { } export interface ListBoxCustomProps { - options : OptionForListBoxProps[]; + options ? : OptionForListBoxProps[]; label ? : string; helpText ? : string; errorText ? : string; diff --git a/src/components/Form/ListBox/list-box.css b/src/components/Form/ListBox/list-box.css index ab05b225..6c3644a2 100644 --- a/src/components/Form/ListBox/list-box.css +++ b/src/components/Form/ListBox/list-box.css @@ -21,7 +21,7 @@ } .list-box-input-wrapper { - padding : 12px 8px; + padding : var(--input-padding) 8px; border-radius : var(--input-border-radius-default); border : var(--global-border-width) solid var(--input-border-default); background-color : var(--list-box-bg-default);