Skip to content

Commit

Permalink
[#383] Select 컴포넌트 (#385)
Browse files Browse the repository at this point in the history
* 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
minjongbaek and gxxrxn authored Jul 27, 2023
1 parent 5aef81d commit aa4ba6c
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 6 deletions.
8 changes: 2 additions & 6 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import type { StorybookConfig } from '@storybook/nextjs';
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
staticDirs: ['../public'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
{
name: '@storybook/addon-styling',
options: {
postCss: true,
},
},
'@storybook/addon-styling',
],
framework: {
name: '@storybook/nextjs',
Expand Down
3 changes: 3 additions & 0 deletions public/icons/select-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
107 changes: 107 additions & 0 deletions src/stories/Base/Select.stories.tsx
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>
),
};
54 changes: 54 additions & 0 deletions src/ui/Base/Select.tsx
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;

0 comments on commit aa4ba6c

Please sign in to comment.