import React, { useId, useState } from 'react' import { Drawer, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerBody, } from '../Drawer' import Block from '../Primitives/Block/Block' import Text from '../Text/Text' import InputLabels from '../Inputs/utils/InputLabels/InputLabels' import InputFooter from '../Inputs/utils/InputFooter/InputFooter' import { useBreakpoints } from '../../hooks/useBreakPoints' import { BREAKPOINTS } from '../../breakpoints/breakPoints' import { useResponsiveTokens } from '../../hooks/useResponsiveTokens' import type { SingleSelectV2TokensType } from './singleSelectV2.tokens' import type { SingleSelectV2Props } from './singleSelectV2.types' import { SingleSelectV2Size, SingleSelectV2Variant, } from './singleSelectV2.types' import SingleSelectV2Trigger from './SingleSelectV2Trigger' import { SearchInputV2 } from '../InputsV2/SearchInputV2' import { Skeleton, SkeletonVariant } from '../Skeleton' import { setupAccessibility, getSingleSelectV2ValueLabelMap } from './utils' import { hasExactMatch as checkExactMatch, getFilteredItemsWithCustomValue, hasRenderableSelectItems, } from '../Select/selectUtils' import SingleSelectV2MobileItem from './mobile/SingleSelectV2MobileItem' import { filterMobileMenuGroups } from './mobile/singleSelectV2.mobile.utils' import { useSelectSearchController } from '../Select/useSelectSearchController' import SelectSearchStatus from '../Select/SelectSearchStatus' type MobileSingleSelectV2Props = SingleSelectV2Props const MobileSingleSelectV2: React.FC = ({ label, subLabel, hintText, required, helpIconText, placeholder, error = {}, size = SingleSelectV2Size.MD, items = [], variant = SingleSelectV2Variant.CONTAINER, open: controlledOpen, onOpenChange, selected, onSelect, search, slot, customTrigger, inline = false, triggerDimensions = { width: 'auto', minWidth: 'auto', maxWidth: 'auto', }, skeleton = { count: 3, show: false, variant: 'pulse', }, allowCustomValue = false, customValueLabel = 'Specify', ...rest }) => { const { disabled, name, ...buttonRest } = rest as { disabled?: boolean name?: string onFocus?: React.FocusEventHandler onBlur?: React.FocusEventHandler [key: string]: unknown } const { breakPointLabel } = useBreakpoints(BREAKPOINTS) const isSmallScreen = breakPointLabel === 'sm' const searchPlaceholder = search?.placeholder ?? 'Search options...' const singleSelectTokens = useResponsiveTokens('SINGLE_SELECT_V2') if (customTrigger !== undefined && !React.isValidElement(customTrigger)) { throw new Error( 'SingleSelectV2: customTrigger must be a valid React element.' ) } const menuItemTokens = singleSelectTokens?.menu?.item ?? { paddingTop: '0px', paddingRight: '0px', paddingBottom: '0px', paddingLeft: '0px', margin: '0px', borderRadius: '0px', gap: 4, backgroundColor: { default: 'transparent', hover: 'transparent', active: 'transparent', focus: 'transparent', focusVisible: 'transparent', disabled: 'transparent', selected: 'transparent', }, groupLabelText: { fontSize: '12px', fontWeight: 400, color: { default: 'inherit', hover: 'inherit', active: 'inherit', focus: 'inherit', focusVisible: 'inherit', disabled: 'inherit', selected: 'inherit', }, }, option: { fontSize: '14px', fontWeight: 400, color: { default: 'inherit', hover: 'inherit', active: 'inherit', focus: 'inherit', focusVisible: 'inherit', disabled: 'inherit', selected: 'inherit', }, }, description: { fontSize: '12px', fontWeight: 400, color: { default: 'inherit', hover: 'inherit', active: 'inherit', focus: 'inherit', focusVisible: 'inherit', disabled: 'inherit', selected: 'inherit', }, }, separator: { color: 'transparent', height: 1, margin: '0px', }, } const [internalPanelOpen, setInternalPanelOpen] = useState(false) const panelOpen = controlledOpen ?? internalPanelOpen const { value: searchText, isControlled: isSearchControlled, isSearchEnabled, shouldFilterInternally, valueForSearchBehavior, dispatchUserValue, resetUncontrolled, } = useSelectSearchController({ controlledValue: search?.searchText, onValueChange: search?.onSearchChange, explicitShow: search?.show, existingSurfaceDefault: false, }) const handlePanelOpenChange = (isOpen: boolean) => { if (controlledOpen === undefined) setInternalPanelOpen(isOpen) onOpenChange?.(isOpen) if (!isOpen && isSearchEnabled) resetUncontrolled() } const isActiveSearchLoading = isSearchEnabled && Boolean(search?.isSearchLoading) const valueLabelMap = getSingleSelectV2ValueLabelMap(items) const hasMatch = React.useMemo( () => checkExactMatch(valueForSearchBehavior, items), [valueForSearchBehavior, items] ) const filteredItems = React.useMemo(() => { const baseFilteredItems = shouldFilterInternally ? filterMobileMenuGroups(items, searchText) : items return getFilteredItemsWithCustomValue( baseFilteredItems, searchText, hasMatch, allowCustomValue, isSearchEnabled && !search?.isSearchLoading, customValueLabel ) }, [ items, searchText, allowCustomValue, hasMatch, isSearchEnabled, search?.isSearchLoading, shouldFilterInternally, customValueLabel, ]) const hasSourceItems = isSearchControlled ? hasRenderableSelectItems(items) : items.length > 0 const hasRenderableItems = isSearchControlled ? hasRenderableSelectItems(filteredItems) : filteredItems.length > 0 const showEmptyState = isSearchControlled ? !hasRenderableItems : !hasSourceItems || (!hasRenderableItems && searchText.length > 0) const isItemSelected = selected.length > 0 const isSmallScreenWithLargeSize = isSmallScreen && size === SingleSelectV2Size.LG const generatedId = useId() const { uniqueName, hintTextId, errorMessageId, ariaAttributes } = setupAccessibility({ name, generatedId, label, hintText, error: error.show, errorMessage: error.message, rest: buttonRest, prefix: 'singleselectv2-mobile', }) const triggerAriaAttributes = { 'aria-describedby': ariaAttributes['aria-describedby'] as | string | undefined, 'aria-labelledby': ariaAttributes['aria-labelledby'] as | string | undefined, 'aria-label': ariaAttributes['aria-label'] as string | undefined, 'aria-invalid': ariaAttributes['aria-invalid'] as boolean | undefined, } return ( {variant === SingleSelectV2Variant.CONTAINER && (!isSmallScreen || size !== SingleSelectV2Size.LG) && ( )} {customTrigger || ( )} {skeleton.show ? ( {Array.from({ length: skeleton.count || 3, }).map((_, index) => ( ))} ) : ( {isSearchEnabled && ( dispatchUserValue( e.target.value ) } aria-label={ searchPlaceholder || 'Search options' } /> )} {isActiveSearchLoading && !hasRenderableItems ? null : showEmptyState ? ( {search?.emptyStateText || (!hasSourceItems ? 'No items available' : 'No results found')} ) : ( {filteredItems.map( (group, groupId) => ( {group.groupLabel && ( { group.groupLabel } )} {group.items.map( ( item, itemIndex ) => { const isSelected = selected === item.value return ( { onSelect( value ) setInternalPanelOpen( false ) }} /> ) } )} {groupId !== filteredItems.length - 1 && group.showSeparator && ( )} ) )} )} )} {variant === SingleSelectV2Variant.CONTAINER && ( )} ) } export default MobileSingleSelectV2