From c19082949b973ab2863f1fc2b778d18e8cd5add1 Mon Sep 17 00:00:00 2001 From: jhynsoo Date: Wed, 28 Aug 2024 16:08:12 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20=EC=9B=8C=ED=81=AC=EC=8A=A4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80=20S11?= =?UTF-8?q?P21S002-75?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 11 +++ frontend/package.json | 1 + .../WorkspaceSidebar/ProjectDirectoryItem.tsx | 58 +++++++++++++ .../WorkspaceSidebar/ProjectFileItem.tsx | 43 ++++++++++ .../WorkspaceSidebar/ProjectStructure.tsx | 60 ++++++++++++++ .../WorkspaceSidebar/index.stories.tsx | 82 +++++++++++++++++++ .../src/components/WorkspaceSidebar/index.tsx | 45 ++++++++++ frontend/src/components/ui/button.tsx | 1 + frontend/src/components/ui/resizable.tsx | 43 ++++++++++ frontend/src/types/index.ts | 21 +++++ frontend/tailwind.config.js | 4 + 11 files changed, 369 insertions(+) create mode 100644 frontend/src/components/WorkspaceSidebar/ProjectDirectoryItem.tsx create mode 100644 frontend/src/components/WorkspaceSidebar/ProjectFileItem.tsx create mode 100644 frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx create mode 100644 frontend/src/components/WorkspaceSidebar/index.stories.tsx create mode 100644 frontend/src/components/WorkspaceSidebar/index.tsx create mode 100644 frontend/src/components/ui/resizable.tsx create mode 100644 frontend/src/types/index.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c1878ce..3fa64d0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -20,6 +20,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.53.0", + "react-resizable-panels": "^2.1.1", "react-router-dom": "^6.26.1", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", @@ -10264,6 +10265,16 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "node_modules/react-resizable-panels": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.1.tgz", + "integrity": "sha512-+cUV/yZBYfiBj+WJtpWDJ3NtR4zgDZfHt3+xtaETKE+FCvp+RK/NJxacDQKxMHgRUTSkfA6AnGljQ5QZNsCQoA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-router": { "version": "6.26.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index bbbb134..9249900 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.53.0", + "react-resizable-panels": "^2.1.1", "react-router-dom": "^6.26.1", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", diff --git a/frontend/src/components/WorkspaceSidebar/ProjectDirectoryItem.tsx b/frontend/src/components/WorkspaceSidebar/ProjectDirectoryItem.tsx new file mode 100644 index 0000000..aee6484 --- /dev/null +++ b/frontend/src/components/WorkspaceSidebar/ProjectDirectoryItem.tsx @@ -0,0 +1,58 @@ +import { DirectoryItem } from '@/types'; +import { ChevronRight } from 'lucide-react'; +import { useState } from 'react'; +import ProjectFileItem from './ProjectFileItem'; +import { cn } from '@/lib/utils'; + +export default function ProjectDirectoryItem({ + className = '', + item, + depth = 1, +}: { + className?: string; + item: DirectoryItem; + depth?: number; +}) { + const [isExpanded, setIsExpanded] = useState(true); + const paddingLeft = depth * 12; + + return ( + <> +
setIsExpanded((prev) => !prev)} + > + + {item.name} +
+ {item.children?.map((child) => { + const childProps = { + className: isExpanded ? '' : 'hidden', + depth: depth + 1, + }; + + return child.type === 'directory' ? ( + + ) : ( + + ); + })} + + ); +} diff --git a/frontend/src/components/WorkspaceSidebar/ProjectFileItem.tsx b/frontend/src/components/WorkspaceSidebar/ProjectFileItem.tsx new file mode 100644 index 0000000..7f6a3a4 --- /dev/null +++ b/frontend/src/components/WorkspaceSidebar/ProjectFileItem.tsx @@ -0,0 +1,43 @@ +import { cn } from '@/lib/utils'; +import { FileItem } from '@/types'; +import { Check, Image, Minus } from 'lucide-react'; + +export default function ProjectFileItem({ + className = '', + item, + depth = 1, +}: { + className?: string; + item: FileItem; + depth?: number; +}) { + const paddingLeft = depth * 12; + + return ( + + ); +} diff --git a/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx b/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx new file mode 100644 index 0000000..181740c --- /dev/null +++ b/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx @@ -0,0 +1,60 @@ +import { Project } from '@/types'; +import { ChevronRight, SquarePenIcon, Upload } from 'lucide-react'; +import { useState } from 'react'; +import ProjectFileItem from './ProjectFileItem'; +import ProjectDirectoryItem from './ProjectDirectoryItem'; + +export default function ProjectStructure({ project }: { project: Project }) { + const [isExpanded, setIsExpanded] = useState(true); + + return ( +
+
+
setIsExpanded((prev) => !prev)} + > + +
+

{project.name}

+

{project.type}

+
+
+ + +
+
+ {project.children.map((item) => + item.type === 'directory' ? ( + + ) : ( + + ) + )} +
+
+ ); +} diff --git a/frontend/src/components/WorkspaceSidebar/index.stories.tsx b/frontend/src/components/WorkspaceSidebar/index.stories.tsx new file mode 100644 index 0000000..03eef0c --- /dev/null +++ b/frontend/src/components/WorkspaceSidebar/index.stories.tsx @@ -0,0 +1,82 @@ +import '@/index.css'; +import WorkspaceSidebar from '.'; +import { ResizablePanel, ResizablePanelGroup } from '../ui/resizable'; +import { Project } from '@/types'; +import { Meta } from '@storybook/react'; +import { Component } from 'react'; + +const meta: Meta = { + title: 'Workspace/WorkspaceSidebar', + component: WorkspaceSidebar, + parameters: { + layout: 'fullscreen', + }, +}; + +export default meta; + +const projects: Project[] = [ + { + id: 1, + name: 'project-111', + type: 'Segmentation', + children: [ + { + id: 12, + type: 'directory', + name: 'directory-1', + children: [ + { + id: 123, + type: 'directory', + name: 'directory-2', + children: [ + { id: 1, url: '', type: 'image', name: 'image-1.jpg', status: 'done' }, + { id: 1, url: '', type: 'image', name: 'image-2.jpg', status: 'idle' }, + ], + }, + { id: 1, url: '', type: 'image', name: 'image-1.jpg', status: 'idle' }, + { id: 1, url: '', type: 'image', name: 'image-2.jpg', status: 'done' }, + ], + }, + { id: 1, url: '', type: 'image', name: 'image-1.jpg', status: 'done' }, + ], + }, + { + id: 2, + name: 'very-extremely-long-long-project-name-222', + type: 'Classification', + children: [ + { + id: 23, + type: 'directory', + name: 'this-is-my-very-very-long-directory-name-that-will-be-overflow', + children: [ + { id: 1, url: '', type: 'image', name: 'image-1.jpg', status: 'done' }, + { id: 1, url: '', type: 'image', name: 'image-2.jpg', status: 'done' }, + ], + }, + { + id: 1, + url: '', + type: 'image', + name: 'wow-this-is-my-very-very-long-image-name-so-this-will-be-overflow-too.jpg', + status: 'idle', + }, + ], + }, +]; + +export const Default = () => ( +
+ + + +
Content
+
+
+
+); diff --git a/frontend/src/components/WorkspaceSidebar/index.tsx b/frontend/src/components/WorkspaceSidebar/index.tsx new file mode 100644 index 0000000..748e9f0 --- /dev/null +++ b/frontend/src/components/WorkspaceSidebar/index.tsx @@ -0,0 +1,45 @@ +import { SquarePen } from 'lucide-react'; +import { ResizableHandle, ResizablePanel } from '../ui/resizable'; +import ProjectStructure from './ProjectStructure'; +import { Button } from '../ui/button'; +import { Project } from '@/types'; + +export default function WorkspaceSidebar({ workspaceName, projects }: { workspaceName: string; projects: Project[] }) { + return ( + <> + { + console.log(size); + }} + > +
+

{workspaceName}

+ + +
+
+ {projects.map((project) => ( + + ))} +
+
+ + + ); +} diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx index c2fe07a..9a10612 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/components/ui/button.tsx @@ -24,6 +24,7 @@ const buttonVariants = cva( }, size: { default: 'h-10 px-4 py-2', + xs: 'rounded-md px-2 py-1', sm: 'h-9 rounded-md px-3', lg: 'h-11 rounded-md px-8', icon: 'h-10 w-10', diff --git a/frontend/src/components/ui/resizable.tsx b/frontend/src/components/ui/resizable.tsx new file mode 100644 index 0000000..a3bba1c --- /dev/null +++ b/frontend/src/components/ui/resizable.tsx @@ -0,0 +1,43 @@ +import { GripVertical } from "lucide-react" +import * as ResizablePrimitive from "react-resizable-panels" + +import { cn } from "@/lib/utils" + +const ResizablePanelGroup = ({ + className, + ...props +}: React.ComponentProps) => ( + +) + +const ResizablePanel = ResizablePrimitive.Panel + +const ResizableHandle = ({ + withHandle, + className, + ...props +}: React.ComponentProps & { + withHandle?: boolean +}) => ( + div]:rotate-90 dark:bg-gray-800 dark:focus-visible:ring-gray-300", + className + )} + {...props} + > + {withHandle && ( +
+ +
+ )} +
+) + +export { ResizablePanelGroup, ResizablePanel, ResizableHandle } diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts new file mode 100644 index 0000000..1bfb42d --- /dev/null +++ b/frontend/src/types/index.ts @@ -0,0 +1,21 @@ +export type FileItem = { + id: number; + name: string; + url: string; + type: 'image' | 'json'; + status: 'idle' | 'done'; +}; + +export type DirectoryItem = { + id: number; + name: string; + type: 'directory'; + children: Array; +}; + +export type Project = { + id: number; + name: string; + type: 'Classification' | 'Detection' | 'Segmentation'; + children: Array; +}; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 54ed2fb..e2a6b48 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -33,6 +33,10 @@ export default { }, }, extend: { + padding: { + 0.5: '0.125rem', + 1.5: '0.375rem', + }, colors: { transparent: 'transparent', current: 'currentColor',