Zum Hauptinhalt springen

MultipleCombobox

Note: Es gibt veraltete Versionen dieser Komponente. Wechseln Sie zu einer Version, die älter als 10.0.x ist, um die entsprechende Dokumentation anzuzeigen.

Beispiele​

Im Abschnitt Verwendung finden Sie Informationen darüber, wie und wann Sie MultipleCombobox verwenden können. Auf den speziellen Registerkarten finden Sie Einzelheiten zu den von der Komponente bereitgestellten Funktionen.

Der Hook useFilteredOptions wird verwendet, um den Filtermechanismus basierend auf Benutzereingaben zu implementieren. Aus Gründen der Übersichtlichkeit und Einfachheit ist er jedoch in einigen Beispielen nicht enthalten. Genauere Informationen zu dem Hook finden Sie hier.

Note: Die meisten Funktionen und Eigenschaften der vier verfügbaren Dropdown-Komponenten unterscheiden sich bei der Verwendung nicht. Einige Beispiele aus anderen Komponenten können auch für MultipleCombobox relevant sein.

Kombinationsfeld, gefiltert nach Benutzereingabe​

export function MultipleComboWithFilter() {
const CHARACTERS = [
{ id: '1', label: 'Anakin Skywalker' },
{ id: '2', label: 'Luke Skywalker' },
{ id: '3', label: 'Master Yoda' },
{ id: '4', label: 'Han Solo' },
];

const { filteredOptions, onSearch, resetFilter } =
useFilteredOptions(CHARACTERS);

return (
<MultipleCombobox
label="Select options"
secondaryLabel="Options filtered by user input"
onSearch={onSearch}
onBlur={resetFilter}>
{filteredOptions.map(({ id, label }) => (
<ComboboxOption
key={id}
value={{ id, label }}
/>
))}
</MultipleCombobox>
);
}

Kombinationsfeld mit Initialwert​

Definieren Sie einen Initialwert für die MultipleCombobox:

export function MultipleComboWithInitial() {
const CHARACTERS = [
{ id: '1', label: 'Anakin Skywalker' },
{ id: '2', label: 'Luke Skywalker' },
{ id: '3', label: 'Master Yoda' },
{ id: '4', label: 'Han Solo' },
];

const { filteredOptions, onSearch, resetFilter } =
useFilteredOptions(CHARACTERS);

return (
<MultipleCombobox
label="Select options"
secondaryLabel="Options filtered by user input"
initialValue={[CHARACTERS[2], CHARACTERS[3]]}
onSearch={onSearch}
onBlur={resetFilter}>
{filteredOptions.map(({ id, label }) => (
<ComboboxOption
key={id}
value={{ id, label }}
/>
))}
</MultipleCombobox>
);
}

Deaktivierte Dropdown-Komponente oder Optionen​

Sie können jede der Dropdown-Komponenten vollständig deaktivieren, indem Sie die Eigenschaft disabled auf true setzen. Mit der Eigenschaft disabled der Komponenten SelectOption und ComboboxOption können Sie auch nur die speziellen Optionen deaktivieren. Wenn die gesamte Komponente deaktiviert ist, hat die Eigenschaft disabled der Optionen keine Auswirkungen.

Deaktivierte Dropdown-Komponente​

Beispiel für eine deaktivierte Dropdown-Komponente unter Verwendung der MultipleCombobox-Komponente.

<MultipleCombobox
label="Your favorite character"
initialValue={[{ id: '1', label: 'Anakin' }]}
disabled={true}>
<ComboboxOption
value={{
id: '1',
label: 'Anakin',
}}
/>
<ComboboxOption
value={{
id: '2',
label: 'Luke',
}}
/>
</MultipleCombobox>

Deaktivierte Optionen​

Beispiel für die Deaktivierung einiger Dropdown-Optionen unter Verwendung der Komponente MultipleCombobox.

export function MultipleComboWithDisabled() {
const CHARACTERS = [
{ id: '1', label: 'Anakin Skywalker' },
{ id: '2', label: 'Luke Skywalker', unavailable: true },
{ id: '3', label: 'Master Yoda' },
{ id: '4', label: 'Han Solo', unavailable: true },
];

const { filteredOptions, onSearch, resetFilter } =
useFilteredOptions(CHARACTERS);

return (
<MultipleCombobox
label="Select options"
secondaryLabel="Options filtered by user input"
onSearch={onSearch}
onBlur={resetFilter}>
{filteredOptions.map(({ id, label, unavailable }) => (
<ComboboxOption
key={id}
value={{ id, label }}
disabled={unavailable}
/>
))}
</MultipleCombobox>
);
}

DisplayOnly-Dropdown​

Durch Festlegen der Eigenschaft displayOnly auf true wird der Komponentenwert als Klartext angezeigt.

Hier ein Beispiel:

<MultipleCombobox
label="Your favorite characters (Multiple Combobox component)"
initialValue={[{ id: '1', label: 'Anakin' }]}
displayOnly={true}>
<ComboboxOption
value={{
id: '1',
label: 'Anakin',
}}
/>
</MultipleCombobox>

Kontrollierte Komponente​

Dieses Verhalten ist bei allen Dropdown-Komponenten gleich: Wenn die value-Eigenschaft festgelegt ist, kann der Benutzer die Komponente nicht direkt ändern. Stattdessen wird sie vollständig über die Implementierung durch Entwickler verwaltet.

Dieses Beispiel gilt für alle Dropdown-Komponenten:

export function MultiComboControlled() {
const [updatedValue, setNewValue] = useState([]);

const onChange = (e, newValue) => {
setNewValue(newValue);
};

const options = [
<ComboboxOption
value={{
id: '1',
label: 'Option 1',
}}
/>,
<ComboboxOption
value={{
id: '2',
label: 'Option 2',
}}
/>,
<ComboboxOption
value={{
id: '3',
label: 'Option 3',
}}
/>,
<ComboboxOption
value={{
id: '4',
label: 'Option 4',
}}
/>,
<ComboboxOption
value={{
id: '5',
label: 'Option 5',
}}
/>,
];

return (
<div>
<MultipleCombobox
label="Choose values"
secondaryLabel="This value will be passed to the list of options below"
onChange={onChange}>
{options}
</MultipleCombobox>
<br />
<MultipleCombobox
label="Changes with the above"
value={updatedValue}>
{options}
</MultipleCombobox>
</div>
);
}

Fokus auf die Dropdown-Komponente setzen​

Mit der Eigenschaft ref und den imperativen Handlern („focus“, „blur“ und „scrollIntoView“) ist es möglich, verschiedene native Aktionen auszuführen. Ein Beispiel ist, die Komponente zu fokussieren.

Dies ist ein Beispiel unter Verwendung der Komponente MultipleCombobox:

export function MultiComboRef() {
const selectRef = useRef(null);

const setFocus = (e) => {
selectRef?.current?.focus();
};

return (
<div>
<Button
label="Set focus on Select"
onClick={setFocus}></Button>
<MultipleCombobox
label="Get the focus from the button"
ref={selectRef}>
<ComboboxOption
value={{
id: '1',
label: 'Option 1',
}}
/>
<ComboboxOption
value={{
id: '2',
label: 'Option 2',
}}
/>
</MultipleCombobox>
</div>
);
}

Komponentenvalidierung​

In den Dropdown-Komponenten lässt sich der Validierungsprozess nicht durchführen, aber Sie können den Fehlerstatus und die anzuzeigenden Meldungen über die Eigenschaft stateMessages bearbeiten.

Die Logik der Statusmeldungen gilt für alle Dropdown-Komponenten. Nachfolgend ein Beispiel für MultipleSelect:

export function MultiComboValidation() {
const [validationMessages, setValidationMessages] = useState({});

const onChange = useCallback((e, newValue) => {
setValidationMessages({});

if (newValue && newValue.length < 2) {
setValidationMessages({
error: ['At least 2 options must be selected'],
});
}
}, []);

return (
<MultipleCombobox
label="Select at least 2 options"
stateMessages={validationMessages}
onChange={onChange}>
<ComboboxOption
value={{
id: '1',
label: 'Option 1',
}}
/>
<ComboboxOption
value={{
id: '2',
label: 'Option 2',
}}
/>
<ComboboxOption
value={{
id: '3',
label: 'Option 3',
}}
/>
<ComboboxOption
value={{
id: '4',
label: 'Option 4',
}}
/>
<ComboboxOption
value={{
id: '5',
label: 'Option 5',
}}
/>
</MultipleCombobox>
);
}

Hook useFilteredOptions​

Beispiel-Implementierung​

Der Hook useFilteredOptions bietet eine grundlegende Datenverarbeitung für die Filterung.

import React, { useState } from 'react';
import { IntlMessageShape } from '@jutro/prop-types';
import { useTranslator } from '@jutro/locale';

type SelectValue = {
id: string;
label: IntlMessageShape;
[index: PropertyKey]: unknown;
};

/**
* @typedef {Object} FilterHookReturnObject
* @property {TValue[]} filteredOptions - array of filtered options
* @property {function} onSearch - function that takes the onSearch input event
* and filters the options based on it
* @property {function} resetFilter - used to reset the filtering for options,
* after using filteredOptions array resets to initial one
* @property {function} onAddNew - function that adds new value to the internal
* options array inside the hook and calls appendToSelection that is responsible
* for selecting this new value inside the dropdown
*/

/**
* Helper hook for filtering options based on user input.
*
* @param {TValue[]} initialOptions - initial list of options in component
* @param {function} createNewValue - function for constructing value object
* from string
* @param {function} appendToSelection - callback that is responsible for
* appending value to the controlled list of selected values
*
* @returns {FilterHookReturnObject} - helpers for filtering purposes:
* filtered options array, onAddNew and onSearch function to be supplied
* to component and resetFilter function
*/
export const useFilteredOptions = <TValue extends SelectValue = SelectValue>(
initialOptions: TValue[],
createNewValue?: (value: string) => TValue,
appendToSelection?: (value: TValue) => void
): {
filteredOptions: TValue[];
onSearch: (event: React.ChangeEvent<HTMLInputElement>) => void;
resetFilter: () => void;
onAddNew: (newValue: string) => void;
} => {
const translator = useTranslator();
const [query, setQuery] = useState('');
const [options, setOptions] = useState(initialOptions);

// Basic filtering. This part should be changed to cover your needs.
const filteredOptions = options.filter(val =>
translator(val.label)
.trim()
.toLowerCase()
.includes(query.trim().toLowerCase())
);

// Function that should be passed to the Combobox onSearch prop.
// It gets the value of the search term inside Combobox.
const onSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
setQuery(event.target.value);
};

// Function that is responsible for clearing the query
// only when the Combobox is on active browser tab.
// Typically connected to Combobox onBlur.
const resetFilter = () => {
if (document.visibilityState === 'visible' && document.hasFocus()) {
setQuery('');
}
};

// Function responsible on adding new values to both
// internal state of filtered items and external
// controlled state value
const onAddNew = (newValue: string) => {
const valueTransformed = createNewValue?.(newValue);

setQuery('');

if (valueTransformed) {
// adds the value to the internal state
setOptions(prevOptions => [...prevOptions, valueTransformed]);
// adds the value to the controlled state
appendToSelection?.(valueTransformed);
}
};

return { filteredOptions, onSearch, resetFilter, onAddNew };
};

Beispiel-Implementierung mit asynchronen API-Aufrufen​

Asynchrone API-Aufrufe werden nicht innerhalb des Basis-Hooks useFilteredOptions verarbeitet. Zu diesem Zweck müssen Sie möglicherweise eine eigene Implementierung erstellen, die auf Ihre speziellen Anforderungen zugeschnitten ist. Dies ist ein Beispiel für einen benutzerdefinierten Hook für die Komponente MultipleCombobox:

import React, { useEffect, useState } from 'react';

import { ComboboxOption, MultipleCombobox } from '@jutro/components';
import { useTranslator } from '@jutro/locale';
import { IntlMessageShape } from '@jutro/prop-types';

type SelectValue = {
id: string;
label: IntlMessageShape;
[index: PropertyKey]: unknown;
};

const someAPICallback = async query => {
console.log('calling the API');

const CHARACTERS = [
{ id: '1', label: 'Anakin Skywalker' },
{ id: '2', label: 'Luke Skywalker' },
{ id: '3', label: 'Master Yoda' },
{ id: '4', label: 'Han Solo' },
];

const result = new Promise(resolve => {
setTimeout(
() =>
resolve(
CHARACTERS.filter(val =>
val.label
.trim()
.toLowerCase()
.includes(query.trim().toLowerCase())
)
),
3000
);
});

return result;
};

const useFilteredOptions = <TValue extends SelectValue = SelectValue>(): {
filteredOptions: TValue[];
onSearch: (event: React.ChangeEvent<HTMLInputElement>) => void;
resetFilter: () => void;
loading: boolean;
} => {
const [query, setQuery] = useState('');
const [filteredOptions, setFilteredOptions] = useState([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
setLoading(true);

// This is a simple example only, the API callback should be debounced
// to ensure the async operation does not overlap with the query change.
someAPICallback(query).then((opts: TValue[]) => {
setFilteredOptions(opts);
setLoading(false);
});
}, [query]);

// Function that should be passed to the Combobox onSearch prop.
// It gets the value of the search term inside Combobox.
const onSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
setQuery(event.target.value);
};

// Function that is responsible for clearing the query
// only when the Combobox is on active browser tab.
// Typically connected to Combobox onBlur.
const resetFilter = () => {
if (document.visibilityState === 'visible' && document.hasFocus()) {
setQuery('');
}
};

return { filteredOptions, onSearch, resetFilter, loading };
};

export const Welcome: React.FC = () => {
const translator = useTranslator();
const { filteredOptions, onSearch, resetFilter, loading } =
useFilteredOptions();

return (
<MultipleCombobox
label="Select options"
secondaryLabel="Options filtered by user input"
onSearch={onSearch}
onBlur={resetFilter}
>
{loading ? (
<span>{translator('Loading...')}</span>
) : (
filteredOptions.map(({ id, label }) => (
<ComboboxOption key={id} value={{ id, label }} />
))
)}
</MultipleCombobox>
);
};

Expanded or collapsed tags​

The MultipleCombobox component's default behavior is to collapse option tags when they cannot fit in a single line. The component expands to a multiline view when you click on the tag containing aggregated options. This behavior is controlled by the tagsAlwaysExpanded property, and by default it's set to false.

export function MultipleComboboxAlwaysExpandedFalse() {
return (
<div>
<MultipleCombobox
label="Select options"
initialValue={[
{ id: '1', label: 'Option 1' },
{ id: '2', label: 'Option 2' },
{ id: '3', label: 'Option 3' },
{ id: '4', label: 'Option 4' },
{ id: '5', label: 'Option 5' },
{ id: '6', label: 'Option 6' },
{ id: '7', label: 'Option 7' },
{ id: '8', label: 'Option 8' },
]}>
<ComboboxOption
value={{
id: '1',
label: 'Option 1',
}}
/>
<ComboboxOption
value={{
id: '2',
label: 'Option 2',
}}
/>
<ComboboxOption
value={{
id: '3',
label: 'Option 3',
}}
/>
<ComboboxOption
value={{
id: '4',
label: 'Option 4',
}}
/>
<ComboboxOption
value={{
id: '5',
label: 'Option 5',
}}
/>
<ComboboxOption
value={{
id: '6',
label: 'Option 6',
}}
/>
<ComboboxOption
value={{
id: '7',
label: 'Option 7',
}}
/>
<ComboboxOption
value={{
id: '8',
label: 'Option 8',
}}
/>
</MultipleCombobox>
</div>
);
}

To display all option tags permanently in an expanded view, set the tagsAlwaysExpanded property to true.

export function MultipleComboboxAlwaysExpanded() {
return (
<div>
<MultipleCombobox
label="Select options"
tagsAlwaysExpanded={true}
initialValue={[
{ id: '1', label: 'Option 1' },
{ id: '2', label: 'Option 2' },
{ id: '3', label: 'Option 3' },
{ id: '4', label: 'Option 4' },
{ id: '5', label: 'Option 5' },
{ id: '6', label: 'Option 6' },
{ id: '7', label: 'Option 7' },
{ id: '8', label: 'Option 8' },
]}>
<ComboboxOption
value={{
id: '1',
label: 'Option 1',
}}
/>
<ComboboxOption
value={{
id: '2',
label: 'Option 2',
}}
/>
<ComboboxOption
value={{
id: '3',
label: 'Option 3',
}}
/>
<ComboboxOption
value={{
id: '4',
label: 'Option 4',
}}
/>
<ComboboxOption
value={{
id: '5',
label: 'Option 5',
}}
/>
<ComboboxOption
value={{
id: '6',
label: 'Option 6',
}}
/>
<ComboboxOption
value={{
id: '7',
label: 'Option 7',
}}
/>
<ComboboxOption
value={{
id: '8',
label: 'Option 8',
}}
/>
</MultipleCombobox>
</div>
);
}

Löschbare Eingabe​

To enable clearing the input with a single click on the icon button, set the clearable property to true. The button appears when at least one option is selected, or any text is typed. It allows the user to unselect the selected options and delete the typed text at once.
export function MultipleComboClearable() {
return (
<MultipleCombobox
label="Select characters"
clearable={true}>
<ComboboxOption value={{ id: '1', label: 'Anakin Skywalker' }} />
<ComboboxOption value={{ id: '2', label: 'Luke Skywalker' }} />
<ComboboxOption value={{ id: '3', label: 'Master Yoda' }} />
<ComboboxOption value={{ id: '4', label: 'Han Solo' }} />
</MultipleCombobox>
);
}