Compare commits
65 Commits
shadcn/min
...
shadcn@2.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1a36d3e17 | ||
|
|
617483fe9c | ||
|
|
6d2728db2e | ||
|
|
625be136f4 | ||
|
|
1bd209a4db | ||
|
|
812e2300f1 | ||
|
|
b52fa4559c | ||
|
|
2fade2326a | ||
|
|
754a66061d | ||
|
|
cc53c2243e | ||
|
|
99fbf4cb77 | ||
|
|
a82db8395d | ||
|
|
79eed50e5b | ||
|
|
799a7f8aa7 | ||
|
|
79a6f54d4e | ||
|
|
26edb8275e | ||
|
|
e15e31e3cb | ||
|
|
d4247d52da | ||
|
|
9d908ae6ca | ||
|
|
883ad8cbc4 | ||
|
|
074eed5605 | ||
|
|
ca7fbc3b64 | ||
|
|
b3b2fe2755 | ||
|
|
1fcb318c56 | ||
|
|
2a783175c5 | ||
|
|
69fc8e23cc | ||
|
|
84ec2f6079 | ||
|
|
37e5192fe7 | ||
|
|
205bfc637e | ||
|
|
9eae13639c | ||
|
|
c77ddfc8b7 | ||
|
|
516ed3d17e | ||
|
|
bbda8c0b5a | ||
|
|
d5be468d32 | ||
|
|
9f094a13b9 | ||
|
|
829ef0cbf6 | ||
|
|
4ebfd6896f | ||
|
|
911c52022a | ||
|
|
61e21e3356 | ||
|
|
28d97b7fc1 | ||
|
|
5085e48241 | ||
|
|
b7afb5aa1e | ||
|
|
a6e2d3672f | ||
|
|
0f6efb9769 | ||
|
|
415b02b4bc | ||
|
|
f1dd9c6903 | ||
|
|
abde54987b | ||
|
|
79f2498b28 | ||
|
|
d999f803a8 | ||
|
|
1d8fe41d64 | ||
|
|
33b6146fde | ||
|
|
1d55420629 | ||
|
|
12d4cf2ab0 | ||
|
|
5466432b01 | ||
|
|
f9e4991f15 | ||
|
|
67e7364b3c | ||
|
|
2f6c3e74bb | ||
|
|
a106fc5412 | ||
|
|
19665adeed | ||
|
|
fc0e8c0c52 | ||
|
|
c16c58d0f9 | ||
|
|
a3fe5074c1 | ||
|
|
3baef994d7 | ||
|
|
a7b3dbf121 | ||
|
|
26dc03b545 |
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
add support for TanStack Start
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
support for version detection in monorepo
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
upgrade @antfu/ni
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
do not add ring for v3
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
add theme vars support
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
add tailwind version detection
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
add support for tailwind v4
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
default to css vars. add --no-css-variables
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
cache registry calls
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
default for new-york for v4
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
fix handling of sidebar colors
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
do not overwrite user defined vars
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix cn import bug in monorepo
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
filter out deprecated from --all
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
add oklch base color
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
hotswap style for v4
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
check for empty css vars
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
add warning for deprecated components
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix tanstack check
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"shadcn": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
add support for route install for react-router and laravel
|
|
||||||
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [shadcn]
|
||||||
6
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
9
SECURITY.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
If you believe you have found a security vulnerability, we encourage you to let us know right away.
|
||||||
|
|
||||||
|
We will investigate all legitimate reports and do our best to quickly fix the problem.
|
||||||
|
|
||||||
|
Our preference is that you make use of GitHub's private vulnerability reporting feature to disclose potential security vulnerabilities in our Open Source Software.
|
||||||
|
|
||||||
|
To do this, please visit the security tab of the repository and click the "Report a vulnerability" button.
|
||||||
@@ -1081,6 +1081,101 @@ export const Index: Record<string, any> = {
|
|||||||
}),
|
}),
|
||||||
meta: undefined,
|
meta: undefined,
|
||||||
},
|
},
|
||||||
|
"dashboard-01": {
|
||||||
|
name: "dashboard-01",
|
||||||
|
description: "A dashboard with sidebar, charts and data table.",
|
||||||
|
type: "registry:block",
|
||||||
|
registryDependencies: [
|
||||||
|
"sidebar",
|
||||||
|
"breadcrumb",
|
||||||
|
"separator",
|
||||||
|
"label",
|
||||||
|
"chart",
|
||||||
|
"card",
|
||||||
|
"select",
|
||||||
|
"tabs",
|
||||||
|
"table",
|
||||||
|
"toggle-group",
|
||||||
|
"badge",
|
||||||
|
"button",
|
||||||
|
"checkbox",
|
||||||
|
"dropdown-menu",
|
||||||
|
"drawer",
|
||||||
|
"input",
|
||||||
|
"avatar",
|
||||||
|
"sheet",
|
||||||
|
"sonner",
|
||||||
|
],
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
path: "registry/blocks/dashboard-01/page.tsx",
|
||||||
|
type: "registry:page",
|
||||||
|
target: "app/dashboard/page.tsx",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "registry/blocks/dashboard-01/data.json",
|
||||||
|
type: "registry:file",
|
||||||
|
target: "app/dashboard/data.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "registry/blocks/dashboard-01/components/app-sidebar.tsx",
|
||||||
|
type: "registry:component",
|
||||||
|
target: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "registry/blocks/dashboard-01/components/chart-area-interactive.tsx",
|
||||||
|
type: "registry:component",
|
||||||
|
target: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "registry/blocks/dashboard-01/components/data-table.tsx",
|
||||||
|
type: "registry:component",
|
||||||
|
target: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "registry/blocks/dashboard-01/components/nav-documents.tsx",
|
||||||
|
type: "registry:component",
|
||||||
|
target: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "registry/blocks/dashboard-01/components/nav-main.tsx",
|
||||||
|
type: "registry:component",
|
||||||
|
target: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "registry/blocks/dashboard-01/components/nav-secondary.tsx",
|
||||||
|
type: "registry:component",
|
||||||
|
target: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "registry/blocks/dashboard-01/components/nav-user.tsx",
|
||||||
|
type: "registry:component",
|
||||||
|
target: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "registry/blocks/dashboard-01/components/section-cards.tsx",
|
||||||
|
type: "registry:component",
|
||||||
|
target: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "registry/blocks/dashboard-01/components/site-header.tsx",
|
||||||
|
type: "registry:component",
|
||||||
|
target: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
component: React.lazy(async () => {
|
||||||
|
const mod = await import(
|
||||||
|
"@/registry/new-york-v4/blocks/dashboard-01/page.tsx"
|
||||||
|
)
|
||||||
|
const exportName =
|
||||||
|
Object.keys(mod).find(
|
||||||
|
(key) =>
|
||||||
|
typeof mod[key] === "function" || typeof mod[key] === "object"
|
||||||
|
) || item.name
|
||||||
|
return { default: mod.default || mod[exportName] }
|
||||||
|
}),
|
||||||
|
meta: undefined,
|
||||||
|
},
|
||||||
"sidebar-01": {
|
"sidebar-01": {
|
||||||
name: "sidebar-01",
|
name: "sidebar-01",
|
||||||
description: "A simple sidebar with navigation grouped by section.",
|
description: "A simple sidebar with navigation grouped by section.",
|
||||||
|
|||||||
@@ -2,8 +2,13 @@ import { FormsDemo } from "@/components/forms-demo"
|
|||||||
|
|
||||||
export default function FormsPage() {
|
export default function FormsPage() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 items-center justify-center p-4">
|
<div className="flex flex-1 flex-col items-center justify-center gap-12 p-4 lg:flex-row">
|
||||||
<FormsDemo />
|
<div className="">
|
||||||
|
<FormsDemo />
|
||||||
|
</div>
|
||||||
|
<div className="theme-scaled">
|
||||||
|
<FormsDemo />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { cookies } from "next/headers"
|
|||||||
import { AppSidebar } from "@/components/app-sidebar"
|
import { AppSidebar } from "@/components/app-sidebar"
|
||||||
import { ModeSwitcher } from "@/components/mode-switcher"
|
import { ModeSwitcher } from "@/components/mode-switcher"
|
||||||
import { NavHeader } from "@/components/nav-header"
|
import { NavHeader } from "@/components/nav-header"
|
||||||
|
import { ThemeSelector } from "@/components/theme-selector"
|
||||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||||
import {
|
import {
|
||||||
SidebarInset,
|
SidebarInset,
|
||||||
@@ -31,6 +32,7 @@ export default async function AppLayout({
|
|||||||
/>
|
/>
|
||||||
<NavHeader />
|
<NavHeader />
|
||||||
<div className="ml-auto flex items-center gap-2">
|
<div className="ml-auto flex items-center gap-2">
|
||||||
|
<ThemeSelector />
|
||||||
<ModeSwitcher />
|
<ModeSwitcher />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default function LoginPage() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-muted flex flex-1 flex-col items-center justify-center gap-16 p-6 md:p-10",
|
"bg-muted dark:bg-background flex flex-1 flex-col items-center justify-center gap-16 p-6 md:p-10",
|
||||||
fontSans.variable,
|
fontSans.variable,
|
||||||
fontSerif.variable,
|
fontSerif.variable,
|
||||||
fontManrope.variable
|
fontManrope.variable
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { addDays, format } from "date-fns"
|
||||||
|
import { CalendarIcon } from "lucide-react"
|
||||||
|
import { DateRange } from "react-day-picker"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/popover"
|
||||||
|
|
||||||
|
export function AnalyticsDatePicker() {
|
||||||
|
const [date, setDate] = React.useState<DateRange | undefined>({
|
||||||
|
from: new Date(new Date().getFullYear(), 0, 20),
|
||||||
|
to: addDays(new Date(new Date().getFullYear(), 0, 20), 20),
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
id="date"
|
||||||
|
variant="outline"
|
||||||
|
className={cn(
|
||||||
|
"w-fit justify-start px-2 font-normal",
|
||||||
|
!date && "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CalendarIcon className="text-muted-foreground" />
|
||||||
|
{date?.from ? (
|
||||||
|
date.to ? (
|
||||||
|
<>
|
||||||
|
{format(date.from, "LLL dd, y")} -{" "}
|
||||||
|
{format(date.to, "LLL dd, y")}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
format(date.from, "LLL dd, y")
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<span>Pick a date</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0" align="end">
|
||||||
|
<Calendar
|
||||||
|
initialFocus
|
||||||
|
mode="range"
|
||||||
|
defaultMonth={date?.from}
|
||||||
|
selected={date}
|
||||||
|
onSelect={setDate}
|
||||||
|
numberOfMonths={2}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
110
apps/v4/app/(examples)/dashboard-03/components/chart-revenue.tsx
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { TrendingUp } from "lucide-react"
|
||||||
|
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
||||||
|
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/registry/new-york-v4/ui/card"
|
||||||
|
import {
|
||||||
|
ChartConfig,
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
} from "@/registry/new-york-v4/ui/chart"
|
||||||
|
|
||||||
|
const chartData = [
|
||||||
|
{ month: "January", desktop: 186, mobile: 80 },
|
||||||
|
{ month: "February", desktop: 305, mobile: 200 },
|
||||||
|
{ month: "March", desktop: 237, mobile: 120 },
|
||||||
|
{ month: "April", desktop: 73, mobile: 190 },
|
||||||
|
{ month: "May", desktop: 209, mobile: 130 },
|
||||||
|
{ month: "June", desktop: 346, mobile: 140 },
|
||||||
|
{ month: "July", desktop: 321, mobile: 275 },
|
||||||
|
{ month: "August", desktop: 132, mobile: 95 },
|
||||||
|
{ month: "September", desktop: 189, mobile: 225 },
|
||||||
|
{ month: "October", desktop: 302, mobile: 248 },
|
||||||
|
{ month: "November", desktop: 342, mobile: 285 },
|
||||||
|
{ month: "December", desktop: 328, mobile: 290 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
desktop: {
|
||||||
|
label: "Desktop",
|
||||||
|
color: "var(--chart-1)",
|
||||||
|
},
|
||||||
|
mobile: {
|
||||||
|
label: "Mobile",
|
||||||
|
color: "var(--chart-2)",
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig
|
||||||
|
|
||||||
|
export function ChartRevenue() {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardDescription>January - June 2024</CardDescription>
|
||||||
|
<CardTitle className="text-3xl font-bold tracking-tight">
|
||||||
|
$45,231.89
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<ChartContainer config={chartConfig} className="aspect-[3/1]">
|
||||||
|
<BarChart
|
||||||
|
accessibilityLayer
|
||||||
|
data={chartData}
|
||||||
|
margin={{
|
||||||
|
left: -16,
|
||||||
|
right: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CartesianGrid vertical={false} />
|
||||||
|
<XAxis
|
||||||
|
dataKey="month"
|
||||||
|
tickLine={false}
|
||||||
|
tickMargin={10}
|
||||||
|
axisLine={false}
|
||||||
|
tickFormatter={(value) => value.slice(0, 3)}
|
||||||
|
/>
|
||||||
|
<YAxis
|
||||||
|
tickLine={false}
|
||||||
|
tickMargin={10}
|
||||||
|
axisLine={false}
|
||||||
|
tickFormatter={(value) => value.toLocaleString()}
|
||||||
|
domain={[0, "dataMax"]}
|
||||||
|
/>
|
||||||
|
<ChartTooltip
|
||||||
|
cursor={false}
|
||||||
|
content={<ChartTooltipContent hideIndicator />}
|
||||||
|
/>
|
||||||
|
<Bar
|
||||||
|
dataKey="desktop"
|
||||||
|
fill="var(--color-desktop)"
|
||||||
|
radius={[0, 0, 4, 4]}
|
||||||
|
stackId={1}
|
||||||
|
/>
|
||||||
|
<Bar
|
||||||
|
dataKey="mobile"
|
||||||
|
fill="var(--color-mobile)"
|
||||||
|
radius={[4, 4, 0, 0]}
|
||||||
|
stackId={1}
|
||||||
|
/>
|
||||||
|
</BarChart>
|
||||||
|
</ChartContainer>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||||
|
<div className="flex gap-2 leading-none font-medium">
|
||||||
|
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground leading-none">
|
||||||
|
Showing total visitors for the last 6 months
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { Label, Pie, PieChart, Sector } from "recharts"
|
||||||
|
import { PieSectorDataItem } from "recharts/types/polar/Pie"
|
||||||
|
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardAction,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/registry/new-york-v4/ui/card"
|
||||||
|
import {
|
||||||
|
ChartConfig,
|
||||||
|
ChartContainer,
|
||||||
|
ChartStyle,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
} from "@/registry/new-york-v4/ui/chart"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/registry/new-york-v4/ui/select"
|
||||||
|
|
||||||
|
const desktopData = [
|
||||||
|
{ month: "january", desktop: 186, fill: "var(--color-january)" },
|
||||||
|
{ month: "february", desktop: 305, fill: "var(--color-february)" },
|
||||||
|
{ month: "march", desktop: 237, fill: "var(--color-march)" },
|
||||||
|
{ month: "april", desktop: 173, fill: "var(--color-april)" },
|
||||||
|
{ month: "may", desktop: 209, fill: "var(--color-may)" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
visitors: {
|
||||||
|
label: "Visitors",
|
||||||
|
},
|
||||||
|
desktop: {
|
||||||
|
label: "Desktop",
|
||||||
|
},
|
||||||
|
mobile: {
|
||||||
|
label: "Mobile",
|
||||||
|
},
|
||||||
|
january: {
|
||||||
|
label: "January",
|
||||||
|
color: "var(--chart-1)",
|
||||||
|
},
|
||||||
|
february: {
|
||||||
|
label: "February",
|
||||||
|
color: "var(--chart-2)",
|
||||||
|
},
|
||||||
|
march: {
|
||||||
|
label: "March",
|
||||||
|
color: "var(--chart-3)",
|
||||||
|
},
|
||||||
|
april: {
|
||||||
|
label: "April",
|
||||||
|
color: "var(--chart-4)",
|
||||||
|
},
|
||||||
|
may: {
|
||||||
|
label: "May",
|
||||||
|
color: "var(--chart-5)",
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig
|
||||||
|
|
||||||
|
export function ChartVisitors() {
|
||||||
|
const id = "pie-interactive"
|
||||||
|
const [activeMonth, setActiveMonth] = React.useState(desktopData[0].month)
|
||||||
|
|
||||||
|
const activeIndex = React.useMemo(
|
||||||
|
() => desktopData.findIndex((item) => item.month === activeMonth),
|
||||||
|
[activeMonth]
|
||||||
|
)
|
||||||
|
const months = React.useMemo(() => desktopData.map((item) => item.month), [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card data-chart={id}>
|
||||||
|
<ChartStyle id={id} config={chartConfig} />
|
||||||
|
<CardHeader>
|
||||||
|
<CardDescription>January - June 2024</CardDescription>
|
||||||
|
<CardTitle className="text-2xl font-bold">1,234 visitors</CardTitle>
|
||||||
|
<CardAction>
|
||||||
|
<Select value={activeMonth} onValueChange={setActiveMonth}>
|
||||||
|
<SelectTrigger
|
||||||
|
className="ml-auto h-8 w-[120px]"
|
||||||
|
aria-label="Select a value"
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Select month" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent align="end">
|
||||||
|
{months.map((key) => {
|
||||||
|
const config = chartConfig[key as keyof typeof chartConfig]
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const color = "color" in config ? config.color : undefined
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SelectItem key={key} value={key}>
|
||||||
|
<div className="flex items-center gap-2 text-xs">
|
||||||
|
<span
|
||||||
|
className="flex h-3 w-3 shrink-0 rounded-sm"
|
||||||
|
style={{
|
||||||
|
backgroundColor: color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{config?.label}
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</CardAction>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-1 justify-center pb-0">
|
||||||
|
<ChartContainer
|
||||||
|
id={id}
|
||||||
|
config={chartConfig}
|
||||||
|
className="mx-auto aspect-square w-full max-w-[300px]"
|
||||||
|
>
|
||||||
|
<PieChart>
|
||||||
|
<ChartTooltip
|
||||||
|
cursor={false}
|
||||||
|
content={<ChartTooltipContent hideLabel />}
|
||||||
|
/>
|
||||||
|
<Pie
|
||||||
|
data={desktopData}
|
||||||
|
dataKey="desktop"
|
||||||
|
nameKey="month"
|
||||||
|
innerRadius={60}
|
||||||
|
strokeWidth={5}
|
||||||
|
activeIndex={activeIndex}
|
||||||
|
activeShape={({
|
||||||
|
outerRadius = 0,
|
||||||
|
...props
|
||||||
|
}: PieSectorDataItem) => (
|
||||||
|
<g>
|
||||||
|
<Sector {...props} outerRadius={outerRadius + 10} />
|
||||||
|
<Sector
|
||||||
|
{...props}
|
||||||
|
outerRadius={outerRadius + 25}
|
||||||
|
innerRadius={outerRadius + 12}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Label
|
||||||
|
content={({ viewBox }) => {
|
||||||
|
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
|
||||||
|
return (
|
||||||
|
<text
|
||||||
|
x={viewBox.cx}
|
||||||
|
y={viewBox.cy}
|
||||||
|
textAnchor="middle"
|
||||||
|
dominantBaseline="middle"
|
||||||
|
>
|
||||||
|
<tspan
|
||||||
|
x={viewBox.cx}
|
||||||
|
y={viewBox.cy}
|
||||||
|
className="fill-foreground text-3xl font-bold"
|
||||||
|
>
|
||||||
|
{desktopData[activeIndex].desktop.toLocaleString()}
|
||||||
|
</tspan>
|
||||||
|
<tspan
|
||||||
|
x={viewBox.cx}
|
||||||
|
y={(viewBox.cy || 0) + 24}
|
||||||
|
className="fill-muted-foreground"
|
||||||
|
>
|
||||||
|
Visitors
|
||||||
|
</tspan>
|
||||||
|
</text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Pie>
|
||||||
|
</PieChart>
|
||||||
|
</ChartContainer>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { MoonIcon, SunIcon } from "lucide-react"
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
|
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
|
||||||
|
export function ModeToggle() {
|
||||||
|
const { setTheme, resolvedTheme } = useTheme()
|
||||||
|
|
||||||
|
const toggleTheme = React.useCallback(() => {
|
||||||
|
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||||
|
}, [resolvedTheme, setTheme])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="icon"
|
||||||
|
className="group/toggle size-8"
|
||||||
|
onClick={toggleTheme}
|
||||||
|
>
|
||||||
|
<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
@@ -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
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
import {
|
||||||
|
ArrowUpDownIcon,
|
||||||
|
EllipsisVerticalIcon,
|
||||||
|
ListFilterIcon,
|
||||||
|
PlusIcon,
|
||||||
|
} from "lucide-react"
|
||||||
|
|
||||||
|
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
} from "@/registry/new-york-v4/ui/card"
|
||||||
|
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||||
|
import {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationEllipsis,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationPrevious,
|
||||||
|
} from "@/registry/new-york-v4/ui/pagination"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/registry/new-york-v4/ui/select"
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/registry/new-york-v4/ui/table"
|
||||||
|
import { Tabs, TabsList, TabsTrigger } from "@/registry/new-york-v4/ui/tabs"
|
||||||
|
|
||||||
|
export function ProductsTable({
|
||||||
|
products,
|
||||||
|
}: {
|
||||||
|
products: {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
price: number
|
||||||
|
stock: number
|
||||||
|
dateAdded: string
|
||||||
|
status: string
|
||||||
|
}[]
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Card className="flex w-full flex-col gap-4">
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
|
<Tabs defaultValue="all">
|
||||||
|
<TabsList className="w-full @3xl/page:w-fit">
|
||||||
|
<TabsTrigger value="all">All Products</TabsTrigger>
|
||||||
|
<TabsTrigger value="in-stock">In Stock</TabsTrigger>
|
||||||
|
<TabsTrigger value="low-stock">Low Stock</TabsTrigger>
|
||||||
|
<TabsTrigger value="add-product" asChild>
|
||||||
|
<button>
|
||||||
|
<PlusIcon />
|
||||||
|
</button>
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<div className="hidden items-center gap-2 **:data-[slot=button]:size-8 **:data-[slot=select-trigger]:h-8 @3xl/page:flex">
|
||||||
|
<Select defaultValue="all">
|
||||||
|
<SelectTrigger>
|
||||||
|
<span className="text-muted-foreground text-sm">Category:</span>
|
||||||
|
<SelectValue placeholder="Select a product" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="all">All</SelectItem>
|
||||||
|
<SelectItem value="in-stock">In Stock</SelectItem>
|
||||||
|
<SelectItem value="low-stock">Low Stock</SelectItem>
|
||||||
|
<SelectItem value="archived">Archived</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Select defaultValue="all">
|
||||||
|
<SelectTrigger>
|
||||||
|
<span className="text-muted-foreground text-sm">Price:</span>
|
||||||
|
<SelectValue placeholder="Select a product" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="all">$100-$200</SelectItem>
|
||||||
|
<SelectItem value="in-stock">$200-$300</SelectItem>
|
||||||
|
<SelectItem value="low-stock">$300-$400</SelectItem>
|
||||||
|
<SelectItem value="archived">$400-$500</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Select defaultValue="all">
|
||||||
|
<SelectTrigger>
|
||||||
|
<span className="text-muted-foreground text-sm">Status:</span>
|
||||||
|
<SelectValue placeholder="Select a product" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="all">In Stock</SelectItem>
|
||||||
|
<SelectItem value="in-stock">Low Stock</SelectItem>
|
||||||
|
<SelectItem value="low-stock">Archived</SelectItem>
|
||||||
|
<SelectItem value="archived">Archived</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Button variant="outline" size="icon">
|
||||||
|
<ListFilterIcon />
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" size="icon">
|
||||||
|
<ArrowUpDownIcon />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead className="w-12 px-4">
|
||||||
|
<Checkbox />
|
||||||
|
</TableHead>
|
||||||
|
<TableHead>Product</TableHead>
|
||||||
|
<TableHead className="text-right">Price</TableHead>
|
||||||
|
<TableHead className="text-right">Stock</TableHead>
|
||||||
|
<TableHead>Status</TableHead>
|
||||||
|
<TableHead>Date Added</TableHead>
|
||||||
|
<TableHead />
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody className="**:data-[slot=table-cell]:py-2.5">
|
||||||
|
{products.map((product) => (
|
||||||
|
<TableRow key={product.id}>
|
||||||
|
<TableCell className="px-4">
|
||||||
|
<Checkbox />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="font-medium">{product.name}</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
${product.price.toFixed(2)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">{product.stock}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className={
|
||||||
|
product.status === "Low Stock"
|
||||||
|
? "border-orange-700 bg-transparent text-orange-700 dark:border-orange-700 dark:bg-transparent dark:text-orange-700"
|
||||||
|
: "bg-green-100 text-green-800 dark:bg-green-950 dark:text-green-100"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{product.status}
|
||||||
|
</Badge>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{new Date(product.dateAdded).toLocaleDateString("en-US", {
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
year: "numeric",
|
||||||
|
})}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon" className="size-6">
|
||||||
|
<EllipsisVerticalIcon />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem variant="destructive">
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="flex flex-col items-center justify-between border-t pt-6 @3xl/page:flex-row">
|
||||||
|
<div className="text-muted-foreground hidden text-sm @3xl/page:block">
|
||||||
|
Showing 1-10 of 100 products
|
||||||
|
</div>
|
||||||
|
<Pagination className="mx-0 w-fit">
|
||||||
|
<PaginationContent>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationPrevious href="#" />
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationLink href="#">1</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationLink href="#" isActive>
|
||||||
|
2
|
||||||
|
</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationLink href="#">3</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationEllipsis />
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationNext href="#" />
|
||||||
|
</PaginationItem>
|
||||||
|
</PaginationContent>
|
||||||
|
</Pagination>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Search } from "lucide-react"
|
||||||
|
|
||||||
|
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||||
|
import { SidebarInput } from "@/registry/new-york-v4/ui/sidebar"
|
||||||
|
|
||||||
|
export function SearchForm({ ...props }: React.ComponentProps<"form">) {
|
||||||
|
return (
|
||||||
|
<form {...props}>
|
||||||
|
<div className="relative">
|
||||||
|
<Label htmlFor="search" className="sr-only">
|
||||||
|
Search
|
||||||
|
</Label>
|
||||||
|
<SidebarInput
|
||||||
|
id="search"
|
||||||
|
placeholder="Type to search..."
|
||||||
|
className="h-8 pl-7"
|
||||||
|
/>
|
||||||
|
<Search className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
8
apps/v4/app/(examples)/dashboard-03/customers/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function CustomersPage() {
|
||||||
|
return (
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="bg-input p-4">Input</div>
|
||||||
|
<div className="bg-input/30 p-4">Input 50</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
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
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
500
apps/v4/app/(examples)/dashboard-03/settings/page.tsx
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
import { Metadata } from "next"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardAction,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/registry/new-york-v4/ui/card"
|
||||||
|
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
||||||
|
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||||
|
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/registry/new-york-v4/ui/select"
|
||||||
|
import { Switch } from "@/registry/new-york-v4/ui/switch"
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/registry/new-york-v4/ui/table"
|
||||||
|
import {
|
||||||
|
Tabs,
|
||||||
|
TabsContent,
|
||||||
|
TabsList,
|
||||||
|
TabsTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/tabs"
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Settings",
|
||||||
|
description: "Manage your account settings",
|
||||||
|
}
|
||||||
|
|
||||||
|
const timezones = [
|
||||||
|
{
|
||||||
|
label: "Americas",
|
||||||
|
timezones: [
|
||||||
|
{ value: "America/New_York", label: "(GMT-5) New York" },
|
||||||
|
{ value: "America/Los_Angeles", label: "(GMT-8) Los Angeles" },
|
||||||
|
{ value: "America/Chicago", label: "(GMT-6) Chicago" },
|
||||||
|
{ value: "America/Toronto", label: "(GMT-5) Toronto" },
|
||||||
|
{ value: "America/Vancouver", label: "(GMT-8) Vancouver" },
|
||||||
|
{ value: "America/Sao_Paulo", label: "(GMT-3) São Paulo" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Europe",
|
||||||
|
timezones: [
|
||||||
|
{ value: "Europe/London", label: "(GMT+0) London" },
|
||||||
|
{ value: "Europe/Paris", label: "(GMT+1) Paris" },
|
||||||
|
{ value: "Europe/Berlin", label: "(GMT+1) Berlin" },
|
||||||
|
{ value: "Europe/Rome", label: "(GMT+1) Rome" },
|
||||||
|
{ value: "Europe/Madrid", label: "(GMT+1) Madrid" },
|
||||||
|
{ value: "Europe/Amsterdam", label: "(GMT+1) Amsterdam" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Asia/Pacific",
|
||||||
|
timezones: [
|
||||||
|
{ value: "Asia/Tokyo", label: "(GMT+9) Tokyo" },
|
||||||
|
{ value: "Asia/Shanghai", label: "(GMT+8) Shanghai" },
|
||||||
|
{ value: "Asia/Singapore", label: "(GMT+8) Singapore" },
|
||||||
|
{ value: "Asia/Dubai", label: "(GMT+4) Dubai" },
|
||||||
|
{ value: "Australia/Sydney", label: "(GMT+11) Sydney" },
|
||||||
|
{ value: "Asia/Seoul", label: "(GMT+9) Seoul" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const loginHistory = [
|
||||||
|
{
|
||||||
|
date: "2024-01-01",
|
||||||
|
ip: "192.168.1.1",
|
||||||
|
location: "New York, USA",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: "2023-12-29",
|
||||||
|
ip: "172.16.0.100",
|
||||||
|
location: "London, UK",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: "2023-12-28",
|
||||||
|
ip: "10.0.0.50",
|
||||||
|
location: "Toronto, Canada",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: "2023-12-25",
|
||||||
|
ip: "192.168.2.15",
|
||||||
|
location: "Sydney, Australia",
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const activeSessions = [
|
||||||
|
{
|
||||||
|
device: "MacBook Pro",
|
||||||
|
browser: "Chrome",
|
||||||
|
os: "macOS",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
device: "iPhone",
|
||||||
|
browser: "Safari",
|
||||||
|
os: "iOS",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
device: "iPad",
|
||||||
|
browser: "Safari",
|
||||||
|
os: "iOS",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
device: "Android Phone",
|
||||||
|
browser: "Chrome",
|
||||||
|
os: "Android",
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export default function SettingsPage() {
|
||||||
|
return (
|
||||||
|
<div className="@container/page flex flex-1 flex-col gap-8 p-6">
|
||||||
|
<Tabs defaultValue="account" className="gap-6">
|
||||||
|
<div
|
||||||
|
data-slot="dashboard-header"
|
||||||
|
className="flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="account">Account</TabsTrigger>
|
||||||
|
<TabsTrigger value="security">Security</TabsTrigger>
|
||||||
|
<TabsTrigger value="notifications">Notifications</TabsTrigger>
|
||||||
|
<TabsTrigger value="privacy">Privacy</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</div>
|
||||||
|
<TabsContent value="account" className="grid gap-6">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Account Settings</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Make changes to your account here.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form id="form-account" className="@container">
|
||||||
|
<FieldGroup>
|
||||||
|
<Field>
|
||||||
|
<Label htmlFor="name">Name</Label>
|
||||||
|
<FieldControl>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
placeholder="First and last name"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FieldControl>
|
||||||
|
<FieldDescription>
|
||||||
|
This is your public display name.
|
||||||
|
</FieldDescription>
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<FieldControl>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
placeholder="you@example.com"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FieldControl>
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
<Label htmlFor="timezone">Timezone</Label>
|
||||||
|
<FieldControl>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id="timezone">
|
||||||
|
<SelectValue placeholder="Select a timezone" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{timezones.map((timezone) => (
|
||||||
|
<SelectGroup key={timezone.label}>
|
||||||
|
<SelectLabel>{timezone.label}</SelectLabel>
|
||||||
|
{timezone.timezones.map((time) => (
|
||||||
|
<SelectItem key={time.value} value={time.value}>
|
||||||
|
{time.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FieldControl>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="border-t">
|
||||||
|
<Button type="submit" form="form-account" variant="secondary">
|
||||||
|
Save changes
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Notifications</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Manage how you receive notifications.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form id="form-notifications" className="@container">
|
||||||
|
<FieldGroup>
|
||||||
|
<Field>
|
||||||
|
<Label htmlFor="channels">Notification Channels</Label>
|
||||||
|
<FieldControl className="flex flex-col gap-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox id="notification-email" />
|
||||||
|
<Label htmlFor="notification-email">Email</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox id="notification-sms" />
|
||||||
|
<Label htmlFor="notification-sms">SMS</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox id="notification-push" />
|
||||||
|
<Label htmlFor="notification-push">Push</Label>
|
||||||
|
</div>
|
||||||
|
</FieldControl>
|
||||||
|
<FieldDescription>
|
||||||
|
Choose how you want to receive notifications.
|
||||||
|
</FieldDescription>
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
<Label htmlFor="types">Notification Types</Label>
|
||||||
|
<FieldControl className="flex flex-col gap-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox id="notification-account" />
|
||||||
|
<Label htmlFor="notification-account">
|
||||||
|
Account Activity
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
id="notification-security"
|
||||||
|
defaultChecked
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<Label htmlFor="notification-security">
|
||||||
|
Security Alerts
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox id="notification-marketing" />
|
||||||
|
<Label htmlFor="notification-marketing">
|
||||||
|
Marketing & Promotions
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</FieldControl>
|
||||||
|
<FieldDescription>
|
||||||
|
Choose how you want to receive notifications.
|
||||||
|
</FieldDescription>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="border-t">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
form="form-notifications"
|
||||||
|
variant="secondary"
|
||||||
|
>
|
||||||
|
Save changes
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent
|
||||||
|
value="security"
|
||||||
|
className="grid gap-6 @3xl/page:grid-cols-2"
|
||||||
|
>
|
||||||
|
<Card className="@3xl/page:col-span-2">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Security Settings</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Make changes to your security settings here.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="@container">
|
||||||
|
<form id="form-security">
|
||||||
|
<FieldGroup>
|
||||||
|
<Field>
|
||||||
|
<Label htmlFor="current-password">Current Password</Label>
|
||||||
|
<FieldControl>
|
||||||
|
<Input
|
||||||
|
id="current-password"
|
||||||
|
placeholder="Current password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FieldControl>
|
||||||
|
<FieldDescription>
|
||||||
|
This is your current password.
|
||||||
|
</FieldDescription>
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
<Label htmlFor="new-password">New Password</Label>
|
||||||
|
<FieldControl>
|
||||||
|
<Input
|
||||||
|
id="new-password"
|
||||||
|
placeholder="New password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FieldControl>
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
<Label htmlFor="confirm-password">Confirm Password</Label>
|
||||||
|
<FieldControl>
|
||||||
|
<Input
|
||||||
|
id="confirm-password"
|
||||||
|
placeholder="Confirm password"
|
||||||
|
/>
|
||||||
|
</FieldControl>
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
<FieldControl>
|
||||||
|
<Switch
|
||||||
|
id="enable-two-factor-auth"
|
||||||
|
className="self-start"
|
||||||
|
/>
|
||||||
|
</FieldControl>
|
||||||
|
<Label htmlFor="enable-two-factor-auth">
|
||||||
|
Enable two-factor authentication
|
||||||
|
</Label>
|
||||||
|
<FieldDescription>
|
||||||
|
This will add an extra layer of security to your account.
|
||||||
|
Make this an extra long description to test the layout.
|
||||||
|
</FieldDescription>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="border-t">
|
||||||
|
<Button type="submit" form="form-security" variant="secondary">
|
||||||
|
Save changes
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Login History</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Recent login activities on your account.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Date</TableHead>
|
||||||
|
<TableHead className="hidden @md/page:table-cell">
|
||||||
|
IP
|
||||||
|
</TableHead>
|
||||||
|
<TableHead>Location</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{loginHistory.map((login) => (
|
||||||
|
<TableRow key={login.date}>
|
||||||
|
<TableCell>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
{new Date(login.date).toLocaleDateString("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
})}
|
||||||
|
<span className="flex @md/page:hidden">
|
||||||
|
{login.ip}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="hidden @md/page:table-cell">
|
||||||
|
{login.ip}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{login.location}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Active Sessions</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Current active sessions on your account.
|
||||||
|
</CardDescription>
|
||||||
|
<CardAction>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<span className="hidden @md/card-header:block">
|
||||||
|
Manage Sessions
|
||||||
|
</span>
|
||||||
|
<span className="block @md/card-header:hidden">Manage</span>
|
||||||
|
</Button>
|
||||||
|
</CardAction>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Device</TableHead>
|
||||||
|
<TableHead>Browser</TableHead>
|
||||||
|
<TableHead>OS</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{activeSessions.map((session) => (
|
||||||
|
<TableRow key={session.device}>
|
||||||
|
<TableCell>{session.device}</TableCell>
|
||||||
|
<TableCell>{session.browser}</TableCell>
|
||||||
|
<TableCell>{session.os}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function FieldGroup({ children }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="field-group"
|
||||||
|
className="@container/field-group flex max-w-4xl min-w-0 flex-col gap-8 @3xl:gap-6"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Field({ children, className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="field"
|
||||||
|
className={cn(
|
||||||
|
"grid auto-rows-min items-start gap-3 *:data-[slot=label]:col-start-1 *:data-[slot=label]:row-start-1 @3xl/field-group:grid-cols-2 @3xl/field-group:gap-6",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function FieldControl({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="field-control"
|
||||||
|
className={cn(
|
||||||
|
"@3xl/field-group:col-start-2 @3xl/field-group:row-span-2 @3xl/field-group:row-start-1 @3xl/field-group:self-start",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function FieldDescription({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"p">) {
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
data-slot="field-description"
|
||||||
|
className={cn(
|
||||||
|
"text-muted-foreground text-sm @3xl/field-group:col-start-1 @3xl/field-group:row-start-1 @3xl/field-group:translate-y-6",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
181
apps/v4/app/(examples)/dashboard/components/app-sidebar.tsx
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
"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 { 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: "#",
|
||||||
|
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} />
|
||||||
|
<NavDocuments items={data.documents} />
|
||||||
|
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||||
|
</SidebarContent>
|
||||||
|
<SidebarFooter>
|
||||||
|
<NavUser user={data.user} />
|
||||||
|
</SidebarFooter>
|
||||||
|
</Sidebar>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||||
|
|
||||||
|
import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile"
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardAction,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/registry/new-york-v4/ui/card"
|
||||||
|
import {
|
||||||
|
ChartConfig,
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
} from "@/registry/new-york-v4/ui/chart"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/registry/new-york-v4/ui/select"
|
||||||
|
import {
|
||||||
|
ToggleGroup,
|
||||||
|
ToggleGroupItem,
|
||||||
|
} from "@/registry/new-york-v4/ui/toggle-group"
|
||||||
|
|
||||||
|
export const description = "An interactive area chart"
|
||||||
|
|
||||||
|
const chartData = [
|
||||||
|
{ date: "2024-04-01", desktop: 222, mobile: 150 },
|
||||||
|
{ date: "2024-04-02", desktop: 97, mobile: 180 },
|
||||||
|
{ date: "2024-04-03", desktop: 167, mobile: 120 },
|
||||||
|
{ date: "2024-04-04", desktop: 242, mobile: 260 },
|
||||||
|
{ date: "2024-04-05", desktop: 373, mobile: 290 },
|
||||||
|
{ date: "2024-04-06", desktop: 301, mobile: 340 },
|
||||||
|
{ date: "2024-04-07", desktop: 245, mobile: 180 },
|
||||||
|
{ date: "2024-04-08", desktop: 409, mobile: 320 },
|
||||||
|
{ date: "2024-04-09", desktop: 59, mobile: 110 },
|
||||||
|
{ date: "2024-04-10", desktop: 261, mobile: 190 },
|
||||||
|
{ date: "2024-04-11", desktop: 327, mobile: 350 },
|
||||||
|
{ date: "2024-04-12", desktop: 292, mobile: 210 },
|
||||||
|
{ date: "2024-04-13", desktop: 342, mobile: 380 },
|
||||||
|
{ date: "2024-04-14", desktop: 137, mobile: 220 },
|
||||||
|
{ date: "2024-04-15", desktop: 120, mobile: 170 },
|
||||||
|
{ date: "2024-04-16", desktop: 138, mobile: 190 },
|
||||||
|
{ date: "2024-04-17", desktop: 446, mobile: 360 },
|
||||||
|
{ date: "2024-04-18", desktop: 364, mobile: 410 },
|
||||||
|
{ date: "2024-04-19", desktop: 243, mobile: 180 },
|
||||||
|
{ date: "2024-04-20", desktop: 89, mobile: 150 },
|
||||||
|
{ date: "2024-04-21", desktop: 137, mobile: 200 },
|
||||||
|
{ date: "2024-04-22", desktop: 224, mobile: 170 },
|
||||||
|
{ date: "2024-04-23", desktop: 138, mobile: 230 },
|
||||||
|
{ date: "2024-04-24", desktop: 387, mobile: 290 },
|
||||||
|
{ date: "2024-04-25", desktop: 215, mobile: 250 },
|
||||||
|
{ date: "2024-04-26", desktop: 75, mobile: 130 },
|
||||||
|
{ date: "2024-04-27", desktop: 383, mobile: 420 },
|
||||||
|
{ date: "2024-04-28", desktop: 122, mobile: 180 },
|
||||||
|
{ date: "2024-04-29", desktop: 315, mobile: 240 },
|
||||||
|
{ date: "2024-04-30", desktop: 454, mobile: 380 },
|
||||||
|
{ date: "2024-05-01", desktop: 165, mobile: 220 },
|
||||||
|
{ date: "2024-05-02", desktop: 293, mobile: 310 },
|
||||||
|
{ date: "2024-05-03", desktop: 247, mobile: 190 },
|
||||||
|
{ date: "2024-05-04", desktop: 385, mobile: 420 },
|
||||||
|
{ date: "2024-05-05", desktop: 481, mobile: 390 },
|
||||||
|
{ date: "2024-05-06", desktop: 498, mobile: 520 },
|
||||||
|
{ date: "2024-05-07", desktop: 388, mobile: 300 },
|
||||||
|
{ date: "2024-05-08", desktop: 149, mobile: 210 },
|
||||||
|
{ date: "2024-05-09", desktop: 227, mobile: 180 },
|
||||||
|
{ date: "2024-05-10", desktop: 293, mobile: 330 },
|
||||||
|
{ date: "2024-05-11", desktop: 335, mobile: 270 },
|
||||||
|
{ date: "2024-05-12", desktop: 197, mobile: 240 },
|
||||||
|
{ date: "2024-05-13", desktop: 197, mobile: 160 },
|
||||||
|
{ date: "2024-05-14", desktop: 448, mobile: 490 },
|
||||||
|
{ date: "2024-05-15", desktop: 473, mobile: 380 },
|
||||||
|
{ date: "2024-05-16", desktop: 338, mobile: 400 },
|
||||||
|
{ date: "2024-05-17", desktop: 499, mobile: 420 },
|
||||||
|
{ date: "2024-05-18", desktop: 315, mobile: 350 },
|
||||||
|
{ date: "2024-05-19", desktop: 235, mobile: 180 },
|
||||||
|
{ date: "2024-05-20", desktop: 177, mobile: 230 },
|
||||||
|
{ date: "2024-05-21", desktop: 82, mobile: 140 },
|
||||||
|
{ date: "2024-05-22", desktop: 81, mobile: 120 },
|
||||||
|
{ date: "2024-05-23", desktop: 252, mobile: 290 },
|
||||||
|
{ date: "2024-05-24", desktop: 294, mobile: 220 },
|
||||||
|
{ date: "2024-05-25", desktop: 201, mobile: 250 },
|
||||||
|
{ date: "2024-05-26", desktop: 213, mobile: 170 },
|
||||||
|
{ date: "2024-05-27", desktop: 420, mobile: 460 },
|
||||||
|
{ date: "2024-05-28", desktop: 233, mobile: 190 },
|
||||||
|
{ date: "2024-05-29", desktop: 78, mobile: 130 },
|
||||||
|
{ date: "2024-05-30", desktop: 340, mobile: 280 },
|
||||||
|
{ date: "2024-05-31", desktop: 178, mobile: 230 },
|
||||||
|
{ date: "2024-06-01", desktop: 178, mobile: 200 },
|
||||||
|
{ date: "2024-06-02", desktop: 470, mobile: 410 },
|
||||||
|
{ date: "2024-06-03", desktop: 103, mobile: 160 },
|
||||||
|
{ date: "2024-06-04", desktop: 439, mobile: 380 },
|
||||||
|
{ date: "2024-06-05", desktop: 88, mobile: 140 },
|
||||||
|
{ date: "2024-06-06", desktop: 294, mobile: 250 },
|
||||||
|
{ date: "2024-06-07", desktop: 323, mobile: 370 },
|
||||||
|
{ date: "2024-06-08", desktop: 385, mobile: 320 },
|
||||||
|
{ date: "2024-06-09", desktop: 438, mobile: 480 },
|
||||||
|
{ date: "2024-06-10", desktop: 155, mobile: 200 },
|
||||||
|
{ date: "2024-06-11", desktop: 92, mobile: 150 },
|
||||||
|
{ date: "2024-06-12", desktop: 492, mobile: 420 },
|
||||||
|
{ date: "2024-06-13", desktop: 81, mobile: 130 },
|
||||||
|
{ date: "2024-06-14", desktop: 426, mobile: 380 },
|
||||||
|
{ date: "2024-06-15", desktop: 307, mobile: 350 },
|
||||||
|
{ date: "2024-06-16", desktop: 371, mobile: 310 },
|
||||||
|
{ date: "2024-06-17", desktop: 475, mobile: 520 },
|
||||||
|
{ date: "2024-06-18", desktop: 107, mobile: 170 },
|
||||||
|
{ date: "2024-06-19", desktop: 341, mobile: 290 },
|
||||||
|
{ date: "2024-06-20", desktop: 408, mobile: 450 },
|
||||||
|
{ date: "2024-06-21", desktop: 169, mobile: 210 },
|
||||||
|
{ date: "2024-06-22", desktop: 317, mobile: 270 },
|
||||||
|
{ date: "2024-06-23", desktop: 480, mobile: 530 },
|
||||||
|
{ date: "2024-06-24", desktop: 132, mobile: 180 },
|
||||||
|
{ date: "2024-06-25", desktop: 141, mobile: 190 },
|
||||||
|
{ date: "2024-06-26", desktop: 434, mobile: 380 },
|
||||||
|
{ date: "2024-06-27", desktop: 448, mobile: 490 },
|
||||||
|
{ date: "2024-06-28", desktop: 149, mobile: 200 },
|
||||||
|
{ date: "2024-06-29", desktop: 103, mobile: 160 },
|
||||||
|
{ date: "2024-06-30", desktop: 446, mobile: 400 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
visitors: {
|
||||||
|
label: "Visitors",
|
||||||
|
},
|
||||||
|
desktop: {
|
||||||
|
label: "Desktop",
|
||||||
|
color: "var(--primary)",
|
||||||
|
},
|
||||||
|
mobile: {
|
||||||
|
label: "Mobile",
|
||||||
|
color: "var(--primary)",
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig
|
||||||
|
|
||||||
|
export function ChartAreaInteractive() {
|
||||||
|
const isMobile = useIsMobile()
|
||||||
|
const [timeRange, setTimeRange] = React.useState("90d")
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isMobile) {
|
||||||
|
setTimeRange("7d")
|
||||||
|
}
|
||||||
|
}, [isMobile])
|
||||||
|
|
||||||
|
const filteredData = chartData.filter((item) => {
|
||||||
|
const date = new Date(item.date)
|
||||||
|
const referenceDate = new Date("2024-06-30")
|
||||||
|
let daysToSubtract = 90
|
||||||
|
if (timeRange === "30d") {
|
||||||
|
daysToSubtract = 30
|
||||||
|
} else if (timeRange === "7d") {
|
||||||
|
daysToSubtract = 7
|
||||||
|
}
|
||||||
|
const startDate = new Date(referenceDate)
|
||||||
|
startDate.setDate(startDate.getDate() - daysToSubtract)
|
||||||
|
return date >= startDate
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="@container/card">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Total Visitors</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
<span className="hidden @[540px]/card:block">
|
||||||
|
Total for the last 3 months
|
||||||
|
</span>
|
||||||
|
<span className="@[540px]/card:hidden">Last 3 months</span>
|
||||||
|
</CardDescription>
|
||||||
|
<CardAction>
|
||||||
|
<ToggleGroup
|
||||||
|
type="single"
|
||||||
|
value={timeRange}
|
||||||
|
onValueChange={setTimeRange}
|
||||||
|
variant="outline"
|
||||||
|
className="hidden *:data-[slot=toggle-group-item]:!px-4 @[767px]/card:flex"
|
||||||
|
>
|
||||||
|
<ToggleGroupItem value="90d">Last 3 months</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="30d">Last 30 days</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="7d">Last 7 days</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
<Select value={timeRange} onValueChange={setTimeRange}>
|
||||||
|
<SelectTrigger
|
||||||
|
className="flex w-40 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate @[767px]/card:hidden"
|
||||||
|
size="sm"
|
||||||
|
aria-label="Select a value"
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Last 3 months" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent className="rounded-xl">
|
||||||
|
<SelectItem value="90d" className="rounded-lg">
|
||||||
|
Last 3 months
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="30d" className="rounded-lg">
|
||||||
|
Last 30 days
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="7d" className="rounded-lg">
|
||||||
|
Last 7 days
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</CardAction>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="px-2 pt-4 sm:px-6 sm:pt-6">
|
||||||
|
<ChartContainer
|
||||||
|
config={chartConfig}
|
||||||
|
className="aspect-auto h-[250px] w-full"
|
||||||
|
>
|
||||||
|
<AreaChart data={filteredData}>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="fillDesktop" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop
|
||||||
|
offset="5%"
|
||||||
|
stopColor="var(--color-desktop)"
|
||||||
|
stopOpacity={1.0}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset="95%"
|
||||||
|
stopColor="var(--color-desktop)"
|
||||||
|
stopOpacity={0.1}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="fillMobile" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop
|
||||||
|
offset="5%"
|
||||||
|
stopColor="var(--color-mobile)"
|
||||||
|
stopOpacity={0.8}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset="95%"
|
||||||
|
stopColor="var(--color-mobile)"
|
||||||
|
stopOpacity={0.1}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<CartesianGrid vertical={false} />
|
||||||
|
<XAxis
|
||||||
|
dataKey="date"
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={false}
|
||||||
|
tickMargin={8}
|
||||||
|
minTickGap={32}
|
||||||
|
tickFormatter={(value) => {
|
||||||
|
const date = new Date(value)
|
||||||
|
return date.toLocaleDateString("en-US", {
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ChartTooltip
|
||||||
|
cursor={false}
|
||||||
|
defaultIndex={isMobile ? -1 : 10}
|
||||||
|
content={
|
||||||
|
<ChartTooltipContent
|
||||||
|
labelFormatter={(value) => {
|
||||||
|
return new Date(value).toLocaleDateString("en-US", {
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
indicator="dot"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
dataKey="mobile"
|
||||||
|
type="natural"
|
||||||
|
fill="url(#fillMobile)"
|
||||||
|
stroke="var(--color-mobile)"
|
||||||
|
stackId="a"
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
dataKey="desktop"
|
||||||
|
type="natural"
|
||||||
|
fill="url(#fillDesktop)"
|
||||||
|
stroke="var(--color-desktop)"
|
||||||
|
stackId="a"
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ChartContainer>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
807
apps/v4/app/(examples)/dashboard/components/data-table.tsx
Normal file
@@ -0,0 +1,807 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import {
|
||||||
|
DndContext,
|
||||||
|
KeyboardSensor,
|
||||||
|
MouseSensor,
|
||||||
|
TouchSensor,
|
||||||
|
closestCenter,
|
||||||
|
useSensor,
|
||||||
|
useSensors,
|
||||||
|
type DragEndEvent,
|
||||||
|
type UniqueIdentifier,
|
||||||
|
} from "@dnd-kit/core"
|
||||||
|
import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
|
||||||
|
import {
|
||||||
|
SortableContext,
|
||||||
|
arrayMove,
|
||||||
|
useSortable,
|
||||||
|
verticalListSortingStrategy,
|
||||||
|
} from "@dnd-kit/sortable"
|
||||||
|
import { CSS } from "@dnd-kit/utilities"
|
||||||
|
import {
|
||||||
|
IconChevronDown,
|
||||||
|
IconChevronLeft,
|
||||||
|
IconChevronRight,
|
||||||
|
IconChevronsLeft,
|
||||||
|
IconChevronsRight,
|
||||||
|
IconCircleCheckFilled,
|
||||||
|
IconDotsVertical,
|
||||||
|
IconGripVertical,
|
||||||
|
IconLayoutColumns,
|
||||||
|
IconLoader,
|
||||||
|
IconPlus,
|
||||||
|
IconTrendingUp,
|
||||||
|
} from "@tabler/icons-react"
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
Row,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFacetedRowModel,
|
||||||
|
getFacetedUniqueValues,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useReactTable,
|
||||||
|
} from "@tanstack/react-table"
|
||||||
|
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile"
|
||||||
|
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
import {
|
||||||
|
ChartConfig,
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
} from "@/registry/new-york-v4/ui/chart"
|
||||||
|
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
DrawerClose,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerDescription,
|
||||||
|
DrawerFooter,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerTitle,
|
||||||
|
DrawerTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/drawer"
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||||
|
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||||
|
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/registry/new-york-v4/ui/select"
|
||||||
|
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/registry/new-york-v4/ui/table"
|
||||||
|
import {
|
||||||
|
Tabs,
|
||||||
|
TabsContent,
|
||||||
|
TabsList,
|
||||||
|
TabsTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/tabs"
|
||||||
|
|
||||||
|
export const schema = z.object({
|
||||||
|
id: z.number(),
|
||||||
|
header: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
status: z.string(),
|
||||||
|
target: z.string(),
|
||||||
|
limit: z.string(),
|
||||||
|
reviewer: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a separate component for the drag handle
|
||||||
|
function DragHandle({ id }: { id: number }) {
|
||||||
|
const { attributes, listeners } = useSortable({
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="text-muted-foreground size-7 hover:bg-transparent"
|
||||||
|
>
|
||||||
|
<IconGripVertical className="text-muted-foreground size-3" />
|
||||||
|
<span className="sr-only">Drag to reorder</span>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||||
|
{
|
||||||
|
id: "drag",
|
||||||
|
header: () => null,
|
||||||
|
cell: ({ row }) => <DragHandle id={row.original.id} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "select",
|
||||||
|
header: ({ table }) => (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
table.getIsAllPageRowsSelected() ||
|
||||||
|
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||||
|
aria-label="Select all"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<Checkbox
|
||||||
|
checked={row.getIsSelected()}
|
||||||
|
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||||
|
aria-label="Select row"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "header",
|
||||||
|
header: "Header",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return <TableCellViewer item={row.original} />
|
||||||
|
},
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "type",
|
||||||
|
header: "Section Type",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="w-32">
|
||||||
|
<Badge variant="outline" className="text-muted-foreground px-1.5">
|
||||||
|
{row.original.type}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "status",
|
||||||
|
header: "Status",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<Badge variant="outline" className="text-muted-foreground px-1.5">
|
||||||
|
{row.original.status === "Done" ? (
|
||||||
|
<IconCircleCheckFilled className="fill-green-500 dark:fill-green-400" />
|
||||||
|
) : (
|
||||||
|
<IconLoader />
|
||||||
|
)}
|
||||||
|
{row.original.status}
|
||||||
|
</Badge>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "target",
|
||||||
|
header: () => <div className="w-full text-right">Target</div>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
||||||
|
loading: `Saving ${row.original.header}`,
|
||||||
|
success: "Done",
|
||||||
|
error: "Error",
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label htmlFor={`${row.original.id}-target`} className="sr-only">
|
||||||
|
Target
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
className="hover:bg-input/30 focus-visible:bg-background dark:hover:bg-input/30 dark:focus-visible:bg-input/30 h-8 w-16 border-transparent bg-transparent text-right shadow-none focus-visible:border dark:bg-transparent"
|
||||||
|
defaultValue={row.original.target}
|
||||||
|
id={`${row.original.id}-target`}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "limit",
|
||||||
|
header: () => <div className="w-full text-right">Limit</div>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
||||||
|
loading: `Saving ${row.original.header}`,
|
||||||
|
success: "Done",
|
||||||
|
error: "Error",
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label htmlFor={`${row.original.id}-limit`} className="sr-only">
|
||||||
|
Limit
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
className="hover:bg-input/30 focus-visible:bg-background dark:hover:bg-input/30 dark:focus-visible:bg-input/30 h-8 w-16 border-transparent bg-transparent text-right shadow-none focus-visible:border dark:bg-transparent"
|
||||||
|
defaultValue={row.original.limit}
|
||||||
|
id={`${row.original.id}-limit`}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "reviewer",
|
||||||
|
header: "Reviewer",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const isAssigned = row.original.reviewer !== "Assign reviewer"
|
||||||
|
|
||||||
|
if (isAssigned) {
|
||||||
|
return row.original.reviewer
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Label htmlFor={`${row.original.id}-reviewer`} className="sr-only">
|
||||||
|
Reviewer
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger
|
||||||
|
className="w-38 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate"
|
||||||
|
size="sm"
|
||||||
|
id={`${row.original.id}-reviewer`}
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Assign reviewer" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent align="end">
|
||||||
|
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
|
||||||
|
<SelectItem value="Jamik Tashpulatov">
|
||||||
|
Jamik Tashpulatov
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
cell: () => (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="data-[state=open]:bg-muted text-muted-foreground flex size-8"
|
||||||
|
size="icon"
|
||||||
|
>
|
||||||
|
<IconDotsVertical />
|
||||||
|
<span className="sr-only">Open menu</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-32">
|
||||||
|
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Make a copy</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Favorite</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {
|
||||||
|
const { transform, transition, setNodeRef, isDragging } = useSortable({
|
||||||
|
id: row.original.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
data-state={row.getIsSelected() && "selected"}
|
||||||
|
data-dragging={isDragging}
|
||||||
|
ref={setNodeRef}
|
||||||
|
className="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
|
||||||
|
style={{
|
||||||
|
transform: CSS.Transform.toString(transform),
|
||||||
|
transition: transition,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DataTable({
|
||||||
|
data: initialData,
|
||||||
|
}: {
|
||||||
|
data: z.infer<typeof schema>[]
|
||||||
|
}) {
|
||||||
|
const [data, setData] = React.useState(() => initialData)
|
||||||
|
const [rowSelection, setRowSelection] = React.useState({})
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
React.useState<VisibilityState>({})
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||||
|
const [pagination, setPagination] = React.useState({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
})
|
||||||
|
const sortableId = React.useId()
|
||||||
|
const sensors = useSensors(
|
||||||
|
useSensor(MouseSensor, {}),
|
||||||
|
useSensor(TouchSensor, {}),
|
||||||
|
useSensor(KeyboardSensor, {})
|
||||||
|
)
|
||||||
|
|
||||||
|
const dataIds = React.useMemo<UniqueIdentifier[]>(
|
||||||
|
() => data?.map(({ id }) => id) || [],
|
||||||
|
[data]
|
||||||
|
)
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
columnVisibility,
|
||||||
|
rowSelection,
|
||||||
|
columnFilters,
|
||||||
|
pagination,
|
||||||
|
},
|
||||||
|
getRowId: (row) => row.id.toString(),
|
||||||
|
enableRowSelection: true,
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
onColumnFiltersChange: setColumnFilters,
|
||||||
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFacetedRowModel: getFacetedRowModel(),
|
||||||
|
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleDragEnd(event: DragEndEvent) {
|
||||||
|
const { active, over } = event
|
||||||
|
if (active && over && active.id !== over.id) {
|
||||||
|
setData((data) => {
|
||||||
|
const oldIndex = dataIds.indexOf(active.id)
|
||||||
|
const newIndex = dataIds.indexOf(over.id)
|
||||||
|
return arrayMove(data, oldIndex, newIndex)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs
|
||||||
|
defaultValue="outline"
|
||||||
|
className="w-full flex-col justify-start gap-6"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between px-4 lg:px-6">
|
||||||
|
<Label htmlFor="view-selector" className="sr-only">
|
||||||
|
View
|
||||||
|
</Label>
|
||||||
|
<Select defaultValue="outline">
|
||||||
|
<SelectTrigger
|
||||||
|
className="flex w-fit @4xl/main:hidden"
|
||||||
|
size="sm"
|
||||||
|
id="view-selector"
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Select a view" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="outline">Outline</SelectItem>
|
||||||
|
<SelectItem value="past-performance">Past Performance</SelectItem>
|
||||||
|
<SelectItem value="key-personnel">Key Personnel</SelectItem>
|
||||||
|
<SelectItem value="focus-documents">Focus Documents</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<TabsList className="**:data-[slot=badge]:bg-muted-foreground/30 hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:px-1 @4xl/main:flex">
|
||||||
|
<TabsTrigger value="outline">Outline</TabsTrigger>
|
||||||
|
<TabsTrigger value="past-performance">
|
||||||
|
Past Performance <Badge variant="secondary">3</Badge>
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="key-personnel">
|
||||||
|
Key Personnel <Badge variant="secondary">2</Badge>
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="focus-documents">Focus Documents</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<IconLayoutColumns />
|
||||||
|
<span className="hidden lg:inline">Customize Columns</span>
|
||||||
|
<span className="lg:hidden">Columns</span>
|
||||||
|
<IconChevronDown />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-56">
|
||||||
|
{table
|
||||||
|
.getAllColumns()
|
||||||
|
.filter(
|
||||||
|
(column) =>
|
||||||
|
typeof column.accessorFn !== "undefined" &&
|
||||||
|
column.getCanHide()
|
||||||
|
)
|
||||||
|
.map((column) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
key={column.id}
|
||||||
|
className="capitalize"
|
||||||
|
checked={column.getIsVisible()}
|
||||||
|
onCheckedChange={(value) =>
|
||||||
|
column.toggleVisibility(!!value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{column.id}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<IconPlus />
|
||||||
|
<span className="hidden lg:inline">Add Section</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<TabsContent
|
||||||
|
value="outline"
|
||||||
|
className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6"
|
||||||
|
>
|
||||||
|
<div className="overflow-hidden rounded-lg border">
|
||||||
|
<DndContext
|
||||||
|
collisionDetection={closestCenter}
|
||||||
|
modifiers={[restrictToVerticalAxis]}
|
||||||
|
onDragEnd={handleDragEnd}
|
||||||
|
sensors={sensors}
|
||||||
|
id={sortableId}
|
||||||
|
>
|
||||||
|
<Table>
|
||||||
|
<TableHeader className="bg-muted sticky top-0 z-10">
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<TableHead key={header.id} colSpan={header.colSpan}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody className="**:data-[slot=table-cell]:first:w-8">
|
||||||
|
{table.getRowModel().rows?.length ? (
|
||||||
|
<SortableContext
|
||||||
|
items={dataIds}
|
||||||
|
strategy={verticalListSortingStrategy}
|
||||||
|
>
|
||||||
|
{table.getRowModel().rows.map((row) => (
|
||||||
|
<DraggableRow key={row.id} row={row} />
|
||||||
|
))}
|
||||||
|
</SortableContext>
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={columns.length}
|
||||||
|
className="h-24 text-center"
|
||||||
|
>
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</DndContext>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between px-4">
|
||||||
|
<div className="text-muted-foreground hidden flex-1 text-sm lg:flex">
|
||||||
|
{table.getFilteredSelectedRowModel().rows.length} of{" "}
|
||||||
|
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full items-center gap-8 lg:w-fit">
|
||||||
|
<div className="hidden items-center gap-2 lg:flex">
|
||||||
|
<Label htmlFor="rows-per-page" className="text-sm font-medium">
|
||||||
|
Rows per page
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={`${table.getState().pagination.pageSize}`}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
table.setPageSize(Number(value))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger size="sm" className="w-20" id="rows-per-page">
|
||||||
|
<SelectValue
|
||||||
|
placeholder={table.getState().pagination.pageSize}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent side="top">
|
||||||
|
{[10, 20, 30, 40, 50].map((pageSize) => (
|
||||||
|
<SelectItem key={pageSize} value={`${pageSize}`}>
|
||||||
|
{pageSize}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-fit items-center justify-center text-sm font-medium">
|
||||||
|
Page {table.getState().pagination.pageIndex + 1} of{" "}
|
||||||
|
{table.getPageCount()}
|
||||||
|
</div>
|
||||||
|
<div className="ml-auto flex items-center gap-2 lg:ml-0">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="hidden h-8 w-8 p-0 lg:flex"
|
||||||
|
onClick={() => table.setPageIndex(0)}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to first page</span>
|
||||||
|
<IconChevronsLeft />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="size-8"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to previous page</span>
|
||||||
|
<IconChevronLeft />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="size-8"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to next page</span>
|
||||||
|
<IconChevronRight />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="hidden size-8 lg:flex"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to last page</span>
|
||||||
|
<IconChevronsRight />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent
|
||||||
|
value="past-performance"
|
||||||
|
className="flex flex-col px-4 lg:px-6"
|
||||||
|
>
|
||||||
|
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="key-personnel" className="flex flex-col px-4 lg:px-6">
|
||||||
|
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent
|
||||||
|
value="focus-documents"
|
||||||
|
className="flex flex-col px-4 lg:px-6"
|
||||||
|
>
|
||||||
|
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartData = [
|
||||||
|
{ month: "January", desktop: 186, mobile: 80 },
|
||||||
|
{ month: "February", desktop: 305, mobile: 200 },
|
||||||
|
{ month: "March", desktop: 237, mobile: 120 },
|
||||||
|
{ month: "April", desktop: 73, mobile: 190 },
|
||||||
|
{ month: "May", desktop: 209, mobile: 130 },
|
||||||
|
{ month: "June", desktop: 214, mobile: 140 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
desktop: {
|
||||||
|
label: "Desktop",
|
||||||
|
color: "var(--primary)",
|
||||||
|
},
|
||||||
|
mobile: {
|
||||||
|
label: "Mobile",
|
||||||
|
color: "var(--primary)",
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig
|
||||||
|
|
||||||
|
function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
|
||||||
|
const isMobile = useIsMobile()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer direction={isMobile ? "bottom" : "right"}>
|
||||||
|
<DrawerTrigger asChild>
|
||||||
|
<Button variant="link" className="text-foreground w-fit px-0 text-left">
|
||||||
|
{item.header}
|
||||||
|
</Button>
|
||||||
|
</DrawerTrigger>
|
||||||
|
<DrawerContent>
|
||||||
|
<DrawerHeader className="gap-1">
|
||||||
|
<DrawerTitle>{item.header}</DrawerTitle>
|
||||||
|
<DrawerDescription>
|
||||||
|
Showing total visitors for the last 6 months
|
||||||
|
</DrawerDescription>
|
||||||
|
</DrawerHeader>
|
||||||
|
<div className="flex flex-col gap-4 overflow-y-auto px-4 text-sm">
|
||||||
|
{!isMobile && (
|
||||||
|
<>
|
||||||
|
<ChartContainer config={chartConfig}>
|
||||||
|
<AreaChart
|
||||||
|
accessibilityLayer
|
||||||
|
data={chartData}
|
||||||
|
margin={{
|
||||||
|
left: 0,
|
||||||
|
right: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CartesianGrid vertical={false} />
|
||||||
|
<XAxis
|
||||||
|
dataKey="month"
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={false}
|
||||||
|
tickMargin={8}
|
||||||
|
tickFormatter={(value) => value.slice(0, 3)}
|
||||||
|
hide
|
||||||
|
/>
|
||||||
|
<ChartTooltip
|
||||||
|
cursor={false}
|
||||||
|
content={<ChartTooltipContent indicator="dot" />}
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
dataKey="mobile"
|
||||||
|
type="natural"
|
||||||
|
fill="var(--color-mobile)"
|
||||||
|
fillOpacity={0.6}
|
||||||
|
stroke="var(--color-mobile)"
|
||||||
|
stackId="a"
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
dataKey="desktop"
|
||||||
|
type="natural"
|
||||||
|
fill="var(--color-desktop)"
|
||||||
|
fillOpacity={0.4}
|
||||||
|
stroke="var(--color-desktop)"
|
||||||
|
stackId="a"
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ChartContainer>
|
||||||
|
<Separator />
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<div className="flex gap-2 leading-none font-medium">
|
||||||
|
Trending up by 5.2% this month{" "}
|
||||||
|
<IconTrendingUp className="size-4" />
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
Showing total visitors for the last 6 months. This is just
|
||||||
|
some random text to test the layout. It spans multiple lines
|
||||||
|
and should wrap around.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<form className="flex flex-col gap-4">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="header">Header</Label>
|
||||||
|
<Input id="header" defaultValue={item.header} />
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="type">Type</Label>
|
||||||
|
<Select defaultValue={item.type}>
|
||||||
|
<SelectTrigger id="type" className="w-full">
|
||||||
|
<SelectValue placeholder="Select a type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Table of Contents">
|
||||||
|
Table of Contents
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Executive Summary">
|
||||||
|
Executive Summary
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Technical Approach">
|
||||||
|
Technical Approach
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Design">Design</SelectItem>
|
||||||
|
<SelectItem value="Capabilities">Capabilities</SelectItem>
|
||||||
|
<SelectItem value="Focus Documents">
|
||||||
|
Focus Documents
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Narrative">Narrative</SelectItem>
|
||||||
|
<SelectItem value="Cover Page">Cover Page</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="status">Status</Label>
|
||||||
|
<Select defaultValue={item.status}>
|
||||||
|
<SelectTrigger id="status" className="w-full">
|
||||||
|
<SelectValue placeholder="Select a status" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Done">Done</SelectItem>
|
||||||
|
<SelectItem value="In Progress">In Progress</SelectItem>
|
||||||
|
<SelectItem value="Not Started">Not Started</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="target">Target</Label>
|
||||||
|
<Input id="target" defaultValue={item.target} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="limit">Limit</Label>
|
||||||
|
<Input id="limit" defaultValue={item.limit} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="reviewer">Reviewer</Label>
|
||||||
|
<Select defaultValue={item.reviewer}>
|
||||||
|
<SelectTrigger id="reviewer" className="w-full">
|
||||||
|
<SelectValue placeholder="Select a reviewer" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
|
||||||
|
<SelectItem value="Jamik Tashpulatov">
|
||||||
|
Jamik Tashpulatov
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Emily Whalen">Emily Whalen</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<DrawerFooter>
|
||||||
|
<Button>Submit</Button>
|
||||||
|
<DrawerClose asChild>
|
||||||
|
<Button variant="outline">Done</Button>
|
||||||
|
</DrawerClose>
|
||||||
|
</DrawerFooter>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
27
apps/v4/app/(examples)/dashboard/components/mode-toggle.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { IconBrightness } from "@tabler/icons-react"
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
|
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
|
||||||
|
export function ModeToggle() {
|
||||||
|
const { setTheme, resolvedTheme } = useTheme()
|
||||||
|
|
||||||
|
const toggleTheme = React.useCallback(() => {
|
||||||
|
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||||
|
}, [resolvedTheme, setTheme])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="icon"
|
||||||
|
className="group/toggle size-8"
|
||||||
|
onClick={toggleTheme}
|
||||||
|
>
|
||||||
|
<IconBrightness />
|
||||||
|
<span className="sr-only">Toggle theme</span>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import {
|
||||||
|
IconDots,
|
||||||
|
IconFolder,
|
||||||
|
IconShare3,
|
||||||
|
IconTrash,
|
||||||
|
type Icon,
|
||||||
|
} from "@tabler/icons-react"
|
||||||
|
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||||
|
import {
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupLabel,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuAction,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
useSidebar,
|
||||||
|
} from "@/registry/new-york-v4/ui/sidebar"
|
||||||
|
|
||||||
|
export function NavDocuments({
|
||||||
|
items,
|
||||||
|
}: {
|
||||||
|
items: {
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
icon: Icon
|
||||||
|
}[]
|
||||||
|
}) {
|
||||||
|
const { isMobile } = useSidebar()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||||
|
<SidebarGroupLabel>Documents</SidebarGroupLabel>
|
||||||
|
<SidebarMenu>
|
||||||
|
{items.map((item) => (
|
||||||
|
<SidebarMenuItem key={item.name}>
|
||||||
|
<SidebarMenuButton asChild>
|
||||||
|
<a href={item.url}>
|
||||||
|
<item.icon />
|
||||||
|
<span>{item.name}</span>
|
||||||
|
</a>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<SidebarMenuAction
|
||||||
|
showOnHover
|
||||||
|
className="data-[state=open]:bg-accent rounded-sm"
|
||||||
|
>
|
||||||
|
<IconDots />
|
||||||
|
<span className="sr-only">More</span>
|
||||||
|
</SidebarMenuAction>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
className="w-24 rounded-lg"
|
||||||
|
side={isMobile ? "bottom" : "right"}
|
||||||
|
align={isMobile ? "end" : "start"}
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<IconFolder />
|
||||||
|
<span>Open</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<IconShare3 />
|
||||||
|
<span>Share</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem variant="destructive">
|
||||||
|
<IconTrash />
|
||||||
|
<span>Delete</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
))}
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton className="text-sidebar-foreground/70">
|
||||||
|
<IconDots className="text-sidebar-foreground/70" />
|
||||||
|
<span>More</span>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
58
apps/v4/app/(examples)/dashboard/components/nav-main.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"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) => (
|
||||||
|
<SidebarMenuItem key={item.title}>
|
||||||
|
<SidebarMenuButton tooltip={item.title}>
|
||||||
|
{item.icon && <item.icon />}
|
||||||
|
<span>{item.title}</span>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroupContent>
|
||||||
|
</SidebarGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
"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>
|
||||||
|
))}
|
||||||
|
<SidebarMenuItem 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>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroupContent>
|
||||||
|
</SidebarGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
110
apps/v4/app/(examples)/dashboard/components/nav-user.tsx
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
"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>
|
||||||
|
)
|
||||||
|
}
|
||||||
102
apps/v4/app/(examples)/dashboard/components/section-cards.tsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
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"
|
||||||
|
|
||||||
|
export function SectionCards() {
|
||||||
|
return (
|
||||||
|
<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-1.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>
|
||||||
|
)
|
||||||
|
}
|
||||||
34
apps/v4/app/(examples)/dashboard/components/site-header.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
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/components/mode-toggle"
|
||||||
|
import { ThemeSelector } from "@/app/(examples)/dashboard/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"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
className="dark:text-foreground"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
<ThemeSelector />
|
||||||
|
<ModeToggle />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
103
apps/v4/app/(examples)/dashboard/components/theme-selector.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useThemeConfig } from "@/components/active-theme"
|
||||||
|
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/registry/new-york-v4/ui/select"
|
||||||
|
|
||||||
|
const DEFAULT_THEMES = [
|
||||||
|
{
|
||||||
|
name: "Default",
|
||||||
|
value: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Blue",
|
||||||
|
value: "blue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Green",
|
||||||
|
value: "green",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Amber",
|
||||||
|
value: "amber",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const SCALED_THEMES = [
|
||||||
|
{
|
||||||
|
name: "Default",
|
||||||
|
value: "default-scaled",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Blue",
|
||||||
|
value: "blue-scaled",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const MONO_THEMES = [
|
||||||
|
{
|
||||||
|
name: "Mono",
|
||||||
|
value: "mono-scaled",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function ThemeSelector() {
|
||||||
|
const { activeTheme, setActiveTheme } = useThemeConfig()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Label htmlFor="theme-selector" className="sr-only">
|
||||||
|
Theme
|
||||||
|
</Label>
|
||||||
|
<Select value={activeTheme} onValueChange={setActiveTheme}>
|
||||||
|
<SelectTrigger
|
||||||
|
id="theme-selector"
|
||||||
|
size="sm"
|
||||||
|
className="justify-start *:data-[slot=select-value]:w-12"
|
||||||
|
>
|
||||||
|
<span className="text-muted-foreground hidden sm:block">
|
||||||
|
Select a theme:
|
||||||
|
</span>
|
||||||
|
<span className="text-muted-foreground block sm:hidden">Theme</span>
|
||||||
|
<SelectValue placeholder="Select a theme" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent align="end">
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Default</SelectLabel>
|
||||||
|
{DEFAULT_THEMES.map((theme) => (
|
||||||
|
<SelectItem key={theme.name} value={theme.value}>
|
||||||
|
{theme.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
<SelectSeparator />
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Scaled</SelectLabel>
|
||||||
|
{SCALED_THEMES.map((theme) => (
|
||||||
|
<SelectItem key={theme.name} value={theme.value}>
|
||||||
|
{theme.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Monospaced</SelectLabel>
|
||||||
|
{MONO_THEMES.map((theme) => (
|
||||||
|
<SelectItem key={theme.name} value={theme.value}>
|
||||||
|
{theme.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
614
apps/v4/app/(examples)/dashboard/data.json
Normal file
@@ -0,0 +1,614 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"header": "Cover page",
|
||||||
|
"type": "Cover page",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "18",
|
||||||
|
"limit": "5",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"header": "Table of contents",
|
||||||
|
"type": "Table of contents",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "29",
|
||||||
|
"limit": "24",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"header": "Executive summary",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "10",
|
||||||
|
"limit": "13",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"header": "Technical approach",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "27",
|
||||||
|
"limit": "23",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"header": "Design",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "2",
|
||||||
|
"limit": "16",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"header": "Capabilities",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "20",
|
||||||
|
"limit": "8",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"header": "Integration with existing systems",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "19",
|
||||||
|
"limit": "21",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"header": "Innovation and Advantages",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "25",
|
||||||
|
"limit": "26",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"header": "Overview of EMR's Innovative Solutions",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "7",
|
||||||
|
"limit": "23",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"header": "Advanced Algorithms and Machine Learning",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "30",
|
||||||
|
"limit": "28",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"header": "Adaptive Communication Protocols",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "9",
|
||||||
|
"limit": "31",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"header": "Advantages Over Current Technologies",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "12",
|
||||||
|
"limit": "0",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"header": "Past Performance",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "22",
|
||||||
|
"limit": "33",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"header": "Customer Feedback and Satisfaction Levels",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "15",
|
||||||
|
"limit": "34",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"header": "Implementation Challenges and Solutions",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "3",
|
||||||
|
"limit": "35",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"header": "Security Measures and Data Protection Policies",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "6",
|
||||||
|
"limit": "36",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"header": "Scalability and Future Proofing",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "4",
|
||||||
|
"limit": "37",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"header": "Cost-Benefit Analysis",
|
||||||
|
"type": "Plain language",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "14",
|
||||||
|
"limit": "38",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"header": "User Training and Onboarding Experience",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "17",
|
||||||
|
"limit": "39",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"header": "Future Development Roadmap",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "11",
|
||||||
|
"limit": "40",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"header": "System Architecture Overview",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "24",
|
||||||
|
"limit": "18",
|
||||||
|
"reviewer": "Maya Johnson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"header": "Risk Management Plan",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "15",
|
||||||
|
"limit": "22",
|
||||||
|
"reviewer": "Carlos Rodriguez"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"header": "Compliance Documentation",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "31",
|
||||||
|
"limit": "27",
|
||||||
|
"reviewer": "Sarah Chen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 24,
|
||||||
|
"header": "API Documentation",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "8",
|
||||||
|
"limit": "12",
|
||||||
|
"reviewer": "Raj Patel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"header": "User Interface Mockups",
|
||||||
|
"type": "Visual",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "19",
|
||||||
|
"limit": "25",
|
||||||
|
"reviewer": "Leila Ahmadi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26,
|
||||||
|
"header": "Database Schema",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "22",
|
||||||
|
"limit": "20",
|
||||||
|
"reviewer": "Thomas Wilson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"header": "Testing Methodology",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "17",
|
||||||
|
"limit": "14",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 28,
|
||||||
|
"header": "Deployment Strategy",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "26",
|
||||||
|
"limit": "30",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 29,
|
||||||
|
"header": "Budget Breakdown",
|
||||||
|
"type": "Financial",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "13",
|
||||||
|
"limit": "16",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 30,
|
||||||
|
"header": "Market Analysis",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "29",
|
||||||
|
"limit": "32",
|
||||||
|
"reviewer": "Sophia Martinez"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 31,
|
||||||
|
"header": "Competitor Comparison",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "21",
|
||||||
|
"limit": "19",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 32,
|
||||||
|
"header": "Maintenance Plan",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "16",
|
||||||
|
"limit": "23",
|
||||||
|
"reviewer": "Alex Thompson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 33,
|
||||||
|
"header": "User Personas",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "27",
|
||||||
|
"limit": "24",
|
||||||
|
"reviewer": "Nina Patel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 34,
|
||||||
|
"header": "Accessibility Compliance",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "18",
|
||||||
|
"limit": "21",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 35,
|
||||||
|
"header": "Performance Metrics",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "23",
|
||||||
|
"limit": "26",
|
||||||
|
"reviewer": "David Kim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 36,
|
||||||
|
"header": "Disaster Recovery Plan",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "14",
|
||||||
|
"limit": "17",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 37,
|
||||||
|
"header": "Third-party Integrations",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "25",
|
||||||
|
"limit": "28",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 38,
|
||||||
|
"header": "User Feedback Summary",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "20",
|
||||||
|
"limit": "15",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 39,
|
||||||
|
"header": "Localization Strategy",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "12",
|
||||||
|
"limit": "19",
|
||||||
|
"reviewer": "Maria Garcia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 40,
|
||||||
|
"header": "Mobile Compatibility",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "28",
|
||||||
|
"limit": "31",
|
||||||
|
"reviewer": "James Wilson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 41,
|
||||||
|
"header": "Data Migration Plan",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "19",
|
||||||
|
"limit": "22",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 42,
|
||||||
|
"header": "Quality Assurance Protocols",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "30",
|
||||||
|
"limit": "33",
|
||||||
|
"reviewer": "Priya Singh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 43,
|
||||||
|
"header": "Stakeholder Analysis",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "11",
|
||||||
|
"limit": "14",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 44,
|
||||||
|
"header": "Environmental Impact Assessment",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "24",
|
||||||
|
"limit": "27",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 45,
|
||||||
|
"header": "Intellectual Property Rights",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "17",
|
||||||
|
"limit": "20",
|
||||||
|
"reviewer": "Sarah Johnson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 46,
|
||||||
|
"header": "Customer Support Framework",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "22",
|
||||||
|
"limit": "25",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 47,
|
||||||
|
"header": "Version Control Strategy",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "15",
|
||||||
|
"limit": "18",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 48,
|
||||||
|
"header": "Continuous Integration Pipeline",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "26",
|
||||||
|
"limit": "29",
|
||||||
|
"reviewer": "Michael Chen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 49,
|
||||||
|
"header": "Regulatory Compliance",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "13",
|
||||||
|
"limit": "16",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 50,
|
||||||
|
"header": "User Authentication System",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "28",
|
||||||
|
"limit": "31",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 51,
|
||||||
|
"header": "Data Analytics Framework",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "21",
|
||||||
|
"limit": "24",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 52,
|
||||||
|
"header": "Cloud Infrastructure",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "16",
|
||||||
|
"limit": "19",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 53,
|
||||||
|
"header": "Network Security Measures",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "29",
|
||||||
|
"limit": "32",
|
||||||
|
"reviewer": "Lisa Wong"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 54,
|
||||||
|
"header": "Project Timeline",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "14",
|
||||||
|
"limit": "17",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 55,
|
||||||
|
"header": "Resource Allocation",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "27",
|
||||||
|
"limit": "30",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 56,
|
||||||
|
"header": "Team Structure and Roles",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "20",
|
||||||
|
"limit": "23",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 57,
|
||||||
|
"header": "Communication Protocols",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "15",
|
||||||
|
"limit": "18",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 58,
|
||||||
|
"header": "Success Metrics",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "30",
|
||||||
|
"limit": "33",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 59,
|
||||||
|
"header": "Internationalization Support",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "23",
|
||||||
|
"limit": "26",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 60,
|
||||||
|
"header": "Backup and Recovery Procedures",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "18",
|
||||||
|
"limit": "21",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 61,
|
||||||
|
"header": "Monitoring and Alerting System",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "25",
|
||||||
|
"limit": "28",
|
||||||
|
"reviewer": "Daniel Park"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 62,
|
||||||
|
"header": "Code Review Guidelines",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "12",
|
||||||
|
"limit": "15",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 63,
|
||||||
|
"header": "Documentation Standards",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "27",
|
||||||
|
"limit": "30",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 64,
|
||||||
|
"header": "Release Management Process",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "22",
|
||||||
|
"limit": "25",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 65,
|
||||||
|
"header": "Feature Prioritization Matrix",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "19",
|
||||||
|
"limit": "22",
|
||||||
|
"reviewer": "Emma Davis"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 66,
|
||||||
|
"header": "Technical Debt Assessment",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "24",
|
||||||
|
"limit": "27",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 67,
|
||||||
|
"header": "Capacity Planning",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "21",
|
||||||
|
"limit": "24",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 68,
|
||||||
|
"header": "Service Level Agreements",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "26",
|
||||||
|
"limit": "29",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
}
|
||||||
|
]
|
||||||
36
apps/v4/app/(examples)/dashboard/layout.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { cookies } from "next/headers"
|
||||||
|
|
||||||
|
import {
|
||||||
|
SidebarInset,
|
||||||
|
SidebarProvider,
|
||||||
|
} from "@/registry/new-york-v4/ui/sidebar"
|
||||||
|
import { AppSidebar } from "@/app/(examples)/dashboard/components/app-sidebar"
|
||||||
|
import { SiteHeader } from "@/app/(examples)/dashboard/components/site-header"
|
||||||
|
|
||||||
|
import "@/app/(examples)/dashboard/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>
|
||||||
|
)
|
||||||
|
}
|
||||||
18
apps/v4/app/(examples)/dashboard/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { ChartAreaInteractive } from "@/app/(examples)/dashboard/components/chart-area-interactive"
|
||||||
|
import { DataTable } from "@/app/(examples)/dashboard/components/data-table"
|
||||||
|
import { SectionCards } from "@/app/(examples)/dashboard/components/section-cards"
|
||||||
|
import data from "@/app/(examples)/dashboard/data.json"
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
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">
|
||||||
|
<SectionCards />
|
||||||
|
<div className="px-4 lg:px-6">
|
||||||
|
<ChartAreaInteractive />
|
||||||
|
</div>
|
||||||
|
<DataTable data={data} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
105
apps/v4/app/(examples)/dashboard/theme.css
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
body {
|
||||||
|
@apply overscroll-none bg-transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--font-sans: var(--font-inter);
|
||||||
|
--header-height: calc(var(--spacing) * 12 + 1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-scaled {
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
--radius: 0.6rem;
|
||||||
|
--text-lg: 1.05rem;
|
||||||
|
--text-base: 0.85rem;
|
||||||
|
--text-sm: 0.8rem;
|
||||||
|
--spacing: 0.222222rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="card"] {
|
||||||
|
--spacing: 0.16rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="select-trigger"],
|
||||||
|
[data-slot="toggle-group-item"] {
|
||||||
|
--spacing: 0.222222rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-default,
|
||||||
|
.theme-default-scaled {
|
||||||
|
--primary: var(--color-neutral-600);
|
||||||
|
--primary-foreground: var(--color-neutral-50);
|
||||||
|
|
||||||
|
@variant dark {
|
||||||
|
--primary: var(--color-neutral-500);
|
||||||
|
--primary-foreground: var(--color-neutral-50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-blue,
|
||||||
|
.theme-blue-scaled {
|
||||||
|
--primary: var(--color-blue-600);
|
||||||
|
--primary-foreground: var(--color-blue-50);
|
||||||
|
|
||||||
|
@variant dark {
|
||||||
|
--primary: var(--color-blue-500);
|
||||||
|
--primary-foreground: var(--color-blue-50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-green,
|
||||||
|
.theme-green-scaled {
|
||||||
|
--primary: var(--color-lime-600);
|
||||||
|
--primary-foreground: var(--color-lime-50);
|
||||||
|
|
||||||
|
@variant dark {
|
||||||
|
--primary: var(--color-lime-600);
|
||||||
|
--primary-foreground: var(--color-lime-50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-amber,
|
||||||
|
.theme-amber-scaled {
|
||||||
|
--primary: var(--color-amber-600);
|
||||||
|
--primary-foreground: var(--color-amber-50);
|
||||||
|
|
||||||
|
@variant dark {
|
||||||
|
--primary: var(--color-amber-500);
|
||||||
|
--primary-foreground: var(--color-amber-50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-mono,
|
||||||
|
.theme-mono-scaled {
|
||||||
|
--font-sans: var(--font-mono);
|
||||||
|
--primary: var(--color-neutral-600);
|
||||||
|
--primary-foreground: var(--color-neutral-50);
|
||||||
|
|
||||||
|
@variant dark {
|
||||||
|
--primary: var(--color-neutral-500);
|
||||||
|
--primary-foreground: var(--color-neutral-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded-xs,
|
||||||
|
.rounded-sm,
|
||||||
|
.rounded-md,
|
||||||
|
.rounded-lg,
|
||||||
|
.rounded-xl {
|
||||||
|
@apply !rounded-none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow-xs,
|
||||||
|
.shadow-sm,
|
||||||
|
.shadow-md,
|
||||||
|
.shadow-lg,
|
||||||
|
.shadow-xl {
|
||||||
|
@apply !shadow-none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="toggle-group"],
|
||||||
|
[data-slot="toggle-group-item"] {
|
||||||
|
@apply !rounded-none !shadow-none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 25 KiB |
@@ -1,10 +1,13 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@plugin "tailwindcss-animate";
|
@import "tw-animate-css";
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
@import "./themes.css";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
--radius: 0.625rem;
|
||||||
--background: oklch(1 0 0);
|
--background: oklch(1 0 0);
|
||||||
--foreground: oklch(0.145 0 0);
|
--foreground: oklch(0.145 0 0);
|
||||||
--card: oklch(1 0 0);
|
--card: oklch(1 0 0);
|
||||||
@@ -20,7 +23,6 @@
|
|||||||
--accent: oklch(0.97 0 0);
|
--accent: oklch(0.97 0 0);
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
--destructive-foreground: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.922 0 0);
|
--border: oklch(0.922 0 0);
|
||||||
--input: oklch(0.922 0 0);
|
--input: oklch(0.922 0 0);
|
||||||
--ring: oklch(0.708 0 0);
|
--ring: oklch(0.708 0 0);
|
||||||
@@ -29,7 +31,6 @@
|
|||||||
--chart-3: oklch(0.398 0.07 227.392);
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
--radius: 0.625rem;
|
|
||||||
--sidebar: oklch(0.985 0 0);
|
--sidebar: oklch(0.985 0 0);
|
||||||
--sidebar-foreground: oklch(0.145 0 0);
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
@@ -43,22 +44,21 @@
|
|||||||
.dark {
|
.dark {
|
||||||
--background: oklch(0.145 0 0);
|
--background: oklch(0.145 0 0);
|
||||||
--foreground: oklch(0.985 0 0);
|
--foreground: oklch(0.985 0 0);
|
||||||
--card: oklch(0.145 0 0);
|
--card: oklch(0.205 0 0);
|
||||||
--card-foreground: oklch(0.985 0 0);
|
--card-foreground: oklch(0.985 0 0);
|
||||||
--popover: oklch(0.145 0 0);
|
--popover: oklch(0.269 0 0);
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
--primary: oklch(0.985 0 0);
|
--primary: oklch(0.922 0 0);
|
||||||
--primary-foreground: oklch(0.205 0 0);
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
--secondary: oklch(0.269 0 0);
|
--secondary: oklch(0.269 0 0);
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
--muted: oklch(0.269 0 0);
|
--muted: oklch(0.269 0 0);
|
||||||
--muted-foreground: oklch(0.708 0 0);
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
--accent: oklch(0.269 0 0);
|
--accent: oklch(0.371 0 0);
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
--destructive: oklch(0.396 0.141 25.723);
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
--destructive-foreground: oklch(0.637 0.237 25.331);
|
--border: oklch(1 0 0 / 10%);
|
||||||
--border: oklch(0.269 0 0);
|
--input: oklch(1 0 0 / 15%);
|
||||||
--input: oklch(0.269 0 0);
|
|
||||||
--ring: oklch(0.556 0 0);
|
--ring: oklch(0.556 0 0);
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
@@ -71,66 +71,15 @@
|
|||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-accent: oklch(0.269 0 0);
|
--sidebar-accent: oklch(0.269 0 0);
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-border: oklch(0.269 0 0);
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
--sidebar-ring: oklch(0.439 0 0);
|
--sidebar-ring: oklch(0.439 0 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-login-one {
|
@theme inline {
|
||||||
--primary: #ce2a2d;
|
|
||||||
--primary-foreground: #fff;
|
|
||||||
--ring: #ce2a2d9c;
|
|
||||||
--radius: 0rem;
|
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
--radius-lg: var(--radius);
|
--radius-lg: var(--radius);
|
||||||
|
--radius-xl: calc(var(--radius) + 4px);
|
||||||
font-family: var(--font-sans);
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="card"] {
|
|
||||||
border-radius: 0rem;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-login-two {
|
|
||||||
--primary: #035fa8;
|
|
||||||
--primary-foreground: #fff;
|
|
||||||
--ring: #035fa89c;
|
|
||||||
font-family: var(--font-serif);
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-login-three {
|
|
||||||
--primary: #22c55e;
|
|
||||||
--primary-foreground: #000;
|
|
||||||
--ring: #22c55e;
|
|
||||||
--radius: 1.5rem;
|
|
||||||
|
|
||||||
font-family: var(--font-manrope);
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="card"] {
|
|
||||||
@apply shadow-xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="input"] {
|
|
||||||
@apply dark:bg-input;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@theme inline {
|
|
||||||
--font-sans: var(--font-sans);
|
|
||||||
--font-mono: var(--font-mono);
|
|
||||||
--color-background: var(--background);
|
--color-background: var(--background);
|
||||||
--color-foreground: var(--foreground);
|
--color-foreground: var(--foreground);
|
||||||
--color-card: var(--card);
|
--color-card: var(--card);
|
||||||
@@ -146,7 +95,6 @@
|
|||||||
--color-accent: var(--accent);
|
--color-accent: var(--accent);
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
--color-destructive: var(--destructive);
|
--color-destructive: var(--destructive);
|
||||||
--color-destructive-foreground: var(--destructive-foreground);
|
|
||||||
--color-border: var(--border);
|
--color-border: var(--border);
|
||||||
--color-input: var(--input);
|
--color-input: var(--input);
|
||||||
--color-ring: var(--ring);
|
--color-ring: var(--ring);
|
||||||
@@ -155,10 +103,6 @@
|
|||||||
--color-chart-3: var(--chart-3);
|
--color-chart-3: var(--chart-3);
|
||||||
--color-chart-4: var(--chart-4);
|
--color-chart-4: var(--chart-4);
|
||||||
--color-chart-5: var(--chart-5);
|
--color-chart-5: var(--chart-5);
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
|
||||||
--radius-lg: var(--radius);
|
|
||||||
--radius-xl: calc(var(--radius) + 4px);
|
|
||||||
--color-sidebar: var(--sidebar);
|
--color-sidebar: var(--sidebar);
|
||||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
--color-sidebar-primary: var(--sidebar-primary);
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
@@ -167,26 +111,6 @@
|
|||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
--animate-accordion-down: accordion-down 0.2s ease-out;
|
|
||||||
--animate-accordion-up: accordion-up 0.2s ease-out;
|
|
||||||
|
|
||||||
@keyframes accordion-down {
|
|
||||||
from {
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
height: var(--radix-accordion-content-height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes accordion-up {
|
|
||||||
from {
|
|
||||||
height: var(--radix-accordion-content-height);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
@@ -1,23 +1,15 @@
|
|||||||
import type { Metadata, Viewport } from "next"
|
import type { Metadata, Viewport } from "next"
|
||||||
import { Geist_Mono as FontMono, Inter as FontSans } from "next/font/google"
|
import { cookies } from "next/headers"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { fontVariables } from "@/lib/fonts"
|
||||||
import { Analytics } from "@/components/analytics"
|
import { Analytics } from "@/components/analytics"
|
||||||
import { ThemeProvider } from "@/components/theme-provider"
|
import { ThemeProvider } from "@/components/theme-provider"
|
||||||
import { Toaster } from "@/registry/new-york-v4/ui/sonner"
|
import { Toaster } from "@/registry/new-york-v4/ui/sonner"
|
||||||
import { siteConfig } from "@/www/config/site"
|
import { siteConfig } from "@/www/config/site"
|
||||||
|
|
||||||
import "./globals.css"
|
import "./globals.css"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
const fontSans = FontSans({
|
import { ActiveThemeProvider } from "@/components/active-theme"
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-sans",
|
|
||||||
})
|
|
||||||
|
|
||||||
const fontMono = FontMono({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-mono",
|
|
||||||
})
|
|
||||||
|
|
||||||
const META_THEME_COLORS = {
|
const META_THEME_COLORS = {
|
||||||
light: "#ffffff",
|
light: "#ffffff",
|
||||||
@@ -29,7 +21,7 @@ export const metadata: Metadata = {
|
|||||||
default: siteConfig.name,
|
default: siteConfig.name,
|
||||||
template: `%s - ${siteConfig.name}`,
|
template: `%s - ${siteConfig.name}`,
|
||||||
},
|
},
|
||||||
metadataBase: new URL(siteConfig.url),
|
metadataBase: new URL("https://v4.shadcn.com"),
|
||||||
description: siteConfig.description,
|
description: siteConfig.description,
|
||||||
keywords: [
|
keywords: [
|
||||||
"Next.js",
|
"Next.js",
|
||||||
@@ -48,13 +40,13 @@ export const metadata: Metadata = {
|
|||||||
openGraph: {
|
openGraph: {
|
||||||
type: "website",
|
type: "website",
|
||||||
locale: "en_US",
|
locale: "en_US",
|
||||||
url: siteConfig.url,
|
url: "https://v4.shadcn.com",
|
||||||
title: siteConfig.name,
|
title: siteConfig.name,
|
||||||
description: siteConfig.description,
|
description: siteConfig.description,
|
||||||
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,
|
||||||
@@ -65,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: {
|
||||||
@@ -80,11 +72,15 @@ export const viewport: Viewport = {
|
|||||||
themeColor: META_THEME_COLORS.light,
|
themeColor: META_THEME_COLORS.light,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout({
|
export default async function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}>) {
|
}>) {
|
||||||
|
const cookieStore = await cookies()
|
||||||
|
const activeThemeValue = cookieStore.get("active_theme")?.value
|
||||||
|
const isScaled = activeThemeValue?.endsWith("-scaled")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<head>
|
<head>
|
||||||
@@ -103,8 +99,9 @@ export default function RootLayout({
|
|||||||
<body
|
<body
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background overscroll-none font-sans antialiased",
|
"bg-background overscroll-none font-sans antialiased",
|
||||||
fontSans.variable,
|
activeThemeValue ? `theme-${activeThemeValue}` : "",
|
||||||
fontMono.variable
|
isScaled ? "theme-scaled" : "",
|
||||||
|
fontVariables
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
@@ -112,10 +109,13 @@ export default function RootLayout({
|
|||||||
defaultTheme="system"
|
defaultTheme="system"
|
||||||
enableSystem
|
enableSystem
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
|
enableColorScheme
|
||||||
>
|
>
|
||||||
{children}
|
<ActiveThemeProvider initialTheme={activeThemeValue}>
|
||||||
<Toaster />
|
{children}
|
||||||
<Analytics />
|
<Toaster />
|
||||||
|
<Analytics />
|
||||||
|
</ActiveThemeProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
364
apps/v4/app/themes.css
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
.theme-stone {
|
||||||
|
--radius: 0.625rem;
|
||||||
|
--background: oklch(1 0 0);
|
||||||
|
--foreground: oklch(0.147 0.004 49.25);
|
||||||
|
--card: oklch(1 0 0);
|
||||||
|
--card-foreground: oklch(0.147 0.004 49.25);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.147 0.004 49.25);
|
||||||
|
--primary: oklch(0.216 0.006 56.043);
|
||||||
|
--primary-foreground: oklch(0.985 0.001 106.423);
|
||||||
|
--secondary: oklch(0.97 0.001 106.424);
|
||||||
|
--secondary-foreground: oklch(0.216 0.006 56.043);
|
||||||
|
--muted: oklch(0.97 0.001 106.424);
|
||||||
|
--muted-foreground: oklch(0.553 0.013 58.071);
|
||||||
|
--accent: oklch(0.97 0.001 106.424);
|
||||||
|
--accent-foreground: oklch(0.216 0.006 56.043);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.923 0.003 48.717);
|
||||||
|
--input: oklch(0.923 0.003 48.717);
|
||||||
|
--ring: oklch(0.709 0.01 56.259);
|
||||||
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
|
--sidebar: oklch(0.985 0.001 106.423);
|
||||||
|
--sidebar-foreground: oklch(0.147 0.004 49.25);
|
||||||
|
--sidebar-primary: oklch(0.216 0.006 56.043);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
||||||
|
--sidebar-accent: oklch(0.97 0.001 106.424);
|
||||||
|
--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
|
||||||
|
--sidebar-border: oklch(0.923 0.003 48.717);
|
||||||
|
--sidebar-ring: oklch(0.709 0.01 56.259);
|
||||||
|
|
||||||
|
@variant dark {
|
||||||
|
--background: oklch(0.147 0.004 49.25);
|
||||||
|
--foreground: oklch(0.985 0.001 106.423);
|
||||||
|
--card: oklch(0.216 0.006 56.043);
|
||||||
|
--card-foreground: oklch(0.985 0.001 106.423);
|
||||||
|
--popover: oklch(0.216 0.006 56.043);
|
||||||
|
--popover-foreground: oklch(0.985 0.001 106.423);
|
||||||
|
--primary: oklch(0.923 0.003 48.717);
|
||||||
|
--primary-foreground: oklch(0.216 0.006 56.043);
|
||||||
|
--secondary: oklch(0.268 0.007 34.298);
|
||||||
|
--secondary-foreground: oklch(0.985 0.001 106.423);
|
||||||
|
--muted: oklch(0.268 0.007 34.298);
|
||||||
|
--muted-foreground: oklch(0.709 0.01 56.259);
|
||||||
|
--accent: oklch(0.268 0.007 34.298);
|
||||||
|
--accent-foreground: oklch(0.985 0.001 106.423);
|
||||||
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
--input: oklch(1 0 0 / 15%);
|
||||||
|
--ring: oklch(0.553 0.013 58.071);
|
||||||
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
|
--sidebar: oklch(0.216 0.006 56.043);
|
||||||
|
--sidebar-foreground: oklch(0.985 0.001 106.423);
|
||||||
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
||||||
|
--sidebar-accent: oklch(0.268 0.007 34.298);
|
||||||
|
--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
|
||||||
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
|
--sidebar-ring: oklch(0.553 0.013 58.071);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-zinc {
|
||||||
|
--radius: 0.625rem;
|
||||||
|
--background: oklch(1 0 0);
|
||||||
|
--foreground: oklch(0.141 0.005 285.823);
|
||||||
|
--card: oklch(1 0 0);
|
||||||
|
--card-foreground: oklch(0.141 0.005 285.823);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.141 0.005 285.823);
|
||||||
|
--primary: oklch(0.21 0.006 285.885);
|
||||||
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
|
--secondary: oklch(0.967 0.001 286.375);
|
||||||
|
--secondary-foreground: oklch(0.21 0.006 285.885);
|
||||||
|
--muted: oklch(0.967 0.001 286.375);
|
||||||
|
--muted-foreground: oklch(0.552 0.016 285.938);
|
||||||
|
--accent: oklch(0.967 0.001 286.375);
|
||||||
|
--accent-foreground: oklch(0.21 0.006 285.885);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.92 0.004 286.32);
|
||||||
|
--input: oklch(0.92 0.004 286.32);
|
||||||
|
--ring: oklch(0.705 0.015 286.067);
|
||||||
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
|
--sidebar: oklch(0.985 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
||||||
|
--sidebar-primary: oklch(0.21 0.006 285.885);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.967 0.001 286.375);
|
||||||
|
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
||||||
|
--sidebar-border: oklch(0.92 0.004 286.32);
|
||||||
|
--sidebar-ring: oklch(0.705 0.015 286.067);
|
||||||
|
|
||||||
|
@variant dark {
|
||||||
|
--background: oklch(0.141 0.005 285.823);
|
||||||
|
--foreground: oklch(0.985 0 0);
|
||||||
|
--card: oklch(0.21 0.006 285.885);
|
||||||
|
--card-foreground: oklch(0.985 0 0);
|
||||||
|
--popover: oklch(0.21 0.006 285.885);
|
||||||
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
|
--primary: oklch(0.92 0.004 286.32);
|
||||||
|
--primary-foreground: oklch(0.21 0.006 285.885);
|
||||||
|
--secondary: oklch(0.274 0.006 286.033);
|
||||||
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
|
--muted: oklch(0.274 0.006 286.033);
|
||||||
|
--muted-foreground: oklch(0.705 0.015 286.067);
|
||||||
|
--accent: oklch(0.274 0.006 286.033);
|
||||||
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
--input: oklch(1 0 0 / 15%);
|
||||||
|
--ring: oklch(0.552 0.016 285.938);
|
||||||
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
|
--sidebar: oklch(0.21 0.006 285.885);
|
||||||
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.274 0.006 286.033);
|
||||||
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
|
--sidebar-ring: oklch(0.552 0.016 285.938);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-neutral {
|
||||||
|
--radius: 0.625rem;
|
||||||
|
--background: oklch(1 0 0);
|
||||||
|
--foreground: oklch(0.145 0 0);
|
||||||
|
--card: oklch(1 0 0);
|
||||||
|
--card-foreground: oklch(0.145 0 0);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
|
--primary: oklch(0.205 0 0);
|
||||||
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
|
--secondary: oklch(0.97 0 0);
|
||||||
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
|
--muted: oklch(0.97 0 0);
|
||||||
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
|
--accent: oklch(0.97 0 0);
|
||||||
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.922 0 0);
|
||||||
|
--input: oklch(0.922 0 0);
|
||||||
|
--ring: oklch(0.708 0 0);
|
||||||
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
|
--sidebar: oklch(0.985 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.97 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
|
||||||
|
@variant dark {
|
||||||
|
--background: oklch(0.145 0 0);
|
||||||
|
--foreground: oklch(0.985 0 0);
|
||||||
|
--card: oklch(0.205 0 0);
|
||||||
|
--card-foreground: oklch(0.985 0 0);
|
||||||
|
--popover: oklch(0.205 0 0);
|
||||||
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
|
--primary: oklch(0.922 0 0);
|
||||||
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
|
--secondary: oklch(0.269 0 0);
|
||||||
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
|
--muted: oklch(0.269 0 0);
|
||||||
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
|
--accent: oklch(0.269 0 0);
|
||||||
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
--input: oklch(1 0 0 / 15%);
|
||||||
|
--ring: oklch(0.556 0 0);
|
||||||
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
|
--sidebar: oklch(0.205 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.269 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
|
--sidebar-ring: oklch(0.556 0 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-gray {
|
||||||
|
--radius: 0.625rem;
|
||||||
|
--background: oklch(1 0 0);
|
||||||
|
--foreground: oklch(0.13 0.028 261.692);
|
||||||
|
--card: oklch(1 0 0);
|
||||||
|
--card-foreground: oklch(0.13 0.028 261.692);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.13 0.028 261.692);
|
||||||
|
--primary: oklch(0.21 0.034 264.665);
|
||||||
|
--primary-foreground: oklch(0.985 0.002 247.839);
|
||||||
|
--secondary: oklch(0.967 0.003 264.542);
|
||||||
|
--secondary-foreground: oklch(0.21 0.034 264.665);
|
||||||
|
--muted: oklch(0.967 0.003 264.542);
|
||||||
|
--muted-foreground: oklch(0.551 0.027 264.364);
|
||||||
|
--accent: oklch(0.967 0.003 264.542);
|
||||||
|
--accent-foreground: oklch(0.21 0.034 264.665);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.928 0.006 264.531);
|
||||||
|
--input: oklch(0.928 0.006 264.531);
|
||||||
|
--ring: oklch(0.707 0.022 261.325);
|
||||||
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
|
--sidebar: oklch(0.985 0.002 247.839);
|
||||||
|
--sidebar-foreground: oklch(0.13 0.028 261.692);
|
||||||
|
--sidebar-primary: oklch(0.21 0.034 264.665);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
|
||||||
|
--sidebar-accent: oklch(0.967 0.003 264.542);
|
||||||
|
--sidebar-accent-foreground: oklch(0.21 0.034 264.665);
|
||||||
|
--sidebar-border: oklch(0.928 0.006 264.531);
|
||||||
|
--sidebar-ring: oklch(0.707 0.022 261.325);
|
||||||
|
|
||||||
|
@variant dark {
|
||||||
|
--background: oklch(0.13 0.028 261.692);
|
||||||
|
--foreground: oklch(0.985 0.002 247.839);
|
||||||
|
--card: oklch(0.21 0.034 264.665);
|
||||||
|
--card-foreground: oklch(0.985 0.002 247.839);
|
||||||
|
--popover: oklch(0.21 0.034 264.665);
|
||||||
|
--popover-foreground: oklch(0.985 0.002 247.839);
|
||||||
|
--primary: oklch(0.928 0.006 264.531);
|
||||||
|
--primary-foreground: oklch(0.21 0.034 264.665);
|
||||||
|
--secondary: oklch(0.278 0.033 256.848);
|
||||||
|
--secondary-foreground: oklch(0.985 0.002 247.839);
|
||||||
|
--muted: oklch(0.278 0.033 256.848);
|
||||||
|
--muted-foreground: oklch(0.707 0.022 261.325);
|
||||||
|
--accent: oklch(0.278 0.033 256.848);
|
||||||
|
--accent-foreground: oklch(0.985 0.002 247.839);
|
||||||
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
--input: oklch(1 0 0 / 15%);
|
||||||
|
--ring: oklch(0.551 0.027 264.364);
|
||||||
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
|
--sidebar: oklch(0.21 0.034 264.665);
|
||||||
|
--sidebar-foreground: oklch(0.985 0.002 247.839);
|
||||||
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
|
||||||
|
--sidebar-accent: oklch(0.278 0.033 256.848);
|
||||||
|
--sidebar-accent-foreground: oklch(0.985 0.002 247.839);
|
||||||
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
|
--sidebar-ring: oklch(0.551 0.027 264.364);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-slate {
|
||||||
|
--radius: 0.625rem;
|
||||||
|
--background: oklch(1 0 0);
|
||||||
|
--foreground: oklch(0.129 0.042 264.695);
|
||||||
|
--card: oklch(1 0 0);
|
||||||
|
--card-foreground: oklch(0.129 0.042 264.695);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.129 0.042 264.695);
|
||||||
|
--primary: oklch(0.208 0.042 265.755);
|
||||||
|
--primary-foreground: oklch(0.984 0.003 247.858);
|
||||||
|
--secondary: oklch(0.968 0.007 247.896);
|
||||||
|
--secondary-foreground: oklch(0.208 0.042 265.755);
|
||||||
|
--muted: oklch(0.968 0.007 247.896);
|
||||||
|
--muted-foreground: oklch(0.554 0.046 257.417);
|
||||||
|
--accent: oklch(0.968 0.007 247.896);
|
||||||
|
--accent-foreground: oklch(0.208 0.042 265.755);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.929 0.013 255.508);
|
||||||
|
--input: oklch(0.929 0.013 255.508);
|
||||||
|
--ring: oklch(0.704 0.04 256.788);
|
||||||
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
|
--sidebar: oklch(0.984 0.003 247.858);
|
||||||
|
--sidebar-foreground: oklch(0.129 0.042 264.695);
|
||||||
|
--sidebar-primary: oklch(0.208 0.042 265.755);
|
||||||
|
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
||||||
|
--sidebar-accent: oklch(0.968 0.007 247.896);
|
||||||
|
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
|
||||||
|
--sidebar-border: oklch(0.929 0.013 255.508);
|
||||||
|
--sidebar-ring: oklch(0.704 0.04 256.788);
|
||||||
|
|
||||||
|
@variant dark {
|
||||||
|
--background: oklch(0.129 0.042 264.695);
|
||||||
|
--foreground: oklch(0.984 0.003 247.858);
|
||||||
|
--card: oklch(0.208 0.042 265.755);
|
||||||
|
--card-foreground: oklch(0.984 0.003 247.858);
|
||||||
|
--popover: oklch(0.208 0.042 265.755);
|
||||||
|
--popover-foreground: oklch(0.984 0.003 247.858);
|
||||||
|
--primary: oklch(0.929 0.013 255.508);
|
||||||
|
--primary-foreground: oklch(0.208 0.042 265.755);
|
||||||
|
--secondary: oklch(0.279 0.041 260.031);
|
||||||
|
--secondary-foreground: oklch(0.984 0.003 247.858);
|
||||||
|
--muted: oklch(0.279 0.041 260.031);
|
||||||
|
--muted-foreground: oklch(0.704 0.04 256.788);
|
||||||
|
--accent: oklch(0.279 0.041 260.031);
|
||||||
|
--accent-foreground: oklch(0.984 0.003 247.858);
|
||||||
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
--input: oklch(1 0 0 / 15%);
|
||||||
|
--ring: oklch(0.551 0.027 264.364);
|
||||||
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
|
--sidebar: oklch(0.208 0.042 265.755);
|
||||||
|
--sidebar-foreground: oklch(0.984 0.003 247.858);
|
||||||
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
|
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
||||||
|
--sidebar-accent: oklch(0.279 0.041 260.031);
|
||||||
|
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
|
||||||
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
|
--sidebar-ring: oklch(0.551 0.027 264.364);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-scaled {
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
--radius: 0.6rem;
|
||||||
|
--text-lg: 1.05rem;
|
||||||
|
--text-base: 0.85rem;
|
||||||
|
--text-sm: 0.8rem;
|
||||||
|
--spacing: 0.222222rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="card"] {
|
||||||
|
--spacing: 0.16rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="card-header"] *,
|
||||||
|
[data-slot="card-content"] *,
|
||||||
|
[data-slot="card-footer"] * {
|
||||||
|
--spacing: 0.222222rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,4 +18,4 @@
|
|||||||
"hooks": "@/hooks"
|
"hooks": "@/hooks"
|
||||||
},
|
},
|
||||||
"iconLibrary": "lucide"
|
"iconLibrary": "lucide"
|
||||||
}
|
}
|
||||||
65
apps/v4/components/active-theme.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import {
|
||||||
|
ReactNode,
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from "react"
|
||||||
|
|
||||||
|
const COOKIE_NAME = "active_theme"
|
||||||
|
const DEFAULT_THEME = "default"
|
||||||
|
|
||||||
|
function setThemeCookie(theme: string) {
|
||||||
|
if (typeof window === "undefined") return
|
||||||
|
|
||||||
|
document.cookie = `${COOKIE_NAME}=${theme}; path=/; max-age=31536000; SameSite=Lax; ${window.location.protocol === "https:" ? "Secure;" : ""}`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThemeContextType = {
|
||||||
|
activeTheme: string
|
||||||
|
setActiveTheme: (theme: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
|
||||||
|
|
||||||
|
export function ActiveThemeProvider({
|
||||||
|
children,
|
||||||
|
initialTheme,
|
||||||
|
}: {
|
||||||
|
children: ReactNode
|
||||||
|
initialTheme?: string
|
||||||
|
}) {
|
||||||
|
const [activeTheme, setActiveTheme] = useState<string>(
|
||||||
|
() => initialTheme || DEFAULT_THEME
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setThemeCookie(activeTheme)
|
||||||
|
|
||||||
|
Array.from(document.body.classList)
|
||||||
|
.filter((className) => className.startsWith("theme-"))
|
||||||
|
.forEach((className) => {
|
||||||
|
document.body.classList.remove(className)
|
||||||
|
})
|
||||||
|
document.body.classList.add(`theme-${activeTheme}`)
|
||||||
|
if (activeTheme.endsWith("-scaled")) {
|
||||||
|
document.body.classList.add("theme-scaled")
|
||||||
|
}
|
||||||
|
}, [activeTheme])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeContext.Provider value={{ activeTheme, setActiveTheme }}>
|
||||||
|
{children}
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useThemeConfig() {
|
||||||
|
const context = useContext(ThemeContext)
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error("useThemeConfig must be used within an ActiveThemeProvider")
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
@@ -151,9 +151,14 @@ const data = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
components: Object.values(Index).filter(
|
components: Object.values(Index)
|
||||||
(item) => item.type === "registry:ui"
|
.filter((item) => item.type === "registry:ui")
|
||||||
),
|
.concat([
|
||||||
|
{
|
||||||
|
name: "combobox",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name)),
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||||
|
|||||||
@@ -22,15 +22,15 @@ import { Label } from "@/registry/new-york-v4/ui/label"
|
|||||||
export function CardDemo() {
|
export function CardDemo() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-start gap-4">
|
<div className="flex flex-col items-start gap-4">
|
||||||
<form>
|
<Card className="w-full max-w-sm">
|
||||||
<Card>
|
<CardHeader>
|
||||||
<CardHeader>
|
<CardTitle>Login to your account</CardTitle>
|
||||||
<CardTitle>Login to your account</CardTitle>
|
<CardDescription>
|
||||||
<CardDescription>
|
Enter your email below to login to your account
|
||||||
Enter your email below to login to your account
|
</CardDescription>
|
||||||
</CardDescription>
|
</CardHeader>
|
||||||
</CardHeader>
|
<CardContent>
|
||||||
<CardContent>
|
<form>
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="email">Email</Label>
|
<Label htmlFor="email">Email</Label>
|
||||||
@@ -54,23 +54,23 @@ export function CardDemo() {
|
|||||||
<Input id="password" type="password" required />
|
<Input id="password" type="password" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</form>
|
||||||
<CardFooter className="flex-col gap-2">
|
</CardContent>
|
||||||
<Button type="submit" className="w-full">
|
<CardFooter className="flex-col gap-2">
|
||||||
Login
|
<Button type="submit" className="w-full">
|
||||||
</Button>
|
Login
|
||||||
<Button variant="outline" className="w-full">
|
</Button>
|
||||||
Login with Google
|
<Button variant="outline" className="w-full">
|
||||||
</Button>
|
Login with Google
|
||||||
<div className="mt-4 text-center text-sm">
|
</Button>
|
||||||
Don't have an account?{" "}
|
<div className="mt-4 text-center text-sm">
|
||||||
<a href="#" className="underline underline-offset-4">
|
Don't have an account?{" "}
|
||||||
Sign up
|
<a href="#" className="underline underline-offset-4">
|
||||||
</a>
|
Sign up
|
||||||
</div>
|
</a>
|
||||||
</CardFooter>
|
</div>
|
||||||
</Card>
|
</CardFooter>
|
||||||
</form>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Meeting Notes</CardTitle>
|
<CardTitle>Meeting Notes</CardTitle>
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ export function ComboboxDemo() {
|
|||||||
timezones={[...timezones]}
|
timezones={[...timezones]}
|
||||||
selectedTimezone={timezones[0].timezones[0]}
|
selectedTimezone={timezones[0].timezones[0]}
|
||||||
/>
|
/>
|
||||||
|
<ComboboxWithCheckbox frameworks={[...frameworks]} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -342,3 +343,63 @@ function TimezoneCombobox({
|
|||||||
</Popover>
|
</Popover>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ComboboxWithCheckbox({ frameworks }: { frameworks: Framework[] }) {
|
||||||
|
const [open, setOpen] = React.useState(false)
|
||||||
|
const [selectedFrameworks, setSelectedFrameworks] = React.useState<
|
||||||
|
Framework[]
|
||||||
|
>([])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
aria-expanded={open}
|
||||||
|
className="w-fit min-w-[280px] justify-between"
|
||||||
|
>
|
||||||
|
{selectedFrameworks.length > 0
|
||||||
|
? selectedFrameworks.map((framework) => framework.label).join(", ")
|
||||||
|
: "Select frameworks (multi-select)..."}
|
||||||
|
<ChevronsUpDown className="text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[300px] p-0" align="start">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Search framework..." />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No framework found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{frameworks.map((framework) => (
|
||||||
|
<CommandItem
|
||||||
|
key={framework.value}
|
||||||
|
value={framework.value}
|
||||||
|
onSelect={(currentValue) => {
|
||||||
|
setSelectedFrameworks(
|
||||||
|
selectedFrameworks.some((f) => f.value === currentValue)
|
||||||
|
? selectedFrameworks.filter(
|
||||||
|
(f) => f.value !== currentValue
|
||||||
|
)
|
||||||
|
: [...selectedFrameworks, framework]
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="border-input data-[selected=true]:border-primary data-[selected=true]:bg-primary data-[selected=true]:text-primary-foreground pointer-events-none size-4 shrink-0 rounded-[4px] border transition-all select-none *:[svg]:opacity-0 data-[selected=true]:*:[svg]:opacity-100"
|
||||||
|
data-selected={selectedFrameworks.some(
|
||||||
|
(f) => f.value === framework.value
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CheckIcon className="size-3.5 text-current" />
|
||||||
|
</div>
|
||||||
|
{framework.label}
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function ContextMenuDemo() {
|
|||||||
<ContextMenuSubTrigger inset>More Tools</ContextMenuSubTrigger>
|
<ContextMenuSubTrigger inset>More Tools</ContextMenuSubTrigger>
|
||||||
<ContextMenuSubContent className="w-48">
|
<ContextMenuSubContent className="w-48">
|
||||||
<ContextMenuItem inset>
|
<ContextMenuItem inset>
|
||||||
Save Page As...
|
Save Page...
|
||||||
<ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>
|
<ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuItem>
|
<ContextMenuItem>
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ function DrawerBottom() {
|
|||||||
dataKey="goal"
|
dataKey="goal"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
fill: "hsl(var(--foreground))",
|
fill: "var(--primary)",
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,55 +44,52 @@ const plans = [
|
|||||||
] as const
|
] as const
|
||||||
|
|
||||||
const themes = {
|
const themes = {
|
||||||
stone: {
|
neutral: {
|
||||||
light: {
|
light: {
|
||||||
"--primary": "oklch(0.216 0.006 56.043)", // --color-stone-900
|
"--primary": "oklch(0.205 0 0)",
|
||||||
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-stone-50
|
"--primary-foreground": "oklch(0.985 0 0)",
|
||||||
"--accent": "oklch(0.97 0.001 106.424)", // --color-stone-100
|
"--ring": "oklch(0.708 0 0)",
|
||||||
"--ring": "oklch(0.869 0.005 56.366)", // --color-stone-300
|
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
"--primary": "oklch(0.985 0.001 106.423)", // --color-stone-50
|
"--primary": "oklch(0.922 0 0)",
|
||||||
"--primary-foreground": "oklch(0.216 0.006 56.043)", // --color-stone-900
|
"--primary-foreground": "oklch(0.205 0 0)",
|
||||||
"--accent": "oklch(0.268 0.007 34.298)", // --color-stone-800
|
"--ring": "oklch(0.556 0 0)",
|
||||||
"--accent-foreground": "oklch(0.985 0.001 106.423)", // --color-stone-50
|
|
||||||
"--ring": "oklch(0.553 0.013 58.071)", // --color-stone-500
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
blue: {
|
blue: {
|
||||||
light: {
|
light: {
|
||||||
"--primary": "oklch(0.546 0.245 262.881)", // --color-blue-600
|
"--primary": "oklch(0.546 0.245 262.881)",
|
||||||
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-blue-50
|
"--primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||||
"--ring": "oklch(0.707 0.165 254.624)", // --color-blue-400
|
"--ring": "oklch(0.546 0.245 262.881)",
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
"--primary": "oklch(0.546 0.245 262.881)", // --color-blue-600
|
"--primary": "oklch(0.623 0.214 259.815)",
|
||||||
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-blue-50
|
"--primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||||
"--ring": "oklch(0.379 0.146 265.522)", // --color-blue-400
|
"--ring": "oklch(0.623 0.214 259.815)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
amber: {
|
amber: {
|
||||||
light: {
|
light: {
|
||||||
"--primary": "oklch(0.769 0.188 70.08)", // --color-blue-600
|
"--primary": "oklch(0.769 0.188 70.08)",
|
||||||
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-blue-50
|
"--primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||||
"--ring": "oklch(0.82 0.13 92.25)", // --color-blue-400
|
"--ring": "oklch(0.82 0.13 92.25)",
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
"--primary": "oklch(0.985 0.001 106.423)", // --color-stone-50
|
"--primary": "oklch(0.769 0.188 70.08)",
|
||||||
"--primary-foreground": "oklch(0.216 0.006 56.043)", // --color-stone-900
|
"--primary-foreground": "oklch(0.216 0.006 56.043)",
|
||||||
"--ring": "oklch(0.553 0.013 58.071)", // --color-stone-500
|
"--ring": "oklch(0.666 0.179 58.318)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
teal: {
|
teal: {
|
||||||
light: {
|
light: {
|
||||||
"--primary": "oklch(0.627 0.194 149.214)", // --color-blue-600
|
"--primary": "oklch(0.627 0.194 149.214)",
|
||||||
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-blue-50
|
"--primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||||
"--ring": "oklch(0.79 0.19 153.13)", // --color-blue-400
|
"--ring": "oklch(0.79 0.19 153.13)",
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
"--primary": "oklch(0.985 0.001 106.423)", // --color-stone-50
|
"--primary": "oklch(0.704 0.14 182.503)",
|
||||||
"--primary-foreground": "oklch(0.216 0.006 56.043)", // --color-stone-900
|
"--primary-foreground": "oklch(0.216 0.006 56.043)",
|
||||||
"--ring": "oklch(0.553 0.013 58.071)", // --color-stone-500
|
"--ring": "oklch(0.704 0.14 182.503)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const
|
} as const
|
||||||
@@ -109,123 +106,129 @@ export function FormsDemo() {
|
|||||||
}, [theme, mode])
|
}, [theme, mode])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex max-w-md flex-col gap-4">
|
<div>
|
||||||
<Card style={themeStyles as React.CSSProperties}>
|
<div className="flex max-w-md flex-col gap-4">
|
||||||
<CardHeader>
|
<Card style={themeStyles as React.CSSProperties}>
|
||||||
<CardTitle className="text-lg">Upgrade your subscription</CardTitle>
|
<CardHeader>
|
||||||
<CardDescription>
|
<CardTitle className="text-lg">Upgrade your subscription</CardTitle>
|
||||||
You are currently on the free plan. Upgrade to the pro plan to get
|
<CardDescription className="text-balance">
|
||||||
access to all features.
|
You are currently on the free plan. Upgrade to the pro plan to get
|
||||||
</CardDescription>
|
access to all features.
|
||||||
</CardHeader>
|
</CardDescription>
|
||||||
<CardContent>
|
</CardHeader>
|
||||||
<div className="flex flex-col gap-6">
|
<CardContent>
|
||||||
<div className="flex flex-col gap-3 md:flex-row">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-3 md:flex-row">
|
||||||
<Label htmlFor="name">Name</Label>
|
<div className="flex flex-1 flex-col gap-2">
|
||||||
<Input id="name" placeholder="Evil Rabbit" />
|
<Label htmlFor="name">Name</Label>
|
||||||
|
<Input id="name" placeholder="Evil Rabbit" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-1 flex-col gap-2">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<Input id="email" placeholder="example@acme.com" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label htmlFor="email">Email</Label>
|
<Label htmlFor="card-number">Card Number</Label>
|
||||||
<Input id="email" placeholder="example@acme.com" />
|
<div className="grid grid-cols-2 gap-3 md:grid-cols-[1fr_80px_60px]">
|
||||||
|
<Input
|
||||||
|
id="card-number"
|
||||||
|
placeholder="1234 1234 1234 1234"
|
||||||
|
className="col-span-2 md:col-span-1"
|
||||||
|
/>
|
||||||
|
<Input id="card-number-expiry" placeholder="MM/YY" />
|
||||||
|
<Input id="card-number-cvc" placeholder="CVC" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex flex-col gap-2">
|
<Label htmlFor="color">Color</Label>
|
||||||
<Label htmlFor="card-number">Card Number</Label>
|
<Select
|
||||||
<div className="grid grid-cols-2 gap-3 md:grid-cols-[1fr_80px_60px]">
|
onValueChange={(value) =>
|
||||||
<Input
|
setTheme(value as keyof typeof themes)
|
||||||
id="card-number"
|
}
|
||||||
placeholder="1234 1234 1234 1234"
|
>
|
||||||
className="col-span-2 md:col-span-1"
|
<SelectTrigger id="color" className="w-full capitalize">
|
||||||
/>
|
<SelectValue placeholder="Select a color" />
|
||||||
<Input id="card-number-expiry" placeholder="MM/YY" />
|
</SelectTrigger>
|
||||||
<Input id="card-number-cvc" placeholder="CVC" />
|
<SelectContent>
|
||||||
|
{Object.keys(themes).map((theme) => (
|
||||||
|
<SelectItem
|
||||||
|
key={theme}
|
||||||
|
value={theme}
|
||||||
|
className="capitalize"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="size-3.5 rounded-full border"
|
||||||
|
style={{
|
||||||
|
backgroundColor:
|
||||||
|
themes[theme as keyof typeof themes][
|
||||||
|
mode as keyof (typeof themes)[keyof typeof themes]
|
||||||
|
]?.["--primary"],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{theme}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<fieldset className="flex flex-col gap-3">
|
||||||
<div className="flex flex-col gap-2">
|
<legend className="text-sm font-medium">Plan</legend>
|
||||||
<Label htmlFor="color">Color</Label>
|
<p className="text-muted-foreground text-sm">
|
||||||
<Select
|
Select the plan that best fits your needs.
|
||||||
onValueChange={(value) =>
|
</p>
|
||||||
setTheme(value as keyof typeof themes)
|
<RadioGroup
|
||||||
}
|
defaultValue="starter"
|
||||||
>
|
className="grid gap-3 md:grid-cols-2"
|
||||||
<SelectTrigger id="color" className="w-full">
|
>
|
||||||
<SelectValue placeholder="Select a color" />
|
{plans.map((plan) => (
|
||||||
</SelectTrigger>
|
<Label
|
||||||
<SelectContent>
|
className="has-[[data-state=checked]]:border-ring has-[[data-state=checked]]:bg-input/30 flex items-start gap-3 rounded-lg border p-3"
|
||||||
{Object.keys(themes).map((theme) => (
|
key={plan.id}
|
||||||
<SelectItem key={theme} value={theme}>
|
>
|
||||||
<div
|
<RadioGroupItem
|
||||||
className="size-3.5 rounded-full"
|
value={plan.id}
|
||||||
style={{
|
id={plan.name}
|
||||||
backgroundColor:
|
className="data-[state=checked]:border-primary"
|
||||||
themes[theme as keyof typeof themes]["light"][
|
|
||||||
"--primary"
|
|
||||||
],
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{theme}
|
<div className="grid gap-1 font-normal">
|
||||||
</SelectItem>
|
<div className="font-medium">{plan.name}</div>
|
||||||
))}
|
<div className="text-muted-foreground pr-2 text-xs leading-snug text-balance">
|
||||||
</SelectContent>
|
{plan.description}
|
||||||
</Select>
|
</div>
|
||||||
</div>
|
|
||||||
<fieldset className="flex flex-col gap-3">
|
|
||||||
<legend className="text-sm font-medium">Plan</legend>
|
|
||||||
<p className="text-muted-foreground text-sm">
|
|
||||||
Select the plan that best fits your needs.
|
|
||||||
</p>
|
|
||||||
<RadioGroup
|
|
||||||
defaultValue="starter"
|
|
||||||
className="grid gap-3 md:grid-cols-2"
|
|
||||||
>
|
|
||||||
{plans.map((plan) => (
|
|
||||||
<Label
|
|
||||||
className="has-[[data-state=checked]]:border-ring has-[[data-state=checked]]:bg-ring/10 flex items-start gap-3 rounded-lg border p-3"
|
|
||||||
key={plan.id}
|
|
||||||
>
|
|
||||||
<RadioGroupItem
|
|
||||||
value={plan.id}
|
|
||||||
id={plan.name}
|
|
||||||
className="data-[state=checked]:border-primary"
|
|
||||||
/>
|
|
||||||
<div className="grid gap-1 font-normal">
|
|
||||||
<div className="font-medium">{plan.name}</div>
|
|
||||||
<div className="text-muted-foreground text-xs leading-snug">
|
|
||||||
{plan.description}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Label>
|
||||||
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
</fieldset>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Label htmlFor="notes">Notes</Label>
|
||||||
|
<Textarea id="notes" placeholder="Enter notes" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox id="terms" />
|
||||||
|
<Label htmlFor="terms" className="font-normal">
|
||||||
|
I agree to the terms and conditions
|
||||||
</Label>
|
</Label>
|
||||||
))}
|
</div>
|
||||||
</RadioGroup>
|
<div className="flex items-center gap-2">
|
||||||
</fieldset>
|
<Checkbox id="newsletter" defaultChecked />
|
||||||
<div className="flex flex-col gap-2">
|
<Label htmlFor="newsletter" className="font-normal">
|
||||||
<Label htmlFor="notes">Notes</Label>
|
Allow us to send you emails
|
||||||
<Textarea id="notes" placeholder="Enter notes" />
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox id="terms" />
|
|
||||||
<Label htmlFor="terms" className="font-normal">
|
|
||||||
I agree to the terms and conditions
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox id="newsletter" defaultChecked />
|
|
||||||
<Label htmlFor="newsletter" className="font-normal">
|
|
||||||
Allow us to send you emails
|
|
||||||
</Label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CardContent>
|
||||||
</CardContent>
|
<CardFooter className="flex justify-between">
|
||||||
<CardFooter className="flex justify-between">
|
<Button variant="outline" size="sm">
|
||||||
<Button variant="outline" size="sm">
|
Cancel
|
||||||
Cancel
|
</Button>
|
||||||
</Button>
|
<Button size="sm">Upgrade Plan</Button>
|
||||||
<Button size="sm">Upgrade Plan</Button>
|
</CardFooter>
|
||||||
</CardFooter>
|
</Card>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ export function ModeSwitcher() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="outline"
|
||||||
className="group/toggle h-8 w-8 px-0"
|
size="icon"
|
||||||
|
className="group/toggle size-8"
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
>
|
>
|
||||||
<SunIcon className="hidden [html.dark_&]:block" />
|
<SunIcon className="hidden [html.dark_&]:block" />
|
||||||
|
|||||||
@@ -4,14 +4,21 @@ import * as React from "react"
|
|||||||
import { MoonIcon, SunIcon } from "lucide-react"
|
import { MoonIcon, SunIcon } from "lucide-react"
|
||||||
import { useTheme } from "next-themes"
|
import { useTheme } from "next-themes"
|
||||||
|
|
||||||
|
import { META_THEME_COLORS, useMetaColor } from "@/hooks/use-meta-color"
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
|
||||||
export function ModeToggle() {
|
export function ModeToggle() {
|
||||||
const { setTheme, resolvedTheme } = useTheme()
|
const { setTheme, resolvedTheme } = useTheme()
|
||||||
|
const { setMetaColor } = useMetaColor()
|
||||||
|
|
||||||
const toggleTheme = React.useCallback(() => {
|
const toggleTheme = React.useCallback(() => {
|
||||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||||
}, [resolvedTheme, setTheme])
|
setMetaColor(
|
||||||
|
resolvedTheme === "dark"
|
||||||
|
? META_THEME_COLORS.light
|
||||||
|
: META_THEME_COLORS.dark
|
||||||
|
)
|
||||||
|
}, [resolvedTheme, setTheme, setMetaColor])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export function NavHeader() {
|
|||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationMenu>
|
<NavigationMenu className="hidden sm:flex">
|
||||||
<NavigationMenuList className="gap-2 *:data-[slot=navigation-menu-item]:h-7 **:data-[slot=navigation-menu-link]:py-1 **:data-[slot=navigation-menu-link]:font-medium">
|
<NavigationMenuList className="gap-2 *:data-[slot=navigation-menu-item]:h-7 **:data-[slot=navigation-menu-link]:py-1 **:data-[slot=navigation-menu-link]:font-medium">
|
||||||
<NavigationMenuItem>
|
<NavigationMenuItem>
|
||||||
<NavigationMenuLink asChild data-active={pathname === "/"}>
|
<NavigationMenuLink asChild data-active={pathname === "/"}>
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { CircleCheckIcon, CircleHelpIcon, CircleIcon } from "lucide-react"
|
import { CircleCheckIcon, CircleHelpIcon, CircleIcon } from "lucide-react"
|
||||||
|
|||||||
@@ -1,24 +1,132 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
|
||||||
export function SonnerDemo() {
|
const promiseCode = "`${data.name} toast has been added`"
|
||||||
return (
|
|
||||||
<Button
|
const allTypes = [
|
||||||
variant="outline"
|
{
|
||||||
onClick={() =>
|
name: "Default",
|
||||||
toast("Event has been created", {
|
snippet: `toast('Event has been created')`,
|
||||||
description: "Sunday, December 03, 2023 at 9:00 AM",
|
action: () => toast("Event has been created"),
|
||||||
action: {
|
},
|
||||||
label: "Undo",
|
{
|
||||||
onClick: () => console.log("Undo"),
|
name: "Description",
|
||||||
|
snippet: `toast.message('Event has been created', {
|
||||||
|
description: 'Monday, January 3rd at 6:00pm',
|
||||||
|
})`,
|
||||||
|
action: () =>
|
||||||
|
toast("Event has been created", {
|
||||||
|
description: "Monday, January 3rd at 6:00pm",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Success",
|
||||||
|
snippet: `toast.success('Event has been created')`,
|
||||||
|
action: () => toast.success("Event has been created"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Info",
|
||||||
|
snippet: `toast.info('Be at the area 10 minutes before the event time')`,
|
||||||
|
action: () => toast.info("Be at the area 10 minutes before the event time"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Warning",
|
||||||
|
snippet: `toast.warning('Event start time cannot be earlier than 8am')`,
|
||||||
|
action: () => toast.warning("Event start time cannot be earlier than 8am"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Error",
|
||||||
|
snippet: `toast.error('Event has not been created')`,
|
||||||
|
action: () => toast.error("Event has not been created"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Action",
|
||||||
|
action: () =>
|
||||||
|
toast.message("Event has been created", {
|
||||||
|
action: {
|
||||||
|
label: "Undo",
|
||||||
|
onClick: () => console.log("Undo"),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cancel",
|
||||||
|
action: () =>
|
||||||
|
toast.message("Event has been created", {
|
||||||
|
cancel: {
|
||||||
|
label: "Cancel",
|
||||||
|
onClick: () => console.log("Cancel"),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Promise",
|
||||||
|
snippet: `const promise = () => new Promise((resolve) => setTimeout(() => resolve({ name: 'Sonner' }), 2000));
|
||||||
|
|
||||||
|
toast.promise(promise, {
|
||||||
|
loading: 'Loading...',
|
||||||
|
success: (data) => {
|
||||||
|
return ${promiseCode};
|
||||||
|
},
|
||||||
|
error: 'Error',
|
||||||
|
});`,
|
||||||
|
action: () =>
|
||||||
|
toast.promise<{ name: string }>(
|
||||||
|
() =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({ name: "Sonner" })
|
||||||
|
}, 2000)
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
loading: "Loading...",
|
||||||
|
success: (data) => {
|
||||||
|
return `${data.name} toast has been added`
|
||||||
},
|
},
|
||||||
})
|
error: "Error",
|
||||||
}
|
}
|
||||||
>
|
),
|
||||||
Show Toast
|
},
|
||||||
</Button>
|
]
|
||||||
|
|
||||||
|
export function SonnerDemo() {
|
||||||
|
const [activeType, setActiveType] = React.useState(allTypes[0])
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap gap-4">
|
||||||
|
<Button onClick={() => toast("My first toast")} variant="outline">
|
||||||
|
Give me a toast
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() =>
|
||||||
|
toast("Event has been created", {
|
||||||
|
description: "Sunday, December 03, 2023 at 9:00 AM",
|
||||||
|
action: {
|
||||||
|
label: "Undo",
|
||||||
|
onClick: () => console.log("Undo"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Show Toast
|
||||||
|
</Button>
|
||||||
|
{allTypes.map((type) => (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
data-active={activeType.name === type.name}
|
||||||
|
onClick={() => {
|
||||||
|
type.action()
|
||||||
|
setActiveType(type)
|
||||||
|
}}
|
||||||
|
key={type.name}
|
||||||
|
>
|
||||||
|
{type.name}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
30
apps/v4/components/theme-selector.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { THEMES } from "@/lib/themes"
|
||||||
|
import { useThemeConfig } from "@/components/active-theme"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/registry/new-york-v4/ui/select"
|
||||||
|
|
||||||
|
export function ThemeSelector() {
|
||||||
|
const { activeTheme, setActiveTheme } = useThemeConfig()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select value={activeTheme} onValueChange={setActiveTheme}>
|
||||||
|
<SelectTrigger size="sm" className="w-32">
|
||||||
|
<SelectValue placeholder="Select a theme" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent align="end">
|
||||||
|
{THEMES.map((theme) => (
|
||||||
|
<SelectItem key={theme.name} value={theme.value}>
|
||||||
|
{theme.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
49
apps/v4/lib/fonts.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import {
|
||||||
|
Geist,
|
||||||
|
Geist_Mono,
|
||||||
|
Instrument_Sans,
|
||||||
|
Inter,
|
||||||
|
Mulish,
|
||||||
|
Noto_Sans_Mono,
|
||||||
|
} from "next/font/google"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const fontSans = Geist({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-sans",
|
||||||
|
})
|
||||||
|
|
||||||
|
const fontMono = Geist_Mono({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-mono",
|
||||||
|
})
|
||||||
|
|
||||||
|
const fontInstrument = Instrument_Sans({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-instrument",
|
||||||
|
})
|
||||||
|
|
||||||
|
const fontNotoMono = Noto_Sans_Mono({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-noto-mono",
|
||||||
|
})
|
||||||
|
|
||||||
|
const fontMullish = Mulish({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-mullish",
|
||||||
|
})
|
||||||
|
|
||||||
|
const fontInter = Inter({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-inter",
|
||||||
|
})
|
||||||
|
|
||||||
|
export const fontVariables = cn(
|
||||||
|
fontSans.variable,
|
||||||
|
fontMono.variable,
|
||||||
|
fontInstrument.variable,
|
||||||
|
fontNotoMono.variable,
|
||||||
|
fontMullish.variable,
|
||||||
|
fontInter.variable
|
||||||
|
)
|
||||||
31
apps/v4/lib/themes.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
export const THEMES = [
|
||||||
|
{
|
||||||
|
name: "Default",
|
||||||
|
value: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Neutral",
|
||||||
|
value: "neutral",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Stone",
|
||||||
|
value: "stone",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Zinc",
|
||||||
|
value: "zinc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Gray",
|
||||||
|
value: "gray",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Slate",
|
||||||
|
value: "slate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Scaled",
|
||||||
|
value: "scaled",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
export type Theme = (typeof THEMES)[number]
|
||||||
@@ -15,6 +15,10 @@
|
|||||||
"registry:build": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts && prettier --log-level silent --write \"registry/**/*.{ts,tsx,json,mdx}\" --cache"
|
"registry:build": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts && prettier --log-level silent --write \"registry/**/*.{ts,tsx,json,mdx}\" --cache"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
"@dnd-kit/modifiers": "^9.0.0",
|
||||||
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@hookform/resolvers": "^3.10.0",
|
"@hookform/resolvers": "^3.10.0",
|
||||||
"@radix-ui/react-accessible-icon": "^1.1.1",
|
"@radix-ui/react-accessible-icon": "^1.1.1",
|
||||||
"@radix-ui/react-accordion": "^1.2.2",
|
"@radix-ui/react-accordion": "^1.2.2",
|
||||||
@@ -35,7 +39,7 @@
|
|||||||
"@radix-ui/react-portal": "^1.1.3",
|
"@radix-ui/react-portal": "^1.1.3",
|
||||||
"@radix-ui/react-progress": "^1.1.1",
|
"@radix-ui/react-progress": "^1.1.1",
|
||||||
"@radix-ui/react-radio-group": "^1.2.2",
|
"@radix-ui/react-radio-group": "^1.2.2",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.2",
|
"@radix-ui/react-scroll-area": "^1.2.3",
|
||||||
"@radix-ui/react-select": "^2.1.5",
|
"@radix-ui/react-select": "^2.1.5",
|
||||||
"@radix-ui/react-separator": "^1.1.1",
|
"@radix-ui/react-separator": "^1.1.1",
|
||||||
"@radix-ui/react-slider": "^1.2.2",
|
"@radix-ui/react-slider": "^1.2.2",
|
||||||
@@ -46,7 +50,9 @@
|
|||||||
"@radix-ui/react-toggle": "^1.1.1",
|
"@radix-ui/react-toggle": "^1.1.1",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.1",
|
"@radix-ui/react-toggle-group": "^1.1.1",
|
||||||
"@radix-ui/react-tooltip": "^1.1.7",
|
"@radix-ui/react-tooltip": "^1.1.7",
|
||||||
|
"@tabler/icons-react": "^3.31.0",
|
||||||
"@tailwindcss/postcss": "^4.0.1",
|
"@tailwindcss/postcss": "^4.0.1",
|
||||||
|
"@tanstack/react-table": "^8.9.1",
|
||||||
"@vercel/analytics": "^1.2.2",
|
"@vercel/analytics": "^1.2.2",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -57,7 +63,7 @@
|
|||||||
"geist": "^1.2.2",
|
"geist": "^1.2.2",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "0.474.0",
|
"lucide-react": "0.474.0",
|
||||||
"next": "15.2.0-canary.33",
|
"next": "15.2.3",
|
||||||
"next-themes": "^0.4.3",
|
"next-themes": "^0.4.3",
|
||||||
"postcss": "^8.5.1",
|
"postcss": "^8.5.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
@@ -67,14 +73,14 @@
|
|||||||
"react-resizable-panels": "^2.1.7",
|
"react-resizable-panels": "^2.1.7",
|
||||||
"recharts": "2.15.1",
|
"recharts": "2.15.1",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"shadcn": "2.3.0",
|
"shadcn": "2.4.1",
|
||||||
"sonner": "^2.0.0",
|
"sonner": "^2.0.0",
|
||||||
"tailwind-merge": "^3.0.1",
|
"tailwind-merge": "^3.0.1",
|
||||||
"tailwindcss": "^4.0.7",
|
"tailwindcss": "^4.0.7",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
|
||||||
"ts-morph": "^22.0.0",
|
"ts-morph": "^22.0.0",
|
||||||
|
"tw-animate-css": "^1.2.4",
|
||||||
"vaul": "1.1.2",
|
"vaul": "1.1.2",
|
||||||
"zod": "^3.21.4"
|
"zod": "^3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
|||||||
BIN
apps/v4/public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
apps/v4/public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
apps/v4/public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
apps/v4/public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 191 B |
BIN
apps/v4/public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 241 B |
BIN
apps/v4/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 379 B |
BIN
apps/v4/public/opengraph-image.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
19
apps/v4/public/site.webmanifest
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"short_name": "",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
BIN
apps/v4/public/twitter-image.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
@@ -6,7 +6,7 @@
|
|||||||
"name": "index",
|
"name": "index",
|
||||||
"type": "registry:style",
|
"type": "registry:style",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"tailwindcss-animate",
|
"tw-animate-css",
|
||||||
"class-variance-authority",
|
"class-variance-authority",
|
||||||
"lucide-react"
|
"lucide-react"
|
||||||
],
|
],
|
||||||
@@ -14,13 +14,6 @@
|
|||||||
"utils"
|
"utils"
|
||||||
],
|
],
|
||||||
"files": [],
|
"files": [],
|
||||||
"tailwind": {
|
|
||||||
"config": {
|
|
||||||
"plugins": [
|
|
||||||
"require(\"tailwindcss-animate\")"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cssVars": {}
|
"cssVars": {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -121,6 +114,9 @@
|
|||||||
{
|
{
|
||||||
"name": "badge",
|
"name": "badge",
|
||||||
"type": "registry:ui",
|
"type": "registry:ui",
|
||||||
|
"dependencies": [
|
||||||
|
"@radix-ui/react-slot"
|
||||||
|
],
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"path": "registry/new-york-v4/ui/badge.tsx",
|
"path": "registry/new-york-v4/ui/badge.tsx",
|
||||||
@@ -244,7 +240,7 @@
|
|||||||
"name": "command",
|
"name": "command",
|
||||||
"type": "registry:ui",
|
"type": "registry:ui",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"cmdk@1.0.0"
|
"cmdk"
|
||||||
],
|
],
|
||||||
"registryDependencies": [
|
"registryDependencies": [
|
||||||
"dialog"
|
"dialog"
|
||||||
@@ -713,6 +709,92 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "dashboard-01",
|
||||||
|
"type": "registry:block",
|
||||||
|
"description": "A dashboard with sidebar, charts and data table.",
|
||||||
|
"dependencies": [
|
||||||
|
"@dnd-kit/core",
|
||||||
|
"@dnd-kit/modifiers",
|
||||||
|
"@dnd-kit/sortable",
|
||||||
|
"@dnd-kit/utilities",
|
||||||
|
"@tanstack/react-table",
|
||||||
|
"zod",
|
||||||
|
"@tabler/icons-react"
|
||||||
|
],
|
||||||
|
"registryDependencies": [
|
||||||
|
"sidebar",
|
||||||
|
"breadcrumb",
|
||||||
|
"separator",
|
||||||
|
"label",
|
||||||
|
"chart",
|
||||||
|
"card",
|
||||||
|
"select",
|
||||||
|
"tabs",
|
||||||
|
"table",
|
||||||
|
"toggle-group",
|
||||||
|
"badge",
|
||||||
|
"button",
|
||||||
|
"checkbox",
|
||||||
|
"dropdown-menu",
|
||||||
|
"drawer",
|
||||||
|
"input",
|
||||||
|
"avatar",
|
||||||
|
"sheet",
|
||||||
|
"sonner"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/dashboard-01/page.tsx",
|
||||||
|
"type": "registry:page",
|
||||||
|
"target": "app/dashboard/page.tsx"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/dashboard-01/data.json",
|
||||||
|
"type": "registry:file",
|
||||||
|
"target": "app/dashboard/data.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/dashboard-01/components/app-sidebar.tsx",
|
||||||
|
"type": "registry:component"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/dashboard-01/components/chart-area-interactive.tsx",
|
||||||
|
"type": "registry:component"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/dashboard-01/components/data-table.tsx",
|
||||||
|
"type": "registry:component"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/dashboard-01/components/nav-documents.tsx",
|
||||||
|
"type": "registry:component"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/dashboard-01/components/nav-main.tsx",
|
||||||
|
"type": "registry:component"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/dashboard-01/components/nav-secondary.tsx",
|
||||||
|
"type": "registry:component"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/dashboard-01/components/nav-user.tsx",
|
||||||
|
"type": "registry:component"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/dashboard-01/components/section-cards.tsx",
|
||||||
|
"type": "registry:component"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/dashboard-01/components/site-header.tsx",
|
||||||
|
"type": "registry:component"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"categories": [
|
||||||
|
"dashboard"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "sidebar-01",
|
"name": "sidebar-01",
|
||||||
"type": "registry:block",
|
"type": "registry:block",
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
"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 { NavDocuments } from "@/registry/new-york-v4/blocks/dashboard-01/components/nav-documents"
|
||||||
|
import { NavMain } from "@/registry/new-york-v4/blocks/dashboard-01/components/nav-main"
|
||||||
|
import { NavSecondary } from "@/registry/new-york-v4/blocks/dashboard-01/components/nav-secondary"
|
||||||
|
import { NavUser } from "@/registry/new-york-v4/blocks/dashboard-01/components/nav-user"
|
||||||
|
import {
|
||||||
|
Sidebar,
|
||||||
|
SidebarContent,
|
||||||
|
SidebarFooter,
|
||||||
|
SidebarHeader,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
} from "@/registry/new-york-v4/ui/sidebar"
|
||||||
|
|
||||||
|
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} />
|
||||||
|
<NavDocuments items={data.documents} />
|
||||||
|
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||||
|
</SidebarContent>
|
||||||
|
<SidebarFooter>
|
||||||
|
<NavUser user={data.user} />
|
||||||
|
</SidebarFooter>
|
||||||
|
</Sidebar>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||||
|
|
||||||
|
import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile"
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardAction,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/registry/new-york-v4/ui/card"
|
||||||
|
import {
|
||||||
|
ChartConfig,
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
} from "@/registry/new-york-v4/ui/chart"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/registry/new-york-v4/ui/select"
|
||||||
|
import {
|
||||||
|
ToggleGroup,
|
||||||
|
ToggleGroupItem,
|
||||||
|
} from "@/registry/new-york-v4/ui/toggle-group"
|
||||||
|
|
||||||
|
export const description = "An interactive area chart"
|
||||||
|
|
||||||
|
const chartData = [
|
||||||
|
{ date: "2024-04-01", desktop: 222, mobile: 150 },
|
||||||
|
{ date: "2024-04-02", desktop: 97, mobile: 180 },
|
||||||
|
{ date: "2024-04-03", desktop: 167, mobile: 120 },
|
||||||
|
{ date: "2024-04-04", desktop: 242, mobile: 260 },
|
||||||
|
{ date: "2024-04-05", desktop: 373, mobile: 290 },
|
||||||
|
{ date: "2024-04-06", desktop: 301, mobile: 340 },
|
||||||
|
{ date: "2024-04-07", desktop: 245, mobile: 180 },
|
||||||
|
{ date: "2024-04-08", desktop: 409, mobile: 320 },
|
||||||
|
{ date: "2024-04-09", desktop: 59, mobile: 110 },
|
||||||
|
{ date: "2024-04-10", desktop: 261, mobile: 190 },
|
||||||
|
{ date: "2024-04-11", desktop: 327, mobile: 350 },
|
||||||
|
{ date: "2024-04-12", desktop: 292, mobile: 210 },
|
||||||
|
{ date: "2024-04-13", desktop: 342, mobile: 380 },
|
||||||
|
{ date: "2024-04-14", desktop: 137, mobile: 220 },
|
||||||
|
{ date: "2024-04-15", desktop: 120, mobile: 170 },
|
||||||
|
{ date: "2024-04-16", desktop: 138, mobile: 190 },
|
||||||
|
{ date: "2024-04-17", desktop: 446, mobile: 360 },
|
||||||
|
{ date: "2024-04-18", desktop: 364, mobile: 410 },
|
||||||
|
{ date: "2024-04-19", desktop: 243, mobile: 180 },
|
||||||
|
{ date: "2024-04-20", desktop: 89, mobile: 150 },
|
||||||
|
{ date: "2024-04-21", desktop: 137, mobile: 200 },
|
||||||
|
{ date: "2024-04-22", desktop: 224, mobile: 170 },
|
||||||
|
{ date: "2024-04-23", desktop: 138, mobile: 230 },
|
||||||
|
{ date: "2024-04-24", desktop: 387, mobile: 290 },
|
||||||
|
{ date: "2024-04-25", desktop: 215, mobile: 250 },
|
||||||
|
{ date: "2024-04-26", desktop: 75, mobile: 130 },
|
||||||
|
{ date: "2024-04-27", desktop: 383, mobile: 420 },
|
||||||
|
{ date: "2024-04-28", desktop: 122, mobile: 180 },
|
||||||
|
{ date: "2024-04-29", desktop: 315, mobile: 240 },
|
||||||
|
{ date: "2024-04-30", desktop: 454, mobile: 380 },
|
||||||
|
{ date: "2024-05-01", desktop: 165, mobile: 220 },
|
||||||
|
{ date: "2024-05-02", desktop: 293, mobile: 310 },
|
||||||
|
{ date: "2024-05-03", desktop: 247, mobile: 190 },
|
||||||
|
{ date: "2024-05-04", desktop: 385, mobile: 420 },
|
||||||
|
{ date: "2024-05-05", desktop: 481, mobile: 390 },
|
||||||
|
{ date: "2024-05-06", desktop: 498, mobile: 520 },
|
||||||
|
{ date: "2024-05-07", desktop: 388, mobile: 300 },
|
||||||
|
{ date: "2024-05-08", desktop: 149, mobile: 210 },
|
||||||
|
{ date: "2024-05-09", desktop: 227, mobile: 180 },
|
||||||
|
{ date: "2024-05-10", desktop: 293, mobile: 330 },
|
||||||
|
{ date: "2024-05-11", desktop: 335, mobile: 270 },
|
||||||
|
{ date: "2024-05-12", desktop: 197, mobile: 240 },
|
||||||
|
{ date: "2024-05-13", desktop: 197, mobile: 160 },
|
||||||
|
{ date: "2024-05-14", desktop: 448, mobile: 490 },
|
||||||
|
{ date: "2024-05-15", desktop: 473, mobile: 380 },
|
||||||
|
{ date: "2024-05-16", desktop: 338, mobile: 400 },
|
||||||
|
{ date: "2024-05-17", desktop: 499, mobile: 420 },
|
||||||
|
{ date: "2024-05-18", desktop: 315, mobile: 350 },
|
||||||
|
{ date: "2024-05-19", desktop: 235, mobile: 180 },
|
||||||
|
{ date: "2024-05-20", desktop: 177, mobile: 230 },
|
||||||
|
{ date: "2024-05-21", desktop: 82, mobile: 140 },
|
||||||
|
{ date: "2024-05-22", desktop: 81, mobile: 120 },
|
||||||
|
{ date: "2024-05-23", desktop: 252, mobile: 290 },
|
||||||
|
{ date: "2024-05-24", desktop: 294, mobile: 220 },
|
||||||
|
{ date: "2024-05-25", desktop: 201, mobile: 250 },
|
||||||
|
{ date: "2024-05-26", desktop: 213, mobile: 170 },
|
||||||
|
{ date: "2024-05-27", desktop: 420, mobile: 460 },
|
||||||
|
{ date: "2024-05-28", desktop: 233, mobile: 190 },
|
||||||
|
{ date: "2024-05-29", desktop: 78, mobile: 130 },
|
||||||
|
{ date: "2024-05-30", desktop: 340, mobile: 280 },
|
||||||
|
{ date: "2024-05-31", desktop: 178, mobile: 230 },
|
||||||
|
{ date: "2024-06-01", desktop: 178, mobile: 200 },
|
||||||
|
{ date: "2024-06-02", desktop: 470, mobile: 410 },
|
||||||
|
{ date: "2024-06-03", desktop: 103, mobile: 160 },
|
||||||
|
{ date: "2024-06-04", desktop: 439, mobile: 380 },
|
||||||
|
{ date: "2024-06-05", desktop: 88, mobile: 140 },
|
||||||
|
{ date: "2024-06-06", desktop: 294, mobile: 250 },
|
||||||
|
{ date: "2024-06-07", desktop: 323, mobile: 370 },
|
||||||
|
{ date: "2024-06-08", desktop: 385, mobile: 320 },
|
||||||
|
{ date: "2024-06-09", desktop: 438, mobile: 480 },
|
||||||
|
{ date: "2024-06-10", desktop: 155, mobile: 200 },
|
||||||
|
{ date: "2024-06-11", desktop: 92, mobile: 150 },
|
||||||
|
{ date: "2024-06-12", desktop: 492, mobile: 420 },
|
||||||
|
{ date: "2024-06-13", desktop: 81, mobile: 130 },
|
||||||
|
{ date: "2024-06-14", desktop: 426, mobile: 380 },
|
||||||
|
{ date: "2024-06-15", desktop: 307, mobile: 350 },
|
||||||
|
{ date: "2024-06-16", desktop: 371, mobile: 310 },
|
||||||
|
{ date: "2024-06-17", desktop: 475, mobile: 520 },
|
||||||
|
{ date: "2024-06-18", desktop: 107, mobile: 170 },
|
||||||
|
{ date: "2024-06-19", desktop: 341, mobile: 290 },
|
||||||
|
{ date: "2024-06-20", desktop: 408, mobile: 450 },
|
||||||
|
{ date: "2024-06-21", desktop: 169, mobile: 210 },
|
||||||
|
{ date: "2024-06-22", desktop: 317, mobile: 270 },
|
||||||
|
{ date: "2024-06-23", desktop: 480, mobile: 530 },
|
||||||
|
{ date: "2024-06-24", desktop: 132, mobile: 180 },
|
||||||
|
{ date: "2024-06-25", desktop: 141, mobile: 190 },
|
||||||
|
{ date: "2024-06-26", desktop: 434, mobile: 380 },
|
||||||
|
{ date: "2024-06-27", desktop: 448, mobile: 490 },
|
||||||
|
{ date: "2024-06-28", desktop: 149, mobile: 200 },
|
||||||
|
{ date: "2024-06-29", desktop: 103, mobile: 160 },
|
||||||
|
{ date: "2024-06-30", desktop: 446, mobile: 400 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
visitors: {
|
||||||
|
label: "Visitors",
|
||||||
|
},
|
||||||
|
desktop: {
|
||||||
|
label: "Desktop",
|
||||||
|
color: "var(--primary)",
|
||||||
|
},
|
||||||
|
mobile: {
|
||||||
|
label: "Mobile",
|
||||||
|
color: "var(--primary)",
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig
|
||||||
|
|
||||||
|
export function ChartAreaInteractive() {
|
||||||
|
const isMobile = useIsMobile()
|
||||||
|
const [timeRange, setTimeRange] = React.useState("90d")
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isMobile) {
|
||||||
|
setTimeRange("7d")
|
||||||
|
}
|
||||||
|
}, [isMobile])
|
||||||
|
|
||||||
|
const filteredData = chartData.filter((item) => {
|
||||||
|
const date = new Date(item.date)
|
||||||
|
const referenceDate = new Date("2024-06-30")
|
||||||
|
let daysToSubtract = 90
|
||||||
|
if (timeRange === "30d") {
|
||||||
|
daysToSubtract = 30
|
||||||
|
} else if (timeRange === "7d") {
|
||||||
|
daysToSubtract = 7
|
||||||
|
}
|
||||||
|
const startDate = new Date(referenceDate)
|
||||||
|
startDate.setDate(startDate.getDate() - daysToSubtract)
|
||||||
|
return date >= startDate
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="@container/card">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Total Visitors</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
<span className="hidden @[540px]/card:block">
|
||||||
|
Total for the last 3 months
|
||||||
|
</span>
|
||||||
|
<span className="@[540px]/card:hidden">Last 3 months</span>
|
||||||
|
</CardDescription>
|
||||||
|
<CardAction>
|
||||||
|
<ToggleGroup
|
||||||
|
type="single"
|
||||||
|
value={timeRange}
|
||||||
|
onValueChange={setTimeRange}
|
||||||
|
variant="outline"
|
||||||
|
className="hidden *:data-[slot=toggle-group-item]:!px-4 @[767px]/card:flex"
|
||||||
|
>
|
||||||
|
<ToggleGroupItem value="90d">Last 3 months</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="30d">Last 30 days</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="7d">Last 7 days</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
<Select value={timeRange} onValueChange={setTimeRange}>
|
||||||
|
<SelectTrigger
|
||||||
|
className="flex w-40 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate @[767px]/card:hidden"
|
||||||
|
size="sm"
|
||||||
|
aria-label="Select a value"
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Last 3 months" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent className="rounded-xl">
|
||||||
|
<SelectItem value="90d" className="rounded-lg">
|
||||||
|
Last 3 months
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="30d" className="rounded-lg">
|
||||||
|
Last 30 days
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="7d" className="rounded-lg">
|
||||||
|
Last 7 days
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</CardAction>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="px-2 pt-4 sm:px-6 sm:pt-6">
|
||||||
|
<ChartContainer
|
||||||
|
config={chartConfig}
|
||||||
|
className="aspect-auto h-[250px] w-full"
|
||||||
|
>
|
||||||
|
<AreaChart data={filteredData}>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="fillDesktop" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop
|
||||||
|
offset="5%"
|
||||||
|
stopColor="var(--color-desktop)"
|
||||||
|
stopOpacity={1.0}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset="95%"
|
||||||
|
stopColor="var(--color-desktop)"
|
||||||
|
stopOpacity={0.1}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="fillMobile" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop
|
||||||
|
offset="5%"
|
||||||
|
stopColor="var(--color-mobile)"
|
||||||
|
stopOpacity={0.8}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset="95%"
|
||||||
|
stopColor="var(--color-mobile)"
|
||||||
|
stopOpacity={0.1}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<CartesianGrid vertical={false} />
|
||||||
|
<XAxis
|
||||||
|
dataKey="date"
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={false}
|
||||||
|
tickMargin={8}
|
||||||
|
minTickGap={32}
|
||||||
|
tickFormatter={(value) => {
|
||||||
|
const date = new Date(value)
|
||||||
|
return date.toLocaleDateString("en-US", {
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ChartTooltip
|
||||||
|
cursor={false}
|
||||||
|
defaultIndex={isMobile ? -1 : 10}
|
||||||
|
content={
|
||||||
|
<ChartTooltipContent
|
||||||
|
labelFormatter={(value) => {
|
||||||
|
return new Date(value).toLocaleDateString("en-US", {
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
indicator="dot"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
dataKey="mobile"
|
||||||
|
type="natural"
|
||||||
|
fill="url(#fillMobile)"
|
||||||
|
stroke="var(--color-mobile)"
|
||||||
|
stackId="a"
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
dataKey="desktop"
|
||||||
|
type="natural"
|
||||||
|
fill="url(#fillDesktop)"
|
||||||
|
stroke="var(--color-desktop)"
|
||||||
|
stackId="a"
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ChartContainer>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,807 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import {
|
||||||
|
DndContext,
|
||||||
|
KeyboardSensor,
|
||||||
|
MouseSensor,
|
||||||
|
TouchSensor,
|
||||||
|
closestCenter,
|
||||||
|
useSensor,
|
||||||
|
useSensors,
|
||||||
|
type DragEndEvent,
|
||||||
|
type UniqueIdentifier,
|
||||||
|
} from "@dnd-kit/core"
|
||||||
|
import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
|
||||||
|
import {
|
||||||
|
SortableContext,
|
||||||
|
arrayMove,
|
||||||
|
useSortable,
|
||||||
|
verticalListSortingStrategy,
|
||||||
|
} from "@dnd-kit/sortable"
|
||||||
|
import { CSS } from "@dnd-kit/utilities"
|
||||||
|
import {
|
||||||
|
IconChevronDown,
|
||||||
|
IconChevronLeft,
|
||||||
|
IconChevronRight,
|
||||||
|
IconChevronsLeft,
|
||||||
|
IconChevronsRight,
|
||||||
|
IconCircleCheckFilled,
|
||||||
|
IconDotsVertical,
|
||||||
|
IconGripVertical,
|
||||||
|
IconLayoutColumns,
|
||||||
|
IconLoader,
|
||||||
|
IconPlus,
|
||||||
|
IconTrendingUp,
|
||||||
|
} from "@tabler/icons-react"
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
Row,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFacetedRowModel,
|
||||||
|
getFacetedUniqueValues,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useReactTable,
|
||||||
|
} from "@tanstack/react-table"
|
||||||
|
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile"
|
||||||
|
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
import {
|
||||||
|
ChartConfig,
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
} from "@/registry/new-york-v4/ui/chart"
|
||||||
|
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
DrawerClose,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerDescription,
|
||||||
|
DrawerFooter,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerTitle,
|
||||||
|
DrawerTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/drawer"
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||||
|
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||||
|
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/registry/new-york-v4/ui/select"
|
||||||
|
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/registry/new-york-v4/ui/table"
|
||||||
|
import {
|
||||||
|
Tabs,
|
||||||
|
TabsContent,
|
||||||
|
TabsList,
|
||||||
|
TabsTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/tabs"
|
||||||
|
|
||||||
|
export const schema = z.object({
|
||||||
|
id: z.number(),
|
||||||
|
header: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
status: z.string(),
|
||||||
|
target: z.string(),
|
||||||
|
limit: z.string(),
|
||||||
|
reviewer: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a separate component for the drag handle
|
||||||
|
function DragHandle({ id }: { id: number }) {
|
||||||
|
const { attributes, listeners } = useSortable({
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="text-muted-foreground size-7 hover:bg-transparent"
|
||||||
|
>
|
||||||
|
<IconGripVertical className="text-muted-foreground size-3" />
|
||||||
|
<span className="sr-only">Drag to reorder</span>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||||
|
{
|
||||||
|
id: "drag",
|
||||||
|
header: () => null,
|
||||||
|
cell: ({ row }) => <DragHandle id={row.original.id} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "select",
|
||||||
|
header: ({ table }) => (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
table.getIsAllPageRowsSelected() ||
|
||||||
|
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||||
|
aria-label="Select all"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<Checkbox
|
||||||
|
checked={row.getIsSelected()}
|
||||||
|
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||||
|
aria-label="Select row"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "header",
|
||||||
|
header: "Header",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return <TableCellViewer item={row.original} />
|
||||||
|
},
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "type",
|
||||||
|
header: "Section Type",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="w-32">
|
||||||
|
<Badge variant="outline" className="text-muted-foreground px-1.5">
|
||||||
|
{row.original.type}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "status",
|
||||||
|
header: "Status",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<Badge variant="outline" className="text-muted-foreground px-1.5">
|
||||||
|
{row.original.status === "Done" ? (
|
||||||
|
<IconCircleCheckFilled className="fill-green-500 dark:fill-green-400" />
|
||||||
|
) : (
|
||||||
|
<IconLoader />
|
||||||
|
)}
|
||||||
|
{row.original.status}
|
||||||
|
</Badge>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "target",
|
||||||
|
header: () => <div className="w-full text-right">Target</div>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
||||||
|
loading: `Saving ${row.original.header}`,
|
||||||
|
success: "Done",
|
||||||
|
error: "Error",
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label htmlFor={`${row.original.id}-target`} className="sr-only">
|
||||||
|
Target
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
className="hover:bg-input/30 focus-visible:bg-background dark:hover:bg-input/30 dark:focus-visible:bg-input/30 h-8 w-16 border-transparent bg-transparent text-right shadow-none focus-visible:border dark:bg-transparent"
|
||||||
|
defaultValue={row.original.target}
|
||||||
|
id={`${row.original.id}-target`}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "limit",
|
||||||
|
header: () => <div className="w-full text-right">Limit</div>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
||||||
|
loading: `Saving ${row.original.header}`,
|
||||||
|
success: "Done",
|
||||||
|
error: "Error",
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label htmlFor={`${row.original.id}-limit`} className="sr-only">
|
||||||
|
Limit
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
className="hover:bg-input/30 focus-visible:bg-background dark:hover:bg-input/30 dark:focus-visible:bg-input/30 h-8 w-16 border-transparent bg-transparent text-right shadow-none focus-visible:border dark:bg-transparent"
|
||||||
|
defaultValue={row.original.limit}
|
||||||
|
id={`${row.original.id}-limit`}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "reviewer",
|
||||||
|
header: "Reviewer",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const isAssigned = row.original.reviewer !== "Assign reviewer"
|
||||||
|
|
||||||
|
if (isAssigned) {
|
||||||
|
return row.original.reviewer
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Label htmlFor={`${row.original.id}-reviewer`} className="sr-only">
|
||||||
|
Reviewer
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger
|
||||||
|
className="w-38 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate"
|
||||||
|
size="sm"
|
||||||
|
id={`${row.original.id}-reviewer`}
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Assign reviewer" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent align="end">
|
||||||
|
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
|
||||||
|
<SelectItem value="Jamik Tashpulatov">
|
||||||
|
Jamik Tashpulatov
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
cell: () => (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="data-[state=open]:bg-muted text-muted-foreground flex size-8"
|
||||||
|
size="icon"
|
||||||
|
>
|
||||||
|
<IconDotsVertical />
|
||||||
|
<span className="sr-only">Open menu</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-32">
|
||||||
|
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Make a copy</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Favorite</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {
|
||||||
|
const { transform, transition, setNodeRef, isDragging } = useSortable({
|
||||||
|
id: row.original.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
data-state={row.getIsSelected() && "selected"}
|
||||||
|
data-dragging={isDragging}
|
||||||
|
ref={setNodeRef}
|
||||||
|
className="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
|
||||||
|
style={{
|
||||||
|
transform: CSS.Transform.toString(transform),
|
||||||
|
transition: transition,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DataTable({
|
||||||
|
data: initialData,
|
||||||
|
}: {
|
||||||
|
data: z.infer<typeof schema>[]
|
||||||
|
}) {
|
||||||
|
const [data, setData] = React.useState(() => initialData)
|
||||||
|
const [rowSelection, setRowSelection] = React.useState({})
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
React.useState<VisibilityState>({})
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||||
|
const [pagination, setPagination] = React.useState({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
})
|
||||||
|
const sortableId = React.useId()
|
||||||
|
const sensors = useSensors(
|
||||||
|
useSensor(MouseSensor, {}),
|
||||||
|
useSensor(TouchSensor, {}),
|
||||||
|
useSensor(KeyboardSensor, {})
|
||||||
|
)
|
||||||
|
|
||||||
|
const dataIds = React.useMemo<UniqueIdentifier[]>(
|
||||||
|
() => data?.map(({ id }) => id) || [],
|
||||||
|
[data]
|
||||||
|
)
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
columnVisibility,
|
||||||
|
rowSelection,
|
||||||
|
columnFilters,
|
||||||
|
pagination,
|
||||||
|
},
|
||||||
|
getRowId: (row) => row.id.toString(),
|
||||||
|
enableRowSelection: true,
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
onColumnFiltersChange: setColumnFilters,
|
||||||
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFacetedRowModel: getFacetedRowModel(),
|
||||||
|
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleDragEnd(event: DragEndEvent) {
|
||||||
|
const { active, over } = event
|
||||||
|
if (active && over && active.id !== over.id) {
|
||||||
|
setData((data) => {
|
||||||
|
const oldIndex = dataIds.indexOf(active.id)
|
||||||
|
const newIndex = dataIds.indexOf(over.id)
|
||||||
|
return arrayMove(data, oldIndex, newIndex)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs
|
||||||
|
defaultValue="outline"
|
||||||
|
className="w-full flex-col justify-start gap-6"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between px-4 lg:px-6">
|
||||||
|
<Label htmlFor="view-selector" className="sr-only">
|
||||||
|
View
|
||||||
|
</Label>
|
||||||
|
<Select defaultValue="outline">
|
||||||
|
<SelectTrigger
|
||||||
|
className="flex w-fit @4xl/main:hidden"
|
||||||
|
size="sm"
|
||||||
|
id="view-selector"
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Select a view" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="outline">Outline</SelectItem>
|
||||||
|
<SelectItem value="past-performance">Past Performance</SelectItem>
|
||||||
|
<SelectItem value="key-personnel">Key Personnel</SelectItem>
|
||||||
|
<SelectItem value="focus-documents">Focus Documents</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<TabsList className="**:data-[slot=badge]:bg-muted-foreground/30 hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:px-1 @4xl/main:flex">
|
||||||
|
<TabsTrigger value="outline">Outline</TabsTrigger>
|
||||||
|
<TabsTrigger value="past-performance">
|
||||||
|
Past Performance <Badge variant="secondary">3</Badge>
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="key-personnel">
|
||||||
|
Key Personnel <Badge variant="secondary">2</Badge>
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="focus-documents">Focus Documents</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<IconLayoutColumns />
|
||||||
|
<span className="hidden lg:inline">Customize Columns</span>
|
||||||
|
<span className="lg:hidden">Columns</span>
|
||||||
|
<IconChevronDown />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-56">
|
||||||
|
{table
|
||||||
|
.getAllColumns()
|
||||||
|
.filter(
|
||||||
|
(column) =>
|
||||||
|
typeof column.accessorFn !== "undefined" &&
|
||||||
|
column.getCanHide()
|
||||||
|
)
|
||||||
|
.map((column) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
key={column.id}
|
||||||
|
className="capitalize"
|
||||||
|
checked={column.getIsVisible()}
|
||||||
|
onCheckedChange={(value) =>
|
||||||
|
column.toggleVisibility(!!value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{column.id}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<IconPlus />
|
||||||
|
<span className="hidden lg:inline">Add Section</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<TabsContent
|
||||||
|
value="outline"
|
||||||
|
className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6"
|
||||||
|
>
|
||||||
|
<div className="overflow-hidden rounded-lg border">
|
||||||
|
<DndContext
|
||||||
|
collisionDetection={closestCenter}
|
||||||
|
modifiers={[restrictToVerticalAxis]}
|
||||||
|
onDragEnd={handleDragEnd}
|
||||||
|
sensors={sensors}
|
||||||
|
id={sortableId}
|
||||||
|
>
|
||||||
|
<Table>
|
||||||
|
<TableHeader className="bg-muted sticky top-0 z-10">
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<TableHead key={header.id} colSpan={header.colSpan}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody className="**:data-[slot=table-cell]:first:w-8">
|
||||||
|
{table.getRowModel().rows?.length ? (
|
||||||
|
<SortableContext
|
||||||
|
items={dataIds}
|
||||||
|
strategy={verticalListSortingStrategy}
|
||||||
|
>
|
||||||
|
{table.getRowModel().rows.map((row) => (
|
||||||
|
<DraggableRow key={row.id} row={row} />
|
||||||
|
))}
|
||||||
|
</SortableContext>
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={columns.length}
|
||||||
|
className="h-24 text-center"
|
||||||
|
>
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</DndContext>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between px-4">
|
||||||
|
<div className="text-muted-foreground hidden flex-1 text-sm lg:flex">
|
||||||
|
{table.getFilteredSelectedRowModel().rows.length} of{" "}
|
||||||
|
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full items-center gap-8 lg:w-fit">
|
||||||
|
<div className="hidden items-center gap-2 lg:flex">
|
||||||
|
<Label htmlFor="rows-per-page" className="text-sm font-medium">
|
||||||
|
Rows per page
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={`${table.getState().pagination.pageSize}`}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
table.setPageSize(Number(value))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger size="sm" className="w-20" id="rows-per-page">
|
||||||
|
<SelectValue
|
||||||
|
placeholder={table.getState().pagination.pageSize}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent side="top">
|
||||||
|
{[10, 20, 30, 40, 50].map((pageSize) => (
|
||||||
|
<SelectItem key={pageSize} value={`${pageSize}`}>
|
||||||
|
{pageSize}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-fit items-center justify-center text-sm font-medium">
|
||||||
|
Page {table.getState().pagination.pageIndex + 1} of{" "}
|
||||||
|
{table.getPageCount()}
|
||||||
|
</div>
|
||||||
|
<div className="ml-auto flex items-center gap-2 lg:ml-0">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="hidden h-8 w-8 p-0 lg:flex"
|
||||||
|
onClick={() => table.setPageIndex(0)}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to first page</span>
|
||||||
|
<IconChevronsLeft />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="size-8"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to previous page</span>
|
||||||
|
<IconChevronLeft />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="size-8"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to next page</span>
|
||||||
|
<IconChevronRight />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="hidden size-8 lg:flex"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to last page</span>
|
||||||
|
<IconChevronsRight />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent
|
||||||
|
value="past-performance"
|
||||||
|
className="flex flex-col px-4 lg:px-6"
|
||||||
|
>
|
||||||
|
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="key-personnel" className="flex flex-col px-4 lg:px-6">
|
||||||
|
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent
|
||||||
|
value="focus-documents"
|
||||||
|
className="flex flex-col px-4 lg:px-6"
|
||||||
|
>
|
||||||
|
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartData = [
|
||||||
|
{ month: "January", desktop: 186, mobile: 80 },
|
||||||
|
{ month: "February", desktop: 305, mobile: 200 },
|
||||||
|
{ month: "March", desktop: 237, mobile: 120 },
|
||||||
|
{ month: "April", desktop: 73, mobile: 190 },
|
||||||
|
{ month: "May", desktop: 209, mobile: 130 },
|
||||||
|
{ month: "June", desktop: 214, mobile: 140 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
desktop: {
|
||||||
|
label: "Desktop",
|
||||||
|
color: "var(--primary)",
|
||||||
|
},
|
||||||
|
mobile: {
|
||||||
|
label: "Mobile",
|
||||||
|
color: "var(--primary)",
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig
|
||||||
|
|
||||||
|
function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
|
||||||
|
const isMobile = useIsMobile()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer direction={isMobile ? "bottom" : "right"}>
|
||||||
|
<DrawerTrigger asChild>
|
||||||
|
<Button variant="link" className="text-foreground w-fit px-0 text-left">
|
||||||
|
{item.header}
|
||||||
|
</Button>
|
||||||
|
</DrawerTrigger>
|
||||||
|
<DrawerContent>
|
||||||
|
<DrawerHeader className="gap-1">
|
||||||
|
<DrawerTitle>{item.header}</DrawerTitle>
|
||||||
|
<DrawerDescription>
|
||||||
|
Showing total visitors for the last 6 months
|
||||||
|
</DrawerDescription>
|
||||||
|
</DrawerHeader>
|
||||||
|
<div className="flex flex-col gap-4 overflow-y-auto px-4 text-sm">
|
||||||
|
{!isMobile && (
|
||||||
|
<>
|
||||||
|
<ChartContainer config={chartConfig}>
|
||||||
|
<AreaChart
|
||||||
|
accessibilityLayer
|
||||||
|
data={chartData}
|
||||||
|
margin={{
|
||||||
|
left: 0,
|
||||||
|
right: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CartesianGrid vertical={false} />
|
||||||
|
<XAxis
|
||||||
|
dataKey="month"
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={false}
|
||||||
|
tickMargin={8}
|
||||||
|
tickFormatter={(value) => value.slice(0, 3)}
|
||||||
|
hide
|
||||||
|
/>
|
||||||
|
<ChartTooltip
|
||||||
|
cursor={false}
|
||||||
|
content={<ChartTooltipContent indicator="dot" />}
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
dataKey="mobile"
|
||||||
|
type="natural"
|
||||||
|
fill="var(--color-mobile)"
|
||||||
|
fillOpacity={0.6}
|
||||||
|
stroke="var(--color-mobile)"
|
||||||
|
stackId="a"
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
dataKey="desktop"
|
||||||
|
type="natural"
|
||||||
|
fill="var(--color-desktop)"
|
||||||
|
fillOpacity={0.4}
|
||||||
|
stroke="var(--color-desktop)"
|
||||||
|
stackId="a"
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ChartContainer>
|
||||||
|
<Separator />
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<div className="flex gap-2 leading-none font-medium">
|
||||||
|
Trending up by 5.2% this month{" "}
|
||||||
|
<IconTrendingUp className="size-4" />
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
Showing total visitors for the last 6 months. This is just
|
||||||
|
some random text to test the layout. It spans multiple lines
|
||||||
|
and should wrap around.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<form className="flex flex-col gap-4">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="header">Header</Label>
|
||||||
|
<Input id="header" defaultValue={item.header} />
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="type">Type</Label>
|
||||||
|
<Select defaultValue={item.type}>
|
||||||
|
<SelectTrigger id="type" className="w-full">
|
||||||
|
<SelectValue placeholder="Select a type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Table of Contents">
|
||||||
|
Table of Contents
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Executive Summary">
|
||||||
|
Executive Summary
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Technical Approach">
|
||||||
|
Technical Approach
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Design">Design</SelectItem>
|
||||||
|
<SelectItem value="Capabilities">Capabilities</SelectItem>
|
||||||
|
<SelectItem value="Focus Documents">
|
||||||
|
Focus Documents
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Narrative">Narrative</SelectItem>
|
||||||
|
<SelectItem value="Cover Page">Cover Page</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="status">Status</Label>
|
||||||
|
<Select defaultValue={item.status}>
|
||||||
|
<SelectTrigger id="status" className="w-full">
|
||||||
|
<SelectValue placeholder="Select a status" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Done">Done</SelectItem>
|
||||||
|
<SelectItem value="In Progress">In Progress</SelectItem>
|
||||||
|
<SelectItem value="Not Started">Not Started</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="target">Target</Label>
|
||||||
|
<Input id="target" defaultValue={item.target} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="limit">Limit</Label>
|
||||||
|
<Input id="limit" defaultValue={item.limit} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="reviewer">Reviewer</Label>
|
||||||
|
<Select defaultValue={item.reviewer}>
|
||||||
|
<SelectTrigger id="reviewer" className="w-full">
|
||||||
|
<SelectValue placeholder="Select a reviewer" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
|
||||||
|
<SelectItem value="Jamik Tashpulatov">
|
||||||
|
Jamik Tashpulatov
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Emily Whalen">Emily Whalen</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<DrawerFooter>
|
||||||
|
<Button>Submit</Button>
|
||||||
|
<DrawerClose asChild>
|
||||||
|
<Button variant="outline">Done</Button>
|
||||||
|
</DrawerClose>
|
||||||
|
</DrawerFooter>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import {
|
||||||
|
IconDots,
|
||||||
|
IconFolder,
|
||||||
|
IconShare3,
|
||||||
|
IconTrash,
|
||||||
|
type Icon,
|
||||||
|
} from "@tabler/icons-react"
|
||||||
|
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||||
|
import {
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupLabel,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuAction,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
useSidebar,
|
||||||
|
} from "@/registry/new-york-v4/ui/sidebar"
|
||||||
|
|
||||||
|
export function NavDocuments({
|
||||||
|
items,
|
||||||
|
}: {
|
||||||
|
items: {
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
icon: Icon
|
||||||
|
}[]
|
||||||
|
}) {
|
||||||
|
const { isMobile } = useSidebar()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||||
|
<SidebarGroupLabel>Documents</SidebarGroupLabel>
|
||||||
|
<SidebarMenu>
|
||||||
|
{items.map((item) => (
|
||||||
|
<SidebarMenuItem key={item.name}>
|
||||||
|
<SidebarMenuButton asChild>
|
||||||
|
<a href={item.url}>
|
||||||
|
<item.icon />
|
||||||
|
<span>{item.name}</span>
|
||||||
|
</a>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<SidebarMenuAction
|
||||||
|
showOnHover
|
||||||
|
className="data-[state=open]:bg-accent rounded-sm"
|
||||||
|
>
|
||||||
|
<IconDots />
|
||||||
|
<span className="sr-only">More</span>
|
||||||
|
</SidebarMenuAction>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
className="w-24 rounded-lg"
|
||||||
|
side={isMobile ? "bottom" : "right"}
|
||||||
|
align={isMobile ? "end" : "start"}
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<IconFolder />
|
||||||
|
<span>Open</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<IconShare3 />
|
||||||
|
<span>Share</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem variant="destructive">
|
||||||
|
<IconTrash />
|
||||||
|
<span>Delete</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
))}
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton className="text-sidebar-foreground/70">
|
||||||
|
<IconDots className="text-sidebar-foreground/70" />
|
||||||
|
<span>More</span>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
"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) => (
|
||||||
|
<SidebarMenuItem key={item.title}>
|
||||||
|
<SidebarMenuButton tooltip={item.title}>
|
||||||
|
{item.icon && <item.icon />}
|
||||||
|
<span>{item.title}</span>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroupContent>
|
||||||
|
</SidebarGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { type Icon } from "@tabler/icons-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: Icon
|
||||||
|
}[]
|
||||||
|
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||||
|
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>
|
||||||
|
</SidebarGroupContent>
|
||||||
|
</SidebarGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
"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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
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"
|
||||||
|
|
||||||
|
export function SectionCards() {
|
||||||
|
return (
|
||||||
|
<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-1.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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
className="dark:text-foreground"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
614
apps/v4/registry/new-york-v4/blocks/dashboard-01/data.json
Normal file
@@ -0,0 +1,614 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"header": "Cover page",
|
||||||
|
"type": "Cover page",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "18",
|
||||||
|
"limit": "5",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"header": "Table of contents",
|
||||||
|
"type": "Table of contents",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "29",
|
||||||
|
"limit": "24",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"header": "Executive summary",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "10",
|
||||||
|
"limit": "13",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"header": "Technical approach",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "27",
|
||||||
|
"limit": "23",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"header": "Design",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "2",
|
||||||
|
"limit": "16",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"header": "Capabilities",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "20",
|
||||||
|
"limit": "8",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"header": "Integration with existing systems",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "19",
|
||||||
|
"limit": "21",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"header": "Innovation and Advantages",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "25",
|
||||||
|
"limit": "26",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"header": "Overview of EMR's Innovative Solutions",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "7",
|
||||||
|
"limit": "23",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"header": "Advanced Algorithms and Machine Learning",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "30",
|
||||||
|
"limit": "28",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"header": "Adaptive Communication Protocols",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "9",
|
||||||
|
"limit": "31",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"header": "Advantages Over Current Technologies",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "12",
|
||||||
|
"limit": "0",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"header": "Past Performance",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "22",
|
||||||
|
"limit": "33",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"header": "Customer Feedback and Satisfaction Levels",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "15",
|
||||||
|
"limit": "34",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"header": "Implementation Challenges and Solutions",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "3",
|
||||||
|
"limit": "35",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"header": "Security Measures and Data Protection Policies",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "6",
|
||||||
|
"limit": "36",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"header": "Scalability and Future Proofing",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "4",
|
||||||
|
"limit": "37",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"header": "Cost-Benefit Analysis",
|
||||||
|
"type": "Plain language",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "14",
|
||||||
|
"limit": "38",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"header": "User Training and Onboarding Experience",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "17",
|
||||||
|
"limit": "39",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"header": "Future Development Roadmap",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "11",
|
||||||
|
"limit": "40",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"header": "System Architecture Overview",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "24",
|
||||||
|
"limit": "18",
|
||||||
|
"reviewer": "Maya Johnson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"header": "Risk Management Plan",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "15",
|
||||||
|
"limit": "22",
|
||||||
|
"reviewer": "Carlos Rodriguez"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"header": "Compliance Documentation",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "31",
|
||||||
|
"limit": "27",
|
||||||
|
"reviewer": "Sarah Chen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 24,
|
||||||
|
"header": "API Documentation",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "8",
|
||||||
|
"limit": "12",
|
||||||
|
"reviewer": "Raj Patel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"header": "User Interface Mockups",
|
||||||
|
"type": "Visual",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "19",
|
||||||
|
"limit": "25",
|
||||||
|
"reviewer": "Leila Ahmadi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26,
|
||||||
|
"header": "Database Schema",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "22",
|
||||||
|
"limit": "20",
|
||||||
|
"reviewer": "Thomas Wilson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"header": "Testing Methodology",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "17",
|
||||||
|
"limit": "14",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 28,
|
||||||
|
"header": "Deployment Strategy",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "26",
|
||||||
|
"limit": "30",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 29,
|
||||||
|
"header": "Budget Breakdown",
|
||||||
|
"type": "Financial",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "13",
|
||||||
|
"limit": "16",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 30,
|
||||||
|
"header": "Market Analysis",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "29",
|
||||||
|
"limit": "32",
|
||||||
|
"reviewer": "Sophia Martinez"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 31,
|
||||||
|
"header": "Competitor Comparison",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "21",
|
||||||
|
"limit": "19",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 32,
|
||||||
|
"header": "Maintenance Plan",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "16",
|
||||||
|
"limit": "23",
|
||||||
|
"reviewer": "Alex Thompson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 33,
|
||||||
|
"header": "User Personas",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "27",
|
||||||
|
"limit": "24",
|
||||||
|
"reviewer": "Nina Patel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 34,
|
||||||
|
"header": "Accessibility Compliance",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "18",
|
||||||
|
"limit": "21",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 35,
|
||||||
|
"header": "Performance Metrics",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "23",
|
||||||
|
"limit": "26",
|
||||||
|
"reviewer": "David Kim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 36,
|
||||||
|
"header": "Disaster Recovery Plan",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "14",
|
||||||
|
"limit": "17",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 37,
|
||||||
|
"header": "Third-party Integrations",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "25",
|
||||||
|
"limit": "28",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 38,
|
||||||
|
"header": "User Feedback Summary",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "20",
|
||||||
|
"limit": "15",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 39,
|
||||||
|
"header": "Localization Strategy",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "12",
|
||||||
|
"limit": "19",
|
||||||
|
"reviewer": "Maria Garcia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 40,
|
||||||
|
"header": "Mobile Compatibility",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "28",
|
||||||
|
"limit": "31",
|
||||||
|
"reviewer": "James Wilson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 41,
|
||||||
|
"header": "Data Migration Plan",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "19",
|
||||||
|
"limit": "22",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 42,
|
||||||
|
"header": "Quality Assurance Protocols",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "30",
|
||||||
|
"limit": "33",
|
||||||
|
"reviewer": "Priya Singh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 43,
|
||||||
|
"header": "Stakeholder Analysis",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "11",
|
||||||
|
"limit": "14",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 44,
|
||||||
|
"header": "Environmental Impact Assessment",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "24",
|
||||||
|
"limit": "27",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 45,
|
||||||
|
"header": "Intellectual Property Rights",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "17",
|
||||||
|
"limit": "20",
|
||||||
|
"reviewer": "Sarah Johnson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 46,
|
||||||
|
"header": "Customer Support Framework",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "22",
|
||||||
|
"limit": "25",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 47,
|
||||||
|
"header": "Version Control Strategy",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "15",
|
||||||
|
"limit": "18",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 48,
|
||||||
|
"header": "Continuous Integration Pipeline",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "26",
|
||||||
|
"limit": "29",
|
||||||
|
"reviewer": "Michael Chen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 49,
|
||||||
|
"header": "Regulatory Compliance",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "13",
|
||||||
|
"limit": "16",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 50,
|
||||||
|
"header": "User Authentication System",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "28",
|
||||||
|
"limit": "31",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 51,
|
||||||
|
"header": "Data Analytics Framework",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "21",
|
||||||
|
"limit": "24",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 52,
|
||||||
|
"header": "Cloud Infrastructure",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "16",
|
||||||
|
"limit": "19",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 53,
|
||||||
|
"header": "Network Security Measures",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "29",
|
||||||
|
"limit": "32",
|
||||||
|
"reviewer": "Lisa Wong"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 54,
|
||||||
|
"header": "Project Timeline",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "14",
|
||||||
|
"limit": "17",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 55,
|
||||||
|
"header": "Resource Allocation",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "27",
|
||||||
|
"limit": "30",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 56,
|
||||||
|
"header": "Team Structure and Roles",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "20",
|
||||||
|
"limit": "23",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 57,
|
||||||
|
"header": "Communication Protocols",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "15",
|
||||||
|
"limit": "18",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 58,
|
||||||
|
"header": "Success Metrics",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "30",
|
||||||
|
"limit": "33",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 59,
|
||||||
|
"header": "Internationalization Support",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "23",
|
||||||
|
"limit": "26",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 60,
|
||||||
|
"header": "Backup and Recovery Procedures",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "18",
|
||||||
|
"limit": "21",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 61,
|
||||||
|
"header": "Monitoring and Alerting System",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "25",
|
||||||
|
"limit": "28",
|
||||||
|
"reviewer": "Daniel Park"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 62,
|
||||||
|
"header": "Code Review Guidelines",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "12",
|
||||||
|
"limit": "15",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 63,
|
||||||
|
"header": "Documentation Standards",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "27",
|
||||||
|
"limit": "30",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 64,
|
||||||
|
"header": "Release Management Process",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "22",
|
||||||
|
"limit": "25",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 65,
|
||||||
|
"header": "Feature Prioritization Matrix",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "19",
|
||||||
|
"limit": "22",
|
||||||
|
"reviewer": "Emma Davis"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 66,
|
||||||
|
"header": "Technical Debt Assessment",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "24",
|
||||||
|
"limit": "27",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 67,
|
||||||
|
"header": "Capacity Planning",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "21",
|
||||||
|
"limit": "24",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 68,
|
||||||
|
"header": "Service Level Agreements",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "26",
|
||||||
|
"limit": "29",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
}
|
||||||
|
]
|
||||||
40
apps/v4/registry/new-york-v4/blocks/dashboard-01/page.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { AppSidebar } from "@/registry/new-york-v4/blocks/dashboard-01/components/app-sidebar"
|
||||||
|
import { ChartAreaInteractive } from "@/registry/new-york-v4/blocks/dashboard-01/components/chart-area-interactive"
|
||||||
|
import { DataTable } from "@/registry/new-york-v4/blocks/dashboard-01/components/data-table"
|
||||||
|
import { SectionCards } from "@/registry/new-york-v4/blocks/dashboard-01/components/section-cards"
|
||||||
|
import { SiteHeader } from "@/registry/new-york-v4/blocks/dashboard-01/components/site-header"
|
||||||
|
import {
|
||||||
|
SidebarInset,
|
||||||
|
SidebarProvider,
|
||||||
|
} from "@/registry/new-york-v4/ui/sidebar"
|
||||||
|
|
||||||
|
import data from "./data.json"
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<SidebarProvider
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--sidebar-width": "calc(var(--spacing) * 72)",
|
||||||
|
"--header-height": "calc(var(--spacing) * 12)",
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AppSidebar variant="inset" />
|
||||||
|
<SidebarInset>
|
||||||
|
<SiteHeader />
|
||||||
|
<div className="flex flex-1 flex-col">
|
||||||
|
<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">
|
||||||
|
<SectionCards />
|
||||||
|
<div className="px-4 lg:px-6">
|
||||||
|
<ChartAreaInteractive />
|
||||||
|
</div>
|
||||||
|
<DataTable data={data} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SidebarInset>
|
||||||
|
</SidebarProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -47,7 +47,7 @@ export function LoginForm({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t">
|
<div className="after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t">
|
||||||
<span className="bg-background text-muted-foreground relative z-10 px-2">
|
<span className="bg-card text-muted-foreground relative z-10 px-2">
|
||||||
Or continue with
|
Or continue with
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||