diff options
| author | Pinapelz <yukais@pinapelz.com> | 2026-06-02 12:02:22 -0700 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2026-06-02 12:02:22 -0700 |
| commit | 4b1c9d1669eb30a093742f7b5319e66a13428271 (patch) | |
| tree | d847e0850ae440aaaf547904bc8395e5bb47ff1d /src/app | |
| parent | f9f1a4a5377d99db30ae6e4507cb0af970f003ef (diff) | |
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/(main)/layout.tsx | 12 | ||||
| -rw-r--r-- | src/app/(main)/page.tsx (renamed from src/app/page.tsx) | 6 | ||||
| -rw-r--r-- | src/app/components/nav-items.ts | 5 | ||||
| -rw-r--r-- | src/app/components/sidebar.tsx | 185 |
4 files changed, 205 insertions, 3 deletions
diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx new file mode 100644 index 0000000..df1036f --- /dev/null +++ b/src/app/(main)/layout.tsx @@ -0,0 +1,12 @@ +import { Sidebar } from "../components/sidebar"; + +export default function MainLayout({ children }: { children: React.ReactNode }) { + return ( + <div style={{ display: "flex", height: "100vh", backgroundColor: "#0b0b10" }}> + <Sidebar /> + <div style={{ flex: 1, minWidth: 0, overflowY: "auto" }}> + {children} + </div> + </div> + ); +} diff --git a/src/app/page.tsx b/src/app/(main)/page.tsx index 7a9b718..e20438f 100644 --- a/src/app/page.tsx +++ b/src/app/(main)/page.tsx @@ -2,8 +2,8 @@ import { useEffect, useState } from "react"; import { FaPlay, FaMusic, FaSearch } from "react-icons/fa"; import { MdLibraryMusic } from "react-icons/md"; -import { useAuth } from "./context/auth"; -import pb from "./lib/pocketbase"; +import { useAuth } from "../context/auth"; +import pb from "../lib/pocketbase"; import { Root, Navbar, @@ -30,7 +30,7 @@ import { CardSub, EmptyState, TypingGlobalStyle, -} from "./page.styles"; +} from "../page.styles"; interface ChartRecord { id: string; diff --git a/src/app/components/nav-items.ts b/src/app/components/nav-items.ts new file mode 100644 index 0000000..d676091 --- /dev/null +++ b/src/app/components/nav-items.ts @@ -0,0 +1,5 @@ +import { Home } from "lucide-react"; + +export const navItems = [ + { title: "Home", href: "/", icon: Home }, +]; diff --git a/src/app/components/sidebar.tsx b/src/app/components/sidebar.tsx new file mode 100644 index 0000000..0185048 --- /dev/null +++ b/src/app/components/sidebar.tsx @@ -0,0 +1,185 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import styled from "styled-components"; +import { ChevronLeft, ChevronRight, LogIn, LogOut } from "lucide-react"; +import { useAuth } from "../context/auth"; +import { navItems } from "./nav-items"; + +const SidebarRoot = styled.aside<{ $collapsed: boolean }>` + width: ${(p) => (p.$collapsed ? "52px" : "200px")}; + flex-shrink: 0; + display: flex; + flex-direction: column; + border-right: 1px solid #1f1f2a; + background-color: #0b0b10; + overflow: hidden; + transition: width 0.2s ease; +`; + +const ToggleBtn = styled.button` + display: flex; + align-items: center; + justify-content: center; + padding: 10px 0; + width: 100%; + flex-shrink: 0; + background: none; + border: none; + border-bottom: 1px solid #1f1f2a; + cursor: pointer; + color: #8b90a0; + transition: background 0.15s, color 0.15s; + &:hover { + background-color: #1a1d29; + color: #f5f5f5; + } +`; + +const Nav = styled.nav` + flex: 1; + padding: 12px 8px; + display: flex; + flex-direction: column; + gap: 2px; + overflow-y: auto; + overflow-x: hidden; +`; + +const NavItem = styled(Link)<{ $active: boolean; $collapsed: boolean }>` + display: flex; + align-items: center; + justify-content: ${(p) => (p.$collapsed ? "center" : "flex-start")}; + gap: ${(p) => (p.$collapsed ? "0" : "10px")}; + padding: ${(p) => (p.$collapsed ? "9px 0" : "9px 12px")}; + text-decoration: none; + font-size: 13px; + font-weight: 500; + white-space: nowrap; + color: ${(p) => (p.$active ? "#f5f5f5" : "#8b90a0")}; + background-color: ${(p) => (p.$active ? "#1a1d29" : "transparent")}; + border-left: 2px solid ${(p) => (p.$active ? "#a78bfa" : "transparent")}; + transition: background 0.15s, color 0.15s; + &:hover { + background-color: #1a1d29; + color: #f5f5f5; + } +`; + +const Label = styled.span<{ $collapsed: boolean }>` + max-width: ${(p) => (p.$collapsed ? "0" : "200px")}; + overflow: hidden; + white-space: nowrap; + opacity: ${(p) => (p.$collapsed ? 0 : 1)}; + transition: max-width 0.2s ease, opacity 0.1s ease; +`; + + +const Footer = styled.div` + padding: 12px 8px; + border-top: 1px solid #1f1f2a; + display: flex; + flex-direction: column; + gap: 2px; +`; + +const Username = styled.span<{ $collapsed: boolean }>` + display: block; + padding: ${(p) => (p.$collapsed ? "0" : "6px 12px")}; + max-height: ${(p) => (p.$collapsed ? "0" : "28px")}; + font-size: 12px; + color: #4b5563; + overflow: hidden; + white-space: nowrap; + opacity: ${(p) => (p.$collapsed ? 0 : 1)}; + transition: max-height 0.2s ease, padding 0.2s ease, opacity 0.1s ease; +`; + +const AuthButton = styled.button<{ $collapsed: boolean }>` + display: flex; + align-items: center; + justify-content: ${(p) => (p.$collapsed ? "center" : "flex-start")}; + gap: 10px; + padding: 9px 12px; + background: none; + border: none; + cursor: pointer; + font-size: 13px; + font-weight: 500; + white-space: nowrap; + color: #8b90a0; + width: 100%; + text-align: left; + transition: background 0.15s, color 0.15s; + &:hover { + background-color: #1a1d29; + color: #f5f5f5; + } +`; + +const AuthLink = styled(Link)<{ $collapsed: boolean }>` + display: flex; + align-items: center; + justify-content: ${(p) => (p.$collapsed ? "center" : "flex-start")}; + gap: 10px; + padding: 9px 12px; + text-decoration: none; + font-size: 13px; + font-weight: 500; + white-space: nowrap; + color: #8b90a0; + transition: background 0.15s, color 0.15s; + &:hover { + background-color: #1a1d29; + color: #f5f5f5; + } +`; + +export function Sidebar() { + const pathname = usePathname(); + const { user, signOut } = useAuth(); + const [collapsed, setCollapsed] = useState(false); + + return ( + <SidebarRoot $collapsed={collapsed}> + <ToggleBtn + onClick={() => setCollapsed((c) => !c)} + aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"} + > + {collapsed ? <ChevronRight size={16} /> : <ChevronLeft size={16} />} + </ToggleBtn> + + <Nav> + {navItems.map((item) => { + const active = pathname === item.href; + const Icon = item.icon; + return ( + <NavItem key={item.href} href={item.href} $active={active} $collapsed={collapsed}> + <Icon size={16} /> + <Label $collapsed={collapsed}>{item.title}</Label> + </NavItem> + ); + })} + </Nav> + + <Footer> + {user ? ( + <> + <Username $collapsed={collapsed}>{user.username || user.name}</Username> + <AuthButton $collapsed={collapsed} onClick={signOut}> + <LogOut size={16} /> + <Label $collapsed={collapsed}>Sign out</Label> + </AuthButton> + </> + ) : ( + <AuthLink href="/signin" $collapsed={collapsed}> + <LogIn size={16} /> + <Label $collapsed={collapsed}>Sign in</Label> + </AuthLink> + )} + </Footer> + </SidebarRoot> + ); +} |
