Compare commits
9 Commits
shadcn@2.4
...
shadcn/min
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e10567393d | ||
|
|
6382edc9bf | ||
|
|
1befb7a653 | ||
|
|
e8f245812f | ||
|
|
c90bcef401 | ||
|
|
7d97a9eba3 | ||
|
|
2b23bdcbec | ||
|
|
bd09f57cb7 | ||
|
|
a3fee32c96 |
5
.changeset/brave-cheetahs-smile.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add support for TanStack Start
|
||||||
5
.changeset/cold-impalas-call.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
support for version detection in monorepo
|
||||||
5
.changeset/cool-mails-bake.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
upgrade @antfu/ni
|
||||||
5
.changeset/curvy-taxis-help.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
do not add ring for v3
|
||||||
5
.changeset/few-houses-impress.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add theme vars support
|
||||||
5
.changeset/five-hounds-tell.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add tailwind version detection
|
||||||
5
.changeset/fresh-cherries-brush.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add support for tailwind v4
|
||||||
5
.changeset/great-olives-flow.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
default to css vars. add --no-css-variables
|
||||||
5
.changeset/green-eels-shout.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
cache registry calls
|
||||||
5
.changeset/new-cheetahs-dance.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
default for new-york for v4
|
||||||
5
.changeset/nine-llamas-sell.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
fix handling of sidebar colors
|
||||||
5
.changeset/ninety-needles-brake.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
do not overwrite user defined vars
|
||||||
5
.changeset/orange-papayas-relax.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix cn import bug in monorepo
|
||||||
5
.changeset/proud-snails-switch.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
filter out deprecated from --all
|
||||||
5
.changeset/quiet-grapes-poke.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add oklch base color
|
||||||
5
.changeset/serious-geese-reply.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
hotswap style for v4
|
||||||
5
.changeset/shaggy-months-tease.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
check for empty css vars
|
||||||
5
.changeset/slow-tools-relax.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add warning for deprecated components
|
||||||
5
.changeset/spotty-plants-juggle.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix tanstack check
|
||||||
5
.changeset/tasty-walls-drum.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add support for route install for react-router and laravel
|
||||||
3
.github/FUNDING.yml
vendored
@@ -1,3 +0,0 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: [shadcn]
|
|
||||||
6
.github/dependabot.yml
vendored
@@ -1,6 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# 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,101 +1081,6 @@ 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,13 +2,8 @@ import { FormsDemo } from "@/components/forms-demo"
|
|||||||
|
|
||||||
export default function FormsPage() {
|
export default function FormsPage() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col items-center justify-center gap-12 p-4 lg:flex-row">
|
<div className="flex flex-1 items-center justify-center p-4">
|
||||||
<div className="">
|
<FormsDemo />
|
||||||
<FormsDemo />
|
|
||||||
</div>
|
|
||||||
<div className="theme-scaled">
|
|
||||||
<FormsDemo />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ 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,
|
||||||
@@ -32,7 +31,6 @@ 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 dark:bg-background flex flex-1 flex-col items-center justify-center gap-16 p-6 md:p-10",
|
"bg-muted 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
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import {
|
|
||||||
ChartLineIcon,
|
|
||||||
FileIcon,
|
|
||||||
HomeIcon,
|
|
||||||
LifeBuoy,
|
|
||||||
Send,
|
|
||||||
Settings2Icon,
|
|
||||||
ShoppingBagIcon,
|
|
||||||
ShoppingCartIcon,
|
|
||||||
UserIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import { Sidebar, SidebarContent } from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
import { NavMain } from "@/app/(examples)/dashboard-03/components/nav-main"
|
|
||||||
import { NavSecondary } from "@/app/(examples)/dashboard-03/components/nav-secondary"
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
navMain: [
|
|
||||||
{
|
|
||||||
title: "Dashboard",
|
|
||||||
url: "/dashboard",
|
|
||||||
icon: HomeIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Analytics",
|
|
||||||
url: "/dashboard/analytics",
|
|
||||||
icon: ChartLineIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Orders",
|
|
||||||
url: "/dashboard/orders",
|
|
||||||
icon: ShoppingBagIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Products",
|
|
||||||
url: "/dashboard/products",
|
|
||||||
icon: ShoppingCartIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Invoices",
|
|
||||||
url: "/dashboard/invoices",
|
|
||||||
icon: FileIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Customers",
|
|
||||||
url: "/dashboard/customers",
|
|
||||||
icon: UserIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Settings",
|
|
||||||
url: "/dashboard/settings",
|
|
||||||
icon: Settings2Icon,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
navSecondary: [
|
|
||||||
{
|
|
||||||
title: "Support",
|
|
||||||
url: "#",
|
|
||||||
icon: LifeBuoy,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Feedback",
|
|
||||||
url: "#",
|
|
||||||
icon: Send,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|
||||||
return (
|
|
||||||
<Sidebar
|
|
||||||
className="top-(--header-height) h-[calc(100svh-var(--header-height))]!"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<SidebarContent>
|
|
||||||
<NavMain items={data.navMain} />
|
|
||||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
|
||||||
</SidebarContent>
|
|
||||||
</Sidebar>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,500 +0,0 @@
|
|||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import {
|
|
||||||
IconCamera,
|
|
||||||
IconChartBar,
|
|
||||||
IconDashboard,
|
|
||||||
IconDatabase,
|
|
||||||
IconFileAi,
|
|
||||||
IconFileDescription,
|
|
||||||
IconFileWord,
|
|
||||||
IconFolder,
|
|
||||||
IconHelp,
|
|
||||||
IconInnerShadowTop,
|
|
||||||
IconListDetails,
|
|
||||||
IconReport,
|
|
||||||
IconSearch,
|
|
||||||
IconSettings,
|
|
||||||
IconUsers,
|
|
||||||
} from "@tabler/icons-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Sidebar,
|
|
||||||
SidebarContent,
|
|
||||||
SidebarFooter,
|
|
||||||
SidebarHeader,
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
import { 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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,807 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { IconCirclePlusFilled, IconMail, type Icon } from "@tabler/icons-react"
|
|
||||||
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import {
|
|
||||||
SidebarGroup,
|
|
||||||
SidebarGroupContent,
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
|
|
||||||
export function NavMain({
|
|
||||||
items,
|
|
||||||
}: {
|
|
||||||
items: {
|
|
||||||
title: string
|
|
||||||
url: string
|
|
||||||
icon?: Icon
|
|
||||||
}[]
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<SidebarGroup>
|
|
||||||
<SidebarGroupContent className="flex flex-col gap-2">
|
|
||||||
<SidebarMenu>
|
|
||||||
<SidebarMenuItem className="flex items-center gap-2">
|
|
||||||
<SidebarMenuButton
|
|
||||||
tooltip="Quick Create"
|
|
||||||
className="bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground min-w-8 duration-200 ease-linear"
|
|
||||||
>
|
|
||||||
<IconCirclePlusFilled />
|
|
||||||
<span>Quick Create</span>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
className="size-8 group-data-[collapsible=icon]:opacity-0"
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
<IconMail />
|
|
||||||
<span className="sr-only">Inbox</span>
|
|
||||||
</Button>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
<SidebarMenu>
|
|
||||||
{items.map((item) => (
|
|
||||||
<SidebarMenuItem key={item.title}>
|
|
||||||
<SidebarMenuButton tooltip={item.title}>
|
|
||||||
{item.icon && <item.icon />}
|
|
||||||
<span>{item.title}</span>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
))}
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarGroupContent>
|
|
||||||
</SidebarGroup>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { IconBrightness, type Icon } from "@tabler/icons-react"
|
|
||||||
import { useTheme } from "next-themes"
|
|
||||||
|
|
||||||
import {
|
|
||||||
SidebarGroup,
|
|
||||||
SidebarGroupContent,
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
import { Skeleton } from "@/registry/new-york-v4/ui/skeleton"
|
|
||||||
import { Switch } from "@/registry/new-york-v4/ui/switch"
|
|
||||||
|
|
||||||
export function NavSecondary({
|
|
||||||
items,
|
|
||||||
...props
|
|
||||||
}: {
|
|
||||||
items: {
|
|
||||||
title: string
|
|
||||||
url: string
|
|
||||||
icon: Icon
|
|
||||||
}[]
|
|
||||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
|
||||||
const { resolvedTheme, setTheme } = useTheme()
|
|
||||||
const [mounted, setMounted] = React.useState(false)
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
setMounted(true)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SidebarGroup {...props}>
|
|
||||||
<SidebarGroupContent>
|
|
||||||
<SidebarMenu>
|
|
||||||
{items.map((item) => (
|
|
||||||
<SidebarMenuItem key={item.title}>
|
|
||||||
<SidebarMenuButton asChild>
|
|
||||||
<a href={item.url}>
|
|
||||||
<item.icon />
|
|
||||||
<span>{item.title}</span>
|
|
||||||
</a>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
))}
|
|
||||||
<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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import {
|
|
||||||
IconCreditCard,
|
|
||||||
IconDotsVertical,
|
|
||||||
IconLogout,
|
|
||||||
IconNotification,
|
|
||||||
IconUserCircle,
|
|
||||||
} from "@tabler/icons-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Avatar,
|
|
||||||
AvatarFallback,
|
|
||||||
AvatarImage,
|
|
||||||
} from "@/registry/new-york-v4/ui/avatar"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuGroup,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
|
||||||
import {
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
useSidebar,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
|
|
||||||
export function NavUser({
|
|
||||||
user,
|
|
||||||
}: {
|
|
||||||
user: {
|
|
||||||
name: string
|
|
||||||
email: string
|
|
||||||
avatar: string
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
const { isMobile } = useSidebar()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SidebarMenu>
|
|
||||||
<SidebarMenuItem>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<SidebarMenuButton
|
|
||||||
size="lg"
|
|
||||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
||||||
>
|
|
||||||
<Avatar className="h-8 w-8 rounded-lg grayscale">
|
|
||||||
<AvatarImage src={user.avatar} alt={user.name} />
|
|
||||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
||||||
<span className="truncate font-medium">{user.name}</span>
|
|
||||||
<span className="text-muted-foreground truncate text-xs">
|
|
||||||
{user.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<IconDotsVertical className="ml-auto size-4" />
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent
|
|
||||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
|
||||||
side={isMobile ? "bottom" : "right"}
|
|
||||||
align="end"
|
|
||||||
sideOffset={4}
|
|
||||||
>
|
|
||||||
<DropdownMenuLabel className="p-0 font-normal">
|
|
||||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
|
||||||
<Avatar className="h-8 w-8 rounded-lg">
|
|
||||||
<AvatarImage src={user.avatar} alt={user.name} />
|
|
||||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
||||||
<span className="truncate font-medium">{user.name}</span>
|
|
||||||
<span className="text-muted-foreground truncate text-xs">
|
|
||||||
{user.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DropdownMenuLabel>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconUserCircle />
|
|
||||||
Account
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconCreditCard />
|
|
||||||
Billing
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconNotification />
|
|
||||||
Notifications
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconLogout />
|
|
||||||
Log out
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
|
||||||
import { SidebarTrigger } from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
import { ModeToggle } from "@/app/(examples)/dashboard/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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,614 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { cookies } from "next/headers"
|
|
||||||
|
|
||||||
import {
|
|
||||||
SidebarInset,
|
|
||||||
SidebarProvider,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
import { AppSidebar } from "@/app/(examples)/dashboard/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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
apps/v4/app/favicon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
@@ -1,13 +1,10 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@import "tw-animate-css";
|
@plugin "tailwindcss-animate";
|
||||||
|
|
||||||
@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);
|
||||||
@@ -23,6 +20,7 @@
|
|||||||
--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);
|
||||||
@@ -31,6 +29,7 @@
|
|||||||
--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);
|
||||||
@@ -44,21 +43,22 @@
|
|||||||
.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.205 0 0);
|
--card: oklch(0.145 0 0);
|
||||||
--card-foreground: oklch(0.985 0 0);
|
--card-foreground: oklch(0.985 0 0);
|
||||||
--popover: oklch(0.269 0 0);
|
--popover: oklch(0.145 0 0);
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
--primary: oklch(0.922 0 0);
|
--primary: oklch(0.985 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.371 0 0);
|
--accent: oklch(0.269 0 0);
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
--destructive: oklch(0.396 0.141 25.723);
|
||||||
--border: oklch(1 0 0 / 10%);
|
--destructive-foreground: oklch(0.637 0.237 25.331);
|
||||||
--input: oklch(1 0 0 / 15%);
|
--border: oklch(0.269 0 0);
|
||||||
|
--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,15 +71,66 @@
|
|||||||
--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(1 0 0 / 10%);
|
--sidebar-border: oklch(0.269 0 0);
|
||||||
--sidebar-ring: oklch(0.439 0 0);
|
--sidebar-ring: oklch(0.439 0 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
.theme-login-one {
|
||||||
|
--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);
|
||||||
@@ -95,6 +146,7 @@
|
|||||||
--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);
|
||||||
@@ -103,6 +155,10 @@
|
|||||||
--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);
|
||||||
@@ -111,6 +167,26 @@
|
|||||||
--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,15 +1,23 @@
|
|||||||
import type { Metadata, Viewport } from "next"
|
import type { Metadata, Viewport } from "next"
|
||||||
import { cookies } from "next/headers"
|
import { Geist_Mono as FontMono, Inter as FontSans } from "next/font/google"
|
||||||
|
|
||||||
import { fontVariables } from "@/lib/fonts"
|
import { cn } from "@/lib/utils"
|
||||||
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"
|
|
||||||
import { ActiveThemeProvider } from "@/components/active-theme"
|
const fontSans = FontSans({
|
||||||
|
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",
|
||||||
@@ -21,7 +29,7 @@ export const metadata: Metadata = {
|
|||||||
default: siteConfig.name,
|
default: siteConfig.name,
|
||||||
template: `%s - ${siteConfig.name}`,
|
template: `%s - ${siteConfig.name}`,
|
||||||
},
|
},
|
||||||
metadataBase: new URL("https://v4.shadcn.com"),
|
metadataBase: new URL(siteConfig.url),
|
||||||
description: siteConfig.description,
|
description: siteConfig.description,
|
||||||
keywords: [
|
keywords: [
|
||||||
"Next.js",
|
"Next.js",
|
||||||
@@ -40,13 +48,13 @@ export const metadata: Metadata = {
|
|||||||
openGraph: {
|
openGraph: {
|
||||||
type: "website",
|
type: "website",
|
||||||
locale: "en_US",
|
locale: "en_US",
|
||||||
url: "https://v4.shadcn.com",
|
url: siteConfig.url,
|
||||||
title: siteConfig.name,
|
title: siteConfig.name,
|
||||||
description: siteConfig.description,
|
description: siteConfig.description,
|
||||||
siteName: siteConfig.name,
|
siteName: siteConfig.name,
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: "https://v4.shadcn.com/opengraph-image.png",
|
url: siteConfig.ogImage,
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 630,
|
height: 630,
|
||||||
alt: siteConfig.name,
|
alt: siteConfig.name,
|
||||||
@@ -57,7 +65,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: ["https://v4.shadcn.com/opengraph-image.png"],
|
images: [siteConfig.ogImage],
|
||||||
creator: "@shadcn",
|
creator: "@shadcn",
|
||||||
},
|
},
|
||||||
icons: {
|
icons: {
|
||||||
@@ -72,15 +80,11 @@ export const viewport: Viewport = {
|
|||||||
themeColor: META_THEME_COLORS.light,
|
themeColor: META_THEME_COLORS.light,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function RootLayout({
|
export default 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>
|
||||||
@@ -99,9 +103,8 @@ export default async function RootLayout({
|
|||||||
<body
|
<body
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background overscroll-none font-sans antialiased",
|
"bg-background overscroll-none font-sans antialiased",
|
||||||
activeThemeValue ? `theme-${activeThemeValue}` : "",
|
fontSans.variable,
|
||||||
isScaled ? "theme-scaled" : "",
|
fontMono.variable
|
||||||
fontVariables
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
@@ -109,13 +112,10 @@ export default async function RootLayout({
|
|||||||
defaultTheme="system"
|
defaultTheme="system"
|
||||||
enableSystem
|
enableSystem
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
enableColorScheme
|
|
||||||
>
|
>
|
||||||
<ActiveThemeProvider initialTheme={activeThemeValue}>
|
{children}
|
||||||
{children}
|
<Toaster />
|
||||||
<Toaster />
|
<Analytics />
|
||||||
<Analytics />
|
|
||||||
</ActiveThemeProvider>
|
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,364 +0,0 @@
|
|||||||
.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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
"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,14 +151,9 @@ const data = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
components: Object.values(Index)
|
components: Object.values(Index).filter(
|
||||||
.filter((item) => item.type === "registry:ui")
|
(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">
|
||||||
<Card className="w-full max-w-sm">
|
<form>
|
||||||
<CardHeader>
|
<Card>
|
||||||
<CardTitle>Login to your account</CardTitle>
|
<CardHeader>
|
||||||
<CardDescription>
|
<CardTitle>Login to your account</CardTitle>
|
||||||
Enter your email below to login to your account
|
<CardDescription>
|
||||||
</CardDescription>
|
Enter your email below to login to your account
|
||||||
</CardHeader>
|
</CardDescription>
|
||||||
<CardContent>
|
</CardHeader>
|
||||||
<form>
|
<CardContent>
|
||||||
<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>
|
||||||
</form>
|
</CardContent>
|
||||||
</CardContent>
|
<CardFooter className="flex-col gap-2">
|
||||||
<CardFooter className="flex-col gap-2">
|
<Button type="submit" className="w-full">
|
||||||
<Button type="submit" className="w-full">
|
Login
|
||||||
Login
|
</Button>
|
||||||
</Button>
|
<Button variant="outline" className="w-full">
|
||||||
<Button variant="outline" className="w-full">
|
Login with Google
|
||||||
Login with Google
|
</Button>
|
||||||
</Button>
|
<div className="mt-4 text-center text-sm">
|
||||||
<div className="mt-4 text-center text-sm">
|
Don't have an account?{" "}
|
||||||
Don't have an account?{" "}
|
<a href="#" className="underline underline-offset-4">
|
||||||
<a href="#" className="underline underline-offset-4">
|
Sign up
|
||||||
Sign up
|
</a>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
</CardFooter>
|
||||||
</CardFooter>
|
</Card>
|
||||||
</Card>
|
</form>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Meeting Notes</CardTitle>
|
<CardTitle>Meeting Notes</CardTitle>
|
||||||
|
|||||||
@@ -119,7 +119,6 @@ export function ComboboxDemo() {
|
|||||||
timezones={[...timezones]}
|
timezones={[...timezones]}
|
||||||
selectedTimezone={timezones[0].timezones[0]}
|
selectedTimezone={timezones[0].timezones[0]}
|
||||||
/>
|
/>
|
||||||
<ComboboxWithCheckbox frameworks={[...frameworks]} />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -343,63 +342,3 @@ 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...
|
Save Page As...
|
||||||
<ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>
|
<ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuItem>
|
<ContextMenuItem>
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ function DrawerBottom() {
|
|||||||
dataKey="goal"
|
dataKey="goal"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
fill: "var(--primary)",
|
fill: "hsl(var(--foreground))",
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,52 +44,55 @@ const plans = [
|
|||||||
] as const
|
] as const
|
||||||
|
|
||||||
const themes = {
|
const themes = {
|
||||||
neutral: {
|
stone: {
|
||||||
light: {
|
light: {
|
||||||
"--primary": "oklch(0.205 0 0)",
|
"--primary": "oklch(0.216 0.006 56.043)", // --color-stone-900
|
||||||
"--primary-foreground": "oklch(0.985 0 0)",
|
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-stone-50
|
||||||
"--ring": "oklch(0.708 0 0)",
|
"--accent": "oklch(0.97 0.001 106.424)", // --color-stone-100
|
||||||
|
"--ring": "oklch(0.869 0.005 56.366)", // --color-stone-300
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
"--primary": "oklch(0.922 0 0)",
|
"--primary": "oklch(0.985 0.001 106.423)", // --color-stone-50
|
||||||
"--primary-foreground": "oklch(0.205 0 0)",
|
"--primary-foreground": "oklch(0.216 0.006 56.043)", // --color-stone-900
|
||||||
"--ring": "oklch(0.556 0 0)",
|
"--accent": "oklch(0.268 0.007 34.298)", // --color-stone-800
|
||||||
|
"--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)",
|
"--primary": "oklch(0.546 0.245 262.881)", // --color-blue-600
|
||||||
"--primary-foreground": "oklch(0.985 0.001 106.423)",
|
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-blue-50
|
||||||
"--ring": "oklch(0.546 0.245 262.881)",
|
"--ring": "oklch(0.707 0.165 254.624)", // --color-blue-400
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
"--primary": "oklch(0.623 0.214 259.815)",
|
"--primary": "oklch(0.546 0.245 262.881)", // --color-blue-600
|
||||||
"--primary-foreground": "oklch(0.985 0.001 106.423)",
|
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-blue-50
|
||||||
"--ring": "oklch(0.623 0.214 259.815)",
|
"--ring": "oklch(0.379 0.146 265.522)", // --color-blue-400
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
amber: {
|
amber: {
|
||||||
light: {
|
light: {
|
||||||
"--primary": "oklch(0.769 0.188 70.08)",
|
"--primary": "oklch(0.769 0.188 70.08)", // --color-blue-600
|
||||||
"--primary-foreground": "oklch(0.985 0.001 106.423)",
|
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-blue-50
|
||||||
"--ring": "oklch(0.82 0.13 92.25)",
|
"--ring": "oklch(0.82 0.13 92.25)", // --color-blue-400
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
"--primary": "oklch(0.769 0.188 70.08)",
|
"--primary": "oklch(0.985 0.001 106.423)", // --color-stone-50
|
||||||
"--primary-foreground": "oklch(0.216 0.006 56.043)",
|
"--primary-foreground": "oklch(0.216 0.006 56.043)", // --color-stone-900
|
||||||
"--ring": "oklch(0.666 0.179 58.318)",
|
"--ring": "oklch(0.553 0.013 58.071)", // --color-stone-500
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
teal: {
|
teal: {
|
||||||
light: {
|
light: {
|
||||||
"--primary": "oklch(0.627 0.194 149.214)",
|
"--primary": "oklch(0.627 0.194 149.214)", // --color-blue-600
|
||||||
"--primary-foreground": "oklch(0.985 0.001 106.423)",
|
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-blue-50
|
||||||
"--ring": "oklch(0.79 0.19 153.13)",
|
"--ring": "oklch(0.79 0.19 153.13)", // --color-blue-400
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
"--primary": "oklch(0.704 0.14 182.503)",
|
"--primary": "oklch(0.985 0.001 106.423)", // --color-stone-50
|
||||||
"--primary-foreground": "oklch(0.216 0.006 56.043)",
|
"--primary-foreground": "oklch(0.216 0.006 56.043)", // --color-stone-900
|
||||||
"--ring": "oklch(0.704 0.14 182.503)",
|
"--ring": "oklch(0.553 0.013 58.071)", // --color-stone-500
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const
|
} as const
|
||||||
@@ -106,129 +109,123 @@ export function FormsDemo() {
|
|||||||
}, [theme, mode])
|
}, [theme, mode])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex max-w-md flex-col gap-4">
|
||||||
<div className="flex max-w-md flex-col gap-4">
|
<Card style={themeStyles as React.CSSProperties}>
|
||||||
<Card style={themeStyles as React.CSSProperties}>
|
<CardHeader>
|
||||||
<CardHeader>
|
<CardTitle className="text-lg">Upgrade your subscription</CardTitle>
|
||||||
<CardTitle className="text-lg">Upgrade your subscription</CardTitle>
|
<CardDescription>
|
||||||
<CardDescription className="text-balance">
|
You are currently on the free plan. Upgrade to the pro plan to get
|
||||||
You are currently on the free plan. Upgrade to the pro plan to get
|
access to all features.
|
||||||
access to all features.
|
</CardDescription>
|
||||||
</CardDescription>
|
</CardHeader>
|
||||||
</CardHeader>
|
<CardContent>
|
||||||
<CardContent>
|
<div className="flex flex-col gap-6">
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-3 md:flex-row">
|
||||||
<div className="flex flex-col gap-3 md:flex-row">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex flex-1 flex-col gap-2">
|
<Label htmlFor="name">Name</Label>
|
||||||
<Label htmlFor="name">Name</Label>
|
<Input id="name" placeholder="Evil Rabbit" />
|
||||||
<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="card-number">Card Number</Label>
|
<Label htmlFor="email">Email</Label>
|
||||||
<div className="grid grid-cols-2 gap-3 md:grid-cols-[1fr_80px_60px]">
|
<Input id="email" placeholder="example@acme.com" />
|
||||||
<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 className="flex flex-col gap-2">
|
|
||||||
<Label htmlFor="color">Color</Label>
|
|
||||||
<Select
|
|
||||||
onValueChange={(value) =>
|
|
||||||
setTheme(value as keyof typeof themes)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger id="color" className="w-full capitalize">
|
|
||||||
<SelectValue placeholder="Select a color" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<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>
|
|
||||||
<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-input/30 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 pr-2 text-xs leading-snug text-balance">
|
|
||||||
{plan.description}
|
|
||||||
</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>
|
|
||||||
</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>
|
<div className="flex flex-col gap-2">
|
||||||
<CardFooter className="flex justify-between">
|
<Label htmlFor="card-number">Card Number</Label>
|
||||||
<Button variant="outline" size="sm">
|
<div className="grid grid-cols-2 gap-3 md:grid-cols-[1fr_80px_60px]">
|
||||||
Cancel
|
<Input
|
||||||
</Button>
|
id="card-number"
|
||||||
<Button size="sm">Upgrade Plan</Button>
|
placeholder="1234 1234 1234 1234"
|
||||||
</CardFooter>
|
className="col-span-2 md:col-span-1"
|
||||||
</Card>
|
/>
|
||||||
</div>
|
<Input id="card-number-expiry" placeholder="MM/YY" />
|
||||||
|
<Input id="card-number-cvc" placeholder="CVC" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Label htmlFor="color">Color</Label>
|
||||||
|
<Select
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setTheme(value as keyof typeof themes)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="color" className="w-full">
|
||||||
|
<SelectValue placeholder="Select a color" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{Object.keys(themes).map((theme) => (
|
||||||
|
<SelectItem key={theme} value={theme}>
|
||||||
|
<div
|
||||||
|
className="size-3.5 rounded-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor:
|
||||||
|
themes[theme as keyof typeof themes]["light"][
|
||||||
|
"--primary"
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{theme}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="flex justify-between">
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button size="sm">Upgrade Plan</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,8 @@ export function ModeSwitcher() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
size="icon"
|
className="group/toggle h-8 w-8 px-0"
|
||||||
className="group/toggle size-8"
|
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
>
|
>
|
||||||
<SunIcon className="hidden [html.dark_&]:block" />
|
<SunIcon className="hidden [html.dark_&]:block" />
|
||||||
|
|||||||
@@ -4,21 +4,14 @@ 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")
|
||||||
setMetaColor(
|
}, [resolvedTheme, setTheme])
|
||||||
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 className="hidden sm:flex">
|
<NavigationMenu>
|
||||||
<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,3 +1,5 @@
|
|||||||
|
"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,132 +1,24 @@
|
|||||||
"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"
|
||||||
|
|
||||||
const promiseCode = "`${data.name} toast has been added`"
|
|
||||||
|
|
||||||
const allTypes = [
|
|
||||||
{
|
|
||||||
name: "Default",
|
|
||||||
snippet: `toast('Event has been created')`,
|
|
||||||
action: () => toast("Event has been created"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export function SonnerDemo() {
|
export function SonnerDemo() {
|
||||||
const [activeType, setActiveType] = React.useState(allTypes[0])
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap gap-4">
|
<Button
|
||||||
<Button onClick={() => toast("My first toast")} variant="outline">
|
variant="outline"
|
||||||
Give me a toast
|
onClick={() =>
|
||||||
</Button>
|
toast("Event has been created", {
|
||||||
<Button
|
description: "Sunday, December 03, 2023 at 9:00 AM",
|
||||||
variant="outline"
|
action: {
|
||||||
onClick={() =>
|
label: "Undo",
|
||||||
toast("Event has been created", {
|
onClick: () => console.log("Undo"),
|
||||||
description: "Sunday, December 03, 2023 at 9:00 AM",
|
},
|
||||||
action: {
|
})
|
||||||
label: "Undo",
|
}
|
||||||
onClick: () => console.log("Undo"),
|
>
|
||||||
},
|
Show Toast
|
||||||
})
|
</Button>
|
||||||
}
|
|
||||||
>
|
|
||||||
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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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,10 +15,6 @@
|
|||||||
"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",
|
||||||
@@ -39,7 +35,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.3",
|
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||||
"@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",
|
||||||
@@ -50,9 +46,7 @@
|
|||||||
"@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",
|
||||||
@@ -63,7 +57,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.3",
|
"next": "15.2.0-canary.33",
|
||||||
"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",
|
||||||
@@ -73,14 +67,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.4.0",
|
"shadcn": "2.3.0",
|
||||||
"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.24.1"
|
"zod": "^3.21.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 191 B |
|
Before Width: | Height: | Size: 241 B |
|
Before Width: | Height: | Size: 379 B |
|
Before Width: | Height: | Size: 82 KiB |
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 82 KiB |
@@ -6,7 +6,7 @@
|
|||||||
"name": "index",
|
"name": "index",
|
||||||
"type": "registry:style",
|
"type": "registry:style",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"tw-animate-css",
|
"tailwindcss-animate",
|
||||||
"class-variance-authority",
|
"class-variance-authority",
|
||||||
"lucide-react"
|
"lucide-react"
|
||||||
],
|
],
|
||||||
@@ -14,6 +14,13 @@
|
|||||||
"utils"
|
"utils"
|
||||||
],
|
],
|
||||||
"files": [],
|
"files": [],
|
||||||
|
"tailwind": {
|
||||||
|
"config": {
|
||||||
|
"plugins": [
|
||||||
|
"require(\"tailwindcss-animate\")"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"cssVars": {}
|
"cssVars": {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -114,9 +121,6 @@
|
|||||||
{
|
{
|
||||||
"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",
|
||||||
@@ -240,7 +244,7 @@
|
|||||||
"name": "command",
|
"name": "command",
|
||||||
"type": "registry:ui",
|
"type": "registry:ui",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"cmdk"
|
"cmdk@1.0.0"
|
||||||
],
|
],
|
||||||
"registryDependencies": [
|
"registryDependencies": [
|
||||||
"dialog"
|
"dialog"
|
||||||
@@ -709,92 +713,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"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",
|
||||||
|
|||||||
@@ -1,181 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import {
|
|
||||||
IconCamera,
|
|
||||||
IconChartBar,
|
|
||||||
IconDashboard,
|
|
||||||
IconDatabase,
|
|
||||||
IconFileAi,
|
|
||||||
IconFileDescription,
|
|
||||||
IconFileWord,
|
|
||||||
IconFolder,
|
|
||||||
IconHelp,
|
|
||||||
IconInnerShadowTop,
|
|
||||||
IconListDetails,
|
|
||||||
IconReport,
|
|
||||||
IconSearch,
|
|
||||||
IconSettings,
|
|
||||||
IconUsers,
|
|
||||||
} from "@tabler/icons-react"
|
|
||||||
|
|
||||||
import { 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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,807 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { IconCirclePlusFilled, IconMail, type Icon } from "@tabler/icons-react"
|
|
||||||
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import {
|
|
||||||
SidebarGroup,
|
|
||||||
SidebarGroupContent,
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
|
|
||||||
export function NavMain({
|
|
||||||
items,
|
|
||||||
}: {
|
|
||||||
items: {
|
|
||||||
title: string
|
|
||||||
url: string
|
|
||||||
icon?: Icon
|
|
||||||
}[]
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<SidebarGroup>
|
|
||||||
<SidebarGroupContent className="flex flex-col gap-2">
|
|
||||||
<SidebarMenu>
|
|
||||||
<SidebarMenuItem className="flex items-center gap-2">
|
|
||||||
<SidebarMenuButton
|
|
||||||
tooltip="Quick Create"
|
|
||||||
className="bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground min-w-8 duration-200 ease-linear"
|
|
||||||
>
|
|
||||||
<IconCirclePlusFilled />
|
|
||||||
<span>Quick Create</span>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
className="size-8 group-data-[collapsible=icon]:opacity-0"
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
<IconMail />
|
|
||||||
<span className="sr-only">Inbox</span>
|
|
||||||
</Button>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
<SidebarMenu>
|
|
||||||
{items.map((item) => (
|
|
||||||
<SidebarMenuItem key={item.title}>
|
|
||||||
<SidebarMenuButton tooltip={item.title}>
|
|
||||||
{item.icon && <item.icon />}
|
|
||||||
<span>{item.title}</span>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
))}
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarGroupContent>
|
|
||||||
</SidebarGroup>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
"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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import {
|
|
||||||
IconCreditCard,
|
|
||||||
IconDotsVertical,
|
|
||||||
IconLogout,
|
|
||||||
IconNotification,
|
|
||||||
IconUserCircle,
|
|
||||||
} from "@tabler/icons-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Avatar,
|
|
||||||
AvatarFallback,
|
|
||||||
AvatarImage,
|
|
||||||
} from "@/registry/new-york-v4/ui/avatar"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuGroup,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
|
||||||
import {
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
useSidebar,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
|
|
||||||
export function NavUser({
|
|
||||||
user,
|
|
||||||
}: {
|
|
||||||
user: {
|
|
||||||
name: string
|
|
||||||
email: string
|
|
||||||
avatar: string
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
const { isMobile } = useSidebar()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SidebarMenu>
|
|
||||||
<SidebarMenuItem>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<SidebarMenuButton
|
|
||||||
size="lg"
|
|
||||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
||||||
>
|
|
||||||
<Avatar className="h-8 w-8 rounded-lg grayscale">
|
|
||||||
<AvatarImage src={user.avatar} alt={user.name} />
|
|
||||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
||||||
<span className="truncate font-medium">{user.name}</span>
|
|
||||||
<span className="text-muted-foreground truncate text-xs">
|
|
||||||
{user.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<IconDotsVertical className="ml-auto size-4" />
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent
|
|
||||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
|
||||||
side={isMobile ? "bottom" : "right"}
|
|
||||||
align="end"
|
|
||||||
sideOffset={4}
|
|
||||||
>
|
|
||||||
<DropdownMenuLabel className="p-0 font-normal">
|
|
||||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
|
||||||
<Avatar className="h-8 w-8 rounded-lg">
|
|
||||||
<AvatarImage src={user.avatar} alt={user.name} />
|
|
||||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
||||||
<span className="truncate font-medium">{user.name}</span>
|
|
||||||
<span className="text-muted-foreground truncate text-xs">
|
|
||||||
{user.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DropdownMenuLabel>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconUserCircle />
|
|
||||||
Account
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconCreditCard />
|
|
||||||
Billing
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconNotification />
|
|
||||||
Notifications
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconLogout />
|
|
||||||
Log out
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
|
||||||
import { SidebarTrigger } from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
|
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,614 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
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-card text-muted-foreground relative z-10 px-2">
|
<span className="bg-background text-muted-foreground relative z-10 px-2">
|
||||||
Or continue with
|
Or continue with
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||