-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FE] Input 컴포넌트 관련한 기능 수정, useDynamic-* 훅 관련 오류 수정, 네이밍 통일, 의미를 명확하게 담도록 네이밍 수정 #183
Changes from 41 commits
7ff3e92
add91ca
d1570e9
7c3f5f7
9dcb6ec
b44b5e0
9d4d9c9
28c1759
537905b
976d797
7701d7b
261742d
014363e
6547b10
5aca949
35be82d
35ff11f
4b16db6
c729152
b86d143
0df0ce2
9a40e2c
e91c87f
f767e00
895b8f2
2f9d538
a1af31c
ec0a285
a4942e8
d2419dd
0a6c763
da8070f
7fb5f31
0c31cab
e00b2b6
9be9ed0
8dca946
fcc18c3
d8468cb
5a10d81
2a848b9
332f11f
4e32498
95b0391
c16864c
48d1355
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ import {Theme} from '@theme/theme.type'; | |
|
||
import {InputType} from './Input.type'; | ||
|
||
const inputBoxBackgroundColorByInputType = (theme: Theme, inputType: InputType = 'input') => { | ||
const getBackgroundColorStyle = (theme: Theme, inputType: InputType = 'input') => { | ||
switch (inputType) { | ||
case 'input': | ||
return theme.colors.lightGrayContainer; | ||
|
@@ -17,6 +17,9 @@ const inputBoxBackgroundColorByInputType = (theme: Theme, inputType: InputType = | |
} | ||
}; | ||
|
||
const getBorderStyle = (isFocus: boolean, theme: Theme, isError?: boolean) => | ||
isError ? `0 0 0 1px ${theme.colors.error} inset` : isFocus ? `0 0 0 1px ${theme.colors.primary} inset` : 'none'; | ||
|
||
export const inputBoxStyle = ( | ||
theme: Theme, | ||
inputType: InputType = 'input', | ||
|
@@ -26,13 +29,13 @@ export const inputBoxStyle = ( | |
css({ | ||
display: 'flex', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Flex 컴포넌트를 사용해봐도 좋을 것 같아요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. props로 속성을 넘기는게 좋을 것 같았는데 적용해야할 스타일이 너무 길어지면 결국 css를 먹이는게 나을 것 같기도 하고용.. 지금 생각이 난 방법은 플렉스 방향, justify, align 정도만 props로 받고 그 외는 css로 받으면 어떤가 싶기도 한데요. 음.. 어떻게 해야할지 고민 해볼게요.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아래 코드처럼 작성하는건 어떻게 생각하시나용? 위에서 말한 방법과 용어만 다를 뿐 방법은 똑같습니다. const Flex = ({ align, direction, split, children, style }) => {
return (
<FlexContainer
style={{ alignItems: align_items(align), flexDirection: flex_direction(direction), ...style }}
> |
||
justifyContent: 'space-between', | ||
|
||
gap: '1rem', | ||
padding: '0.75rem 1rem', | ||
borderRadius: '1rem', | ||
backgroundColor: inputBoxBackgroundColorByInputType(theme, inputType), | ||
backgroundColor: getBackgroundColorStyle(theme, inputType), | ||
|
||
boxSizing: 'border-box', | ||
outline: isFocus ? `1px solid ${theme.colors.primary}` : isError ? `1px solid ${theme.colors.error}` : 'none', | ||
boxShadow: getBorderStyle(isFocus, theme, isError), | ||
}); | ||
|
||
export const inputStyle = (theme: Theme) => | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,28 @@ | ||
/** @jsxImportSource @emotion/react */ | ||
import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react'; | ||
import React, {forwardRef, useImperativeHandle, useRef} from 'react'; | ||
|
||
import IconButton from '@components/IconButton/IconButton'; | ||
import {InputProps} from '@components/Input/Input.type'; | ||
import {inputBoxStyle, inputStyle} from '@components/Input/Input.style'; | ||
import {useInput} from '@components/Input/useInput'; | ||
import {useTheme} from '@/theme/HDesignProvider'; | ||
|
||
import {useTheme} from '@theme/HDesignProvider'; | ||
import IconButton from '../IconButton/IconButton'; | ||
|
||
import {useInput} from './useInput'; | ||
import {InputProps} from './Input.type'; | ||
import {inputBoxStyle, inputStyle} from './Input.style'; | ||
|
||
export const Input: React.FC<InputProps> = forwardRef<HTMLInputElement, InputProps>(function Input( | ||
{value: propsValue, onChange, inputType, isError, ...htmlProps}: InputProps, | ||
{value: propsValue, onChange, onFocus, onBlur, inputType, isError, placeholder, ...htmlProps}: InputProps, | ||
ref, | ||
) { | ||
const {theme} = useTheme(); | ||
const inputRef = useRef<HTMLInputElement>(null); | ||
|
||
useImperativeHandle(ref, () => inputRef.current!); | ||
|
||
const {value, hasFocus, handleChange, handleClickDelete, toggleFocus} = useInput({propsValue, onChange, inputRef}); | ||
const inputRef = useRef<HTMLInputElement>(null); | ||
const {value, handleChange, hasFocus, handleClickDelete, handleBlur, handleFocus, handleKeyDown} = useInput({ | ||
propsValue, | ||
onChange, | ||
onBlur, | ||
onFocus, | ||
inputRef, | ||
}); | ||
|
||
return ( | ||
<div css={inputBoxStyle(theme, inputType, hasFocus, isError)}> | ||
|
@@ -26,8 +31,10 @@ export const Input: React.FC<InputProps> = forwardRef<HTMLInputElement, InputPro | |
ref={inputRef} | ||
value={value} | ||
onChange={handleChange} | ||
onFocus={toggleFocus} | ||
onBlur={toggleFocus} | ||
onBlur={handleBlur} | ||
onFocus={handleFocus} | ||
placeholder={inputRef.current === document.activeElement ? '' : placeholder} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. activeElement라는 것이 있는지 처음 알았어요👍👍 |
||
onKeyDown={handleKeyDown} | ||
{...htmlProps} | ||
/> | ||
{value && hasFocus && <IconButton iconType="inputDelete" onClick={handleClickDelete} />} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,39 +3,66 @@ import {RefObject, useEffect, useState} from 'react'; | |
interface UseInputProps<T> { | ||
propsValue: T; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 훅 내부에서 as 키워드를 사용해 강제로 string으로 타입 변환을 시켜주고 있는 것 같아요. 그래서 T type을 string으로 고정시켜서 제네릭을 없애는 방향으로 가는 것은 어떨까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋은 것 같아요~ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generic을 없애는 방법 말고, 전체적으로 제너릭을 받을 수 있는 코드로 수정했습니다~ |
||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void; | ||
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void; | ||
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void; | ||
inputRef: RefObject<HTMLInputElement>; | ||
autoFocus?: boolean; | ||
} | ||
|
||
export const useInput = <T>({propsValue, onChange, inputRef}: UseInputProps<T>) => { | ||
const [value, setValue] = useState(propsValue || ''); | ||
const [hasFocus, setHasFocus] = useState(false); | ||
export const useInput = <T>({propsValue, onChange, onBlur, onFocus, inputRef, autoFocus}: UseInputProps<T>) => { | ||
const [value, setValue] = useState<T>(propsValue); | ||
const [hasFocus, setHasFocus] = useState(inputRef.current === document.activeElement); | ||
|
||
useEffect(() => { | ||
setValue(propsValue || ''); | ||
}, [propsValue]); | ||
setHasFocus(inputRef.current === document.activeElement); | ||
}, []); | ||
|
||
const handleClickDelete = () => { | ||
setValue(''); | ||
|
||
if (inputRef.current) { | ||
inputRef.current.focus(); | ||
} | ||
useEffect(() => { | ||
setValue(propsValue); | ||
}, [value]); | ||
|
||
const handleClickDelete = (event: React.MouseEvent) => { | ||
event.preventDefault(); | ||
setValue('' as T); | ||
if (onChange) { | ||
onChange({target: {value: ''}} as React.ChangeEvent<HTMLInputElement>); | ||
} | ||
if (inputRef.current) { | ||
inputRef.current.focus(); | ||
} | ||
}; | ||
|
||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
setValue(e.target.value); | ||
setValue(e.target.value as T); | ||
if (onChange) { | ||
onChange(e); | ||
} | ||
}; | ||
|
||
const toggleFocus = () => { | ||
setHasFocus(!hasFocus); | ||
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => { | ||
setHasFocus(false); | ||
if (onBlur) { | ||
onBlur(e); | ||
} | ||
}; | ||
|
||
const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => { | ||
setHasFocus(true); | ||
if (onFocus) { | ||
onFocus(e); | ||
} | ||
}; | ||
|
||
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => { | ||
if (event.nativeEvent.isComposing) return; | ||
|
||
if (event.key === 'Enter' || event.key === 'Escape') { | ||
setHasFocus(false); | ||
if (inputRef.current) { | ||
inputRef.current.blur(); | ||
} | ||
} | ||
}; | ||
|
||
return {value, hasFocus, handleChange, handleClickDelete, toggleFocus}; | ||
return {value, handleChange, hasFocus, handleClickDelete, handleBlur, handleFocus, handleKeyDown}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/** @jsxImportSource @emotion/react */ | ||
|
||
import {forwardRef, useEffect, useImperativeHandle, useRef, useState} from 'react'; | ||
|
||
import Input from '../Input/Input'; | ||
|
||
import {ElementProps} from './Element.type'; | ||
import {useGroupInputContext} from './GroupInputContext'; | ||
|
||
const Element: React.FC<ElementProps> = forwardRef<HTMLInputElement, ElementProps>(function Element( | ||
{elementKey, value: propsValue, onChange, onBlur, onFocus, isError, ...htmlProps}: ElementProps, | ||
|
||
ref, | ||
) { | ||
useImperativeHandle(ref, () => inputRef.current!); | ||
const inputRef = useRef<HTMLInputElement>(null); | ||
const {setHasAnyFocus, values, setValues, hasAnyErrors, setHasAnyErrors} = useGroupInputContext(); | ||
|
||
useEffect(() => { | ||
setValues({...values, [elementKey]: `${propsValue}`}); | ||
}, [propsValue]); | ||
|
||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
const newValue = e.target.value; | ||
setValues({...values, [elementKey]: newValue}); | ||
if (onChange) { | ||
onChange(e); | ||
} | ||
}; | ||
|
||
useEffect(() => { | ||
setHasAnyErrors({...hasAnyErrors, [elementKey]: isError ?? false}); | ||
}, [isError]); | ||
|
||
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => { | ||
setHasAnyFocus(false); | ||
if (onBlur) { | ||
onBlur(e); | ||
} | ||
}; | ||
|
||
const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => { | ||
setHasAnyFocus(true); | ||
if (onFocus) { | ||
onFocus(e); | ||
} | ||
}; | ||
|
||
return ( | ||
<Input | ||
ref={inputRef} | ||
isError={isError} | ||
value={propsValue} | ||
onChange={handleChange} | ||
onBlur={handleBlur} | ||
onFocus={handleFocus} | ||
{...htmlProps} | ||
/> | ||
); | ||
}); | ||
|
||
export default Element; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
export interface ElementStyleProps {} | ||
|
||
export interface ElementCustomProps { | ||
elementKey: string; | ||
isError?: boolean; | ||
} | ||
|
||
export type ElementOptionProps = ElementStyleProps & ElementCustomProps; | ||
|
||
export type ElementProps = React.ComponentProps<'input'> & ElementOptionProps; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import React, {createContext, PropsWithChildren, useContext, useState} from 'react'; | ||
|
||
interface GroupInputContextProps { | ||
hasAnyFocus: boolean; | ||
setHasAnyFocus: React.Dispatch<React.SetStateAction<boolean>>; | ||
values: {[key: string]: string}; | ||
setValues: React.Dispatch<React.SetStateAction<{[key: string]: string}>>; | ||
hasAnyErrors: {[key: string]: boolean}; | ||
setHasAnyErrors: React.Dispatch<React.SetStateAction<{[key: string]: boolean}>>; | ||
} | ||
|
||
const GroupInputContext = createContext<GroupInputContextProps | undefined>(undefined); | ||
|
||
export const useGroupInputContext = () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용하는 측에서는 그룹인풋을 사용하는 바텀시트안에서 provider를 감싸면 되는 구조인거죠? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
const context = useContext(GroupInputContext); | ||
if (!context) { | ||
throw new Error('useGroupInputContext must be used within an GroupInputProvider'); | ||
} | ||
return context; | ||
}; | ||
|
||
export const GroupInputProvider: React.FC<PropsWithChildren> = ({children}: React.PropsWithChildren) => { | ||
const [hasAnyFocus, setHasAnyFocus] = useState(false); | ||
const [values, setValues] = useState<{[key: string]: string}>({}); | ||
const [hasAnyErrors, setHasAnyErrors] = useState<{[key: string]: boolean}>({}); | ||
|
||
return ( | ||
<GroupInputContext.Provider value={{hasAnyFocus, setHasAnyFocus, values, setValues, hasAnyErrors, setHasAnyErrors}}> | ||
{children} | ||
</GroupInputContext.Provider> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
벌써 버전이 52까지;;;; 와우