mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 09:51:40 +00:00
feat(v4): move dashboard
This commit is contained in:
@@ -1,183 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
IconCamera,
|
||||
IconChartBar,
|
||||
IconDashboard,
|
||||
IconDatabase,
|
||||
IconFileAi,
|
||||
IconFileDescription,
|
||||
IconFileWord,
|
||||
IconFolder,
|
||||
IconHelp,
|
||||
IconInnerShadowTop,
|
||||
IconListDetails,
|
||||
IconReport,
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
IconUsers,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
// import { NavClouds } from "@/app/(examples)/dashboard-02/components/nav-clouds"
|
||||
import { NavDocuments } from "@/app/(examples)/dashboard-02/components/nav-documents"
|
||||
import { NavMain } from "@/app/(examples)/dashboard-02/components/nav-main"
|
||||
import { NavSecondary } from "@/app/(examples)/dashboard-02/components/nav-secondary"
|
||||
import { NavUser } from "@/app/(examples)/dashboard-02/components/nav-user"
|
||||
|
||||
const data = {
|
||||
user: {
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
avatar: "/avatars/shadcn.jpg",
|
||||
},
|
||||
navMain: [
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "#",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
{
|
||||
title: "Lifecycle",
|
||||
url: "#",
|
||||
icon: IconListDetails,
|
||||
},
|
||||
{
|
||||
title: "Analytics",
|
||||
url: "#",
|
||||
icon: IconChartBar,
|
||||
},
|
||||
{
|
||||
title: "Projects",
|
||||
url: "#",
|
||||
icon: IconFolder,
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
url: "#",
|
||||
icon: IconUsers,
|
||||
},
|
||||
],
|
||||
navClouds: [
|
||||
{
|
||||
title: "Capture",
|
||||
icon: IconCamera,
|
||||
isActive: true,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Proposal",
|
||||
icon: IconFileDescription,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Prompts",
|
||||
icon: IconFileAi,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
navSecondary: [
|
||||
{
|
||||
title: "Settings",
|
||||
url: "#",
|
||||
icon: IconSettings,
|
||||
},
|
||||
{
|
||||
title: "Get Help",
|
||||
url: "#",
|
||||
icon: IconHelp,
|
||||
},
|
||||
{
|
||||
title: "Search",
|
||||
url: "#",
|
||||
icon: IconSearch,
|
||||
},
|
||||
],
|
||||
documents: [
|
||||
{
|
||||
name: "Data Library",
|
||||
url: "#",
|
||||
icon: IconDatabase,
|
||||
},
|
||||
{
|
||||
name: "Reports",
|
||||
url: "#",
|
||||
icon: IconReport,
|
||||
},
|
||||
{
|
||||
name: "Word Assistant",
|
||||
url: "#",
|
||||
icon: IconFileWord,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
<Sidebar collapsible="offcanvas" {...props}>
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
className="data-[slot=sidebar-menu-button]:!p-1.5"
|
||||
>
|
||||
<a href="#">
|
||||
<IconInnerShadowTop className="!size-5" />
|
||||
<span className="text-base font-semibold">Acme Inc.</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<NavMain items={data.navMain} />
|
||||
{/* <NavClouds items={data.navClouds} /> */}
|
||||
<NavDocuments items={data.documents} />
|
||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<NavUser user={data.user} />
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { IconCirclePlusFilled, IconMail, type Icon } from "@tabler/icons-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavMain({
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon?: Icon
|
||||
}[]
|
||||
}) {
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupContent className="flex flex-col gap-2">
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem className="flex items-center gap-2">
|
||||
<SidebarMenuButton
|
||||
tooltip="Quick Create"
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground min-w-8 duration-200 ease-linear"
|
||||
>
|
||||
<IconCirclePlusFilled />
|
||||
<span>Quick Create</span>
|
||||
</SidebarMenuButton>
|
||||
<Button
|
||||
size="icon"
|
||||
className="size-8 group-data-[collapsible=icon]:opacity-0"
|
||||
variant="outline"
|
||||
>
|
||||
<IconMail />
|
||||
<span className="sr-only">Inbox</span>
|
||||
</Button>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuButton key={item.title} tooltip={item.title}>
|
||||
{item.icon && <item.icon />}
|
||||
<span>{item.title}</span>
|
||||
</SidebarMenuButton>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { IconBrightness, type Icon } from "@tabler/icons-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { Skeleton } from "@/registry/new-york-v4/ui/skeleton"
|
||||
import { Switch } from "@/registry/new-york-v4/ui/switch"
|
||||
|
||||
export function NavSecondary({
|
||||
items,
|
||||
...props
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: Icon
|
||||
}[]
|
||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||
const { resolvedTheme, setTheme } = useTheme()
|
||||
const [mounted, setMounted] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
<SidebarMenu className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarMenuButton asChild>
|
||||
<label>
|
||||
<IconBrightness />
|
||||
<span>Dark Mode</span>
|
||||
{mounted ? (
|
||||
<Switch
|
||||
className="ml-auto"
|
||||
checked={resolvedTheme !== "light"}
|
||||
onCheckedChange={() =>
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton className="ml-auto h-4 w-8 rounded-full" />
|
||||
)}
|
||||
</label>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenu>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
IconCreditCard,
|
||||
IconDotsVertical,
|
||||
IconLogout,
|
||||
IconNotification,
|
||||
IconUserCircle,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavUser({
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
name: string
|
||||
email: string
|
||||
avatar: string
|
||||
}
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="h-8 w-8 rounded-lg grayscale">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
<IconDotsVertical className="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<IconUserCircle />
|
||||
Account
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<IconCreditCard />
|
||||
Billing
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<IconNotification />
|
||||
Notifications
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<IconLogout />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import { SidebarTrigger } from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { ModeToggle } from "@/app/(examples)/dashboard-02/components/mode-toggle"
|
||||
import { ThemeSelector } from "@/app/(examples)/dashboard-02/components/theme-selector"
|
||||
|
||||
export function SiteHeader() {
|
||||
return (
|
||||
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
|
||||
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="mx-2 data-[orientation=vertical]:h-4"
|
||||
/>
|
||||
<h1 className="text-base font-medium">Documents</h1>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Button variant="ghost" asChild size="sm" className="hidden sm:flex">
|
||||
<a
|
||||
href="https://github.com/shadcn-ui/ui/tree/main/apps/v4/app/(examples)/dashboard-02"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="dark:text-foreground"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</Button>
|
||||
<ThemeSelector />
|
||||
<ModeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { cookies } from "next/headers"
|
||||
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { AppSidebar } from "@/app/(examples)/dashboard-02/components/app-sidebar"
|
||||
import { SiteHeader } from "@/app/(examples)/dashboard-02/components/site-header"
|
||||
|
||||
import "./theme.css"
|
||||
|
||||
export default async function DashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const cookieStore = await cookies()
|
||||
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
||||
|
||||
return (
|
||||
<SidebarProvider
|
||||
defaultOpen={defaultOpen}
|
||||
style={
|
||||
{
|
||||
"--sidebar-width": "calc(var(--spacing) * 72)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<AppSidebar variant="inset" />
|
||||
<SidebarInset>
|
||||
<SiteHeader />
|
||||
<div className="flex flex-1 flex-col">{children}</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import { Metadata } from "next"
|
||||
import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react"
|
||||
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import { ChartAreaInteractive } from "@/app/(examples)/dashboard-02/components/chart-area-interactive"
|
||||
import { DataTable } from "@/app/(examples)/dashboard-02/components/data-table"
|
||||
|
||||
import data from "./data.json"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Dashboard",
|
||||
description: "A dashboard with tabs, table and sidebar.",
|
||||
}
|
||||
|
||||
export default async function Dashboard02() {
|
||||
return (
|
||||
<div className="@container/main flex flex-1 flex-col gap-2">
|
||||
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
||||
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>Total Revenue</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
$1,250.00
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<IconTrendingUp />
|
||||
+12.5%
|
||||
</Badge>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-2.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Trending up this month <IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>New Customers</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
1,234
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<IconTrendingDown />
|
||||
-20%
|
||||
</Badge>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Down 20% this period <IconTrendingDown className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Acquisition needs attention
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>Active Accounts</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
45,678
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<IconTrendingUp />
|
||||
+12.5%
|
||||
</Badge>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Strong user retention <IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Engagement exceed targets
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>Growth Rate</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
4.5%
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<IconTrendingUp />
|
||||
+4.5%
|
||||
</Badge>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Steady performance increase{" "}
|
||||
<IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Meets growth projections
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="px-4 lg:px-6">
|
||||
<ChartAreaInteractive />
|
||||
</div>
|
||||
<DataTable data={data} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
ChartLineIcon,
|
||||
FileIcon,
|
||||
HomeIcon,
|
||||
LifeBuoy,
|
||||
Send,
|
||||
Settings2Icon,
|
||||
ShoppingBagIcon,
|
||||
ShoppingCartIcon,
|
||||
UserIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Sidebar, SidebarContent } from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { NavMain } from "@/app/(examples)/dashboard-03/components/nav-main"
|
||||
import { NavSecondary } from "@/app/(examples)/dashboard-03/components/nav-secondary"
|
||||
|
||||
const data = {
|
||||
navMain: [
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "/dashboard",
|
||||
icon: HomeIcon,
|
||||
},
|
||||
{
|
||||
title: "Analytics",
|
||||
url: "/dashboard/analytics",
|
||||
icon: ChartLineIcon,
|
||||
},
|
||||
{
|
||||
title: "Orders",
|
||||
url: "/dashboard/orders",
|
||||
icon: ShoppingBagIcon,
|
||||
},
|
||||
{
|
||||
title: "Products",
|
||||
url: "/dashboard/products",
|
||||
icon: ShoppingCartIcon,
|
||||
},
|
||||
{
|
||||
title: "Invoices",
|
||||
url: "/dashboard/invoices",
|
||||
icon: FileIcon,
|
||||
},
|
||||
{
|
||||
title: "Customers",
|
||||
url: "/dashboard/customers",
|
||||
icon: UserIcon,
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
url: "/dashboard/settings",
|
||||
icon: Settings2Icon,
|
||||
},
|
||||
],
|
||||
navSecondary: [
|
||||
{
|
||||
title: "Support",
|
||||
url: "#",
|
||||
icon: LifeBuoy,
|
||||
},
|
||||
{
|
||||
title: "Feedback",
|
||||
url: "#",
|
||||
icon: Send,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
<Sidebar
|
||||
className="top-(--header-height) h-[calc(100svh-var(--header-height))]!"
|
||||
{...props}
|
||||
>
|
||||
<SidebarContent>
|
||||
<NavMain items={data.navMain} />
|
||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||
</SidebarContent>
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { IconBrightness } from "@tabler/icons-react"
|
||||
import { MoonIcon, SunIcon } from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
@@ -20,7 +20,8 @@ export function ModeToggle() {
|
||||
className="group/toggle size-8"
|
||||
onClick={toggleTheme}
|
||||
>
|
||||
<IconBrightness />
|
||||
<SunIcon className="hidden [html.dark_&]:block" />
|
||||
<MoonIcon className="hidden [html.light_&]:block" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
)
|
||||
91
apps/v4/app/(examples)/dashboard-03/components/nav-main.tsx
Normal file
91
apps/v4/app/(examples)/dashboard-03/components/nav-main.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
"use client"
|
||||
|
||||
import { usePathname } from "next/navigation"
|
||||
import { ChevronRight, type LucideIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/registry/new-york-v4/ui/collapsible"
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavMain({
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
isActive?: boolean
|
||||
items?: {
|
||||
title: string
|
||||
url: string
|
||||
}[]
|
||||
disabled?: boolean
|
||||
}[]
|
||||
}) {
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Dashboard</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<Collapsible key={item.title} asChild defaultOpen={item.isActive}>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
tooltip={item.title}
|
||||
isActive={pathname === item.url}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
<a
|
||||
href={item.disabled ? "#" : item.url}
|
||||
data-disabled={item.disabled}
|
||||
className="data-[disabled=true]:opacity-50"
|
||||
>
|
||||
<item.icon className="text-muted-foreground" />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
{item.items?.length ? (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
||||
<ChevronRight />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{item.items?.map((subItem) => (
|
||||
<SidebarMenuSubItem key={subItem.title}>
|
||||
<SidebarMenuSubButton asChild>
|
||||
<a href={subItem.url}>
|
||||
<span>{subItem.title}</span>
|
||||
</a>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
) : null}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import * as React from "react"
|
||||
import { type LucideIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavSecondary({
|
||||
items,
|
||||
...props
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
}[]
|
||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild size="sm">
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
90
apps/v4/app/(examples)/dashboard-03/components/nav-user.tsx
Normal file
90
apps/v4/app/(examples)/dashboard-03/components/nav-user.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
"use client"
|
||||
|
||||
import { BadgeCheck, Bell, CreditCard, LogOut, Sparkles } from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
|
||||
export function NavUser({
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
name: string
|
||||
email: string
|
||||
avatar: string
|
||||
}
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Avatar className="size-8 rounded-md">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
side="bottom"
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<Sparkles />
|
||||
Upgrade to Pro
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<BadgeCheck />
|
||||
Account
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CreditCard />
|
||||
Billing
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Bell />
|
||||
Notifications
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<LogOut />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
103
apps/v4/app/(examples)/dashboard-03/components/site-header.tsx
Normal file
103
apps/v4/app/(examples)/dashboard-03/components/site-header.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
"use client"
|
||||
|
||||
import { Fragment, useMemo } from "react"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { SidebarIcon } from "lucide-react"
|
||||
|
||||
import { ThemeSelector } from "@/components/theme-selector"
|
||||
import { SearchForm } from "@/registry/new-york-v4/blocks/sidebar-16/components/search-form"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/registry/new-york-v4/ui/breadcrumb"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import { useSidebar } from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { ModeToggle } from "@/app/(examples)/dashboard-03/components/mode-toggle"
|
||||
import { NavUser } from "@/app/(examples)/dashboard-03/components/nav-user"
|
||||
|
||||
export function SiteHeader() {
|
||||
const { toggleSidebar } = useSidebar()
|
||||
const pathname = usePathname()
|
||||
|
||||
// Faux breadcrumbs for demo.
|
||||
const breadcrumbs = useMemo(() => {
|
||||
return pathname
|
||||
.split("/")
|
||||
.filter((path) => path !== "")
|
||||
.map((path, index, array) => ({
|
||||
label: path,
|
||||
href: `/${array.slice(0, index + 1).join("/")}`,
|
||||
}))
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<header
|
||||
data-slot="site-header"
|
||||
className="bg-background sticky top-0 z-50 flex w-full items-center border-b"
|
||||
>
|
||||
<div className="flex h-(--header-height) w-full items-center gap-2 px-2 pr-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={toggleSidebar}
|
||||
className="gap-2.5 has-[>svg]:px-2"
|
||||
>
|
||||
<SidebarIcon />
|
||||
<span className="truncate font-medium">Acme Inc</span>
|
||||
</Button>
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="mr-2 data-[orientation=vertical]:h-4"
|
||||
/>
|
||||
<Breadcrumb className="hidden sm:block">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/" className="capitalize">
|
||||
Home
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
{breadcrumbs.map((breadcrumb, index) =>
|
||||
index === breadcrumbs.length - 1 ? (
|
||||
<BreadcrumbItem key={index}>
|
||||
<BreadcrumbPage className="capitalize">
|
||||
{breadcrumb.label}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
) : (
|
||||
<Fragment key={index}>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink
|
||||
href={breadcrumb.href}
|
||||
className="capitalize"
|
||||
>
|
||||
{breadcrumb.label}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
</Fragment>
|
||||
)
|
||||
)}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<SearchForm className="w-fullsm:w-auto" />
|
||||
<ThemeSelector />
|
||||
<ModeToggle />
|
||||
<NavUser
|
||||
user={{
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
avatar: "/avatars/shadcn.jpg",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
31
apps/v4/app/(examples)/dashboard-03/layout.tsx
Normal file
31
apps/v4/app/(examples)/dashboard-03/layout.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { cookies } from "next/headers"
|
||||
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { AppSidebar } from "@/app/(examples)/dashboard-03/components/app-sidebar"
|
||||
import { SiteHeader } from "@/app/(examples)/dashboard-03/components/site-header"
|
||||
|
||||
import "../../themes.css"
|
||||
|
||||
export default async function DashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const cookieStore = await cookies()
|
||||
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
||||
|
||||
return (
|
||||
<main className="[--header-height:calc(theme(spacing.14))]">
|
||||
<SidebarProvider defaultOpen={defaultOpen} className="flex flex-col">
|
||||
<SiteHeader />
|
||||
<div className="flex flex-1">
|
||||
<AppSidebar />
|
||||
<SidebarInset>{children}</SidebarInset>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
206
apps/v4/app/(examples)/dashboard-03/page.tsx
Normal file
206
apps/v4/app/(examples)/dashboard-03/page.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
import { Metadata } from "next"
|
||||
import {
|
||||
DownloadIcon,
|
||||
FilterIcon,
|
||||
TrendingDownIcon,
|
||||
TrendingUpIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/registry/new-york-v4/ui/tabs"
|
||||
import { AnalyticsDatePicker } from "@/app/(examples)/dashboard-03/components/analytics-date-picker"
|
||||
import { ChartRevenue } from "@/app/(examples)/dashboard-03/components/chart-revenue"
|
||||
import { ChartVisitors } from "@/app/(examples)/dashboard-03/components/chart-visitors"
|
||||
import { ProductsTable } from "@/app/(examples)/dashboard-03/components/products-table"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Dashboard",
|
||||
description: "An example dashboard to test the new components.",
|
||||
}
|
||||
|
||||
// Load from database.
|
||||
const products = [
|
||||
{
|
||||
id: "1",
|
||||
name: "BJÖRKSNÄS Dining Table",
|
||||
price: 599.99,
|
||||
stock: 12,
|
||||
dateAdded: "2023-06-15",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "POÄNG Armchair",
|
||||
price: 249.99,
|
||||
stock: 28,
|
||||
dateAdded: "2023-07-22",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "MALM Bed Frame",
|
||||
price: 399.99,
|
||||
stock: 15,
|
||||
dateAdded: "2023-08-05",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "KALLAX Shelf Unit",
|
||||
price: 179.99,
|
||||
stock: 32,
|
||||
dateAdded: "2023-09-12",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "STOCKHOLM Rug",
|
||||
price: 299.99,
|
||||
stock: 8,
|
||||
dateAdded: "2023-10-18",
|
||||
status: "Low Stock",
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
name: "KIVIK Sofa",
|
||||
price: 899.99,
|
||||
stock: 6,
|
||||
dateAdded: "2023-11-02",
|
||||
status: "Low Stock",
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
name: "LISABO Coffee Table",
|
||||
price: 149.99,
|
||||
stock: 22,
|
||||
dateAdded: "2023-11-29",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
name: "HEMNES Bookcase",
|
||||
price: 249.99,
|
||||
stock: 17,
|
||||
dateAdded: "2023-12-10",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
name: "EKEDALEN Dining Chairs (Set of 2)",
|
||||
price: 199.99,
|
||||
stock: 14,
|
||||
dateAdded: "2024-01-05",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "10",
|
||||
name: "FRIHETEN Sleeper Sofa",
|
||||
price: 799.99,
|
||||
stock: 9,
|
||||
dateAdded: "2024-01-18",
|
||||
status: "Low Stock",
|
||||
},
|
||||
]
|
||||
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<div className="@container/page flex flex-1 flex-col gap-8 p-6">
|
||||
<Tabs defaultValue="overview" className="gap-6">
|
||||
<div
|
||||
data-slot="dashboard-header"
|
||||
className="flex items-center justify-between"
|
||||
>
|
||||
<TabsList className="w-full @3xl/page:w-fit">
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
||||
<TabsTrigger value="reports">Reports</TabsTrigger>
|
||||
<TabsTrigger value="exports" disabled>
|
||||
Exports
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="hidden items-center gap-2 @3xl/page:flex">
|
||||
<AnalyticsDatePicker />
|
||||
<Button variant="outline">
|
||||
<FilterIcon />
|
||||
Filter
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
<DownloadIcon />
|
||||
Export
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<TabsContent value="overview" className="flex flex-col gap-4">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Total Revenue</CardTitle>
|
||||
<CardDescription>$1,250.00 in the last 30 days</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
<Badge variant="outline">
|
||||
<TrendingUpIcon />
|
||||
+12.5%
|
||||
</Badge>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>New Customers</CardTitle>
|
||||
<CardDescription>-12 customers from last month</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
<Badge variant="outline">
|
||||
<TrendingDownIcon />
|
||||
-20%
|
||||
</Badge>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Active Accounts</CardTitle>
|
||||
<CardDescription>+2,345 users from last month</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
<Badge variant="outline">
|
||||
<TrendingUpIcon />
|
||||
+12.5%
|
||||
</Badge>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Growth Rate</CardTitle>
|
||||
<CardDescription>+12.5% increase per month</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
<Badge variant="outline">
|
||||
<TrendingUpIcon />
|
||||
+4.5%
|
||||
</Badge>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 @4xl/page:grid-cols-[2fr_1fr]">
|
||||
<ChartRevenue />
|
||||
<ChartVisitors />
|
||||
</div>
|
||||
<ProductsTable products={products} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,83 +2,182 @@
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
ChartLineIcon,
|
||||
FileIcon,
|
||||
HomeIcon,
|
||||
LifeBuoy,
|
||||
Send,
|
||||
Settings2Icon,
|
||||
ShoppingBagIcon,
|
||||
ShoppingCartIcon,
|
||||
UserIcon,
|
||||
} from "lucide-react"
|
||||
IconCamera,
|
||||
IconChartBar,
|
||||
IconDashboard,
|
||||
IconDatabase,
|
||||
IconFileAi,
|
||||
IconFileDescription,
|
||||
IconFileWord,
|
||||
IconFolder,
|
||||
IconHelp,
|
||||
IconInnerShadowTop,
|
||||
IconListDetails,
|
||||
IconReport,
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
IconUsers,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import { Sidebar, SidebarContent } from "@/registry/new-york-v4/ui/sidebar"
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
// import { NavClouds } from "@/app/(examples)/dashboard-02/components/nav-clouds"
|
||||
import { NavDocuments } from "@/app/(examples)/dashboard/components/nav-documents"
|
||||
import { NavMain } from "@/app/(examples)/dashboard/components/nav-main"
|
||||
import { NavSecondary } from "@/app/(examples)/dashboard/components/nav-secondary"
|
||||
import { NavUser } from "@/app/(examples)/dashboard/components/nav-user"
|
||||
|
||||
const data = {
|
||||
user: {
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
avatar: "/avatars/shadcn.jpg",
|
||||
},
|
||||
navMain: [
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "/dashboard",
|
||||
icon: HomeIcon,
|
||||
url: "#",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
{
|
||||
title: "Lifecycle",
|
||||
url: "#",
|
||||
icon: IconListDetails,
|
||||
},
|
||||
{
|
||||
title: "Analytics",
|
||||
url: "/dashboard/analytics",
|
||||
icon: ChartLineIcon,
|
||||
url: "#",
|
||||
icon: IconChartBar,
|
||||
},
|
||||
{
|
||||
title: "Orders",
|
||||
url: "/dashboard/orders",
|
||||
icon: ShoppingBagIcon,
|
||||
title: "Projects",
|
||||
url: "#",
|
||||
icon: IconFolder,
|
||||
},
|
||||
{
|
||||
title: "Products",
|
||||
url: "/dashboard/products",
|
||||
icon: ShoppingCartIcon,
|
||||
title: "Team",
|
||||
url: "#",
|
||||
icon: IconUsers,
|
||||
},
|
||||
],
|
||||
navClouds: [
|
||||
{
|
||||
title: "Capture",
|
||||
icon: IconCamera,
|
||||
isActive: true,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Invoices",
|
||||
url: "/dashboard/invoices",
|
||||
icon: FileIcon,
|
||||
title: "Proposal",
|
||||
icon: IconFileDescription,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Customers",
|
||||
url: "/dashboard/customers",
|
||||
icon: UserIcon,
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
url: "/dashboard/settings",
|
||||
icon: Settings2Icon,
|
||||
title: "Prompts",
|
||||
icon: IconFileAi,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
navSecondary: [
|
||||
{
|
||||
title: "Support",
|
||||
title: "Settings",
|
||||
url: "#",
|
||||
icon: LifeBuoy,
|
||||
icon: IconSettings,
|
||||
},
|
||||
{
|
||||
title: "Feedback",
|
||||
title: "Get Help",
|
||||
url: "#",
|
||||
icon: Send,
|
||||
icon: IconHelp,
|
||||
},
|
||||
{
|
||||
title: "Search",
|
||||
url: "#",
|
||||
icon: IconSearch,
|
||||
},
|
||||
],
|
||||
documents: [
|
||||
{
|
||||
name: "Data Library",
|
||||
url: "#",
|
||||
icon: IconDatabase,
|
||||
},
|
||||
{
|
||||
name: "Reports",
|
||||
url: "#",
|
||||
icon: IconReport,
|
||||
},
|
||||
{
|
||||
name: "Word Assistant",
|
||||
url: "#",
|
||||
icon: IconFileWord,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
<Sidebar
|
||||
className="top-(--header-height) h-[calc(100svh-var(--header-height))]!"
|
||||
{...props}
|
||||
>
|
||||
<Sidebar collapsible="offcanvas" {...props}>
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
className="data-[slot=sidebar-menu-button]:!p-1.5"
|
||||
>
|
||||
<a href="#">
|
||||
<IconInnerShadowTop className="!size-5" />
|
||||
<span className="text-base font-semibold">Acme Inc.</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<NavMain items={data.navMain} />
|
||||
{/* <NavClouds items={data.navClouds} /> */}
|
||||
<NavDocuments items={data.documents} />
|
||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<NavUser user={data.user} />
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { MoonIcon, SunIcon } from "lucide-react"
|
||||
import { IconBrightness } from "@tabler/icons-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
@@ -20,8 +20,7 @@ export function ModeToggle() {
|
||||
className="group/toggle size-8"
|
||||
onClick={toggleTheme}
|
||||
>
|
||||
<SunIcon className="hidden [html.dark_&]:block" />
|
||||
<MoonIcon className="hidden [html.light_&]:block" />
|
||||
<IconBrightness />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
)
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
"use client"
|
||||
|
||||
import { usePathname } from "next/navigation"
|
||||
import { ChevronRight, type LucideIcon } from "lucide-react"
|
||||
import { IconCirclePlusFilled, IconMail, type Icon } from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/registry/new-york-v4/ui/collapsible"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavMain({
|
||||
@@ -26,66 +17,40 @@ export function NavMain({
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
isActive?: boolean
|
||||
items?: {
|
||||
title: string
|
||||
url: string
|
||||
}[]
|
||||
disabled?: boolean
|
||||
icon?: Icon
|
||||
}[]
|
||||
}) {
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Dashboard</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<Collapsible key={item.title} asChild defaultOpen={item.isActive}>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
tooltip={item.title}
|
||||
isActive={pathname === item.url}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
<a
|
||||
href={item.disabled ? "#" : item.url}
|
||||
data-disabled={item.disabled}
|
||||
className="data-[disabled=true]:opacity-50"
|
||||
>
|
||||
<item.icon className="text-muted-foreground" />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
{item.items?.length ? (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
||||
<ChevronRight />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{item.items?.map((subItem) => (
|
||||
<SidebarMenuSubItem key={subItem.title}>
|
||||
<SidebarMenuSubButton asChild>
|
||||
<a href={subItem.url}>
|
||||
<span>{subItem.title}</span>
|
||||
</a>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
) : null}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
<SidebarGroupContent className="flex flex-col gap-2">
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem className="flex items-center gap-2">
|
||||
<SidebarMenuButton
|
||||
tooltip="Quick Create"
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground min-w-8 duration-200 ease-linear"
|
||||
>
|
||||
<IconCirclePlusFilled />
|
||||
<span>Quick Create</span>
|
||||
</SidebarMenuButton>
|
||||
<Button
|
||||
size="icon"
|
||||
className="size-8 group-data-[collapsible=icon]:opacity-0"
|
||||
variant="outline"
|
||||
>
|
||||
<IconMail />
|
||||
<span className="sr-only">Inbox</span>
|
||||
</Button>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuButton key={item.title} tooltip={item.title}>
|
||||
{item.icon && <item.icon />}
|
||||
<span>{item.title}</span>
|
||||
</SidebarMenuButton>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { type LucideIcon } from "lucide-react"
|
||||
import { IconBrightness, type Icon } from "@tabler/icons-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
@@ -8,6 +11,8 @@ import {
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { Skeleton } from "@/registry/new-york-v4/ui/skeleton"
|
||||
import { Switch } from "@/registry/new-york-v4/ui/switch"
|
||||
|
||||
export function NavSecondary({
|
||||
items,
|
||||
@@ -16,16 +21,23 @@ export function NavSecondary({
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
icon: Icon
|
||||
}[]
|
||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||
const { resolvedTheme, setTheme } = useTheme()
|
||||
const [mounted, setMounted] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild size="sm">
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
@@ -33,6 +45,25 @@ export function NavSecondary({
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
<SidebarMenu className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarMenuButton asChild>
|
||||
<label>
|
||||
<IconBrightness />
|
||||
<span>Dark Mode</span>
|
||||
{mounted ? (
|
||||
<Switch
|
||||
className="ml-auto"
|
||||
checked={resolvedTheme !== "light"}
|
||||
onCheckedChange={() =>
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton className="ml-auto h-4 w-8 rounded-full" />
|
||||
)}
|
||||
</label>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenu>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
"use client"
|
||||
|
||||
import { BadgeCheck, Bell, CreditCard, LogOut, Sparkles } from "lucide-react"
|
||||
import {
|
||||
IconCreditCard,
|
||||
IconDotsVertical,
|
||||
IconLogout,
|
||||
IconNotification,
|
||||
IconUserCircle,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -17,6 +22,12 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavUser({
|
||||
user,
|
||||
@@ -27,64 +38,73 @@ export function NavUser({
|
||||
avatar: string
|
||||
}
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Avatar className="size-8 rounded-md">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
side="bottom"
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<Sparkles />
|
||||
Upgrade to Pro
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<BadgeCheck />
|
||||
Account
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CreditCard />
|
||||
Billing
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Bell />
|
||||
Notifications
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<LogOut />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="h-8 w-8 rounded-lg grayscale">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
<IconDotsVertical className="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<IconUserCircle />
|
||||
Account
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<IconCreditCard />
|
||||
Billing
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<IconNotification />
|
||||
Notifications
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<IconLogout />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,101 +1,32 @@
|
||||
"use client"
|
||||
|
||||
import { Fragment, useMemo } from "react"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { SidebarIcon } from "lucide-react"
|
||||
|
||||
import { ThemeSelector } from "@/components/theme-selector"
|
||||
import { SearchForm } from "@/registry/new-york-v4/blocks/sidebar-16/components/search-form"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/registry/new-york-v4/ui/breadcrumb"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import { useSidebar } from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { SidebarTrigger } from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { ModeToggle } from "@/app/(examples)/dashboard/components/mode-toggle"
|
||||
import { NavUser } from "@/app/(examples)/dashboard/components/nav-user"
|
||||
import { ThemeSelector } from "@/app/(examples)/dashboard/components/theme-selector"
|
||||
|
||||
export function SiteHeader() {
|
||||
const { toggleSidebar } = useSidebar()
|
||||
const pathname = usePathname()
|
||||
|
||||
// Faux breadcrumbs for demo.
|
||||
const breadcrumbs = useMemo(() => {
|
||||
return pathname
|
||||
.split("/")
|
||||
.filter((path) => path !== "")
|
||||
.map((path, index, array) => ({
|
||||
label: path,
|
||||
href: `/${array.slice(0, index + 1).join("/")}`,
|
||||
}))
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<header
|
||||
data-slot="site-header"
|
||||
className="bg-background sticky top-0 z-50 flex w-full items-center border-b"
|
||||
>
|
||||
<div className="flex h-(--header-height) w-full items-center gap-2 px-2 pr-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={toggleSidebar}
|
||||
className="gap-2.5 has-[>svg]:px-2"
|
||||
>
|
||||
<SidebarIcon />
|
||||
<span className="truncate font-medium">Acme Inc</span>
|
||||
</Button>
|
||||
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
|
||||
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="mr-2 data-[orientation=vertical]:h-4"
|
||||
className="mx-2 data-[orientation=vertical]:h-4"
|
||||
/>
|
||||
<Breadcrumb className="hidden sm:block">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/" className="capitalize">
|
||||
Home
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
{breadcrumbs.map((breadcrumb, index) =>
|
||||
index === breadcrumbs.length - 1 ? (
|
||||
<BreadcrumbItem key={index}>
|
||||
<BreadcrumbPage className="capitalize">
|
||||
{breadcrumb.label}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
) : (
|
||||
<Fragment key={index}>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink
|
||||
href={breadcrumb.href}
|
||||
className="capitalize"
|
||||
>
|
||||
{breadcrumb.label}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
</Fragment>
|
||||
)
|
||||
)}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
<h1 className="text-base font-medium">Documents</h1>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<SearchForm className="w-fullsm:w-auto" />
|
||||
<Button variant="ghost" asChild size="sm" className="hidden sm:flex">
|
||||
<a
|
||||
href="https://github.com/shadcn-ui/ui/tree/main/apps/v4/app/(examples)/dashboard"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="dark:text-foreground"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</Button>
|
||||
<ThemeSelector />
|
||||
<ModeToggle />
|
||||
<NavUser
|
||||
user={{
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
avatar: "/avatars/shadcn.jpg",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { AppSidebar } from "@/app/(examples)/dashboard/components/app-sidebar"
|
||||
import { SiteHeader } from "@/app/(examples)/dashboard/components/site-header"
|
||||
|
||||
import "../../themes.css"
|
||||
import "./theme.css"
|
||||
|
||||
export default async function DashboardLayout({
|
||||
children,
|
||||
@@ -18,14 +18,19 @@ export default async function DashboardLayout({
|
||||
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
||||
|
||||
return (
|
||||
<main className="[--header-height:calc(theme(spacing.14))]">
|
||||
<SidebarProvider defaultOpen={defaultOpen} className="flex flex-col">
|
||||
<SidebarProvider
|
||||
defaultOpen={defaultOpen}
|
||||
style={
|
||||
{
|
||||
"--sidebar-width": "calc(var(--spacing) * 72)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<AppSidebar variant="inset" />
|
||||
<SidebarInset>
|
||||
<SiteHeader />
|
||||
<div className="flex flex-1">
|
||||
<AppSidebar />
|
||||
<SidebarInset>{children}</SidebarInset>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
</main>
|
||||
<div className="flex flex-1 flex-col">{children}</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,206 +1,125 @@
|
||||
import { Metadata } from "next"
|
||||
import {
|
||||
DownloadIcon,
|
||||
FilterIcon,
|
||||
TrendingDownIcon,
|
||||
TrendingUpIcon,
|
||||
} from "lucide-react"
|
||||
import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react"
|
||||
|
||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/registry/new-york-v4/ui/tabs"
|
||||
import { AnalyticsDatePicker } from "@/app/(examples)/dashboard/components/analytics-date-picker"
|
||||
import { ChartRevenue } from "@/app/(examples)/dashboard/components/chart-revenue"
|
||||
import { ChartVisitors } from "@/app/(examples)/dashboard/components/chart-visitors"
|
||||
import { ProductsTable } from "@/app/(examples)/dashboard/components/products-table"
|
||||
import { ChartAreaInteractive } from "@/app/(examples)/dashboard/components/chart-area-interactive"
|
||||
import { DataTable } from "@/app/(examples)/dashboard/components/data-table"
|
||||
|
||||
import data from "./data.json"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Dashboard",
|
||||
description: "An example dashboard to test the new components.",
|
||||
description: "A dashboard with tabs, table and sidebar.",
|
||||
}
|
||||
|
||||
// Load from database.
|
||||
const products = [
|
||||
{
|
||||
id: "1",
|
||||
name: "BJÖRKSNÄS Dining Table",
|
||||
price: 599.99,
|
||||
stock: 12,
|
||||
dateAdded: "2023-06-15",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "POÄNG Armchair",
|
||||
price: 249.99,
|
||||
stock: 28,
|
||||
dateAdded: "2023-07-22",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "MALM Bed Frame",
|
||||
price: 399.99,
|
||||
stock: 15,
|
||||
dateAdded: "2023-08-05",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "KALLAX Shelf Unit",
|
||||
price: 179.99,
|
||||
stock: 32,
|
||||
dateAdded: "2023-09-12",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "STOCKHOLM Rug",
|
||||
price: 299.99,
|
||||
stock: 8,
|
||||
dateAdded: "2023-10-18",
|
||||
status: "Low Stock",
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
name: "KIVIK Sofa",
|
||||
price: 899.99,
|
||||
stock: 6,
|
||||
dateAdded: "2023-11-02",
|
||||
status: "Low Stock",
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
name: "LISABO Coffee Table",
|
||||
price: 149.99,
|
||||
stock: 22,
|
||||
dateAdded: "2023-11-29",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
name: "HEMNES Bookcase",
|
||||
price: 249.99,
|
||||
stock: 17,
|
||||
dateAdded: "2023-12-10",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
name: "EKEDALEN Dining Chairs (Set of 2)",
|
||||
price: 199.99,
|
||||
stock: 14,
|
||||
dateAdded: "2024-01-05",
|
||||
status: "In Stock",
|
||||
},
|
||||
{
|
||||
id: "10",
|
||||
name: "FRIHETEN Sleeper Sofa",
|
||||
price: 799.99,
|
||||
stock: 9,
|
||||
dateAdded: "2024-01-18",
|
||||
status: "Low Stock",
|
||||
},
|
||||
]
|
||||
|
||||
export default function DashboardPage() {
|
||||
export default async function Dashboard02() {
|
||||
return (
|
||||
<div className="@container/page flex flex-1 flex-col gap-8 p-6">
|
||||
<Tabs defaultValue="overview" className="gap-6">
|
||||
<div
|
||||
data-slot="dashboard-header"
|
||||
className="flex items-center justify-between"
|
||||
>
|
||||
<TabsList className="w-full @3xl/page:w-fit">
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
||||
<TabsTrigger value="reports">Reports</TabsTrigger>
|
||||
<TabsTrigger value="exports" disabled>
|
||||
Exports
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="hidden items-center gap-2 @3xl/page:flex">
|
||||
<AnalyticsDatePicker />
|
||||
<Button variant="outline">
|
||||
<FilterIcon />
|
||||
Filter
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
<DownloadIcon />
|
||||
Export
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<TabsContent value="overview" className="flex flex-col gap-4">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Total Revenue</CardTitle>
|
||||
<CardDescription>$1,250.00 in the last 30 days</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
<div className="@container/main flex flex-1 flex-col gap-2">
|
||||
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
||||
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>Total Revenue</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
$1,250.00
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<TrendingUpIcon />
|
||||
<IconTrendingUp />
|
||||
+12.5%
|
||||
</Badge>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>New Customers</CardTitle>
|
||||
<CardDescription>-12 customers from last month</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-2.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Trending up this month <IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>New Customers</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
1,234
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<TrendingDownIcon />
|
||||
<IconTrendingDown />
|
||||
-20%
|
||||
</Badge>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Active Accounts</CardTitle>
|
||||
<CardDescription>+2,345 users from last month</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Down 20% this period <IconTrendingDown className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Acquisition needs attention
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>Active Accounts</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
45,678
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<TrendingUpIcon />
|
||||
<IconTrendingUp />
|
||||
+12.5%
|
||||
</Badge>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Growth Rate</CardTitle>
|
||||
<CardDescription>+12.5% increase per month</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Strong user retention <IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Engagement exceed targets
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card className="@container/card">
|
||||
<CardHeader>
|
||||
<CardDescription>Growth Rate</CardDescription>
|
||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
4.5%
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="outline">
|
||||
<TrendingUpIcon />
|
||||
<IconTrendingUp />
|
||||
+4.5%
|
||||
</Badge>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 @4xl/page:grid-cols-[2fr_1fr]">
|
||||
<ChartRevenue />
|
||||
<ChartVisitors />
|
||||
</div>
|
||||
<ProductsTable products={products} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||
Steady performance increase{" "}
|
||||
<IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
Meets growth projections
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="px-4 lg:px-6">
|
||||
<ChartAreaInteractive />
|
||||
</div>
|
||||
<DataTable data={data} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export const metadata: Metadata = {
|
||||
siteName: siteConfig.name,
|
||||
images: [
|
||||
{
|
||||
url: siteConfig.ogImage,
|
||||
url: "https://v4.shadcn.com/opengraph-image.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: siteConfig.name,
|
||||
@@ -57,7 +57,7 @@ export const metadata: Metadata = {
|
||||
card: "summary_large_image",
|
||||
title: siteConfig.name,
|
||||
description: siteConfig.description,
|
||||
images: [siteConfig.ogImage],
|
||||
images: ["https://v4.shadcn.com/opengraph-image.png"],
|
||||
creator: "@shadcn",
|
||||
},
|
||||
icons: {
|
||||
|
||||
BIN
apps/v4/app/opengraph-image.png
Normal file
BIN
apps/v4/app/opengraph-image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
BIN
apps/v4/app/twitter-image.png
Normal file
BIN
apps/v4/app/twitter-image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
BIN
apps/v4/public/opengraph-image.png
Normal file
BIN
apps/v4/public/opengraph-image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
BIN
apps/v4/public/twitter-image.png
Normal file
BIN
apps/v4/public/twitter-image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
Reference in New Issue
Block a user