combobox: add stateless mode

This commit is contained in:
Elijah Duffy
2025-07-15 18:35:33 -07:00
parent 1ae9b3a34f
commit c8a7c37635
2 changed files with 17 additions and 13 deletions

View File

@@ -50,6 +50,8 @@
lazy?: boolean; lazy?: boolean;
/** uses a compact layout for the search input with less padding and smaller text */ /** uses a compact layout for the search input with less padding and smaller text */
compact?: boolean; compact?: boolean;
/** allows the user to select an option without selecting a value (events are still triggered) */
stateless?: boolean;
notFoundMessage?: string; notFoundMessage?: string;
class?: ClassValue | null | undefined; class?: ClassValue | null | undefined;
use?: () => void; use?: () => void;
@@ -77,6 +79,7 @@
loading = false, loading = false,
lazy = false, lazy = false,
compact = false, compact = false,
stateless = false,
notFoundMessage = 'No results found', notFoundMessage = 'No results found',
class: classValue, class: classValue,
use, use,
@@ -191,6 +194,13 @@
onclose?.(); // trigger onclose event if defined onclose?.(); // trigger onclose event if defined
}; };
/** updates the value of the combobox and triggers any callbacks, including closing the picker */
const updateValue = (newValue: ComboboxOption) => {
if (!stateless) value = newValue;
closePicker();
onchange?.(newValue);
};
/** action to set the minimum width of the combobox based on the number of options */ /** action to set the minimum width of the combobox based on the number of options */
const minWidth: Action<HTMLDivElement, { options: ComboboxOption[] }> = ( const minWidth: Action<HTMLDivElement, { options: ComboboxOption[] }> = (
container, container,
@@ -290,10 +300,7 @@
export const setValueByString = (searchVal: string) => { export const setValueByString = (searchVal: string) => {
const item = options.find((opt) => opt.value === searchVal); const item = options.find((opt) => opt.value === searchVal);
if (item) { if (item) {
value = item; updateValue(item);
searchValue = '';
closePicker();
onchange?.(item);
} else { } else {
console.warn(`Combobox: No option found with value "${searchVal}"`); console.warn(`Combobox: No option found with value "${searchVal}"`);
} }
@@ -393,7 +400,7 @@
aria-label={getLabel(item)} aria-label={getLabel(item)}
aria-disabled={item.disabled} aria-disabled={item.disabled}
class={[ class={[
'picker-item options-center mb-0.5 flex min-h-10 flex-wrap py-2.5 pr-1.5 pl-5', 'mb-0.5 flex min-h-10 flex-wrap items-center py-2.5 pr-1.5 pl-5',
'rounded-sm text-sm capitalize outline-hidden select-none', 'rounded-sm text-sm capitalize outline-hidden select-none',
'hover:bg-sui-accent-500/30 dark:hover:bg-sui-accent-700/30', 'hover:bg-sui-accent-500/30 dark:hover:bg-sui-accent-700/30',
item.value === highlighted?.value && 'bg-sui-accent-500/80 dark:bg-sui-accent-700/80', item.value === highlighted?.value && 'bg-sui-accent-500/80 dark:bg-sui-accent-700/80',
@@ -402,10 +409,8 @@
role="option" role="option"
onclick={() => { onclick={() => {
if (item.disabled) return; if (item.disabled) return;
value = item; updateValue(item);
closePicker();
searchInput?.focus(); searchInput?.focus();
onchange?.(item);
}} }}
onkeydown={() => {}} onkeydown={() => {}}
tabindex="-1" tabindex="-1"
@@ -523,14 +528,12 @@
if (e.key === 'Tab' || e.key === 'Enter') { if (e.key === 'Tab' || e.key === 'Enter') {
if (open && highlighted && !highlighted.disabled && highlighted.value !== value?.value) { if (open && highlighted && !highlighted.disabled && highlighted.value !== value?.value) {
value = highlighted; updateValue(highlighted);
onchange?.(highlighted);
} }
if (e.key === 'Enter') { if (e.key === 'Enter') {
closePicker();
e.preventDefault(); e.preventDefault();
} }
closePicker();
return; return;
} else if (e.key === 'Escape') { } else if (e.key === 'Escape') {
closePicker(); closePicker();

View File

@@ -136,7 +136,8 @@
<Combobox <Combobox
loading loading
label="Loading state combobox" label="Loading stateless combobox"
stateless
placeholder="Choose..." placeholder="Choose..."
options={[ options={[
{ value: 'option1', label: 'Option 1' }, { value: 'option1', label: 'Option 1' },