- Notifications
You must be signed in to change notification settings - Fork 0
refactor: adopt Grok-style layout with shadcn/ui sidebar#18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
722c4792982f54d9ea92884246dc202c77bf702f3eFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| "use client"; | ||
| import { SignedIn, UserButton } from "@clerk/nextjs"; | ||
| import Image from "next/image"; | ||
| import Link from "next/link"; | ||
| import { usePathname } from "next/navigation"; | ||
| import { ThemeLogo } from "@/components/navigation/full-logo"; | ||
| import { NAV_LINKS } from "@/components/navigation/nav-links.constants"; | ||
| import { SidebarToggleButton } from "@/components/navigation/sidebar-toggle-button"; | ||
| import { | ||
| Sidebar, | ||
| SidebarContent, | ||
| SidebarFooter, | ||
| SidebarGroup, | ||
| SidebarGroupContent, | ||
| SidebarHeader, | ||
| SidebarMenu, | ||
| SidebarMenuButton, | ||
| SidebarMenuItem, | ||
| SidebarRail, | ||
| } from "@/components/ui/sidebar"; | ||
| import { | ||
| cn, | ||
| getNavIconClasses, | ||
| isRouteActive, | ||
| NAV_LINK_ACTIVE_CLASSES, | ||
| NAV_LINK_INACTIVE_CLASSES, | ||
| } from "@/lib/utils"; | ||
| export function AppSidebar() { | ||
| const pathname = usePathname(); | ||
| return ( | ||
| <Sidebar id="app-sidebar" collapsible="icon"> | ||
| {/* Header: Logo - h-14 matches ContentTopBar height */} | ||
| <SidebarHeader className="h-14 flex-row items-center px-3"> | ||
| <Link | ||
| href="/" | ||
| aria-label="DevFlow sidebar logo" | ||
| className="flex items-center group-data-[collapsible=icon]:size-6 group-data-[collapsible=icon]:justify-center" | ||
| > | ||
| {/* Icon-only when collapsed */} | ||
| {/* biome-ignore lint/a11y/useAltText: Decorative logo, aria-label on parent link */} | ||
| {/* biome-ignore lint/performance/noImgElement: SVG logo doesn't benefit from next/image optimisation */} | ||
| <img | ||
| src="/images/site-logo.svg" | ||
| className="size-6 group-data-[collapsible=icon]:block hidden" | ||
| /> | ||
| {/* Full logo when expanded */} | ||
| <ThemeLogo className="group-data-[collapsible=icon]:hidden" /> | ||
| </Link> | ||
| </SidebarHeader> | ||
| {/* Main Navigation */} | ||
| <SidebarContent> | ||
| <SidebarGroup> | ||
| <SidebarGroupContent> | ||
| <SidebarMenu className="gap-3"> | ||
| {NAV_LINKS.map((link) => { | ||
| const isActive = isRouteActive(pathname, link.route); | ||
| return ( | ||
| <SidebarMenuItem key={link.route}> | ||
| <SidebarMenuButton | ||
| asChild | ||
| isActive={isActive} | ||
| tooltip={link.label} | ||
| className={cn( | ||
| "rounded-lg", | ||
| isActive | ||
| ? `${NAV_LINK_ACTIVE_CLASSES} hover:bg-(image:--gradient-primary) hover:text-primary-foreground` | ||
| : NAV_LINK_INACTIVE_CLASSES, | ||
| )} | ||
michellepace marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| > | ||
| <Link href={link.route}> | ||
| <Image | ||
| src={link.imgURL} | ||
| alt="" | ||
| width={20} | ||
| height={20} | ||
| className={getNavIconClasses(isActive)} | ||
| /> | ||
| <span>{link.label}</span> | ||
| </Link> | ||
| </SidebarMenuButton> | ||
| </SidebarMenuItem> | ||
| ); | ||
| })} | ||
| </SidebarMenu> | ||
| </SidebarGroupContent> | ||
| </SidebarGroup> | ||
| </SidebarContent> | ||
| {/* Footer: UserButton + Toggle */} | ||
| <SidebarFooter className="p-2"> | ||
| <div | ||
| className={cn( | ||
| "flex items-center gap-2", | ||
| // Expanded: horizontal row with space between | ||
| "justify-between", | ||
| // Collapsed (icon mode): vertical stack | ||
| "group-data-[collapsible=icon]:flex-col", | ||
| "group-data-[collapsible=icon]:justify-start", | ||
| )} | ||
| > | ||
| <SignedIn> | ||
| <UserButton /> | ||
| </SignedIn> | ||
| <SidebarToggleButton /> | ||
| </div> | ||
| </SidebarFooter> | ||
| {/* Edge-click rail to toggle */} | ||
| <SidebarRail /> | ||
| </Sidebar> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { SignedOut, SignInButton, SignUpButton } from "@clerk/nextjs"; | ||
| import { ThemeToggle } from "@/components/navigation/theme-toggle"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { CONTENT_HORIZONTAL_PADDING, cn, HEADER_HEIGHT } from "@/lib/utils"; | ||
| export function ContentTopBar() { | ||
| return ( | ||
| <header | ||
| className={cn( | ||
| "sticky top-0 z-40 hidden items-center gap-4 border-b bg-background sm:flex", | ||
| HEADER_HEIGHT, | ||
| CONTENT_HORIZONTAL_PADDING, | ||
| )} | ||
| > | ||
| {/* Left: Search - grows and centres */} | ||
| <div className="flex flex-1 justify-center"> | ||
| <p className="text-muted-foreground">Global Search</p> | ||
michellepace marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| </div> | ||
| {/* Right: Theme + Auth - fixed width */} | ||
| <div className="flex flex-none items-center gap-2"> | ||
| <ThemeToggle /> | ||
| <SignedOut> | ||
| <SignInButton> | ||
| <Button variant="ghost">Sign in</Button> | ||
| </SignInButton> | ||
| <SignUpButton> | ||
| <Button>Sign up</Button> | ||
| </SignUpButton> | ||
| </SignedOut> | ||
michellepace marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| </div> | ||
| </header> | ||
| ); | ||
| } | ||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import Link from "next/link"; | ||
| import { MobileNav } from "@/components/navigation/mobile-nav"; | ||
| import { ThemeToggle } from "@/components/navigation/theme-toggle"; | ||
| import { cn, HEADER_HEIGHT } from "@/lib/utils"; | ||
| export function MobileHeader() { | ||
| return ( | ||
| <header | ||
| className={cn( | ||
| "fixed top-0 z-50 flex w-full items-center justify-between bg-sidebar px-4 shadow-sm sm:hidden", | ||
| HEADER_HEIGHT, | ||
| )} | ||
| > | ||
| {/* Left: Logo icon */} | ||
| <Link href="/" aria-label="DevFlow mobile logo"> | ||
| {/* biome-ignore lint/a11y/useAltText: Decorative logo, aria-label on parent link */} | ||
| {/* biome-ignore lint/performance/noImgElement: SVG logo doesn't benefit from next/image optimisation */} | ||
| <img src="/images/site-logo.svg" className="size-7" /> | ||
| </Link> | ||
| {/* Right: Theme toggle + hamburger */} | ||
| <div className="flex items-center gap-2"> | ||
| <ThemeToggle /> | ||
| <MobileNav /> | ||
| </div> | ||
| </header> | ||
| ); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.