diff --git a/packages/components/news/7344.feature b/packages/components/news/7344.feature new file mode 100644 index 00000000000..19b05434507 --- /dev/null +++ b/packages/components/news/7344.feature @@ -0,0 +1 @@ +Multiselect widget seven @dobri1408 diff --git a/packages/components/src/components/MultiSelect/MultiSelect.quanta.stories.tsx b/packages/components/src/components/MultiSelect/MultiSelect.quanta.stories.tsx index a7ec83b95f1..da410f5b567 100644 --- a/packages/components/src/components/MultiSelect/MultiSelect.quanta.stories.tsx +++ b/packages/components/src/components/MultiSelect/MultiSelect.quanta.stories.tsx @@ -63,6 +63,18 @@ export default { control: 'boolean', description: 'Whether the user can create new options not in the list.', }, + isDisabled: { + control: 'boolean', + description: 'Whether the field is disabled.', + }, + isRequired: { + control: 'boolean', + description: 'Whether the field is required.', + }, + errorMessage: { + control: 'text', + description: 'Error message to display below the field.', + }, }, }; @@ -92,3 +104,87 @@ NonCreatable.args = { creatable: false, label: 'Non-Creatable Fruits', }; + +export const Disabled = Default.bind({}); + +Disabled.args = { + ...Default.args, + isDisabled: true, + label: 'Disabled Fruits', +}; + +export const Required = Default.bind({}); + +Required.args = { + ...Default.args, + isRequired: true, + label: 'Required Fruits', + description: 'This field is required', +}; + +export const WithError = (args: MultiSelectProps) => { + const selectedItems = useListData({ + initialItems: [], + }); + + return ( + + + + ); +}; + +WithError.args = { + ...Default.args, + errorMessage: 'Please select at least one fruit', + label: 'Fruits with Error', +}; + +export const Controlled = (args: MultiSelectProps) => { + const selectedItems = useListData({ + initialItems: [items[1]], + }); + + const handleChange = (newSelectedItems: Option[]) => { + // onChange este apelat cu starea actualizată + console.log('onChange called with:', newSelectedItems); + }; + + return ( + + + + Currently Selected: + + {selectedItems.items.length === 0 ? ( + Nothing selected + ) : ( + + {selectedItems.items.map((item) => ( + + {item.name} + + ))} + + Total: {selectedItems.items.length} items + + + )} + + + + ); +}; + +Controlled.args = { + ...Default.args, + label: 'Controlled Fruits', + description: 'This component demonstrates onChange callback', +}; diff --git a/packages/components/src/components/MultiSelect/MultiSelect.quanta.tsx b/packages/components/src/components/MultiSelect/MultiSelect.quanta.tsx index fff39fcbf2b..b10e8ddc66d 100644 --- a/packages/components/src/components/MultiSelect/MultiSelect.quanta.tsx +++ b/packages/components/src/components/MultiSelect/MultiSelect.quanta.tsx @@ -10,6 +10,7 @@ import { TagGroup, TagList, Tag, + type ValidationResult, } from 'react-aria-components'; import { Description, Label } from '../Field/Field.quanta'; import { useListData } from 'react-stately'; @@ -32,9 +33,15 @@ export interface MultiSelectProps { placeholder?: string; label?: string; description?: string; + errorMessage?: string | ((validation: ValidationResult) => string); creatable?: boolean; + onChange?: (selectedItems: Array) => void; + onBlur?: () => void; + onFocus?: () => void; onItemInserted?: (key: Key) => void; onItemCleared?: (key: Key) => void; + isDisabled?: boolean; + isRequired?: boolean; renderEmptyState?: (inputValue: string) => React.ReactNode; } @@ -44,11 +51,17 @@ export function MultiSelect({ items, className = '', description, + errorMessage, + onChange, + onBlur, + onFocus, onItemCleared, onItemInserted, renderEmptyState, selectedItems, creatable = true, + isDisabled = false, + isRequired = false, ...props }: MultiSelectProps) { const triggerRef = useRef(null); @@ -80,20 +93,21 @@ export function MultiSelect({ const onRemove = useCallback( (keys: Set) => { const key = keys.values().next().value; - if (key) { + if (key && !isDisabled) { selectedItems.remove(key); setFieldState({ inputValue: '', selectedKey: null, }); onItemCleared?.(key); + onChange?.(selectedItems.items); } }, - [selectedItems, onItemCleared], + [selectedItems, onItemCleared, onChange, isDisabled], ); const onSelectionChange = (id: Key | null) => { - if (!id) { + if (!id || isDisabled) { return; } @@ -110,6 +124,8 @@ export function MultiSelect({ selectedKey: id, }); onItemInserted?.(id); + // Call onChange with the updated array + setTimeout(() => onChange?.(selectedItems.items), 0); } accessibleList.setFilterText(''); @@ -140,6 +156,8 @@ export function MultiSelect({ }, []); const onCreateTag = useCallback(() => { + if (isDisabled) return; + const inputValue = fieldState.inputValue.trim(); const id = inputValue.toLocaleLowerCase(); @@ -159,11 +177,19 @@ export function MultiSelect({ accessibleList.append(item); selectedItems.append(item); + onChange?.(selectedItems.items); } - }, [fieldState.inputValue, accessibleList, selectedItems, setFieldState]); + }, [ + fieldState.inputValue, + accessibleList, + selectedItems, + setFieldState, + onChange, + isDisabled, + ]); const popLast = useCallback(() => { - if (selectedItems.items.length === 0) { + if (selectedItems.items.length === 0 || isDisabled) { return; } @@ -172,13 +198,14 @@ export function MultiSelect({ if (endKey) { selectedItems.remove(endKey.id); onItemCleared?.(endKey.id); + onChange?.(selectedItems.items); } setFieldState({ inputValue: '', selectedKey: null, }); - }, [selectedItems, onItemCleared]); + }, [selectedItems, onItemCleared, onChange, isDisabled]); const onKeyDownCapture = useCallback( (e: React.KeyboardEvent) => { @@ -202,7 +229,14 @@ export function MultiSelect({ {label && {label}} - + { setFieldState({ inputValue: '', selectedKey: null, }); accessibleList.setFilterText(''); + onBlur?.(); }} + onFocus={onFocus} onKeyDownCapture={onKeyDownCapture} className="min-w-0 flex-1 bg-white px-2 py-1.5 text-sm text-gray-800 outline-0 disabled:text-gray-200" /> @@ -317,6 +355,13 @@ export function MultiSelect({ {description && {description}} + {errorMessage && ( + + {typeof errorMessage === 'function' + ? errorMessage({} as ValidationResult) + : errorMessage} + + )} ); }