@spaced-out/ui-design-system
Version:
Sense UI components library
195 lines (178 loc) • 5.68 kB
Flow
// @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>
);
},
);