테마 적용

This commit is contained in:
2026-02-11 14:06:06 +09:00
parent def87bd47a
commit 95291e6922
30 changed files with 1209 additions and 496 deletions

View File

@@ -79,7 +79,7 @@ export function Header({
: "text-foreground group-hover:text-primary",
)}
>
AutoTrade
Jurini
</span>
</Link>

View File

@@ -1,9 +1,17 @@
"use client";
import { cn } from "@/lib/utils";
import { BarChart2, Home, Settings, User, Wallet } from "lucide-react";
import {
BarChart2,
ChevronLeft,
Home,
Settings,
User,
Wallet,
} from "lucide-react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { MenuItem } from "../types";
const MENU_ITEMS: MenuItem[] = [
@@ -52,9 +60,36 @@ const MENU_ITEMS: MenuItem[] = [
*/
export function Sidebar() {
const pathname = usePathname();
const [isExpanded, setIsExpanded] = useState(false);
return (
<aside className="group/sidebar hidden h-[calc(100vh-4rem)] shrink-0 overflow-y-auto border-r border-zinc-200 bg-white px-2 py-5 transition-[width] duration-200 dark:border-zinc-800 dark:bg-black md:sticky md:top-16 md:block md:w-[74px] md:hover:w-64 md:focus-within:w-64">
<aside
className={cn(
"relative hidden h-[calc(100vh-4rem)] shrink-0 overflow-x-visible overflow-y-auto border-r border-brand-100 bg-white px-2 py-5 transition-[width] duration-200 dark:border-brand-900/40 dark:bg-background md:sticky md:top-16 md:block",
isExpanded ? "md:w-64" : "md:w-[74px]",
)}
>
<button
type="button"
onClick={() => setIsExpanded((prev) => !prev)}
aria-label={isExpanded ? "Collapse sidebar" : "Expand sidebar"}
className={cn(
"absolute -right-3 top-20 z-50 hidden h-8 w-8 items-center justify-center rounded-full",
"border border-zinc-200/50 bg-white/80 shadow-lg backdrop-blur-md transition-all duration-300",
"hover:scale-110 hover:bg-white active:scale-95",
"dark:border-zinc-800/50 dark:bg-zinc-900/80 dark:hover:bg-zinc-900",
"md:flex",
)}
>
<ChevronLeft
className={cn(
"h-4 w-4 text-zinc-600 transition-transform duration-300 dark:text-zinc-300",
isExpanded ? "rotate-0" : "rotate-180",
)}
/>
</button>
<div className="h-1.5" />
{/* ========== SIDEBAR ITEMS ========== */}
<div className="flex flex-col space-y-1.5">
{MENU_ITEMS.map((item) => {
@@ -69,10 +104,10 @@ export function Sidebar() {
title={item.title}
className={cn(
"group/item relative flex items-center rounded-xl px-3 py-2.5 text-sm transition-colors",
"hover:bg-zinc-100 hover:text-zinc-900 dark:hover:bg-zinc-800 dark:hover:text-zinc-50",
"hover:bg-brand-50 hover:text-brand-800 dark:hover:bg-brand-900/30 dark:hover:text-brand-100",
isActive
? "bg-zinc-100 text-zinc-900 shadow-sm dark:bg-zinc-800 dark:text-zinc-50"
: "text-zinc-500 dark:text-zinc-400",
? "bg-brand-100 text-brand-800 shadow-sm dark:bg-brand-900/40 dark:text-brand-100"
: "text-muted-foreground dark:text-brand-200/80",
)}
>
{/* ========== ACTIVE BAR ========== */}
@@ -88,17 +123,24 @@ export function Sidebar() {
className={cn(
"h-5 w-5 shrink-0 transition-colors",
isActive
? "text-zinc-900 dark:text-zinc-50"
: "text-zinc-400 group-hover/item:text-zinc-900 dark:text-zinc-500 dark:group-hover/item:text-zinc-50",
? "text-brand-700 dark:text-brand-200"
: "text-zinc-400 group-hover/item:text-brand-700 dark:text-brand-300/70 dark:group-hover/item:text-brand-200",
)}
/>
{item.badge && (
<span className="absolute left-7 top-2 h-2 w-2 rounded-full bg-brand-500 md:group-hover/sidebar:hidden md:group-focus-within/sidebar:hidden" />
{item.badge && !isExpanded && (
<span className="absolute left-7 top-2 h-2 w-2 rounded-full bg-brand-500" />
)}
{/* ========== LABEL (EXPAND ON HOVER/FOCUS) ========== */}
<span className="ml-3 flex min-w-0 items-center gap-1.5 overflow-hidden whitespace-nowrap transition-all duration-200 md:max-w-0 md:opacity-0 md:group-hover/sidebar:max-w-[180px] md:group-hover/sidebar:opacity-100 md:group-focus-within/sidebar:max-w-[180px] md:group-focus-within/sidebar:opacity-100">
{/* ========== LABEL (EXPAND ON TOGGLE) ========== */}
<span
className={cn(
"ml-3 flex min-w-0 items-center gap-1.5 overflow-hidden whitespace-nowrap transition-all duration-200",
isExpanded
? "max-w-[180px] opacity-100"
: "max-w-0 opacity-0",
)}
>
<span className="truncate font-medium">{item.title}</span>
{item.badge && (
<span className="shrink-0 rounded-full bg-brand-100 px-1.5 py-0.5 text-[10px] font-semibold text-brand-700">
@@ -120,15 +162,22 @@ export function Sidebar() {
*/
export function MobileBottomNav() {
const pathname = usePathname();
const bottomItems = MENU_ITEMS.filter((item) => item.showInBottomNav !== false);
const bottomItems = MENU_ITEMS.filter(
(item) => item.showInBottomNav !== false,
);
return (
<nav
aria-label="모바일 빠른 메뉴"
className="fixed inset-x-0 bottom-0 z-40 border-t border-zinc-200 bg-white/95 backdrop-blur-sm supports-backdrop-filter:bg-white/80 dark:border-zinc-800 dark:bg-black/95 dark:supports-backdrop-filter:bg-black/80 md:hidden"
className="fixed inset-x-0 bottom-0 z-40 border-t border-brand-100 bg-white/95 backdrop-blur-sm supports-backdrop-filter:bg-white/80 dark:border-brand-900/40 dark:bg-background/95 dark:supports-backdrop-filter:bg-background/80 md:hidden"
>
{/* ========== BOTTOM NAV ITEMS ========== */}
<div className={cn("grid", bottomItems.length === 4 ? "grid-cols-4" : "grid-cols-5")}>
<div
className={cn(
"grid",
bottomItems.length === 4 ? "grid-cols-4" : "grid-cols-5",
)}
>
{bottomItems.map((item) => {
const isActive = item.matchExact
? pathname === item.href
@@ -142,11 +191,13 @@ export function MobileBottomNav() {
"flex min-h-16 flex-col items-center justify-center gap-1.5 text-[11px] font-medium transition-colors",
isActive
? "text-brand-700"
: "text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-50",
: "text-muted-foreground hover:text-brand-700 dark:text-brand-200/80 dark:hover:text-brand-200",
)}
>
<span className="relative">
<item.icon className={cn("h-4 w-4", isActive && "text-brand-600")} />
<item.icon
className={cn("h-4 w-4", isActive && "text-brand-600")}
/>
{item.badge && (
<span className="absolute -right-1 -top-1 h-2 w-2 rounded-full bg-brand-500" />
)}