Refactor: 워크스페이스 페이지 리팩토링 - S11P21S002-139
This commit is contained in:
parent
04ba6cf258
commit
6b53eccd5c
@ -4,6 +4,8 @@ import GoogleLogo from '@/assets/icons/web_neutral_rd_ctn@1x.png';
|
|||||||
import useAuthStore from '@/stores/useAuthStore';
|
import useAuthStore from '@/stores/useAuthStore';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { fetchProfileApi, reissueTokenApi } from '@/api/authApi';
|
import { fetchProfileApi, reissueTokenApi } from '@/api/authApi';
|
||||||
|
import { SuccessResponse, MemberResponseDTO, CustomError } from '@/types';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
|
||||||
const DOMAIN = 'https://j11s002.p.ssafy.io';
|
const DOMAIN = 'https://j11s002.p.ssafy.io';
|
||||||
|
|
||||||
@ -12,24 +14,20 @@ export default function Home() {
|
|||||||
const { isLoggedIn, setLoggedIn, profile, setProfile } = useAuthStore();
|
const { isLoggedIn, setLoggedIn, profile, setProfile } = useAuthStore();
|
||||||
const hasFetchedProfile = useRef(false);
|
const hasFetchedProfile = useRef(false);
|
||||||
|
|
||||||
if (!isLoggedIn && !profile.id && !hasFetchedProfile.current) {
|
if (!isLoggedIn && !profile && !hasFetchedProfile.current) {
|
||||||
const accessToken = localStorage.getItem('accessToken');
|
const accessToken = sessionStorage.getItem('accessToken');
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
setLoggedIn(true, accessToken);
|
setLoggedIn(true, accessToken);
|
||||||
fetchProfileApi()
|
fetchProfileApi()
|
||||||
.then((data) => {
|
.then((data: SuccessResponse<MemberResponseDTO>) => {
|
||||||
if (data?.isSuccess && data.data) {
|
if (data?.isSuccess && data.data) {
|
||||||
setProfile({
|
setProfile(data.data);
|
||||||
id: data.data.id,
|
|
||||||
nickname: data.data.nickname,
|
|
||||||
profileImage: data.data.profileImage,
|
|
||||||
});
|
|
||||||
hasFetchedProfile.current = true;
|
hasFetchedProfile.current = true;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error: AxiosError<CustomError>) => {
|
||||||
alert('프로필을 가져오는 중 오류가 발생했습니다. 다시 시도해주세요.');
|
alert('프로필을 가져오는 중 오류가 발생했습니다. 다시 시도해주세요.');
|
||||||
console.error('프로필 가져오기 실패:', error);
|
console.error('프로필 가져오기 실패:', error?.response?.data?.message || '알 수 없는 오류');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,16 +4,29 @@ import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialog
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
|
|
||||||
export default function ProjectCreateModal({
|
interface ProjectCreateModalProps {
|
||||||
onSubmit,
|
onSubmit: (data: { title: string; labelType: 'classification' | 'detection' | 'segmentation' }) => void;
|
||||||
}: {
|
}
|
||||||
onSubmit: (data: { title: string; labelType: 'Classification' | 'Detection' | 'Segmentation' }) => void;
|
|
||||||
}) {
|
export default function ProjectCreateModal({ onSubmit }: ProjectCreateModalProps) {
|
||||||
const [isOpen, setIsOpen] = React.useState(false);
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
|
||||||
const handleOpen = () => setIsOpen(true);
|
const handleOpen = () => setIsOpen(true);
|
||||||
const handleClose = () => setIsOpen(false);
|
const handleClose = () => setIsOpen(false);
|
||||||
|
|
||||||
|
const formatLabelType = (
|
||||||
|
labelType: 'Classification' | 'Detection' | 'Segmentation'
|
||||||
|
): 'classification' | 'detection' | 'segmentation' => {
|
||||||
|
switch (labelType) {
|
||||||
|
case 'Classification':
|
||||||
|
return 'classification';
|
||||||
|
case 'Detection':
|
||||||
|
return 'detection';
|
||||||
|
case 'Segmentation':
|
||||||
|
return 'segmentation';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
@ -35,7 +48,7 @@ export default function ProjectCreateModal({
|
|||||||
onSubmit={(data: ProjectCreateFormValues) => {
|
onSubmit={(data: ProjectCreateFormValues) => {
|
||||||
const formattedData = {
|
const formattedData = {
|
||||||
title: data.projectName,
|
title: data.projectName,
|
||||||
labelType: data.labelType,
|
labelType: formatLabelType(data.labelType),
|
||||||
};
|
};
|
||||||
onSubmit(formattedData);
|
onSubmit(formattedData);
|
||||||
handleClose();
|
handleClose();
|
||||||
|
@ -19,7 +19,11 @@ const defaultValues: Partial<WorkSpaceCreateFormValues> = {
|
|||||||
workspaceDescription: '',
|
workspaceDescription: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function WorkSpaceCreateForm({ onSubmit }: { onSubmit: (data: WorkSpaceCreateFormValues) => void }) {
|
interface WorkSpaceCreateFormProps {
|
||||||
|
onSubmit: (data: WorkSpaceCreateFormValues) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WorkSpaceCreateForm({ onSubmit }: WorkSpaceCreateFormProps) {
|
||||||
const form = useForm<WorkSpaceCreateFormValues>({
|
const form = useForm<WorkSpaceCreateFormValues>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues,
|
defaultValues,
|
||||||
|
@ -2,17 +2,27 @@ import * as React from 'react';
|
|||||||
import WorkSpaceCreateForm, { WorkSpaceCreateFormValues } from './WorkSpaceCreateForm';
|
import WorkSpaceCreateForm, { WorkSpaceCreateFormValues } from './WorkSpaceCreateForm';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom';
|
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
|
import { WorkspaceRequestDTO } from '@/types';
|
||||||
|
|
||||||
export default function WorkSpaceCreateModal({
|
interface WorkSpaceCreateModalProps {
|
||||||
onSubmit,
|
onSubmit: (data: WorkspaceRequestDTO) => void;
|
||||||
}: {
|
}
|
||||||
onSubmit: (data: { title: string; content: string }) => void;
|
|
||||||
}) {
|
export default function WorkSpaceCreateModal({ onSubmit }: WorkSpaceCreateModalProps) {
|
||||||
const [isOpen, setIsOpen] = React.useState(false);
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
|
||||||
const handleOpen = () => setIsOpen(true);
|
const handleOpen = () => setIsOpen(true);
|
||||||
const handleClose = () => setIsOpen(false);
|
const handleClose = () => setIsOpen(false);
|
||||||
|
|
||||||
|
const handleFormSubmit = (data: WorkSpaceCreateFormValues) => {
|
||||||
|
const formattedData: WorkspaceRequestDTO = {
|
||||||
|
title: data.workspaceName,
|
||||||
|
content: data.workspaceDescription || '',
|
||||||
|
};
|
||||||
|
onSubmit(formattedData);
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
@ -28,16 +38,7 @@ export default function WorkSpaceCreateModal({
|
|||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader title="새 워크스페이스" />
|
<DialogHeader title="새 워크스페이스" />
|
||||||
<WorkSpaceCreateForm
|
<WorkSpaceCreateForm onSubmit={handleFormSubmit} />
|
||||||
onSubmit={(data: WorkSpaceCreateFormValues) => {
|
|
||||||
const formattedData = {
|
|
||||||
title: data.workspaceName,
|
|
||||||
content: data.workspaceDescription || '',
|
|
||||||
};
|
|
||||||
onSubmit(formattedData);
|
|
||||||
handleClose();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
@ -4,23 +4,31 @@ import { Smile } from 'lucide-react';
|
|||||||
import ProjectCreateModal from '../ProjectCreateModal';
|
import ProjectCreateModal from '../ProjectCreateModal';
|
||||||
import { useGetAllProjects, useCreateProject } from '@/hooks/useProjectHooks';
|
import { useGetAllProjects, useCreateProject } from '@/hooks/useProjectHooks';
|
||||||
import useAuthStore from '@/stores/useAuthStore';
|
import useAuthStore from '@/stores/useAuthStore';
|
||||||
import { Key } from 'react';
|
import { ProjectResponseDTO } from '@/types';
|
||||||
|
|
||||||
export default function WorkspaceBrowseDetail() {
|
export default function WorkspaceBrowseDetail() {
|
||||||
const { workspaceId } = useParams<{ workspaceId: string }>();
|
const { workspaceId } = useParams<{ workspaceId: string }>();
|
||||||
const numericWorkspaceId = Number(workspaceId);
|
const numericWorkspaceId = Number(workspaceId);
|
||||||
const { profile } = useAuthStore();
|
const { profile } = useAuthStore();
|
||||||
const memberId = profile.id ?? 0;
|
const memberId = profile?.id ?? 0;
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: projectsResponse,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
refetch,
|
||||||
|
} = useGetAllProjects(numericWorkspaceId, memberId, {
|
||||||
|
enabled: !isNaN(numericWorkspaceId),
|
||||||
|
});
|
||||||
|
|
||||||
const { data: projectsResponse, isLoading, isError, refetch } = useGetAllProjects(numericWorkspaceId || 0, memberId);
|
|
||||||
const createProject = useCreateProject();
|
const createProject = useCreateProject();
|
||||||
|
|
||||||
const handleCreateProject = (data: { title: string; labelType: 'Classification' | 'Detection' | 'Segmentation' }) => {
|
const handleCreateProject = (data: { title: string; labelType: 'classification' | 'detection' | 'segmentation' }) => {
|
||||||
createProject.mutate(
|
createProject.mutate(
|
||||||
{
|
{
|
||||||
workspaceId: numericWorkspaceId,
|
workspaceId: numericWorkspaceId,
|
||||||
memberId,
|
memberId,
|
||||||
data: { title: data.title, projectType: data.labelType.toLowerCase() },
|
data: { title: data.title, projectType: data.labelType },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@ -29,8 +37,6 @@ export default function WorkspaceBrowseDetail() {
|
|||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.error('프로젝트 생성 실패:', error);
|
console.error('프로젝트 생성 실패:', error);
|
||||||
console.log('Error details:', JSON.stringify(error, null, 2));
|
|
||||||
|
|
||||||
const errorMessage = error?.response?.data?.message || error.message || '알 수 없는 오류';
|
const errorMessage = error?.response?.data?.message || error.message || '알 수 없는 오류';
|
||||||
console.error('프로젝트 생성 실패:', errorMessage);
|
console.error('프로젝트 생성 실패:', errorMessage);
|
||||||
},
|
},
|
||||||
@ -38,9 +44,21 @@ export default function WorkspaceBrowseDetail() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const projects = Array.isArray(projectsResponse?.data?.workspaceResponses)
|
const projects: ProjectResponseDTO[] = projectsResponse?.data?.workspaceResponses ?? [];
|
||||||
? projectsResponse.data.workspaceResponses
|
|
||||||
: [];
|
if (isNaN(numericWorkspaceId)) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<Smile
|
||||||
|
size={48}
|
||||||
|
className="mb-2 text-gray-300"
|
||||||
|
/>
|
||||||
|
<div className="body text-gray-400">작업할 워크스페이스를 선택하세요</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <p>Loading projects...</p>;
|
return <p>Loading projects...</p>;
|
||||||
@ -74,7 +92,7 @@ export default function WorkspaceBrowseDetail() {
|
|||||||
</div>
|
</div>
|
||||||
{projects.length > 0 ? (
|
{projects.length > 0 ? (
|
||||||
<div className="flex flex-wrap gap-6">
|
<div className="flex flex-wrap gap-6">
|
||||||
{projects.map((project: { id: Key | null | undefined; title: string; projectType: string }) => (
|
{projects.map((project: ProjectResponseDTO) => (
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
key={project.id}
|
key={project.id}
|
||||||
title={project.title}
|
title={project.title}
|
||||||
|
@ -6,10 +6,11 @@ import useAuthStore from '@/stores/useAuthStore';
|
|||||||
import WorkSpaceCreateModal from '../WorkSpaceCreateModal';
|
import WorkSpaceCreateModal from '../WorkSpaceCreateModal';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
import { WorkspaceRequestDTO, WorkspaceResponseDTO, CustomError } from '@/types';
|
||||||
|
|
||||||
export default function WorkspaceBrowseLayout() {
|
export default function WorkspaceBrowseLayout() {
|
||||||
const { profile, isLoggedIn } = useAuthStore();
|
const { profile, isLoggedIn } = useAuthStore();
|
||||||
const memberId = profile.id;
|
const memberId = profile?.id ?? 0;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ export default function WorkspaceBrowseLayout() {
|
|||||||
|
|
||||||
const createWorkspace = useCreateWorkspace();
|
const createWorkspace = useCreateWorkspace();
|
||||||
|
|
||||||
const handleCreateWorkspace = (data: { title: string; content: string }) => {
|
const handleCreateWorkspace = (data: WorkspaceRequestDTO) => {
|
||||||
if (!memberId) return;
|
if (!memberId) return;
|
||||||
createWorkspace.mutate(
|
createWorkspace.mutate(
|
||||||
{ memberId, data },
|
{ memberId, data },
|
||||||
@ -35,7 +36,7 @@ export default function WorkspaceBrowseLayout() {
|
|||||||
console.log('워크스페이스가 성공적으로 생성되었습니다.');
|
console.log('워크스페이스가 성공적으로 생성되었습니다.');
|
||||||
queryClient.invalidateQueries({ queryKey: ['workspaces'] });
|
queryClient.invalidateQueries({ queryKey: ['workspaces'] });
|
||||||
},
|
},
|
||||||
onError: (error: AxiosError) => {
|
onError: (error: AxiosError<CustomError>) => {
|
||||||
console.error('워크스페이스 생성 실패:', error.message);
|
console.error('워크스페이스 생성 실패:', error.message);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -61,7 +62,7 @@ export default function WorkspaceBrowseLayout() {
|
|||||||
<WorkSpaceCreateModal onSubmit={handleCreateWorkspace} />
|
<WorkSpaceCreateModal onSubmit={handleCreateWorkspace} />
|
||||||
</div>
|
</div>
|
||||||
{workspaces.length > 0 ? (
|
{workspaces.length > 0 ? (
|
||||||
workspaces.map((workspace) => (
|
workspaces.map((workspace: WorkspaceResponseDTO) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
to={`/browse/${workspace.id}`}
|
to={`/browse/${workspace.id}`}
|
||||||
key={workspace.id}
|
key={workspace.id}
|
||||||
|
@ -57,4 +57,5 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
);
|
);
|
||||||
Button.displayName = 'Button';
|
Button.displayName = 'Button';
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
export { Button, buttonVariants };
|
export { Button, buttonVariants };
|
||||||
|
@ -1,34 +1,25 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react';
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import * as LabelPrimitive from '@radix-ui/react-label';
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from '@radix-ui/react-slot';
|
||||||
import {
|
import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider, useFormContext } from 'react-hook-form';
|
||||||
Controller,
|
|
||||||
ControllerProps,
|
|
||||||
FieldPath,
|
|
||||||
FieldValues,
|
|
||||||
FormProvider,
|
|
||||||
useFormContext,
|
|
||||||
} from "react-hook-form"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils';
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from '@/components/ui/label';
|
||||||
|
|
||||||
const Form = FormProvider
|
const Form = FormProvider;
|
||||||
|
|
||||||
type FormFieldContextValue<
|
type FormFieldContextValue<
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||||
> = {
|
> = {
|
||||||
name: TName
|
name: TName;
|
||||||
}
|
};
|
||||||
|
|
||||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
|
||||||
{} as FormFieldContextValue
|
|
||||||
)
|
|
||||||
|
|
||||||
const FormField = <
|
const FormField = <
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||||
>({
|
>({
|
||||||
...props
|
...props
|
||||||
}: ControllerProps<TFieldValues, TName>) => {
|
}: ControllerProps<TFieldValues, TName>) => {
|
||||||
@ -36,21 +27,21 @@ const FormField = <
|
|||||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||||
<Controller {...props} />
|
<Controller {...props} />
|
||||||
</FormFieldContext.Provider>
|
</FormFieldContext.Provider>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const useFormField = () => {
|
const useFormField = () => {
|
||||||
const fieldContext = React.useContext(FormFieldContext)
|
const fieldContext = React.useContext(FormFieldContext);
|
||||||
const itemContext = React.useContext(FormItemContext)
|
const itemContext = React.useContext(FormItemContext);
|
||||||
const { getFieldState, formState } = useFormContext()
|
const { getFieldState, formState } = useFormContext();
|
||||||
|
|
||||||
const fieldState = getFieldState(fieldContext.name, formState)
|
const fieldState = getFieldState(fieldContext.name, formState);
|
||||||
|
|
||||||
if (!fieldContext) {
|
if (!fieldContext) {
|
||||||
throw new Error("useFormField should be used within <FormField>")
|
throw new Error('useFormField should be used within <FormField>');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = itemContext
|
const { id } = itemContext;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
@ -59,112 +50,107 @@ const useFormField = () => {
|
|||||||
formDescriptionId: `${id}-form-item-description`,
|
formDescriptionId: `${id}-form-item-description`,
|
||||||
formMessageId: `${id}-form-item-message`,
|
formMessageId: `${id}-form-item-message`,
|
||||||
...fieldState,
|
...fieldState,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
type FormItemContextValue = {
|
type FormItemContextValue = {
|
||||||
id: string
|
id: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
|
||||||
{} as FormItemContextValue
|
|
||||||
)
|
|
||||||
|
|
||||||
const FormItem = React.forwardRef<
|
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
HTMLDivElement,
|
({ className, ...props }, ref) => {
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
const id = React.useId();
|
||||||
>(({ className, ...props }, ref) => {
|
|
||||||
const id = React.useId()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormItemContext.Provider value={{ id }}>
|
<FormItemContext.Provider value={{ id }}>
|
||||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn('space-y-2', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
</FormItemContext.Provider>
|
</FormItemContext.Provider>
|
||||||
)
|
);
|
||||||
})
|
}
|
||||||
FormItem.displayName = "FormItem"
|
);
|
||||||
|
FormItem.displayName = 'FormItem';
|
||||||
|
|
||||||
const FormLabel = React.forwardRef<
|
const FormLabel = React.forwardRef<
|
||||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||||
>(({ className, ...props }, ref) => {
|
>(({ className, ...props }, ref) => {
|
||||||
const { error, formItemId } = useFormField()
|
const { error, formItemId } = useFormField();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Label
|
<Label
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(error && "text-red-500 dark:text-red-900", className)}
|
className={cn(error && 'text-red-500 dark:text-red-900', className)}
|
||||||
htmlFor={formItemId}
|
htmlFor={formItemId}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
FormLabel.displayName = "FormLabel"
|
FormLabel.displayName = 'FormLabel';
|
||||||
|
|
||||||
const FormControl = React.forwardRef<
|
const FormControl = React.forwardRef<React.ElementRef<typeof Slot>, React.ComponentPropsWithoutRef<typeof Slot>>(
|
||||||
React.ElementRef<typeof Slot>,
|
({ ...props }, ref) => {
|
||||||
React.ComponentPropsWithoutRef<typeof Slot>
|
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
||||||
>(({ ...props }, ref) => {
|
|
||||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Slot
|
<Slot
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={formItemId}
|
id={formItemId}
|
||||||
aria-describedby={
|
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
||||||
!error
|
|
||||||
? `${formDescriptionId}`
|
|
||||||
: `${formDescriptionId} ${formMessageId}`
|
|
||||||
}
|
|
||||||
aria-invalid={!!error}
|
aria-invalid={!!error}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
})
|
}
|
||||||
FormControl.displayName = "FormControl"
|
);
|
||||||
|
FormControl.displayName = 'FormControl';
|
||||||
|
|
||||||
const FormDescription = React.forwardRef<
|
const FormDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||||
HTMLParagraphElement,
|
({ className, ...props }, ref) => {
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
const { formDescriptionId } = useFormField();
|
||||||
>(({ className, ...props }, ref) => {
|
|
||||||
const { formDescriptionId } = useFormField()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p
|
<p
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={formDescriptionId}
|
id={formDescriptionId}
|
||||||
className={cn("text-sm text-gray-500 dark:text-gray-400", className)}
|
className={cn('text-sm text-gray-500 dark:text-gray-400', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
})
|
}
|
||||||
FormDescription.displayName = "FormDescription"
|
);
|
||||||
|
FormDescription.displayName = 'FormDescription';
|
||||||
|
|
||||||
const FormMessage = React.forwardRef<
|
const FormMessage = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||||
HTMLParagraphElement,
|
({ className, children, ...props }, ref) => {
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
const { error, formMessageId } = useFormField();
|
||||||
>(({ className, children, ...props }, ref) => {
|
const body = error ? String(error?.message) : children;
|
||||||
const { error, formMessageId } = useFormField()
|
|
||||||
const body = error ? String(error?.message) : children
|
|
||||||
|
|
||||||
if (!body) {
|
if (!body) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p
|
<p
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={formMessageId}
|
id={formMessageId}
|
||||||
className={cn("text-sm font-medium text-red-500 dark:text-red-900", className)}
|
className={cn('text-sm font-medium text-red-500 dark:text-red-900', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{body}
|
{body}
|
||||||
</p>
|
</p>
|
||||||
)
|
);
|
||||||
})
|
}
|
||||||
FormMessage.displayName = "FormMessage"
|
);
|
||||||
|
FormMessage.displayName = 'FormMessage';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
useFormField,
|
useFormField,
|
||||||
Form,
|
Form,
|
||||||
FormItem,
|
FormItem,
|
||||||
@ -173,4 +159,4 @@ export {
|
|||||||
FormDescription,
|
FormDescription,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
FormField,
|
FormField,
|
||||||
}
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user