mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-07-08 14:35:09 +00:00
refactor: clean up unused files (#10227)
* refactor: clean up unused files * fix
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
ChartLineIcon,
|
||||
FileIcon,
|
||||
HomeIcon,
|
||||
LifeBuoy,
|
||||
Send,
|
||||
Settings2Icon,
|
||||
ShoppingBagIcon,
|
||||
ShoppingCartIcon,
|
||||
UserIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Sidebar, SidebarContent } from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { NavMain } from "@/app/(examples)/dashboard-03/components/nav-main"
|
||||
import { NavSecondary } from "@/app/(examples)/dashboard-03/components/nav-secondary"
|
||||
|
||||
const data = {
|
||||
navMain: [
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "/dashboard",
|
||||
icon: HomeIcon,
|
||||
},
|
||||
{
|
||||
title: "Analytics",
|
||||
url: "/dashboard/analytics",
|
||||
icon: ChartLineIcon,
|
||||
},
|
||||
{
|
||||
title: "Orders",
|
||||
url: "/dashboard/orders",
|
||||
icon: ShoppingBagIcon,
|
||||
},
|
||||
{
|
||||
title: "Products",
|
||||
url: "/dashboard/products",
|
||||
icon: ShoppingCartIcon,
|
||||
},
|
||||
{
|
||||
title: "Invoices",
|
||||
url: "/dashboard/invoices",
|
||||
icon: FileIcon,
|
||||
},
|
||||
{
|
||||
title: "Customers",
|
||||
url: "/dashboard/customers",
|
||||
icon: UserIcon,
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
url: "/dashboard/settings",
|
||||
icon: Settings2Icon,
|
||||
},
|
||||
],
|
||||
navSecondary: [
|
||||
{
|
||||
title: "Support",
|
||||
url: "#",
|
||||
icon: LifeBuoy,
|
||||
},
|
||||
{
|
||||
title: "Feedback",
|
||||
url: "#",
|
||||
icon: Send,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
<Sidebar
|
||||
className="top-(--header-height) h-[calc(100svh-var(--header-height))]!"
|
||||
{...props}
|
||||
>
|
||||
<SidebarContent>
|
||||
<NavMain items={data.navMain} />
|
||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||
</SidebarContent>
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { TrendingUp } from "lucide-react"
|
||||
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { MoonIcon, SunIcon } from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export function ModeToggle() {
|
||||
const { setTheme, resolvedTheme } = useTheme()
|
||||
|
||||
const toggleTheme = React.useCallback(() => {
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
}, [resolvedTheme, setTheme])
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="group/toggle size-8"
|
||||
onClick={toggleTheme}
|
||||
>
|
||||
<SunIcon className="hidden [html.dark_&]:block" />
|
||||
<MoonIcon className="hidden [html.light_&]:block" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { usePathname } from "next/navigation"
|
||||
import { ChevronRight, type LucideIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/registry/new-york-v4/ui/collapsible"
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavMain({
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
isActive?: boolean
|
||||
items?: {
|
||||
title: string
|
||||
url: string
|
||||
}[]
|
||||
disabled?: boolean
|
||||
}[]
|
||||
}) {
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Dashboard</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<Collapsible key={item.title} asChild defaultOpen={item.isActive}>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
tooltip={item.title}
|
||||
isActive={pathname === item.url}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
<a
|
||||
href={item.disabled ? "#" : item.url}
|
||||
data-disabled={item.disabled}
|
||||
className="data-[disabled=true]:opacity-50"
|
||||
>
|
||||
<item.icon className="text-muted-foreground" />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
{item.items?.length ? (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
||||
<ChevronRight />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{item.items?.map((subItem) => (
|
||||
<SidebarMenuSubItem key={subItem.title}>
|
||||
<SidebarMenuSubButton asChild>
|
||||
<a href={subItem.url}>
|
||||
<span>{subItem.title}</span>
|
||||
</a>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
) : null}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import * as React from "react"
|
||||
import { type LucideIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavSecondary({
|
||||
items,
|
||||
...props
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
}[]
|
||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild size="sm">
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { BadgeCheck, Bell, CreditCard, LogOut, Sparkles } from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
|
||||
export function NavUser({
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
name: string
|
||||
email: string
|
||||
avatar: string
|
||||
}
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Avatar className="size-8 rounded-md">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
side="bottom"
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Search } from "lucide-react"
|
||||
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import { SidebarInput } from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function SearchForm({ ...props }: React.ComponentProps<"form">) {
|
||||
return (
|
||||
<form {...props}>
|
||||
<div className="relative">
|
||||
<Label htmlFor="search" className="sr-only">
|
||||
Search
|
||||
</Label>
|
||||
<SidebarInput
|
||||
id="search"
|
||||
placeholder="Type to search..."
|
||||
className="h-8 pl-7"
|
||||
/>
|
||||
<Search className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" />
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { Fragment, useMemo } from "react"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { SidebarIcon } from "lucide-react"
|
||||
|
||||
import { ThemeSelector } from "@/components/theme-selector"
|
||||
import { SearchForm } from "@/registry/new-york-v4/blocks/sidebar-16/components/search-form"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/registry/new-york-v4/ui/breadcrumb"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import { useSidebar } from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { ModeToggle } from "@/app/(examples)/dashboard-03/components/mode-toggle"
|
||||
import { NavUser } from "@/app/(examples)/dashboard-03/components/nav-user"
|
||||
|
||||
export function SiteHeader() {
|
||||
const { toggleSidebar } = useSidebar()
|
||||
const pathname = usePathname()
|
||||
|
||||
// Faux breadcrumbs for demo.
|
||||
const breadcrumbs = useMemo(() => {
|
||||
return pathname
|
||||
.split("/")
|
||||
.filter((path) => path !== "")
|
||||
.map((path, index, array) => ({
|
||||
label: path,
|
||||
href: `/${array.slice(0, index + 1).join("/")}`,
|
||||
}))
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<header
|
||||
data-slot="site-header"
|
||||
className="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>
|
||||
)
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export default function CustomersPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="bg-input p-4">Input</div>
|
||||
<div className="bg-input/30 p-4">Input 50</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
IconCamera,
|
||||
IconChartBar,
|
||||
IconDashboard,
|
||||
IconDatabase,
|
||||
IconFileAi,
|
||||
IconFileDescription,
|
||||
IconFileWord,
|
||||
IconFolder,
|
||||
IconHelp,
|
||||
IconInnerShadowTop,
|
||||
IconListDetails,
|
||||
IconReport,
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
IconUsers,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { NavDocuments } from "@/app/(examples)/dashboard/components/nav-documents"
|
||||
import { NavMain } from "@/app/(examples)/dashboard/components/nav-main"
|
||||
import { NavSecondary } from "@/app/(examples)/dashboard/components/nav-secondary"
|
||||
import { NavUser } from "@/app/(examples)/dashboard/components/nav-user"
|
||||
|
||||
const data = {
|
||||
user: {
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
avatar: "/avatars/shadcn.jpg",
|
||||
},
|
||||
navMain: [
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "#",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
{
|
||||
title: "Lifecycle",
|
||||
url: "#",
|
||||
icon: IconListDetails,
|
||||
},
|
||||
{
|
||||
title: "Analytics",
|
||||
url: "#",
|
||||
icon: IconChartBar,
|
||||
},
|
||||
{
|
||||
title: "Projects",
|
||||
url: "#",
|
||||
icon: IconFolder,
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
url: "#",
|
||||
icon: IconUsers,
|
||||
},
|
||||
],
|
||||
navClouds: [
|
||||
{
|
||||
title: "Capture",
|
||||
icon: IconCamera,
|
||||
isActive: true,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Proposal",
|
||||
icon: IconFileDescription,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Prompts",
|
||||
icon: IconFileAi,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
navSecondary: [
|
||||
{
|
||||
title: "Settings",
|
||||
url: "#",
|
||||
icon: IconSettings,
|
||||
},
|
||||
{
|
||||
title: "Get Help",
|
||||
url: "#",
|
||||
icon: IconHelp,
|
||||
},
|
||||
{
|
||||
title: "Search",
|
||||
url: "#",
|
||||
icon: IconSearch,
|
||||
},
|
||||
],
|
||||
documents: [
|
||||
{
|
||||
name: "Data Library",
|
||||
url: "#",
|
||||
icon: IconDatabase,
|
||||
},
|
||||
{
|
||||
name: "Reports",
|
||||
url: "#",
|
||||
icon: IconReport,
|
||||
},
|
||||
{
|
||||
name: "Word Assistant",
|
||||
url: "#",
|
||||
icon: IconFileWord,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
<Sidebar collapsible="offcanvas" {...props}>
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
className="data-[slot=sidebar-menu-button]:p-1.5!"
|
||||
>
|
||||
<a href="#">
|
||||
<IconInnerShadowTop className="size-5!" />
|
||||
<span className="text-base font-semibold">Acme Inc.</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<NavMain items={data.navMain} />
|
||||
<NavDocuments items={data.documents} />
|
||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<NavUser user={data.user} />
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
@@ -1,292 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||
|
||||
import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile"
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/registry/new-york-v4/ui/card"
|
||||
import {
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { IconBrightness } from "@tabler/icons-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export function ModeToggle() {
|
||||
const { setTheme, resolvedTheme } = useTheme()
|
||||
|
||||
const toggleTheme = React.useCallback(() => {
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
}, [resolvedTheme, setTheme])
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="group/toggle size-8"
|
||||
onClick={toggleTheme}
|
||||
>
|
||||
<IconBrightness />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
IconDots,
|
||||
IconFolder,
|
||||
IconShare3,
|
||||
IconTrash,
|
||||
type Icon,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavDocuments({
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
name: string
|
||||
url: string
|
||||
icon: Icon
|
||||
}[]
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
|
||||
return (
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarGroupLabel>Documents</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.name}>
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.name}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuAction
|
||||
showOnHover
|
||||
className="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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { IconBrightness, type Icon } from "@tabler/icons-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { Skeleton } from "@/registry/new-york-v4/ui/skeleton"
|
||||
import { Switch } from "@/registry/new-york-v4/ui/switch"
|
||||
|
||||
export function NavSecondary({
|
||||
items,
|
||||
...props
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: Icon
|
||||
}[]
|
||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||
const { resolvedTheme, setTheme } = useTheme()
|
||||
const [mounted, setMounted] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
<SidebarMenuItem className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarMenuButton asChild>
|
||||
<label>
|
||||
<IconBrightness />
|
||||
<span>Dark Mode</span>
|
||||
{mounted ? (
|
||||
<Switch
|
||||
className="ml-auto"
|
||||
checked={resolvedTheme !== "light"}
|
||||
onCheckedChange={() =>
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton className="ml-auto h-4 w-8 rounded-full" />
|
||||
)}
|
||||
</label>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
IconCreditCard,
|
||||
IconDotsVertical,
|
||||
IconLogout,
|
||||
IconNotification,
|
||||
IconUserCircle,
|
||||
} from "@tabler/icons-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
export function NavUser({
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
name: string
|
||||
email: string
|
||||
avatar: string
|
||||
}
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="h-8 w-8 rounded-lg grayscale">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||
import { SidebarTrigger } from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { ModeToggle } from "@/app/(examples)/dashboard/components/mode-toggle"
|
||||
import { ThemeSelector } from "@/app/(examples)/dashboard/components/theme-selector"
|
||||
|
||||
export function SiteHeader() {
|
||||
return (
|
||||
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
|
||||
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="mx-2 data-[orientation=vertical]:h-4"
|
||||
/>
|
||||
<h1 className="text-base font-medium">Documents</h1>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Button variant="ghost" asChild size="sm" className="hidden sm:flex">
|
||||
<a
|
||||
href="https://github.com/shadcn-ui/ui/tree/main/apps/v4/app/(examples)/dashboard"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="dark:text-foreground"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</Button>
|
||||
<ThemeSelector />
|
||||
<ModeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useThemeConfig } from "@/components/active-theme"
|
||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/registry/new-york-v4/ui/select"
|
||||
|
||||
const DEFAULT_THEMES = [
|
||||
{
|
||||
name: "Default",
|
||||
value: "default",
|
||||
},
|
||||
{
|
||||
name: "Blue",
|
||||
value: "blue",
|
||||
},
|
||||
{
|
||||
name: "Green",
|
||||
value: "green",
|
||||
},
|
||||
{
|
||||
name: "Amber",
|
||||
value: "amber",
|
||||
},
|
||||
]
|
||||
|
||||
const SCALED_THEMES = [
|
||||
{
|
||||
name: "Default",
|
||||
value: "default-scaled",
|
||||
},
|
||||
{
|
||||
name: "Blue",
|
||||
value: "blue-scaled",
|
||||
},
|
||||
]
|
||||
|
||||
const MONO_THEMES = [
|
||||
{
|
||||
name: "Mono",
|
||||
value: "mono-scaled",
|
||||
},
|
||||
]
|
||||
|
||||
export function ThemeSelector() {
|
||||
const { activeTheme, setActiveTheme } = useThemeConfig()
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="theme-selector" className="sr-only">
|
||||
Theme
|
||||
</Label>
|
||||
<Select value={activeTheme} onValueChange={setActiveTheme}>
|
||||
<SelectTrigger
|
||||
id="theme-selector"
|
||||
size="sm"
|
||||
className="justify-start *:data-[slot=select-value]:w-12"
|
||||
>
|
||||
<span className="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>
|
||||
)
|
||||
}
|
||||
@@ -1,614 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"header": "Cover page",
|
||||
"type": "Cover page",
|
||||
"status": "In Process",
|
||||
"target": "18",
|
||||
"limit": "5",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"header": "Table of contents",
|
||||
"type": "Table of contents",
|
||||
"status": "Done",
|
||||
"target": "29",
|
||||
"limit": "24",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"header": "Executive summary",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "10",
|
||||
"limit": "13",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"header": "Technical approach",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "27",
|
||||
"limit": "23",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"header": "Design",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "2",
|
||||
"limit": "16",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"header": "Capabilities",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "20",
|
||||
"limit": "8",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"header": "Integration with existing systems",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "19",
|
||||
"limit": "21",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"header": "Innovation and Advantages",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "25",
|
||||
"limit": "26",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"header": "Overview of EMR's Innovative Solutions",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "7",
|
||||
"limit": "23",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"header": "Advanced Algorithms and Machine Learning",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "30",
|
||||
"limit": "28",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"header": "Adaptive Communication Protocols",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "9",
|
||||
"limit": "31",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"header": "Advantages Over Current Technologies",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "12",
|
||||
"limit": "0",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"header": "Past Performance",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "22",
|
||||
"limit": "33",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"header": "Customer Feedback and Satisfaction Levels",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "15",
|
||||
"limit": "34",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"header": "Implementation Challenges and Solutions",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "3",
|
||||
"limit": "35",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"header": "Security Measures and Data Protection Policies",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "6",
|
||||
"limit": "36",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"header": "Scalability and Future Proofing",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "4",
|
||||
"limit": "37",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"header": "Cost-Benefit Analysis",
|
||||
"type": "Plain language",
|
||||
"status": "Done",
|
||||
"target": "14",
|
||||
"limit": "38",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"header": "User Training and Onboarding Experience",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "17",
|
||||
"limit": "39",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"header": "Future Development Roadmap",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "11",
|
||||
"limit": "40",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"header": "System Architecture Overview",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "24",
|
||||
"limit": "18",
|
||||
"reviewer": "Maya Johnson"
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"header": "Risk Management Plan",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "15",
|
||||
"limit": "22",
|
||||
"reviewer": "Carlos Rodriguez"
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"header": "Compliance Documentation",
|
||||
"type": "Legal",
|
||||
"status": "In Process",
|
||||
"target": "31",
|
||||
"limit": "27",
|
||||
"reviewer": "Sarah Chen"
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"header": "API Documentation",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "8",
|
||||
"limit": "12",
|
||||
"reviewer": "Raj Patel"
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"header": "User Interface Mockups",
|
||||
"type": "Visual",
|
||||
"status": "In Process",
|
||||
"target": "19",
|
||||
"limit": "25",
|
||||
"reviewer": "Leila Ahmadi"
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"header": "Database Schema",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "22",
|
||||
"limit": "20",
|
||||
"reviewer": "Thomas Wilson"
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"header": "Testing Methodology",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "17",
|
||||
"limit": "14",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"header": "Deployment Strategy",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "26",
|
||||
"limit": "30",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"header": "Budget Breakdown",
|
||||
"type": "Financial",
|
||||
"status": "In Process",
|
||||
"target": "13",
|
||||
"limit": "16",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"header": "Market Analysis",
|
||||
"type": "Research",
|
||||
"status": "Done",
|
||||
"target": "29",
|
||||
"limit": "32",
|
||||
"reviewer": "Sophia Martinez"
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"header": "Competitor Comparison",
|
||||
"type": "Research",
|
||||
"status": "In Process",
|
||||
"target": "21",
|
||||
"limit": "19",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"header": "Maintenance Plan",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "16",
|
||||
"limit": "23",
|
||||
"reviewer": "Alex Thompson"
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"header": "User Personas",
|
||||
"type": "Research",
|
||||
"status": "In Process",
|
||||
"target": "27",
|
||||
"limit": "24",
|
||||
"reviewer": "Nina Patel"
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"header": "Accessibility Compliance",
|
||||
"type": "Legal",
|
||||
"status": "Done",
|
||||
"target": "18",
|
||||
"limit": "21",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"header": "Performance Metrics",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "23",
|
||||
"limit": "26",
|
||||
"reviewer": "David Kim"
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"header": "Disaster Recovery Plan",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "14",
|
||||
"limit": "17",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"header": "Third-party Integrations",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "25",
|
||||
"limit": "28",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"header": "User Feedback Summary",
|
||||
"type": "Research",
|
||||
"status": "Done",
|
||||
"target": "20",
|
||||
"limit": "15",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"header": "Localization Strategy",
|
||||
"type": "Narrative",
|
||||
"status": "In Process",
|
||||
"target": "12",
|
||||
"limit": "19",
|
||||
"reviewer": "Maria Garcia"
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"header": "Mobile Compatibility",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "28",
|
||||
"limit": "31",
|
||||
"reviewer": "James Wilson"
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"header": "Data Migration Plan",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "19",
|
||||
"limit": "22",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"header": "Quality Assurance Protocols",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "30",
|
||||
"limit": "33",
|
||||
"reviewer": "Priya Singh"
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"header": "Stakeholder Analysis",
|
||||
"type": "Research",
|
||||
"status": "In Process",
|
||||
"target": "11",
|
||||
"limit": "14",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"header": "Environmental Impact Assessment",
|
||||
"type": "Research",
|
||||
"status": "Done",
|
||||
"target": "24",
|
||||
"limit": "27",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"header": "Intellectual Property Rights",
|
||||
"type": "Legal",
|
||||
"status": "In Process",
|
||||
"target": "17",
|
||||
"limit": "20",
|
||||
"reviewer": "Sarah Johnson"
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"header": "Customer Support Framework",
|
||||
"type": "Narrative",
|
||||
"status": "Done",
|
||||
"target": "22",
|
||||
"limit": "25",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"header": "Version Control Strategy",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "15",
|
||||
"limit": "18",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"header": "Continuous Integration Pipeline",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "26",
|
||||
"limit": "29",
|
||||
"reviewer": "Michael Chen"
|
||||
},
|
||||
{
|
||||
"id": 49,
|
||||
"header": "Regulatory Compliance",
|
||||
"type": "Legal",
|
||||
"status": "In Process",
|
||||
"target": "13",
|
||||
"limit": "16",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 50,
|
||||
"header": "User Authentication System",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "28",
|
||||
"limit": "31",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 51,
|
||||
"header": "Data Analytics Framework",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "21",
|
||||
"limit": "24",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 52,
|
||||
"header": "Cloud Infrastructure",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "16",
|
||||
"limit": "19",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 53,
|
||||
"header": "Network Security Measures",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "29",
|
||||
"limit": "32",
|
||||
"reviewer": "Lisa Wong"
|
||||
},
|
||||
{
|
||||
"id": 54,
|
||||
"header": "Project Timeline",
|
||||
"type": "Planning",
|
||||
"status": "Done",
|
||||
"target": "14",
|
||||
"limit": "17",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 55,
|
||||
"header": "Resource Allocation",
|
||||
"type": "Planning",
|
||||
"status": "In Process",
|
||||
"target": "27",
|
||||
"limit": "30",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 56,
|
||||
"header": "Team Structure and Roles",
|
||||
"type": "Planning",
|
||||
"status": "Done",
|
||||
"target": "20",
|
||||
"limit": "23",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 57,
|
||||
"header": "Communication Protocols",
|
||||
"type": "Planning",
|
||||
"status": "In Process",
|
||||
"target": "15",
|
||||
"limit": "18",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 58,
|
||||
"header": "Success Metrics",
|
||||
"type": "Planning",
|
||||
"status": "Done",
|
||||
"target": "30",
|
||||
"limit": "33",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 59,
|
||||
"header": "Internationalization Support",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "23",
|
||||
"limit": "26",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 60,
|
||||
"header": "Backup and Recovery Procedures",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "18",
|
||||
"limit": "21",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 61,
|
||||
"header": "Monitoring and Alerting System",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "25",
|
||||
"limit": "28",
|
||||
"reviewer": "Daniel Park"
|
||||
},
|
||||
{
|
||||
"id": 62,
|
||||
"header": "Code Review Guidelines",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "12",
|
||||
"limit": "15",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 63,
|
||||
"header": "Documentation Standards",
|
||||
"type": "Technical content",
|
||||
"status": "In Process",
|
||||
"target": "27",
|
||||
"limit": "30",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 64,
|
||||
"header": "Release Management Process",
|
||||
"type": "Planning",
|
||||
"status": "Done",
|
||||
"target": "22",
|
||||
"limit": "25",
|
||||
"reviewer": "Assign reviewer"
|
||||
},
|
||||
{
|
||||
"id": 65,
|
||||
"header": "Feature Prioritization Matrix",
|
||||
"type": "Planning",
|
||||
"status": "In Process",
|
||||
"target": "19",
|
||||
"limit": "22",
|
||||
"reviewer": "Emma Davis"
|
||||
},
|
||||
{
|
||||
"id": 66,
|
||||
"header": "Technical Debt Assessment",
|
||||
"type": "Technical content",
|
||||
"status": "Done",
|
||||
"target": "24",
|
||||
"limit": "27",
|
||||
"reviewer": "Eddie Lake"
|
||||
},
|
||||
{
|
||||
"id": 67,
|
||||
"header": "Capacity Planning",
|
||||
"type": "Planning",
|
||||
"status": "In Process",
|
||||
"target": "21",
|
||||
"limit": "24",
|
||||
"reviewer": "Jamik Tashpulatov"
|
||||
},
|
||||
{
|
||||
"id": 68,
|
||||
"header": "Service Level Agreements",
|
||||
"type": "Legal",
|
||||
"status": "Done",
|
||||
"target": "26",
|
||||
"limit": "29",
|
||||
"reviewer": "Assign reviewer"
|
||||
}
|
||||
]
|
||||
@@ -1,36 +0,0 @@
|
||||
import { cookies } from "next/headers"
|
||||
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
import { AppSidebar } from "@/app/(examples)/dashboard/components/app-sidebar"
|
||||
import { SiteHeader } from "@/app/(examples)/dashboard/components/site-header"
|
||||
|
||||
import "@/app/(examples)/dashboard/theme.css"
|
||||
|
||||
export default async function DashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const cookieStore = await cookies()
|
||||
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
||||
|
||||
return (
|
||||
<SidebarProvider
|
||||
defaultOpen={defaultOpen}
|
||||
style={
|
||||
{
|
||||
"--sidebar-width": "calc(var(--spacing) * 72)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<AppSidebar variant="inset" />
|
||||
<SidebarInset>
|
||||
<SiteHeader />
|
||||
<div className="flex flex-1 flex-col">{children}</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { ChartAreaInteractive } from "@/app/(examples)/dashboard/components/chart-area-interactive"
|
||||
import { DataTable } from "@/app/(examples)/dashboard/components/data-table"
|
||||
import { SectionCards } from "@/app/(examples)/dashboard/components/section-cards"
|
||||
import data from "@/app/(examples)/dashboard/data.json"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="@container/main flex flex-1 flex-col gap-2">
|
||||
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
||||
<SectionCards />
|
||||
<div className="px-4 lg:px-6">
|
||||
<ChartAreaInteractive />
|
||||
</div>
|
||||
<DataTable data={data} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
body {
|
||||
@apply overscroll-none bg-transparent;
|
||||
}
|
||||
|
||||
:root {
|
||||
--font-sans: var(--font-inter);
|
||||
--header-height: calc(var(--spacing) * 12 + 1px);
|
||||
}
|
||||
|
||||
.theme-scaled {
|
||||
@media (min-width: 1024px) {
|
||||
--radius: 0.6rem;
|
||||
--text-lg: 1.05rem;
|
||||
--text-base: 0.85rem;
|
||||
--text-sm: 0.8rem;
|
||||
--spacing: 0.222222rem;
|
||||
}
|
||||
|
||||
[data-slot="card"] {
|
||||
--spacing: 0.16rem;
|
||||
}
|
||||
|
||||
[data-slot="select-trigger"],
|
||||
[data-slot="toggle-group-item"] {
|
||||
--spacing: 0.222222rem;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-default,
|
||||
.theme-default-scaled {
|
||||
--primary: var(--color-neutral-600);
|
||||
--primary-foreground: var(--color-neutral-50);
|
||||
|
||||
@variant dark {
|
||||
--primary: var(--color-neutral-500);
|
||||
--primary-foreground: var(--color-neutral-50);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-blue,
|
||||
.theme-blue-scaled {
|
||||
--primary: var(--color-blue-600);
|
||||
--primary-foreground: var(--color-blue-50);
|
||||
|
||||
@variant dark {
|
||||
--primary: var(--color-blue-500);
|
||||
--primary-foreground: var(--color-blue-50);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-green,
|
||||
.theme-green-scaled {
|
||||
--primary: var(--color-lime-600);
|
||||
--primary-foreground: var(--color-lime-50);
|
||||
|
||||
@variant dark {
|
||||
--primary: var(--color-lime-600);
|
||||
--primary-foreground: var(--color-lime-50);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-amber,
|
||||
.theme-amber-scaled {
|
||||
--primary: var(--color-amber-600);
|
||||
--primary-foreground: var(--color-amber-50);
|
||||
|
||||
@variant dark {
|
||||
--primary: var(--color-amber-500);
|
||||
--primary-foreground: var(--color-amber-50);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-mono,
|
||||
.theme-mono-scaled {
|
||||
--font-sans: var(--font-mono);
|
||||
--primary: var(--color-neutral-600);
|
||||
--primary-foreground: var(--color-neutral-50);
|
||||
|
||||
@variant dark {
|
||||
--primary: var(--color-neutral-500);
|
||||
--primary-foreground: var(--color-neutral-50);
|
||||
}
|
||||
|
||||
.rounded-xs,
|
||||
.rounded-sm,
|
||||
.rounded-md,
|
||||
.rounded-lg,
|
||||
.rounded-xl {
|
||||
@apply !rounded-none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.shadow-xs,
|
||||
.shadow-sm,
|
||||
.shadow-md,
|
||||
.shadow-lg,
|
||||
.shadow-xl {
|
||||
@apply !shadow-none;
|
||||
}
|
||||
|
||||
[data-slot="toggle-group"],
|
||||
[data-slot="toggle-group-item"] {
|
||||
@apply !rounded-none !shadow-none;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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'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'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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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'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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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'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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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'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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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"
|
||||
),
|
||||
})
|
||||
@@ -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'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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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' aesthetic.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger>Is it animated?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It'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'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's DynamoDB and Google'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>
|
||||
)
|
||||
}
|
||||
@@ -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'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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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'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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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'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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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'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're looking for doesn'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're looking for doesn'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'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'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
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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%]" />
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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'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'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
Reference in New Issue
Block a user