Skip to content

Commit d93f209

Browse files
authored
[#43] ✨ PasswordInput 구현 (#74)
* [#43]✨ Implement TextInput component * [#43] ♻️ Refactor TextInput with tailwind-merge and update baseStyles * [#43] ✨ Create IconList component with svg icons * [#43] 💄 Add essential styles for button, input and select in globals.css * [#43] ✨ Implement PasswordInput component * [#43] ✨ Create useToggle hook * [#43] ✅ add storybook stories for PasswordInput component * [#43] 🐛 Fix Storybook bug preventing SVGR components from loading * [#43] ♻️ Update PasswordInput Storybook to ensure icon displays correctly inside input field * [#43] ♻️ Refactor useToggle hook to return an object * [#43] ♻️ Refactor for accessibility, update useToggle hook usage, and remove props extension * [#43] ♻️ Remove className prop from TextInputProps * [#43] ♻️ Extend TextInputProps to accept additional props
1 parent 524d22a commit d93f209

File tree

5 files changed

+102
-2
lines changed

5 files changed

+102
-2
lines changed

.storybook/main.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ const config = {
1818
...config.resolve.alias,
1919
'@': path.resolve(__dirname, '../src'),
2020
}
21+
22+
const fileLoaderRule = config.module.rules.filter(
23+
rule => rule.test && rule.test.test('.svg')
24+
)
25+
fileLoaderRule.forEach(rule => (rule.exclude = /\.svg$/))
26+
27+
config.module.rules.push({
28+
test: /\.svg$/,
29+
enforce: 'pre',
30+
loader: require.resolve('@svgr/webpack'),
31+
})
2132
return config
2233
},
2334
staticDirs: ['../public'],
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { IcEyeClosed, IcEyeOpen } from '@/assets/IconList'
2+
import { FieldValues, UseFormRegister } from 'react-hook-form'
3+
4+
import { useToggle } from '@/hooks/useToggle'
5+
6+
import { TextInput, TextInputProps } from './TextInput'
7+
8+
interface PasswordInputProps
9+
extends Omit<TextInputProps, 'startAdornment' | 'endAdornment' | 'type'> {
10+
error?: boolean
11+
register?: ReturnType<UseFormRegister<FieldValues>>
12+
className?: string
13+
fullWidth?: boolean
14+
}
15+
16+
export const PasswordInput = ({
17+
error = false,
18+
register,
19+
className = '',
20+
fullWidth = false,
21+
...props
22+
}: PasswordInputProps): JSX.Element => {
23+
const { isOpen: showPassword, toggle: toggleShowPassword } = useToggle()
24+
const visibilityIcon = showPassword ? (
25+
<IcEyeOpen width={24} height={24} />
26+
) : (
27+
<IcEyeClosed width={24} height={24} />
28+
)
29+
30+
return (
31+
<TextInput
32+
{...register}
33+
type={showPassword ? 'text' : 'password'}
34+
fullWidth={fullWidth}
35+
endAdornment={
36+
<button
37+
aria-label={showPassword ? '비밀번호 숨김' : '비밀번호 보임'}
38+
onClick={toggleShowPassword}
39+
type='button'
40+
>
41+
{visibilityIcon}
42+
</button>
43+
}
44+
{...props}
45+
/>
46+
)
47+
}

src/components/common/input/TextInput.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import { ReactNode } from 'react'
33
import { FieldValues, UseFormRegister } from 'react-hook-form'
44
import { twMerge } from 'tailwind-merge'
55

6-
interface TextInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
6+
export interface TextInputProps
7+
extends React.InputHTMLAttributes<HTMLInputElement> {
78
error?: boolean
89
register?: ReturnType<UseFormRegister<FieldValues>>
910
startAdornment?: ReactNode
1011
endAdornment?: ReactNode
11-
className?: string
1212
fullWidth?: boolean
1313
}
1414

src/hooks/useToggle.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { useState } from 'react'
2+
3+
export const useToggle = (
4+
initialState = false
5+
): { isOpen: boolean; toggle: () => void } => {
6+
const [isOpen, setIsOpen] = useState(initialState)
7+
const toggle = () => setIsOpen(prev => !prev)
8+
return { isOpen, toggle }
9+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { fn } from '@storybook/test'
2+
3+
import { PasswordInput } from '@/components/common/input/PasswordInput'
4+
5+
export default {
6+
title: 'Example/PasswordInput',
7+
component: PasswordInput,
8+
parameters: {
9+
layout: 'fullscreen',
10+
},
11+
tags: ['input', 'password'],
12+
argTypes: {
13+
error: { control: 'boolean' },
14+
fullWidth: { control: 'boolean' },
15+
},
16+
args: {
17+
onChange: fn(),
18+
},
19+
}
20+
21+
export const Default = {
22+
args: {
23+
placeholder: '비밀번호를 입력하세요.',
24+
fullWidth: true,
25+
},
26+
}
27+
28+
export const Error = {
29+
args: {
30+
error: true,
31+
fullWidth: true,
32+
},
33+
}

0 commit comments

Comments
 (0)