Skip to content

Commit b9e6a3c

Browse files
authored
Merge pull request #2 from YeongseoYoon-hanghae/feat/home-design
feat: 전반적인 홈 스타일 개선(테마 스타일링, 사이드바 추가)
2 parents f57addc + cd133e0 commit b9e6a3c

File tree

17 files changed

+1877
-594
lines changed

17 files changed

+1877
-594
lines changed

packages/app/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
},
1818
"dependencies": {
1919
"@hanghae-plus/domain": "workspace:*",
20+
"@radix-ui/react-dialog": "^1.1.14",
21+
"@radix-ui/react-separator": "^1.1.7",
2022
"@radix-ui/react-slot": "^1.2.3",
23+
"@radix-ui/react-tooltip": "^1.2.7",
2124
"@tailwindcss/vite": "^4.1.11",
2225
"@tanstack/react-query": "^5.84.1",
2326
"axios": "^1.11.0",

packages/app/src/App.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,23 @@ import { Home, User } from "@/pages";
55
import { BASE_URL } from "@/constants";
66
import { withBaseLayout } from "@/components";
77

8+
const Index = withBaseLayout(() => <Home />);
9+
const StudentsPage = withBaseLayout(() => <div className="p-6">수강생 목록 페이지</div>);
10+
const StudentDetailPage = withBaseLayout(() => <div className="p-6">수강생 상세 페이지</div>);
11+
const AssignmentsPage = withBaseLayout(() => <div className="p-6">과제 목록 페이지</div>);
12+
const NotFound = withBaseLayout(() => <div className="p-6">404 - 페이지를 찾을 수 없습니다</div>);
13+
814
export const App = () => {
915
return (
1016
<QueryClientProvider client={queryClient}>
1117
<BrowserRouter basename={BASE_URL}>
1218
<Routes>
13-
<Route path="/" Component={withBaseLayout(Home)} />
14-
<Route path="/user/:id" Component={withBaseLayout(User)} />
19+
<Route path="/" element={<Index />} />
20+
<Route path="/students" element={<StudentsPage />} />
21+
<Route path="/students/:id" element={<StudentDetailPage />} />
22+
<Route path="/assignments" element={<AssignmentsPage />} />
23+
<Route path="/user/:id" element={<User />} />
24+
<Route path="*" element={<NotFound />} />
1525
</Routes>
1626
</BrowserRouter>
1727
</QueryClientProvider>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { NavLink, useLocation } from "react-router";
2+
import {
3+
Sidebar,
4+
SidebarContent,
5+
SidebarGroup,
6+
SidebarGroupContent,
7+
SidebarGroupLabel,
8+
SidebarMenu,
9+
SidebarMenuButton,
10+
SidebarMenuItem,
11+
useSidebar,
12+
} from "@/components/ui/Sidebar";
13+
import { Users, BookOpen } from "lucide-react";
14+
15+
const navigationItems = [
16+
{
17+
title: "수강생 목록",
18+
url: "/students",
19+
icon: Users,
20+
},
21+
{
22+
title: "과제 목록",
23+
url: "/assignments",
24+
icon: BookOpen,
25+
},
26+
];
27+
28+
export function AppSidebar() {
29+
const { state } = useSidebar();
30+
const location = useLocation();
31+
const currentPath = location.pathname;
32+
33+
const collapsed = state === "collapsed";
34+
35+
const isActive = (path: string) => {
36+
if (path === "/students") {
37+
return currentPath === "/students" || currentPath.startsWith("/students/");
38+
}
39+
return currentPath === path;
40+
};
41+
42+
const getNavCls = (path: string) =>
43+
isActive(path)
44+
? "bg-primary text-primary-foreground shadow-glow font-medium"
45+
: "text-foreground hover:bg-secondary hover:text-secondary-foreground";
46+
47+
return (
48+
<Sidebar className={collapsed ? "w-16" : "w-64"}>
49+
<SidebarContent className="bg-card border-r border-border">
50+
<div className="p-4">
51+
<div className="flex items-center space-x-2 mb-6">
52+
{!collapsed && (
53+
<>
54+
<div className="w-8 h-8 bg-gradient-primary rounded-lg flex items-center justify-center">
55+
<span className="text-white font-bold text-sm"></span>
56+
</div>
57+
<h1 className="text-lg font-bold text-primary">항해99 플러스</h1>
58+
</>
59+
)}
60+
</div>
61+
</div>
62+
63+
<SidebarGroup>
64+
<SidebarGroupLabel className="text-muted-foreground px-4">{!collapsed && "학습 관리"}</SidebarGroupLabel>
65+
<SidebarGroupContent>
66+
<SidebarMenu className="px-2">
67+
{navigationItems.map((item) => (
68+
<SidebarMenuItem key={item.title}>
69+
<SidebarMenuButton asChild className="h-12">
70+
<NavLink to={item.url} className={`${getNavCls(item.url)} rounded-lg transition-all duration-300`}>
71+
<item.icon className="h-5 w-5" />
72+
{!collapsed && <span className="ml-3">{item.title}</span>}
73+
</NavLink>
74+
</SidebarMenuButton>
75+
</SidebarMenuItem>
76+
))}
77+
</SidebarMenu>
78+
</SidebarGroupContent>
79+
</SidebarGroup>
80+
</SidebarContent>
81+
</Sidebar>
82+
);
83+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./ui";
22
export * from "./layout";
3+
export * from "./AppSidebar";

packages/app/src/components/layout/BaseLayout.tsx

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,20 @@
1-
import { BookOpen } from "lucide-react";
21
import type { ComponentProps, PropsWithChildren } from "react";
2+
import { AppSidebar, SidebarProvider, SidebarTrigger } from "../";
33

44
export function BaseLayout({ children }: PropsWithChildren) {
55
return (
6-
<div className="bg-background-primary">
7-
{/* 헤더 */}
8-
<header className="border-b border-slate-800/50 backdrop-blur-sm color-background-primary sticky top-0 z-10">
9-
<div className="max-w-7xl mx-auto px-4 py-3">
10-
<div className="flex items-center justify-between">
11-
<div className="flex items-center space-x-3">
12-
<div className="flex items-center space-x-2">
13-
<div className="w-8 h-8 bg-gradient-to-r bg-red-500 rounded-lg flex items-center justify-center">
14-
<BookOpen className="w-4 h-4 text-white" />
15-
</div>
16-
<div>
17-
<h1 className="text-lg font-bold text-white">항해플러스 6기</h1>
18-
<p className="text-xs text-slate-400">수강생 커뮤니티</p>
19-
</div>
20-
</div>
21-
</div>
22-
</div>
6+
<SidebarProvider>
7+
<div className="min-h-screen flex w-full">
8+
<AppSidebar />
9+
<div className="flex-1">
10+
<header className="h-12 flex items-center border-b border-border bg-card px-4">
11+
<SidebarTrigger className="mr-4 text-white" />
12+
<h1 className="text-lg font-semibold text-primary">항해플러스 6기 프론트엔드 수강생 커뮤니티</h1>
13+
</header>
14+
<main className="p-6">{children}</main>
2315
</div>
24-
</header>
25-
26-
{children}
27-
</div>
16+
</div>
17+
</SidebarProvider>
2818
);
2919
}
3020

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as React from "react";
2+
3+
import { cn } from "@/lib/utils";
4+
5+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6+
return (
7+
<input
8+
type={type}
9+
data-slot="input"
10+
className={cn(
11+
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
13+
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
14+
className,
15+
)}
16+
{...props}
17+
/>
18+
);
19+
}
20+
21+
export { Input };
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import * as SeparatorPrimitive from "@radix-ui/react-separator";
5+
6+
import { cn } from "@/lib/utils";
7+
8+
function Separator({
9+
className,
10+
orientation = "horizontal",
11+
decorative = true,
12+
...props
13+
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
14+
return (
15+
<SeparatorPrimitive.Root
16+
data-slot="separator"
17+
decorative={decorative}
18+
orientation={orientation}
19+
className={cn(
20+
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
21+
className,
22+
)}
23+
{...props}
24+
/>
25+
);
26+
}
27+
28+
export { Separator };
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import * as React from "react";
2+
import * as SheetPrimitive from "@radix-ui/react-dialog";
3+
import { XIcon } from "lucide-react";
4+
5+
import { cn } from "@/lib/utils";
6+
7+
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
8+
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
9+
}
10+
11+
function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
12+
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
13+
}
14+
15+
function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>) {
16+
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
17+
}
18+
19+
function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
20+
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
21+
}
22+
23+
function SheetOverlay({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
24+
return (
25+
<SheetPrimitive.Overlay
26+
data-slot="sheet-overlay"
27+
className={cn(
28+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
29+
className,
30+
)}
31+
{...props}
32+
/>
33+
);
34+
}
35+
36+
function SheetContent({
37+
className,
38+
children,
39+
side = "right",
40+
...props
41+
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
42+
side?: "top" | "right" | "bottom" | "left";
43+
}) {
44+
return (
45+
<SheetPortal>
46+
<SheetOverlay />
47+
<SheetPrimitive.Content
48+
data-slot="sheet-content"
49+
className={cn(
50+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
51+
side === "right" &&
52+
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
53+
side === "left" &&
54+
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
55+
side === "top" &&
56+
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
57+
side === "bottom" &&
58+
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
59+
className,
60+
)}
61+
{...props}
62+
>
63+
{children}
64+
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
65+
<XIcon className="size-4" />
66+
<span className="sr-only">Close</span>
67+
</SheetPrimitive.Close>
68+
</SheetPrimitive.Content>
69+
</SheetPortal>
70+
);
71+
}
72+
73+
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
74+
return <div data-slot="sheet-header" className={cn("flex flex-col gap-1.5 p-4", className)} {...props} />;
75+
}
76+
77+
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
78+
return <div data-slot="sheet-footer" className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />;
79+
}
80+
81+
function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
82+
return (
83+
<SheetPrimitive.Title
84+
data-slot="sheet-title"
85+
className={cn("text-foreground font-semibold", className)}
86+
{...props}
87+
/>
88+
);
89+
}
90+
91+
function SheetDescription({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Description>) {
92+
return (
93+
<SheetPrimitive.Description
94+
data-slot="sheet-description"
95+
className={cn("text-muted-foreground text-sm", className)}
96+
{...props}
97+
/>
98+
);
99+
}
100+
101+
export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription };

0 commit comments

Comments
 (0)