From 807e22a17b43e7b49fbd413d17bf5cf6b15ac512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Sim=C3=A3o?= Date: Fri, 2 Feb 2024 11:49:13 +0000 Subject: [PATCH] feat(components): add textarea --- packages/components/src/Input/BaseInput.tsx | 48 ++++++- packages/components/src/Input/Input.style.tsx | 7 +- packages/components/src/Input/Input.tsx | 5 +- .../src/TextArea/TextArea.stories.tsx | 98 ++++++++++++++ packages/components/src/TextArea/TextArea.tsx | 52 ++++++++ .../src/TextArea/__tests__/TextArea.test.tsx | 123 ++++++++++++++++++ packages/components/src/TextArea/index.tsx | 2 + 7 files changed, 326 insertions(+), 9 deletions(-) create mode 100644 packages/components/src/TextArea/TextArea.stories.tsx create mode 100644 packages/components/src/TextArea/TextArea.tsx create mode 100644 packages/components/src/TextArea/__tests__/TextArea.test.tsx create mode 100644 packages/components/src/TextArea/index.tsx diff --git a/packages/components/src/Input/BaseInput.tsx b/packages/components/src/Input/BaseInput.tsx index 474768bbe..2ab34c963 100644 --- a/packages/components/src/Input/BaseInput.tsx +++ b/packages/components/src/Input/BaseInput.tsx @@ -1,5 +1,15 @@ -import { Sizes } from '@interlay/theme'; -import { FocusEvent, forwardRef, InputHTMLAttributes, ReactNode, useEffect, useRef, useState } from 'react'; +import { Sizes, Spacing } from '@interlay/theme'; +import { + FocusEvent, + forwardRef, + InputHTMLAttributes, + ReactNode, + TextareaHTMLAttributes, + useEffect, + useRef, + useState +} from 'react'; +import { ElementTypeProp } from 'src/utils/types'; import { Field, FieldProps, useFieldProps } from '../Field'; import { HelperTextProps } from '../HelperText'; @@ -8,6 +18,19 @@ import { hasError } from '../utils/input'; import { Adornment, StyledBaseInput } from './Input.style'; +// TODO: might need to consolidate this later +interface HTMLInputProps extends ElementTypeProp { + elementType?: 'input'; + inputProps: InputHTMLAttributes; + onChange?: (e: React.ChangeEvent) => void; +} + +interface HTMLTextAreaProps extends ElementTypeProp { + elementType?: 'textarea'; + inputProps: TextareaHTMLAttributes; + onChange?: (e: React.ChangeEvent) => void; +} + type Props = { label?: ReactNode; labelProps?: LabelProps; @@ -18,11 +41,10 @@ type Props = { defaultValue?: string | ReadonlyArray | number; size?: Sizes; isInvalid?: boolean; - onChange?: (e: React.ChangeEvent) => void; + minHeight?: Spacing; onFocus?: (e: FocusEvent) => void; onBlur?: (e: FocusEvent) => void; - inputProps: InputHTMLAttributes; -}; +} & (HTMLInputProps | HTMLTextAreaProps); type InheritAttrs = Omit< HelperTextProps & @@ -33,7 +55,17 @@ type BaseInputProps = Props & InheritAttrs; const BaseInput = forwardRef( ( - { startAdornment, endAdornment, bottomAdornment, size = 'medium', isInvalid, inputProps, ...props }, + { + startAdornment, + endAdornment, + bottomAdornment, + size = 'medium', + isInvalid, + inputProps, + minHeight, + elementType = 'input', + ...props + }, ref ): JSX.Element => { const endAdornmentRef = useRef(null); @@ -54,12 +86,14 @@ const BaseInput = forwardRef( {startAdornment && {startAdornment}} {bottomAdornment && {bottomAdornment}} diff --git a/packages/components/src/Input/Input.style.tsx b/packages/components/src/Input/Input.style.tsx index 485482eff..6976bfcfc 100644 --- a/packages/components/src/Input/Input.style.tsx +++ b/packages/components/src/Input/Input.style.tsx @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import { theme } from '@interlay/theme'; +import { Spacing, theme } from '@interlay/theme'; import { Placement, Sizes } from '@interlay/theme'; type BaseInputProps = { @@ -8,6 +8,7 @@ type BaseInputProps = { $isDisabled: boolean; $hasError: boolean; $endAdornmentWidth: number; + $minHeight?: Spacing; }; type AdornmentProps = { @@ -60,6 +61,10 @@ const StyledBaseInput = styled.input` }}; padding-bottom: ${({ $adornments }) => ($adornments.bottom ? theme.spacing.spacing6 : theme.spacing.spacing2)}; + min-height: ${({ $minHeight, as }) => + $minHeight ? theme.spacing[$minHeight] : as === 'textarea' && theme.spacing.spacing16}; + resize: ${({ as }) => as === 'textarea' && 'vertical'}; + &:hover:not(:disabled):not(:focus) { border: ${(props) => !props.$isDisabled && !props.$hasError && theme.border.focus}; } diff --git a/packages/components/src/Input/Input.tsx b/packages/components/src/Input/Input.tsx index 620db899a..6e98e54dd 100644 --- a/packages/components/src/Input/Input.tsx +++ b/packages/components/src/Input/Input.tsx @@ -9,6 +9,7 @@ type Props = { value?: string; defaultValue?: string; onValueChange?: (value: string) => void; + onChange?: (e: React.ChangeEvent) => void; }; type InheritAttrs = Omit; @@ -18,13 +19,14 @@ type AriaAttrs = Omit, (keyof Props & InheritAttrs type InputProps = Props & InheritAttrs & AriaAttrs; const Input = forwardRef( - ({ isInvalid, onValueChange, onChange, ...props }, ref): JSX.Element => { + ({ isInvalid, onValueChange, onChange, elementType = 'input', ...props }, ref): JSX.Element => { const inputRef = useDOMRef(ref); // We are specifing `validationState` so that when there are errors, `aria-invalid` is set to `true` const { inputProps, descriptionProps, errorMessageProps, labelProps } = useTextField( { ...props, isInvalid: isInvalid || !!props.errorMessage, + inputElementType: elementType, onChange: onValueChange }, inputRef @@ -34,6 +36,7 @@ const Input = forwardRef( ; + +export const Default: StoryObj = {}; + +export const Controlled: StoryFn = (args) => { + const [state, setState] = useState(); + + return