import React, { ReactElement, forwardRef, ChangeEvent, useState, useRef, useCallback, useEffect, } from 'react'; import { useTheme } from 'styled-components'; import css from '../../utils/css'; import StyledInput from '../Input/StyledInput'; import { getThemeState } from '../Input/utils'; import { TagInputWrapper, TagInputContainer } from './StyledTagInput'; import Affix from '../Input/Affix'; import { IconName } from '../Icon'; import Tag from '../Tag'; import { useResizeObserver } from '../../utils/hooks'; import { CommonProps } from '../common'; import { isNonEmptyArray } from '../../fp/NonEmptyArray'; export interface TagInputProps extends Omit { /** * Whether the input is disabled. */ disabled?: boolean; /** * Id of element. */ id?: string; /** * Whether the input is invalid */ invalid?: boolean; /** * Name of element, is used to refer to the form data for submission. */ name?: string; /** * Change event handler. Use `event.target.value` for new value */ onChange?: (e: ChangeEvent) => void; /** * Callback invoked when the user clicks the X button on a tag. Receives value of removed tag. */ onRemove?: (tag: { text: string | ReactElement; value?: string | number; }) => void; /** * Placeholder text in the absence of any value. */ placeholder?: string; /** * Name of Icon or an Icon element to render on the left side of the input, before the user's cursor. */ prefix?: IconName | ReactElement; /** * Whether or not Input's value is read only. */ readonly?: boolean; /** * The size of the input box. */ size?: 'small' | 'medium' | 'large'; /** * Name of Icon or an Icon element to render on the right side of the input. */ suffix?: IconName | ReactElement; /** * Array of tags to be rendered inside the input. */ tags: { text: string | ReactElement; value?: string | number; }[]; /** * The input's content value. */ value?: string; } const TagInput = forwardRef( ( { tags, size = 'medium', invalid = false, disabled = false, readonly = false, placeholder, onRemove, prefix, suffix, name, id, className, style, sx = {}, 'data-test-id': dataTestId, ...inputAttrs }: TagInputProps, forwardedRef ): ReactElement => { const theme = useTheme(); const themeHeightStrs = theme.sizes.input[size].match(/\d+/g); const themeHeight = parseInt( themeHeightStrs !== null && isNonEmptyArray(themeHeightStrs) ? themeHeightStrs[0] : '0', 10 ); const [wrapperHeight, setWrapperHeight] = useState( themeHeight ); const containerRef = useRef(null); const expandInput = useCallback( ({ height }) => { if (height > themeHeight) setWrapperHeight('auto'); else setWrapperHeight(themeHeight); }, [themeHeight, setWrapperHeight] ); useResizeObserver(expandInput, containerRef.current); useEffect(() => { if ( containerRef.current !== null && containerRef.current.clientHeight > themeHeight ) { setWrapperHeight('auto'); } }, [containerRef, themeHeight]); return ( {tags.map(tag => ( ))} 0 ? undefined : placeholder} readOnly={readonly} id={id} name={name} ref={forwardedRef} themePaddingLeft="none" themePaddingRight="none" {...inputAttrs} /> ); } ); TagInput.displayName = 'TagInput'; export default TagInput;