From 4b1c9d1669eb30a093742f7b5319e66a13428271 Mon Sep 17 00:00:00 2001 From: Pinapelz Date: Tue, 2 Jun 2026 12:02:22 -0700 Subject: add sidebar navigation --- src/app/(main)/layout.tsx | 12 +++ src/app/(main)/page.tsx | 143 +++++++++++++++++++++++++++++++ src/app/components/nav-items.ts | 5 ++ src/app/components/sidebar.tsx | 185 ++++++++++++++++++++++++++++++++++++++++ src/app/page.tsx | 143 ------------------------------- 5 files changed, 345 insertions(+), 143 deletions(-) create mode 100644 src/app/(main)/layout.tsx create mode 100644 src/app/(main)/page.tsx create mode 100644 src/app/components/nav-items.ts create mode 100644 src/app/components/sidebar.tsx delete mode 100644 src/app/page.tsx (limited to 'src/app') 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 ( +
+ +
+ {children} +
+
+ ); +} diff --git a/src/app/(main)/page.tsx b/src/app/(main)/page.tsx new file mode 100644 index 0000000..e20438f --- /dev/null +++ b/src/app/(main)/page.tsx @@ -0,0 +1,143 @@ +"use client"; +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 { + Root, + Navbar, + Logo, + LogoIcon, + NavCtaLink, + NavLeft, + NavCenter, + SearchBox, + SearchInput, + SearchButton, + NavRight, + + GridContainer, + CardGrid, + Card, + ThumbnailWrapper, + Thumbnail, + PlayOverlay, + PlayCircle, + CardMeta, + CardInfo, + CardTitle, + CardSub, + EmptyState, + TypingGlobalStyle, +} from "../page.styles"; + +interface ChartRecord { + id: string; + title: string; + artist: string; + thumbnail: string; + lrc: string; + media: string; + offset: number; +} + + +export default function TypingPage() { + const { user, signOut } = useAuth(); + const [charts, setCharts] = useState([]); + const [search, setSearch] = useState(""); + + useEffect(() => { + pb.collection("charts") + .getFullList({ sort: "-created" }) + .then(setCharts) + .catch(console.error); + }, []); + + const normalizedSearch = search.trim().toLowerCase(); + const filtered = normalizedSearch + ? charts.filter( + (item) => + item.title.toLowerCase().includes(normalizedSearch) || + item.artist.toLowerCase().includes(normalizedSearch), + ) + : charts; + + return ( + <> + + + + + + + + + TypingMIXX + + + + + + setSearch(e.target.value)} + /> + + + + + + + + {user ? ( + <> + + {user.username || user.name} + + { e.preventDefault(); signOut(); }}> + Sign out + + + ) : ( + Sign in + )} + + + + + + {filtered.length === 0 ? ( + No results found. + ) : ( + filtered.map((item) => ( + + + {item.thumbnail ? ( + + ) : ( + + )} + + + + + + + + + {item.title} + {item.artist} + + + + )) + )} + + + + + ); +} 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 ( + + setCollapsed((c) => !c)} + aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"} + > + {collapsed ? : } + + + + +
+ {user ? ( + <> + {user.username || user.name} + + + + + + ) : ( + + + + + )} +
+
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx deleted file mode 100644 index 7a9b718..0000000 --- a/src/app/page.tsx +++ /dev/null @@ -1,143 +0,0 @@ -"use client"; -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 { - Root, - Navbar, - Logo, - LogoIcon, - NavCtaLink, - NavLeft, - NavCenter, - SearchBox, - SearchInput, - SearchButton, - NavRight, - - GridContainer, - CardGrid, - Card, - ThumbnailWrapper, - Thumbnail, - PlayOverlay, - PlayCircle, - CardMeta, - CardInfo, - CardTitle, - CardSub, - EmptyState, - TypingGlobalStyle, -} from "./page.styles"; - -interface ChartRecord { - id: string; - title: string; - artist: string; - thumbnail: string; - lrc: string; - media: string; - offset: number; -} - - -export default function TypingPage() { - const { user, signOut } = useAuth(); - const [charts, setCharts] = useState([]); - const [search, setSearch] = useState(""); - - useEffect(() => { - pb.collection("charts") - .getFullList({ sort: "-created" }) - .then(setCharts) - .catch(console.error); - }, []); - - const normalizedSearch = search.trim().toLowerCase(); - const filtered = normalizedSearch - ? charts.filter( - (item) => - item.title.toLowerCase().includes(normalizedSearch) || - item.artist.toLowerCase().includes(normalizedSearch), - ) - : charts; - - return ( - <> - - - - - - - - - TypingMIXX - - - - - - setSearch(e.target.value)} - /> - - - - - - - - {user ? ( - <> - - {user.username || user.name} - - { e.preventDefault(); signOut(); }}> - Sign out - - - ) : ( - Sign in - )} - - - - - - {filtered.length === 0 ? ( - No results found. - ) : ( - filtered.map((item) => ( - - - {item.thumbnail ? ( - - ) : ( - - )} - - - - - - - - - {item.title} - {item.artist} - - - - )) - )} - - - - - ); -} -- cgit v1.2.3