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"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { IconBrightness } from "@tabler/icons-react"
|
import { MoonIcon, SunIcon } from "lucide-react"
|
||||||
import { useTheme } from "next-themes"
|
import { useTheme } from "next-themes"
|
||||||
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
@@ -20,7 +20,8 @@ export function ModeToggle() {
|
|||||||
className="group/toggle size-8"
|
className="group/toggle size-8"
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
>
|
>
|
||||||
<IconBrightness />
|
<SunIcon className="hidden [html.dark_&]:block" />
|
||||||
|
<MoonIcon className="hidden [html.light_&]:block" />
|
||||||
<span className="sr-only">Toggle theme</span>
|
<span className="sr-only">Toggle theme</span>
|
||||||
</Button>
|
</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 * as React from "react"
|
||||||
import {
|
import {
|
||||||
ChartLineIcon,
|
IconCamera,
|
||||||
FileIcon,
|
IconChartBar,
|
||||||
HomeIcon,
|
IconDashboard,
|
||||||
LifeBuoy,
|
IconDatabase,
|
||||||
Send,
|
IconFileAi,
|
||||||
Settings2Icon,
|
IconFileDescription,
|
||||||
ShoppingBagIcon,
|
IconFileWord,
|
||||||
ShoppingCartIcon,
|
IconFolder,
|
||||||
UserIcon,
|
IconHelp,
|
||||||
} from "lucide-react"
|
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 { NavMain } from "@/app/(examples)/dashboard/components/nav-main"
|
||||||
import { NavSecondary } from "@/app/(examples)/dashboard/components/nav-secondary"
|
import { NavSecondary } from "@/app/(examples)/dashboard/components/nav-secondary"
|
||||||
|
import { NavUser } from "@/app/(examples)/dashboard/components/nav-user"
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
|
user: {
|
||||||
|
name: "shadcn",
|
||||||
|
email: "m@example.com",
|
||||||
|
avatar: "/avatars/shadcn.jpg",
|
||||||
|
},
|
||||||
navMain: [
|
navMain: [
|
||||||
{
|
{
|
||||||
title: "Dashboard",
|
title: "Dashboard",
|
||||||
url: "/dashboard",
|
url: "#",
|
||||||
icon: HomeIcon,
|
icon: IconDashboard,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Lifecycle",
|
||||||
|
url: "#",
|
||||||
|
icon: IconListDetails,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Analytics",
|
title: "Analytics",
|
||||||
url: "/dashboard/analytics",
|
url: "#",
|
||||||
icon: ChartLineIcon,
|
icon: IconChartBar,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Orders",
|
title: "Projects",
|
||||||
url: "/dashboard/orders",
|
url: "#",
|
||||||
icon: ShoppingBagIcon,
|
icon: IconFolder,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Products",
|
title: "Team",
|
||||||
url: "/dashboard/products",
|
url: "#",
|
||||||
icon: ShoppingCartIcon,
|
icon: IconUsers,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
navClouds: [
|
||||||
|
{
|
||||||
|
title: "Capture",
|
||||||
|
icon: IconCamera,
|
||||||
|
isActive: true,
|
||||||
|
url: "#",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Active Proposals",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Archived",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Invoices",
|
title: "Proposal",
|
||||||
url: "/dashboard/invoices",
|
icon: IconFileDescription,
|
||||||
icon: FileIcon,
|
url: "#",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Active Proposals",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Archived",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Customers",
|
title: "Prompts",
|
||||||
url: "/dashboard/customers",
|
icon: IconFileAi,
|
||||||
icon: UserIcon,
|
url: "#",
|
||||||
},
|
items: [
|
||||||
{
|
{
|
||||||
title: "Settings",
|
title: "Active Proposals",
|
||||||
url: "/dashboard/settings",
|
url: "#",
|
||||||
icon: Settings2Icon,
|
},
|
||||||
|
{
|
||||||
|
title: "Archived",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
navSecondary: [
|
navSecondary: [
|
||||||
{
|
{
|
||||||
title: "Support",
|
title: "Settings",
|
||||||
url: "#",
|
url: "#",
|
||||||
icon: LifeBuoy,
|
icon: IconSettings,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Feedback",
|
title: "Get Help",
|
||||||
url: "#",
|
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>) {
|
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||||
return (
|
return (
|
||||||
<Sidebar
|
<Sidebar collapsible="offcanvas" {...props}>
|
||||||
className="top-(--header-height) h-[calc(100svh-var(--header-height))]!"
|
<SidebarHeader>
|
||||||
{...props}
|
<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>
|
<SidebarContent>
|
||||||
<NavMain items={data.navMain} />
|
<NavMain items={data.navMain} />
|
||||||
|
{/* <NavClouds items={data.navClouds} /> */}
|
||||||
|
<NavDocuments items={data.documents} />
|
||||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
|
<SidebarFooter>
|
||||||
|
<NavUser user={data.user} />
|
||||||
|
</SidebarFooter>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { MoonIcon, SunIcon } from "lucide-react"
|
import { IconBrightness } from "@tabler/icons-react"
|
||||||
import { useTheme } from "next-themes"
|
import { useTheme } from "next-themes"
|
||||||
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
@@ -20,8 +20,7 @@ export function ModeToggle() {
|
|||||||
className="group/toggle size-8"
|
className="group/toggle size-8"
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
>
|
>
|
||||||
<SunIcon className="hidden [html.dark_&]:block" />
|
<IconBrightness />
|
||||||
<MoonIcon className="hidden [html.light_&]:block" />
|
|
||||||
<span className="sr-only">Toggle theme</span>
|
<span className="sr-only">Toggle theme</span>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,23 +1,14 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { usePathname } from "next/navigation"
|
import { IconCirclePlusFilled, IconMail, type Icon } from "@tabler/icons-react"
|
||||||
import { ChevronRight, type LucideIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import {
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
Collapsible,
|
|
||||||
CollapsibleContent,
|
|
||||||
CollapsibleTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/collapsible"
|
|
||||||
import {
|
import {
|
||||||
SidebarGroup,
|
SidebarGroup,
|
||||||
SidebarGroupLabel,
|
SidebarGroupContent,
|
||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
SidebarMenuAction,
|
|
||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
SidebarMenuSub,
|
|
||||||
SidebarMenuSubButton,
|
|
||||||
SidebarMenuSubItem,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
} from "@/registry/new-york-v4/ui/sidebar"
|
||||||
|
|
||||||
export function NavMain({
|
export function NavMain({
|
||||||
@@ -26,66 +17,40 @@ export function NavMain({
|
|||||||
items: {
|
items: {
|
||||||
title: string
|
title: string
|
||||||
url: string
|
url: string
|
||||||
icon: LucideIcon
|
icon?: Icon
|
||||||
isActive?: boolean
|
|
||||||
items?: {
|
|
||||||
title: string
|
|
||||||
url: string
|
|
||||||
}[]
|
|
||||||
disabled?: boolean
|
|
||||||
}[]
|
}[]
|
||||||
}) {
|
}) {
|
||||||
const pathname = usePathname()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupLabel>Dashboard</SidebarGroupLabel>
|
<SidebarGroupContent className="flex flex-col gap-2">
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{items.map((item) => (
|
<SidebarMenuItem className="flex items-center gap-2">
|
||||||
<Collapsible key={item.title} asChild defaultOpen={item.isActive}>
|
<SidebarMenuButton
|
||||||
<SidebarMenuItem>
|
tooltip="Quick Create"
|
||||||
<SidebarMenuButton
|
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"
|
||||||
asChild
|
>
|
||||||
tooltip={item.title}
|
<IconCirclePlusFilled />
|
||||||
isActive={pathname === item.url}
|
<span>Quick Create</span>
|
||||||
disabled={item.disabled}
|
</SidebarMenuButton>
|
||||||
>
|
<Button
|
||||||
<a
|
size="icon"
|
||||||
href={item.disabled ? "#" : item.url}
|
className="size-8 group-data-[collapsible=icon]:opacity-0"
|
||||||
data-disabled={item.disabled}
|
variant="outline"
|
||||||
className="data-[disabled=true]:opacity-50"
|
>
|
||||||
>
|
<IconMail />
|
||||||
<item.icon className="text-muted-foreground" />
|
<span className="sr-only">Inbox</span>
|
||||||
<span>{item.title}</span>
|
</Button>
|
||||||
</a>
|
</SidebarMenuItem>
|
||||||
</SidebarMenuButton>
|
</SidebarMenu>
|
||||||
{item.items?.length ? (
|
<SidebarMenu>
|
||||||
<>
|
{items.map((item) => (
|
||||||
<CollapsibleTrigger asChild>
|
<SidebarMenuButton key={item.title} tooltip={item.title}>
|
||||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
{item.icon && <item.icon />}
|
||||||
<ChevronRight />
|
<span>{item.title}</span>
|
||||||
<span className="sr-only">Toggle</span>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuAction>
|
))}
|
||||||
</CollapsibleTrigger>
|
</SidebarMenu>
|
||||||
<CollapsibleContent>
|
</SidebarGroupContent>
|
||||||
<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>
|
</SidebarGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
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 {
|
import {
|
||||||
SidebarGroup,
|
SidebarGroup,
|
||||||
@@ -8,6 +11,8 @@ import {
|
|||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
} 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({
|
export function NavSecondary({
|
||||||
items,
|
items,
|
||||||
@@ -16,16 +21,23 @@ export function NavSecondary({
|
|||||||
items: {
|
items: {
|
||||||
title: string
|
title: string
|
||||||
url: string
|
url: string
|
||||||
icon: LucideIcon
|
icon: Icon
|
||||||
}[]
|
}[]
|
||||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||||
|
const { resolvedTheme, setTheme } = useTheme()
|
||||||
|
const [mounted, setMounted] = React.useState(false)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setMounted(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarGroup {...props}>
|
<SidebarGroup {...props}>
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<SidebarMenuItem key={item.title}>
|
<SidebarMenuItem key={item.title}>
|
||||||
<SidebarMenuButton asChild size="sm">
|
<SidebarMenuButton asChild>
|
||||||
<a href={item.url}>
|
<a href={item.url}>
|
||||||
<item.icon />
|
<item.icon />
|
||||||
<span>{item.title}</span>
|
<span>{item.title}</span>
|
||||||
@@ -33,6 +45,25 @@ export function NavSecondary({
|
|||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</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>
|
</SidebarMenu>
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { BadgeCheck, Bell, CreditCard, LogOut, Sparkles } from "lucide-react"
|
import {
|
||||||
|
IconCreditCard,
|
||||||
|
IconDotsVertical,
|
||||||
|
IconLogout,
|
||||||
|
IconNotification,
|
||||||
|
IconUserCircle,
|
||||||
|
} from "@tabler/icons-react"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
AvatarFallback,
|
AvatarFallback,
|
||||||
AvatarImage,
|
AvatarImage,
|
||||||
} from "@/registry/new-york-v4/ui/avatar"
|
} from "@/registry/new-york-v4/ui/avatar"
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -17,6 +22,12 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||||
|
import {
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
useSidebar,
|
||||||
|
} from "@/registry/new-york-v4/ui/sidebar"
|
||||||
|
|
||||||
export function NavUser({
|
export function NavUser({
|
||||||
user,
|
user,
|
||||||
@@ -27,64 +38,73 @@ export function NavUser({
|
|||||||
avatar: string
|
avatar: string
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
|
const { isMobile } = useSidebar()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<SidebarMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<SidebarMenuItem>
|
||||||
<Button variant="ghost" size="icon">
|
<DropdownMenu>
|
||||||
<Avatar className="size-8 rounded-md">
|
<DropdownMenuTrigger asChild>
|
||||||
<AvatarImage src={user.avatar} alt={user.name} />
|
<SidebarMenuButton
|
||||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
size="lg"
|
||||||
</Avatar>
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||||
</Button>
|
>
|
||||||
</DropdownMenuTrigger>
|
<Avatar className="h-8 w-8 rounded-lg grayscale">
|
||||||
<DropdownMenuContent
|
<AvatarImage src={user.avatar} alt={user.name} />
|
||||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||||
side="bottom"
|
</Avatar>
|
||||||
align="end"
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
sideOffset={4}
|
<span className="truncate font-medium">{user.name}</span>
|
||||||
>
|
<span className="text-muted-foreground truncate text-xs">
|
||||||
<DropdownMenuLabel className="p-0 font-normal">
|
{user.email}
|
||||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
</span>
|
||||||
<Avatar className="h-8 w-8 rounded-lg">
|
</div>
|
||||||
<AvatarImage src={user.avatar} alt={user.name} />
|
<IconDotsVertical className="ml-auto size-4" />
|
||||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
</SidebarMenuButton>
|
||||||
</Avatar>
|
</DropdownMenuTrigger>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<DropdownMenuContent
|
||||||
<span className="truncate font-medium">{user.name}</span>
|
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||||
<span className="text-muted-foreground truncate text-xs">
|
side={isMobile ? "bottom" : "right"}
|
||||||
{user.email}
|
align="end"
|
||||||
</span>
|
sideOffset={4}
|
||||||
</div>
|
>
|
||||||
</div>
|
<DropdownMenuLabel className="p-0 font-normal">
|
||||||
</DropdownMenuLabel>
|
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||||
<DropdownMenuSeparator />
|
<Avatar className="h-8 w-8 rounded-lg">
|
||||||
<DropdownMenuGroup>
|
<AvatarImage src={user.avatar} alt={user.name} />
|
||||||
<DropdownMenuItem>
|
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||||
<Sparkles />
|
</Avatar>
|
||||||
Upgrade to Pro
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
</DropdownMenuItem>
|
<span className="truncate font-medium">{user.name}</span>
|
||||||
</DropdownMenuGroup>
|
<span className="text-muted-foreground truncate text-xs">
|
||||||
<DropdownMenuSeparator />
|
{user.email}
|
||||||
<DropdownMenuGroup>
|
</span>
|
||||||
<DropdownMenuItem>
|
</div>
|
||||||
<BadgeCheck />
|
</div>
|
||||||
Account
|
</DropdownMenuLabel>
|
||||||
</DropdownMenuItem>
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem>
|
<DropdownMenuGroup>
|
||||||
<CreditCard />
|
<DropdownMenuItem>
|
||||||
Billing
|
<IconUserCircle />
|
||||||
</DropdownMenuItem>
|
Account
|
||||||
<DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<Bell />
|
<DropdownMenuItem>
|
||||||
Notifications
|
<IconCreditCard />
|
||||||
</DropdownMenuItem>
|
Billing
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuItem>
|
||||||
<DropdownMenuItem>
|
<IconNotification />
|
||||||
<LogOut />
|
Notifications
|
||||||
Log out
|
</DropdownMenuItem>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuGroup>
|
||||||
</DropdownMenuContent>
|
<DropdownMenuSeparator />
|
||||||
</DropdownMenu>
|
<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 { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
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 { 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() {
|
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 (
|
return (
|
||||||
<header
|
<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)">
|
||||||
data-slot="site-header"
|
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
||||||
className="bg-background sticky top-0 z-50 flex w-full items-center border-b"
|
<SidebarTrigger className="-ml-1" />
|
||||||
>
|
|
||||||
<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
|
<Separator
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
className="mr-2 data-[orientation=vertical]:h-4"
|
className="mx-2 data-[orientation=vertical]:h-4"
|
||||||
/>
|
/>
|
||||||
<Breadcrumb className="hidden sm:block">
|
<h1 className="text-base font-medium">Documents</h1>
|
||||||
<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">
|
<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 />
|
<ThemeSelector />
|
||||||
<ModeToggle />
|
<ModeToggle />
|
||||||
<NavUser
|
|
||||||
user={{
|
|
||||||
name: "shadcn",
|
|
||||||
email: "m@example.com",
|
|
||||||
avatar: "/avatars/shadcn.jpg",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
import { AppSidebar } from "@/app/(examples)/dashboard/components/app-sidebar"
|
import { AppSidebar } from "@/app/(examples)/dashboard/components/app-sidebar"
|
||||||
import { SiteHeader } from "@/app/(examples)/dashboard/components/site-header"
|
import { SiteHeader } from "@/app/(examples)/dashboard/components/site-header"
|
||||||
|
|
||||||
import "../../themes.css"
|
import "./theme.css"
|
||||||
|
|
||||||
export default async function DashboardLayout({
|
export default async function DashboardLayout({
|
||||||
children,
|
children,
|
||||||
@@ -18,14 +18,19 @@ export default async function DashboardLayout({
|
|||||||
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="[--header-height:calc(theme(spacing.14))]">
|
<SidebarProvider
|
||||||
<SidebarProvider defaultOpen={defaultOpen} className="flex flex-col">
|
defaultOpen={defaultOpen}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--sidebar-width": "calc(var(--spacing) * 72)",
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AppSidebar variant="inset" />
|
||||||
|
<SidebarInset>
|
||||||
<SiteHeader />
|
<SiteHeader />
|
||||||
<div className="flex flex-1">
|
<div className="flex flex-1 flex-col">{children}</div>
|
||||||
<AppSidebar />
|
</SidebarInset>
|
||||||
<SidebarInset>{children}</SidebarInset>
|
</SidebarProvider>
|
||||||
</div>
|
|
||||||
</SidebarProvider>
|
|
||||||
</main>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,206 +1,125 @@
|
|||||||
import { Metadata } from "next"
|
import { Metadata } from "next"
|
||||||
import {
|
import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react"
|
||||||
DownloadIcon,
|
|
||||||
FilterIcon,
|
|
||||||
TrendingDownIcon,
|
|
||||||
TrendingUpIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
|
CardAction,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/registry/new-york-v4/ui/card"
|
} from "@/registry/new-york-v4/ui/card"
|
||||||
import {
|
import { ChartAreaInteractive } from "@/app/(examples)/dashboard/components/chart-area-interactive"
|
||||||
Tabs,
|
import { DataTable } from "@/app/(examples)/dashboard/components/data-table"
|
||||||
TabsContent,
|
|
||||||
TabsList,
|
import data from "./data.json"
|
||||||
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"
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Dashboard",
|
title: "Dashboard",
|
||||||
description: "An example dashboard to test the new components.",
|
description: "A dashboard with tabs, table and sidebar.",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load from database.
|
export default async function Dashboard02() {
|
||||||
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 (
|
return (
|
||||||
<div className="@container/page flex flex-1 flex-col gap-8 p-6">
|
<div className="@container/main flex flex-1 flex-col gap-2">
|
||||||
<Tabs defaultValue="overview" className="gap-6">
|
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
||||||
<div
|
<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">
|
||||||
data-slot="dashboard-header"
|
<Card className="@container/card">
|
||||||
className="flex items-center justify-between"
|
<CardHeader>
|
||||||
>
|
<CardDescription>Total Revenue</CardDescription>
|
||||||
<TabsList className="w-full @3xl/page:w-fit">
|
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
$1,250.00
|
||||||
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
</CardTitle>
|
||||||
<TabsTrigger value="reports">Reports</TabsTrigger>
|
<CardAction>
|
||||||
<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">
|
<Badge variant="outline">
|
||||||
<TrendingUpIcon />
|
<IconTrendingUp />
|
||||||
+12.5%
|
+12.5%
|
||||||
</Badge>
|
</Badge>
|
||||||
</CardFooter>
|
</CardAction>
|
||||||
</Card>
|
</CardHeader>
|
||||||
<Card>
|
<CardFooter className="flex-col items-start gap-2.5 text-sm">
|
||||||
<CardHeader>
|
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||||
<CardTitle>New Customers</CardTitle>
|
Trending up this month <IconTrendingUp className="size-4" />
|
||||||
<CardDescription>-12 customers from last month</CardDescription>
|
</div>
|
||||||
</CardHeader>
|
<div className="text-muted-foreground">
|
||||||
<CardFooter>
|
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">
|
<Badge variant="outline">
|
||||||
<TrendingDownIcon />
|
<IconTrendingDown />
|
||||||
-20%
|
-20%
|
||||||
</Badge>
|
</Badge>
|
||||||
</CardFooter>
|
</CardAction>
|
||||||
</Card>
|
</CardHeader>
|
||||||
<Card>
|
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||||
<CardHeader>
|
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||||
<CardTitle>Active Accounts</CardTitle>
|
Down 20% this period <IconTrendingDown className="size-4" />
|
||||||
<CardDescription>+2,345 users from last month</CardDescription>
|
</div>
|
||||||
</CardHeader>
|
<div className="text-muted-foreground">
|
||||||
<CardFooter>
|
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">
|
<Badge variant="outline">
|
||||||
<TrendingUpIcon />
|
<IconTrendingUp />
|
||||||
+12.5%
|
+12.5%
|
||||||
</Badge>
|
</Badge>
|
||||||
</CardFooter>
|
</CardAction>
|
||||||
</Card>
|
</CardHeader>
|
||||||
<Card>
|
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||||
<CardHeader>
|
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||||
<CardTitle>Growth Rate</CardTitle>
|
Strong user retention <IconTrendingUp className="size-4" />
|
||||||
<CardDescription>+12.5% increase per month</CardDescription>
|
</div>
|
||||||
</CardHeader>
|
<div className="text-muted-foreground">
|
||||||
<CardFooter>
|
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">
|
<Badge variant="outline">
|
||||||
<TrendingUpIcon />
|
<IconTrendingUp />
|
||||||
+4.5%
|
+4.5%
|
||||||
</Badge>
|
</Badge>
|
||||||
</CardFooter>
|
</CardAction>
|
||||||
</Card>
|
</CardHeader>
|
||||||
</div>
|
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||||
<div className="grid grid-cols-1 gap-4 @4xl/page:grid-cols-[2fr_1fr]">
|
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||||
<ChartRevenue />
|
Steady performance increase{" "}
|
||||||
<ChartVisitors />
|
<IconTrendingUp className="size-4" />
|
||||||
</div>
|
</div>
|
||||||
<ProductsTable products={products} />
|
<div className="text-muted-foreground">
|
||||||
</TabsContent>
|
Meets growth projections
|
||||||
</Tabs>
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<div className="px-4 lg:px-6">
|
||||||
|
<ChartAreaInteractive />
|
||||||
|
</div>
|
||||||
|
<DataTable data={data} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export const metadata: Metadata = {
|
|||||||
siteName: siteConfig.name,
|
siteName: siteConfig.name,
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: siteConfig.ogImage,
|
url: "https://v4.shadcn.com/opengraph-image.png",
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 630,
|
height: 630,
|
||||||
alt: siteConfig.name,
|
alt: siteConfig.name,
|
||||||
@@ -57,7 +57,7 @@ export const metadata: Metadata = {
|
|||||||
card: "summary_large_image",
|
card: "summary_large_image",
|
||||||
title: siteConfig.name,
|
title: siteConfig.name,
|
||||||
description: siteConfig.description,
|
description: siteConfig.description,
|
||||||
images: [siteConfig.ogImage],
|
images: ["https://v4.shadcn.com/opengraph-image.png"],
|
||||||
creator: "@shadcn",
|
creator: "@shadcn",
|
||||||
},
|
},
|
||||||
icons: {
|
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