From 33b8349931ff2fd8f1dc27d66da9143f027336c2 Mon Sep 17 00:00:00 2001 From: Tai-DucTran Date: Wed, 11 Sep 2024 17:29:48 +0700 Subject: [PATCH] Input: created the floating placeholder added new props height, border, borderRadius, isPlaceholderPloating, required --- src/@next/Input/Input.tsx | 53 +++++++++++++++++-- src/@next/Input/InputStyle.ts | 38 ++++++++++--- src/@next/NumberInput/NumberInput.tsx | 30 +++++++++-- src/@next/PasswordInput/PasswordInput.tsx | 22 +++++++- src/@next/TextArea/TextArea.stories.tsx | 23 ++++++++ src/@next/TextArea/TextArea.tsx | 24 +++++++++ src/@next/TextArea/TextAreaStyle.ts | 9 +++- src/@next/TextInput/TextInput.stories.tsx | 24 +++++++++ src/@next/TextInput/TextInput.tsx | 27 +++++++++- .../PhoneNumberInput.stories.tsx | 12 +++++ .../PhoneNumberInput/PhoneNumberInput.tsx | 27 ++++++++-- .../PhoneNumberInputStyles.ts | 7 ++- .../PhoneNumberInput.test.tsx.snap | 32 +++++------ 13 files changed, 289 insertions(+), 39 deletions(-) diff --git a/src/@next/Input/Input.tsx b/src/@next/Input/Input.tsx index dc225fe6a..8b3a82c23 100644 --- a/src/@next/Input/Input.tsx +++ b/src/@next/Input/Input.tsx @@ -1,5 +1,7 @@ import React, { useEffect, useRef } from 'react'; import { + AsteriskIcon, + FloatingLabel, StyledContainer, StyledInput, StyledPrefixContainer, @@ -9,6 +11,12 @@ import { export interface InputProps extends Omit, 'prefix'> { error?: boolean; + height?: string; + border?: string; + borderRadius?: string; + floatingFontSize?: string; + isPlaceholderFloating?: boolean; + required?: boolean; prefix?: React.ReactNode; suffix?: React.ReactNode; inputRef?: React.Ref; @@ -16,7 +24,21 @@ export interface InputProps export const Input = React.forwardRef( function Input( - { error, disabled, prefix, suffix, inputRef, ...props }: InputProps, + { + error, + disabled, + prefix, + suffix, + inputRef, + height, + border, + borderRadius, + floatingFontSize, + isPlaceholderFloating, + placeholder, + required, + ...props + }: InputProps, ref ) { const hasPrefix = !!prefix; @@ -27,12 +49,16 @@ export const Input = React.forwardRef( const Prefix = () => hasPrefix ? ( - {prefix} + + {prefix} + ) : null; const Suffix = () => hasSuffix ? ( - {suffix} + + {suffix} + ) : null; const [prefixWidth, setPrefixWidth] = React.useState(0); @@ -52,6 +78,8 @@ export const Input = React.forwardRef( } }, [hasSuffix, suffix]); + const selectedPlaceholder = required ? `${placeholder}*` : placeholder; + return ( ( suffixWidth={suffixWidth} > - + + {isPlaceholderFloating && ( + + {placeholder} + {required && *} + + )} ); diff --git a/src/@next/Input/InputStyle.ts b/src/@next/Input/InputStyle.ts index 0c6ccb999..6160ef298 100644 --- a/src/@next/Input/InputStyle.ts +++ b/src/@next/Input/InputStyle.ts @@ -5,6 +5,7 @@ import { InputProps } from './Input'; import { space12, space4, space8 } from '../utilities/spacing'; import { NotoSans } from '../utilities/fonts'; import { borderRadius4 } from '../utilities/borderRadius'; +import { Greyscale } from '../../Utils/Colors'; export interface PreffixSuffixWidthProps { prefixWidth: number; @@ -56,13 +57,15 @@ export const StyledContainer = styled.div` } `; -export const StyledPrefixContainer = styled.div` +export const StyledPrefixContainer = styled.div<{ + height?: string; +}>` position: absolute; left: 0; color: ${Neutral.B40}; display: flex; align-items: center; - height: 36px; + height: ${({ height }) => height ?? '36px'}; padding: 0px ${space8} 0 ${space12}; svg { @@ -72,18 +75,35 @@ export const StyledPrefixContainer = styled.div` } `; -export const StyledSuffixContainer = styled(StyledPrefixContainer)` +export const StyledSuffixContainer = styled(StyledPrefixContainer)<{ + height?: string; +}>` left: auto; right: 0; padding: 0px ${space12} 0 ${space4}; `; +export const FloatingLabel = styled.label<{ + top?: number; + left?: number; + fontSize?: string; +}>` + position: absolute; + left: ${({ left }) => left ?? '0.5'}em; + top: ${({ top }) => top ?? '-1.25'}em; + visibility: visible; + padding: 0 0.3em; + background: white; + font-size: ${({ fontSize }) => fontSize ?? '12px'}; + color: ${Greyscale.devilsgrey}; +`; + export const StyledInput = styled.input` background: ${Neutral.B100}; box-sizing: border-box; - border: 1px solid ${Neutral.B68}; - border-radius: ${borderRadius4}; + border: ${({ border }) => border ?? `1px solid ${Neutral.B68}`}; + border-radius: ${({ borderRadius }) => borderRadius ?? borderRadius4}; padding: 0 12px; font-family: ${NotoSans}, sans-serif; @@ -98,7 +118,7 @@ export const StyledInput = styled.input` order: 1; align-self: stretch; flex-grow: 0; - height: 36px; + height: ${({ height }) => height ?? `36px`}; &::placeholder { color: ${Neutral.B68}; @@ -115,3 +135,9 @@ export const StyledInput = styled.input` font-size: 14px; } `; + +export const AsteriskIcon = styled.span` + color: ${Red.B93}; + font-size: 20px; + vertical-align: middle; +`; diff --git a/src/@next/NumberInput/NumberInput.tsx b/src/@next/NumberInput/NumberInput.tsx index 083ed25d4..ff0104064 100644 --- a/src/@next/NumberInput/NumberInput.tsx +++ b/src/@next/NumberInput/NumberInput.tsx @@ -2,7 +2,12 @@ import React from 'react'; import styled from 'styled-components'; import { Input, InputProps } from '../Input/Input'; -export type NumberInputProps = Omit; +export type NumberInputProps = Omit & { + border?: string; + borderRadius?: string; + required?: boolean; + isPlaceholderFloating?: boolean; +}; const StyledInput = styled(Input)` &[type='number'] { @@ -17,7 +22,26 @@ const StyledInput = styled(Input)` `; export const NumberInput = React.forwardRef( - function NumberInput(props: NumberInputProps, ref) { - return ; + function NumberInput( + { + border, + borderRadius, + isPlaceholderFloating, + required, + ...props + }: NumberInputProps, + ref + ) { + return ( + + ); } ); diff --git a/src/@next/PasswordInput/PasswordInput.tsx b/src/@next/PasswordInput/PasswordInput.tsx index 36b7880fc..c8979527e 100644 --- a/src/@next/PasswordInput/PasswordInput.tsx +++ b/src/@next/PasswordInput/PasswordInput.tsx @@ -9,13 +9,28 @@ export type PasswordInputProps = Omit< > & { onChange?: (value: string) => void; error?: boolean; + height?: string; + border?: string; + borderRadius?: string; + required?: boolean; + isPlaceholderFloating?: boolean; }; export const PasswordInput = React.forwardRef< HTMLInputElement, PasswordInputProps >(function PasswordInput( - { value, onChange, error, ...props }: PasswordInputProps, + { + value, + onChange, + error, + height, + border, + borderRadius, + required, + isPlaceholderFloating, + ...props + }: PasswordInputProps, ref ) { const [showPassword, setShowPassword] = useState(false); @@ -44,6 +59,11 @@ export const PasswordInput = React.forwardRef< onChange={e => onChange?.(e.currentTarget.value)} error={error} suffix={!props.disabled && suffixComponent} + height={height} + border={border} + borderRadius={borderRadius} + required={required} + isPlaceholderFloating={isPlaceholderFloating} {...props} /> diff --git a/src/@next/TextArea/TextArea.stories.tsx b/src/@next/TextArea/TextArea.stories.tsx index 3702664f7..e0280e3eb 100644 --- a/src/@next/TextArea/TextArea.stories.tsx +++ b/src/@next/TextArea/TextArea.stories.tsx @@ -29,3 +29,26 @@ Interactive.args = { maxLength: 0, width: '520px', }; + +const WithFloatingPlaceholderTemplate: Story = args => { + const [value, setValue] = useState(''); + + return ( +