Compare commits

..

9 Commits

Author SHA1 Message Date
shadcn
e10567393d fix: chart and input-otp lint errors 2025-03-04 13:46:21 +04:00
shadcn
6382edc9bf Merge branch 'main' into main 2025-03-04 13:27:04 +04:00
shadcn
1befb7a653 chore(shadcn): changeset 2025-03-04 13:01:50 +04:00
shadcn
e8f245812f fix: minor fixes 2025-03-04 11:10:36 +04:00
shadcn
c90bcef401 feat: update default monorepo template 2025-03-04 10:29:35 +04:00
shadcn
7d97a9eba3 feat(shadcn): add monorepo tailwind detection 2025-03-04 10:29:13 +04:00
shadcn
2b23bdcbec Merge branch 'main' into main 2025-03-03 11:58:12 +04:00
shadcn
bd09f57cb7 Merge branch 'main' into main 2025-03-03 11:51:45 +04:00
Kaikai
a3fee32c96 feat(monorepo): use tailwindcss v4 in monorepo example 2025-02-22 02:59:48 +08:00
339 changed files with 2243 additions and 19453 deletions

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add support for TanStack Start

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
support for version detection in monorepo

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
upgrade @antfu/ni

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
do not add ring for v3

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add theme vars support

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add tailwind version detection

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add support for tailwind v4

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
default to css vars. add --no-css-variables

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
cache registry calls

View File

@@ -1,5 +0,0 @@
---
"shadcn": patch
---
resolved prefixing issue for Tailwind CSS v4 compatibility

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
default for new-york for v4

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
fix handling of sidebar colors

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
do not overwrite user defined vars

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
fix cn import bug in monorepo

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
filter out deprecated from --all

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add oklch base color

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
hotswap style for v4

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
check for empty css vars

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add warning for deprecated components

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
fix tanstack check

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add support for route install for react-router and laravel

3
.github/FUNDING.yml vendored
View File

@@ -1,3 +0,0 @@
# These are supported funding model platforms
github: [shadcn]

View File

@@ -1,6 +0,0 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -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.

View File

@@ -1081,101 +1081,6 @@ export const Index: Record<string, any> = {
}),
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": {
name: "sidebar-01",
description: "A simple sidebar with navigation grouped by section.",

View File

@@ -2,13 +2,8 @@ import { FormsDemo } from "@/components/forms-demo"
export default function FormsPage() {
return (
<div className="flex flex-1 flex-col items-center justify-center gap-12 p-4 lg:flex-row">
<div className="">
<FormsDemo />
</div>
<div className="theme-scaled">
<FormsDemo />
</div>
<div className="flex flex-1 items-center justify-center p-4">
<FormsDemo />
</div>
)
}

View File

@@ -3,7 +3,6 @@ import { cookies } from "next/headers"
import { AppSidebar } from "@/components/app-sidebar"
import { ModeSwitcher } from "@/components/mode-switcher"
import { NavHeader } from "@/components/nav-header"
import { ThemeSelector } from "@/components/theme-selector"
import { Separator } from "@/registry/new-york-v4/ui/separator"
import {
SidebarInset,
@@ -32,7 +31,6 @@ export default async function AppLayout({
/>
<NavHeader />
<div className="ml-auto flex items-center gap-2">
<ThemeSelector />
<ModeSwitcher />
</div>
</div>

View File

@@ -17,7 +17,7 @@ export default function LoginPage() {
return (
<div
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,
fontSerif.variable,
fontManrope.variable

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -1,807 +0,0 @@
"use client"
import * as React from "react"
import {
closestCenter,
DndContext,
KeyboardSensor,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
type DragEndEvent,
type UniqueIdentifier,
} from "@dnd-kit/core"
import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
import {
arrayMove,
SortableContext,
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,
flexRender,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
Row,
SortingState,
useReactTable,
VisibilityState,
} 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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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"
}
]

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1,13 +1,10 @@
@import "tailwindcss";
@import "tw-animate-css";
@plugin "tailwindcss-animate";
@custom-variant dark (&:is(.dark *));
@import "./themes.css";
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
@@ -23,6 +20,7 @@
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: 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);
@@ -31,6 +29,7 @@
--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);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
@@ -44,21 +43,22 @@
.dark {
--background: oklch(0.145 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);
--popover: oklch(0.269 0 0);
--popover: oklch(0.145 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);
--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.371 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%);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
@@ -71,15 +71,66 @@
--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-border: oklch(0.269 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-md: calc(var(--radius) - 2px);
--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-foreground: var(--foreground);
--color-card: var(--card);
@@ -95,6 +146,7 @@
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
@@ -103,6 +155,10 @@
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--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-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
@@ -111,6 +167,26 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--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 {

View File

@@ -1,7 +1,7 @@
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 { ThemeProvider } from "@/components/theme-provider"
import { Toaster } from "@/registry/new-york-v4/ui/sonner"
@@ -9,8 +9,15 @@ import { siteConfig } from "@/www/config/site"
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 = {
light: "#ffffff",
@@ -22,7 +29,7 @@ export const metadata: Metadata = {
default: siteConfig.name,
template: `%s - ${siteConfig.name}`,
},
metadataBase: new URL("https://v4.shadcn.com"),
metadataBase: new URL(siteConfig.url),
description: siteConfig.description,
keywords: [
"Next.js",
@@ -41,13 +48,13 @@ export const metadata: Metadata = {
openGraph: {
type: "website",
locale: "en_US",
url: "https://v4.shadcn.com",
url: siteConfig.url,
title: siteConfig.name,
description: siteConfig.description,
siteName: siteConfig.name,
images: [
{
url: "https://v4.shadcn.com/opengraph-image.png",
url: siteConfig.ogImage,
width: 1200,
height: 630,
alt: siteConfig.name,
@@ -58,7 +65,7 @@ export const metadata: Metadata = {
card: "summary_large_image",
title: siteConfig.name,
description: siteConfig.description,
images: ["https://v4.shadcn.com/opengraph-image.png"],
images: [siteConfig.ogImage],
creator: "@shadcn",
},
icons: {
@@ -73,15 +80,11 @@ export const viewport: Viewport = {
themeColor: META_THEME_COLORS.light,
}
export default async function RootLayout({
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
const cookieStore = await cookies()
const activeThemeValue = cookieStore.get("active_theme")?.value
const isScaled = activeThemeValue?.endsWith("-scaled")
return (
<html lang="en" suppressHydrationWarning>
<head>
@@ -100,9 +103,8 @@ export default async function RootLayout({
<body
className={cn(
"bg-background overscroll-none font-sans antialiased",
activeThemeValue ? `theme-${activeThemeValue}` : "",
isScaled ? "theme-scaled" : "",
fontVariables
fontSans.variable,
fontMono.variable
)}
>
<ThemeProvider
@@ -110,13 +112,10 @@ export default async function RootLayout({
defaultTheme="system"
enableSystem
disableTransitionOnChange
enableColorScheme
>
<ActiveThemeProvider initialTheme={activeThemeValue}>
{children}
<Toaster />
<Analytics />
</ActiveThemeProvider>
{children}
<Toaster />
<Analytics />
</ThemeProvider>
</body>
</html>

View File

@@ -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;
}
}

View File

@@ -18,4 +18,4 @@
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
}

View File

@@ -1,65 +0,0 @@
"use client"
import {
createContext,
ReactNode,
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
}

View File

@@ -151,14 +151,9 @@ const data = {
],
},
],
components: Object.values(Index)
.filter((item) => item.type === "registry:ui")
.concat([
{
name: "combobox",
},
])
.sort((a, b) => a.name.localeCompare(b.name)),
components: Object.values(Index).filter(
(item) => item.type === "registry:ui"
),
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {

View File

@@ -22,15 +22,15 @@ import { Label } from "@/registry/new-york-v4/ui/label"
export function CardDemo() {
return (
<div className="flex flex-col items-start gap-4">
<Card className="w-full max-w-sm">
<CardHeader>
<CardTitle>Login to your account</CardTitle>
<CardDescription>
Enter your email below to login to your account
</CardDescription>
</CardHeader>
<CardContent>
<form>
<form>
<Card>
<CardHeader>
<CardTitle>Login to your account</CardTitle>
<CardDescription>
Enter your email below to login to your account
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
@@ -54,23 +54,23 @@ export function CardDemo() {
<Input id="password" type="password" required />
</div>
</div>
</form>
</CardContent>
<CardFooter className="flex-col gap-2">
<Button type="submit" className="w-full">
Login
</Button>
<Button variant="outline" className="w-full">
Login with Google
</Button>
<div className="mt-4 text-center text-sm">
Don&apos;t have an account?{" "}
<a href="#" className="underline underline-offset-4">
Sign up
</a>
</div>
</CardFooter>
</Card>
</CardContent>
<CardFooter className="flex-col gap-2">
<Button type="submit" className="w-full">
Login
</Button>
<Button variant="outline" className="w-full">
Login with Google
</Button>
<div className="mt-4 text-center text-sm">
Don&apos;t have an account?{" "}
<a href="#" className="underline underline-offset-4">
Sign up
</a>
</div>
</CardFooter>
</Card>
</form>
<Card>
<CardHeader>
<CardTitle>Meeting Notes</CardTitle>

View File

@@ -119,7 +119,6 @@ export function ComboboxDemo() {
timezones={[...timezones]}
selectedTimezone={timezones[0].timezones[0]}
/>
<ComboboxWithCheckbox frameworks={[...frameworks]} />
</div>
)
}
@@ -343,63 +342,3 @@ function TimezoneCombobox({
</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>
)
}

View File

@@ -39,7 +39,7 @@ export function ContextMenuDemo() {
<ContextMenuSubTrigger inset>More Tools</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-48">
<ContextMenuItem inset>
Save Page...
Save Page As...
<ContextMenuShortcut>S</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>

View File

@@ -124,7 +124,7 @@ function DrawerBottom() {
dataKey="goal"
style={
{
fill: "var(--primary)",
fill: "hsl(var(--foreground))",
opacity: 0.9,
} as React.CSSProperties
}

View File

@@ -44,52 +44,55 @@ const plans = [
] as const
const themes = {
neutral: {
stone: {
light: {
"--primary": "oklch(0.205 0 0)",
"--primary-foreground": "oklch(0.985 0 0)",
"--ring": "oklch(0.708 0 0)",
"--primary": "oklch(0.216 0.006 56.043)", // --color-stone-900
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-stone-50
"--accent": "oklch(0.97 0.001 106.424)", // --color-stone-100
"--ring": "oklch(0.869 0.005 56.366)", // --color-stone-300
},
dark: {
"--primary": "oklch(0.922 0 0)",
"--primary-foreground": "oklch(0.205 0 0)",
"--ring": "oklch(0.556 0 0)",
"--primary": "oklch(0.985 0.001 106.423)", // --color-stone-50
"--primary-foreground": "oklch(0.216 0.006 56.043)", // --color-stone-900
"--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: {
light: {
"--primary": "oklch(0.546 0.245 262.881)",
"--primary-foreground": "oklch(0.985 0.001 106.423)",
"--ring": "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)", // --color-blue-50
"--ring": "oklch(0.707 0.165 254.624)", // --color-blue-400
},
dark: {
"--primary": "oklch(0.623 0.214 259.815)",
"--primary-foreground": "oklch(0.985 0.001 106.423)",
"--ring": "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)", // --color-blue-50
"--ring": "oklch(0.379 0.146 265.522)", // --color-blue-400
},
},
amber: {
light: {
"--primary": "oklch(0.769 0.188 70.08)",
"--primary-foreground": "oklch(0.985 0.001 106.423)",
"--ring": "oklch(0.82 0.13 92.25)",
"--primary": "oklch(0.769 0.188 70.08)", // --color-blue-600
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-blue-50
"--ring": "oklch(0.82 0.13 92.25)", // --color-blue-400
},
dark: {
"--primary": "oklch(0.769 0.188 70.08)",
"--primary-foreground": "oklch(0.216 0.006 56.043)",
"--ring": "oklch(0.666 0.179 58.318)",
"--primary": "oklch(0.985 0.001 106.423)", // --color-stone-50
"--primary-foreground": "oklch(0.216 0.006 56.043)", // --color-stone-900
"--ring": "oklch(0.553 0.013 58.071)", // --color-stone-500
},
},
teal: {
light: {
"--primary": "oklch(0.627 0.194 149.214)",
"--primary-foreground": "oklch(0.985 0.001 106.423)",
"--ring": "oklch(0.79 0.19 153.13)",
"--primary": "oklch(0.627 0.194 149.214)", // --color-blue-600
"--primary-foreground": "oklch(0.985 0.001 106.423)", // --color-blue-50
"--ring": "oklch(0.79 0.19 153.13)", // --color-blue-400
},
dark: {
"--primary": "oklch(0.704 0.14 182.503)",
"--primary-foreground": "oklch(0.216 0.006 56.043)",
"--ring": "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)", // --color-stone-900
"--ring": "oklch(0.553 0.013 58.071)", // --color-stone-500
},
},
} as const
@@ -106,129 +109,123 @@ export function FormsDemo() {
}, [theme, mode])
return (
<div>
<div className="flex max-w-md flex-col gap-4">
<Card style={themeStyles as React.CSSProperties}>
<CardHeader>
<CardTitle className="text-lg">Upgrade your subscription</CardTitle>
<CardDescription className="text-balance">
You are currently on the free plan. Upgrade to the pro plan to get
access to all features.
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-3 md:flex-row">
<div className="flex flex-1 flex-col gap-2">
<Label htmlFor="name">Name</Label>
<Input id="name" placeholder="Evil Rabbit" />
</div>
<div className="flex flex-1 flex-col gap-2">
<Label htmlFor="email">Email</Label>
<Input id="email" placeholder="example@acme.com" />
</div>
<div className="flex max-w-md flex-col gap-4">
<Card style={themeStyles as React.CSSProperties}>
<CardHeader>
<CardTitle className="text-lg">Upgrade your subscription</CardTitle>
<CardDescription>
You are currently on the free plan. Upgrade to the pro plan to get
access to all features.
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-3 md:flex-row">
<div className="flex flex-col gap-2">
<Label htmlFor="name">Name</Label>
<Input id="name" placeholder="Evil Rabbit" />
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="card-number">Card Number</Label>
<div className="grid grid-cols-2 gap-3 md:grid-cols-[1fr_80px_60px]">
<Input
id="card-number"
placeholder="1234 1234 1234 1234"
className="col-span-2 md:col-span-1"
/>
<Input id="card-number-expiry" placeholder="MM/YY" />
<Input id="card-number-cvc" placeholder="CVC" />
</div>
</div>
<div 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>
<Label htmlFor="email">Email</Label>
<Input id="email" placeholder="example@acme.com" />
</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 className="flex flex-col gap-2">
<Label htmlFor="card-number">Card Number</Label>
<div className="grid grid-cols-2 gap-3 md:grid-cols-[1fr_80px_60px]">
<Input
id="card-number"
placeholder="1234 1234 1234 1234"
className="col-span-2 md:col-span-1"
/>
<Input id="card-number-expiry" placeholder="MM/YY" />
<Input id="card-number-cvc" placeholder="CVC" />
</div>
</div>
<div 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>
)
}

View File

@@ -22,9 +22,8 @@ export function ModeSwitcher() {
return (
<Button
variant="outline"
size="icon"
className="group/toggle size-8"
variant="ghost"
className="group/toggle h-8 w-8 px-0"
onClick={toggleTheme}
>
<SunIcon className="hidden [html.dark_&]:block" />

View File

@@ -4,21 +4,14 @@ import * as React from "react"
import { MoonIcon, SunIcon } from "lucide-react"
import { useTheme } from "next-themes"
import { META_THEME_COLORS, useMetaColor } from "@/hooks/use-meta-color"
import { Button } from "@/registry/new-york-v4/ui/button"
export function ModeToggle() {
const { setTheme, resolvedTheme } = useTheme()
const { setMetaColor } = useMetaColor()
const toggleTheme = React.useCallback(() => {
setTheme(resolvedTheme === "dark" ? "light" : "dark")
setMetaColor(
resolvedTheme === "dark"
? META_THEME_COLORS.light
: META_THEME_COLORS.dark
)
}, [resolvedTheme, setTheme, setMetaColor])
}, [resolvedTheme, setTheme])
return (
<Button

View File

@@ -14,7 +14,7 @@ export function NavHeader() {
const pathname = usePathname()
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">
<NavigationMenuItem>
<NavigationMenuLink asChild data-active={pathname === "/"}>

View File

@@ -1,3 +1,5 @@
"use client"
import * as React from "react"
import Link from "next/link"
import { CircleCheckIcon, CircleHelpIcon, CircleIcon } from "lucide-react"

View File

@@ -1,132 +1,24 @@
"use client"
import * as React from "react"
import { toast } from "sonner"
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() {
const [activeType, setActiveType] = React.useState(allTypes[0])
return (
<div className="flex flex-wrap gap-4">
<Button onClick={() => toast("My first toast")} variant="outline">
Give me a toast
</Button>
<Button
variant="outline"
onClick={() =>
toast("Event has been created", {
description: "Sunday, December 03, 2023 at 9:00 AM",
action: {
label: "Undo",
onClick: () => console.log("Undo"),
},
})
}
>
Show Toast
</Button>
{allTypes.map((type) => (
<Button
variant="ghost"
data-active={activeType.name === type.name}
onClick={() => {
type.action()
setActiveType(type)
}}
key={type.name}
>
{type.name}
</Button>
))}
</div>
<Button
variant="outline"
onClick={() =>
toast("Event has been created", {
description: "Sunday, December 03, 2023 at 9:00 AM",
action: {
label: "Undo",
onClick: () => console.log("Undo"),
},
})
}
>
Show Toast
</Button>
)
}

View File

@@ -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>
)
}

View File

@@ -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
)

View File

@@ -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]

View File

@@ -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"
},
"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",
"@radix-ui/react-accessible-icon": "^1.1.1",
"@radix-ui/react-accordion": "^1.2.2",
@@ -39,7 +35,7 @@
"@radix-ui/react-portal": "^1.1.3",
"@radix-ui/react-progress": "^1.1.1",
"@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-separator": "^1.1.1",
"@radix-ui/react-slider": "^1.2.2",
@@ -50,9 +46,7 @@
"@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-toggle-group": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.7",
"@tabler/icons-react": "^3.31.0",
"@tailwindcss/postcss": "^4.0.1",
"@tanstack/react-table": "^8.9.1",
"@vercel/analytics": "^1.2.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -63,7 +57,7 @@
"geist": "^1.2.2",
"input-otp": "^1.4.2",
"lucide-react": "0.474.0",
"next": "15.2.3",
"next": "15.2.0-canary.33",
"next-themes": "^0.4.3",
"postcss": "^8.5.1",
"react": "^19.0.0",
@@ -73,14 +67,14 @@
"react-resizable-panels": "^2.1.7",
"recharts": "2.15.1",
"rimraf": "^6.0.1",
"shadcn": "2.5.0",
"shadcn": "2.3.0",
"sonner": "^2.0.0",
"tailwind-merge": "^3.0.1",
"tailwindcss": "^4.0.7",
"tailwindcss-animate": "^1.0.7",
"ts-morph": "^22.0.0",
"tw-animate-css": "^1.2.4",
"vaul": "1.1.2",
"zod": "^3.24.1"
"zod": "^3.21.4"
},
"devDependencies": {
"@eslint/eslintrc": "^3",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

View File

@@ -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"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

View File

@@ -6,16 +6,21 @@
"name": "index",
"type": "registry:style",
"dependencies": [
"tailwindcss-animate",
"class-variance-authority",
"lucide-react"
],
"devDependencies": [
"tw-animate-css"
],
"registryDependencies": [
"utils"
],
"files": [],
"tailwind": {
"config": {
"plugins": [
"require(\"tailwindcss-animate\")"
]
}
},
"cssVars": {}
},
{
@@ -29,7 +34,37 @@
"path": "registry/new-york-v4/ui/accordion.tsx",
"type": "registry:ui"
}
]
],
"tailwind": {
"config": {
"theme": {
"extend": {
"keyframes": {
"accordion-down": {
"from": {
"height": "0"
},
"to": {
"height": "var(--radix-accordion-content-height)"
}
},
"accordion-up": {
"from": {
"height": "var(--radix-accordion-content-height)"
},
"to": {
"height": "0"
}
}
},
"animation": {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out"
}
}
}
}
}
},
{
"name": "alert",
@@ -86,9 +121,6 @@
{
"name": "badge",
"type": "registry:ui",
"dependencies": [
"@radix-ui/react-slot"
],
"files": [
{
"path": "registry/new-york-v4/ui/badge.tsx",
@@ -212,7 +244,7 @@
"name": "command",
"type": "registry:ui",
"dependencies": [
"cmdk"
"cmdk@1.0.0"
],
"registryDependencies": [
"dialog"
@@ -681,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",
"type": "registry:block",

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -1,807 +0,0 @@
"use client"
import * as React from "react"
import {
closestCenter,
DndContext,
KeyboardSensor,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
type DragEndEvent,
type UniqueIdentifier,
} from "@dnd-kit/core"
import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
import {
arrayMove,
SortableContext,
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,
flexRender,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
Row,
SortingState,
useReactTable,
VisibilityState,
} 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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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"
}
]

View File

@@ -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>
)
}

Some files were not shown because too many files have changed in this diff Show More