refactor: clean up unused files (#10227)

* refactor: clean up unused files

* fix
This commit is contained in:
shadcn
2026-03-29 12:04:18 +04:00
committed by GitHub
parent 23b2ac4dcf
commit d3de6aa760
127 changed files with 9 additions and 23359 deletions

View File

@@ -1,64 +0,0 @@
import { type Metadata } from "next"
import Link from "next/link"
import { Announcement } from "@/components/announcement"
import {
PageActions,
PageHeader,
PageHeaderDescription,
PageHeaderHeading,
} from "@/components/page-header"
import { Button } from "@/registry/new-york-v4/ui/button"
const title = "Pick a Color. Make it yours."
const description =
"Try our hand-picked themes. Copy and paste them into your project. New theme editor coming soon."
export const metadata: Metadata = {
title,
description,
openGraph: {
images: [
{
url: `/og?title=${encodeURIComponent(
title
)}&description=${encodeURIComponent(description)}`,
},
],
},
twitter: {
card: "summary_large_image",
images: [
{
url: `/og?title=${encodeURIComponent(
title
)}&description=${encodeURIComponent(description)}`,
},
],
},
}
export default function ThemesLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div>
<PageHeader>
<Announcement />
<PageHeaderHeading>{title}</PageHeaderHeading>
<PageHeaderDescription>{description}</PageHeaderDescription>
<PageActions>
<Button asChild size="sm">
<a href="#themes">Browse Themes</a>
</Button>
<Button asChild variant="ghost" size="sm">
<Link href="/docs/theming">Documentation</Link>
</Button>
</PageActions>
</PageHeader>
{children}
</div>
)
}

View File

@@ -1,22 +0,0 @@
import { CardsDemo } from "@/components/cards"
import { ThemeCustomizer } from "@/components/theme-customizer"
export const dynamic = "force-static"
export const revalidate = false
export default function ThemesPage() {
return (
<>
<div id="themes" className="container-wrapper scroll-mt-20">
<div className="container flex items-center justify-between gap-8 px-6 py-4 md:px-8">
<ThemeCustomizer />
</div>
</div>
<div className="container-wrapper flex flex-1 flex-col section-soft pb-6">
<div className="container flex flex-1 flex-col theme-container">
<CardsDemo />
</div>
</div>
</>
)
}

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 { type 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 {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} 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="leading-none text-muted-foreground">
Showing total visitors for the last 6 months
</div>
</CardFooter>
</Card>
)
}

View File

@@ -1,199 +0,0 @@
"use client"
import * as React from "react"
import { Label, Pie, PieChart, Sector } from "recharts"
import type {
PieSectorDataItem,
PieSectorShapeProps,
} from "recharts/types/polar/Pie"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import {
ChartContainer,
ChartStyle,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} 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), [])
const renderPieShape = React.useCallback(
({ index, outerRadius = 0, ...props }: PieSectorShapeProps) => {
if (index === activeIndex) {
return (
<g>
<Sector {...props} outerRadius={outerRadius + 10} />
<Sector
{...props}
outerRadius={outerRadius + 25}
innerRadius={outerRadius + 12}
/>
</g>
)
}
return <Sector {...props} outerRadius={outerRadius} />
},
[activeIndex]
)
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}
shape={renderPieShape}
>
<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="truncate text-xs text-muted-foreground">
{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-sm text-muted-foreground">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-sm text-muted-foreground">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-sm text-muted-foreground">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="hidden text-sm text-muted-foreground @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="sticky top-0 z-50 flex w-full items-center border-b bg-background"
>
<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,29 +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"
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 { type 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 { type 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-sm text-muted-foreground @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 {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} 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 {
flexRender,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
type ColumnDef,
type ColumnFiltersState,
type Row,
type SortingState,
type 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 {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} 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="size-7 text-muted-foreground hover:bg-transparent"
>
<IconGripVertical className="size-3 text-muted-foreground" />
<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="px-1.5 text-muted-foreground">
{row.original.type}
</Badge>
</div>
),
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => (
<Badge variant="outline" className="px-1.5 text-muted-foreground">
{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="h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background dark:bg-transparent dark:hover:bg-input/30 dark:focus-visible:bg-input/30"
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="h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background dark:bg-transparent dark:hover:bg-input/30 dark:focus-visible:bg-input/30"
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="flex size-8 text-muted-foreground data-[state=open]:bg-muted"
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="hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:bg-muted-foreground/30 **: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="sticky top-0 z-10 bg-muted">
{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="hidden flex-1 text-sm text-muted-foreground 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="w-fit px-0 text-left text-foreground">
{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="rounded-sm data-[state=open]:bg-accent"
>
<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="min-w-8 bg-primary text-primary-foreground duration-200 ease-linear hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground"
>
<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="truncate text-xs text-muted-foreground">
{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="truncate text-xs text-muted-foreground">
{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="grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4 dark:*:data-[slot=card]:bg-card">
<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="hidden text-muted-foreground sm:block">
Select a theme:
</span>
<span className="block text-muted-foreground 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;
}
}

View File

@@ -1,168 +0,0 @@
import Image from "next/image"
import { CheckIcon } from "lucide-react"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
FieldTitle,
} from "@/registry/new-york-v4/ui/field"
import { Label } from "@/registry/new-york-v4/ui/label"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/new-york-v4/ui/radio-group"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import { Switch } from "@/registry/new-york-v4/ui/switch"
const modes = [
{
name: "Light",
value: "light",
image: "/placeholder.svg",
},
{
name: "Dark",
value: "dark",
image: "/placeholder.svg",
},
{
name: "System",
value: "system",
image: "/placeholder.svg",
},
]
const accents = [
{
name: "Blue",
value: "#007AFF",
},
{
name: "Purple",
value: "#6A4695",
},
{
name: "Red",
value: "#FF3B30",
},
{
name: "Orange",
value: "#FF9500",
},
]
export function AppearanceSettings() {
return (
<FieldSet>
<FieldLegend>Appearance</FieldLegend>
<FieldDescription>
Configure appearance. accent, scroll bar, and more.
</FieldDescription>
<FieldGroup>
<FieldSet>
<FieldLegend variant="label">Mode</FieldLegend>
<FieldDescription>
Select the mode to use for the appearance.
</FieldDescription>
<RadioGroup
className="flex flex-col gap-4 @min-[28rem]/field-group:grid @min-[28rem]/field-group:grid-cols-3"
defaultValue="light"
>
{modes.map((mode) => (
<FieldLabel
htmlFor={mode.value}
className="gap-0 overflow-hidden"
key={mode.value}
>
<Image
src={mode.image}
alt={mode.name}
width={160}
height={90}
className="hidden aspect-video w-full object-cover @min-[28rem]/field-group:block dark:brightness-[0.2] dark:grayscale"
/>
<Field
orientation="horizontal"
className="@min-[28rem]/field-group:border-t @min-[28rem]/field-group:border-t-input"
>
<FieldTitle>{mode.name}</FieldTitle>
<RadioGroupItem id={mode.value} value={mode.value} />
</Field>
</FieldLabel>
))}
</RadioGroup>
</FieldSet>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Accent</FieldTitle>
<FieldDescription>
Select the accent color to use for the appearance.
</FieldDescription>
</FieldContent>
<FieldSet aria-label="Accent">
<RadioGroup className="flex flex-wrap gap-2" defaultValue="#007AFF">
{accents.map((accent) => (
<Label
htmlFor={accent.value}
key={accent.value}
className="flex size-6 items-center justify-center rounded-full"
style={{ backgroundColor: accent.value }}
>
<RadioGroupItem
id={accent.value}
value={accent.value}
aria-label={accent.name}
className="peer sr-only"
/>
<CheckIcon className="hidden size-4 stroke-white peer-data-[state=checked]:block" />
</Label>
))}
</RadioGroup>
</FieldSet>
</Field>
<FieldSeparator />
<Field orientation="responsive">
<FieldContent>
<FieldLabel htmlFor="icon-size">Sidebar Icon Size</FieldLabel>
<FieldDescription>
Select the size of the sidebar icons.
</FieldDescription>
</FieldContent>
<Select>
<SelectTrigger id="icon-size" className="ml-auto">
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent align="end">
<SelectItem value="small">Small</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="large">Large</SelectItem>
</SelectContent>
</Select>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="tinting">Wallpaper Tinting</FieldLabel>
<FieldDescription>
Allow the wallpaper to be tinted with the accent color.
</FieldDescription>
</FieldContent>
<Switch id="tinting" defaultChecked />
</Field>
</FieldGroup>
</FieldSet>
)
}

View File

@@ -1,463 +0,0 @@
"use client"
import { useState } from "react"
import { CircleIcon, InfoIcon } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSeparator,
FieldSet,
FieldTitle,
} from "@/registry/new-york-v4/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/registry/new-york-v4/ui/input-group"
import { Kbd } from "@/registry/new-york-v4/ui/kbd"
import {
Select,
SelectContent,
SelectItem,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import { Switch } from "@/registry/new-york-v4/ui/switch"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/new-york-v4/ui/tabs"
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york-v4/ui/tooltip"
const spokenLanguages = [
{ label: "English", value: "en" },
{ label: "Spanish", value: "es" },
{ label: "French", value: "fr" },
{ label: "German", value: "de" },
{ label: "Italian", value: "it" },
{ label: "Portuguese", value: "pt" },
{ label: "Russian", value: "ru" },
{ label: "Chinese", value: "zh" },
{ label: "Japanese", value: "ja" },
{ label: "Korean", value: "ko" },
{ label: "Arabic", value: "ar" },
{ label: "Hindi", value: "hi" },
{ label: "Bengali", value: "bn" },
{ label: "Telugu", value: "te" },
{ label: "Marathi", value: "mr" },
{ label: "Kannada", value: "kn" },
{ label: "Malayalam", value: "ml" },
]
const voices = [
{ label: "Samantha", value: "samantha" },
{ label: "Alex", value: "alex" },
{ label: "Fred", value: "fred" },
{ label: "Victoria", value: "victoria" },
{ label: "Tom", value: "tom" },
{ label: "Karen", value: "karen" },
{ label: "Sam", value: "sam" },
{ label: "Daniel", value: "daniel" },
]
const personalities = [
{
label: "Friendly",
value: "friendly",
description: "Friendly and approachable.",
},
{
label: "Professional",
value: "professional",
description: "Professional and authoritative.",
},
{ label: "Funny", value: "funny", description: "Funny and light-hearted." },
{
label: "Sarcastic",
value: "sarcastic",
description: "Sarcastic and witty.",
},
{ label: "Cynical", value: "cynical", description: "Cynical and skeptical." },
]
const instructions = [
{
label: "Witty",
value: "witty",
description: "Use quick and clever responses when appropriate.",
},
{
label: "Professional",
value: "professional",
description: "Have a professional and authoritative tone.",
},
{
label: "Funny",
value: "funny",
description: "Use humor and wit to engage the user.",
},
{
label: "Sarcastic",
value: "sarcastic",
description: "Use sarcasm and wit to engage the user.",
},
{
label: "Cynical",
value: "cynical",
description: "Use cynicism and skepticism to engage the user.",
},
]
export function ChatSettings() {
const [tab, setTab] = useState("general")
const [theme, setTheme] = useState("system")
const [accentColor, setAccentColor] = useState("default")
const [spokenLanguage, setSpokenLanguage] = useState("en")
const [voice, setVoice] = useState("samantha")
const [personality, setPersonality] = useState("friendly")
const [customInstructions, setCustomInstructions] = useState("")
return (
<div className="flex flex-col gap-4">
<Button variant="outline" asChild className="w-full md:hidden">
<select
value={tab}
onChange={(e) => setTab(e.target.value)}
className="appearance-none"
>
<option value="general">General</option>
<option value="notifications">Notifications</option>
<option value="personalization">Personalization</option>
<option value="security">Security</option>
</select>
</Button>
<Tabs value={tab} onValueChange={setTab}>
<TabsList className="hidden md:flex">
<TabsTrigger value="general">General</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger>
<TabsTrigger value="personalization">Personalization</TabsTrigger>
<TabsTrigger value="security">Security</TabsTrigger>
</TabsList>
<div className="rounded-lg border p-6 [&_[data-slot=select-trigger]]:min-w-[125px]">
<TabsContent value="general">
<FieldSet>
<FieldGroup>
<Field orientation="horizontal">
<FieldLabel htmlFor="theme">Theme</FieldLabel>
<Select value={theme} onValueChange={setTheme}>
<SelectTrigger id="theme">
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent align="end">
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldLabel htmlFor="accent-color">Accent Color</FieldLabel>
<Select value={accentColor} onValueChange={setAccentColor}>
<SelectTrigger id="accent-color">
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent align="end">
<SelectItem value="default">
<CircleIcon className="fill-neutral-500 stroke-neutral-500 dark:fill-neutral-400 dark:stroke-neutral-400" />
Default
</SelectItem>
<SelectItem value="red">
<CircleIcon className="fill-red-500 stroke-red-500 dark:fill-red-400 dark:stroke-red-400" />
Red
</SelectItem>
<SelectItem value="blue">
<CircleIcon className="fill-blue-500 stroke-blue-500 dark:fill-blue-400 dark:stroke-blue-400" />
Blue
</SelectItem>
<SelectItem value="green">
<CircleIcon className="fill-green-500 stroke-green-500 dark:fill-green-400 dark:stroke-green-400" />
Green
</SelectItem>
<SelectItem value="purple">
<CircleIcon className="fill-purple-500 stroke-purple-500 dark:fill-purple-400 dark:stroke-purple-400" />
Purple
</SelectItem>
<SelectItem value="pink">
<CircleIcon className="fill-pink-500 stroke-pink-500 dark:fill-pink-400 dark:stroke-pink-400" />
Pink
</SelectItem>
</SelectContent>
</Select>
</Field>
<FieldSeparator />
<Field orientation="responsive">
<FieldContent>
<FieldLabel htmlFor="spoken-language">
Spoken Language
</FieldLabel>
<FieldDescription>
For best results, select the language you mainly speak. If
it&apos;s not listed, it may still be supported via
auto-detection.
</FieldDescription>
</FieldContent>
<Select
value={spokenLanguage}
onValueChange={setSpokenLanguage}
>
<SelectTrigger id="spoken-language">
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent align="end" position="item-aligned">
<SelectItem value="auto">Auto</SelectItem>
<SelectSeparator />
{spokenLanguages.map((language) => (
<SelectItem key={language.value} value={language.value}>
{language.label}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldLabel htmlFor="voice">Voice</FieldLabel>
<Select value={voice} onValueChange={setVoice}>
<SelectTrigger id="voice">
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent align="end" position="item-aligned">
{voices.map((voice) => (
<SelectItem key={voice.value} value={voice.value}>
{voice.label}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
</FieldGroup>
</FieldSet>
</TabsContent>
<TabsContent value="notifications">
<FieldGroup>
<FieldSet>
<FieldLabel>Responses</FieldLabel>
<FieldDescription>
Get notified when ChatGPT responds to requests that take time,
like research or image generation.
</FieldDescription>
<FieldGroup data-slot="checkbox-group">
<Field orientation="horizontal">
<Checkbox id="push" defaultChecked disabled />
<FieldLabel htmlFor="push" className="font-normal">
Push notifications
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
<FieldSeparator />
<FieldSet>
<FieldLabel>Tasks</FieldLabel>
<FieldDescription>
Get notified when tasks you&apos;ve created have updates.{" "}
<a href="#">Manage tasks</a>
</FieldDescription>
<FieldGroup data-slot="checkbox-group">
<Field orientation="horizontal">
<Checkbox id="push-tasks" />
<FieldLabel htmlFor="push-tasks" className="font-normal">
Push notifications
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="email-tasks" />
<FieldLabel htmlFor="email-tasks" className="font-normal">
Email notifications
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
</FieldGroup>
</TabsContent>
<TabsContent value="personalization">
<FieldGroup>
<Field orientation="responsive">
<FieldLabel htmlFor="nickname">Nickname</FieldLabel>
<InputGroup>
<InputGroupInput
id="nickname"
placeholder="Broski"
className="@md/field-group:max-w-[200px]"
/>
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton size="icon-xs">
<InfoIcon />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent className="flex items-center gap-2">
Used to identify you in the chat. <Kbd>N</Kbd>
</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
</Field>
<FieldSeparator />
<Field
orientation="responsive"
className="@md/field-group:flex-col @2xl/field-group:flex-row"
>
<FieldContent>
<FieldLabel htmlFor="about">More about you</FieldLabel>
<FieldDescription>
Tell us more about yourself. This will be used to help us
personalize your experience.
</FieldDescription>
</FieldContent>
<Textarea
id="about"
placeholder="I'm a software engineer..."
className="min-h-[120px] @md/field-group:min-w-full @2xl/field-group:min-w-[300px]"
/>
</Field>
<FieldSeparator />
<FieldLabel>
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="customization">
Enable customizations
</FieldLabel>
<FieldDescription>
Enable customizations to make ChatGPT more personalized.
</FieldDescription>
</FieldContent>
<Switch id="customization" defaultChecked />
</Field>
</FieldLabel>
<FieldSeparator />
<Field orientation="responsive">
<FieldContent>
<FieldLabel htmlFor="personality">
ChatGPT Personality
</FieldLabel>
<FieldDescription>
Set the style and tone ChatGPT should use when responding.
</FieldDescription>
</FieldContent>
<Select value={personality} onValueChange={setPersonality}>
<SelectTrigger id="personality">
{personalities.find((p) => p.value === personality)?.label}
</SelectTrigger>
<SelectContent align="end">
{personalities.map((personality) => (
<SelectItem
key={personality.value}
value={personality.value}
>
<FieldContent className="gap-0.5">
<FieldLabel>{personality.label}</FieldLabel>
<FieldDescription className="text-xs">
{personality.description}
</FieldDescription>
</FieldContent>
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
<FieldSeparator />
<Field>
<FieldLabel htmlFor="instructions">
Custom Instructions
</FieldLabel>
<Textarea
id="instructions"
value={customInstructions}
onChange={(e) => setCustomInstructions(e.target.value)}
/>
<div className="flex flex-wrap gap-2">
{instructions.map((instruction) => (
<Button
variant="outline"
key={instruction.value}
value={instruction.value}
className="rounded-full"
size="sm"
onClick={() =>
setCustomInstructions(
`${customInstructions} ${instruction.description}`
)
}
>
{instruction.label}
</Button>
))}
</div>
</Field>
</FieldGroup>
</TabsContent>
<TabsContent value="security">
<FieldGroup>
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="2fa">
Multi-factor authentication
</FieldLabel>
<FieldDescription>
Enable multi-factor authentication to secure your account.
If you do not have a two-factor authentication device, you
can use a one-time code sent to your email.
</FieldDescription>
</FieldContent>
<Switch id="2fa" />
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Log out</FieldTitle>
<FieldDescription>
Log out of your account on this device.
</FieldDescription>
</FieldContent>
<Button variant="outline" size="sm">
Log Out
</Button>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Log out of all devices</FieldTitle>
<FieldDescription>
This will log you out of all devices, including the current
session. It may take up to 30 minutes for the changes to
take effect.
</FieldDescription>
</FieldContent>
<Button variant="outline" size="sm">
Log Out All
</Button>
</Field>
</FieldGroup>
</TabsContent>
</div>
</Tabs>
</div>
)
}

View File

@@ -1,137 +0,0 @@
import { SunDimIcon, SunIcon } from "lucide-react"
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
FieldTitle,
} from "@/registry/new-york-v4/ui/field"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import { Slider } from "@/registry/new-york-v4/ui/slider"
import { Switch } from "@/registry/new-york-v4/ui/switch"
export function DisplaySettings() {
return (
<FieldSet>
<FieldLegend>Display</FieldLegend>
<FieldDescription>
Configure display settings, brightness, refresh rate, and more.
</FieldDescription>
<FieldGroup>
<Field orientation="responsive">
<FieldContent>
<FieldLabel htmlFor="resolution">Resolution</FieldLabel>
<FieldDescription>Select the display resolution.</FieldDescription>
</FieldContent>
<Select>
<SelectTrigger id="resolution" className="ml-auto">
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent align="end">
<SelectItem value="1920x1080">1920 x 1080</SelectItem>
<SelectItem value="2560x1440">2560 x 1440</SelectItem>
<SelectItem value="3840x2160">3840 x 2160</SelectItem>
<SelectItem value="auto">Auto</SelectItem>
</SelectContent>
</Select>
</Field>
<FieldSeparator />
<Field orientation="responsive">
<FieldContent>
<FieldTitle>Brightness</FieldTitle>
<FieldDescription>
Adjust the display brightness level.
</FieldDescription>
</FieldContent>
<div className="flex min-w-[150px] items-center gap-2">
<SunDimIcon className="size-4 shrink-0" />
<Slider
id="brightness"
defaultValue={[75]}
max={100}
step={1}
aria-label="Brightness"
/>
<SunIcon className="size-4 shrink-0" />
</div>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="auto-brightness">
Automatically Adjust Brightness
</FieldLabel>
<FieldDescription>
Automatically adjust brightness based on ambient light.
</FieldDescription>
</FieldContent>
<Checkbox id="auto-brightness" defaultChecked />
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="true-tone">True Tone</FieldLabel>
<FieldDescription>
Automatically adjust colors to match ambient lighting.
</FieldDescription>
</FieldContent>
<Switch id="true-tone" />
</Field>
<FieldSeparator />
<Field orientation="responsive">
<FieldContent>
<FieldLabel htmlFor="refresh-rate">Refresh Rate</FieldLabel>
<FieldDescription>
Select the display refresh rate.
</FieldDescription>
</FieldContent>
<Select>
<SelectTrigger id="refresh-rate" className="ml-auto min-w-[200px]">
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent align="end">
<SelectItem value="60hz">60 Hz</SelectItem>
<SelectItem value="120hz">120 Hz</SelectItem>
<SelectItem value="144hz">144 Hz</SelectItem>
<SelectItem value="240hz">240 Hz</SelectItem>
</SelectContent>
</Select>
</Field>
<FieldSeparator />
<Field orientation="responsive">
<FieldContent>
<FieldLabel htmlFor="tv-connection">
When connected to TV
</FieldLabel>
<FieldDescription>
Choose display behavior when connected to a TV.
</FieldDescription>
</FieldContent>
<Select>
<SelectTrigger id="tv-connection" className="ml-auto">
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent align="end">
<SelectItem value="mirror">Mirror Display</SelectItem>
<SelectItem value="extend">Extend Display</SelectItem>
<SelectItem value="tv-only">TV Only</SelectItem>
<SelectItem value="auto">Auto</SelectItem>
</SelectContent>
</Select>
</Field>
</FieldGroup>
</FieldSet>
)
}

View File

@@ -1,464 +0,0 @@
"use client"
import { useMemo, useState } from "react"
import {
IconApps,
IconArrowUp,
IconAt,
IconBook,
IconBrandAbstract,
IconBrandOpenai,
IconBrandZeit,
IconCircleDashedPlus,
IconPaperclip,
IconPlus,
IconWorld,
IconX,
} from "@tabler/icons-react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import { Badge } from "@/registry/new-york-v4/ui/badge"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/registry/new-york-v4/ui/command"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
import { Field, FieldLabel } from "@/registry/new-york-v4/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupTextarea,
} from "@/registry/new-york-v4/ui/input-group"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
import { Switch } from "@/registry/new-york-v4/ui/switch"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york-v4/ui/tooltip"
const SAMPLE_DATA = {
mentionable: [
{
type: "page",
title: "Meeting Notes",
image: "📝",
},
{
type: "page",
title: "Project Dashboard",
image: "📊",
},
{
type: "page",
title: "Ideas & Brainstorming",
image: "💡",
},
{
type: "page",
title: "Calendar & Events",
image: "📅",
},
{
type: "page",
title: "Documentation",
image: "📚",
},
{
type: "page",
title: "Goals & Objectives",
image: "🎯",
},
{
type: "page",
title: "Budget Planning",
image: "💰",
},
{
type: "page",
title: "Team Directory",
image: "👥",
},
{
type: "page",
title: "Technical Specs",
image: "🔧",
},
{
type: "page",
title: "Analytics Report",
image: "📈",
},
{
type: "user",
title: "shadcn",
image: "https://github.com/shadcn.png",
workspace: "Workspace",
},
{
type: "user",
title: "maxleiter",
image: "https://github.com/maxleiter.png",
workspace: "Cursor",
},
{
type: "user",
title: "evilrabbit",
image: "https://github.com/evilrabbit.png",
workspace: "Vercel",
},
],
models: [
{
name: "Auto",
icon: IconBrandZeit,
},
{
name: "Claude Sonnet 4",
icon: IconBrandAbstract,
badge: "Beta",
},
{
name: "GPT-5",
icon: IconBrandOpenai,
badge: "Beta",
},
],
}
function MentionableIcon({
item,
}: {
item: (typeof SAMPLE_DATA.mentionable)[0]
}) {
return item.type === "page" ? (
<span className="flex size-4 items-center justify-center">
{item.image}
</span>
) : (
<Avatar className="size-4">
<AvatarImage src={item.image} />
<AvatarFallback>{item.title[0]}</AvatarFallback>
</Avatar>
)
}
export function NotionPromptForm() {
const [mentions, setMentions] = useState<string[]>([])
const [mentionPopoverOpen, setMentionPopoverOpen] = useState(false)
const [modelPopoverOpen, setModelPopoverOpen] = useState(false)
const [selectedModel, setSelectedModel] = useState<
(typeof SAMPLE_DATA.models)[0]
>(SAMPLE_DATA.models[0])
const [scopeMenuOpen, setScopeMenuOpen] = useState(false)
const grouped = useMemo(() => {
return SAMPLE_DATA.mentionable.reduce(
(acc, item) => {
const isAvailable = !mentions.includes(item.title)
if (isAvailable) {
if (!acc[item.type]) {
acc[item.type] = []
}
acc[item.type].push(item)
}
return acc
},
{} as Record<string, typeof SAMPLE_DATA.mentionable>
)
}, [mentions])
const hasMentions = mentions.length > 0
return (
<form className="[--radius:1.2rem]">
<Field>
<FieldLabel htmlFor="notion-prompt" className="sr-only">
Prompt
</FieldLabel>
<InputGroup className="bg-background shadow-none dark:bg-background">
<InputGroupTextarea
id="notion-prompt"
placeholder="Ask, search, or make anything..."
/>
<InputGroupAddon align="block-start">
<Popover
open={mentionPopoverOpen}
onOpenChange={setMentionPopoverOpen}
>
<Tooltip>
<TooltipTrigger asChild>
<PopoverTrigger asChild>
<InputGroupButton
variant="outline"
size={!hasMentions ? "sm" : "icon-sm"}
className="rounded-full transition-transform"
>
<IconAt /> {!hasMentions && "Add context"}
</InputGroupButton>
</PopoverTrigger>
</TooltipTrigger>
<TooltipContent>Mention a person, page, or date</TooltipContent>
</Tooltip>
<PopoverContent className="p-0 [--radius:1.2rem]" align="start">
<Command>
<CommandInput placeholder="Search pages..." />
<CommandList>
<CommandEmpty>No pages found</CommandEmpty>
{Object.entries(grouped).map(([type, items]) => (
<CommandGroup
key={type}
heading={type === "page" ? "Pages" : "Users"}
>
{items.map((item) => (
<CommandItem
key={item.title}
value={item.title}
onSelect={(currentValue) => {
setMentions((prev) => [...prev, currentValue])
setMentionPopoverOpen(false)
}}
>
<MentionableIcon item={item} />
{item.title}
</CommandItem>
))}
</CommandGroup>
))}
</CommandList>
</Command>
</PopoverContent>
</Popover>
<div className="-m-1.5 no-scrollbar flex gap-1 overflow-y-auto p-1.5">
{mentions.map((mention) => {
const item = SAMPLE_DATA.mentionable.find(
(item) => item.title === mention
)
if (!item) {
return null
}
return (
<InputGroupButton
key={mention}
size="sm"
variant="secondary"
className="rounded-full pl-2!"
onClick={() => {
setMentions((prev) => prev.filter((m) => m !== mention))
}}
>
<MentionableIcon item={item} />
{item.title}
<IconX />
</InputGroupButton>
)
})}
</div>
</InputGroupAddon>
<InputGroupAddon align="block-end" className="gap-1">
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton
size="icon-sm"
className="rounded-full"
aria-label="Attach file"
>
<IconPaperclip />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>Attach file</TooltipContent>
</Tooltip>
<DropdownMenu
open={modelPopoverOpen}
onOpenChange={setModelPopoverOpen}
>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<InputGroupButton size="sm" className="rounded-full">
{selectedModel.icon && selectedModel.name !== "Auto" && (
<selectedModel.icon />
)}
{selectedModel.name}
</InputGroupButton>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent>Select AI model</TooltipContent>
</Tooltip>
<DropdownMenuContent
side="top"
align="start"
className="[--radius:1.2rem]"
>
<DropdownMenuGroup className="w-72">
<DropdownMenuLabel className="text-xs text-muted-foreground">
Get answers about your workspace
</DropdownMenuLabel>
{SAMPLE_DATA.models.map((model) => (
<DropdownMenuCheckboxItem
key={model.name}
checked={model.name === selectedModel.name}
onCheckedChange={(checked) => {
if (checked) {
setSelectedModel(model)
}
}}
className="pl-2 *:[span:first-child]:right-2 *:[span:first-child]:left-auto"
>
{model.icon && <model.icon />}
{model.name}
{model.badge && (
<Badge
variant="secondary"
className="h-5 rounded-sm bg-blue-100 px-1 text-xs text-blue-800 dark:bg-blue-900 dark:text-blue-100"
>
{model.badge}
</Badge>
)}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu open={scopeMenuOpen} onOpenChange={setScopeMenuOpen}>
<DropdownMenuTrigger asChild>
<InputGroupButton size="sm" className="rounded-full">
<IconWorld /> All Sources
</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="start"
className="[--radius:1.2rem]"
>
<DropdownMenuGroup>
<DropdownMenuItem
asChild
onSelect={(e) => e.preventDefault()}
>
<label htmlFor="web-search">
<IconWorld /> Web Search{" "}
<Switch
id="web-search"
className="ml-auto"
defaultChecked
/>
</label>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem
asChild
onSelect={(e) => e.preventDefault()}
>
<label htmlFor="apps">
<IconApps /> Apps and Integrations
<Switch id="apps" className="ml-auto" defaultChecked />
</label>
</DropdownMenuItem>
<DropdownMenuItem>
<IconCircleDashedPlus /> All Sources I can access
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Avatar className="size-4">
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
shadcn
</DropdownMenuSubTrigger>
<DropdownMenuSubContent className="w-72 p-0 [--radius:1.2rem]">
<Command>
<CommandInput
placeholder="Find or use knowledge in..."
autoFocus
/>
<CommandList>
<CommandEmpty>No knowledge found</CommandEmpty>
<CommandGroup>
{SAMPLE_DATA.mentionable
.filter((item) => item.type === "user")
.map((user) => (
<CommandItem
key={user.title}
value={user.title}
onSelect={() => {
// Handle user selection here
console.log("Selected user:", user.title)
}}
>
<Avatar className="size-4">
<AvatarImage src={user.image} />
<AvatarFallback>
{user.title[0]}
</AvatarFallback>
</Avatar>
{user.title}{" "}
<span className="text-muted-foreground">
- {user.workspace}
</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuItem>
<IconBook /> Help Center
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlus /> Connect Apps
</DropdownMenuItem>
<DropdownMenuLabel className="text-xs text-muted-foreground">
We&apos;ll only search in the sources selected here.
</DropdownMenuLabel>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<InputGroupButton
aria-label="Send"
className="ml-auto rounded-full"
variant="default"
size="icon-sm"
>
<IconArrowUp />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
</form>
)
}

View File

@@ -1,31 +0,0 @@
import { AppearanceSettings } from "@/app/(internal)/sink/(pages)/forms/appearance-settings"
import { ChatSettings } from "@/app/(internal)/sink/(pages)/forms/chat-settings"
import { DisplaySettings } from "@/app/(internal)/sink/(pages)/forms/display-settings"
import { NotionPromptForm } from "@/app/(internal)/sink/(pages)/forms/notion-prompt-form"
import { ShipRegistrationForm } from "@/app/(internal)/sink/(pages)/forms/ship-registration-form"
import { ShippingForm } from "@/app/(internal)/sink/(pages)/forms/shipping-form"
export default function FormsPage() {
return (
<div className="@container flex flex-1 flex-col gap-12 p-4">
<div className="grid flex-1 gap-12 @3xl:grid-cols-2 @5xl:grid-cols-3 @[120rem]:grid-cols-4 [&>div]:max-w-lg">
<div className="flex flex-col gap-12">
<NotionPromptForm />
<ChatSettings />
</div>
<div className="flex flex-col gap-12">
<AppearanceSettings />
</div>
<div className="flex flex-col gap-12">
<DisplaySettings />
</div>
<div className="flex flex-col gap-12">
<ShippingForm />
</div>
<div className="col-span-2 flex flex-col gap-12">
<ShipRegistrationForm />
</div>
</div>
</div>
)
}

View File

@@ -1,193 +0,0 @@
import { Button } from "@/registry/new-york-v4/ui/button"
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
FieldTitle,
} from "@/registry/new-york-v4/ui/field"
import { Input } from "@/registry/new-york-v4/ui/input"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/registry/new-york-v4/ui/input-group"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/new-york-v4/ui/radio-group"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
export function ShipRegistrationForm() {
return (
<div className="flex max-w-md flex-col gap-6">
<div className="flex flex-col gap-2">
<h1 className="text-2xl font-bold tracking-tight">
Join us in SF or online on October 23
</h1>
<FieldDescription>
Already signed up? <a href="#">Log in</a>
</FieldDescription>
</div>
<form>
<FieldGroup>
<FieldSet>
<FieldLegend>1. Select your ticket type</FieldLegend>
<FieldDescription>
Select your ticket type to join us in San Francisco or online on
October 23.
</FieldDescription>
<Field>
<RadioGroup>
<FieldLabel htmlFor="in-person">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>In Person</FieldTitle>
<FieldDescription>
Join us in San Francisco on October 23.
</FieldDescription>
</FieldContent>
<RadioGroupItem value="in-person" id="in-person" />
</Field>
</FieldLabel>
<FieldLabel htmlFor="online">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Online</FieldTitle>
<FieldDescription>
Join us online on October 23.
</FieldDescription>
</FieldContent>
<RadioGroupItem value="online" id="online" />
</Field>
</FieldLabel>
</RadioGroup>
</Field>
</FieldSet>
<Field orientation="horizontal">
<Checkbox id="next-conf" />
<FieldLabel htmlFor="next-conf">
Also sign up for Next.js Conf 2025
</FieldLabel>
</Field>
<FieldSet>
<FieldLegend>2. Complete your attendee information</FieldLegend>
<FieldDescription>
By entering your information, you acknowledge that you have read
and agree to the <a href="#">Terms of Service</a> and{" "}
<a href="#">Privacy Policy</a>.
</FieldDescription>
<FieldGroup className="grid grid-cols-2 gap-x-4">
<Field>
<FieldLabel htmlFor="first-name">First Name</FieldLabel>
<Input id="first-name" placeholder="Jane" required />
</Field>
<Field>
<FieldLabel htmlFor="last-name">Last Name</FieldLabel>
<Input id="last-name" placeholder="Doe" required />
</Field>
<Field>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" placeholder="jane.doe@example.com" required />
</Field>
<Field>
<FieldLabel htmlFor="company">Company</FieldLabel>
<Input id="company" placeholder="Example Inc." required />
</Field>
<Field>
<FieldLabel htmlFor="job-title">Job Title</FieldLabel>
<Input
id="job-title"
placeholder="Software Engineer"
required
/>
</Field>
<Field>
<FieldLabel htmlFor="country">Country</FieldLabel>
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a country" />
</SelectTrigger>
<SelectContent>
<SelectItem value="us">United States</SelectItem>
<SelectItem value="uk">United Kingdom</SelectItem>
<SelectItem value="ca">Canada</SelectItem>
</SelectContent>
</Select>
</Field>
<Field className="col-span-2">
<FieldLabel htmlFor="topics">
What AI-related topics are you most curious about?
</FieldLabel>
<Textarea
id="topics"
placeholder="Agents, Security, Improving UX/Personalization, etc."
className="min-h-[100px]"
/>
</Field>
<Field className="col-span-2">
<FieldLabel htmlFor="workloads">
What types of AI workloads are you tackling right now?
</FieldLabel>
<Textarea id="workloads" className="min-h-[100px]" />
</Field>
</FieldGroup>
</FieldSet>
<FieldSet>
<FieldLegend>3. Buy your ticket</FieldLegend>
<FieldDescription>
Enter your card details to purchase your ticket.
</FieldDescription>
<FieldGroup className="grid grid-cols-2 gap-x-4">
<Field className="col-span-2">
<FieldLabel htmlFor="card-number">Card Number</FieldLabel>
<Input
id="card-number"
placeholder="1234 5678 9012 3456"
required
/>
</Field>
<Field>
<FieldLabel htmlFor="expiry-date">Expiry Date</FieldLabel>
<Input id="expiry-date" placeholder="MM/YY" required />
</Field>
<Field>
<FieldLabel htmlFor="cvv">CVV</FieldLabel>
<Input id="cvv" placeholder="123" required />
</Field>
<Field className="col-span-2">
<FieldLabel htmlFor="promo-code">Promo Code</FieldLabel>
<InputGroup>
<InputGroupInput id="promo-code" placeholder="PROMO10" />
<InputGroupAddon align="inline-end">
<InputGroupButton>Apply</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
</FieldGroup>
</FieldSet>
<Field>
<Button type="submit">Purchase Ticket</Button>
<FieldDescription>
By clicking Purchase Ticket, you agree to the{" "}
<a href="#">Terms of Service</a> and{" "}
<a href="#">Privacy Policy</a>.
</FieldDescription>
</Field>
</FieldGroup>
</form>
</div>
)
}

View File

@@ -1,121 +0,0 @@
import { Badge } from "@/registry/new-york-v4/ui/badge"
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
FieldTitle,
} from "@/registry/new-york-v4/ui/field"
import { Input } from "@/registry/new-york-v4/ui/input"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/new-york-v4/ui/radio-group"
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
export function ShippingForm() {
return (
<FieldSet>
<FieldLegend>Shipping Details</FieldLegend>
<FieldDescription>
Please provide your shipping details so we can deliver your order.
</FieldDescription>
<FieldGroup>
<Field>
<FieldLabel htmlFor="street-address">Street Address</FieldLabel>
<Input id="street-address" autoComplete="off" />
</Field>
<Field>
<FieldLabel htmlFor="city">City</FieldLabel>
<Input id="city" />
</Field>
<FieldSet>
<FieldLegend variant="label">Shipping Method</FieldLegend>
<FieldDescription>
Please select the shipping method for your order.
</FieldDescription>
<RadioGroup>
<Field orientation="horizontal">
<RadioGroupItem value="standard" id="shipping-method-1" />
<FieldLabel htmlFor="shipping-method-1" className="font-normal">
Standard{" "}
<Badge className="rounded-full py-px" variant="outline">
Free
</Badge>
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="express" id="shipping-method-2" />
<FieldLabel htmlFor="shipping-method-2" className="font-normal">
Express
</FieldLabel>
</Field>
</RadioGroup>
</FieldSet>
<Field>
<FieldLabel htmlFor="message">Message</FieldLabel>
<Textarea id="message" />
<FieldDescription>Anything else you want to add?</FieldDescription>
</Field>
<FieldSet>
<FieldLegend>Additional Items</FieldLegend>
<FieldDescription>
Please select the additional items for your order.
</FieldDescription>
<FieldGroup data-slot="checkbox-group">
<FieldLabel htmlFor="gift-wrapping">
<Field orientation="horizontal">
<Checkbox
value="gift-wrapping"
id="gift-wrapping"
aria-label="Gift Wrapping"
/>
<FieldContent>
<FieldTitle>Gift Wrapping</FieldTitle>
<FieldDescription>
Add elegant gift wrapping with a personalized message.
</FieldDescription>
</FieldContent>
</Field>
</FieldLabel>
<FieldLabel htmlFor="insurance">
<Field orientation="horizontal">
<Checkbox
value="insurance"
id="insurance"
aria-label="Package Insurance"
/>
<FieldContent>
<FieldTitle>Package Insurance</FieldTitle>
<FieldDescription>
Protect your shipment with comprehensive insurance coverage.
</FieldDescription>
</FieldContent>
</Field>
</FieldLabel>
<FieldLabel htmlFor="signature-confirmation">
<Field orientation="horizontal">
<Checkbox
value="signature-confirmation"
id="signature-confirmation"
aria-label="Signature Confirmation"
/>
<FieldContent>
<FieldTitle>Signature Confirmation</FieldTitle>
<FieldDescription>
Require recipient signature upon delivery for added
security.
</FieldDescription>
</FieldContent>
</Field>
</FieldLabel>
</FieldGroup>
</FieldSet>
</FieldGroup>
</FieldSet>
)
}

View File

@@ -1,55 +0,0 @@
"use server"
import { type FormState } from "@/app/(internal)/sink/(pages)/next-form/example-form"
import { exampleFormSchema } from "@/app/(internal)/sink/(pages)/schema"
export async function subscriptionAction(
_prevState: FormState,
formData: FormData
): Promise<FormState> {
// Simulate server processing
await new Promise((resolve) => setTimeout(resolve, 1000))
const values = {
name: formData.get("name") as string,
email: formData.get("email") as string,
plan: formData.get("plan") as "basic" | "pro",
billingPeriod: formData.get("billingPeriod") as string,
addons: formData.getAll("addons") as string[],
teamSize: parseInt(formData.get("teamSize") as string) || 1,
emailNotifications: formData.get("emailNotifications") === "on",
startDate: formData.get("startDate")
? new Date(formData.get("startDate") as string)
: new Date(),
theme: formData.get("theme") as string,
password: formData.get("password") as string,
comments: formData.get("comments") as string,
}
const result = exampleFormSchema.safeParse(values)
if (!result.success) {
return {
values,
success: false,
errors: result.error.flatten().fieldErrors,
}
}
// Simulate some business logic validation
if (result.data.email.includes("invalid")) {
return {
values,
success: false,
errors: {
email: ["This email domain is not supported"],
},
}
}
return {
values,
errors: null,
success: true,
}
}

View File

@@ -1,394 +0,0 @@
"use client"
import * as React from "react"
import Form from "next/form"
import { type z } from "zod"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/registry/new-york-v4/ui/dialog"
import {
Field,
FieldContent,
FieldDescription,
FieldError,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
FieldTitle,
} from "@/registry/new-york-v4/ui/field"
import { Input } from "@/registry/new-york-v4/ui/input"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/new-york-v4/ui/radio-group"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import { Spinner } from "@/registry/new-york-v4/ui/spinner"
import { Switch } from "@/registry/new-york-v4/ui/switch"
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
import {
addons,
type exampleFormSchema,
} from "@/app/(internal)/sink/(pages)/schema"
import { subscriptionAction } from "./actions"
export type FormState = {
values: z.infer<typeof exampleFormSchema>
errors: null | Partial<
Record<keyof z.infer<typeof exampleFormSchema>, string[]>
>
success: boolean
}
export function ExampleForm() {
const formId = React.useId()
const [formKey, setFormKey] = React.useState(formId)
const [showResults, setShowResults] = React.useState(false)
const [formState, formAction, pending] = React.useActionState<
FormState,
FormData
>(subscriptionAction, {
values: {
name: "",
email: "",
plan: "basic",
billingPeriod: "",
addons: ["analytics"],
teamSize: 1,
emailNotifications: false,
comments: "",
startDate: new Date(),
theme: "system",
password: "",
},
errors: null,
success: false,
})
React.useEffect(() => {
if (formState.success) {
setShowResults(true)
}
}, [formState.success])
return (
<>
<Card className="w-full max-w-sm">
<CardHeader className="border-b">
<CardTitle>Subscription Form</CardTitle>
<CardDescription>
Create your subscription using server actions and useActionState.
</CardDescription>
</CardHeader>
<CardContent>
<Form action={formAction} id="subscription-form" key={formKey}>
<FieldGroup>
<Field data-invalid={!!formState.errors?.name?.length}>
<FieldLabel htmlFor="name">Name</FieldLabel>
<Input
id="name"
name="name"
defaultValue={formState.values.name}
disabled={pending}
aria-invalid={!!formState.errors?.name?.length}
autoComplete="off"
/>
<FieldDescription>Enter your name</FieldDescription>
{formState.errors?.name && (
<FieldError>{formState.errors.name[0]}</FieldError>
)}
</Field>
<Field data-invalid={!!formState.errors?.email?.length}>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input
id="email"
name="email"
type="email"
defaultValue={formState.values.email}
disabled={pending}
aria-invalid={!!formState.errors?.email?.length}
autoComplete="off"
/>
<FieldDescription>Enter your email address</FieldDescription>
{formState.errors?.email && (
<FieldError>{formState.errors.email[0]}</FieldError>
)}
</Field>
<FieldSeparator />
<FieldSet data-invalid={!!formState.errors?.plan?.length}>
<FieldLegend>Subscription Plan</FieldLegend>
<FieldDescription>
Choose your subscription plan.
</FieldDescription>
<RadioGroup
name="plan"
defaultValue={formState.values.plan}
disabled={pending}
aria-invalid={!!formState.errors?.plan?.length}
>
<FieldLabel htmlFor="basic">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Basic</FieldTitle>
<FieldDescription>
For individuals and small teams
</FieldDescription>
</FieldContent>
<RadioGroupItem value="basic" id="basic" />
</Field>
</FieldLabel>
<FieldLabel htmlFor="pro">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Pro</FieldTitle>
<FieldDescription>
For businesses with higher demands
</FieldDescription>
</FieldContent>
<RadioGroupItem value="pro" id="pro" />
</Field>
</FieldLabel>
</RadioGroup>
{formState.errors?.plan && (
<FieldError>{formState.errors.plan[0]}</FieldError>
)}
</FieldSet>
<FieldSeparator />
<Field data-invalid={!!formState.errors?.billingPeriod?.length}>
<FieldLabel htmlFor="billingPeriod">Billing Period</FieldLabel>
<Select
name="billingPeriod"
defaultValue={formState.values.billingPeriod}
disabled={pending}
aria-invalid={!!formState.errors?.billingPeriod?.length}
>
<SelectTrigger id="billingPeriod">
<SelectValue placeholder="Select billing period" />
</SelectTrigger>
<SelectContent>
<SelectItem value="monthly">Monthly</SelectItem>
<SelectItem value="yearly">Yearly</SelectItem>
</SelectContent>
</Select>
<FieldDescription>
Choose how often you want to be billed.
</FieldDescription>
{formState.errors?.billingPeriod && (
<FieldError>{formState.errors.billingPeriod[0]}</FieldError>
)}
</Field>
<FieldSeparator />
<FieldSet data-invalid={!!formState.errors?.addons?.length}>
<FieldLegend>Add-ons</FieldLegend>
<FieldDescription>
Select additional features you&apos;d like to include.
</FieldDescription>
<FieldGroup data-slot="checkbox-group">
{addons.map((addon) => (
<Field key={addon.id} orientation="horizontal">
<Checkbox
id={addon.id}
name="addons"
value={addon.id}
defaultChecked={formState.values.addons.includes(
addon.id
)}
disabled={pending}
aria-invalid={!!formState.errors?.addons?.length}
/>
<FieldContent>
<FieldLabel htmlFor={addon.id}>
{addon.title}
</FieldLabel>
<FieldDescription>{addon.description}</FieldDescription>
</FieldContent>
</Field>
))}
</FieldGroup>
{formState.errors?.addons && (
<FieldError>{formState.errors.addons[0]}</FieldError>
)}
</FieldSet>
<FieldSeparator />
<Field data-invalid={!!formState.errors?.teamSize?.length}>
<FieldLabel htmlFor="teamSize">Team Size</FieldLabel>
<Input
id="teamSize"
name="teamSize"
type="number"
min="1"
max="50"
defaultValue={formState.values.teamSize.toString()}
disabled={pending}
aria-invalid={!!formState.errors?.teamSize?.length}
/>
<FieldDescription>
How many people will be using the subscription? (1-50)
</FieldDescription>
{formState.errors?.teamSize && (
<FieldError>{formState.errors.teamSize[0]}</FieldError>
)}
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="emailNotifications">
Email Notifications
</FieldLabel>
<FieldDescription>
Receive email updates about your subscription
</FieldDescription>
</FieldContent>
<Switch
id="emailNotifications"
name="emailNotifications"
defaultChecked={formState.values.emailNotifications}
disabled={pending}
aria-invalid={!!formState.errors?.emailNotifications?.length}
/>
</Field>
<FieldSeparator />
<Field data-invalid={!!formState.errors?.startDate?.length}>
<FieldLabel htmlFor="startDate">Start Date</FieldLabel>
<Input
id="startDate"
name="startDate"
type="date"
defaultValue={
formState.values.startDate.toISOString().split("T")[0]
}
disabled={pending}
aria-invalid={!!formState.errors?.startDate?.length}
/>
<FieldDescription>
Choose when your subscription should start
</FieldDescription>
{formState.errors?.startDate && (
<FieldError>{formState.errors.startDate[0]}</FieldError>
)}
</Field>
<FieldSeparator />
<Field data-invalid={!!formState.errors?.theme?.length}>
<FieldLabel htmlFor="theme">Theme Preference</FieldLabel>
<Select
name="theme"
defaultValue={formState.values.theme}
disabled={pending}
aria-invalid={!!formState.errors?.theme?.length}
>
<SelectTrigger id="theme">
<SelectValue placeholder="Select theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
<FieldDescription>
Choose your preferred color theme
</FieldDescription>
{formState.errors?.theme && (
<FieldError>{formState.errors.theme[0]}</FieldError>
)}
</Field>
<FieldSeparator />
<Field data-invalid={!!formState.errors?.password?.length}>
<FieldLabel htmlFor="password">Password</FieldLabel>
<Input
id="password"
name="password"
type="password"
defaultValue={formState.values.password}
placeholder="Enter your password"
disabled={pending}
aria-invalid={!!formState.errors?.password?.length}
/>
<FieldDescription>
Must contain uppercase, lowercase, number, and be 8+
characters
</FieldDescription>
{formState.errors?.password && (
<FieldError>{formState.errors.password[0]}</FieldError>
)}
</Field>
<FieldSeparator />
<Field data-invalid={!!formState.errors?.comments?.length}>
<FieldLabel htmlFor="comments">Additional Comments</FieldLabel>
<Textarea
id="comments"
name="comments"
defaultValue={formState.values.comments}
placeholder="Tell us more about your needs..."
rows={3}
disabled={pending}
aria-invalid={!!formState.errors?.comments?.length}
/>
<FieldDescription>
Share any additional requirements or feedback (10-240
characters)
</FieldDescription>
{formState.errors?.comments && (
<FieldError>{formState.errors.comments[0]}</FieldError>
)}
</Field>
</FieldGroup>
</Form>
</CardContent>
<CardFooter className="border-t">
<Field orientation="horizontal" className="justify-end">
<Button
type="button"
variant="outline"
disabled={pending}
form="subscription-form"
onClick={() => setFormKey(formKey + 1)}
>
Reset
</Button>
<Button type="submit" disabled={pending} form="subscription-form">
{pending && <Spinner />}
Create Subscription
</Button>
</Field>
</CardFooter>
</Card>
<Dialog open={showResults} onOpenChange={setShowResults}>
<DialogContent>
<DialogHeader>
<DialogTitle>Subscription Created!</DialogTitle>
<DialogDescription>
Here are the details of your subscription.
</DialogDescription>
</DialogHeader>
<pre className="overflow-x-auto rounded-md bg-black p-4 font-mono text-sm text-white">
<code>{JSON.stringify(formState.values, null, 2)}</code>
</pre>
</DialogContent>
</Dialog>
</>
)
}

View File

@@ -1,9 +0,0 @@
import { ExampleForm } from "@/app/(internal)/sink/(pages)/next-form/example-form"
export default function NextFormPage() {
return (
<div className="flex min-h-screen items-center justify-center p-4">
<ExampleForm />
</div>
)
}

View File

@@ -1,490 +0,0 @@
"use client"
import { useState } from "react"
import { zodResolver } from "@hookform/resolvers/zod"
import { format } from "date-fns"
import { Controller, useForm } from "react-hook-form"
import type z from "zod"
import { Button } from "@/registry/new-york-v4/ui/button"
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/registry/new-york-v4/ui/dialog"
import {
Field,
FieldContent,
FieldDescription,
FieldError,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
FieldTitle,
} from "@/registry/new-york-v4/ui/field"
import { Input } from "@/registry/new-york-v4/ui/input"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/new-york-v4/ui/radio-group"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import { Slider } from "@/registry/new-york-v4/ui/slider"
import { Switch } from "@/registry/new-york-v4/ui/switch"
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/new-york-v4/ui/toggle-group"
import { addons, exampleFormSchema } from "@/app/(internal)/sink/(pages)/schema"
export function ExampleForm() {
const [values, setValues] = useState<z.infer<typeof exampleFormSchema>>()
const [open, setOpen] = useState(false)
const form = useForm<z.infer<typeof exampleFormSchema>>({
resolver: zodResolver(exampleFormSchema),
mode: "onChange",
defaultValues: {
name: "",
email: "",
plan: "basic" as const,
billingPeriod: "",
addons: ["analytics"],
emailNotifications: false,
teamSize: 1,
comments: "",
startDate: new Date(),
theme: "system",
password: "",
},
})
function onSubmit(data: z.infer<typeof exampleFormSchema>) {
setValues(data)
setOpen(true)
}
return (
<>
<Card className="w-full max-w-sm">
<CardHeader className="border-b">
<CardTitle>React Hook Form</CardTitle>
<CardDescription>
This form uses React Hook Form with Zod validation.
</CardDescription>
</CardHeader>
<CardContent>
<form id="subscription-form" onSubmit={form.handleSubmit(onSubmit)}>
<FieldGroup>
<Controller
name="name"
control={form.control}
render={({ field, fieldState }) => {
const isInvalid = fieldState.invalid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Name</FieldLabel>
<Input
{...field}
id={field.name}
aria-invalid={isInvalid}
autoComplete="off"
/>
<FieldDescription>Enter your name</FieldDescription>
{isInvalid && <FieldError errors={[fieldState.error]} />}
</Field>
)
}}
/>
<Controller
name="email"
control={form.control}
render={({ field, fieldState }) => {
const isInvalid = fieldState.invalid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Email</FieldLabel>
<Input
{...field}
type="email"
id={field.name}
aria-invalid={isInvalid}
autoComplete="off"
/>
<FieldDescription>
Enter your email address
</FieldDescription>
{isInvalid && <FieldError errors={[fieldState.error]} />}
</Field>
)
}}
/>
<FieldSeparator />
<Controller
name="plan"
control={form.control}
render={({ field, fieldState }) => {
const isInvalid = fieldState.invalid
return (
<FieldSet data-invalid={isInvalid}>
<FieldLegend>Subscription Plan</FieldLegend>
<FieldDescription>
Choose your subscription plan.
</FieldDescription>
<RadioGroup
name={field.name}
value={field.value}
onValueChange={field.onChange}
aria-invalid={isInvalid}
>
<FieldLabel htmlFor="basic">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Basic</FieldTitle>
<FieldDescription>
For individuals and small teams
</FieldDescription>
</FieldContent>
<RadioGroupItem
value="basic"
id="basic"
aria-invalid={isInvalid}
/>
</Field>
</FieldLabel>
<FieldLabel htmlFor="pro">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Pro</FieldTitle>
<FieldDescription>
For businesses with higher demands
</FieldDescription>
</FieldContent>
<RadioGroupItem
value="pro"
id="pro"
aria-invalid={isInvalid}
/>
</Field>
</FieldLabel>
</RadioGroup>
{isInvalid && <FieldError errors={[fieldState.error]} />}
</FieldSet>
)
}}
/>
<FieldSeparator />
<Controller
name="billingPeriod"
control={form.control}
render={({ field, fieldState }) => {
const isInvalid = fieldState.invalid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>
Billing Period
</FieldLabel>
<Select
name={field.name}
value={field.value}
onValueChange={field.onChange}
aria-invalid={isInvalid}
>
<SelectTrigger id={field.name}>
<SelectValue placeholder="Select billing period" />
</SelectTrigger>
<SelectContent>
<SelectItem value="monthly">Monthly</SelectItem>
<SelectItem value="yearly">Yearly</SelectItem>
</SelectContent>
</Select>
<FieldDescription>
Choose how often you want to be billed.
</FieldDescription>
{isInvalid && <FieldError errors={[fieldState.error]} />}
</Field>
)
}}
/>
<FieldSeparator />
<Controller
name="addons"
control={form.control}
render={({ field, fieldState }) => {
const isInvalid = fieldState.invalid
return (
<FieldSet data-invalid={isInvalid}>
<FieldLegend>Add-ons</FieldLegend>
<FieldDescription>
Select additional features you&apos;d like to include.
</FieldDescription>
<FieldGroup data-slot="checkbox-group">
{addons.map((addon) => (
<Field key={addon.id} orientation="horizontal">
<Checkbox
id={addon.id}
name={field.name}
aria-invalid={isInvalid}
checked={field.value.includes(addon.id)}
onCheckedChange={(checked) => {
const newValue = checked
? [...field.value, addon.id]
: field.value.filter(
(value) => value !== addon.id
)
field.onChange(newValue)
field.onBlur()
}}
/>
<FieldContent>
<FieldLabel htmlFor={addon.id}>
{addon.title}
</FieldLabel>
<FieldDescription>
{addon.description}
</FieldDescription>
</FieldContent>
</Field>
))}
</FieldGroup>
{isInvalid && <FieldError errors={[fieldState.error]} />}
</FieldSet>
)
}}
/>
<FieldSeparator />
<Controller
name="teamSize"
control={form.control}
render={({ field, fieldState }) => {
const isInvalid = fieldState.invalid
return (
<Field data-invalid={isInvalid}>
<FieldTitle>Team Size</FieldTitle>
<FieldDescription>
How many people will be using the subscription?
</FieldDescription>
<Slider
id={field.name}
name={field.name}
value={[field.value]}
onValueChange={field.onChange}
min={1}
max={50}
step={1}
aria-invalid={isInvalid}
/>
{isInvalid && <FieldError errors={[fieldState.error]} />}
</Field>
)
}}
/>
<FieldSeparator />
<Controller
name="emailNotifications"
control={form.control}
render={({ field, fieldState }) => {
const isInvalid = fieldState.invalid
return (
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor={field.name}>
Email Notifications
</FieldLabel>
<FieldDescription>
Receive email updates about your subscription
</FieldDescription>
</FieldContent>
<Switch
id={field.name}
name={field.name}
checked={field.value}
onCheckedChange={field.onChange}
aria-invalid={isInvalid}
/>
{isInvalid && <FieldError errors={[fieldState.error]} />}
</Field>
)
}}
/>
<FieldSeparator />
<Controller
name="startDate"
control={form.control}
render={({ field, fieldState }) => {
const isInvalid = fieldState.invalid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Start Date</FieldLabel>
<Popover>
<PopoverTrigger asChild>
<Button
id={field.name}
variant="outline"
className="justify-start"
aria-invalid={isInvalid}
>
{field.value ? (
format(field.value, "PPP")
) : (
<span>Pick a date</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
required
mode="single"
selected={field.value}
onSelect={field.onChange}
/>
</PopoverContent>
</Popover>
<FieldDescription>
Choose when your subscription should start
</FieldDescription>
{isInvalid && <FieldError errors={[fieldState.error]} />}
</Field>
)
}}
/>
<FieldSeparator />
<Controller
name="theme"
control={form.control}
render={({ field, fieldState }) => {
const isInvalid = fieldState.invalid
return (
<Field data-invalid={isInvalid}>
<FieldTitle>Theme Preference</FieldTitle>
<ToggleGroup
type="single"
variant="outline"
value={field.value}
onValueChange={(value) =>
value && field.onChange(value)
}
aria-invalid={isInvalid}
>
<ToggleGroupItem value="light">Light</ToggleGroupItem>
<ToggleGroupItem value="dark">Dark</ToggleGroupItem>
<ToggleGroupItem value="system">System</ToggleGroupItem>
</ToggleGroup>
<FieldDescription>
Choose your preferred color theme
</FieldDescription>
{isInvalid && <FieldError errors={[fieldState.error]} />}
</Field>
)
}}
/>
<FieldSeparator />
<Controller
name="password"
control={form.control}
render={({ field, fieldState }) => {
const isInvalid = fieldState.invalid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Password</FieldLabel>
<Input
{...field}
type="password"
placeholder="Enter your password"
id={field.name}
aria-invalid={isInvalid}
/>
<FieldDescription>
Must contain uppercase, lowercase, number, and be 8+
characters
</FieldDescription>
{isInvalid && <FieldError errors={[fieldState.error]} />}
</Field>
)
}}
/>
<FieldSeparator />
<Controller
name="comments"
control={form.control}
render={({ field, fieldState }) => {
const isInvalid = fieldState.invalid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>
Additional Comments
</FieldLabel>
<Textarea
{...field}
id={field.name}
placeholder="Tell us more about your needs..."
rows={3}
aria-invalid={isInvalid}
/>
<FieldDescription>
Share any additional requirements or feedback (10-240
characters)
</FieldDescription>
{isInvalid && <FieldError errors={[fieldState.error]} />}
</Field>
)
}}
/>
</FieldGroup>
</form>
</CardContent>
<CardFooter className="border-t">
<Field orientation="horizontal" className="justify-end">
<Button
type="button"
variant="outline"
onClick={() => form.reset()}
>
Reset
</Button>
<Button type="submit" form="subscription-form">
Submit
</Button>
</Field>
</CardFooter>
</Card>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Submitted Values</DialogTitle>
<DialogDescription>
Here are the values you submitted.
</DialogDescription>
</DialogHeader>
<pre className="overflow-x-auto rounded-md bg-black p-4 font-mono text-sm text-white">
<code>{JSON.stringify(values, null, 2)}</code>
</pre>
</DialogContent>
</Dialog>
</>
)
}

View File

@@ -1,9 +0,0 @@
import { ExampleForm } from "@/app/(internal)/sink/(pages)/react-hook-form/example-form"
export default function ReactHookFormPage() {
return (
<div className="flex min-h-screen items-center justify-center p-4">
<ExampleForm />
</div>
)
}

View File

@@ -1,97 +0,0 @@
import { z } from "zod"
export const addons = [
{
id: "analytics",
title: "Analytics",
description: "Advanced analytics and reporting",
},
{
id: "backup",
title: "Backup",
description: "Automated daily backups",
},
{
id: "support",
title: "Priority Support",
description: "24/7 premium customer support",
},
] as const
export const exampleFormSchema = z.object({
name: z
.string({
required_error: "Name is required",
invalid_type_error: "Name must be a string",
})
.min(2, "Name must be at least 2 characters")
.max(50, "Name must be less than 50 characters")
.refine((value) => !/\d/.test(value), {
message: "Name must not contain numbers",
}),
email: z
.string({
required_error: "Email is required",
})
.email("Please enter a valid email address"),
plan: z
.string({
required_error: "Please select a subscription plan",
})
.min(1, "Please select a subscription plan")
.refine((value) => value === "basic" || value === "pro", {
message: "Invalid plan selection. Please choose Basic or Pro",
}),
billingPeriod: z
.string({
required_error: "Please select a billing period",
})
.min(1, "Please select a billing period"),
addons: z
.array(z.string())
.min(1, "Please select at least one add-on")
.max(3, "You can select up to 3 add-ons"),
teamSize: z.number().min(1).max(10),
emailNotifications: z.boolean({
required_error: "Please choose email notification preference",
}),
comments: z
.string()
.min(10, "Comments must be at least 10 characters")
.max(240, "Comments must not exceed 240 characters"),
startDate: z
.date({
required_error: "Please select a start date",
invalid_type_error: "Invalid date format",
})
.min(new Date(), "Start date cannot be in the past")
.refine(
(date) => {
const now = new Date()
const oneWeekFromNow = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000)
return date <= oneWeekFromNow
},
{
message: "Start date must be within the current week",
}
),
theme: z
.string({
required_error: "Please select a theme",
})
.min(1, "Please select a theme"),
password: z
.string({
required_error: "Password is required",
})
.min(8, "Password must be at least 8 characters")
.regex(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
"Password must contain at least one uppercase letter, one lowercase letter, and one number"
),
})

View File

@@ -1,532 +0,0 @@
/* eslint-disable react/no-children-prop */
"use client"
import * as React from "react"
import { useForm } from "@tanstack/react-form"
import { format } from "date-fns"
import { Button } from "@/registry/new-york-v4/ui/button"
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/registry/new-york-v4/ui/dialog"
import {
Field,
FieldContent,
FieldDescription,
FieldError,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
FieldTitle,
} from "@/registry/new-york-v4/ui/field"
import { Input } from "@/registry/new-york-v4/ui/input"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/new-york-v4/ui/radio-group"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import { Slider } from "@/registry/new-york-v4/ui/slider"
import { Switch } from "@/registry/new-york-v4/ui/switch"
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/new-york-v4/ui/toggle-group"
import { addons, exampleFormSchema } from "@/app/(internal)/sink/(pages)/schema"
export function ExampleForm() {
const form = useForm({
defaultValues: {
name: "",
email: "",
plan: "",
billingPeriod: "",
addons: ["analytics"],
emailNotifications: false,
teamSize: 1,
comments: "",
startDate: new Date(),
theme: "system",
password: "",
},
validators: {
onBlur: exampleFormSchema,
},
onSubmit: async ({ value }) => {
setValues(value)
setOpen(true)
},
})
const [values, setValues] = React.useState<typeof form.state.values>()
const [open, setOpen] = React.useState(false)
return (
<>
<Card className="w-full max-w-sm">
<CardHeader className="border-b">
<CardTitle>Example Form</CardTitle>
<CardDescription>
This is an example form using TanStack Form.
</CardDescription>
</CardHeader>
<CardContent>
<form
id="example-form"
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
void form.handleSubmit()
}}
>
<FieldGroup>
<form.Field
name="name"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Name</FieldLabel>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
aria-invalid={isInvalid}
autoComplete="off"
/>
<FieldDescription>Enter your name</FieldDescription>
{isInvalid && (
<FieldError errors={field.state.meta.errors} />
)}
</Field>
)
}}
/>
<form.Field
name="email"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Email</FieldLabel>
<Input
id={field.name}
name={field.name}
type="email"
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
aria-invalid={isInvalid}
autoComplete="off"
/>
<FieldDescription>
Enter your email address
</FieldDescription>
{isInvalid && (
<FieldError errors={field.state.meta.errors} />
)}
</Field>
)
}}
/>
<FieldSeparator />
<form.Field
name="plan"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<FieldSet data-invalid={isInvalid}>
<FieldLegend>Subscription Plan</FieldLegend>
<FieldDescription>
Choose your subscription plan.
</FieldDescription>
<RadioGroup
name={field.name}
value={field.state.value}
onValueChange={field.handleChange}
aria-invalid={isInvalid}
>
<FieldLabel htmlFor="basic">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Basic</FieldTitle>
<FieldDescription>
For individuals and small teams
</FieldDescription>
</FieldContent>
<RadioGroupItem
value="basic"
id="basic"
aria-invalid={isInvalid}
/>
</Field>
</FieldLabel>
<FieldLabel htmlFor="pro">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Pro</FieldTitle>
<FieldDescription>
For businesses with higher demands
</FieldDescription>
</FieldContent>
<RadioGroupItem
value="pro"
id="pro"
aria-invalid={isInvalid}
/>
</Field>
</FieldLabel>
</RadioGroup>
{isInvalid && (
<FieldError errors={field.state.meta.errors} />
)}
</FieldSet>
)
}}
/>
<FieldSeparator />
<form.Field
name="billingPeriod"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>
Billing Period
</FieldLabel>
<Select
name={field.name}
value={field.state.value}
onValueChange={field.handleChange}
aria-invalid={isInvalid}
>
<SelectTrigger id={field.name}>
<SelectValue placeholder="Select billing period" />
</SelectTrigger>
<SelectContent>
<SelectItem value="monthly">Monthly</SelectItem>
<SelectItem value="yearly">Yearly</SelectItem>
</SelectContent>
</Select>
<FieldDescription>
Choose how often you want to be billed.
</FieldDescription>
{isInvalid && (
<FieldError errors={field.state.meta.errors} />
)}
</Field>
)
}}
/>
<FieldSeparator />
<form.Field
name="addons"
mode="array"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<FieldSet data-invalid={isInvalid}>
<FieldLegend>Add-ons</FieldLegend>
<FieldDescription>
Select additional features you&apos;d like to include.
</FieldDescription>
<FieldGroup data-slot="checkbox-group">
{addons.map((addon) => (
<Field key={addon.id} orientation="horizontal">
<Checkbox
id={addon.id}
name={field.name}
aria-invalid={isInvalid}
checked={field.state.value.includes(addon.id)}
onCheckedChange={(checked) => {
if (checked) {
field.pushValue(addon.id)
} else {
const index = field.state.value.indexOf(
addon.id
)
if (index > -1) {
field.removeValue(index)
}
}
}}
/>
<FieldContent>
<FieldLabel htmlFor={addon.id}>
{addon.title}
</FieldLabel>
<FieldDescription>
{addon.description}
</FieldDescription>
</FieldContent>
</Field>
))}
</FieldGroup>
{isInvalid && (
<FieldError errors={field.state.meta.errors} />
)}
</FieldSet>
)
}}
/>
<FieldSeparator />
<form.Field
name="teamSize"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldTitle>Team Size</FieldTitle>
<FieldDescription>
How many people will be using the subscription?
</FieldDescription>
<Slider
id={field.name}
name={field.name}
value={[field.state.value]}
onValueChange={(value) => field.handleChange(value[0])}
min={1}
max={50}
step={10}
/>
{isInvalid && (
<FieldError errors={field.state.meta.errors} />
)}
</Field>
)
}}
/>
<FieldSeparator />
<form.Field
name="emailNotifications"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor={field.name}>
Email Notifications
</FieldLabel>
<FieldDescription>
Receive email updates about your subscription
</FieldDescription>
</FieldContent>
<Switch
id={field.name}
name={field.name}
checked={field.state.value}
onCheckedChange={field.handleChange}
/>
{isInvalid && (
<FieldError errors={field.state.meta.errors} />
)}
</Field>
)
}}
/>
<FieldSeparator />
<form.Field
name="startDate"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Start Date</FieldLabel>
<Popover>
<PopoverTrigger asChild>
<Button
id={field.name}
variant="outline"
className="justify-start"
>
{field.state.value ? (
format(field.state.value, "PPP")
) : (
<span>Pick a date</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
required
mode="single"
selected={field.state.value}
onSelect={field.handleChange}
/>
</PopoverContent>
</Popover>
<FieldDescription>
Choose when your subscription should start
</FieldDescription>
{isInvalid && (
<FieldError errors={field.state.meta.errors} />
)}
</Field>
)
}}
/>
<FieldSeparator />
<form.Field
name="theme"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldTitle>Theme Preference</FieldTitle>
<ToggleGroup
id={field.name}
type="single"
variant="outline"
value={field.state.value}
onValueChange={(value) =>
value && field.handleChange(value)
}
aria-invalid={isInvalid}
>
<ToggleGroupItem value="light">Light</ToggleGroupItem>
<ToggleGroupItem value="dark">Dark</ToggleGroupItem>
<ToggleGroupItem value="system">System</ToggleGroupItem>
</ToggleGroup>
<FieldDescription>
Choose your preferred color theme
</FieldDescription>
{isInvalid && (
<FieldError errors={field.state.meta.errors} />
)}
</Field>
)
}}
/>
<FieldSeparator />
<form.Field
name="password"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Password</FieldLabel>
<Input
id={field.name}
name={field.name}
type="password"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
placeholder="Enter your password"
aria-invalid={isInvalid}
/>
<FieldDescription>
Must contain uppercase, lowercase, number, and be 8+
characters
</FieldDescription>
{isInvalid && (
<FieldError errors={field.state.meta.errors} />
)}
</Field>
)
}}
/>
<FieldSeparator />
<form.Field
name="comments"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>
Additional Comments
</FieldLabel>
<Textarea
id={field.name}
name={field.name}
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
placeholder="Tell us more about your needs..."
rows={3}
aria-invalid={isInvalid}
/>
<FieldDescription>
Share any additional requirements or feedback (10-240
characters)
</FieldDescription>
{isInvalid && (
<FieldError errors={field.state.meta.errors} />
)}
</Field>
)
}}
/>
</FieldGroup>
</form>
</CardContent>
<CardFooter className="border-t">
<Field orientation="horizontal" className="justify-end">
<Button
type="button"
variant="outline"
onClick={() => form.reset()}
>
Reset
</Button>
<Button type="submit" form="example-form">
Submit
</Button>
</Field>
</CardFooter>
</Card>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Submitted Values</DialogTitle>
<DialogDescription>
Here are the values you submitted.
</DialogDescription>
</DialogHeader>
<pre className="overflow-x-auto rounded-md bg-black p-4 font-mono text-sm text-white">
<code>{JSON.stringify(values, null, 2)}</code>
</pre>
</DialogContent>
</Dialog>
</>
)
}

View File

@@ -1,9 +0,0 @@
import { ExampleForm } from "@/app/(internal)/sink/(pages)/tanstack-form/example-form"
export default function TanstackFormPage() {
return (
<div className="flex min-h-screen items-center justify-center p-4">
<ExampleForm />
</div>
)
}

View File

@@ -1,54 +0,0 @@
import { type Metadata } from "next"
import { notFound } from "next/navigation"
import { componentRegistry } from "@/app/(internal)/sink/component-registry"
export const dynamic = "force-static"
export const revalidate = false
export async function generateStaticParams() {
return Object.keys(componentRegistry).map((name) => ({
name,
}))
}
export async function generateMetadata({
params,
}: {
params: Promise<{ name: string }>
}): Promise<Metadata> {
const { name } = await params
const component = componentRegistry[name as keyof typeof componentRegistry]
if (!component) {
return {
title: "Component Not Found",
}
}
return {
title: `${component.name} - Kitchen Sink`,
description: `Demo page for ${component.name} component`,
}
}
export default async function ComponentPage({
params,
}: {
params: Promise<{ name: string }>
}) {
const { name } = await params
const component = componentRegistry[name as keyof typeof componentRegistry]
if (!component) {
notFound()
}
const Component = component.component
return (
<div className="p-6">
<Component />
</div>
)
}

View File

@@ -1,437 +0,0 @@
import FormsPage from "@/app/(internal)/sink/(pages)/forms/page"
import NextFormPage from "./(pages)/next-form/page"
import ReactHookFormPage from "./(pages)/react-hook-form/page"
import TanstackFormPage from "./(pages)/tanstack-form/page"
import { AccordionDemo } from "./components/accordion-demo"
import { AlertDemo } from "./components/alert-demo"
import { AlertDialogDemo } from "./components/alert-dialog-demo"
import { AspectRatioDemo } from "./components/aspect-ratio-demo"
import { AvatarDemo } from "./components/avatar-demo"
import { BadgeDemo } from "./components/badge-demo"
import { BreadcrumbDemo } from "./components/breadcrumb-demo"
import { ButtonDemo } from "./components/button-demo"
import { ButtonGroupDemo } from "./components/button-group-demo"
import { CalendarDemo } from "./components/calendar-demo"
import { CardDemo } from "./components/card-demo"
import { CarouselDemo } from "./components/carousel-demo"
import { ChartDemo } from "./components/chart-demo"
import { CheckboxDemo } from "./components/checkbox-demo"
import { CollapsibleDemo } from "./components/collapsible-demo"
import { ComboboxDemo } from "./components/combobox-demo"
import { CommandDemo } from "./components/command-demo"
import { ContextMenuDemo } from "./components/context-menu-demo"
import { DatePickerDemo } from "./components/date-picker-demo"
import { DialogDemo } from "./components/dialog-demo"
import { DrawerDemo } from "./components/drawer-demo"
import { DropdownMenuDemo } from "./components/dropdown-menu-demo"
import { EmptyDemo } from "./components/empty-demo"
import { FieldDemo } from "./components/field-demo"
import { FormDemo } from "./components/form-demo"
import { HoverCardDemo } from "./components/hover-card-demo"
import { InputDemo } from "./components/input-demo"
import { InputGroupDemo } from "./components/input-group-demo"
import { InputOTPDemo } from "./components/input-otp-demo"
import { ItemDemo } from "./components/item-demo"
import { KbdDemo } from "./components/kbd-demo"
import { LabelDemo } from "./components/label-demo"
import { MenubarDemo } from "./components/menubar-demo"
import { NativeSelectDemo } from "./components/native-select-demo"
import { NavigationMenuDemo } from "./components/navigation-menu-demo"
import { PaginationDemo } from "./components/pagination-demo"
import { PopoverDemo } from "./components/popover-demo"
import { ProgressDemo } from "./components/progress-demo"
import { RadioGroupDemo } from "./components/radio-group-demo"
import { ResizableDemo } from "./components/resizable-demo"
import { ScrollAreaDemo } from "./components/scroll-area-demo"
import { SelectDemo } from "./components/select-demo"
import { SeparatorDemo } from "./components/separator-demo"
import { SheetDemo } from "./components/sheet-demo"
import { SkeletonDemo } from "./components/skeleton-demo"
import { SliderDemo } from "./components/slider-demo"
import { SonnerDemo } from "./components/sonner-demo"
import { SpinnerDemo } from "./components/spinner-demo"
import { SwitchDemo } from "./components/switch-demo"
import { TableDemo } from "./components/table-demo"
import { TabsDemo } from "./components/tabs-demo"
import { TextareaDemo } from "./components/textarea-demo"
import { ToggleDemo } from "./components/toggle-demo"
import { ToggleGroupDemo } from "./components/toggle-group-demo"
import { TooltipDemo } from "./components/tooltip-demo"
type ComponentConfig = {
name: string
component: React.ComponentType
className?: string
type: "registry:ui" | "registry:page" | "registry:block"
href: string
label?: string
}
export const componentRegistry: Record<string, ComponentConfig> = {
accordion: {
name: "Accordion",
component: AccordionDemo,
type: "registry:ui",
href: "/sink/accordion",
},
alert: {
name: "Alert",
component: AlertDemo,
type: "registry:ui",
href: "/sink/alert",
},
"alert-dialog": {
name: "Alert Dialog",
component: AlertDialogDemo,
type: "registry:ui",
href: "/sink/alert-dialog",
},
"aspect-ratio": {
name: "Aspect Ratio",
component: AspectRatioDemo,
type: "registry:ui",
href: "/sink/aspect-ratio",
},
avatar: {
name: "Avatar",
component: AvatarDemo,
type: "registry:ui",
href: "/sink/avatar",
},
badge: {
name: "Badge",
component: BadgeDemo,
type: "registry:ui",
href: "/sink/badge",
},
breadcrumb: {
name: "Breadcrumb",
component: BreadcrumbDemo,
type: "registry:ui",
href: "/sink/breadcrumb",
},
button: {
name: "Button",
component: ButtonDemo,
type: "registry:ui",
href: "/sink/button",
},
"button-group": {
name: "Button Group",
component: ButtonGroupDemo,
type: "registry:ui",
href: "/sink/button-group",
label: "New",
},
calendar: {
name: "Calendar",
component: CalendarDemo,
type: "registry:ui",
href: "/sink/calendar",
},
card: {
name: "Card",
component: CardDemo,
type: "registry:ui",
href: "/sink/card",
},
carousel: {
name: "Carousel",
component: CarouselDemo,
type: "registry:ui",
href: "/sink/carousel",
},
chart: {
name: "Chart",
component: ChartDemo,
className: "w-full",
type: "registry:ui",
href: "/sink/chart",
},
checkbox: {
name: "Checkbox",
component: CheckboxDemo,
type: "registry:ui",
href: "/sink/checkbox",
},
collapsible: {
name: "Collapsible",
component: CollapsibleDemo,
type: "registry:ui",
href: "/sink/collapsible",
},
combobox: {
name: "Combobox",
component: ComboboxDemo,
type: "registry:ui",
href: "/sink/combobox",
},
command: {
name: "Command",
component: CommandDemo,
type: "registry:ui",
href: "/sink/command",
},
"context-menu": {
name: "Context Menu",
component: ContextMenuDemo,
type: "registry:ui",
href: "/sink/context-menu",
},
"date-picker": {
name: "Date Picker",
component: DatePickerDemo,
type: "registry:ui",
href: "/sink/date-picker",
},
dialog: {
name: "Dialog",
component: DialogDemo,
type: "registry:ui",
href: "/sink/dialog",
},
drawer: {
name: "Drawer",
component: DrawerDemo,
type: "registry:ui",
href: "/sink/drawer",
},
"dropdown-menu": {
name: "Dropdown Menu",
component: DropdownMenuDemo,
type: "registry:ui",
href: "/sink/dropdown-menu",
},
empty: {
name: "Empty",
component: EmptyDemo,
type: "registry:ui",
href: "/sink/empty",
label: "New",
},
field: {
name: "Field",
component: FieldDemo,
type: "registry:ui",
href: "/sink/field",
label: "New",
},
form: {
name: "Form",
component: FormDemo,
type: "registry:ui",
href: "/sink/form",
},
"hover-card": {
name: "Hover Card",
component: HoverCardDemo,
type: "registry:ui",
href: "/sink/hover-card",
},
input: {
name: "Input",
component: InputDemo,
type: "registry:ui",
href: "/sink/input",
},
"input-group": {
name: "Input Group",
component: InputGroupDemo,
type: "registry:ui",
href: "/sink/input-group",
label: "New",
},
"input-otp": {
name: "Input OTP",
component: InputOTPDemo,
type: "registry:ui",
href: "/sink/input-otp",
},
item: {
name: "Item",
component: ItemDemo,
type: "registry:ui",
href: "/sink/item",
label: "New",
},
kbd: {
name: "Kbd",
component: KbdDemo,
type: "registry:ui",
href: "/sink/kbd",
label: "New",
},
label: {
name: "Label",
component: LabelDemo,
type: "registry:ui",
href: "/sink/label",
},
menubar: {
name: "Menubar",
component: MenubarDemo,
type: "registry:ui",
href: "/sink/menubar",
},
"navigation-menu": {
name: "Navigation Menu",
component: NavigationMenuDemo,
type: "registry:ui",
href: "/sink/navigation-menu",
},
"native-select": {
name: "Native Select",
component: NativeSelectDemo,
type: "registry:ui",
href: "/sink/native-select",
label: "New",
},
pagination: {
name: "Pagination",
component: PaginationDemo,
type: "registry:ui",
href: "/sink/pagination",
},
popover: {
name: "Popover",
component: PopoverDemo,
type: "registry:ui",
href: "/sink/popover",
},
progress: {
name: "Progress",
component: ProgressDemo,
type: "registry:ui",
href: "/sink/progress",
},
"radio-group": {
name: "Radio Group",
component: RadioGroupDemo,
type: "registry:ui",
href: "/sink/radio-group",
},
resizable: {
name: "Resizable",
component: ResizableDemo,
type: "registry:ui",
href: "/sink/resizable",
},
"scroll-area": {
name: "Scroll Area",
component: ScrollAreaDemo,
type: "registry:ui",
href: "/sink/scroll-area",
},
select: {
name: "Select",
component: SelectDemo,
type: "registry:ui",
href: "/sink/select",
},
separator: {
name: "Separator",
component: SeparatorDemo,
type: "registry:ui",
href: "/sink/separator",
},
sheet: {
name: "Sheet",
component: SheetDemo,
type: "registry:ui",
href: "/sink/sheet",
},
skeleton: {
name: "Skeleton",
component: SkeletonDemo,
type: "registry:ui",
href: "/sink/skeleton",
},
slider: {
name: "Slider",
component: SliderDemo,
type: "registry:ui",
href: "/sink/slider",
},
sonner: {
name: "Sonner",
component: SonnerDemo,
type: "registry:ui",
href: "/sink/sonner",
},
spinner: {
name: "Spinner",
component: SpinnerDemo,
type: "registry:ui",
href: "/sink/spinner",
label: "New",
},
switch: {
name: "Switch",
component: SwitchDemo,
type: "registry:ui",
href: "/sink/switch",
},
table: {
name: "Table",
component: TableDemo,
type: "registry:ui",
href: "/sink/table",
},
tabs: {
name: "Tabs",
component: TabsDemo,
type: "registry:ui",
href: "/sink/tabs",
},
textarea: {
name: "Textarea",
component: TextareaDemo,
type: "registry:ui",
href: "/sink/textarea",
},
toggle: {
name: "Toggle",
component: ToggleDemo,
type: "registry:ui",
href: "/sink/toggle",
},
"toggle-group": {
name: "Toggle Group",
component: ToggleGroupDemo,
type: "registry:ui",
href: "/sink/toggle-group",
},
tooltip: {
name: "Tooltip",
component: TooltipDemo,
type: "registry:ui",
href: "/sink/tooltip",
},
blocks: {
name: "Forms",
component: FormsPage,
type: "registry:page",
href: "/sink/forms",
},
"next-form": {
name: "Next.js Form",
component: NextFormPage,
type: "registry:page",
href: "/sink/next-form",
},
"tanstack-form": {
name: "Tanstack Form",
component: TanstackFormPage,
type: "registry:page",
href: "/sink/tanstack-form",
},
"react-hook-form": {
name: "React Hook Form",
component: ReactHookFormPage,
type: "registry:page",
href: "/sink/react-hook-form",
},
}
export type ComponentKey = keyof typeof componentRegistry

View File

@@ -1,72 +0,0 @@
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/registry/new-york-v4/ui/accordion"
export function AccordionDemo() {
return (
<div className="grid w-full max-w-xl gap-4">
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="item-1">
<AccordionTrigger>Is it accessible?</AccordionTrigger>
<AccordionContent>
Yes. It adheres to the WAI-ARIA design pattern.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>Is it styled?</AccordionTrigger>
<AccordionContent>
Yes. It comes with default styles that matches the other
components&apos; aesthetic.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger>Is it animated?</AccordionTrigger>
<AccordionContent>
Yes. It&apos;s animated by default, but you can disable it if you
prefer.
</AccordionContent>
</AccordionItem>
</Accordion>
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="item-1">
<AccordionTrigger>
What are the key considerations when implementing a comprehensive
enterprise-level authentication system?
</AccordionTrigger>
<AccordionContent>
Implementing a robust enterprise authentication system requires
careful consideration of multiple factors. This includes secure
password hashing and storage, multi-factor authentication (MFA)
implementation, session management, OAuth2 and SSO integration,
regular security audits, rate limiting to prevent brute force
attacks, and maintaining detailed audit logs. Additionally,
you&apos;ll need to consider scalability, performance impact, and
compliance with relevant data protection regulations such as GDPR or
HIPAA.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>
How does modern distributed system architecture handle eventual
consistency and data synchronization across multiple regions?
</AccordionTrigger>
<AccordionContent>
Modern distributed systems employ various strategies to maintain
data consistency across regions. This often involves using
techniques like CRDT (Conflict-Free Replicated Data Types), vector
clocks, and gossip protocols. Systems might implement event sourcing
patterns, utilize message queues for asynchronous updates, and
employ sophisticated conflict resolution strategies. Popular
solutions like Amazon&apos;s DynamoDB and Google&apos;s Spanner
demonstrate different approaches to solving these challenges,
balancing between consistency, availability, and partition tolerance
as described in the CAP theorem.
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
)
}

View File

@@ -1,115 +0,0 @@
import {
AlertCircleIcon,
BookmarkCheckIcon,
CheckCircle2Icon,
GiftIcon,
PopcornIcon,
ShieldAlertIcon,
} from "lucide-react"
import {
Alert,
AlertDescription,
AlertTitle,
} from "@/registry/new-york-v4/ui/alert"
import { Button } from "@/registry/new-york-v4/ui/button"
export function AlertDemo() {
return (
<div className="grid max-w-xl items-start gap-4">
<Alert>
<CheckCircle2Icon />
<AlertTitle>Success! Your changes have been saved</AlertTitle>
<AlertDescription>
This is an alert with icon, title and description.
</AlertDescription>
</Alert>
<Alert>
<BookmarkCheckIcon>Heads up!</BookmarkCheckIcon>
<AlertDescription>
This one has an icon and a description only. No title.
</AlertDescription>
</Alert>
<Alert>
<AlertDescription>
This one has a description only. No title. No icon.
</AlertDescription>
</Alert>
<Alert>
<PopcornIcon />
<AlertTitle>Let&apos;s try one with icon and title.</AlertTitle>
</Alert>
<Alert>
<ShieldAlertIcon />
<AlertTitle>
This is a very long alert title that demonstrates how the component
handles extended text content and potentially wraps across multiple
lines
</AlertTitle>
</Alert>
<Alert>
<GiftIcon />
<AlertDescription>
This is a very long alert description that demonstrates how the
component handles extended text content and potentially wraps across
multiple lines
</AlertDescription>
</Alert>
<Alert>
<AlertCircleIcon />
<AlertTitle>
This is an extremely long alert title that spans multiple lines to
demonstrate how the component handles very lengthy headings while
maintaining readability and proper text wrapping behavior
</AlertTitle>
<AlertDescription>
This is an equally long description that contains detailed information
about the alert. It shows how the component can accommodate extensive
content while preserving proper spacing, alignment, and readability
across different screen sizes and viewport widths. This helps ensure
the user experience remains consistent regardless of the content
length.
</AlertDescription>
</Alert>
<Alert variant="destructive">
<AlertCircleIcon />
<AlertTitle>Something went wrong!</AlertTitle>
<AlertDescription>
Your session has expired. Please log in again.
</AlertDescription>
</Alert>
<Alert variant="destructive">
<AlertCircleIcon />
<AlertTitle>Unable to process your payment.</AlertTitle>
<AlertDescription>
<p>Please verify your billing information and try again.</p>
<ul className="list-inside list-disc text-sm">
<li>Check your card details</li>
<li>Ensure sufficient funds</li>
<li>Verify billing address</li>
</ul>
</AlertDescription>
</Alert>
<Alert>
<CheckCircle2Icon />
<AlertTitle className="max-w-[calc(100%-4rem)] overflow-ellipsis">
The selected emails have been marked as spam.
</AlertTitle>
<Button
size="sm"
variant="outline"
className="absolute top-2.5 right-3 h-6 shadow-none"
>
Undo
</Button>
</Alert>
<Alert className="border-amber-50 bg-amber-50 text-amber-900 dark:border-amber-950 dark:bg-amber-950 dark:text-amber-100">
<CheckCircle2Icon />
<AlertTitle>Plot Twist: This Alert is Actually Amber!</AlertTitle>
<AlertDescription>
This one has custom colors for light and dark mode.
</AlertDescription>
</Alert>
</div>
)
}

View File

@@ -1,81 +0,0 @@
import { TrashIcon } from "lucide-react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/registry/new-york-v4/ui/alert-dialog"
import { Button } from "@/registry/new-york-v4/ui/button"
export function AlertDialogDemo() {
return (
<div className="flex flex-wrap gap-4">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">Default</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">With Media</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogMedia>
<TrashIcon className="size-8" />
</AlertDialogMedia>
<AlertDialogTitle>Delete this item?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the
item from your account.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive">Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">Small Size</Button>
</AlertDialogTrigger>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogMedia>
<TrashIcon className="size-8" />
</AlertDialogMedia>
<AlertDialogTitle>Delete this item?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive">Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
)
}

View File

@@ -1,43 +0,0 @@
"use client"
import { useParams } from "next/navigation"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/registry/new-york-v4/ui/breadcrumb"
export function AppBreadcrumbs() {
const params = useParams()
const { name } = params
if (!name) {
return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbPage>Kitchen Sink</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
)
}
return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/sink">Kitchen Sink</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="hidden sm:flex" />
<BreadcrumbItem className="hidden sm:block">
<BreadcrumbPage className="capitalize">{name}</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
)
}

View File

@@ -1,297 +0,0 @@
"use client"
import * as React from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import {
AudioWaveform,
BookOpen,
Bot,
ChevronRightIcon,
Command,
GalleryVerticalEnd,
SearchIcon,
Settings2,
SquareTerminal,
} from "lucide-react"
import { Index } from "@/registry/__index__"
import { NavUser } from "@/registry/new-york-v4/blocks/sidebar-07/components/nav-user"
import { TeamSwitcher } from "@/registry/new-york-v4/blocks/sidebar-07/components/team-switcher"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/registry/new-york-v4/ui/collapsible"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/new-york-v4/ui/input-group"
import { Label } from "@/registry/new-york-v4/ui/label"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarRail,
} from "@/registry/new-york-v4/ui/sidebar"
import { componentRegistry } from "@/app/(internal)/sink/component-registry"
// This is sample data.
const data = {
user: {
name: "shadcn",
email: "m@example.com",
avatar: "/avatars/shadcn.jpg",
},
teams: [
{
name: "Acme Inc",
logo: GalleryVerticalEnd,
plan: "Enterprise",
},
{
name: "Acme Corp.",
logo: AudioWaveform,
plan: "Startup",
},
{
name: "Evil Corp.",
logo: Command,
plan: "Free",
},
],
navMain: [
{
title: "Playground",
url: "#",
icon: SquareTerminal,
isActive: true,
items: [
{
title: "History",
url: "#",
},
{
title: "Starred",
url: "#",
},
{
title: "Settings",
url: "#",
},
],
},
{
title: "Models",
url: "#",
icon: Bot,
items: [
{
title: "Genesis",
url: "#",
},
{
title: "Explorer",
url: "#",
},
{
title: "Quantum",
url: "#",
},
],
},
{
title: "Documentation",
url: "#",
icon: BookOpen,
items: [
{
title: "Introduction",
url: "#",
},
{
title: "Get Started",
url: "#",
},
{
title: "Tutorials",
url: "#",
},
{
title: "Changelog",
url: "#",
},
],
},
{
title: "Settings",
url: "#",
icon: Settings2,
items: [
{
title: "General",
url: "#",
},
{
title: "Team",
url: "#",
},
{
title: "Billing",
url: "#",
},
{
title: "Limits",
url: "#",
},
],
},
],
components: Object.values(Index)
.filter((item) => item.type === "registry:ui")
.concat([
{
name: "combobox",
},
])
.sort((a, b) => a.name.localeCompare(b.name)),
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const pathname = usePathname()
return (
<Sidebar side="left" collapsible="icon" {...props}>
<SidebarHeader>
<TeamSwitcher teams={data.teams} />
<SidebarGroup className="py-0 group-data-[collapsible=icon]:hidden">
<SidebarGroupContent>
<form className="relative">
<Label htmlFor="search" className="sr-only">
Search
</Label>
<InputGroup className="h-8 bg-background shadow-none">
<InputGroupInput
id="search"
placeholder="Search the docs..."
className="h-7"
data-slot="input-group-control"
/>
<InputGroupAddon>
<SearchIcon className="text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
</form>
</SidebarGroupContent>
</SidebarGroup>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
{data.navMain.map((item) => (
<Collapsible
key={item.title}
asChild
defaultOpen={item.isActive}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}>
{item.icon && <item.icon />}
<span>{item.title}</span>
<ChevronRightIcon className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
</SidebarMenuButton>
</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>
</SidebarMenuItem>
</Collapsible>
))}
</SidebarMenu>
</SidebarGroup>
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarMenu>
{["registry:ui", "registry:page", "registry:block"].map((type) => {
const typeComponents = Object.entries(componentRegistry).filter(
([, item]) => item.type === type
)
if (typeComponents.length === 0) {
return null
}
return (
<Collapsible
key={type}
asChild
defaultOpen={pathname.includes("/sink/")}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton>
<span>
{type === "registry:ui"
? "Components"
: type === "registry:page"
? "Pages"
: "Blocks"}
</span>
<ChevronRightIcon className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{typeComponents.map(([key, item]) => (
<SidebarMenuSubItem key={key}>
<SidebarMenuSubButton
asChild
isActive={pathname === item.href}
>
<Link href={item.href}>
<span>{item.name}</span>
{item.label && (
<span className="flex size-2 rounded-full bg-blue-500" />
)}
</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
)
})}
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<NavUser user={data.user} />
</SidebarFooter>
<SidebarRail />
</Sidebar>
)
}

View File

@@ -1,26 +0,0 @@
import Image from "next/image"
import { AspectRatio } from "@/registry/new-york-v4/ui/aspect-ratio"
export function AspectRatioDemo() {
return (
<div className="grid w-full max-w-sm items-start gap-4">
<AspectRatio ratio={16 / 9} className="rounded-lg bg-muted">
<Image
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
alt="Photo by Drew Beamer"
fill
className="h-full w-full rounded-lg object-cover dark:brightness-[0.2] dark:grayscale"
/>
</AspectRatio>
<AspectRatio ratio={1 / 1} className="rounded-lg bg-muted">
<Image
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
alt="Photo by Drew Beamer"
fill
className="h-full w-full rounded-lg object-cover dark:brightness-[0.2] dark:grayscale"
/>
</AspectRatio>
</div>
)
}

View File

@@ -1,174 +0,0 @@
import { PlusIcon } from "lucide-react"
import {
Avatar,
AvatarBadge,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
export function AvatarDemo() {
return (
<div className="flex flex-col gap-6">
{/* Sizes. */}
<div className="flex flex-row flex-wrap items-center gap-4">
<Avatar size="sm">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</div>
{/* Fallback. */}
<div className="flex flex-row flex-wrap items-center gap-4">
<Avatar size="sm">
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</div>
{/* With badge. */}
<div className="flex flex-row flex-wrap items-center gap-4">
<Avatar size="sm">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
<AvatarBadge />
</Avatar>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
<AvatarBadge />
</Avatar>
<Avatar size="lg">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
<AvatarBadge>
<PlusIcon />
</AvatarBadge>
</Avatar>
</div>
{/* Avatar group. */}
<div className="flex flex-row flex-wrap items-center gap-4">
<AvatarGroup>
<Avatar size="sm">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="sm">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>ML</AvatarFallback>
</Avatar>
<Avatar size="sm">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>ML</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
<AvatarGroup>
<Avatar size="lg">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>ML</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
</div>
{/* Avatar group with count. */}
<div className="flex flex-row flex-wrap items-center gap-4">
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>ML</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<AvatarGroupCount>+3</AvatarGroupCount>
</AvatarGroup>
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>ML</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<AvatarGroupCount>
<PlusIcon />
</AvatarGroupCount>
</AvatarGroup>
</div>
</div>
)
}

View File

@@ -1,75 +0,0 @@
import { AlertCircleIcon, ArrowRightIcon, CheckIcon } from "lucide-react"
import { Badge } from "@/registry/new-york-v4/ui/badge"
export function BadgeDemo() {
return (
<div className="flex flex-col items-center gap-2">
<div className="flex w-full flex-wrap gap-2">
<Badge>Badge</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>
<Badge variant="ghost">Ghost</Badge>
<Badge variant="link">Link</Badge>
</div>
<div className="flex w-full flex-wrap gap-2">
<Badge variant="outline">
<CheckIcon />
Badge
</Badge>
<Badge variant="destructive">
<AlertCircleIcon />
Alert
</Badge>
<Badge className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums">
8
</Badge>
<Badge
className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
variant="destructive"
>
99
</Badge>
<Badge
className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
variant="outline"
>
20+
</Badge>
</div>
<div className="flex w-full flex-wrap gap-2">
<Badge asChild>
<a href="#">
Link <ArrowRightIcon />
</a>
</Badge>
<Badge asChild variant="secondary">
<a href="#">
Link <ArrowRightIcon />
</a>
</Badge>
<Badge asChild variant="destructive">
<a href="#">
Link <ArrowRightIcon />
</a>
</Badge>
<Badge asChild variant="outline">
<a href="#">
Link <ArrowRightIcon />
</a>
</Badge>
<Badge asChild variant="ghost">
<a href="#">
Link <ArrowRightIcon />
</a>
</Badge>
<Badge asChild variant="link">
<a href="#">
Link <ArrowRightIcon />
</a>
</Badge>
</div>
</div>
)
}

View File

@@ -1,49 +0,0 @@
import {
Breadcrumb,
BreadcrumbEllipsis,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/registry/new-york-v4/ui/breadcrumb"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
export function BreadcrumbDemo() {
return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1">
<BreadcrumbEllipsis className="h-4 w-4" />
<span className="sr-only">Toggle menu</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>Documentation</DropdownMenuItem>
<DropdownMenuItem>Themes</DropdownMenuItem>
<DropdownMenuItem>GitHub</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="/docs/components">Components</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
)
}

View File

@@ -1,105 +0,0 @@
import { ArrowRightIcon, Loader2Icon, PlusIcon, SendIcon } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
export function ButtonDemo() {
return (
<div className="flex flex-col gap-6">
<div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button size="xs">Extra Small</Button>
<Button variant="outline" size="xs">
Outline
</Button>
<Button variant="ghost" size="xs">
Ghost
</Button>
<Button variant="destructive" size="xs">
Destructive
</Button>
<Button variant="secondary" size="xs">
Secondary
</Button>
<Button variant="link" size="xs">
Link
</Button>
<Button variant="outline" size="xs">
<SendIcon /> Send
</Button>
</div>
<div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button size="sm">Small</Button>
<Button variant="outline" size="sm">
Outline
</Button>
<Button variant="ghost" size="sm">
Ghost
</Button>
<Button variant="destructive" size="sm">
Destructive
</Button>
<Button variant="secondary" size="sm">
Secondary
</Button>
<Button variant="link" size="sm">
Link
</Button>
<Button variant="outline" size="sm">
<SendIcon /> Send
</Button>
</div>
<div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button>Button</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="link">Link</Button>
<Button variant="outline">
<SendIcon /> Send
</Button>
<Button variant="outline">
Learn More <ArrowRightIcon />
</Button>
<Button disabled variant="outline">
<Loader2Icon className="animate-spin" />
Please wait
</Button>
</div>
<div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button size="lg">Large</Button>
<Button variant="outline" size="lg">
Outline
</Button>
<Button variant="ghost" size="lg">
Ghost
</Button>
<Button variant="destructive" size="lg">
Destructive
</Button>
<Button variant="secondary" size="lg">
Secondary
</Button>
<Button variant="link" size="lg">
Link
</Button>
<Button variant="outline" size="lg">
<SendIcon /> Send
</Button>
</div>
<div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button size="icon-xs" variant="outline">
<PlusIcon />
</Button>
<Button size="icon-sm" variant="outline">
<PlusIcon />
</Button>
<Button size="icon" variant="outline">
<PlusIcon />
</Button>
<Button size="icon-lg" variant="outline">
<PlusIcon />
</Button>
</div>
</div>
)
}

View File

@@ -1,581 +0,0 @@
"use client"
import { useState } from "react"
import {
IconArrowRight,
IconBrandGithubCopilot,
IconChevronDown,
IconCircleCheck,
IconCloudCode,
IconHeart,
IconMinus,
IconPin,
IconPlus,
IconUserCircle,
} from "@tabler/icons-react"
import {
AlertTriangleIcon,
ArrowLeftIcon,
ArrowRightIcon,
AudioLinesIcon,
CheckIcon,
ChevronDownIcon,
CopyIcon,
FlipHorizontalIcon,
FlipVerticalIcon,
MoreHorizontalIcon,
PercentIcon,
RotateCwIcon,
SearchIcon,
ShareIcon,
TrashIcon,
UserRoundXIcon,
VolumeOffIcon,
} from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
} from "@/registry/new-york-v4/ui/button-group"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
import { Field, FieldGroup } from "@/registry/new-york-v4/ui/field"
import { Input } from "@/registry/new-york-v4/ui/input"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/registry/new-york-v4/ui/input-group"
import { Label } from "@/registry/new-york-v4/ui/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import { Separator } from "@/registry/new-york-v4/ui/separator"
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york-v4/ui/tooltip"
export function ButtonGroupDemo() {
const [currency, setCurrency] = useState("$")
return (
<div className="flex gap-12">
<div className="flex max-w-sm flex-col gap-6">
<ButtonGroup>
<Button>Button</Button>
<Button>
Get Started <IconArrowRight />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button>Button</Button>
<ButtonGroupSeparator className="bg-primary/80" />
<Button>
Get Started <IconArrowRight />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Button</Button>
<Input placeholder="Type something here..." />
</ButtonGroup>
<ButtonGroup>
<Input placeholder="Type something here..." />
<Button variant="outline">Button</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Button</Button>
<Button variant="outline">Another Button</Button>
</ButtonGroup>
<ButtonGroup>
<ButtonGroupText>Text</ButtonGroupText>
<Button variant="outline">Another Button</Button>
</ButtonGroup>
<ButtonGroup>
<ButtonGroupText asChild>
<Label htmlFor="input">
<IconCloudCode /> GPU Size
</Label>
</ButtonGroupText>
<Input id="input" placeholder="Type something here..." />
</ButtonGroup>
<ButtonGroup>
<ButtonGroupText>Prefix</ButtonGroupText>
<Input id="input" placeholder="Type something here..." />
<ButtonGroupText>Suffix</ButtonGroupText>
</ButtonGroup>
<div className="flex gap-4">
<ButtonGroup>
<Button variant="outline">Update</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
<ChevronDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Disable</DropdownMenuItem>
<DropdownMenuItem variant="destructive">
Uninstall
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
<ButtonGroup className="[--radius:9999px]">
<Button variant="outline">Follow</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="pl-2!">
<ChevronDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="[--radius:0.95rem]">
<DropdownMenuGroup>
<DropdownMenuItem>
<VolumeOffIcon />
Mute Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<CheckIcon />
Mark as Read
</DropdownMenuItem>
<DropdownMenuItem>
<AlertTriangleIcon />
Report Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<UserRoundXIcon />
Block User
</DropdownMenuItem>
<DropdownMenuItem>
<ShareIcon />
Share Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<CopyIcon />
Copy Conversation
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<TrashIcon />
Delete Conversation
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
<ButtonGroup className="[--radius:0.9rem]">
<Button variant="secondary">Actions</Button>
<ButtonGroupSeparator />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary">
<MoreHorizontalIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="[--radius:0.9rem]">
<DropdownMenuGroup>
<DropdownMenuItem>
<IconCircleCheck />
Select Messages
</DropdownMenuItem>
<DropdownMenuItem>
<IconPin />
Edit Pins
</DropdownMenuItem>
<DropdownMenuItem>
<IconUserCircle />
Set Up Name & Photo
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</div>
<Field>
<Label htmlFor="amount">Amount</Label>
<ButtonGroup>
<Select value={currency} onValueChange={setCurrency}>
<SelectTrigger className="font-mono">{currency}</SelectTrigger>
<SelectContent>
<SelectItem value="$">$</SelectItem>
<SelectItem value="€"></SelectItem>
<SelectItem value="£">£</SelectItem>
</SelectContent>
</Select>
<Input placeholder="Enter amount to send" />
<Button variant="outline">
<ArrowRightIcon />
</Button>
</ButtonGroup>
</Field>
</div>
<div className="flex max-w-xs flex-col gap-6">
<ButtonGroup className="[--spacing:0.2rem]">
<Button variant="outline">
<FlipHorizontalIcon />
</Button>
<Button variant="outline">
<FlipVerticalIcon />
</Button>
<Button variant="outline">
<RotateCwIcon />
</Button>
<InputGroup>
<InputGroupInput placeholder="0.00" />
<InputGroupAddon
align="inline-end"
className="text-muted-foreground"
>
<PercentIcon />
</InputGroupAddon>
</InputGroup>
</ButtonGroup>
<div className="flex gap-2 [--radius:0.95rem] [--ring:var(--color-blue-300)] [--spacing:0.22rem] **:[.shadow-xs]:shadow-none">
<InputGroup>
<InputGroupInput placeholder="Type to search..." />
<InputGroupAddon
align="inline-start"
className="text-muted-foreground"
>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
<ButtonGroup>
<Button variant="outline">
<IconBrandGithubCopilot />
</Button>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">
<IconCloudCode />
<IconChevronDown />
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="rounded-xl p-0 text-sm">
<div className="px-4 py-3">
<div className="text-sm font-medium">Agent Tasks</div>
</div>
<Separator />
<div className="p-4 *:[p:not(:last-child)]:mb-2">
<Textarea
placeholder="Describe your task in natural language."
className="mb-4 resize-none"
/>
<p className="font-medium">Start a new task with Copilot</p>
<p className="text-muted-foreground">
Describe your task in natural language. Copilot will work in
the background and open a pull request for your review.
</p>
</div>
</PopoverContent>
</Popover>
</ButtonGroup>
</div>
<FieldGroup className="grid grid-cols-2 gap-4 [--spacing:0.22rem]">
<Field>
<Label htmlFor="width">Width</Label>
<ButtonGroup>
<InputGroup>
<InputGroupInput id="width" />
<InputGroupAddon className="text-muted-foreground">
W
</InputGroupAddon>
<InputGroupAddon
align="inline-end"
className="text-muted-foreground"
>
px
</InputGroupAddon>
</InputGroup>
<Button variant="outline" size="icon">
<IconMinus />
</Button>
<Button variant="outline" size="icon">
<IconPlus />
</Button>
</ButtonGroup>
</Field>
<Field className="w-full">
<Label htmlFor="color">Color</Label>
<ButtonGroup className="w-full">
<InputGroup>
<InputGroupInput id="color" />
<InputGroupAddon align="inline-start">
<Popover>
<PopoverTrigger asChild>
<InputGroupButton>
<span className="size-4 rounded-xs bg-blue-600" />
</InputGroupButton>
</PopoverTrigger>
<PopoverContent
align="start"
className="max-w-48 rounded-lg p-2"
alignOffset={-8}
sideOffset={8}
>
<div className="flex flex-wrap gap-1.5">
{[
"#EA4335", // Red
"#FBBC04", // Yellow
"#34A853", // Green
"#4285F4", // Blue
"#9333EA", // Purple
"#EC4899", // Pink
"#10B981", // Emerald
"#F97316", // Orange
"#6366F1", // Indigo
"#14B8A6", // Teal
"#8B5CF6", // Violet
"#F59E0B", // Amber
].map((color) => (
<div
key={color}
className="size-6 cursor-pointer rounded-sm transition-transform hover:scale-110"
style={{ backgroundColor: color }}
/>
))}
</div>
</PopoverContent>
</Popover>
</InputGroupAddon>
<InputGroupAddon
align="inline-end"
className="text-muted-foreground"
>
%
</InputGroupAddon>
</InputGroup>
</ButtonGroup>
</Field>
</FieldGroup>
<ButtonGroup>
<Button variant="outline">
<IconHeart /> Like
</Button>
<Button
variant="outline"
asChild
className="pointer-events-none px-2 text-muted-foreground"
>
<span>1.2K</span>
</Button>
</ButtonGroup>
<ExportButtonGroup />
<ButtonGroup>
<Select defaultValue="hours">
<SelectTrigger id="duration">
<SelectValue placeholder="Select duration" />
</SelectTrigger>
<SelectContent>
<SelectItem value="hours">Hours</SelectItem>
<SelectItem value="days">Days</SelectItem>
<SelectItem value="weeks">Weeks</SelectItem>
</SelectContent>
</Select>
<Input />
</ButtonGroup>
<ButtonGroup className="[--radius:9999rem]">
<ButtonGroup>
<Button variant="outline" size="icon">
<IconPlus />
</Button>
</ButtonGroup>
<ButtonGroup>
<InputGroup>
<InputGroupInput placeholder="Send a message..." />
<Tooltip>
<TooltipTrigger asChild>
<InputGroupAddon align="inline-end">
<AudioLinesIcon />
</InputGroupAddon>
</TooltipTrigger>
<TooltipContent>Voice Mode</TooltipContent>
</Tooltip>
</InputGroup>
</ButtonGroup>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="sm">
<ArrowLeftIcon />
Previous
</Button>
<Button variant="outline" size="sm">
1
</Button>
<Button variant="outline" size="sm">
2
</Button>
<Button variant="outline" size="sm">
3
</Button>
<Button variant="outline" size="sm">
4
</Button>
<Button variant="outline" size="sm">
5
</Button>
<Button variant="outline" size="sm">
Next
<ArrowRightIcon />
</Button>
</ButtonGroup>
<ButtonGroup className="[--radius:0.9rem] [--spacing:0.22rem]">
<ButtonGroup>
<Button variant="outline">1</Button>
<Button variant="outline">2</Button>
<Button variant="outline">3</Button>
<Button variant="outline">4</Button>
<Button variant="outline">5</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="icon">
<ArrowLeftIcon />
</Button>
<Button variant="outline" size="icon">
<ArrowRightIcon />
</Button>
</ButtonGroup>
</ButtonGroup>
<ButtonGroup>
<ButtonGroup>
<Button variant="outline">
<ArrowLeftIcon />
</Button>
<Button variant="outline">
<ArrowRightIcon />
</Button>
</ButtonGroup>
<ButtonGroup aria-label="Single navigation button">
<Button variant="outline" size="icon">
<ArrowLeftIcon />
</Button>
</ButtonGroup>
</ButtonGroup>
</div>
<div className="flex max-w-xs flex-col gap-6">
<Field>
<Label id="alignment-label">Text Alignment</Label>
<ButtonGroup aria-labelledby="alignment-label">
<Button variant="outline" size="sm">
Left
</Button>
<Button variant="outline" size="sm">
Center
</Button>
<Button variant="outline" size="sm">
Right
</Button>
<Button variant="outline" size="sm">
Justify
</Button>
</ButtonGroup>
</Field>
<div className="flex gap-6">
<ButtonGroup
orientation="vertical"
aria-label="Media controls"
className="h-fit"
>
<Button variant="outline" size="icon">
<IconPlus />
</Button>
<Button variant="outline" size="icon">
<IconMinus />
</Button>
</ButtonGroup>
<ButtonGroup orientation="vertical" aria-label="Design tools palette">
<ButtonGroup orientation="vertical">
<Button variant="outline" size="icon">
<SearchIcon />
</Button>
<Button variant="outline" size="icon">
<CopyIcon />
</Button>
<Button variant="outline" size="icon">
<ShareIcon />
</Button>
</ButtonGroup>
<ButtonGroup orientation="vertical">
<Button variant="outline" size="icon">
<FlipHorizontalIcon />
</Button>
<Button variant="outline" size="icon">
<FlipVerticalIcon />
</Button>
<Button variant="outline" size="icon">
<RotateCwIcon />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="icon">
<TrashIcon />
</Button>
</ButtonGroup>
</ButtonGroup>
<ButtonGroup orientation="vertical">
<Button variant="outline" size="sm">
<IconPlus /> Increase
</Button>
<Button variant="outline" size="sm">
<IconMinus /> Decrease
</Button>
</ButtonGroup>
<ButtonGroup orientation="vertical">
<Button variant="secondary" size="sm">
<IconPlus /> Increase
</Button>
<ButtonGroupSeparator orientation="horizontal" />
<Button variant="secondary" size="sm">
<IconMinus /> Decrease
</Button>
</ButtonGroup>
</div>
</div>
</div>
)
}
function ExportButtonGroup() {
const [exportType, setExportType] = useState("pdf")
return (
<ButtonGroup>
<Input />
<Select value={exportType} onValueChange={setExportType}>
<SelectTrigger>
<SelectValue asChild>
<span>{exportType}</span>
</SelectValue>
</SelectTrigger>
<SelectContent align="end">
<SelectItem value="pdf">pdf</SelectItem>
<SelectItem value="xlsx">xlsx</SelectItem>
<SelectItem value="csv">csv</SelectItem>
<SelectItem value="json">json</SelectItem>
</SelectContent>
</Select>
</ButtonGroup>
)
}

View File

@@ -1,276 +0,0 @@
"use client"
import * as React from "react"
import { addDays } from "date-fns"
import { Clock2Icon } from "lucide-react"
import { type DateRange } from "react-day-picker"
import { es } from "react-day-picker/locale"
import { Button } from "@/registry/new-york-v4/ui/button"
import { Calendar, CalendarDayButton } from "@/registry/new-york-v4/ui/calendar"
import { Card, CardContent, CardFooter } from "@/registry/new-york-v4/ui/card"
import { Input } from "@/registry/new-york-v4/ui/input"
import { Label } from "@/registry/new-york-v4/ui/label"
export function CalendarDemo() {
return (
<div className="flex flex-1 flex-col flex-wrap justify-center gap-8 bg-muted p-10 lg:flex-row">
<CalendarSingle />
<CalendarMultiple />
<CalendarRange />
<CalendarBookedDates />
<CalendarRangeMultipleMonths />
<CalendarWithTime />
<CalendarWithPresets />
<CalendarCustomDays />
</div>
)
}
function CalendarSingle() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(new Date().getFullYear(), new Date().getMonth(), 12)
)
return (
<div className="flex flex-col gap-3">
<div className="px-2 text-center text-sm">Single Selection</div>
<Calendar
mode="single"
selected={date}
onSelect={setDate}
className="rounded-lg border shadow-sm"
captionLayout="dropdown"
/>
</div>
)
}
function CalendarMultiple() {
return (
<div className="flex flex-col gap-3">
<div className="px-2 text-center text-sm">Multiple Selection</div>
<Calendar mode="multiple" className="rounded-lg border shadow-sm" />
</div>
)
}
function CalendarRange() {
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
from: new Date(new Date().getFullYear(), 0, 12),
to: addDays(new Date(new Date().getFullYear(), 0, 12), 30),
})
return (
<div className="flex flex-col gap-3">
<div className="px-2 text-center text-sm">Range Selection</div>
<Calendar
mode="range"
defaultMonth={dateRange?.from}
selected={dateRange}
onSelect={setDateRange}
numberOfMonths={2}
disabled={(date) => date > new Date() || date < new Date("1900-01-01")}
className="rounded-lg border shadow-sm"
/>
</div>
)
}
function CalendarRangeMultipleMonths() {
const [range, setRange] = React.useState<DateRange | undefined>({
from: new Date(new Date().getFullYear(), 3, 12),
to: addDays(new Date(new Date().getFullYear(), 3, 12), 60),
})
return (
<div className="flex flex-col gap-3">
<div className="px-2 text-center text-sm">Range Selection + Locale</div>
<Calendar
mode="range"
defaultMonth={range?.from}
selected={range}
onSelect={setRange}
numberOfMonths={3}
locale={es}
fixedWeeks
className="rounded-lg border shadow-sm"
/>
</div>
)
}
function CalendarBookedDates() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(new Date().getFullYear(), 1, 3)
)
const bookedDates = Array.from(
{ length: 15 },
(_, i) => new Date(new Date().getFullYear(), new Date().getMonth(), 12 + i)
)
return (
<div className="flex flex-col gap-3">
<div className="px-2 text-center text-sm">With booked dates</div>
<Calendar
mode="single"
defaultMonth={date}
selected={date}
onSelect={setDate}
disabled={bookedDates}
modifiers={{
booked: bookedDates,
}}
modifiersClassNames={{
booked: "[&>button]:line-through opacity-100",
}}
className="rounded-lg border shadow-sm"
/>
</div>
)
}
function CalendarWithTime() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(new Date().getFullYear(), new Date().getMonth(), 12)
)
return (
<div className="flex flex-col gap-3">
<div className="px-2 text-center text-sm">With Time Input</div>
<Card className="w-fit py-4">
<CardContent className="px-4">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
className="p-0"
/>
</CardContent>
<CardFooter className="flex flex-col gap-3 border-t px-4 pt-4">
<div className="flex w-full flex-col gap-2">
<Label htmlFor="time-from">Start Time</Label>
<div className="relative flex w-full items-center gap-2">
<Clock2Icon className="pointer-events-none absolute left-2.5 size-4 text-muted-foreground select-none" />
<Input
id="time-from"
type="time"
step="1"
defaultValue="10:30:00"
className="appearance-none pl-8 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
/>
</div>
</div>
<div className="flex w-full flex-col gap-2">
<Label htmlFor="time-to">End Time</Label>
<div className="relative flex w-full items-center gap-2">
<Clock2Icon className="pointer-events-none absolute left-2.5 size-4 text-muted-foreground select-none" />
<Input
id="time-to"
type="time"
step="1"
defaultValue="12:30:00"
className="appearance-none pl-8 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
/>
</div>
</div>
</CardFooter>
</Card>
</div>
)
}
function CalendarCustomDays() {
const [range, setRange] = React.useState<DateRange | undefined>({
from: new Date(new Date().getFullYear(), 11, 8),
to: addDays(new Date(new Date().getFullYear(), 11, 8), 10),
})
return (
<div className="flex flex-col gap-3">
<div className="px-2 text-center text-sm">
With Custom Days and Formatters
</div>
<Calendar
mode="range"
defaultMonth={range?.from}
selected={range}
onSelect={setRange}
numberOfMonths={1}
captionLayout="dropdown"
className="rounded-lg border shadow-sm [--cell-size:--spacing(12)]"
formatters={{
formatMonthDropdown: (date) => {
return date.toLocaleString("default", { month: "long" })
},
}}
components={{
DayButton: ({ children, modifiers, day, ...props }) => {
const isWeekend = day.date.getDay() === 0 || day.date.getDay() === 6
return (
<CalendarDayButton day={day} modifiers={modifiers} {...props}>
{children}
{!modifiers.outside && (
<span>{isWeekend ? "$120" : "$100"}</span>
)}
</CalendarDayButton>
)
},
}}
/>
</div>
)
}
function CalendarWithPresets() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(new Date().getFullYear(), 1, 12)
)
const [currentMonth, setCurrentMonth] = React.useState<Date>(
new Date(new Date().getFullYear(), new Date().getMonth(), 1)
)
return (
<div className="flex max-w-[300px] flex-col gap-3">
<div className="px-2 text-center text-sm">With Presets</div>
<Card className="w-fit py-4">
<CardContent className="px-4">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
month={currentMonth}
onMonthChange={setCurrentMonth}
fixedWeeks
className="p-0 [--cell-size:--spacing(9.5)]"
/>
</CardContent>
<CardFooter className="flex flex-wrap gap-2 border-t px-4 pt-4">
{[
{ label: "Today", value: 0 },
{ label: "Tomorrow", value: 1 },
{ label: "In 3 days", value: 3 },
{ label: "In a week", value: 7 },
{ label: "In 2 weeks", value: 14 },
].map((preset) => (
<Button
key={preset.value}
variant="outline"
size="sm"
className="flex-1"
onClick={() => {
const newDate = addDays(new Date(), preset.value)
setDate(newDate)
setCurrentMonth(
new Date(newDate.getFullYear(), newDate.getMonth(), 1)
)
}}
>
{preset.label}
</Button>
))}
</CardFooter>
</Card>
</div>
)
}

View File

@@ -1,209 +0,0 @@
import Image from "next/image"
import { BathIcon, BedIcon, LandPlotIcon } from "lucide-react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import { Badge } from "@/registry/new-york-v4/ui/badge"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import { Input } from "@/registry/new-york-v4/ui/input"
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>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
/>
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
<a
href="#"
className="ml-auto inline-block text-sm underline-offset-4 hover:underline"
>
Forgot your password?
</a>
</div>
<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>
<Card>
<CardHeader>
<CardTitle>Meeting Notes</CardTitle>
<CardDescription>
Transcript from the meeting with the client.
</CardDescription>
</CardHeader>
<CardContent className="text-sm">
<p>
Client requested dashboard redesign with focus on mobile
responsiveness.
</p>
<ol className="mt-4 flex list-decimal flex-col gap-2 pl-6">
<li>New analytics widgets for daily/weekly metrics</li>
<li>Simplified navigation menu</li>
<li>Dark mode support</li>
<li>Timeline: 6 weeks</li>
<li>Follow-up meeting scheduled for next Tuesday</li>
</ol>
</CardContent>
<CardFooter>
<div className="flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background *:data-[slot=avatar]:grayscale">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</div>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Is this an image?</CardTitle>
<CardDescription>This is a card with an image.</CardDescription>
</CardHeader>
<CardContent className="px-0">
<Image
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
alt="Photo by Drew Beamer"
className="aspect-video object-cover"
width={500}
height={500}
/>
</CardContent>
<CardFooter className="flex items-center gap-2">
<Badge variant="outline">
<BedIcon /> 4
</Badge>
<Badge variant="outline">
<BathIcon /> 2
</Badge>
<Badge variant="outline">
<LandPlotIcon /> 350m²
</Badge>
<div className="ml-auto font-medium tabular-nums">$135,000</div>
</CardFooter>
</Card>
<div className="flex w-full flex-wrap items-start gap-8 md:*:data-[slot=card]:basis-1/4">
<Card>
<CardContent className="text-sm">Content Only</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Header Only</CardTitle>
<CardDescription>
This is a card with a header and a description.
</CardDescription>
</CardHeader>
</Card>
<Card>
<CardHeader>
<CardTitle>Header and Content</CardTitle>
<CardDescription>
This is a card with a header and a content.
</CardDescription>
</CardHeader>
<CardContent className="text-sm">Content</CardContent>
</Card>
<Card>
<CardFooter className="text-sm">Footer Only</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Header + Footer</CardTitle>
<CardDescription>
This is a card with a header and a footer.
</CardDescription>
</CardHeader>
<CardFooter className="text-sm">Footer</CardFooter>
</Card>
<Card>
<CardContent className="text-sm">Content</CardContent>
<CardFooter className="text-sm">Footer</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Header + Footer</CardTitle>
<CardDescription>
This is a card with a header and a footer.
</CardDescription>
</CardHeader>
<CardContent className="text-sm">Content</CardContent>
<CardFooter className="text-sm">Footer</CardFooter>
</Card>
<Card>
<CardHeader className="border-b">
<CardTitle>Header with Border</CardTitle>
<CardDescription>
This is a card with a header that has a bottom border.
</CardDescription>
</CardHeader>
<CardContent className="text-sm">Content</CardContent>
</Card>
<Card>
<CardContent className="text-sm">Content</CardContent>
<CardFooter className="border-t text-sm">
Footer with Border
</CardFooter>
</Card>
</div>
</div>
)
}

View File

@@ -1,73 +0,0 @@
import * as React from "react"
import { Card, CardContent } from "@/registry/new-york-v4/ui/card"
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/registry/new-york-v4/ui/carousel"
export function CarouselDemo() {
return (
<div className="hidden w-full flex-col items-center gap-4 @4xl:flex">
<Carousel className="max-w-sm *:data-[slot=carousel-next]:hidden *:data-[slot=carousel-previous]:hidden *:data-[slot=carousel-next]:md:inline-flex *:data-[slot=carousel-previous]:md:inline-flex">
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-4xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<Carousel
className="max-w-sm *:data-[slot=carousel-next]:hidden *:data-[slot=carousel-previous]:hidden *:data-[slot=carousel-next]:md:inline-flex *:data-[slot=carousel-previous]:md:inline-flex"
opts={{
align: "start",
}}
>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} className="md:basis-1/2 lg:basis-1/3">
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-3xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<Carousel className="max-w-sm *:data-[slot=carousel-next]:hidden *:data-[slot=carousel-previous]:hidden *:data-[slot=carousel-next]:md:inline-flex *:data-[slot=carousel-previous]:md:inline-flex">
<CarouselContent className="-ml-1">
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} className="pl-1 md:basis-1/2">
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-2xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</div>
)
}

View File

@@ -1,94 +0,0 @@
"use client"
import { TrendingUp } from "lucide-react"
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/new-york-v4/ui/chart"
export const description = "A simple area chart"
const chartData = [
{ month: "January", desktop: 186 },
{ month: "February", desktop: 305 },
{ month: "March", desktop: 237 },
{ month: "April", desktop: 73 },
{ month: "May", desktop: 209 },
{ month: "June", desktop: 214 },
]
const chartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
} satisfies ChartConfig
export function ChartAreaDemo() {
return (
<Card>
<CardHeader>
<CardTitle>Area Chart</CardTitle>
<CardDescription>
Showing total visitors for the last 6 months
</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig}>
<AreaChart
accessibilityLayer
data={chartData}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
axisLine={false}
tickMargin={8}
tickFormatter={(value) => value.slice(0, 3)}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="line" />}
/>
<Area
dataKey="desktop"
type="natural"
fill="var(--color-desktop)"
fillOpacity={0.4}
stroke="var(--color-desktop)"
/>
</AreaChart>
</ChartContainer>
</CardContent>
<CardFooter>
<div className="flex w-full items-start gap-2 text-sm">
<div className="grid gap-2">
<div className="flex items-center gap-2 leading-none font-medium">
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
</div>
<div className="flex items-center gap-2 leading-none text-muted-foreground">
January - June 2024
</div>
</div>
</div>
</CardFooter>
</Card>
)
}

View File

@@ -1,80 +0,0 @@
"use client"
import { TrendingUp } from "lucide-react"
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/new-york-v4/ui/chart"
export const description = "A multiple bar 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: 214, mobile: 140 },
]
const chartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
mobile: {
label: "Mobile",
color: "var(--chart-2)",
},
} satisfies ChartConfig
export function ChartBarDemo() {
return (
<Card>
<CardHeader>
<CardTitle>Bar Chart - Multiple</CardTitle>
<CardDescription>January - June 2024</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig}>
<BarChart accessibilityLayer data={chartData}>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
tickMargin={10}
axisLine={false}
tickFormatter={(value) => value.slice(0, 3)}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="dashed" />}
/>
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
</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="leading-none text-muted-foreground">
Showing total visitors for the last 6 months
</div>
</CardFooter>
</Card>
)
}

View File

@@ -1,103 +0,0 @@
"use client"
import { TrendingUp } from "lucide-react"
import { Bar, BarChart, XAxis, YAxis } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/new-york-v4/ui/chart"
export const description = "A mixed bar chart"
const chartData = [
{ browser: "chrome", visitors: 275, fill: "var(--color-chrome)" },
{ browser: "safari", visitors: 200, fill: "var(--color-safari)" },
{ browser: "firefox", visitors: 187, fill: "var(--color-firefox)" },
{ browser: "edge", visitors: 173, fill: "var(--color-edge)" },
{ browser: "other", visitors: 90, fill: "var(--color-other)" },
]
const chartConfig = {
visitors: {
label: "Visitors",
},
chrome: {
label: "Chrome",
color: "var(--chart-1)",
},
safari: {
label: "Safari",
color: "var(--chart-2)",
},
firefox: {
label: "Firefox",
color: "var(--chart-3)",
},
edge: {
label: "Edge",
color: "var(--chart-4)",
},
other: {
label: "Other",
color: "var(--chart-5)",
},
} satisfies ChartConfig
export function ChartBarMixed() {
return (
<Card>
<CardHeader>
<CardTitle>Bar Chart - Mixed</CardTitle>
<CardDescription>January - June 2024</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig}>
<BarChart
accessibilityLayer
data={chartData}
layout="vertical"
margin={{
left: 0,
}}
>
<YAxis
dataKey="browser"
type="category"
tickLine={false}
tickMargin={10}
axisLine={false}
tickFormatter={(value) =>
chartConfig[value as keyof typeof chartConfig]?.label
}
/>
<XAxis dataKey="visitors" type="number" hide />
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Bar dataKey="visitors" radius={5} />
</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="leading-none text-muted-foreground">
Showing total visitors for the last 6 months
</div>
</CardFooter>
</Card>
)
}

View File

@@ -1,17 +0,0 @@
import { ChartBarMixed } from "@/registry/new-york-v4/charts/chart-bar-mixed"
import { ChartAreaDemo } from "@/app/(internal)/sink/components/chart-area-demo"
import { ChartBarDemo } from "@/app/(internal)/sink/components/chart-bar-demo"
import { ChartLineDemo } from "@/app/(internal)/sink/components/chart-line-demo"
export function ChartDemo() {
return (
<div className="grid w-full max-w-screen-2xl gap-4 *:data-[slot=card]:flex-1 @2xl:grid-cols-2 @6xl:grid-cols-3">
<ChartAreaDemo />
<ChartBarDemo />
<ChartBarMixed />
<div className="@6xl:hidden">
<ChartLineDemo />
</div>
</div>
)
}

View File

@@ -1,100 +0,0 @@
"use client"
import { TrendingUp } from "lucide-react"
import { CartesianGrid, Line, LineChart, XAxis } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/new-york-v4/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/new-york-v4/ui/chart"
export const description = "A multiple line 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: 214, mobile: 140 },
]
const chartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
mobile: {
label: "Mobile",
color: "var(--chart-2)",
},
} satisfies ChartConfig
export function ChartLineDemo() {
return (
<Card>
<CardHeader>
<CardTitle>Line Chart - Multiple</CardTitle>
<CardDescription>January - June 2024</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig}>
<LineChart
accessibilityLayer
data={chartData}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
axisLine={false}
tickMargin={8}
tickFormatter={(value) => value.slice(0, 3)}
/>
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
<Line
dataKey="desktop"
type="monotone"
stroke="var(--color-desktop)"
strokeWidth={2}
dot={false}
/>
<Line
dataKey="mobile"
type="monotone"
stroke="var(--color-mobile)"
strokeWidth={2}
dot={false}
/>
</LineChart>
</ChartContainer>
</CardContent>
<CardFooter>
<div className="flex w-full items-start gap-2 text-sm">
<div className="grid gap-2">
<div className="flex items-center gap-2 leading-none font-medium">
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
</div>
<div className="flex items-center gap-2 leading-none text-muted-foreground">
Showing total visitors for the last 6 months
</div>
</div>
</div>
</CardFooter>
</Card>
)
}

View File

@@ -1,43 +0,0 @@
"use client"
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import { Label } from "@/registry/new-york-v4/ui/label"
export function CheckboxDemo() {
return (
<div className="flex flex-col gap-6">
<div className="flex items-center gap-3">
<Checkbox id="terms" />
<Label htmlFor="terms">Accept terms and conditions</Label>
</div>
<div className="flex items-start gap-3">
<Checkbox id="terms-2" defaultChecked />
<div className="grid gap-2">
<Label htmlFor="terms-2">Accept terms and conditions</Label>
<p className="text-sm text-muted-foreground">
By clicking this checkbox, you agree to the terms and conditions.
</p>
</div>
</div>
<div className="flex items-start gap-3">
<Checkbox id="toggle" disabled />
<Label htmlFor="toggle">Enable notifications</Label>
</div>
<Label className="flex items-start gap-3 rounded-lg border p-3 hover:bg-accent/50 has-[[aria-checked=true]]:border-blue-600 has-[[aria-checked=true]]:bg-blue-50 dark:has-[[aria-checked=true]]:border-blue-900 dark:has-[[aria-checked=true]]:bg-blue-950">
<Checkbox
id="toggle-2"
defaultChecked
className="data-[state=checked]:border-blue-600 data-[state=checked]:bg-blue-600 data-[state=checked]:text-white dark:data-[state=checked]:border-blue-700 dark:data-[state=checked]:bg-blue-700"
/>
<div className="grid gap-1.5 font-normal">
<p className="text-sm leading-none font-medium">
Enable notifications
</p>
<p className="text-sm text-muted-foreground">
You can enable or disable notifications at any time.
</p>
</div>
</Label>
</div>
)
}

View File

@@ -1,46 +0,0 @@
"use client"
import * as React from "react"
import { ChevronsUpDown } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/registry/new-york-v4/ui/collapsible"
export function CollapsibleDemo() {
const [isOpen, setIsOpen] = React.useState(false)
return (
<Collapsible
open={isOpen}
onOpenChange={setIsOpen}
className="flex w-full flex-col gap-2 md:w-[350px]"
>
<div className="flex items-center justify-between gap-4 px-4">
<h4 className="line-clamp-1 text-sm font-semibold">
@peduarte starred 3 repositories
</h4>
<CollapsibleTrigger asChild>
<Button variant="ghost" size="sm">
<ChevronsUpDown className="h-4 w-4" />
<span className="sr-only">Toggle</span>
</Button>
</CollapsibleTrigger>
</div>
<div className="rounded-md border px-4 py-2 font-mono text-sm shadow-xs">
@radix-ui/primitives
</div>
<CollapsibleContent className="flex flex-col gap-2">
<div className="rounded-md border px-4 py-2 font-mono text-sm shadow-xs">
@radix-ui/colors
</div>
<div className="rounded-md border px-4 py-2 font-mono text-sm shadow-xs">
@stitches/react
</div>
</CollapsibleContent>
</Collapsible>
)
}

View File

@@ -1,183 +0,0 @@
"use client"
import * as React from "react"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Combobox,
ComboboxChip,
ComboboxChips,
ComboboxChipsInput,
ComboboxCollection,
ComboboxContent,
ComboboxEmpty,
ComboboxGroup,
ComboboxInput,
ComboboxItem,
ComboboxLabel,
ComboboxList,
ComboboxTrigger,
ComboboxValue,
useComboboxAnchor,
} from "@/registry/new-york-v4/ui/combobox"
const frameworks = [
"Next.js",
"SvelteKit",
"Nuxt.js",
"Remix",
"Astro",
] as const
const timezones = [
{
value: "Americas",
items: ["(GMT-5) New York", "(GMT-8) Los Angeles", "(GMT-6) Chicago"],
},
{
value: "Europe",
items: ["(GMT+0) London", "(GMT+1) Paris", "(GMT+1) Berlin"],
},
{
value: "Asia/Pacific",
items: ["(GMT+9) Tokyo", "(GMT+8) Shanghai", "(GMT+8) Singapore"],
},
] as const
const countries = [
{ code: "", value: "", label: "Select country" },
{ code: "us", value: "united-states", label: "United States" },
{ code: "ca", value: "canada", label: "Canada" },
{ code: "gb", value: "united-kingdom", label: "United Kingdom" },
{ code: "de", value: "germany", label: "Germany" },
{ code: "fr", value: "france", label: "France" },
{ code: "jp", value: "japan", label: "Japan" },
]
export function ComboboxDemo() {
return (
<div className="flex w-full flex-col gap-6">
{/* Basic combobox. */}
<div className="flex flex-wrap items-start gap-4">
<Combobox items={frameworks}>
<ComboboxInput placeholder="Select a framework" />
<ComboboxContent>
<ComboboxEmpty>No items found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
{/* With clear button. */}
<div className="flex flex-wrap items-start gap-4">
<Combobox items={frameworks} defaultValue={frameworks[0]}>
<ComboboxInput placeholder="Select a framework" showClear />
<ComboboxContent>
<ComboboxEmpty>No items found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
{/* With groups. */}
<div className="flex flex-wrap items-start gap-4">
<Combobox items={timezones}>
<ComboboxInput placeholder="Select a timezone" />
<ComboboxContent>
<ComboboxEmpty>No timezones found.</ComboboxEmpty>
<ComboboxList>
{(group) => (
<ComboboxGroup key={group.value} items={group.items}>
<ComboboxLabel>{group.value}</ComboboxLabel>
<ComboboxCollection>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxCollection>
</ComboboxGroup>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
{/* With trigger button. */}
<div className="flex flex-wrap items-start gap-4">
<Combobox items={countries} defaultValue={countries[0]}>
<ComboboxTrigger
render={
<Button
variant="outline"
className="w-64 justify-between font-normal"
/>
}
>
<ComboboxValue />
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput showTrigger={false} placeholder="Search" />
<ComboboxEmpty>No items found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item.code} value={item}>
{item.label}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
{/* Multiple selection with chips. */}
<ComboboxMultiple />
</div>
)
}
function ComboboxMultiple() {
const anchor = useComboboxAnchor()
return (
<div className="flex flex-wrap items-start gap-4">
<Combobox
multiple
autoHighlight
items={frameworks}
defaultValue={[frameworks[0]]}
>
<ComboboxChips ref={anchor}>
<ComboboxValue>
{(values) => (
<React.Fragment>
{values.map((value: string) => (
<ComboboxChip key={value}>{value}</ComboboxChip>
))}
<ComboboxChipsInput placeholder="Add framework..." />
</React.Fragment>
)}
</ComboboxValue>
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxEmpty>No items found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
)
}

View File

@@ -1,87 +0,0 @@
"use client"
import * as React from "react"
import {
Calculator,
Calendar,
CreditCard,
Settings,
Smile,
User,
} from "lucide-react"
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/registry/new-york-v4/ui/command"
export function CommandDemo() {
const [open, setOpen] = React.useState(false)
React.useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "j" && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
setOpen((open) => !open)
}
}
document.addEventListener("keydown", down)
return () => document.removeEventListener("keydown", down)
}, [])
return (
<>
<p className="text-sm text-muted-foreground">
Press{" "}
<kbd className="pointer-events-none inline-flex h-5 items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100 select-none">
<span className="text-xs"></span>J
</kbd>
</p>
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>
<Calendar />
<span>Calendar</span>
</CommandItem>
<CommandItem>
<Smile />
<span>Search Emoji</span>
</CommandItem>
<CommandItem>
<Calculator />
<span>Calculator</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem>
<User />
<span>Profile</span>
<CommandShortcut>P</CommandShortcut>
</CommandItem>
<CommandItem>
<CreditCard />
<span>Billing</span>
<CommandShortcut>B</CommandShortcut>
</CommandItem>
<CommandItem>
<Settings />
<span>Settings</span>
<CommandShortcut>S</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
</>
)
}

View File

@@ -1,66 +0,0 @@
"use client"
import * as React from "react"
import { cn } from "@/registry/new-york-v4/lib/utils"
export function ComponentWrapper({
className,
name,
children,
...props
}: React.ComponentPropsWithoutRef<"div"> & { name: string }) {
return (
<ComponentErrorBoundary name={name}>
<div
id={name}
data-name={name.toLowerCase()}
className={cn(
"flex w-full scroll-mt-16 flex-col rounded-lg border",
className
)}
{...props}
>
<div className="border-b px-4 py-3">
<div className="text-sm font-medium">{getComponentName(name)}</div>
</div>
<div className="flex flex-1 items-center gap-2 p-4">{children}</div>
</div>
</ComponentErrorBoundary>
)
}
class ComponentErrorBoundary extends React.Component<
{ children: React.ReactNode; name: string },
{ hasError: boolean }
> {
constructor(props: { children: React.ReactNode; name: string }) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError() {
return { hasError: true }
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error(`Error in component ${this.props.name}:`, error, errorInfo)
}
render() {
if (this.state.hasError) {
return (
<div className="p-4 text-red-500">
Something went wrong in component: {this.props.name}
</div>
)
}
return this.props.children
}
}
function getComponentName(name: string) {
// convert kebab-case to title case
return name.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase())
}

View File

@@ -1,79 +0,0 @@
import { Code2Icon, PlusIcon, TrashIcon } from "lucide-react"
import {
ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuItem,
ContextMenuLabel,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "@/registry/new-york-v4/ui/context-menu"
export function ContextMenuDemo() {
return (
<ContextMenu>
<ContextMenuTrigger className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm">
Right click here
</ContextMenuTrigger>
<ContextMenuContent className="w-64">
<ContextMenuItem inset>
Back
<ContextMenuShortcut>[</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem inset disabled>
Forward
<ContextMenuShortcut>]</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem inset>
Reload
<ContextMenuShortcut>R</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger inset>More Tools</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-48">
<ContextMenuItem inset>
Save Page...
<ContextMenuShortcut>S</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
<PlusIcon />
Create Shortcut...
</ContextMenuItem>
<ContextMenuItem inset>Name Window...</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem>
<Code2Icon />
Developer Tools
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem variant="destructive">
<TrashIcon />
Delete
</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuSeparator />
<ContextMenuCheckboxItem checked>
Show Bookmarks Bar
<ContextMenuShortcut>B</ContextMenuShortcut>
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem>Show Full URLs</ContextMenuCheckboxItem>
<ContextMenuSeparator />
<ContextMenuRadioGroup value="pedro">
<ContextMenuLabel inset>People</ContextMenuLabel>
<ContextMenuRadioItem value="pedro">
Pedro Duarte
</ContextMenuRadioItem>
<ContextMenuRadioItem value="colm">Colm Tuite</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuContent>
</ContextMenu>
)
}

View File

@@ -1,179 +0,0 @@
"use client"
import * as React from "react"
import { addDays, format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import { type DateRange } from "react-day-picker"
import { cn } from "@/lib/utils"
import { useIsMobile } from "@/hooks/use-mobile"
import { Button } from "@/registry/new-york-v4/ui/button"
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/registry/new-york-v4/ui/drawer"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
export function DatePickerDemo() {
return (
<div className="flex flex-col items-start gap-4 md:flex-row">
<DatePickerSimple />
<DataPickerWithDropdowns />
<DatePickerWithRange />
</div>
)
}
function DatePickerSimple() {
const [date, setDate] = React.useState<Date>()
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"min-w-[200px] justify-start px-2 font-normal",
!date && "text-muted-foreground"
)}
>
<CalendarIcon className="text-muted-foreground" />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar mode="single" selected={date} onSelect={setDate} />
</PopoverContent>
</Popover>
)
}
function DatePickerWithRange() {
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="start">
<Calendar
mode="range"
defaultMonth={date?.from}
selected={date}
onSelect={setDate}
numberOfMonths={2}
/>
</PopoverContent>
</Popover>
)
}
function DataPickerWithDropdowns() {
const [date, setDate] = React.useState<Date>()
const [open, setOpen] = React.useState(false)
const isMobile = useIsMobile(450)
if (isMobile) {
return (
<Drawer open={open} onOpenChange={setOpen}>
<DrawerTrigger asChild>
<Button
variant="outline"
className={cn(
"min-w-[200px] justify-start px-2 font-normal",
!date && "text-muted-foreground"
)}
>
{date ? format(date, "PPP") : <span>Pick a date</span>}
<CalendarIcon className="ml-auto text-muted-foreground" />
</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader className="sr-only">
<DrawerTitle>Select a date</DrawerTitle>
<DrawerDescription>
Pick a date for your appointment.
</DrawerDescription>
</DrawerHeader>
<Calendar
mode="single"
selected={date}
onSelect={(day) => {
setDate(day)
setOpen(false)
}}
/>
</DrawerContent>
</Drawer>
)
}
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className={cn(
"min-w-[200px] justify-start px-2 font-normal",
!date && "text-muted-foreground"
)}
>
{date ? format(date, "PPP") : <span>Pick a date</span>}
<CalendarIcon className="ml-auto text-muted-foreground" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
captionLayout="dropdown"
/>
<div className="flex gap-2 border-t p-2">
<Button
variant="outline"
size="sm"
className="w-full"
onClick={() => setOpen(false)}
>
Done
</Button>
</div>
</PopoverContent>
</Popover>
)
}

View File

@@ -1,129 +0,0 @@
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/new-york-v4/ui/dialog"
import { Input } from "@/registry/new-york-v4/ui/input"
import { Label } from "@/registry/new-york-v4/ui/label"
export function DialogDemo() {
return (
<div className="flex flex-col items-start gap-4 md:flex-row">
<DialogWithForm />
<DialogScrollableContent />
<DialogWithStickyFooter />
</div>
)
}
function DialogWithForm() {
return (
<Dialog>
<form>
<DialogTrigger asChild>
<Button variant="outline">Edit Profile</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you&apos;re
done.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<div className="grid gap-3">
<Label htmlFor="name-1">Name</Label>
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
</div>
<div className="grid gap-3">
<Label htmlFor="username-1">Username</Label>
<Input id="username-1" name="username" defaultValue="@peduarte" />
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</form>
</Dialog>
)
}
function DialogScrollableContent() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Scrollable Content</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Scrollable Content</DialogTitle>
<DialogDescription>
This is a dialog with scrollable content.
</DialogDescription>
</DialogHeader>
<div className="-mx-6 max-h-[500px] overflow-y-auto px-6 text-sm">
<h4 className="mb-4 text-lg leading-none font-medium">Lorem Ipsum</h4>
{Array.from({ length: 10 }).map((_, index) => (
<p key={index} className="mb-4 leading-normal">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
))}
</div>
</DialogContent>
</Dialog>
)
}
function DialogWithStickyFooter() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Sticky Footer</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>Scrollable Content</DialogTitle>
<DialogDescription>
This is a dialog with scrollable content.
</DialogDescription>
</DialogHeader>
<div className="-mx-6 max-h-[500px] overflow-y-auto px-6 text-sm">
<h4 className="mb-4 text-lg leading-none font-medium">Lorem Ipsum</h4>
{Array.from({ length: 10 }).map((_, index) => (
<p key={index} className="mb-4 leading-normal">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
))}
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Close</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
}

View File

@@ -1,228 +0,0 @@
"use client"
import * as React from "react"
import { Minus, Plus } from "lucide-react"
import { Bar, BarChart, ResponsiveContainer } from "recharts"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/registry/new-york-v4/ui/drawer"
const data = [
{
goal: 400,
},
{
goal: 300,
},
{
goal: 200,
},
{
goal: 300,
},
{
goal: 200,
},
{
goal: 278,
},
{
goal: 189,
},
{
goal: 239,
},
{
goal: 300,
},
{
goal: 200,
},
{
goal: 278,
},
{
goal: 189,
},
{
goal: 349,
},
]
export function DrawerDemo() {
return (
<div className="flex flex-wrap items-start gap-4">
<DrawerBottom />
<DrawerScrollableContent />
<DrawerDirections />
</div>
)
}
function DrawerBottom() {
const [goal, setGoal] = React.useState(350)
const onClick = React.useCallback((adjustment: number) => {
setGoal((prevGoal) => Math.max(200, Math.min(400, prevGoal + adjustment)))
}, [])
return (
<Drawer>
<DrawerTrigger asChild>
<Button variant="outline">Open Drawer</Button>
</DrawerTrigger>
<DrawerContent>
<div className="mx-auto w-full max-w-sm">
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
</DrawerHeader>
<div className="p-4 pb-0">
<div className="flex items-center justify-center space-x-2">
<Button
variant="outline"
size="icon"
className="h-8 w-8 shrink-0 rounded-full"
onClick={() => onClick(-10)}
disabled={goal <= 200}
>
<Minus />
<span className="sr-only">Decrease</span>
</Button>
<div className="flex-1 text-center">
<div className="text-7xl font-bold tracking-tighter">
{goal}
</div>
<div className="text-[0.70rem] text-muted-foreground uppercase">
Calories/day
</div>
</div>
<Button
variant="outline"
size="icon"
className="h-8 w-8 shrink-0 rounded-full"
onClick={() => onClick(10)}
disabled={goal >= 400}
>
<Plus />
<span className="sr-only">Increase</span>
</Button>
</div>
<div className="mt-3 h-[120px]">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data}>
<Bar
dataKey="goal"
style={
{
fill: "var(--primary)",
opacity: 0.9,
} as React.CSSProperties
}
/>
</BarChart>
</ResponsiveContainer>
</div>
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose asChild>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</div>
</DrawerContent>
</Drawer>
)
}
function DrawerScrollableContent() {
return (
<Drawer direction="right">
<DrawerTrigger asChild>
<Button variant="outline">Scrollable Content</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
</DrawerHeader>
<div className="overflow-y-auto px-4 text-sm">
<h4 className="mb-4 text-lg leading-none font-medium">Lorem Ipsum</h4>
{Array.from({ length: 10 }).map((_, index) => (
<p key={index} className="mb-4 leading-normal">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
))}
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose asChild>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
)
}
const directions = ["top", "right", "bottom", "left"] as const
function DrawerDirections() {
return (
<>
{directions.map((direction) => (
<Drawer key={direction} direction={direction}>
<DrawerTrigger asChild>
<Button variant="outline" className="capitalize">
{direction}
</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>
Set your daily activity goal.
</DrawerDescription>
</DrawerHeader>
<div className="overflow-y-auto px-4 text-sm">
{Array.from({ length: 10 }).map((_, index) => (
<p key={index} className="mb-4 leading-normal">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum
dolore eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
</p>
))}
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose asChild>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
))}
</>
)
}

View File

@@ -1,471 +0,0 @@
"use client"
import * as React from "react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function DropdownMenuDemo() {
return (
<div className="flex flex-wrap items-start gap-4">
<DropdownMenuSimple />
<DropdownMenuCheckboxes />
<DropdownMenuRadioGroupDemo />
<DropdownMenuWithAvatar />
<DropdownMenuAvatarOnly />
<DropdownMenuIconColor />
</div>
)
}
function DropdownMenuSimple() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Open</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-56">
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuGroup>
<DropdownMenuItem>
Profile
<DropdownMenuShortcut>P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Billing
<DropdownMenuShortcut>B</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Settings
<DropdownMenuShortcut>S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Keyboard shortcuts
<DropdownMenuShortcut>K</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>Team</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>Invite users</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Email</DropdownMenuItem>
<DropdownMenuItem>Message</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>More...</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuItem>
New Team
<DropdownMenuShortcut>+T</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>GitHub</DropdownMenuItem>
<DropdownMenuItem>Support</DropdownMenuItem>
<DropdownMenuItem disabled>API</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
Log out
<DropdownMenuShortcut>Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
function DropdownMenuCheckboxes() {
const [showStatusBar, setShowStatusBar] = React.useState(true)
const [showActivityBar, setShowActivityBar] = React.useState(false)
const [showPanel, setShowPanel] = React.useState(false)
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Checkboxes</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-56">
<DropdownMenuGroup>
<DropdownMenuLabel>Account</DropdownMenuLabel>
<DropdownMenuItem>
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
Profile
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="SettingsIcon"
tabler="IconSettings"
hugeicons="SettingsIcon"
phosphor="GearIcon"
remixicon="RiSettings3Line"
/>
Settings
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
<DropdownMenuCheckboxItem
checked={showStatusBar}
onCheckedChange={setShowStatusBar}
>
Status Bar
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={showActivityBar}
onCheckedChange={setShowActivityBar}
disabled
>
Activity Bar
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={showPanel}
onCheckedChange={setShowPanel}
>
Panel
</DropdownMenuCheckboxItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="LogOutIcon"
tabler="IconLogout"
hugeicons="LogoutIcon"
phosphor="SignOutIcon"
remixicon="RiLogoutBoxLine"
/>
Sign Out
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
)
}
function DropdownMenuRadioGroupDemo() {
const [position, setPosition] = React.useState("bottom")
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Radio Group</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-56">
<DropdownMenuLabel inset>Panel Position</DropdownMenuLabel>
<DropdownMenuGroup>
<DropdownMenuRadioGroup value={position} onValueChange={setPosition}>
<DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="right" disabled>
Right
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
)
}
function DropdownMenuWithAvatar() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="h-12 justify-start px-2 md:max-w-[200px]"
>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="Shadcn" />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">shadcn</span>
<span className="truncate text-xs text-muted-foreground">
shadcn@example.com
</span>
</div>
<IconPlaceholder
lucide="ChevronsUpDownIcon"
tabler="IconChevronsUpDown"
hugeicons="ChevronUpDownIcon"
phosphor="CaretUpDownIcon"
remixicon="RiExpandUpDownLine"
className="ml-auto text-muted-foreground"
/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-(--radix-dropdown-menu-trigger-width) min-w-56"
align="start"
>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="Shadcn" />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">shadcn</span>
<span className="truncate text-xs text-muted-foreground">
shadcn@example.com
</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="SparklesIcon"
tabler="IconSparkles"
hugeicons="SparklesIcon"
phosphor="SparklesIcon"
remixicon="RiSparklingLine"
/>
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="BadgeCheckIcon"
tabler="IconBadgeCheck"
hugeicons="BadgeCheckIcon"
phosphor="CheckCircleIcon"
remixicon="RiVerifiedBadgeLine"
/>
Account
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="BellIcon"
tabler="IconBell"
hugeicons="BellIcon"
phosphor="BellIcon"
remixicon="RiNotification3Line"
/>
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<IconPlaceholder
lucide="LogOutIcon"
tabler="IconLogout"
hugeicons="LogoutIcon"
phosphor="SignOutIcon"
remixicon="RiLogoutBoxLine"
/>
Sign Out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
function DropdownMenuAvatarOnly() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="size-8 rounded-full border-none p-0"
>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="maxleiter"
/>
<AvatarFallback className="rounded-lg">LR</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-(--radix-dropdown-menu-trigger-width) min-w-56"
align="start"
>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="maxleiter"
/>
<AvatarFallback className="rounded-lg">LR</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">maxleiter</span>
<span className="truncate text-xs text-muted-foreground">
maxleiter@example.com
</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="SparklesIcon"
tabler="IconSparkles"
hugeicons="SparklesIcon"
phosphor="SparklesIcon"
remixicon="RiSparklingLine"
/>
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="BadgeCheckIcon"
tabler="IconBadgeCheck"
hugeicons="BadgeCheckIcon"
phosphor="CheckCircleIcon"
remixicon="RiVerifiedBadgeLine"
/>
Account
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="BellIcon"
tabler="IconBell"
hugeicons="BellIcon"
phosphor="BellIcon"
remixicon="RiNotification3Line"
/>
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<IconPlaceholder
lucide="LogOutIcon"
tabler="IconLogout"
hugeicons="LogoutIcon"
phosphor="SignOutIcon"
remixicon="RiLogoutBoxLine"
/>
Sign Out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
function DropdownMenuIconColor() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeOutlineIcon"
remixicon="RiMoreLine"
/>
<span className="sr-only">Toggle menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="PencilIcon"
tabler="IconPencil"
hugeicons="EditIcon"
phosphor="PencilIcon"
remixicon="RiPencilLine"
/>
Edit
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="ShareIcon"
tabler="IconShare"
hugeicons="ShareIcon"
phosphor="ShareIcon"
remixicon="RiShareLine"
/>
Share
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="DeleteIcon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
Delete
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
)
}

View File

@@ -1,250 +0,0 @@
import { IconArrowUpRight, IconFolderCode } from "@tabler/icons-react"
import { PlusIcon, SearchIcon } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import { Card, CardContent } from "@/registry/new-york-v4/ui/card"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/new-york-v4/ui/dialog"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/new-york-v4/ui/empty"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/new-york-v4/ui/input-group"
import { Kbd } from "@/registry/new-york-v4/ui/kbd"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
export function EmptyDemo() {
return (
<div className="grid w-full gap-8">
<Empty className="min-h-[80svh]">
<EmptyHeader>
<EmptyMedia variant="icon">
<IconFolderCode />
</EmptyMedia>
<EmptyTitle>No projects yet</EmptyTitle>
<EmptyDescription>
You haven&apos;t created any projects yet. Get started by creating
your first project.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<div className="flex gap-2">
<Button asChild>
<a href="#">Create project</a>
</Button>
<Button variant="outline">Import project</Button>
</div>
<Button variant="link" asChild className="text-muted-foreground">
<a href="#">
Learn more <IconArrowUpRight />
</a>
</Button>
</EmptyContent>
</Empty>
<Empty className="min-h-[80svh] bg-muted">
<EmptyHeader>
<EmptyTitle>No results found</EmptyTitle>
<EmptyDescription>
No results found for your search. Try adjusting your search terms.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Try again</Button>
<Button variant="link" asChild className="text-muted-foreground">
<a href="#">
Learn more <IconArrowUpRight />
</a>
</Button>
</EmptyContent>
</Empty>
<Empty className="min-h-[80svh] border">
<EmptyHeader>
<EmptyTitle>404 - Not Found</EmptyTitle>
<EmptyDescription>
The page you&apos;re looking for doesn&apos;t exist. Try searching
for what you need below.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<InputGroup className="w-3/4">
<InputGroupInput placeholder="Try searching for pages..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>/</Kbd>
</InputGroupAddon>
</InputGroup>
<EmptyDescription>
Need help? <a href="#">Contact support</a>
</EmptyDescription>
</EmptyContent>
</Empty>
<Empty className="min-h-[80svh]">
<EmptyHeader>
<EmptyTitle>Nothing to see here</EmptyTitle>
<EmptyDescription>
No posts have been created yet. Get started by{" "}
<a href="#">creating your first post</a> to share with the
community.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="outline">
<PlusIcon />
New Post
</Button>
</EmptyContent>
</Empty>
<div className="flex min-h-[800px] items-center justify-center rounded-lg bg-muted p-20">
<Card className="max-w-sm">
<CardContent>
<Empty className="p-4">
<EmptyHeader>
<EmptyTitle>404 - Not Found</EmptyTitle>
<EmptyDescription>
The page you&apos;re looking for doesn&apos;t exist. Try
searching for what you need below.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<InputGroup className="w-3/4">
<InputGroupInput placeholder="Try searching for pages..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>/</Kbd>
</InputGroupAddon>
</InputGroup>
<EmptyDescription>
Need help? <a href="#">Contact support</a>
</EmptyDescription>
</EmptyContent>
</Empty>
</CardContent>
</Card>
</div>
<div className="flex min-h-[800px] items-center justify-center rounded-lg bg-muted p-20">
<Card className="max-w-sm">
<CardContent>
<Empty className="p-4">
<EmptyHeader>
<EmptyMedia variant="icon">
<IconFolderCode />
</EmptyMedia>
<EmptyTitle>No projects yet</EmptyTitle>
<EmptyDescription>
You haven&apos;t created any projects yet. Get started by
creating your first project.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<div className="flex gap-2">
<Button asChild>
<a href="#">Create project</a>
</Button>
<Button variant="outline">Import project</Button>
</div>
<Button
variant="link"
asChild
className="text-muted-foreground"
>
<a href="#">
Learn more <IconArrowUpRight />
</a>
</Button>
</EmptyContent>
</Empty>
</CardContent>
</Card>
</div>
<div className="grid grid-cols-3 gap-6">
<div className="flex gap-4">
<Dialog>
<DialogTrigger asChild>
<Button>Open Dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader className="sr-only">
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>Dialog Description</DialogDescription>
</DialogHeader>
<Empty className="p-4">
<EmptyHeader>
<EmptyMedia variant="icon">
<IconFolderCode />
</EmptyMedia>
<EmptyTitle>No projects yet</EmptyTitle>
<EmptyDescription>
You haven&apos;t created any projects yet. Get started by
creating your first project.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<div className="flex gap-2">
<Button asChild>
<a href="#">Create project</a>
</Button>
<Button variant="outline">Import project</Button>
</div>
<Button
variant="link"
asChild
className="text-muted-foreground"
>
<a href="#">
Learn more <IconArrowUpRight />
</a>
</Button>
</EmptyContent>
</Empty>
</DialogContent>
</Dialog>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">Open Popover</Button>
</PopoverTrigger>
<PopoverContent className="rounded-2xl p-2">
<Empty className="rounded-sm p-6">
<EmptyHeader>
<EmptyTitle>Nothing to see here</EmptyTitle>
<EmptyDescription>
No posts have been created yet.{" "}
<a href="#">Create your first post</a> to share with the
community.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="outline">
<PlusIcon />
New Post
</Button>
</EmptyContent>
</Empty>
</PopoverContent>
</Popover>
</div>
</div>
</div>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,399 +0,0 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { z } from "zod"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/new-york-v4/ui/button"
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/registry/new-york-v4/ui/form"
import { Input } from "@/registry/new-york-v4/ui/input"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/new-york-v4/ui/radio-group"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import { Switch } from "@/registry/new-york-v4/ui/switch"
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
const items = [
{
id: "recents",
label: "Recents",
},
{
id: "home",
label: "Home",
},
{
id: "applications",
label: "Applications",
},
{
id: "desktop",
label: "Desktop",
},
{
id: "downloads",
label: "Downloads",
},
{
id: "documents",
label: "Documents",
},
] as const
const FormSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
bio: z
.string()
.min(10, {
message: "Bio must be at least 10 characters.",
})
.max(160, {
message: "Bio must not be longer than 30 characters.",
}),
email: z
.string({
required_error: "Please select an email to display.",
})
.email(),
type: z.enum(["all", "mentions", "none"], {
required_error: "You need to select a notification type.",
}),
mobile: z.boolean().default(false).optional(),
items: z.array(z.string()).refine((value) => value.some((item) => item), {
message: "You have to select at least one item.",
}),
dob: z.date({
required_error: "A date of birth is required.",
}),
marketing_emails: z.boolean().default(false).optional(),
security_emails: z.boolean(),
})
export function FormDemo() {
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
username: "",
items: ["recents", "home"],
},
})
function onSubmit(data: z.infer<typeof FormSchema>) {
toast("You submitted the following values:", {
description: (
<pre className="mt-2 w-[320px] rounded-md bg-neutral-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
})
}
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full max-w-sm gap-6"
>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a verified email to display" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="m@example.com">m@example.com</SelectItem>
<SelectItem value="m@google.com">m@google.com</SelectItem>
<SelectItem value="m@support.com">m@support.com</SelectItem>
</SelectContent>
</Select>
<FormDescription>
You can manage email addresses in your email settings.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="bio"
render={({ field }) => (
<FormItem>
<FormLabel>Bio</FormLabel>
<FormControl>
<Textarea
placeholder="Tell us a little bit about yourself"
className="resize-none"
{...field}
/>
</FormControl>
<FormDescription>
You can <span>@mention</span> other users and organizations.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem className="flex flex-col gap-3">
<FormLabel>Notify me about...</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex flex-col gap-3"
>
<FormItem className="flex items-center gap-2">
<FormControl>
<RadioGroupItem value="all" />
</FormControl>
<FormLabel className="font-normal">
All new messages
</FormLabel>
</FormItem>
<FormItem className="flex items-center gap-2">
<FormControl>
<RadioGroupItem value="mentions" />
</FormControl>
<FormLabel className="font-normal">
Direct messages and mentions
</FormLabel>
</FormItem>
<FormItem className="flex items-center gap-2">
<FormControl>
<RadioGroupItem value="none" />
</FormControl>
<FormLabel className="font-normal">Nothing</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="mobile"
render={({ field }) => (
<FormItem className="flex flex-row items-start gap-3 rounded-md border p-4 shadow-xs">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="flex flex-col gap-1">
<FormLabel className="leading-snug">
Use different settings for my mobile devices
</FormLabel>
<FormDescription className="leading-snug">
You can manage your mobile notifications in the mobile
settings page.
</FormDescription>
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="items"
render={() => (
<FormItem className="flex flex-col gap-4">
<div>
<FormLabel className="text-base">Sidebar</FormLabel>
<FormDescription>
Select the items you want to display in the sidebar.
</FormDescription>
</div>
<div className="flex flex-col gap-2">
{items.map((item) => (
<FormField
key={item.id}
control={form.control}
name="items"
render={({ field }) => {
return (
<FormItem
key={item.id}
className="flex items-start gap-3"
>
<FormControl>
<Checkbox
checked={field.value?.includes(item.id)}
onCheckedChange={(checked) => {
return checked
? field.onChange([...field.value, item.id])
: field.onChange(
field.value?.filter(
(value) => value !== item.id
)
)
}}
/>
</FormControl>
<FormLabel className="text-sm leading-tight font-normal">
{item.label}
</FormLabel>
</FormItem>
)
}}
/>
))}
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="dob"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Date of birth</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant={"outline"}
className={cn(
"w-[240px] pl-3 text-left font-normal",
!field.value && "text-muted-foreground"
)}
>
{field.value ? (
format(field.value, "PPP")
) : (
<span>Pick a date</span>
)}
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={field.value}
onSelect={field.onChange}
disabled={(date: Date) =>
date > new Date() || date < new Date("1900-01-01")
}
initialFocus
/>
</PopoverContent>
</Popover>
<FormDescription>
Your date of birth is used to calculate your age.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div>
<h3 className="mb-4 text-lg font-medium">Email Notifications</h3>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="marketing_emails"
render={({ field }) => (
<FormItem className="flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
<div className="flex flex-col gap-0.5">
<FormLabel className="leading-normal">
Marketing emails
</FormLabel>
<FormDescription className="leading-snug">
Receive emails about new products, features, and more.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="security_emails"
render={({ field }) => (
<FormItem className="flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
<div className="flex flex-col gap-0.5 opacity-60">
<FormLabel className="leading-normal">
Security emails
</FormLabel>
<FormDescription className="leading-snug">
Receive emails about your account security.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled
aria-readonly
/>
</FormControl>
</FormItem>
)}
/>
</div>
</div>
<Button type="submit">Submit</Button>
</form>
</Form>
)
}

View File

@@ -1,43 +0,0 @@
import { CalendarIcon } from "lucide-react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/registry/new-york-v4/ui/hover-card"
export function HoverCardDemo() {
return (
<HoverCard>
<HoverCardTrigger asChild>
<Button variant="link">@nextjs</Button>
</HoverCardTrigger>
<HoverCardContent className="w-80" side="right">
<div className="flex justify-between gap-4">
<Avatar>
<AvatarImage src="https://github.com/vercel.png" />
<AvatarFallback>VC</AvatarFallback>
</Avatar>
<div className="flex flex-col gap-1">
<h4 className="text-sm font-semibold">@nextjs</h4>
<p className="text-sm">
The React Framework created and maintained by @vercel.
</p>
<div className="mt-1 flex items-center gap-2">
<CalendarIcon className="size-4 text-muted-foreground" />{" "}
<span className="text-xs text-muted-foreground">
Joined December 2021
</span>
</div>
</div>
</div>
</HoverCardContent>
</HoverCard>
)
}

View File

@@ -1,23 +0,0 @@
import { Input } from "@/registry/new-york-v4/ui/input"
export function InputDemo() {
return (
<div className="flex flex-col flex-wrap gap-4 md:flex-row">
<Input type="email" placeholder="Email" />
<Input type="text" placeholder="Error" aria-invalid="true" />
<Input type="password" placeholder="Password" />
<Input type="number" placeholder="Number" />
<Input type="file" placeholder="File" />
<Input type="tel" placeholder="Tel" />
<Input type="text" placeholder="Text" />
<Input type="url" placeholder="URL" />
<Input type="search" placeholder="Search" />
<Input type="date" placeholder="Date" />
<Input type="datetime-local" placeholder="Datetime Local" />
<Input type="month" placeholder="Month" />
<Input type="time" placeholder="Time" />
<Input type="week" placeholder="Week" />
<Input disabled placeholder="Disabled" />
</div>
)
}

View File

@@ -1,663 +0,0 @@
"use client"
import { useState } from "react"
import {
IconBrandJavascript,
IconCheck,
IconChevronDown,
IconCopy,
IconInfoCircle,
IconLoader2,
IconMicrophone,
IconPlayerRecordFilled,
IconPlus,
IconRefresh,
IconSearch,
IconServerSpark,
IconStar,
IconTrash,
} from "@tabler/icons-react"
import {
ArrowRightIcon,
ArrowUpIcon,
ChevronDownIcon,
EyeClosedIcon,
FlipVerticalIcon,
SearchIcon,
} from "lucide-react"
import { toast } from "sonner"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
ButtonGroup,
ButtonGroupText,
} from "@/registry/new-york-v4/ui/button-group"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/registry/new-york-v4/ui/field"
import { Input } from "@/registry/new-york-v4/ui/input"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/registry/new-york-v4/ui/input-group"
import { Kbd, KbdGroup } from "@/registry/new-york-v4/ui/kbd"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
import { Spinner } from "@/registry/new-york-v4/ui/spinner"
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york-v4/ui/tooltip"
export function InputGroupDemo() {
const [country, setCountry] = useState("+1")
return (
<div className="flex w-full flex-wrap gap-12 pb-72 *:[div]:w-full *:[div]:max-w-sm">
<div className="flex flex-col gap-10">
<Field>
<FieldLabel htmlFor="input-default-01">
Default (No Input Group)
</FieldLabel>
<Input placeholder="Default" id="input-default-01" />
</Field>
<Field>
<FieldLabel htmlFor="input-group-02">Input Group</FieldLabel>
<InputGroup>
<InputGroupInput id="input-group-02" placeholder="Default" />
</InputGroup>
</Field>
<Field data-disabled="true">
<FieldLabel htmlFor="input-disabled-03">Disabled</FieldLabel>
<InputGroup>
<InputGroupInput
id="input-disabled-03"
placeholder="This field is disabled"
disabled
/>
</InputGroup>
</Field>
<Field data-invalid="true">
<FieldLabel htmlFor="input-invalid-04">Invalid</FieldLabel>
<InputGroup>
<InputGroupInput
id="input-invalid-04"
placeholder="This field is invalid"
aria-invalid="true"
/>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-icon-left-05">Icon (left)</FieldLabel>
<InputGroup>
<InputGroupInput id="input-icon-left-05" />
<InputGroupAddon>
<SearchIcon className="text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-icon-left-06" />
<InputGroupAddon>
<FlipVerticalIcon className="text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-icon-right-07">Icon (right)</FieldLabel>
<InputGroup>
<InputGroupInput id="input-icon-right-07" />
<InputGroupAddon align="inline-end">
<EyeClosedIcon />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-icon-right-08" />
<InputGroupAddon align="inline-end">
<IconLoader2 className="animate-spin text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-icon-both-09">Icon (both)</FieldLabel>
<InputGroup>
<InputGroupInput id="input-icon-both-09" />
<InputGroupAddon>
<IconMicrophone className="text-muted-foreground" />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<IconPlayerRecordFilled className="animate-pulse text-red-500" />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-icon-both-10">Multiple Icons</FieldLabel>
<InputGroup>
<InputGroupInput id="input-icon-both-10" />
<InputGroupAddon align="inline-end">
<IconStar />
<InputGroupButton
size="icon-xs"
onClick={() => toast.success("Copied to clipboard")}
>
<IconCopy />
</InputGroupButton>
</InputGroupAddon>
<InputGroupAddon>
<IconPlayerRecordFilled className="animate-pulse text-red-500" />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-description-10">Description</FieldLabel>
<InputGroup>
<InputGroupInput id="input-description-10" />
<InputGroupAddon align="inline-end">
<IconInfoCircle />
</InputGroupAddon>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<FieldGroup className="grid grid-cols-2 gap-4">
<Field>
<FieldLabel htmlFor="input-group-11">First Name</FieldLabel>
<InputGroup>
<InputGroupInput id="input-group-11" placeholder="First Name" />
<InputGroupAddon align="inline-end">
<IconInfoCircle />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-group-12">Last Name</FieldLabel>
<InputGroup>
<InputGroupInput id="input-group-12" placeholder="Last Name" />
<InputGroupAddon align="inline-end">
<IconInfoCircle />
</InputGroupAddon>
</InputGroup>
</Field>
</FieldGroup>
</div>
<div className="flex flex-col gap-10">
<Field>
<FieldLabel htmlFor="input-tooltip-20">Tooltip</FieldLabel>
<InputGroup>
<InputGroupInput id="input-tooltip-20" />
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton className="rounded-full" size="icon-xs">
<IconInfoCircle />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>This is content in a tooltip.</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-dropdown-21">Dropdown</FieldLabel>
<InputGroup>
<InputGroupInput id="input-dropdown-21" />
<InputGroupAddon>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<InputGroupButton className="text-muted-foreground tabular-nums">
{country} <ChevronDownIcon />
</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
className="min-w-16"
sideOffset={10}
alignOffset={-8}
>
<DropdownMenuItem onClick={() => setCountry("+1")}>
+1
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setCountry("+44")}>
+44
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setCountry("+46")}>
+46
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-label-10">Label</FieldLabel>
<InputGroup>
<InputGroupAddon>
<FieldLabel htmlFor="input-label-10">Label</FieldLabel>
</InputGroupAddon>
<InputGroupInput id="input-label-10" />
</InputGroup>
<InputGroup className="gap-0">
<InputGroupAddon>
<FieldLabel
htmlFor="input-prefix-11"
className="text-muted-foreground"
>
example.com/
</FieldLabel>
</InputGroupAddon>
<InputGroupInput id="input-prefix-11" />
</InputGroup>
<InputGroup>
<InputGroupInput id="input-optional-12" />
<InputGroupAddon align="inline-end">
<InputGroupText>(optional)</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-button-13">Button</FieldLabel>
<InputGroup>
<InputGroupInput id="input-button-13" />
<InputGroupAddon>
<InputGroupButton>Button</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-button-14" />
<InputGroupAddon>
<InputGroupButton variant="outline">Button</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-button-15" />
<InputGroupAddon>
<InputGroupButton variant="secondary">Button</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-button-16" />
<InputGroupAddon align="inline-end">
<InputGroupButton variant="secondary">Button</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-button-17" />
<InputGroupAddon align="inline-end">
<InputGroupButton size="icon-xs">
<IconCopy />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-button-18" />
<InputGroupAddon align="inline-end">
<InputGroupButton variant="secondary" size="icon-xs">
<IconTrash />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup className="[--radius:9999px]">
<Popover>
<PopoverTrigger asChild>
<InputGroupAddon>
<InputGroupButton variant="secondary" size="icon-xs">
<IconInfoCircle />
</InputGroupButton>
</InputGroupAddon>
</PopoverTrigger>
<PopoverContent
align="start"
className="flex flex-col gap-1 rounded-xl text-sm"
>
<p className="font-medium">Your connection is not secure.</p>
<p>
You should not enter any sensitive information on this site.
</p>
</PopoverContent>
</Popover>
<InputGroupAddon className="text-muted-foreground">
https://
</InputGroupAddon>
<InputGroupInput id="input-secure-19" />
<InputGroupAddon align="inline-end">
<InputGroupButton
size="icon-xs"
onClick={() => toast.success("Added to favorites")}
>
<IconStar />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-addon-20">Addon (block-start)</FieldLabel>
<InputGroup className="h-auto">
<InputGroupInput id="input-addon-20" />
<InputGroupAddon align="block-start">
<InputGroupText>First Name</InputGroupText>
<IconInfoCircle className="ml-auto text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-addon-21">Addon (block-end)</FieldLabel>
<InputGroup className="h-auto">
<InputGroupInput id="input-addon-21" />
<InputGroupAddon align="block-end">
<InputGroupText>20/240 characters</InputGroupText>
<IconInfoCircle className="ml-auto text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="textarea-comment-33">Default Button</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-comment-33"
placeholder="Share your thoughts..."
className="py-2.5"
/>
<InputGroupAddon align="block-end">
<ButtonGroup>
<Button variant="outline" size="sm">
Button
</Button>
<Button variant="outline" size="icon" className="size-8">
<IconChevronDown />
</Button>
</ButtonGroup>
<Button variant="ghost" className="ml-auto" size="sm">
Cancel
</Button>
<Button variant="default" size="sm">
Post <ArrowRightIcon />
</Button>
</InputGroupAddon>
</InputGroup>
</Field>
</div>
<div className="flex flex-col gap-10">
<Field>
<FieldLabel htmlFor="input-kbd-22">Input Group with Kbd</FieldLabel>
<InputGroup>
<InputGroupInput id="input-kbd-22" />
<InputGroupAddon>
<Kbd>K</Kbd>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-kbd-23" />
<InputGroupAddon align="inline-end">
<Kbd>K</Kbd>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput
id="input-search-apps-24"
placeholder="Search for Apps..."
/>
<InputGroupAddon align="inline-end">Ask AI</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>Tab</Kbd>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput
id="input-search-type-25"
placeholder="Type to search..."
/>
<InputGroupAddon align="inline-start">
<IconServerSpark />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<KbdGroup>
<Kbd>Ctrl</Kbd>
<Kbd>C</Kbd>
</KbdGroup>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-username-26">Username</FieldLabel>
<InputGroup>
<InputGroupInput id="input-username-26" defaultValue="shadcn" />
<InputGroupAddon align="inline-end">
<div className="flex size-4 items-center justify-center rounded-full bg-green-500 dark:bg-green-800">
<IconCheck className="size-3 text-white" />
</div>
</InputGroupAddon>
</InputGroup>
<FieldDescription className="text-green-700">
This username is available.
</FieldDescription>
</Field>
<InputGroup>
<InputGroupInput
id="input-search-docs-27"
placeholder="Search documentation..."
/>
<InputGroupAddon>
<IconSearch />
</InputGroupAddon>
<InputGroupAddon align="inline-end">12 results</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled="true">
<InputGroupInput
id="input-search-disabled-28"
placeholder="Search documentation..."
disabled
/>
<InputGroupAddon>
<IconSearch />
</InputGroupAddon>
<InputGroupAddon align="inline-end">Disabled</InputGroupAddon>
</InputGroup>
<Field>
<FieldLabel htmlFor="url">With Button Group</FieldLabel>
<ButtonGroup>
<ButtonGroupText>https://</ButtonGroupText>
<InputGroup>
<InputGroupInput id="url" />
<InputGroupAddon align="inline-end">
<IconInfoCircle />
</InputGroupAddon>
</InputGroup>
<ButtonGroupText>.com</ButtonGroupText>
</ButtonGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<Field data-disabled="true">
<FieldLabel htmlFor="input-group-29">Loading</FieldLabel>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
<InputGroup>
<InputGroupInput
id="input-group-29"
disabled
defaultValue="shadcn"
/>
<InputGroupAddon align="inline-end">
<Spinner />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="textarea-code-32">Code Editor</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-code-32"
placeholder="console.log('Hello, world!');"
className="min-h-[300px] py-3"
/>
<InputGroupAddon align="block-start" className="border-b">
<InputGroupText className="font-mono font-medium">
<IconBrandJavascript />
script.js
</InputGroupText>
<InputGroupButton size="icon-xs" className="ml-auto">
<IconRefresh />
</InputGroupButton>
<InputGroupButton size="icon-xs" variant="ghost">
<IconCopy />
</InputGroupButton>
</InputGroupAddon>
<InputGroupAddon align="block-end" className="border-t">
<InputGroupText>Line 1, Column 1</InputGroupText>
<InputGroupText className="ml-auto">JavaScript</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
</div>
<div className="flex flex-col gap-10">
<Field>
<FieldLabel htmlFor="textarea-header-footer-12">Default</FieldLabel>
<Textarea
id="textarea-header-footer-12"
placeholder="Enter your text here..."
/>
</Field>
<Field>
<FieldLabel htmlFor="textarea-header-footer-13">
Input Group
</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-header-footer-13"
placeholder="Enter your text here..."
/>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<Field data-invalid="true">
<FieldLabel htmlFor="textarea-header-footer-14">Invalid</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-header-footer-14"
placeholder="Enter your text here..."
aria-invalid="true"
/>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<Field data-disabled="true">
<FieldLabel htmlFor="textarea-header-footer-15">Disabled</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-header-footer-15"
placeholder="Enter your text here..."
disabled
/>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="textarea-header-footer-30">Textarea</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-header-footer-30"
placeholder="Enter your text here..."
/>
<InputGroupAddon align="block-end">
<InputGroupText>0/280 characters</InputGroupText>
<InputGroupButton
variant="default"
size="icon-xs"
className="ml-auto rounded-full"
>
<ArrowUpIcon />
<span className="sr-only">Send</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="prompt-31">Enter your prompt</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="prompt-31"
placeholder="Ask, Search or Chat..."
/>
<InputGroupAddon align="block-end">
<InputGroupButton
variant="outline"
className="rounded-full"
size="icon-xs"
>
<IconPlus />
</InputGroupButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<InputGroupButton variant="ghost">Auto</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent side="top" align="start">
<DropdownMenuItem>Auto</DropdownMenuItem>
<DropdownMenuItem>Agent</DropdownMenuItem>
<DropdownMenuItem>Manual</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<InputGroupText className="ml-auto">
12 messages left
</InputGroupText>
<InputGroupButton
variant="default"
className="rounded-full"
size="icon-xs"
>
<ArrowUpIcon />
<span className="sr-only">Send</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="textarea-comment-31">Comment Box</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-comment-31"
placeholder="Share your thoughts..."
/>
<InputGroupAddon align="block-end">
<InputGroupButton variant="ghost" className="ml-auto" size="sm">
Cancel
</InputGroupButton>
<InputGroupButton variant="default" size="sm">
Post Comment
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
</div>
</div>
)
}

View File

@@ -1,109 +0,0 @@
"use client"
import * as React from "react"
import { REGEXP_ONLY_DIGITS } from "input-otp"
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/registry/new-york-v4/ui/input-otp"
import { Label } from "@/registry/new-york-v4/ui/label"
export function InputOTPDemo() {
return (
<div className="flex flex-col flex-wrap gap-6 md:flex-row">
<InputOTPSimple />
<InputOTPPattern />
<InputOTPWithSeparator />
<InputOTPWithSpacing />
</div>
)
}
function InputOTPSimple() {
return (
<div className="grid gap-2">
<Label htmlFor="simple">Simple</Label>
<InputOTP id="simple" maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</div>
)
}
function InputOTPPattern() {
return (
<div className="grid gap-2">
<Label htmlFor="digits-only">Digits Only</Label>
<InputOTP id="digits-only" maxLength={6} pattern={REGEXP_ONLY_DIGITS}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</div>
)
}
function InputOTPWithSeparator() {
const [value, setValue] = React.useState("123456")
return (
<div className="grid gap-2">
<Label htmlFor="with-separator">With Separator</Label>
<InputOTP
id="with-separator"
maxLength={6}
value={value}
onChange={setValue}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</div>
)
}
function InputOTPWithSpacing() {
return (
<div className="grid gap-2">
<Label htmlFor="with-spacing">With Spacing</Label>
<InputOTP id="with-spacing" maxLength={6}>
<InputOTPGroup className="gap-2 *:data-[slot=input-otp-slot]:rounded-md *:data-[slot=input-otp-slot]:border">
<InputOTPSlot index={0} aria-invalid="true" />
<InputOTPSlot index={1} aria-invalid="true" />
<InputOTPSlot index={2} aria-invalid="true" />
<InputOTPSlot index={3} aria-invalid="true" />
</InputOTPGroup>
</InputOTP>
</div>
)
}

View File

@@ -1,392 +0,0 @@
import * as React from "react"
import Image from "next/image"
import { IconChevronRight, IconDownload } from "@tabler/icons-react"
import { PlusIcon, TicketIcon } from "lucide-react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
FieldTitle,
} from "@/registry/new-york-v4/ui/field"
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemFooter,
ItemGroup,
ItemHeader,
ItemMedia,
ItemSeparator,
ItemTitle,
} from "@/registry/new-york-v4/ui/item"
import { Progress } from "@/registry/new-york-v4/ui/progress"
import { Spinner } from "@/registry/new-york-v4/ui/spinner"
const people = [
{
username: "shadcn",
avatar: "https://github.com/shadcn.png",
message: "Just shipped a component that fixes itself",
},
{
username: "pranathip",
avatar: "https://github.com/pranathip.png",
message: "My code is so clean, it does its own laundry",
},
{
username: "evilrabbit",
avatar: "https://github.com/evilrabbit.png",
message:
"Debugging is like being a detective in a crime movie where you're also the murderer",
},
{
username: "maxleiter",
avatar: "https://github.com/maxleiter.png",
message:
"I don't always test my code, but when I do, I test it in production",
},
]
const music = [
{
title: "Midnight City Lights",
artist: "Neon Dreams",
album: "Electric Nights",
duration: "3:45",
},
{
title: "Coffee Shop Conversations",
artist: "The Morning Brew",
album: "Urban Stories",
duration: "4:05",
},
{
title: "Digital Rain",
artist: "Cyber Symphony",
album: "Binary Beats",
duration: "3:30",
},
{
title: "Sunset Boulevard",
artist: "Golden Hour",
album: "California Dreams",
duration: "3:55",
},
{
title: "Neon Sign Romance",
artist: "Retro Wave",
album: "80s Forever",
duration: "4:10",
},
{
title: "Ocean Depths",
artist: "Deep Blue",
album: "Underwater Symphony",
duration: "3:40",
},
{
title: "Space Station Alpha",
artist: "Cosmic Explorers",
album: "Galactic Journey",
duration: "3:50",
},
{
title: "Forest Whispers",
artist: "Nature's Choir",
album: "Woodland Tales",
duration: "3:35",
},
]
const issues = [
{
number: 1247,
date: "March 15, 2024",
title:
"Button component doesn't respect disabled state when using custom variants",
description:
"When applying custom variants to the Button component, the disabled prop is ignored and the button remains clickable. This affects accessibility and user experience.",
},
{
number: 892,
date: "February 8, 2024",
title: "Dialog component causes scroll lock on mobile devices",
description:
"The Dialog component prevents scrolling on the background content but doesn't restore scroll position properly on mobile Safari and Chrome, causing layout shifts.",
},
{
number: 1156,
date: "January 22, 2024",
title: "TypeScript errors with Select component in strict mode",
description:
"Using the Select component with TypeScript strict mode enabled throws type errors related to generic constraints and value prop typing.",
},
{
number: 734,
date: "December 3, 2023",
title: "Dark mode toggle causes flash of unstyled content",
description:
"When switching between light and dark themes, there's a brief moment where components render with incorrect styling before the theme transition completes.",
},
{
number: 1389,
date: "April 2, 2024",
title: "Form validation messages overlap with floating labels",
description:
"Error messages in Form components with floating labels appear underneath the label text, making them difficult to read. Need better positioning logic for validation feedback.",
},
]
export function ItemDemo() {
return (
<div className="@container w-full">
<div className="flex flex-wrap gap-6 2xl:gap-12">
<div className="flex max-w-sm flex-col gap-6">
<Item>
<ItemContent>
<ItemTitle>Item Title</ItemTitle>
</ItemContent>
<ItemActions>
<Button variant="outline">Button</Button>
</ItemActions>
</Item>
<Item variant="outline">
<ItemContent>
<ItemTitle>Item Title</ItemTitle>
</ItemContent>
<ItemActions>
<Button variant="outline">Button</Button>
</ItemActions>
</Item>
<Item>
<ItemContent>
<ItemTitle>Item Title</ItemTitle>
<ItemDescription>Item Description</ItemDescription>
</ItemContent>
<ItemActions>
<Button variant="outline">Button</Button>
</ItemActions>
</Item>
<Item variant="outline">
<ItemContent>
<ItemTitle>Item Title</ItemTitle>
<ItemDescription>Item Description</ItemDescription>
</ItemContent>
</Item>
<Item variant="muted">
<ItemContent>
<ItemTitle>Item Title</ItemTitle>
<ItemDescription>Item Description</ItemDescription>
</ItemContent>
</Item>
<Item variant="muted">
<ItemContent>
<ItemTitle>Item Title</ItemTitle>
<ItemDescription>Item Description</ItemDescription>
</ItemContent>
<ItemActions>
<Button variant="outline">Button</Button>
<Button variant="outline">Button</Button>
</ItemActions>
</Item>
<Item variant="outline">
<ItemMedia variant="icon">
<TicketIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>Item Title</ItemTitle>
</ItemContent>
<ItemActions>
<Button size="sm">Purchase</Button>
</ItemActions>
</Item>
<Item variant="muted">
<ItemMedia variant="icon">
<TicketIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>Item Title</ItemTitle>
<ItemDescription>Item Description</ItemDescription>
</ItemContent>
<ItemActions>
<Button size="sm">Upgrade</Button>
</ItemActions>
</Item>
<FieldLabel>
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Field Title</FieldTitle>
<FieldDescription>Field Description</FieldDescription>
</FieldContent>
<Button variant="outline">Button</Button>
</Field>
</FieldLabel>
</div>
<div className="flex max-w-sm flex-col gap-6">
<ItemGroup>
{people.map((person, index) => (
<React.Fragment key={person.username}>
<Item>
<ItemMedia>
<Avatar>
<AvatarImage src={person.avatar} />
<AvatarFallback>
{person.username.charAt(0)}
</AvatarFallback>
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>{person.username}</ItemTitle>
<ItemDescription>{person.message}</ItemDescription>
</ItemContent>
<ItemActions>
<Button
variant="outline"
size="icon"
className="size-8 rounded-full"
>
<PlusIcon />
</Button>
</ItemActions>
</Item>
{index !== people.length - 1 && <ItemSeparator />}
</React.Fragment>
))}
</ItemGroup>
<Item variant="outline">
<ItemMedia>
<div className="flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background *:data-[slot=avatar]:grayscale">
<Avatar>
<AvatarImage
src="https://github.com/shadcn.png"
alt="@shadcn"
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</div>
</ItemMedia>
<ItemContent>
<ItemTitle>Design Department</ItemTitle>
<ItemDescription>
Meet our team of designers, engineers, and researchers.
</ItemDescription>
</ItemContent>
<ItemActions className="self-start">
<Button
variant="outline"
size="icon"
className="size-8 rounded-full"
>
<IconChevronRight />
</Button>
</ItemActions>
</Item>
<Item variant="outline">
<ItemHeader>Your download has started.</ItemHeader>
<ItemMedia variant="icon">
<Spinner />
</ItemMedia>
<ItemContent>
<ItemTitle>Downloading...</ItemTitle>
<ItemDescription>129 MB / 1000 MB</ItemDescription>
</ItemContent>
<ItemActions>
<Button variant="outline" size="sm">
Cancel
</Button>
</ItemActions>
<ItemFooter>
<Progress value={50} />
</ItemFooter>
</Item>
</div>
<div className="flex max-w-lg flex-col gap-6">
<ItemGroup className="gap-4">
{music.map((song) => (
<Item key={song.title} variant="outline" asChild role="listitem">
<a href="#">
<ItemMedia variant="image">
<Image
src={`https://avatar.vercel.sh/${song.title}`}
alt={song.title}
width={32}
height={32}
className="grayscale"
/>
</ItemMedia>
<ItemContent>
<ItemTitle className="line-clamp-1">
{song.title} -{" "}
<span className="text-muted-foreground">
{song.album}
</span>
</ItemTitle>
<ItemDescription>{song.artist}</ItemDescription>
</ItemContent>
<ItemContent className="flex-none text-center">
<ItemDescription>{song.duration}</ItemDescription>
</ItemContent>
<ItemActions>
<Button
variant="ghost"
size="icon"
className="size-8 rounded-full"
aria-label="Download"
>
<IconDownload />
</Button>
</ItemActions>
</a>
</Item>
))}
</ItemGroup>
</div>
<div className="flex max-w-lg flex-col gap-6">
<ItemGroup>
{issues.map((issue) => (
<React.Fragment key={issue.number}>
<Item asChild className="rounded-none">
<a href="#">
<ItemContent>
<ItemTitle className="line-clamp-1">
{issue.title}
</ItemTitle>
<ItemDescription>{issue.description}</ItemDescription>
</ItemContent>
<ItemContent className="self-start">
#{issue.number}
</ItemContent>
</a>
</Item>
<ItemSeparator />
</React.Fragment>
))}
</ItemGroup>
</div>
</div>
</div>
)
}

View File

@@ -1,103 +0,0 @@
import { IconArrowLeft, IconArrowRight } from "@tabler/icons-react"
import { CommandIcon, WavesIcon } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import { ButtonGroup } from "@/registry/new-york-v4/ui/button-group"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/new-york-v4/ui/input-group"
import { Kbd, KbdGroup } from "@/registry/new-york-v4/ui/kbd"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york-v4/ui/tooltip"
export function KbdDemo() {
return (
<div className="flex max-w-xs flex-col items-start gap-4">
<div className="flex items-center gap-2">
<Kbd>Ctrl</Kbd>
<Kbd>K</Kbd>
<Kbd>Ctrl + B</Kbd>
</div>
<div className="flex items-center gap-2">
<Kbd></Kbd>
<Kbd>C</Kbd>
</div>
<KbdGroup>
<Kbd>Ctrl</Kbd>
<Kbd>Shift</Kbd>
<Kbd>P</Kbd>
</KbdGroup>
<div className="flex items-center gap-2">
<Kbd></Kbd>
<Kbd></Kbd>
<Kbd></Kbd>
<Kbd></Kbd>
</div>
<KbdGroup>
<Kbd>
<CommandIcon />
</Kbd>
<Kbd>
<IconArrowLeft />
</Kbd>
<Kbd>
<IconArrowRight />
</Kbd>
</KbdGroup>
<KbdGroup>
<Kbd>
<IconArrowLeft />
Left
</Kbd>
<Kbd>
<WavesIcon />
Voice Enabled
</Kbd>
</KbdGroup>
<InputGroup>
<InputGroupInput />
<InputGroupAddon>
<Kbd>Space</Kbd>
</InputGroupAddon>
</InputGroup>
<ButtonGroup>
<Tooltip>
<TooltipTrigger asChild>
<Button size="sm" variant="outline">
Save
</Button>
</TooltipTrigger>
<TooltipContent>
<div className="flex items-center gap-2">
Save Changes <Kbd>S</Kbd>
</div>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button size="sm" variant="outline">
Print
</Button>
</TooltipTrigger>
<TooltipContent>
<div className="flex items-center gap-2">
Print Document{" "}
<KbdGroup>
<Kbd>Ctrl</Kbd>
<Kbd>P</Kbd>
</KbdGroup>
</div>
</TooltipContent>
</Tooltip>
</ButtonGroup>
<Kbd>
<samp>File</samp>
</Kbd>
</div>
)
}

View File

@@ -1,27 +0,0 @@
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 { Textarea } from "@/registry/new-york-v4/ui/textarea"
export function LabelDemo() {
return (
<div className="grid w-full max-w-sm gap-6">
<div className="flex items-center gap-3">
<Checkbox id="label-demo-terms" />
<Label htmlFor="label-demo-terms">Accept terms and conditions</Label>
</div>
<div className="grid gap-3">
<Label htmlFor="label-demo-username">Username</Label>
<Input id="label-demo-username" placeholder="Username" />
</div>
<div className="group grid gap-3" data-disabled={true}>
<Label htmlFor="label-demo-disabled">Disabled</Label>
<Input id="label-demo-disabled" placeholder="Disabled" disabled />
</div>
<div className="grid gap-3">
<Label htmlFor="label-demo-message">Message</Label>
<Textarea id="label-demo-message" placeholder="Message" />
</div>
</div>
)
}

View File

@@ -1,130 +0,0 @@
import { HelpCircleIcon, SettingsIcon, Trash2Icon } from "lucide-react"
import {
Menubar,
MenubarCheckboxItem,
MenubarContent,
MenubarGroup,
MenubarItem,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/registry/new-york-v4/ui/menubar"
export function MenubarDemo() {
return (
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>
New Tab <MenubarShortcut>T</MenubarShortcut>
</MenubarItem>
<MenubarItem>
New Window <MenubarShortcut>N</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>New Incognito Window</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>Share</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Email link</MenubarItem>
<MenubarItem>Messages</MenubarItem>
<MenubarItem>Notes</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem>
Print... <MenubarShortcut>P</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Edit</MenubarTrigger>
<MenubarContent>
<MenubarItem>
Undo <MenubarShortcut>Z</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Redo <MenubarShortcut>Z</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>Find</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Search the web</MenubarItem>
<MenubarSeparator />
<MenubarItem>Find...</MenubarItem>
<MenubarItem>Find Next</MenubarItem>
<MenubarItem>Find Previous</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem>Cut</MenubarItem>
<MenubarItem>Copy</MenubarItem>
<MenubarItem>Paste</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent>
<MenubarCheckboxItem>Always Show Bookmarks Bar</MenubarCheckboxItem>
<MenubarCheckboxItem checked>
Always Show Full URLs
</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarItem inset>
Reload <MenubarShortcut>R</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled inset>
Force Reload <MenubarShortcut>R</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem inset>Toggle Fullscreen</MenubarItem>
<MenubarSeparator />
<MenubarItem inset>Hide Sidebar</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Profiles</MenubarTrigger>
<MenubarContent>
<MenubarRadioGroup value="benoit">
<MenubarRadioItem value="andy">Andy</MenubarRadioItem>
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
<MenubarRadioItem value="Luis">Luis</MenubarRadioItem>
</MenubarRadioGroup>
<MenubarSeparator />
<MenubarItem inset>Edit...</MenubarItem>
<MenubarSeparator />
<MenubarItem inset>Add Profile...</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>More</MenubarTrigger>
<MenubarContent>
<MenubarGroup>
<MenubarItem>
<SettingsIcon />
Settings
</MenubarItem>
<MenubarItem>
<HelpCircleIcon />
Help
</MenubarItem>
<MenubarSeparator />
<MenubarItem variant="destructive">
<Trash2Icon />
Delete
</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
</Menubar>
)
}

View File

@@ -1,135 +0,0 @@
import {
NativeSelect,
NativeSelectOptGroup,
NativeSelectOption,
} from "@/registry/new-york-v4/ui/native-select"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
export function NativeSelectDemo() {
return (
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-3">
<div className="text-sm font-medium text-muted-foreground">
Basic Select
</div>
<div className="flex flex-col gap-4">
<NativeSelect>
<NativeSelectOption value="">Select a fruit</NativeSelectOption>
<NativeSelectOption value="apple">Apple</NativeSelectOption>
<NativeSelectOption value="banana">Banana</NativeSelectOption>
<NativeSelectOption value="blueberry">Blueberry</NativeSelectOption>
<NativeSelectOption value="grapes" disabled>
Grapes
</NativeSelectOption>
<NativeSelectOption value="pineapple">Pineapple</NativeSelectOption>
</NativeSelect>
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
<SelectItem value="blueberry">Blueberry</SelectItem>
<SelectItem value="grapes" disabled>
Grapes
</SelectItem>
<SelectItem value="pineapple">Pineapple</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex flex-col gap-3">
<div className="text-sm font-medium text-muted-foreground">
With Groups
</div>
<div className="flex flex-col gap-4">
<NativeSelect>
<NativeSelectOption value="">Select a food</NativeSelectOption>
<NativeSelectOptGroup label="Fruits">
<NativeSelectOption value="apple">Apple</NativeSelectOption>
<NativeSelectOption value="banana">Banana</NativeSelectOption>
<NativeSelectOption value="blueberry">
Blueberry
</NativeSelectOption>
</NativeSelectOptGroup>
<NativeSelectOptGroup label="Vegetables">
<NativeSelectOption value="carrot">Carrot</NativeSelectOption>
<NativeSelectOption value="broccoli">Broccoli</NativeSelectOption>
<NativeSelectOption value="spinach">Spinach</NativeSelectOption>
</NativeSelectOptGroup>
</NativeSelect>
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a food" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
<SelectItem value="blueberry">Blueberry</SelectItem>
</SelectGroup>
<SelectGroup>
<SelectLabel>Vegetables</SelectLabel>
<SelectItem value="carrot">Carrot</SelectItem>
<SelectItem value="broccoli">Broccoli</SelectItem>
<SelectItem value="spinach">Spinach</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
<div className="flex flex-col gap-3">
<div className="text-sm font-medium text-muted-foreground">
Disabled State
</div>
<div className="flex flex-col gap-4">
<NativeSelect disabled>
<NativeSelectOption value="">Disabled</NativeSelectOption>
<NativeSelectOption value="apple">Apple</NativeSelectOption>
<NativeSelectOption value="banana">Banana</NativeSelectOption>
</NativeSelect>
<Select disabled>
<SelectTrigger>
<SelectValue placeholder="Disabled" />
</SelectTrigger>
<SelectContent>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex flex-col gap-3">
<div className="text-sm font-medium text-muted-foreground">
Error State
</div>
<div className="flex flex-col gap-4">
<NativeSelect aria-invalid="true">
<NativeSelectOption value="">Error state</NativeSelectOption>
<NativeSelectOption value="apple">Apple</NativeSelectOption>
<NativeSelectOption value="banana">Banana</NativeSelectOption>
</NativeSelect>
<Select>
<SelectTrigger aria-invalid="true">
<SelectValue placeholder="Error state" />
</SelectTrigger>
<SelectContent>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
)
}

View File

@@ -1,227 +0,0 @@
import * as React from "react"
import Link from "next/link"
import { CircleCheckIcon, CircleHelpIcon, CircleIcon } from "lucide-react"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/registry/new-york-v4/ui/navigation-menu"
const components: { title: string; href: string; description: string }[] = [
{
title: "Alert Dialog",
href: "/docs/primitives/alert-dialog",
description:
"A modal dialog that interrupts the user with important content and expects a response.",
},
{
title: "Hover Card",
href: "/docs/primitives/hover-card",
description:
"For sighted users to preview content available behind a link.",
},
{
title: "Progress",
href: "/docs/primitives/progress",
description:
"Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.",
},
{
title: "Scroll-area",
href: "/docs/primitives/scroll-area",
description: "Visually or semantically separates content.",
},
{
title: "Tabs",
href: "/docs/primitives/tabs",
description:
"A set of layered sections of content—known as tab panels—that are displayed one at a time.",
},
{
title: "Tooltip",
href: "/docs/primitives/tooltip",
description:
"A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.",
},
]
export function NavigationMenuDemo() {
return (
<div className="hidden w-full flex-col items-center justify-center gap-6 @xl:flex">
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Getting started</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid gap-2 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]">
<li className="row-span-3">
<NavigationMenuLink asChild>
<a
className="flex h-full w-full flex-col justify-end rounded-md bg-linear-to-b from-muted/50 to-muted p-6 no-underline outline-hidden select-none focus:shadow-md"
href="/"
>
<div className="mt-4 mb-2 text-lg font-medium">
shadcn/ui
</div>
<p className="text-sm leading-tight text-muted-foreground">
Beautifully designed components built with Tailwind CSS.
</p>
</a>
</NavigationMenuLink>
</li>
<ListItem href="/docs" title="Introduction">
Re-usable components built using Radix UI and Tailwind CSS.
</ListItem>
<ListItem href="/docs/installation" title="Installation">
How to install dependencies and structure your app.
</ListItem>
<ListItem href="/docs/primitives/typography" title="Typography">
Styles for headings, paragraphs, lists...etc
</ListItem>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>Components</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] gap-2 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
{components.map((component) => (
<ListItem
key={component.title}
title={component.title}
href={component.href}
>
{component.description}
</ListItem>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
asChild
className={navigationMenuTriggerStyle()}
>
<Link href="/docs">Documentation</Link>
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
<NavigationMenu viewport={false}>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuLink
asChild
className={navigationMenuTriggerStyle()}
>
<Link href="/docs">Documentation</Link>
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>List</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[300px] gap-4">
<li>
<NavigationMenuLink asChild>
<Link href="#">
<div className="font-medium">Components</div>
<div className="text-muted-foreground">
Browse all components in the library.
</div>
</Link>
</NavigationMenuLink>
<NavigationMenuLink asChild>
<Link href="#">
<div className="font-medium">Documentation</div>
<div className="text-muted-foreground">
Learn how to use the library.
</div>
</Link>
</NavigationMenuLink>
<NavigationMenuLink asChild>
<Link href="#">
<div className="font-medium">Blog</div>
<div className="text-muted-foreground">
Read our latest blog posts.
</div>
</Link>
</NavigationMenuLink>
</li>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>Simple List</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[200px] gap-4">
<li>
<NavigationMenuLink asChild>
<Link href="#">Components</Link>
</NavigationMenuLink>
<NavigationMenuLink asChild>
<Link href="#">Documentation</Link>
</NavigationMenuLink>
<NavigationMenuLink asChild>
<Link href="#">Blocks</Link>
</NavigationMenuLink>
</li>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>With Icon</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[200px] gap-4">
<li>
<NavigationMenuLink asChild>
<Link href="#" className="flex-row items-center gap-2">
<CircleHelpIcon />
Backlog
</Link>
</NavigationMenuLink>
<NavigationMenuLink asChild>
<Link href="#" className="flex-row items-center gap-2">
<CircleIcon />
To Do
</Link>
</NavigationMenuLink>
<NavigationMenuLink asChild>
<Link href="#" className="flex-row items-center gap-2">
<CircleCheckIcon />
Done
</Link>
</NavigationMenuLink>
</li>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</div>
)
}
function ListItem({
title,
children,
href,
...props
}: React.ComponentPropsWithoutRef<"li"> & { href: string }) {
return (
<li {...props}>
<NavigationMenuLink asChild>
<Link href={href}>
<div className="text-sm leading-none font-medium">{title}</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
{children}
</p>
</Link>
</NavigationMenuLink>
</li>
)
}

View File

@@ -1,40 +0,0 @@
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/registry/new-york-v4/ui/pagination"
export function PaginationDemo() {
return (
<div className="flex flex-col gap-6">
<Pagination>
<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>
</div>
)
}

View File

@@ -1,67 +0,0 @@
import { Button } from "@/registry/new-york-v4/ui/button"
import { Input } from "@/registry/new-york-v4/ui/input"
import { Label } from "@/registry/new-york-v4/ui/label"
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
export function PopoverDemo() {
return (
<div className="flex gap-4">
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">Open popover</Button>
</PopoverTrigger>
<PopoverContent className="w-80" align="start">
<div className="grid gap-4">
<PopoverHeader>
<PopoverTitle>Dimensions</PopoverTitle>
<PopoverDescription>
Set the dimensions for the layer.
</PopoverDescription>
</PopoverHeader>
<div className="grid gap-2">
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="width">Width</Label>
<Input
id="width"
defaultValue="100%"
className="col-span-2 h-8"
/>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="maxWidth">Max. width</Label>
<Input
id="maxWidth"
defaultValue="300px"
className="col-span-2 h-8"
/>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="height">Height</Label>
<Input
id="height"
defaultValue="25px"
className="col-span-2 h-8"
/>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="maxHeight">Max. height</Label>
<Input
id="maxHeight"
defaultValue="none"
className="col-span-2 h-8"
/>
</div>
</div>
</div>
</PopoverContent>
</Popover>
</div>
)
}

View File

@@ -1,16 +0,0 @@
"use client"
import * as React from "react"
import { Progress } from "@/registry/new-york-v4/ui/progress"
export function ProgressDemo() {
const [progress, setProgress] = React.useState(13)
React.useEffect(() => {
const timer = setTimeout(() => setProgress(66), 500)
return () => clearTimeout(timer)
}, [])
return <Progress value={progress} className="w-[60%]" />
}

View File

@@ -1,62 +0,0 @@
import { Label } from "@/registry/new-york-v4/ui/label"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/new-york-v4/ui/radio-group"
const plans = [
{
id: "starter",
name: "Starter Plan",
description:
"Perfect for small businesses getting started with our platform",
price: "$10",
},
{
id: "pro",
name: "Pro Plan",
description: "Advanced features for growing businesses with higher demands",
price: "$20",
},
] as const
export function RadioGroupDemo() {
return (
<div className="flex flex-col gap-6">
<RadioGroup defaultValue="comfortable">
<div className="flex items-center gap-3">
<RadioGroupItem value="default" id="r1" />
<Label htmlFor="r1">Default</Label>
</div>
<div className="flex items-center gap-3">
<RadioGroupItem value="comfortable" id="r2" />
<Label htmlFor="r2">Comfortable</Label>
</div>
<div className="flex items-center gap-3">
<RadioGroupItem value="compact" id="r3" />
<Label htmlFor="r3">Compact</Label>
</div>
</RadioGroup>
<RadioGroup defaultValue="starter" className="max-w-sm">
{plans.map((plan) => (
<Label
className="flex items-start gap-3 rounded-lg border p-4 hover:bg-accent/50 has-[[data-state=checked]]:border-green-600 has-[[data-state=checked]]:bg-green-50 dark:has-[[data-state=checked]]:border-green-900 dark:has-[[data-state=checked]]:bg-green-950"
key={plan.id}
>
<RadioGroupItem
value={plan.id}
id={plan.name}
className="shadow-none data-[state=checked]:border-green-600 data-[state=checked]:bg-green-600 *:data-[slot=radio-group-indicator]:[&>svg]:fill-white *:data-[slot=radio-group-indicator]:[&>svg]:stroke-white"
/>
<div className="grid gap-1 font-normal">
<div className="font-medium">{plan.name}</div>
<div className="leading-snug text-muted-foreground">
{plan.description}
</div>
</div>
</Label>
))}
</RadioGroup>
</div>
)
}

View File

@@ -1,70 +0,0 @@
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/registry/new-york-v4/ui/resizable"
export function ResizableDemo() {
return (
<div className="flex w-full flex-col gap-6">
<ResizablePanelGroup
orientation="horizontal"
className="max-w-md rounded-lg border md:min-w-[450px]"
>
<ResizablePanel defaultSize="50%">
<div className="flex h-[200px] items-center justify-center p-6">
<span className="font-semibold">One</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="50%">
<ResizablePanelGroup orientation="vertical">
<ResizablePanel defaultSize="25%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Two</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="75%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Three</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</ResizablePanel>
</ResizablePanelGroup>
<ResizablePanelGroup
orientation="horizontal"
className="min-h-[200px] max-w-md rounded-lg border md:min-w-[450px]"
>
<ResizablePanel defaultSize="25%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Sidebar</span>
</div>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize="75%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Content</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
<ResizablePanelGroup
orientation="vertical"
className="min-h-[200px] max-w-md rounded-lg border md:min-w-[450px]"
>
<ResizablePanel defaultSize="25%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Header</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="75%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Content</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</div>
)
}

View File

@@ -1,80 +0,0 @@
import * as React from "react"
import Image from "next/image"
import { ScrollArea, ScrollBar } from "@/registry/new-york-v4/ui/scroll-area"
import { Separator } from "@/registry/new-york-v4/ui/separator"
export function ScrollAreaDemo() {
return (
<div className="flex flex-col gap-6">
<ScrollAreaVertical />
<ScrollAreaHorizontalDemo />
</div>
)
}
const tags = Array.from({ length: 50 }).map(
(_, i, a) => `v1.2.0-beta.${a.length - i}`
)
function ScrollAreaVertical() {
return (
<div className="flex flex-col gap-6">
<ScrollArea className="h-72 w-48 rounded-md border">
<div className="p-4">
<h4 className="mb-4 text-sm leading-none font-medium">Tags</h4>
{tags.map((tag) => (
<React.Fragment key={tag}>
<div className="text-sm">{tag}</div>
<Separator className="my-2" />
</React.Fragment>
))}
</div>
</ScrollArea>
</div>
)
}
export const works = [
{
artist: "Ornella Binni",
art: "https://images.unsplash.com/photo-1465869185982-5a1a7522cbcb?auto=format&fit=crop&w=300&q=80",
},
{
artist: "Tom Byrom",
art: "https://images.unsplash.com/photo-1548516173-3cabfa4607e9?auto=format&fit=crop&w=300&q=80",
},
{
artist: "Vladimir Malyav",
art: "https://images.unsplash.com/photo-1494337480532-3725c85fd2ab?auto=format&fit=crop&w=300&q=80",
},
] as const
function ScrollAreaHorizontalDemo() {
return (
<ScrollArea className="w-full max-w-96 rounded-md border p-4">
<div className="flex gap-4">
{works.map((artwork) => (
<figure key={artwork.artist} className="shrink-0">
<div className="overflow-hidden rounded-md">
<Image
src={artwork.art}
alt={`Photo by ${artwork.artist}`}
className="aspect-[3/4] h-fit w-fit object-cover"
width={300}
height={400}
/>
</div>
<figcaption className="pt-2 text-xs text-muted-foreground">
Photo by{" "}
<span className="font-semibold text-foreground">
{artwork.artist}
</span>
</figcaption>
</figure>
))}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
)
}

View File

@@ -1,93 +0,0 @@
import * as React from "react"
import {
ChartBarIcon,
ChartLineIcon,
ChartPieIcon,
CircleDashed,
} from "lucide-react"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
export function SelectDemo() {
return (
<div className="flex flex-wrap items-start gap-4">
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
<SelectItem value="blueberry">Blueberry</SelectItem>
<SelectItem value="grapes" disabled>
Grapes
</SelectItem>
<SelectItem value="pineapple">Pineapple</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Large List" />
</SelectTrigger>
<SelectContent>
{Array.from({ length: 100 }).map((_, i) => (
<SelectItem key={i} value={`item-${i}`}>
Item {i}
</SelectItem>
))}
</SelectContent>
</Select>
<Select disabled>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Disabled" />
</SelectTrigger>
<SelectContent>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
<SelectItem value="blueberry">Blueberry</SelectItem>
<SelectItem value="grapes" disabled>
Grapes
</SelectItem>
<SelectItem value="pineapple">Pineapple</SelectItem>
</SelectContent>
</Select>
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue
placeholder={
<>
<CircleDashed className="text-muted-foreground" />
With Icon
</>
}
/>
</SelectTrigger>
<SelectContent>
<SelectItem value="line">
<ChartLineIcon />
Line
</SelectItem>
<SelectItem value="bar">
<ChartBarIcon />
Bar
</SelectItem>
<SelectItem value="pie">
<ChartPieIcon />
Pie
</SelectItem>
</SelectContent>
</Select>
</div>
)
}

View File

@@ -1,22 +0,0 @@
import { Separator } from "@/registry/new-york-v4/ui/separator"
export function SeparatorDemo() {
return (
<div>
<div className="flex flex-col gap-1">
<div className="text-sm leading-none font-medium">Tailwind CSS</div>
<div className="text-sm text-muted-foreground">
A utility-first CSS framework.
</div>
</div>
<Separator className="my-4" />
<div className="flex h-5 items-center gap-4 text-sm">
<div>Blog</div>
<Separator orientation="vertical" />
<div>Docs</div>
<Separator orientation="vertical" />
<div>Source</div>
</div>
</div>
)
}

View File

@@ -1,116 +0,0 @@
import { Button } from "@/registry/new-york-v4/ui/button"
import { Input } from "@/registry/new-york-v4/ui/input"
import { Label } from "@/registry/new-york-v4/ui/label"
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/registry/new-york-v4/ui/sheet"
const SHEET_SIDES = ["top", "right", "bottom", "left"] as const
export function SheetDemo() {
return (
<div className="flex flex-col gap-6 md:flex-row">
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Open</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>
Make changes to your profile here. Click save when you&apos;re
done.
</SheetDescription>
</SheetHeader>
<div className="grid flex-1 auto-rows-min gap-6 px-4">
<div className="grid gap-3">
<Label htmlFor="sheet-demo-name">Name</Label>
<Input id="sheet-demo-name" defaultValue="Pedro Duarte" />
</div>
<div className="grid gap-3">
<Label htmlFor="sheet-demo-username">Username</Label>
<Input id="sheet-demo-username" defaultValue="@peduarte" />
</div>
</div>
<SheetFooter>
<Button type="submit">Save changes</Button>
<SheetClose asChild>
<Button variant="outline">Close</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">No Close Button</Button>
</SheetTrigger>
<SheetContent showCloseButton={false}>
<SheetHeader>
<SheetTitle>Custom Close</SheetTitle>
<SheetDescription>
This sheet has no default close button. Use the footer buttons
instead.
</SheetDescription>
</SheetHeader>
<div className="flex-1 px-4" />
<SheetFooter>
<SheetClose asChild>
<Button variant="outline">Cancel</Button>
</SheetClose>
<Button type="submit">Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<div className="flex gap-2">
{SHEET_SIDES.map((side) => (
<Sheet key={side}>
<SheetTrigger asChild>
<Button variant="outline" className="capitalize">
{side}
</Button>
</SheetTrigger>
<SheetContent side={side}>
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>
Make changes to your profile here. Click save when you&apos;re
done.
</SheetDescription>
</SheetHeader>
<div className="overflow-y-auto px-4 text-sm">
<h4 className="mb-4 text-lg leading-none font-medium">
Lorem Ipsum
</h4>
{Array.from({ length: 10 }).map((_, index) => (
<p key={index} className="mb-4 leading-normal">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
</p>
))}
</div>
<SheetFooter>
<Button type="submit">Save changes</Button>
<SheetClose asChild>
<Button variant="outline">Cancel</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
))}
</div>
</div>
)
}

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