diff --git a/packages/components/src/Input/BaseInput.tsx b/packages/components/src/Input/BaseInput.tsx index 474768bbe..d81825f9d 100644 --- a/packages/components/src/Input/BaseInput.tsx +++ b/packages/components/src/Input/BaseInput.tsx @@ -1,6 +1,16 @@ -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 '../utils/types'; import { Field, FieldProps, useFieldProps } from '../Field'; import { HelperTextProps } from '../HelperText'; import { LabelProps } from '../Label'; @@ -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..68664fde5 100644 --- a/packages/components/src/Input/Input.tsx +++ b/packages/components/src/Input/Input.tsx @@ -9,14 +9,20 @@ type Props = { value?: string; defaultValue?: string; onValueChange?: (value: string) => void; + onChange?: (e: React.ChangeEvent) => void; }; -type InheritAttrs = Omit; +type InheritAttrs = Omit< + BaseInputProps, + keyof Props | 'errorMessageProps' | 'descriptionProps' | 'inputProps' | 'elementType' +>; type AriaAttrs = Omit, (keyof Props & InheritAttrs) | 'onChange'>; type InputProps = Props & InheritAttrs & AriaAttrs; +const elementType = 'input'; + const Input = forwardRef( ({ isInvalid, onValueChange, onChange, ...props }, ref): JSX.Element => { const inputRef = useDOMRef(ref); @@ -25,6 +31,7 @@ const Input = forwardRef( { ...props, isInvalid: isInvalid || !!props.errorMessage, + inputElementType: elementType, onChange: onValueChange }, inputRef @@ -34,6 +41,7 @@ const Input = forwardRef( void; + onChange?: (e: React.ChangeEvent) => void; inputMode?: 'numeric' | 'decimal'; }; -type InheritAttrs = Omit; +type InheritAttrs = Omit< + BaseInputProps, + keyof Props | 'errorMessageProps' | 'descriptionProps' | 'inputProps' | 'elementType' +>; type AriaAttrs = Omit, keyof (Props & InheritAttrs)>; type NumberInputProps = Props & InheritAttrs & AriaAttrs; +const elementType = 'input'; + // FIXME: some event are running duplicate const NumberInput = forwardRef( ( @@ -56,7 +62,8 @@ const NumberInput = forwardRef( inputMode, isInvalid: isInvalid || !!props.errorMessage, value: value, - autoComplete: 'off' + autoComplete: 'off', + inputElementType: elementType }, inputRef ); @@ -72,6 +79,7 @@ const NumberInput = forwardRef( ref={inputRef} autoCorrect='off' descriptionProps={descriptionProps} + elementType={elementType} errorMessageProps={errorMessageProps} inputProps={mergeProps(inputProps, { onChange: handleChange })} labelProps={labelProps} diff --git a/packages/components/src/TextArea/TextArea.stories.tsx b/packages/components/src/TextArea/TextArea.stories.tsx new file mode 100644 index 000000000..5581cde0e --- /dev/null +++ b/packages/components/src/TextArea/TextArea.stories.tsx @@ -0,0 +1,98 @@ +import { Meta, StoryFn, StoryObj } from '@storybook/react'; +import { useState } from 'react'; +import { InformationCircle } from '@interlay/icons'; + +import { Flex, Span } from '..'; + +import { TextArea, TextAreaProps } from '.'; + +export default { + title: 'Forms/TextArea', + component: TextArea, + parameters: { + layout: 'centered' + }, + args: { + label: 'Address' + } +} as Meta; + +export const Default: StoryObj = {}; + +export const Controlled: StoryFn = (args) => { + const [state, setState] = useState(); + + return