文章
nextjs shadcn starter
安装shadcn
npx shadcn@latest init
安装shadcn组件
npx shadcn@latest add button
npx shadcn@latest add avatar
npx shadcn@latest add dropdown-menu
npx shadcn@latest add sidebar
自定义组件
/src/components/AppSiderbar.tsx
import { Calendar, ChevronDown, ChevronUp, Home, Inbox, Plus, Projector, Search, Settings, User2 } from "lucide-react"
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarSeparator } from "./ui/sidebar"
import Link from "next/link"
import Image from "next/image"
import { DropdownMenuTrigger, DropdownMenu, DropdownMenuContent, DropdownMenuItem } from "./ui/dropdown-menu"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible"
const items = [
{
title: "Home",
url: "/",
icon: Home,
},
{
title: "Inbox",
url: "/",
icon: Inbox,
},
{
title: "Calendar",
url: "/",
icon: Calendar,
},
{
title: "Search",
url: "/",
icon: Search,
},
{
title: "Settings",
url: "/",
icon: Settings,
},
]
export default function AppSideBar() {
return (
<Sidebar collapsible="icon">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<Link href="/">
<Image src="./vercel.svg" alt="log" width={20} height={20} />
<span>Dashboard</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarSeparator />
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Application</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<Link href={item.url}>
<item.icon />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
{item.title === "Inbox" && (
<SidebarMenuBadge>24</SidebarMenuBadge>
)}
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup>
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarGroupAction>
<Plus /> <span className="sr-only">Add Project</span>
</SidebarGroupAction>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<Link href="/">
<Projector />
see All Projects
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<Link href="/">
<Plus />
see All Projects
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
{/* COLLAPSIBLE */}
<Collapsible defaultOpen className="group/collapsible">
<SidebarGroup>
<SidebarGroupLabel asChild>
<CollapsibleTrigger>
Collapsible
<ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
</CollapsibleTrigger>
</SidebarGroupLabel>
<CollapsibleContent>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<Link href="/">
<Projector />
see All Projects
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<Link href="/">
<Plus />
see All Projects
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</CollapsibleContent>
</SidebarGroup>
</Collapsible>
{/* NESTED */}
<SidebarGroup>
<SidebarGroupLabel>Nested Items</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<Link href="/">
<Projector />
see All Projects
</Link>
</SidebarMenuButton>
<SidebarMenuSub>
<SidebarMenuSubItem>
<SidebarMenuSubButton asChild>
<Link href="/" >
<Plus />
Add Project
</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
<SidebarMenuSubItem>
<SidebarMenuSubButton asChild>
<Link href="/" >
<Plus />
Add Category
</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton>
<User2 /> admin <ChevronUp className="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Account</DropdownMenuItem>
<DropdownMenuItem>Setting</DropdownMenuItem>
<DropdownMenuItem>Sign out</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>
)
}
/src/components/NavBar.tsx
"use client"
import { LogOut, Moon, Settings, Sun, User } from "lucide-react";
import Link from "next/link";
import { Avatar, AvatarImage, AvatarFallback } from "./ui/avatar";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Button } from "./ui/button";
import { useTheme } from "next-themes";
import { SidebarTrigger, useSidebar } from "./ui/sidebar";
export default function NavBar() {
const { setTheme } = useTheme()
// const {toggleSidebar} = useSidebar()
return (
<div className="flex items-center justify-between p-4">
{/* LEFT */}
<SidebarTrigger />
{/* <Button variant="outline" onClick={toggleSidebar}>Custom Button</Button> */}
{/* RIGHT */}
<div className="flex items-center gap-4">
<Link href="/">Dashboard</Link>
{/* THEME MENU */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{/* USER MENU */}
<DropdownMenu>
<DropdownMenuTrigger>
<Avatar>
<AvatarImage src="https://ui.shadcn.com/avatars/04.png" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</DropdownMenuTrigger>
<DropdownMenuContent sideOffset={10}>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>
<User className="h-[1.2rem] w-[1.2rem] mr-2" />
Profile
</DropdownMenuItem>
<DropdownMenuItem>
<Settings className="h-[1.2rem] w-[1.2rem] mr-2" />
Settings
</DropdownMenuItem>
<DropdownMenuItem variant="destructive">
<LogOut className="h-[1.2rem] w-[1.2rem] mr-2" />
Logout
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
)
}
/src/providers/ThemeProvider.tsx
npm install next-themes
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({
children,
...props
}: React.ComponentProps<typeof NextThemesProvider>) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
其他配置
layout.tsx
import type { Metadata } from "next";
import "./globals.css";
import AppSideBar from "@/components/AppSidebar";
import NavBar from "@/components/NavBar";
import { ThemeProvider } from "@/components/providers/ThemeProvider";
import { SidebarProvider } from "@/components/ui/sidebar";
import { cookies } from "next/headers";
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const cookieStore = await cookies();
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true";
return (
<html lang="en" suppressHydrationWarning>
<body className={`antialiased flex`}>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<SidebarProvider defaultOpen={defaultOpen}>
<AppSideBar />
<main className="w-full">
<NavBar />
<div className="p-4">
{children}
</div>
</main>
</SidebarProvider>
</ThemeProvider>
</body>
</html>
);
}
page.tsx
import { Button } from "@/components/ui/button";
import { CirclePlus } from "lucide-react";
const HomePage = () => {
return (
<>
<div className="">
HomePage
</div>
</>
);
}
export default HomePage;