Merge branch 'fe/feat/member-add-modal' into 'fe/develop'

Feat: MemberAdd 컴포넌트 구현

See merge request s11-s-project/S11P21S002!15
This commit is contained in:
조현수 2024-08-29 13:33:55 +09:00
commit 5f0c04bbc1
4 changed files with 164 additions and 0 deletions

View File

@ -0,0 +1,81 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../ui/form';
import { Input } from '../ui/input';
import { Button } from '../ui/button';
import { Select } from '../ui/select';
const formSchema = z.object({
email: z.string().email({ message: '올바른 이메일 형식을 입력해주세요.' }),
role: z.string().min(1, { message: '역할을 선택해주세요.' }),
});
export type MemberAddFormValues = z.infer<typeof formSchema>;
const defaultValues: Partial<MemberAddFormValues> = {
email: '',
role: '',
};
const roleOptions = [
{ value: 'admin', label: '관리자' },
{ value: 'viewer', label: '뷰어' },
{ value: 'editor', label: '에디터' },
];
export default function MemberAddForm({ onSubmit }: { onSubmit: (data: MemberAddFormValues) => void }) {
const form = useForm<MemberAddFormValues>({
resolver: zodResolver(formSchema),
defaultValues,
});
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-5"
>
<FormField
name="email"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel className="body-strong"></FormLabel>
<FormControl>
<Input
placeholder="이메일을 입력해주세요."
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
name="role"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel className="body-strong"></FormLabel>
<FormControl>
<Select
{...field}
options={roleOptions}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
variant="outlinePrimary"
disabled={!form.formState.isValid}
>
</Button>
</form>
</Form>
);
}

View File

@ -0,0 +1,20 @@
import { Meta, StoryObj } from '@storybook/react';
import MemberAddModal from '.';
const meta: Meta<typeof MemberAddModal> = {
title: 'Modal/MemberAddModal',
component: MemberAddModal,
argTypes: {
title: { control: 'text' },
},
};
export default meta;
type Story = StoryObj<typeof MemberAddModal>;
export const Default: Story = {
args: {
title: '프로젝트 멤버 초대하기',
},
};

View File

@ -0,0 +1,27 @@
import MemberAddForm, { MemberAddFormValues } from './MemberAddForm';
import XIcon from '@/assets/icons/x.svg?react';
export default function MemberAddModal({
title = '새 멤버 초대',
onClose,
onSubmit,
}: {
title?: string;
onClose: () => void;
onSubmit: (data: MemberAddFormValues) => void;
}) {
return (
<div className="flex w-[610px] flex-col gap-10 rounded-3xl border px-10 py-5 shadow-lg">
<header className="flex gap-5">
<h1 className="small-title w-full">{title}</h1>
<button
className="flex h-8 w-8 items-center justify-center"
onClick={onClose}
>
<XIcon className="stroke-gray-900" />
</button>
</header>
<MemberAddForm onSubmit={onSubmit} />
</div>
);
}

View File

@ -0,0 +1,36 @@
import * as React from 'react';
import { ChevronDown } from 'lucide-react';
import { cn } from '@/lib/utils';
export interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
options?: { value: string; label: string }[];
}
const Select = React.forwardRef<HTMLSelectElement, SelectProps>(({ className, options = [], ...props }, ref) => {
return (
<div className="relative flex items-center">
<select
ref={ref}
className={cn(
'flex h-10 w-full appearance-none rounded-md border border-gray-200 bg-white px-3 py-2 text-sm text-gray-900 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-800 dark:bg-gray-950 dark:ring-offset-gray-950 dark:placeholder:text-gray-400 dark:focus-visible:ring-gray-300',
className
)}
{...props}
>
{options.map((option) => (
<option
key={option.value}
value={option.value}
>
{option.label}
</option>
))}
</select>
<ChevronDown className="pointer-events-none absolute right-3 h-4 w-4 text-gray-900" />
</div>
);
});
Select.displayName = 'Select';
export { Select };