prefetched sidebar main content

This commit is contained in:
mr-shortman 2025-03-15 12:39:09 +01:00
parent 38b43174ec
commit f591e18847
9 changed files with 153 additions and 112 deletions

View File

@ -2,18 +2,23 @@ import { AppSidebar } from "@/components/layout/app-sidebar";
import Navbar from "@/components/layout/navbar";
import { SidebarProvider } from "@/components/ui/sidebar";
import { auth } from "@/server/auth";
import { api, HydrateClient } from "@/trpc/server";
import React from "react";
async function Layout({ children }: { children: React.ReactNode }) {
const session = await auth();
void api.app.getSidebarMain.prefetch();
return (
<SidebarProvider>
<AppSidebar user={session?.user} />
<div className="h-screen w-full bg-background">
<Navbar />
<main className="space-y-4 p-4">{children}</main>
</div>
</SidebarProvider>
<HydrateClient>
<SidebarProvider>
<AppSidebar user={session?.user} />
<div className="h-screen w-full bg-background">
<Navbar />
<main className="space-y-4 p-4">{children}</main>
</div>
</SidebarProvider>
</HydrateClient>
);
}

View File

@ -91,7 +91,7 @@ function ArticleFilterBar({
sort: currentValue?.length ? currentValue : undefined,
});
}}
className="w-full"
className="w-full lg:max-w-64"
messageUi={{
selectIcon: FilterIcon,
select: "Sortieren",

View File

@ -1,6 +1,5 @@
"use client";
import * as React from "react";
import { Folder, LifeBuoy, Newspaper, Send } from "lucide-react";
import { NavMain } from "./nav-main";
@ -19,71 +18,6 @@ import NavBranding from "./nav-branding";
import { Icons } from "@/components/icons";
import { appConfig } from "@/config";
const data = {
user: {
name: "shadcn",
email: "m@example.com",
avatar: "/avatars/shadcn.jpg",
},
navMain: [
{
title: "Artikel",
url: "/artikel",
isActive: true,
icon: Newspaper,
items: [
{
title: "Genesis",
url: "#",
},
{
title: "Explorer",
url: "#",
},
{
title: "Quantum",
url: "#",
},
{
title: "Alle Artikel",
url: "/artikel",
},
],
},
{
title: "Kategorien",
url: "/kategorien",
icon: Folder,
items: [
{
title: "History",
url: "#",
},
{
title: "Starred",
url: "#",
},
{
title: "Settings",
url: "#",
},
{
title: "Alle Kategorien",
url: "#",
},
],
},
],
navSecondary: [
{
title: "Discord",
url: appConfig.socials.discord,
icon: Icons.discord,
external: true,
},
],
};
export function AppSidebar({
...props
}: React.ComponentProps<typeof Sidebar> & { user?: User | null }) {
@ -93,7 +27,8 @@ export function AppSidebar({
<NavBranding subTitle="Wissen" />
</SidebarHeader>
<SidebarContent>
<NavMain items={data.navMain} />
<NavMain />
<NavSecondary items={data.navSecondary} className="mt-auto" />
</SidebarContent>
<SidebarFooter>
@ -103,3 +38,14 @@ export function AppSidebar({
</Sidebar>
);
}
const data = {
navSecondary: [
{
title: "Discord",
url: appConfig.socials.discord,
icon: Icons.discord,
external: true,
},
],
};

View File

@ -1,18 +1,24 @@
"use client";
import React from "react";
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar";
import { Icons } from "@/components/icons";
import { appConfig } from "@/config";
import Link from "next/link";
import { cn } from "@/lib/utils";
import { XIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
function NavBranding({ subTitle }: { subTitle: string }) {
const { isMobile, toggleSidebar } = useSidebar();
return (
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" asChild>
<SidebarMenu className={cn(isMobile && "flex flex-row items-center")}>
<SidebarMenuItem className="w-full">
<SidebarMenuButton size="lg" asChild className="w-full">
<Link href="/">
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-foreground text-background">
<Icons.logo className="size-4" />
@ -24,6 +30,15 @@ function NavBranding({ subTitle }: { subTitle: string }) {
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
{isMobile && (
<SidebarMenuItem className="w-max">
<SidebarMenuButton asChild>
<Button variant={"ghost"} size={"icon"} onClick={toggleSidebar}>
<XIcon className="size-4" />
</Button>
</SidebarMenuButton>
</SidebarMenuItem>
)}
</SidebarMenu>
);
}

View File

@ -1,5 +1,4 @@
"use client";
import { ChevronRight, type LucideIcon } from "lucide-react";
import {
@ -18,22 +17,45 @@ import {
SidebarMenuSubButton,
SidebarMenuSubItem,
} from "@/components/ui/sidebar";
import { Skeleton } from "@/components/ui/skeleton";
import { api } from "@/trpc/react";
import { Icons } from "@/components/icons";
export function NavMain({
items,
}: {
items: {
title: string;
url: string;
icon: LucideIcon;
isActive?: boolean;
items?: {
title: string;
url: string;
hideBorder?: boolean;
}[];
}[];
}) {
export function NavMain() {
const [{ articles, categories }] = api.app.getSidebarMain.useSuspenseQuery();
const items = [
{
title: "Artikel",
url: "/artikel",
isActive: true,
icon: Icons.article,
items: [
...articles.map((article) => ({
title: article.title,
url: `/artikel/${article.slug}`,
})),
{
title: "Alle Artikel",
url: "/artikel",
},
],
},
{
title: "Kategorien",
url: "/kategorie",
icon: Icons.category,
items: [
...categories.map((category) => ({
title: category.name,
url: `/kategorie/${category.slug}`,
})),
{
title: "Alle Kategorien",
url: "#",
},
],
},
];
return (
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
@ -77,3 +99,20 @@ export function NavMain({
</SidebarGroup>
);
}
export function NavMainSkeleton() {
return (
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
{Array.from({ length: 6 }).map((_, i) => (
<SidebarMenuItem key={i}>
<SidebarMenuButton asChild size="sm">
<Skeleton className="h-4 w-full" />
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
);
}

View File

@ -0,0 +1,24 @@
"use client";
import { Button } from "@/components/ui/button";
import { useSidebar } from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";
import { ChevronRightIcon, MenuIcon } from "lucide-react";
export function SidebarTrigger() {
const { toggleSidebar, state, isMobile } = useSidebar();
const open = state === "expanded";
return (
<Button onClick={toggleSidebar} size={"icon"} variant={"ghost"}>
{isMobile ? (
<MenuIcon className="size-4" />
) : (
<ChevronRightIcon
className={cn(
"transition-transform duration-150",
open && "rotate-180",
)}
/>
)}
</Button>
);
}

View File

@ -5,6 +5,7 @@ import { hasPermission, Role } from "@/lib/validation/permissions";
import { ModeToggle } from "../mode-switch";
import EditorDropdown from "../editor-dropdown";
import { SidebarTrigger } from "./app-sidebar/sidebar-trigger";
async function Navbar() {
const session = await auth();
@ -13,10 +14,13 @@ async function Navbar() {
: false;
return (
<div className="flex h-14 items-center justify-end gap-4 border-b bg-background px-4">
{isEditor && <EditorDropdown />}
<div className="flex h-14 items-center justify-between gap-4 border-b bg-sidebar px-4">
<SidebarTrigger />
<div className="flex items-center gap-4">
{isEditor && <EditorDropdown />}
<ModeToggle />
<ModeToggle />
</div>
</div>
);
}

View File

@ -24,16 +24,24 @@ export const appRouter = createTRPCRouter({
return { articles, categories };
}),
getSidebarContent: publicProcedure.query(async ({ ctx }) => {
return await ctx.db.query.categories.findMany({
with: {
articles: {
columns: {
title: true,
slug: true,
},
},
getSidebarMain: publicProcedure.query(async ({ ctx }) => {
const categories = await ctx.db.query.categories.findMany({
limit: 3,
columns: {
slug: true,
name: true,
},
});
const articles = await ctx.db.query.articles.findMany({
limit: 3,
columns: {
slug: true,
title: true,
},
});
return {
categories,
articles,
};
}),
});

View File

@ -29,8 +29,8 @@
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
/* --sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%; */
--sidebar-background: 0 0% 100%;
--sidebar-foreground: 240 10% 3.9%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
@ -40,9 +40,9 @@
}
.dark {
--background: 240 10% 3.9%;
--background: 240 10% 1%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card: 240 10% 1%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
@ -64,8 +64,8 @@
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
/* --sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%; */
--sidebar-background: 240 10% 1%;
--sidebar-foreground: 0 0% 98%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;