UNPKG

@spaced-out/ui-design-system

Version:
195 lines (178 loc) 5.68 kB
// @flow strict import * as React from 'react'; import {size200} from '../../styles/variables/_size'; import classify from '../../utils/classify'; import {Button, BUTTON_TYPES} from '../Button'; import type {IconType} from '../Icon'; import {ICON_TYPE} from '../Icon'; import {BodySmallBold, FormLabelSmall} from '../Text'; import {Textarea} from '../Textarea'; import css from './PromptInput.module.css'; const CONSTANTS = { DEFAULT_INPUT_PLACEHOLDER: 'Ask me anything', INPUT_NAME: 'prompt-textarea', BUTTON_TEXT: 'Generate', BUTTON_ICON: 'sparkles', BUTTON_ARIA_LABEL: 'Prompt Button', TEXT_AREA_ROWS: 1, }; type ClassNames = $ReadOnly<{ wrapper?: string, inputBox?: string, textarea?: string, buttonWrapper?: string, buttonIcon?: string, buttonText?: string, }>; export type PromptInputProps = { // textArea Props value?: string, onInputChange?: ( evt: SyntheticInputEvent<HTMLInputElement>, isEnter?: boolean, ) => mixed, onInputFocus?: (e: SyntheticInputEvent<HTMLInputElement>) => mixed, onInputBlur?: (e: SyntheticInputEvent<HTMLInputElement>) => mixed, onInputKeyDown?: (e: SyntheticKeyboardEvent<HTMLInputElement>) => mixed, inputName?: string, inputDisabled?: boolean, inputPlaceholder?: string, inputLocked?: boolean, inputError?: boolean, inputErrorText?: string, // Common props helperContent?: React.Node, textCountLimit?: number, classNames?: ClassNames, withPadding?: boolean, // Button Props buttonText?: string, buttonDisabled?: boolean, onButtonClick?: ?(SyntheticEvent<HTMLElement>) => mixed, buttonAriaLabel?: string, isButtonLoading?: boolean, buttonIconLeftName?: string, buttonIconLeftType?: IconType, }; export const PromptInput: React$AbstractComponent< PromptInputProps, HTMLDivElement, > = React.forwardRef<PromptInputProps, HTMLDivElement>( ( { value, onInputChange, onInputFocus, onInputBlur, onInputKeyDown, inputName = CONSTANTS.INPUT_NAME, inputDisabled, inputPlaceholder = CONSTANTS.DEFAULT_INPUT_PLACEHOLDER, inputLocked, inputError, inputErrorText, helperContent, textCountLimit, classNames, withPadding = true, buttonText = CONSTANTS.BUTTON_TEXT, buttonDisabled, onButtonClick, buttonAriaLabel = CONSTANTS.BUTTON_ARIA_LABEL, isButtonLoading, buttonIconLeftName = CONSTANTS.BUTTON_ICON, buttonIconLeftType = ICON_TYPE.solid, }: PromptInputProps, ref, ) => { const textareaRef = React.useRef(null); const handleInput = () => { const textarea = textareaRef.current; if (textarea) { textarea.style.height = 'auto'; // Reset height to auto to shrink if (textarea.scrollHeight > parseInt(size200)) { textarea.style.height = size200; // Limit height to size200 textarea.style.overflowY = 'auto'; } else { textarea.style.height = `${textarea.scrollHeight}px`; // Adjust to content height textarea.style.overflowY = 'hidden'; } } }; // Reset height on value change React.useEffect(() => { handleInput(); }, [value]); const textCountError = React.useMemo(() => { const charCount = value ? value.length : 0; return textCountLimit != null && charCount > textCountLimit; }, [value, textCountLimit]); return ( <div ref={ref} data-testid="PromptInput" className={classify( css.wrapper, {[css.styledPromptContainer]: withPadding}, classNames?.wrapper, )} > <div className={css.inputActionWrapper}> <Textarea classNames={{ box: classify(css.promptInputBox, classNames?.inputBox), textarea: classify(css.textarea, classNames?.textarea), }} ref={textareaRef} value={value} onChange={onInputChange} onFocus={onInputFocus} onBlur={onInputBlur} onKeyDown={onInputKeyDown} name={inputName} disabled={inputDisabled} placeholder={inputPlaceholder} locked={inputLocked} error={inputError || textCountError} errorText={inputErrorText} rows={CONSTANTS.TEXT_AREA_ROWS} ></Textarea> <Button iconLeftName={buttonIconLeftName} iconLeftType={buttonIconLeftType} onClick={onButtonClick} type={BUTTON_TYPES.gradient} disabled={buttonDisabled} ariaLabel={buttonAriaLabel} isLoading={isButtonLoading} classNames={{ wrapper: classify(css.actionButton, classNames?.buttonWrapper), icon: classNames?.buttonIcon, text: classNames?.buttonText, }} > {buttonText} </Button> </div> {(!!helperContent || !!textCountLimit) && ( <div className={css.secondaryRow}> {typeof helperContent === 'string' ? ( <BodySmallBold color={inputDisabled ? 'disabled' : 'secondary'}> {helperContent} </BodySmallBold> ) : ( helperContent )} <FormLabelSmall color={inputError || textCountError ? 'danger' : 'secondary'} className={css.textCounter} > {!!textCountLimit && ((value && value.length) || 0) + '/' + textCountLimit} </FormLabelSmall> </div> )} </div> ); }, );