-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: Select 컴포넌트 마크업 * feat: Select 컴포넌트 에러 스타일링 * refactor: 기본 select 태그 사용하도록 수정 * setting: 스토리북 staticDir 설정 * refactor: SelectProps 타입 수정 * Update src/ui/Base/Select.tsx Co-authored-by: kyuran kim <[email protected]> --------- Co-authored-by: kyuran kim <[email protected]>
- Loading branch information
1 parent
5aef81d
commit aa4ba6c
Showing
4 changed files
with
166 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { Meta, StoryObj } from '@storybook/react'; | ||
import Select from '@/ui/Base/Select'; | ||
import { SubmitHandler, useForm } from 'react-hook-form'; | ||
import Button from '@/ui/Base/Button'; | ||
|
||
const meta: Meta<typeof Select> = { | ||
title: 'Base/Select', | ||
component: Select, | ||
tags: ['autodocs'], | ||
}; | ||
|
||
const numbers = [1, 2, 3] as const; | ||
|
||
export default meta; | ||
|
||
type DefaultValues = { | ||
requiredNumber: number; | ||
number: number; | ||
}; | ||
|
||
const SelectWithUseForm = () => { | ||
const { | ||
register, | ||
handleSubmit, | ||
formState: { errors }, | ||
} = useForm<DefaultValues>({ | ||
mode: 'all', | ||
}); | ||
|
||
const handleSubmitForm: SubmitHandler<DefaultValues> = ({ | ||
requiredNumber, | ||
number, | ||
}) => { | ||
alert(`requiredNumber: ${requiredNumber}, number: ${number}`); | ||
}; | ||
|
||
return ( | ||
<form | ||
onSubmit={handleSubmit(handleSubmitForm)} | ||
className="flex w-[43rem] flex-col gap-[1.6rem]" | ||
> | ||
<Select | ||
placeholder="숫자를 선택해주세요. (필수)" | ||
{...register('requiredNumber', { | ||
required: '필수 항목입니다.', | ||
})} | ||
errorMessage={errors.requiredNumber?.message} | ||
> | ||
{numbers.map(number => ( | ||
<Select.Option key={number} value={number}> | ||
{number} | ||
</Select.Option> | ||
))} | ||
</Select> | ||
<Select | ||
placeholder="숫자를 선택해주세요." | ||
{...register('number')} | ||
errorMessage={errors.number?.message} | ||
> | ||
{numbers.map(number => ( | ||
<Select.Option key={number} value={number}> | ||
{number} | ||
</Select.Option> | ||
))} | ||
</Select> | ||
<Button | ||
size="large" | ||
type="submit" | ||
onClick={handleSubmit(handleSubmitForm)} | ||
> | ||
Submit | ||
</Button> | ||
</form> | ||
); | ||
}; | ||
|
||
type Story = StoryObj<typeof Select>; | ||
|
||
export const Default: Story = { | ||
args: { | ||
placeholder: '선택해 주세요.', | ||
}, | ||
render: args => ( | ||
<Select {...args}> | ||
{numbers.map(number => ( | ||
<Select.Option key={number} value={number}> | ||
{number} | ||
</Select.Option> | ||
))} | ||
</Select> | ||
), | ||
}; | ||
|
||
export const Invalid: Story = { | ||
args: { | ||
placeholder: '선택해 주세요.', | ||
errorMessage: '에러 메시지에요.', | ||
}, | ||
}; | ||
|
||
export const WithUseForm: Story = { | ||
render: () => ( | ||
<div className="min-h-[20rem]"> | ||
<SelectWithUseForm /> | ||
</div> | ||
), | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { ComponentPropsWithoutRef, forwardRef, Ref } from 'react'; | ||
|
||
interface SelectProps | ||
extends Omit< | ||
ComponentPropsWithoutRef<'select'>, | ||
'className' | 'defaultValue' | 'required' | ||
> { | ||
errorMessage?: string; | ||
} | ||
|
||
const Select = ( | ||
{ errorMessage, children, placeholder, ...props }: SelectProps, | ||
ref: Ref<HTMLSelectElement> | ||
) => { | ||
const borderColor = errorMessage | ||
? 'border-warning-800' | ||
: 'border-black-400 focus:border-main-900'; | ||
|
||
return ( | ||
<div className="flex flex-col gap-[0.5rem] text-sm"> | ||
<select | ||
ref={ref} | ||
defaultValue="" | ||
required | ||
className={`rounded-[0.5rem] border-[0.05rem] px-[1.0rem] py-[1.3rem] outline-none ${borderColor} cursor-pointer appearance-none bg-[url('/icons/select-icon.svg')] bg-[calc(100%-1rem)_center] bg-no-repeat invalid:text-placeholder`} | ||
{...props} | ||
> | ||
{placeholder && ( | ||
<option value="" disabled> | ||
{placeholder} | ||
</option> | ||
)} | ||
{children} | ||
</select> | ||
{errorMessage && <div className="text-warning-800">{errorMessage}</div>} | ||
</div> | ||
); | ||
}; | ||
|
||
const Option = ({ | ||
value, | ||
children, | ||
...props | ||
}: ComponentPropsWithoutRef<'option'>) => { | ||
return ( | ||
<option value={value} {...props}> | ||
{children} | ||
</option> | ||
); | ||
}; | ||
|
||
const ExportSelect = Object.assign(forwardRef(Select), { Option }); | ||
|
||
export default ExportSelect; |