import { useEffect, useRef, useState, useId } from 'react' import Block from '../../Primitives/Block/Block' import InputLabels from '../utils/InputLabels/InputLabels' import InputFooter from '../utils/InputFooter/InputFooter' import PrimitiveInput from '../../Primitives/PrimitiveInput/PrimitiveInput' import { TextInputSize } from '../TextInput/types' import { DropdownPosition, type DropdownInputProps } from './types' import type { DropdownInputTokensType } from './dropdownInput.tokens' import { toPixels } from '../../../global-utils/GlobalUtils' import { useResponsiveTokens } from '../../../hooks/useResponsiveTokens' import { useBreakpoints } from '../../../hooks/useBreakPoints' import { BREAKPOINTS } from '../../../breakpoints/breakPoints' import FloatingLabels from '../utils/FloatingLabels/FloatingLabels' import { SingleSelectV2, SingleSelectV2Size, SingleSelectV2Variant, SingleSelectV2Alignment, } from '../../SingleSelectV2' import { useTheme } from '../../../context/ThemeContext' import { useErrorShake } from '../../common/useErrorShake' import { getErrorShakeStyle, errorShakeAnimation, } from '../../common/error.animations' import styled from 'styled-components' const Wrapper = styled(Block)` ${errorShakeAnimation} ` const DropdownInput = ({ label, sublabel, disabled, helpIconHintText, name, required, error, errorMessage, hintText, value, onChange, slot, size = TextInputSize.MEDIUM, dropdownPosition = DropdownPosition.RIGHT, placeholder, dropDownValue, dropDownPlaceholder, onDropDownChange, dropDownItems, dropdownName, onDropdownOpen, onDropdownClose, onBlur, onFocus, maxMenuHeight, minMenuWidth, maxMenuWidth, ...rest }: DropdownInputProps) => { const { foundationTokens } = useTheme() const dropdownInputTokens = useResponsiveTokens('DROPDOWN_INPUT') const [isFocused, setIsFocused] = useState(false) const shouldShake = useErrorShake(error || false) const [slotWidth, setSlotWidth] = useState(0) const [dropdownWidth, setDropdownWidth] = useState(0) const { breakPointLabel } = useBreakpoints(BREAKPOINTS) const isSmallScreen = breakPointLabel === 'sm' const inputFocusedOrWithValue = isFocused || (value !== undefined && value.length > 0) const isSmallScreenWithLargeSize = isSmallScreen && size === TextInputSize.LARGE const slotRef = useRef(null) const dropdownRef = useRef(null) const paddingX = toPixels( dropdownInputTokens.inputContainer.padding.x[size] ) const paddingY = toPixels(dropdownInputTokens.inputContainer.padding.y[size]) + (isSmallScreenWithLargeSize ? 0.5 : 1) const GAP = toPixels(dropdownInputTokens.inputContainer.gap) const generatedId = useId() const inputId = rest.id || generatedId const errorId = `${inputId}-error` const hintId = `${inputId}-hint` const ariaDescribedBy = [ hintText && !error ? hintId : null, error && errorMessage ? errorId : null, ] .filter(Boolean) .join(' ') || undefined const handleDropdownOpenChange = (open: boolean) => { if (open) { onDropdownOpen?.() } else { onDropdownClose?.() } } const paddingInlineStart = dropdownPosition === DropdownPosition.LEFT ? paddingX + (slotWidth ? slotWidth : 0) + dropdownWidth + GAP : paddingX + (slotWidth ? slotWidth + GAP : 0) const paddingInlineEnd = dropdownPosition === DropdownPosition.RIGHT ? paddingX + (dropdownWidth ? dropdownWidth + 2 * GAP : 0) : paddingX // Helper function to check if a specific dropdown item is disabled // This accesses the `isDisabled` property from items in dropDownItems const isItemDisabled = (itemValue: string): boolean => { for (const group of dropDownItems) { for (const item of group.items) { if (item.value === itemValue) { // Check both `disabled` and `isDisabled` properties return item.disabled === true || item.isDisabled === true } // Check subMenu items if they exist if (item.subMenu) { for (const subItem of item.subMenu) { if (subItem.value === itemValue) { return ( subItem.disabled === true || subItem.isDisabled === true ) } } } } } return false } // Check if the currently selected dropdown item is disabled const isSelectedItemDisabled = dropDownValue ? isItemDisabled(dropDownValue) : false // Combine component-level disabled with selected item disabled state const isInputDisabled = disabled || isSelectedItemDisabled useEffect(() => { if (slotRef.current) { setSlotWidth(slotRef.current.offsetWidth) } else { setSlotWidth(0) } if (dropdownRef.current) { setDropdownWidth(dropdownRef.current.offsetWidth) } else { setDropdownWidth(0) } }, [slot, dropDownValue, dropDownPlaceholder, placeholder]) return ( {(!isSmallScreen || size !== TextInputSize.LARGE) && ( )} {slot && ( {slot} )} {label && isSmallScreenWithLargeSize && ( )} {!slot && dropdownPosition === DropdownPosition.LEFT && ( onDropDownChange?.(value)} onOpenChange={handleDropdownOpenChange} name={dropdownName} /> )} { if (!isInputDisabled) { onChange?.(e) } }} paddingInlineStart={paddingInlineStart} paddingInlineEnd={paddingInlineEnd} paddingTop={ label && isSmallScreenWithLargeSize && inputFocusedOrWithValue ? paddingY * 1.5 : paddingY } paddingBottom={ label && isSmallScreenWithLargeSize && inputFocusedOrWithValue ? paddingY / 2 : paddingY } placeholder={isSmallScreenWithLargeSize ? '' : placeholder} aria-required={required ? 'true' : undefined} aria-invalid={error ? 'true' : 'false'} aria-describedby={ariaDescribedBy} borderRadius={ dropdownInputTokens.inputContainer.borderRadius?.[size] } border={ error ? dropdownInputTokens.inputContainer.border.error : dropdownInputTokens.inputContainer.border.default } fontSize={dropdownInputTokens.inputContainer.fontSize[size]} fontWeight={ dropdownInputTokens.inputContainer.fontWeight[size] } outline="none" width={'100%'} backgroundColor={ dropdownInputTokens.inputContainer.backgroundColor[ error ? 'error' : 'default' ] } transition="border 200ms ease-in-out, box-shadow 200ms ease-in-out, background-color 200ms ease-in-out" placeholderStyles={{ transition: 'opacity 150ms ease-out', opacity: isFocused ? 0 : 1, }} _hover={{ border: dropdownInputTokens.inputContainer.border[ error ? 'error' : 'hover' ], backgroundColor: dropdownInputTokens.inputContainer.backgroundColor[ error ? 'error' : 'hover' ], }} color={ isInputDisabled ? dropdownInputTokens.inputContainer.color.disabled : dropdownInputTokens.inputContainer.color.default } _focus={{ border: dropdownInputTokens.inputContainer.border[ error ? 'error' : 'focus' ], outline: 'none !important', boxShadow: foundationTokens.shadows.focusPrimary, backgroundColor: dropdownInputTokens.inputContainer.backgroundColor .focus, }} disabled={isInputDisabled} _disabled={{ backgroundColor: dropdownInputTokens.inputContainer.backgroundColor .disabled, border: dropdownInputTokens.inputContainer.border .disabled, cursor: 'not-allowed', }} onFocus={(e) => { setIsFocused(true) onFocus?.(e) }} onBlur={(e) => { setIsFocused(false) onBlur?.(e) }} {...rest} /> {dropdownPosition === DropdownPosition.RIGHT && ( onDropDownChange?.(value)} onOpenChange={handleDropdownOpenChange} name={dropdownName} /> )} ) } export default DropdownInput