import { Fragment, useMemo } from 'react' import { FlatList, Modal, Pressable, View, type StyleProp, type ViewStyle } from 'react-native' import type { ColorPairToken } from '@helpwave/hightide-design/theme-tokens' import { Chip } from '../visualization-and-display/Chip' import { ThemedText } from '../visualization-and-display/ThemedText' import { ThemedIcon } from '../visualization-and-display/ThemedIcon' import { ListActionItem } from '../list/ListActionItem' import { IconButton } from './IconButton' import { SearchBar } from './SearchBar' import { HightideIconRegistry } from '../../icons/HightideIconRegistry' import { useTheme } from '../../global-contexts/theme/ThemeContext' import { useMultiSelect, type UseMultiSelectOption } from '../../hooks/useMultiSelect' import type { MultiSelectState } from '../../theme/types/components/multiSelect' import type { FormFieldDataHandling, FormFieldInteractionStates } from '../../types/formField' import { useTranslation } from '@helpwave/hightide-utils/context' export type MultiSelectOption = UseMultiSelectOption export type MultiSelectProps = Partial> & Partial & { options: ReadonlyArray, value?: string[], initialValue?: string[], placeholder?: string, showSearch?: boolean, color?: ColorPairToken, style?: StyleProp, } export const MultiSelect = ({ options, value: controlledValue, initialValue = [], placeholder = 'Select…', showSearch = true, color, disabled = false, readOnly = false, invalid = false, onValueChange, onEditComplete, style, }: MultiSelectProps) => { const { theme } = useTheme() const translation = useTranslation() const interactive = !disabled && !readOnly const multiSelect = useMultiSelect({ options, value: controlledValue, initialValue, onValueChange, onEditComplete, }) const isSearchVisible = showSearch && options.length >= 6 const selectedOptions = useMemo(() => { return options.filter((option) => multiSelect.isSelected(option.id)) }, [multiSelect, options]) const state = useMemo((): MultiSelectState => ({ color, isDisabled: disabled, isReadonly: readOnly, isInvalid: invalid, isOpen: multiSelect.isOpen, hasSelections: selectedOptions.length > 0, hasValue: selectedOptions.length > 0, }), [color, disabled, invalid, multiSelect.isOpen, readOnly, selectedOptions.length]) const multiSelectTheme = theme.components.multiSelect const resolvedOverlayStyle = useMemo( () => multiSelectTheme.overlay({}), [multiSelectTheme] ) const resolvedMenuStyle = useMemo( () => multiSelectTheme.menu({ hasSearch: isSearchVisible }), [multiSelectTheme, isSearchVisible] ) const resolvedHeaderStyle = useMemo( () => multiSelectTheme.header({}), [multiSelectTheme] ) const resolvedEmptyTextStyle = useMemo( () => multiSelectTheme.emptyText({}), [multiSelectTheme] ) const visibleOptions = useMemo( () => options.filter((option) => multiSelect.visibleOptionIds.includes(option.id)), [options, multiSelect.visibleOptionIds] ) const showEmptySearchResults = isSearchVisible && multiSelect.searchQuery.trim().length > 0 && visibleOptions.length === 0 return ( multiSelect.toggleOpen()} style={(pressableState) => { const interaction = pressableState as { pressed: boolean, hovered?: boolean, focused?: boolean, focusVisible?: boolean, } return multiSelectTheme.trigger({ ...state, isPressed: interaction.pressed, isHovered: !!interaction.hovered, isFocused: !!interaction.focused, isFocusVisible: !!interaction.focusVisible, }) }} > {(pressableState) => { const interaction = pressableState as { pressed: boolean, hovered?: boolean, focused?: boolean, focusVisible?: boolean, } const triggerState = { ...state, isPressed: interaction.pressed, isHovered: !!interaction.hovered, isFocused: !!interaction.focused, isFocusVisible: !!interaction.focusVisible, } return ( {selectedOptions.length > 0 ? ( {selectedOptions.map((option) => ( {option.label ?? option.id} {!state.isReadonly && ( multiSelect.toggleSelection(option.id, false)} style={{ position: 'absolute', left: '50%', top: '50%', transform: [ { translateX: '-50%' }, { translateY: '-50%' }, ], }} icon={HightideIconRegistry.X} /> )} ))} ) : ( {placeholder} )} ) }} multiSelect.setIsOpen(false)} > multiSelect.setIsOpen(false)} > event.stopPropagation()} > {isSearchVisible && ( )} {showEmptySearchResults ? ( {translation('nothingFound')} ) : ( option.id} style={{ flex: 1 }} renderItem={({ item }) => { const selected = multiSelect.isSelected(item.id) const isHighlighted = multiSelect.highlightedId === item.id const optionState = { color, isSelected: selected, isHighlighted, isDisabled: item.disabled, } const checkboxIcon = multiSelectTheme.checkboxIcon(optionState) const optionColor = selected ? (color ?? theme.colors.primary) : undefined return ( multiSelect.toggleSelection(item.id)} leading={( )} /> ) }} /> )} ) }