import { useEffect, forwardRef, useMemo } from 'react' import { useRef, useState } from 'react' import Block from '../../Primitives/Block/Block' import PrimitiveInput from '../../Primitives/PrimitiveInput/PrimitiveInput' import { XIcon } from '@phosphor-icons/react' import type { SearchInputV2Props } from './SearchInputV2.types' import type { SearchInputV2TokensType } from './SearchInputV2.tokens' import { useResponsiveTokens } from '../../../hooks/useResponsiveTokens' import { filterBlockedProps } from '../../../utils/prop-helpers' import { applyIconStyles, createSearchInputV2ClearHandler, getSearchInputV2InputStateKey, getSearchInputV2PaddingInline, getSearchInputV2PrimitiveInputChrome, getSearchInputV2SlotWrapperStyle, shouldShowSearchInputV2Clear, toPixels, } from './utils' import { InputSizeV2 } from '../inputV2.types' const SearchInputV2 = forwardRef( ( { leftSlot, rightSlot, error = false, placeholder = 'Enter', value, onChange, name, allowClear = true, onClear, clearIcon, disabled = false, onFocus, onBlur, ...rest }, ref ) => { const filteredRest = filterBlockedProps(rest) const searchInputTokens = useResponsiveTokens('SEARCH_INPUT_V2') const leftSlotRef = useRef(null) const rightSlotRef = useRef(null) const [leftSlotWidth, setLeftSlotWidth] = useState(0) const [rightSlotWidth, setRightSlotWidth] = useState(0) const [isFocused, setIsFocused] = useState(false) const inputState = useMemo( () => getSearchInputV2InputStateKey(error, isFocused, disabled), [error, isFocused, disabled] ) const showClearButton = useMemo( () => shouldShowSearchInputV2Clear(allowClear, value, disabled), [allowClear, value, disabled] ) const handleClear = useMemo( () => createSearchInputV2ClearHandler({ disabled, onClear, onChange, }), [disabled, onClear, onChange] ) const effectiveRightSlot = useMemo( () => rightSlot ? rightSlot : showClearButton ? clearIcon || ( ) : undefined, [rightSlot, showClearButton, clearIcon] ) useEffect(() => { if (leftSlotRef.current) { setLeftSlotWidth(leftSlotRef.current.offsetWidth) } else { setLeftSlotWidth(0) } if (rightSlotRef.current) { setRightSlotWidth(rightSlotRef.current.offsetWidth) } else { setRightSlotWidth(0) } }, [leftSlot, effectiveRightSlot]) const paddingInlineStartBase = toPixels( searchInputTokens.inputContainer.paddingLeft[InputSizeV2.SM] ) const paddingInlineEndBase = toPixels( searchInputTokens.inputContainer.paddingRight[InputSizeV2.SM] ) const paddingY = toPixels( searchInputTokens.inputContainer.paddingTop[InputSizeV2.SM] ) const GAP = toPixels(searchInputTokens.gap) const { paddingInlineStart, paddingInlineEnd } = useMemo(() => { const { paddingInlineStart } = getSearchInputV2PaddingInline({ paddingX: paddingInlineStartBase, gap: GAP, hasLeftSlot: Boolean(leftSlot), leftSlotWidth, hasRightSlot: false, rightSlotWidth: 0, }) const { paddingInlineEnd } = getSearchInputV2PaddingInline({ paddingX: paddingInlineEndBase, gap: GAP, hasLeftSlot: false, leftSlotWidth: 0, hasRightSlot: Boolean(effectiveRightSlot), rightSlotWidth, }) return { paddingInlineStart, paddingInlineEnd } }, [ paddingInlineStartBase, paddingInlineEndBase, GAP, leftSlot, leftSlotWidth, effectiveRightSlot, rightSlotWidth, ]) const slotWrapperStyle = useMemo( () => getSearchInputV2SlotWrapperStyle( searchInputTokens.inputContainer.slot, inputState ), [searchInputTokens.inputContainer.slot, inputState] ) const primitiveChrome = useMemo( () => getSearchInputV2PrimitiveInputChrome( searchInputTokens.inputContainer, disabled, error ), [searchInputTokens.inputContainer, disabled, error] ) const styledLeftSlot = leftSlot ? applyIconStyles(leftSlot, searchInputTokens, inputState) : null const styledRightSlot = effectiveRightSlot ? applyIconStyles(effectiveRightSlot, searchInputTokens, inputState) : null return ( {styledLeftSlot && ( {styledLeftSlot} )} {styledRightSlot && ( { if ( !showClearButton || rightSlot || (event.key !== 'Enter' && event.key !== ' ') ) { return } event.preventDefault() handleClear() }} style={{ ...slotWrapperStyle, cursor: showClearButton && !rightSlot ? 'pointer' : undefined, }} > {styledRightSlot} )} { setIsFocused(true) onFocus?.(e) }} onBlur={(e) => { setIsFocused(false) onBlur?.(e) }} {...filteredRest} /> ) } ) SearchInputV2.displayName = 'SearchInputV2' export default SearchInputV2