import { useEffect, forwardRef, cloneElement, isValidElement, useMemo, } from 'react' import { useRef, useState } from 'react' import Block from '../../Primitives/Block/Block' import PrimitiveInput from '../../Primitives/PrimitiveInput/PrimitiveInput' import { X } from 'lucide-react' import type { SearchInputProps } from './types' import type { SearchInputTokensType } from './searchInput.tokens' import { useResponsiveTokens } from '../../../hooks/useResponsiveTokens' import { FOUNDATION_THEME } from '../../../tokens' const toPixels = (value: string | number | undefined): number => { if (typeof value === 'number') { return value } if (typeof value === 'string') { // Remove 'px' and convert to number const numericValue = parseFloat(value.replace('px', '')) return isNaN(numericValue) ? 0 : numericValue } return 0 } const applyIconStyles = ( icon: React.ReactNode, tokens: SearchInputTokensType, disabled: boolean, error: boolean ): React.ReactNode => { if (!isValidElement(icon)) { return icon } const getIconColor = () => { if (disabled) return tokens.icon.color.disabled if (error) return tokens.icon.color.error return tokens.icon.color.default } return cloneElement( icon as React.ReactElement<{ style?: React.CSSProperties }>, { style: { ...((icon.props as { style?: React.CSSProperties }).style || {}), color: getIconColor(), width: tokens.icon.width, height: tokens.icon.width, }, } ) } const SearchInput = forwardRef( ( { leftSlot, rightSlot, error = false, placeholder = 'Enter', value, onChange, name, allowClear = true, onClear, clearIcon, ...rest }, ref ) => { const searchInputTokens = useResponsiveTokens('SEARCH_INPUT') const leftSlotRef = useRef(null) const rightSlotRef = useRef(null) const [leftSlotWidth, setLeftSlotWidth] = useState(0) const [rightSlotWidth, setRightSlotWidth] = useState(0) const [isFocused, setIsFocused] = useState(false) const isRequired = rest.required ?? false const showClearButton = allowClear && value && value.length > 0 const handleClear = () => { if (onClear) { onClear() } else if (onChange) { const syntheticEvent = { target: { value: '' }, currentTarget: { value: '' }, preventDefault: () => {}, stopPropagation: () => {}, } as React.ChangeEvent onChange(syntheticEvent) } } 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 paddingX = toPixels(searchInputTokens.inputContainer.padding.x) const paddingY = toPixels(searchInputTokens.inputContainer.padding.y) const GAP = toPixels(searchInputTokens.gap) const paddingInlineStart = leftSlot ? paddingX + leftSlotWidth + GAP : paddingX const paddingInlineEnd = effectiveRightSlot ? paddingX + rightSlotWidth + GAP : paddingX const styledLeftSlot = leftSlot ? applyIconStyles( leftSlot, searchInputTokens, rest.disabled || false, error ) : null const styledRightSlot = effectiveRightSlot ? applyIconStyles( effectiveRightSlot, searchInputTokens, rest.disabled || false, error ) : null return ( {styledLeftSlot && ( {styledLeftSlot} )} {styledRightSlot && ( {styledRightSlot} )} { setIsFocused(true) rest.onFocus?.(e) }} onBlur={(e) => { setIsFocused(false) rest.onBlur?.(e) }} {...rest} /> ) } ) export default SearchInput