diff --git a/src/components/TextInput/index.tsx b/src/components/TextInput/index.tsx index bf579fe..d98c26a 100644 --- a/src/components/TextInput/index.tsx +++ b/src/components/TextInput/index.tsx @@ -4,6 +4,7 @@ import { FocusEvent, InputHTMLAttributes, MouseEvent, + forwardRef, useRef, useState, } from 'react'; @@ -56,194 +57,210 @@ export type TextInputType = InputHTMLAttributes & | undefined; }; -const TextInput: FC = ({ - message, - error, - onChange, - onBlur, - onFocus, - onClickIconLeft, - onClickIconRight, - style, - label, - textInputStyle, - value, - id, - name, - maxLength, - autoFocus, - maskOptions, - iconRight, - iconLeft, - variant = 'default', - ...rest -}) => { - const [isFocused, setIsFocused] = useState(false); - - const hasValue = value?.length > 0; - const hasError = error ? error.length > 0 : false; - const hasFocus = isFocused || hasValue; - - const RightIconComponent: any = iconRight && Icons[iconRight]; - const LeftIconComponent: any = iconLeft && Icons[iconLeft]; - const ErrorIconComponent: any = Icons['ExclamationTriangleIcon']; - - const onAccept = (value: any) => { - if (onChange) { - onChange({ - target: { - name, - value, - }, - } as any); - } - }; +const TextInput: FC = forwardRef< + HTMLInputElement, + TextInputType +>( + ( + { + message, + error, + onChange, + onBlur, + onFocus, + onClickIconLeft, + onClickIconRight, + style, + label, + textInputStyle, + value, + id, + name, + maxLength, + autoFocus, + maskOptions, + iconRight, + iconLeft, + variant = 'default', + ...rest + }, + ref, + ) => { + const [isFocused, setIsFocused] = useState(false); - const handleFocus = () => { - setIsFocused(true); - }; + const hasValue = value?.length > 0; + const hasError = error ? error.length > 0 : false; + const hasFocus = isFocused || hasValue; - const handleBlur = (event: FocusEvent) => { - if (event.target.value === '') { - setIsFocused(false); - } - }; + const RightIconComponent: any = iconRight && Icons[iconRight]; + const LeftIconComponent: any = iconLeft && Icons[iconLeft]; + const ErrorIconComponent: any = Icons['ExclamationTriangleIcon']; + + const onAccept = (value: any) => { + if (onChange) { + onChange({ + target: { + name, + value, + }, + } as any); + } + }; + + const handleFocus = () => { + setIsFocused(true); + }; - const ref = useRef(null); - const inputRef = useRef(null); + const handleBlur = (event: FocusEvent) => { + if (event.target.value === '') { + setIsFocused(false); + } + }; + + const inputRef = useRef(null); + + return ( + + {variant === 'outlined' && ( + + )} - return ( - - {variant === 'outlined' && ( - - )} - - - {iconLeft && ( - - + + + )} + + {maskOptions ? ( + { + handleFocus(); + onFocus?.(event); + }} + onBlur={(event) => { + handleBlur(event); + onBlur?.(event); + }} + mask={hasFocus ? maskOptions?.mask : ''} + defaultValue={value} + $hasIconLeft={!!iconLeft} + $hasIconRight={!!iconRight} + $hasError={hasError} + $variant={variant} + $isDisabled={rest.disabled} + {...rest} /> - - )} + ) : ( + { + handleFocus(); + onFocus?.(event); + }} + onBlur={(event) => { + handleBlur(event); + onBlur?.(event); + }} + onChange={onChange} + maxLength={maxLength} + autoFocus={autoFocus} + $hasError={hasError} + $hasIconLeft={!!iconLeft} + $hasIconRight={!!iconRight} + $variant={variant} + $isDisabled={rest.disabled} + {...rest} + /> + )} + + {iconRight && ( + + + + )} - {maskOptions ? ( - { - handleFocus(); - onFocus?.(event); - }} - onBlur={(event) => { - handleBlur(event); - onBlur?.(event); - }} - mask={hasFocus ? maskOptions?.mask : ''} - defaultValue={value} + {variant === 'outlined' && ( +
+ + {label} + +
+ )} +
+ + {variant !== 'outlined' && ( + - ) : ( - { - handleFocus(); - onFocus?.(event); - }} - onBlur={(event) => { - handleBlur(event); - onBlur?.(event); - }} - onChange={onChange} - maxLength={maxLength} - autoFocus={autoFocus} - $hasError={hasError} - $hasIconLeft={!!iconLeft} - $hasIconRight={!!iconRight} - $variant={variant} - {...rest} - /> + $hasValue={hasValue} + > + {label} + )} - {iconRight && ( - - - - )} - - {variant === 'outlined' && ( -
- - {label} - -
- )} - - - {variant !== 'outlined' && ( - - {label} - - )} - - {error || message ? ( - - {variant === 'outlined' && } - {error || message} - - ) : null} -
- ); -}; + {variant === 'outlined' && } + {error || message} + + ) : null} +
+ ); + }, +); export default TextInput; diff --git a/src/components/TextInput/styles.tsx b/src/components/TextInput/styles.tsx index 7c91a2e..8a58ee5 100644 --- a/src/components/TextInput/styles.tsx +++ b/src/components/TextInput/styles.tsx @@ -20,11 +20,13 @@ type MessageProps = VariantProps & { type InputProps = VariantProps & { $hasError: boolean; + $isDisabled?: boolean; } & HasIcon; type InputWrapperProps = VariantProps & { $hasFocus: boolean; $hasError: boolean; + $isDisabled?: boolean; }; type IconProps = { @@ -35,8 +37,15 @@ type IconProps = { type LabelProps = { $hasFocus: boolean; $hasError: boolean; + $isDisabled?: boolean; } & HasIcon; +type FieldsetProps = { + $hasFocus: boolean; + $hasError: boolean; + $isDisabled?: boolean; +}; + const primaryMain = getTheme('brand.primary.main'); const dangerMain = getTheme('danger.main'); const fontSizeMin = getTheme('fontSizes.min'); @@ -63,6 +72,7 @@ const inputOutlinedStyles = ({ height: 30px; padding: 0; background: transparent; + margin-bottom: 0; ${$hasIconRight && `padding-right: ${pxToRem(36)};`} ${$hasIconLeft && `padding-left: ${pxToRem(36)};`} @@ -71,6 +81,10 @@ const inputOutlinedStyles = ({ &::placeholder { color: transparent; } + + &:disabled { + cursor: not-allowed; + } `; const inputDefaultStyles = ({ @@ -154,8 +168,9 @@ export const Message = styled.p` `}; `; -export const Wrapper = styled.div` - margin-bottom: ${spacingLg}px; +export const Wrapper = styled.div` + margin-bottom: ${({ $variant }) => + $variant === 'outlined' ? `0` : `${spacingLg}px`}; position: relative; `; @@ -197,10 +212,16 @@ export const Label = styled.label` color: ${hasError(dangerMain, primaryMain)}; padding: 0 ${pxToRem(4)}; `} + + ${({ $isDisabled }) => + $isDisabled && + css` + opacity: 0.5; + `} `; export const InputWrapper = styled.div` - ${({ $variant, $hasFocus }) => + ${({ $variant, $hasFocus, $isDisabled }) => $variant === 'outlined' && css` position: relative; @@ -213,16 +234,16 @@ export const InputWrapper = styled.div` border: ${$hasFocus ? 'none' : `1px solid #10141633`}; border-color: ${hasError(dangerMain, '')}; + ${$isDisabled && `opacity: 0.5`}; + &:hover { border-color: ${hasError(dangerMain, primaryMain)}; + ${$isDisabled && `border-color: #10141633`}; } `} `; -export const Fieldset = styled.fieldset<{ - $hasFocus: boolean; - $hasError: boolean; -}>` +export const Fieldset = styled.fieldset` position: absolute; top: ${pxToRem(-5)}; left: ${pxToRem(-1)}; @@ -235,6 +256,12 @@ export const Fieldset = styled.fieldset<{ pointer-events: none; display: ${({ $hasFocus }) => !$hasFocus && 'none'}; + ${({ $isDisabled }) => + $isDisabled && + css` + border: 1px solid #10141633; + `} + legend { width: auto; padding: 0 ${pxToRem(5)}; @@ -244,7 +271,7 @@ export const Fieldset = styled.fieldset<{ color: ${hasError(dangerMain, primaryMain)}; span { - visibility: ${(props) => props.$hasFocus && 'hidden'}; + visibility: ${({ $hasFocus }) => $hasFocus && 'hidden'}; } } `; diff --git a/src/components/TextInput/text-input.stories.tsx b/src/components/TextInput/text-input.stories.tsx index ae4cb87..38ba6ab 100644 --- a/src/components/TextInput/text-input.stories.tsx +++ b/src/components/TextInput/text-input.stories.tsx @@ -38,6 +38,10 @@ const meta: Meta = { type: 'string', defaultValue: 'default', }, + disabled: { + type: 'boolean', + defaultValue: false, + }, }, args: { id: mockTextId, @@ -48,6 +52,7 @@ const meta: Meta = { onFocus: events.onFocus, onClickIconLeft: events.onClickIconLeft, variant: 'default', + disabled: false, }, tags: ['autodocs'], };