Compare commits
18 Commits
shadcn@2.3
...
canary
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f4d65fc8f | ||
|
|
1e357cb20d | ||
|
|
6339aaa315 | ||
|
|
e2caa3cd35 | ||
|
|
c74a094f14 | ||
|
|
bfb5b6357e | ||
|
|
ac3d965c6e | ||
|
|
6e7cd11d68 | ||
|
|
a75b4ca80e | ||
|
|
475abbbb20 | ||
|
|
762cd73554 | ||
|
|
8c06932800 | ||
|
|
e516481394 | ||
|
|
d1eb24e23a | ||
|
|
9a14c1d092 | ||
|
|
5ef2bc5f45 | ||
|
|
8f6a64f176 | ||
|
|
e85920c191 |
@@ -7,5 +7,5 @@
|
|||||||
"access": "public",
|
"access": "public",
|
||||||
"baseBranch": "main",
|
"baseBranch": "main",
|
||||||
"updateInternalDependencies": "patch",
|
"updateInternalDependencies": "patch",
|
||||||
"ignore": ["www"]
|
"ignore": ["www", "v4"]
|
||||||
}
|
}
|
||||||
|
|||||||
5
.changeset/few-houses-impress.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add theme vars support
|
||||||
5
.changeset/five-hounds-tell.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add tailwind version detection
|
||||||
5
.changeset/fresh-cherries-brush.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add support for tailwind v4
|
||||||
5
.changeset/new-cheetahs-dance.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
default for new-york for v4
|
||||||
5
.changeset/nine-llamas-sell.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
fix handling of sidebar colors
|
||||||
5
.changeset/serious-geese-reply.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
hotswap style for v4
|
||||||
5
.changeset/slow-tools-relax.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add warning for deprecated components
|
||||||
2
.github/workflows/prerelease.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
|||||||
path: packages/shadcn
|
path: packages/shadcn
|
||||||
|
|
||||||
- name: Upload packaged artifact
|
- name: Upload packaged artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: npm-package-shadcn@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
|
name: npm-package-shadcn@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
|
||||||
path: packages/shadcn/dist/index.js
|
path: packages/shadcn/dist/index.js
|
||||||
|
|||||||
6
.vscode/settings.json
vendored
@@ -4,8 +4,10 @@
|
|||||||
{ "pattern": "packages/*/" }
|
{ "pattern": "packages/*/" }
|
||||||
],
|
],
|
||||||
"tailwindCSS.experimental.classRegex": [
|
"tailwindCSS.experimental.classRegex": [
|
||||||
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
["cva\\(((?:[^()]|\\([^()]*\\))*)\\)", "[\"'`]?([^\"'`]+)[\"'`]?"],
|
||||||
["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
|
["cn\\(((?:[^()]|\\([^()]*\\))*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||||
|
// "cva\\(([^)]*)\\)",
|
||||||
|
// "[\"'`]([^\"'`]*).*?[\"'`]"
|
||||||
],
|
],
|
||||||
"vitest.debugExclude": [
|
"vitest.debugExclude": [
|
||||||
"<node_internals>/**",
|
"<node_internals>/**",
|
||||||
|
|||||||
41
apps/v4/.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for committing if needed)
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
6
apps/v4/.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
build
|
||||||
|
.contentlayer
|
||||||
|
registry/__index__.tsx
|
||||||
1
apps/v4/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
This is a wip registry for the `shadcn` canary version. It has React 19 and Tailwind v4 components.
|
||||||
1
apps/v4/__registry__/.autogenerated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
// The content of this directory is autogenerated by the registry server.
|
||||||
0
apps/v4/__registry__/.gitkeep
Normal file
1
apps/v4/__registry__/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
> Files inside this directory is autogenerated by `./scripts/build-registry.ts`. **Do not edit them manually.** - shadcn
|
||||||
3788
apps/v4/__registry__/index.tsx
Normal file
76
apps/v4/app/(app)/charts/charts.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
export { ChartAreaDefault } from "@/registry/new-york-v4/charts/chart-area-default"
|
||||||
|
export { ChartAreaLinear } from "@/registry/new-york-v4/charts/chart-area-linear"
|
||||||
|
export { ChartAreaStep } from "@/registry/new-york-v4/charts/chart-area-step"
|
||||||
|
export { ChartAreaLegend } from "@/registry/new-york-v4/charts/chart-area-legend"
|
||||||
|
export { ChartAreaStacked } from "@/registry/new-york-v4/charts/chart-area-stacked"
|
||||||
|
export { ChartAreaStackedExpand } from "@/registry/new-york-v4/charts/chart-area-stacked-expand"
|
||||||
|
export { ChartAreaIcons } from "@/registry/new-york-v4/charts/chart-area-icons"
|
||||||
|
export { ChartAreaGradient } from "@/registry/new-york-v4/charts/chart-area-gradient"
|
||||||
|
export { ChartAreaAxes } from "@/registry/new-york-v4/charts/chart-area-axes"
|
||||||
|
export { ChartAreaInteractive } from "@/registry/new-york-v4/charts/chart-area-interactive"
|
||||||
|
|
||||||
|
export { ChartBarDefault } from "@/registry/new-york-v4/charts/chart-bar-default"
|
||||||
|
export { ChartBarHorizontal } from "@/registry/new-york-v4/charts/chart-bar-horizontal"
|
||||||
|
export { ChartBarMultiple } from "@/registry/new-york-v4/charts/chart-bar-multiple"
|
||||||
|
export { ChartBarStacked } from "@/registry/new-york-v4/charts/chart-bar-stacked"
|
||||||
|
export { ChartBarLabel } from "@/registry/new-york-v4/charts/chart-bar-label"
|
||||||
|
export { ChartBarLabelCustom } from "@/registry/new-york-v4/charts/chart-bar-label-custom"
|
||||||
|
export { ChartBarMixed } from "@/registry/new-york-v4/charts/chart-bar-mixed"
|
||||||
|
export { ChartBarActive } from "@/registry/new-york-v4/charts/chart-bar-active"
|
||||||
|
export { ChartBarNegative } from "@/registry/new-york-v4/charts/chart-bar-negative"
|
||||||
|
export { ChartBarInteractive } from "@/registry/new-york-v4/charts/chart-bar-interactive"
|
||||||
|
|
||||||
|
export { ChartLineDefault } from "@/registry/new-york-v4/charts/chart-line-default"
|
||||||
|
export { ChartLineLinear } from "@/registry/new-york-v4/charts/chart-line-linear"
|
||||||
|
export { ChartLineStep } from "@/registry/new-york-v4/charts/chart-line-step"
|
||||||
|
export { ChartLineMultiple } from "@/registry/new-york-v4/charts/chart-line-multiple"
|
||||||
|
export { ChartLineDots } from "@/registry/new-york-v4/charts/chart-line-dots"
|
||||||
|
export { ChartLineDotsCustom } from "@/registry/new-york-v4/charts/chart-line-dots-custom"
|
||||||
|
export { ChartLineDotsColors } from "@/registry/new-york-v4/charts/chart-line-dots-colors"
|
||||||
|
export { ChartLineLabel } from "@/registry/new-york-v4/charts/chart-line-label"
|
||||||
|
export { ChartLineLabelCustom } from "@/registry/new-york-v4/charts/chart-line-label-custom"
|
||||||
|
export { ChartLineInteractive } from "@/registry/new-york-v4/charts/chart-line-interactive"
|
||||||
|
|
||||||
|
export { ChartPieSimple } from "@/registry/new-york-v4/charts/chart-pie-simple"
|
||||||
|
export { ChartPieSeparatorNone } from "@/registry/new-york-v4/charts/chart-pie-separator-none"
|
||||||
|
export { ChartPieLabel } from "@/registry/new-york-v4/charts/chart-pie-label"
|
||||||
|
export { ChartPieLabelCustom } from "@/registry/new-york-v4/charts/chart-pie-label-custom"
|
||||||
|
export { ChartPieLabelList } from "@/registry/new-york-v4/charts/chart-pie-label-list"
|
||||||
|
export { ChartPieLegend } from "@/registry/new-york-v4/charts/chart-pie-legend"
|
||||||
|
export { ChartPieDonut } from "@/registry/new-york-v4/charts/chart-pie-donut"
|
||||||
|
export { ChartPieDonutActive } from "@/registry/new-york-v4/charts/chart-pie-donut-active"
|
||||||
|
export { ChartPieDonutText } from "@/registry/new-york-v4/charts/chart-pie-donut-text"
|
||||||
|
export { ChartPieStacked } from "@/registry/new-york-v4/charts/chart-pie-stacked"
|
||||||
|
export { ChartPieInteractive } from "@/registry/new-york-v4/charts/chart-pie-interactive"
|
||||||
|
|
||||||
|
export { ChartRadarDefault } from "@/registry/new-york-v4/charts/chart-radar-default"
|
||||||
|
export { ChartRadarDots } from "@/registry/new-york-v4/charts/chart-radar-dots"
|
||||||
|
export { ChartRadarLinesOnly } from "@/registry/new-york-v4/charts/chart-radar-lines-only"
|
||||||
|
export { ChartRadarLabelCustom } from "@/registry/new-york-v4/charts/chart-radar-label-custom"
|
||||||
|
export { ChartRadarGridCustom } from "@/registry/new-york-v4/charts/chart-radar-grid-custom"
|
||||||
|
export { ChartRadarGridNone } from "@/registry/new-york-v4/charts/chart-radar-grid-none"
|
||||||
|
export { ChartRadarGridCircle } from "@/registry/new-york-v4/charts/chart-radar-grid-circle"
|
||||||
|
export { ChartRadarGridCircleNoLines } from "@/registry/new-york-v4/charts/chart-radar-grid-circle-no-lines"
|
||||||
|
export { ChartRadarGridCircleFill } from "@/registry/new-york-v4/charts/chart-radar-grid-circle-fill"
|
||||||
|
export { ChartRadarGridFill } from "@/registry/new-york-v4/charts/chart-radar-grid-fill"
|
||||||
|
export { ChartRadarMultiple } from "@/registry/new-york-v4/charts/chart-radar-multiple"
|
||||||
|
export { ChartRadarLegend } from "@/registry/new-york-v4/charts/chart-radar-legend"
|
||||||
|
export { ChartRadarIcons } from "@/registry/new-york-v4/charts/chart-radar-icons"
|
||||||
|
export { ChartRadarRadius } from "@/registry/new-york-v4/charts/chart-radar-radius"
|
||||||
|
|
||||||
|
export { ChartRadialSimple } from "@/registry/new-york-v4/charts/chart-radial-simple"
|
||||||
|
export { ChartRadialLabel } from "@/registry/new-york-v4/charts/chart-radial-label"
|
||||||
|
export { ChartRadialGrid } from "@/registry/new-york-v4/charts/chart-radial-grid"
|
||||||
|
export { ChartRadialText } from "@/registry/new-york-v4/charts/chart-radial-text"
|
||||||
|
export { ChartRadialShape } from "@/registry/new-york-v4/charts/chart-radial-shape"
|
||||||
|
export { ChartRadialStacked } from "@/registry/new-york-v4/charts/chart-radial-stacked"
|
||||||
|
|
||||||
|
export { ChartTooltipDefault } from "@/registry/new-york-v4/charts/chart-tooltip-default"
|
||||||
|
export { ChartTooltipIndicatorLine } from "@/registry/new-york-v4/charts/chart-tooltip-indicator-line"
|
||||||
|
export { ChartTooltipIndicatorNone } from "@/registry/new-york-v4/charts/chart-tooltip-indicator-none"
|
||||||
|
export { ChartTooltipLabelCustom } from "@/registry/new-york-v4/charts/chart-tooltip-label-custom"
|
||||||
|
export { ChartTooltipLabelFormatter } from "@/registry/new-york-v4/charts/chart-tooltip-label-formatter"
|
||||||
|
export { ChartTooltipLabelNone } from "@/registry/new-york-v4/charts/chart-tooltip-label-none"
|
||||||
|
export { ChartTooltipFormatter } from "@/registry/new-york-v4/charts/chart-tooltip-formatter"
|
||||||
|
export { ChartTooltipIcons } from "@/registry/new-york-v4/charts/chart-tooltip-icons"
|
||||||
|
export { ChartTooltipAdvanced } from "@/registry/new-york-v4/charts/chart-tooltip-advanced"
|
||||||
20
apps/v4/app/(app)/charts/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { ComponentWrapper } from "@/components/component-wrapper"
|
||||||
|
import * as Charts from "@/app/(app)/charts/charts"
|
||||||
|
|
||||||
|
export default function ChartsPage() {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-3 items-start gap-4 p-4 2xl:grid-cols-4">
|
||||||
|
{Object.entries(Charts)
|
||||||
|
.sort()
|
||||||
|
.map(([key, Component]) => (
|
||||||
|
<ComponentWrapper
|
||||||
|
key={key}
|
||||||
|
name={key}
|
||||||
|
className="w-auto data-[name=chartareainteractive]:col-span-3 data-[name=chartbarinteractive]:col-span-3 data-[name=chartlineinteractive]:col-span-3 **:data-[slot=card]:w-full"
|
||||||
|
>
|
||||||
|
<Component />
|
||||||
|
</ComponentWrapper>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
45
apps/v4/app/(app)/layout.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { cookies } from "next/headers"
|
||||||
|
|
||||||
|
import { AppSidebar } from "@/components/app-sidebar"
|
||||||
|
import { ModeSwitcher } from "@/components/mode-switcher"
|
||||||
|
import { NavHeader } from "@/components/nav-header"
|
||||||
|
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||||
|
import {
|
||||||
|
SidebarInset,
|
||||||
|
SidebarProvider,
|
||||||
|
SidebarTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/sidebar"
|
||||||
|
|
||||||
|
export default async function AppLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode
|
||||||
|
}>) {
|
||||||
|
const cookieStore = await cookies()
|
||||||
|
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarProvider
|
||||||
|
defaultOpen={defaultOpen}
|
||||||
|
className="flex flex-col pt-(--header-height) [--header-height:calc(--spacing(14))]"
|
||||||
|
>
|
||||||
|
<header className="bg-background fixed inset-x-0 top-0 isolate z-10 flex shrink-0 items-center gap-2 border-b">
|
||||||
|
<div className="flex h-(--header-height) w-full items-center gap-2 px-4">
|
||||||
|
<SidebarTrigger className="-ml-1.5" />
|
||||||
|
<Separator
|
||||||
|
orientation="vertical"
|
||||||
|
className="mr-2 data-[orientation=vertical]:h-4"
|
||||||
|
/>
|
||||||
|
<NavHeader />
|
||||||
|
<div className="ml-auto flex items-center gap-2">
|
||||||
|
<ModeSwitcher />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div className="flex flex-1">
|
||||||
|
<AppSidebar />
|
||||||
|
<SidebarInset>{children}</SidebarInset>
|
||||||
|
</div>
|
||||||
|
</SidebarProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
196
apps/v4/app/(app)/page.tsx
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
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 { 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 { ComponentWrapper } from "@/components/component-wrapper"
|
||||||
|
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 { FormDemo } from "@/components/form-demo"
|
||||||
|
import { HoverCardDemo } from "@/components/hover-card-demo"
|
||||||
|
import { InputDemo } from "@/components/input-demo"
|
||||||
|
import { InputOTPDemo } from "@/components/input-otp-demo"
|
||||||
|
import { LabelDemo } from "@/components/label-demo"
|
||||||
|
import { MenubarDemo } from "@/components/menubar-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 { 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"
|
||||||
|
|
||||||
|
export default function SinkPage() {
|
||||||
|
return (
|
||||||
|
<div className="grid gap-4 p-4">
|
||||||
|
<ComponentWrapper name="accordion">
|
||||||
|
<AccordionDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="alert">
|
||||||
|
<AlertDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="alert-dialog">
|
||||||
|
<AlertDialogDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="aspect-ratio">
|
||||||
|
<AspectRatioDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="avatar">
|
||||||
|
<AvatarDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="badge">
|
||||||
|
<BadgeDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="breadcrumb">
|
||||||
|
<BreadcrumbDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="button">
|
||||||
|
<ButtonDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="calendar">
|
||||||
|
<CalendarDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="card">
|
||||||
|
<CardDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="carousel" className="hidden md:flex">
|
||||||
|
<CarouselDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="chart" className="w-full">
|
||||||
|
<ChartDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="checkbox">
|
||||||
|
<CheckboxDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="collapsible">
|
||||||
|
<CollapsibleDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="combobox">
|
||||||
|
<ComboboxDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="command">
|
||||||
|
<CommandDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="context-menu">
|
||||||
|
<ContextMenuDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="date-picker">
|
||||||
|
<DatePickerDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="dialog">
|
||||||
|
<DialogDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="drawer">
|
||||||
|
<DrawerDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="dropdown-menu">
|
||||||
|
<DropdownMenuDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="form">
|
||||||
|
<FormDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="hover-card">
|
||||||
|
<HoverCardDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="input">
|
||||||
|
<InputDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="input-otp">
|
||||||
|
<InputOTPDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="label">
|
||||||
|
<LabelDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="menubar">
|
||||||
|
<MenubarDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="navigation-menu">
|
||||||
|
<NavigationMenuDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="pagination">
|
||||||
|
<PaginationDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="popover">
|
||||||
|
<PopoverDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="progress">
|
||||||
|
<ProgressDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="radio-group">
|
||||||
|
<RadioGroupDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="resizable">
|
||||||
|
<ResizableDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="scroll-area">
|
||||||
|
<ScrollAreaDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="select">
|
||||||
|
<SelectDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="separator">
|
||||||
|
<SeparatorDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="sheet">
|
||||||
|
<SheetDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="skeleton">
|
||||||
|
<SkeletonDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="slider">
|
||||||
|
<SliderDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="sonner">
|
||||||
|
<SonnerDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="switch">
|
||||||
|
<SwitchDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="table">
|
||||||
|
<TableDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="tabs">
|
||||||
|
<TabsDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="textarea">
|
||||||
|
<TextareaDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="toggle">
|
||||||
|
<ToggleDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="toggle-group">
|
||||||
|
<ToggleGroupDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
<ComponentWrapper name="tooltip">
|
||||||
|
<TooltipDemo />
|
||||||
|
</ComponentWrapper>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
96
apps/v4/app/(view)/view/[name]/page.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Metadata } from "next"
|
||||||
|
import { notFound } from "next/navigation"
|
||||||
|
import { registryItemSchema } from "shadcn/registry"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { getRegistryComponent, getRegistryItem } from "@/lib/registry"
|
||||||
|
import { absoluteUrl, cn } from "@/lib/utils"
|
||||||
|
import { siteConfig } from "@/www/config/site"
|
||||||
|
|
||||||
|
const getCachedRegistryItem = React.cache(async (name: string) => {
|
||||||
|
return await getRegistryItem(name)
|
||||||
|
})
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{
|
||||||
|
name: string
|
||||||
|
}>
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { name } = await params
|
||||||
|
const item = await getCachedRegistryItem(name)
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = item.name
|
||||||
|
const description = item.description
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: `${item.name}${item.description ? ` - ${item.description}` : ""}`,
|
||||||
|
description,
|
||||||
|
openGraph: {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
type: "article",
|
||||||
|
url: absoluteUrl(`/blocks/${item.name}`),
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: siteConfig.ogImage,
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
alt: siteConfig.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
images: [siteConfig.ogImage],
|
||||||
|
creator: "@shadcn",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dynamicParams = false
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
const { Index } = await import("@/__registry__")
|
||||||
|
const index = z.record(registryItemSchema).parse(Index)
|
||||||
|
|
||||||
|
return Object.values(index)
|
||||||
|
.filter((block) =>
|
||||||
|
["registry:block", "registry:component"].includes(block.type)
|
||||||
|
)
|
||||||
|
.map((block) => ({
|
||||||
|
name: block.name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function BlockPage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{
|
||||||
|
name: string
|
||||||
|
}>
|
||||||
|
}) {
|
||||||
|
const { name } = await params
|
||||||
|
const item = await getCachedRegistryItem(name)
|
||||||
|
const Component = getRegistryComponent(name)
|
||||||
|
|
||||||
|
if (!item || !Component) {
|
||||||
|
return notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={cn("themes-wrapper bg-background", item.meta?.container)}>
|
||||||
|
<Component />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
BIN
apps/v4/app/favicon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
144
apps/v4/app/globals.css
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@plugin "tailwindcss-animate";
|
||||||
|
|
||||||
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: hsl(0 0% 100%);
|
||||||
|
--foreground: hsl(240 10% 3.9%);
|
||||||
|
--card: hsl(0 0% 100%);
|
||||||
|
--card-foreground: hsl(240 10% 3.9%);
|
||||||
|
--popover: hsl(0 0% 100%);
|
||||||
|
--popover-foreground: hsl(240 10% 3.9%);
|
||||||
|
--primary: hsl(240 5.9% 10%);
|
||||||
|
--primary-foreground: hsl(0 0% 98%);
|
||||||
|
--secondary: hsl(240 4.8% 95.9%);
|
||||||
|
--secondary-foreground: hsl(240 5.9% 10%);
|
||||||
|
--muted: hsl(240 4.8% 95.9%);
|
||||||
|
--muted-foreground: hsl(240 3.8% 46.1%);
|
||||||
|
--accent: hsl(240 4.8% 95.9%);
|
||||||
|
--accent-foreground: hsl(240 5.9% 10%);
|
||||||
|
--destructive: hsl(0 84.2% 60.2%);
|
||||||
|
--destructive-foreground: hsl(0 0% 98%);
|
||||||
|
--border: hsl(240 5.9% 90%);
|
||||||
|
--input: hsl(240 5.9% 90%);
|
||||||
|
--ring: hsl(240 10% 3.9%);
|
||||||
|
--chart-1: hsl(12 76% 61%);
|
||||||
|
--chart-2: hsl(173 58% 39%);
|
||||||
|
--chart-3: hsl(197 37% 24%);
|
||||||
|
--chart-4: hsl(43 74% 66%);
|
||||||
|
--chart-5: hsl(27 87% 67%);
|
||||||
|
--radius: 0.6rem;
|
||||||
|
--sidebar-background: hsl(0 0% 98%);
|
||||||
|
--sidebar-foreground: hsl(240 5.3% 26.1%);
|
||||||
|
--sidebar-primary: hsl(240 5.9% 10%);
|
||||||
|
--sidebar-primary-foreground: hsl(0 0% 98%);
|
||||||
|
--sidebar-accent: hsl(240 4.8% 95.9%);
|
||||||
|
--sidebar-accent-foreground: hsl(240 5.9% 10%);
|
||||||
|
--sidebar-border: hsl(220 13% 91%);
|
||||||
|
--sidebar-ring: hsl(217.2 91.2% 59.8%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: hsl(240 10% 3.9%);
|
||||||
|
--foreground: hsl(0 0% 98%);
|
||||||
|
--card: hsl(240 10% 3.9%);
|
||||||
|
--card-foreground: hsl(0 0% 98%);
|
||||||
|
--popover: hsl(240 10% 3.9%);
|
||||||
|
--popover-foreground: hsl(0 0% 98%);
|
||||||
|
--primary: hsl(0 0% 98%);
|
||||||
|
--primary-foreground: hsl(240 5.9% 10%);
|
||||||
|
--secondary: hsl(240 3.7% 15.9%);
|
||||||
|
--secondary-foreground: hsl(0 0% 98%);
|
||||||
|
--muted: hsl(240 3.7% 15.9%);
|
||||||
|
--muted-foreground: hsl(240 5% 64.9%);
|
||||||
|
--accent: hsl(240 3.7% 15.9%);
|
||||||
|
--accent-foreground: hsl(0 0% 98%);
|
||||||
|
--destructive: hsl(0 62.8% 30.6%);
|
||||||
|
--destructive-foreground: hsl(0 0% 98%);
|
||||||
|
--border: hsl(240 3.7% 15.9%);
|
||||||
|
--input: hsl(240 3.7% 15.9%);
|
||||||
|
--ring: hsl(240 4.9% 83.9%);
|
||||||
|
--chart-1: hsl(220 70% 50%);
|
||||||
|
--chart-2: hsl(160 60% 45%);
|
||||||
|
--chart-3: hsl(30 80% 55%);
|
||||||
|
--chart-4: hsl(280 65% 60%);
|
||||||
|
--chart-5: hsl(340 75% 55%);
|
||||||
|
--sidebar-background: hsl(240 5.9% 10%);
|
||||||
|
--sidebar-foreground: hsl(240 4.8% 95.9%);
|
||||||
|
--sidebar-primary: hsl(224.3 76.3% 48%);
|
||||||
|
--sidebar-primary-foreground: hsl(0 0% 100%);
|
||||||
|
--sidebar-accent: hsl(240 3.7% 15.9%);
|
||||||
|
--sidebar-accent-foreground: hsl(240 4.8% 95.9%);
|
||||||
|
--sidebar-border: hsl(240 3.7% 15.9%);
|
||||||
|
--sidebar-ring: hsl(217.2 91.2% 59.8%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-popover: var(--popover);
|
||||||
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-destructive: var(--destructive);
|
||||||
|
--color-destructive-foreground: var(--destructive-foreground);
|
||||||
|
--color-border: var(--border);
|
||||||
|
--color-input: var(--input);
|
||||||
|
--color-ring: var(--ring);
|
||||||
|
--color-chart-1: var(--chart-1);
|
||||||
|
--color-chart-2: var(--chart-2);
|
||||||
|
--color-chart-3: var(--chart-3);
|
||||||
|
--color-chart-4: var(--chart-4);
|
||||||
|
--color-chart-5: var(--chart-5);
|
||||||
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-xl: calc(var(--radius) + 4px);
|
||||||
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
|
--color-sidebar-accent: var(--sidebar-accent);
|
||||||
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||||
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
|
--color-sidebar: var(--sidebar-background);
|
||||||
|
--animate-accordion-down: accordion-down 0.2s ease-out;
|
||||||
|
--animate-accordion-up: accordion-up 0.2s ease-out;
|
||||||
|
|
||||||
|
@keyframes accordion-down {
|
||||||
|
from {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
height: var(--radix-accordion-content-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes accordion-up {
|
||||||
|
from {
|
||||||
|
height: var(--radix-accordion-content-height);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border outline-ring/50;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
118
apps/v4/app/layout.tsx
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import type { Metadata, Viewport } from "next"
|
||||||
|
import { GeistMono } from "geist/font/mono"
|
||||||
|
import { GeistSans } from "geist/font/sans"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Analytics } from "@/components/analytics"
|
||||||
|
import { ThemeProvider } from "@/components/theme-provider"
|
||||||
|
import { Toaster } from "@/registry/new-york-v4/ui/sonner"
|
||||||
|
import { siteConfig } from "@/www/config/site"
|
||||||
|
|
||||||
|
import "./globals.css"
|
||||||
|
|
||||||
|
const fontSans = GeistSans
|
||||||
|
|
||||||
|
const fontMono = GeistMono
|
||||||
|
|
||||||
|
const META_THEME_COLORS = {
|
||||||
|
light: "#ffffff",
|
||||||
|
dark: "#09090b",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: {
|
||||||
|
default: siteConfig.name,
|
||||||
|
template: `%s - ${siteConfig.name}`,
|
||||||
|
},
|
||||||
|
metadataBase: new URL(siteConfig.url),
|
||||||
|
description: siteConfig.description,
|
||||||
|
keywords: [
|
||||||
|
"Next.js",
|
||||||
|
"React",
|
||||||
|
"Tailwind CSS",
|
||||||
|
"Server Components",
|
||||||
|
"Radix UI",
|
||||||
|
],
|
||||||
|
authors: [
|
||||||
|
{
|
||||||
|
name: "shadcn",
|
||||||
|
url: "https://shadcn.com",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
creator: "shadcn",
|
||||||
|
openGraph: {
|
||||||
|
type: "website",
|
||||||
|
locale: "en_US",
|
||||||
|
url: siteConfig.url,
|
||||||
|
title: siteConfig.name,
|
||||||
|
description: siteConfig.description,
|
||||||
|
siteName: siteConfig.name,
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: siteConfig.ogImage,
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
alt: siteConfig.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title: siteConfig.name,
|
||||||
|
description: siteConfig.description,
|
||||||
|
images: [siteConfig.ogImage],
|
||||||
|
creator: "@shadcn",
|
||||||
|
},
|
||||||
|
icons: {
|
||||||
|
icon: "/favicon.ico",
|
||||||
|
shortcut: "/favicon-16x16.png",
|
||||||
|
apple: "/apple-touch-icon.png",
|
||||||
|
},
|
||||||
|
manifest: `${siteConfig.url}/site.webmanifest`,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const viewport: Viewport = {
|
||||||
|
themeColor: META_THEME_COLORS.light,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en" suppressHydrationWarning>
|
||||||
|
<head>
|
||||||
|
<script
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `
|
||||||
|
try {
|
||||||
|
if (localStorage.theme === 'dark' || ((!('theme' in localStorage) || localStorage.theme === 'system') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
|
document.querySelector('meta[name="theme-color"]').setAttribute('content', '${META_THEME_COLORS.dark}')
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body
|
||||||
|
className={cn(
|
||||||
|
"bg-background min-h-svh overscroll-none font-sans antialiased",
|
||||||
|
fontSans.variable,
|
||||||
|
fontMono.variable
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ThemeProvider
|
||||||
|
attribute="class"
|
||||||
|
defaultTheme="system"
|
||||||
|
enableSystem
|
||||||
|
disableTransitionOnChange
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<Toaster />
|
||||||
|
<Analytics />
|
||||||
|
</ThemeProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
21
apps/v4/components.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "",
|
||||||
|
"css": "app/globals.css",
|
||||||
|
"baseColor": "zinc",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide"
|
||||||
|
}
|
||||||
72
apps/v4/components/accordion-demo.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
113
apps/v4/components/alert-demo.tsx
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
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>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>
|
||||||
|
)
|
||||||
|
}
|
||||||
35
apps/v4/components/alert-dialog-demo.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/alert-dialog"
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
|
||||||
|
export function AlertDialogDemo() {
|
||||||
|
return (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button variant="outline">Show Dialog</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
7
apps/v4/components/analytics.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { Analytics as VercelAnalytics } from "@vercel/analytics/react"
|
||||||
|
|
||||||
|
export function Analytics() {
|
||||||
|
return <VercelAnalytics />
|
||||||
|
}
|
||||||
247
apps/v4/components/app-sidebar.tsx
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { Index } from "@/__registry__"
|
||||||
|
import {
|
||||||
|
AudioWaveform,
|
||||||
|
BookOpen,
|
||||||
|
Bot,
|
||||||
|
ChevronRightIcon,
|
||||||
|
Command,
|
||||||
|
GalleryVerticalEnd,
|
||||||
|
Search,
|
||||||
|
Settings2,
|
||||||
|
SquareTerminal,
|
||||||
|
} from "lucide-react"
|
||||||
|
|
||||||
|
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 { Label } from "@/registry/new-york-v4/ui/label"
|
||||||
|
import {
|
||||||
|
Sidebar,
|
||||||
|
SidebarContent,
|
||||||
|
SidebarFooter,
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupContent,
|
||||||
|
SidebarGroupLabel,
|
||||||
|
SidebarHeader,
|
||||||
|
SidebarInput,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
SidebarMenuSub,
|
||||||
|
SidebarMenuSubButton,
|
||||||
|
SidebarMenuSubItem,
|
||||||
|
SidebarRail,
|
||||||
|
} from "@/registry/new-york-v4/ui/sidebar"
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||||
|
return (
|
||||||
|
<Sidebar
|
||||||
|
collapsible="icon"
|
||||||
|
className="top-(--delta) h-[calc(100svh-var(--delta))]! [--delta:calc(var(--header-height)+1px)]"
|
||||||
|
{...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>
|
||||||
|
<SidebarInput
|
||||||
|
id="search"
|
||||||
|
placeholder="Search the docs..."
|
||||||
|
className="pl-8"
|
||||||
|
/>
|
||||||
|
<Search className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" />
|
||||||
|
</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">
|
||||||
|
<SidebarGroupLabel>Components</SidebarGroupLabel>
|
||||||
|
<SidebarMenu>
|
||||||
|
{data.components.map((item) => (
|
||||||
|
<SidebarMenuItem key={item.name}>
|
||||||
|
<SidebarMenuButton asChild>
|
||||||
|
<a href={`/#${item.name}`}>
|
||||||
|
<span>{getComponentName(item.name)}</span>
|
||||||
|
</a>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroup>
|
||||||
|
</SidebarContent>
|
||||||
|
<SidebarFooter>
|
||||||
|
<NavUser user={data.user} />
|
||||||
|
</SidebarFooter>
|
||||||
|
<SidebarRail />
|
||||||
|
</Sidebar>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getComponentName(name: string) {
|
||||||
|
// convert kebab-case to title case
|
||||||
|
return name.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase())
|
||||||
|
}
|
||||||
26
apps/v4/components/aspect-ratio-demo.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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="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-md object-cover dark:brightness-[0.2] dark:grayscale"
|
||||||
|
/>
|
||||||
|
</AspectRatio>
|
||||||
|
<AspectRatio ratio={1 / 1} className="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-md object-cover dark:brightness-[0.2] dark:grayscale"
|
||||||
|
/>
|
||||||
|
</AspectRatio>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
81
apps/v4/components/avatar-demo.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
AvatarFallback,
|
||||||
|
AvatarImage,
|
||||||
|
} from "@/registry/new-york-v4/ui/avatar"
|
||||||
|
|
||||||
|
export function AvatarDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center gap-4 md:flex-row">
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||||
|
<AvatarFallback>CN</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<Avatar>
|
||||||
|
<AvatarFallback>CN</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<Avatar className="size-12">
|
||||||
|
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||||
|
<AvatarFallback>CN</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<Avatar className="rounded-lg">
|
||||||
|
<AvatarImage
|
||||||
|
src="https://github.com/evilrabbit.png"
|
||||||
|
alt="@evilrabbit"
|
||||||
|
/>
|
||||||
|
<AvatarFallback>ER</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||||
|
<AvatarFallback>CN</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
|
||||||
|
<AvatarFallback>LR</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage
|
||||||
|
src="https://github.com/evilrabbit.png"
|
||||||
|
alt="@evilrabbit"
|
||||||
|
/>
|
||||||
|
<AvatarFallback>ER</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||||
|
<AvatarFallback>CN</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
|
||||||
|
<AvatarFallback>LR</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage
|
||||||
|
src="https://github.com/evilrabbit.png"
|
||||||
|
alt="@evilrabbit"
|
||||||
|
/>
|
||||||
|
<AvatarFallback>ER</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 hover:space-x-1 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale *:data-[slot=avatar]:transition-all *:data-[slot=avatar]:duration-300 *:data-[slot=avatar]:ease-in-out">
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||||
|
<AvatarFallback>CN</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
|
||||||
|
<AvatarFallback>LR</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage
|
||||||
|
src="https://github.com/evilrabbit.png"
|
||||||
|
alt="@evilrabbit"
|
||||||
|
/>
|
||||||
|
<AvatarFallback>ER</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
49
apps/v4/components/badge-demo.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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-col gap-2 md:flex-row">
|
||||||
|
<Badge>Badge</Badge>
|
||||||
|
<Badge variant="secondary">Secondary</Badge>
|
||||||
|
<Badge variant="destructive">Destructive</Badge>
|
||||||
|
<Badge variant="outline">Outline</Badge>
|
||||||
|
<Badge variant="outline">
|
||||||
|
<CheckIcon />
|
||||||
|
Badge
|
||||||
|
</Badge>
|
||||||
|
<Badge variant="destructive">
|
||||||
|
<AlertCircleIcon />
|
||||||
|
Alert
|
||||||
|
</Badge>
|
||||||
|
<Badge className="size-5 rounded-full p-0 font-mono tabular-nums">
|
||||||
|
8
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full flex-col gap-2 md:flex-row">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
49
apps/v4/components/breadcrumb-demo.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
84
apps/v4/components/button-demo.tsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { ArrowRightIcon, Loader2Icon, 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-col 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-col 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>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
Learn More <ArrowRightIcon />
|
||||||
|
</Button>
|
||||||
|
<Button disabled size="sm" variant="outline">
|
||||||
|
<Loader2Icon className="animate-spin" />
|
||||||
|
Please wait
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col 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>
|
||||||
|
<Button variant="outline" size="lg">
|
||||||
|
Learn More <ArrowRightIcon />
|
||||||
|
</Button>
|
||||||
|
<Button disabled size="lg" variant="outline">
|
||||||
|
<Loader2Icon className="animate-spin" />
|
||||||
|
Please wait
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
35
apps/v4/components/calendar-demo.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { addDays } from "date-fns"
|
||||||
|
import { type DateRange } from "react-day-picker"
|
||||||
|
|
||||||
|
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
|
||||||
|
|
||||||
|
export function CalendarDemo() {
|
||||||
|
const [date, setDate] = React.useState<Date | undefined>(new Date())
|
||||||
|
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 items-start gap-2 md:flex-row">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={date}
|
||||||
|
onSelect={setDate}
|
||||||
|
className="rounded-md border shadow-sm"
|
||||||
|
/>
|
||||||
|
<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-md border shadow-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
143
apps/v4/components/card-demo.tsx
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
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>
|
||||||
|
<form>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Login to your account</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Enter your email below to login to your account
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
</form>
|
||||||
|
</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="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||||
|
<AvatarFallback>CN</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
|
||||||
|
<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="p-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 p-6">
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
74
apps/v4/components/carousel-demo.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
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="w-full flex-col items-center gap-4 md: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>
|
||||||
|
)
|
||||||
|
}
|
||||||
94
apps/v4/components/chart-area-demo.tsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
"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 {
|
||||||
|
ChartConfig,
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
} 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="text-muted-foreground flex items-center gap-2 leading-none">
|
||||||
|
January - June 2024
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
80
apps/v4/components/chart-bar-demo.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
"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 {
|
||||||
|
ChartConfig,
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
} 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="text-muted-foreground leading-none">
|
||||||
|
Showing total visitors for the last 6 months
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
13
apps/v4/components/chart-demo.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { ChartAreaDemo } from "@/components/chart-area-demo"
|
||||||
|
import { ChartBarDemo } from "@/components/chart-bar-demo"
|
||||||
|
import { ChartLineDemo } from "@/components/chart-line-demo"
|
||||||
|
|
||||||
|
export function ChartDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex w-full max-w-screen-xl flex-col flex-wrap gap-4 *:data-[slot=card]:flex-1 md:flex-row">
|
||||||
|
<ChartAreaDemo />
|
||||||
|
<ChartBarDemo />
|
||||||
|
<ChartLineDemo />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
100
apps/v4/components/chart-line-demo.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
"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 {
|
||||||
|
ChartConfig,
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
} 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="text-muted-foreground flex items-center gap-2 leading-none">
|
||||||
|
Showing total visitors for the last 6 months
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
43
apps/v4/components/checkbox-demo.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
"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-muted-foreground text-sm">
|
||||||
|
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="hover:bg-accent/50 flex items-start gap-3 rounded-lg border p-3 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-muted-foreground text-sm">
|
||||||
|
You can enable or disable notifications at any time.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
46
apps/v4/components/collapsible-demo.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
"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>
|
||||||
|
)
|
||||||
|
}
|
||||||
344
apps/v4/components/combobox-demo.tsx
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import {
|
||||||
|
CheckIcon,
|
||||||
|
ChevronDownIcon,
|
||||||
|
ChevronsUpDown,
|
||||||
|
PlusCircleIcon,
|
||||||
|
} from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
AvatarFallback,
|
||||||
|
AvatarImage,
|
||||||
|
} from "@/registry/new-york-v4/ui/avatar"
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
CommandSeparator,
|
||||||
|
} from "@/registry/new-york-v4/ui/command"
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/popover"
|
||||||
|
|
||||||
|
const frameworks = [
|
||||||
|
{
|
||||||
|
value: "next.js",
|
||||||
|
label: "Next.js",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "sveltekit",
|
||||||
|
label: "SvelteKit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "nuxt.js",
|
||||||
|
label: "Nuxt.js",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "remix",
|
||||||
|
label: "Remix",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "astro",
|
||||||
|
label: "Astro",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
type Framework = (typeof frameworks)[number]
|
||||||
|
|
||||||
|
const users = [
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
username: "shadcn",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
username: "leerob",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
username: "evilrabbit",
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
|
type User = (typeof users)[number]
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
type Timezone = (typeof timezones)[number]
|
||||||
|
|
||||||
|
export function ComboboxDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex w-full flex-col items-start gap-4 md:flex-row">
|
||||||
|
<FrameworkCombobox frameworks={[...frameworks]} />
|
||||||
|
<UserCombobox users={[...users]} selectedUserId={users[0].id} />
|
||||||
|
<TimezoneCombobox
|
||||||
|
timezones={[...timezones]}
|
||||||
|
selectedTimezone={timezones[0].timezones[0]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function FrameworkCombobox({ frameworks }: { frameworks: Framework[] }) {
|
||||||
|
const [open, setOpen] = React.useState(false)
|
||||||
|
const [value, setValue] = React.useState("")
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
aria-expanded={open}
|
||||||
|
className="w-full justify-between md:max-w-[200px]"
|
||||||
|
>
|
||||||
|
{value
|
||||||
|
? frameworks.find((framework) => framework.value === value)?.label
|
||||||
|
: "Select framework..."}
|
||||||
|
<ChevronsUpDown className="text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-(--radix-popover-trigger-width) p-0">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Search framework..." />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No framework found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{frameworks.map((framework) => (
|
||||||
|
<CommandItem
|
||||||
|
key={framework.value}
|
||||||
|
value={framework.value}
|
||||||
|
onSelect={(currentValue) => {
|
||||||
|
setValue(currentValue === value ? "" : currentValue)
|
||||||
|
setOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{framework.label}
|
||||||
|
<CheckIcon
|
||||||
|
className={cn(
|
||||||
|
"ml-auto",
|
||||||
|
value === framework.value ? "opacity-100" : "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function UserCombobox({
|
||||||
|
users,
|
||||||
|
selectedUserId,
|
||||||
|
}: {
|
||||||
|
users: User[]
|
||||||
|
selectedUserId: string
|
||||||
|
}) {
|
||||||
|
const [open, setOpen] = React.useState(false)
|
||||||
|
const [value, setValue] = React.useState(selectedUserId)
|
||||||
|
|
||||||
|
const selectedUser = React.useMemo(
|
||||||
|
() => users.find((user) => user.id === value),
|
||||||
|
[value, users]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
aria-expanded={open}
|
||||||
|
className="w-full justify-between px-2 md:max-w-[200px]"
|
||||||
|
>
|
||||||
|
{selectedUser ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Avatar className="size-5">
|
||||||
|
<AvatarImage
|
||||||
|
src={`https://github.com/${selectedUser.username}.png`}
|
||||||
|
/>
|
||||||
|
<AvatarFallback>{selectedUser.username[0]}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
{selectedUser.username}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
"Select user..."
|
||||||
|
)}
|
||||||
|
<ChevronsUpDown className="text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-(--radix-popover-trigger-width) p-0">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Search user..." />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No user found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{users.map((user) => (
|
||||||
|
<CommandItem
|
||||||
|
key={user.id}
|
||||||
|
value={user.id}
|
||||||
|
onSelect={(currentValue) => {
|
||||||
|
setValue(currentValue === value ? "" : currentValue)
|
||||||
|
setOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar className="size-5">
|
||||||
|
<AvatarImage
|
||||||
|
src={`https://github.com/${user.username}.png`}
|
||||||
|
/>
|
||||||
|
<AvatarFallback>{user.username[0]}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
{user.username}
|
||||||
|
<CheckIcon
|
||||||
|
className={cn(
|
||||||
|
"ml-auto",
|
||||||
|
value === user.id ? "opacity-100" : "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
<CommandSeparator />
|
||||||
|
<CommandGroup>
|
||||||
|
<CommandItem>
|
||||||
|
<PlusCircleIcon />
|
||||||
|
Create user
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TimezoneCombobox({
|
||||||
|
timezones,
|
||||||
|
selectedTimezone,
|
||||||
|
}: {
|
||||||
|
timezones: Timezone[]
|
||||||
|
selectedTimezone: Timezone["timezones"][number]
|
||||||
|
}) {
|
||||||
|
const [open, setOpen] = React.useState(false)
|
||||||
|
const [value, setValue] = React.useState(selectedTimezone.value)
|
||||||
|
|
||||||
|
const selectedGroup = React.useMemo(
|
||||||
|
() =>
|
||||||
|
timezones.find((group) =>
|
||||||
|
group.timezones.find((tz) => tz.value === value)
|
||||||
|
),
|
||||||
|
[value, timezones]
|
||||||
|
)
|
||||||
|
|
||||||
|
const selectedTimezoneLabel = React.useMemo(
|
||||||
|
() => selectedGroup?.timezones.find((tz) => tz.value === value)?.label,
|
||||||
|
[value, selectedGroup]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="h-12 w-full justify-between px-2.5 md:max-w-[200px]"
|
||||||
|
>
|
||||||
|
{selectedTimezone ? (
|
||||||
|
<div className="flex flex-col items-start gap-0.5">
|
||||||
|
<span className="text-muted-foreground text-xs font-normal">
|
||||||
|
{selectedGroup?.label}
|
||||||
|
</span>
|
||||||
|
<span>{selectedTimezoneLabel}</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
"Select timezone"
|
||||||
|
)}
|
||||||
|
<ChevronDownIcon className="text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="p-0" align="start">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Search timezone..." />
|
||||||
|
<CommandList className="scroll-pb-12">
|
||||||
|
<CommandEmpty>No timezone found.</CommandEmpty>
|
||||||
|
{timezones.map((region) => (
|
||||||
|
<CommandGroup key={region.label} heading={region.label}>
|
||||||
|
{region.timezones.map((timezone) => (
|
||||||
|
<CommandItem
|
||||||
|
key={timezone.value}
|
||||||
|
value={timezone.value}
|
||||||
|
onSelect={(currentValue) => {
|
||||||
|
setValue(
|
||||||
|
currentValue as Timezone["timezones"][number]["value"]
|
||||||
|
)
|
||||||
|
setOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{timezone.label}
|
||||||
|
<CheckIcon
|
||||||
|
className="ml-auto opacity-0 data-[selected=true]:opacity-100"
|
||||||
|
data-selected={value === timezone.value}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
))}
|
||||||
|
<CommandSeparator className="sticky bottom-10" />
|
||||||
|
<CommandGroup className="bg-popover sticky bottom-0">
|
||||||
|
<CommandItem>
|
||||||
|
<PlusCircleIcon />
|
||||||
|
Create timezone
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
87
apps/v4/components/command-demo.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
"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-muted-foreground text-sm">
|
||||||
|
Press{" "}
|
||||||
|
<kbd className="bg-muted text-muted-foreground pointer-events-none inline-flex h-5 items-center gap-1 rounded border px-1.5 font-mono text-[10px] font-medium 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
66
apps/v4/components/component-wrapper.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
"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())
|
||||||
|
}
|
||||||
79
apps/v4/components/context-menu-demo.tsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
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 As...
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
99
apps/v4/components/date-picker-demo.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { addDays, format } from "date-fns"
|
||||||
|
import { CalendarIcon } from "lucide-react"
|
||||||
|
import { DateRange } from "react-day-picker"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/popover"
|
||||||
|
|
||||||
|
export function DatePickerDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-start gap-4 md:flex-row">
|
||||||
|
<DatePickerSimple />
|
||||||
|
<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 />
|
||||||
|
{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}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
</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 />
|
||||||
|
{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
|
||||||
|
initialFocus
|
||||||
|
mode="range"
|
||||||
|
defaultMonth={date?.from}
|
||||||
|
selected={date}
|
||||||
|
onSelect={setDate}
|
||||||
|
numberOfMonths={2}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
129
apps/v4/components/dialog-demo.tsx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
228
apps/v4/components/drawer-demo.tsx
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
"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-col items-start gap-4 md:flex-row md:items-center">
|
||||||
|
<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-muted-foreground text-[0.70rem] 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: "hsl(var(--foreground))",
|
||||||
|
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>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
360
apps/v4/components/dropdown-menu-demo.tsx
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import {
|
||||||
|
BadgeCheckIcon,
|
||||||
|
BellIcon,
|
||||||
|
ChevronsUpDownIcon,
|
||||||
|
CreditCardIcon,
|
||||||
|
LogOut,
|
||||||
|
LogOutIcon,
|
||||||
|
MoreHorizontalIcon,
|
||||||
|
PencilIcon,
|
||||||
|
Settings2Icon,
|
||||||
|
ShareIcon,
|
||||||
|
SparklesIcon,
|
||||||
|
TrashIcon,
|
||||||
|
UserIcon,
|
||||||
|
} 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,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuGroup,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuPortal,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuShortcut,
|
||||||
|
DropdownMenuSub,
|
||||||
|
DropdownMenuSubContent,
|
||||||
|
DropdownMenuSubTrigger,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||||
|
|
||||||
|
export function DropdownMenuDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center gap-4 md:flex-row">
|
||||||
|
<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>
|
||||||
|
<UserIcon /> Profile
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<CreditCardIcon /> Billing
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Settings2Icon /> 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>
|
||||||
|
<LogOutIcon /> 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="text-muted-foreground truncate text-xs">
|
||||||
|
shadcn@example.com
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ChevronsUpDownIcon className="text-muted-foreground ml-auto" />
|
||||||
|
</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="text-muted-foreground truncate text-xs">
|
||||||
|
shadcn@example.com
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<SparklesIcon />
|
||||||
|
Upgrade to Pro
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<BadgeCheckIcon />
|
||||||
|
Account
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<CreditCardIcon />
|
||||||
|
Billing
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<BellIcon />
|
||||||
|
Notifications
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<LogOut />
|
||||||
|
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/leerob.png" alt="leerob" />
|
||||||
|
<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/leerob.png" alt="leerob" />
|
||||||
|
<AvatarFallback className="rounded-lg">LR</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
|
<span className="truncate font-semibold">leerob</span>
|
||||||
|
<span className="text-muted-foreground truncate text-xs">
|
||||||
|
leerob@example.com
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<SparklesIcon />
|
||||||
|
Upgrade to Pro
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<BadgeCheckIcon />
|
||||||
|
Account
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<CreditCardIcon />
|
||||||
|
Billing
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<BellIcon />
|
||||||
|
Notifications
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<LogOut />
|
||||||
|
Sign Out
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuIconColor() {
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon">
|
||||||
|
<MoreHorizontalIcon />
|
||||||
|
<span className="sr-only">Toggle menu</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start">
|
||||||
|
<DropdownMenuGroup className="*:data-[slot=dropdown-menu-item]:[&>svg]:text-muted-foreground">
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<PencilIcon />
|
||||||
|
Edit
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<ShareIcon />
|
||||||
|
Share
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem variant="destructive">
|
||||||
|
<TrashIcon />
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
395
apps/v4/components/form-demo.tsx
Normal file
@@ -0,0 +1,395 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import Link from "next/link"
|
||||||
|
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-[340px] rounded-md bg-slate-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{" "}
|
||||||
|
<Link href="/examples/forms">email settings</Link>.
|
||||||
|
</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="space-y-3">
|
||||||
|
<FormLabel>Notify me about...</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroup
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value}
|
||||||
|
className="flex flex-col space-y-1"
|
||||||
|
>
|
||||||
|
<FormItem className="flex items-center space-y-0 space-x-3">
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroupItem value="all" />
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className="font-normal">
|
||||||
|
All new messages
|
||||||
|
</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem className="flex items-center space-y-0 space-x-3">
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroupItem value="mentions" />
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className="font-normal">
|
||||||
|
Direct messages and mentions
|
||||||
|
</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem className="flex items-center space-y-0 space-x-3">
|
||||||
|
<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 space-y-0 space-x-3 rounded-md border p-4 shadow">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="space-y-1 leading-none">
|
||||||
|
<FormLabel>
|
||||||
|
Use different settings for my mobile devices
|
||||||
|
</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
You can manage your mobile notifications in the{" "}
|
||||||
|
<Link href="/examples/forms">mobile settings</Link> page.
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="items"
|
||||||
|
render={() => (
|
||||||
|
<FormItem>
|
||||||
|
<div className="mb-4">
|
||||||
|
<FormLabel className="text-base">Sidebar</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Select the items you want to display in the sidebar.
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
{items.map((item) => (
|
||||||
|
<FormField
|
||||||
|
key={item.id}
|
||||||
|
control={form.control}
|
||||||
|
name="items"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem
|
||||||
|
key={item.id}
|
||||||
|
className="flex flex-row items-start space-y-0 space-x-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 font-normal">
|
||||||
|
{item.label}
|
||||||
|
</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<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 > 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="space-y-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="marketing_emails"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>Marketing emails</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
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-center justify-between rounded-lg border p-3 shadow-sm">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>Security emails</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
43
apps/v4/components/hover-card-demo.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
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="text-muted-foreground size-4" />{" "}
|
||||||
|
<span className="text-muted-foreground text-xs">
|
||||||
|
Joined December 2021
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HoverCardContent>
|
||||||
|
</HoverCard>
|
||||||
|
)
|
||||||
|
}
|
||||||
23
apps/v4/components/input-demo.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
109
apps/v4/components/input-otp-demo.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
"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} />
|
||||||
|
<InputOTPSlot index={1} />
|
||||||
|
<InputOTPSlot index={2} />
|
||||||
|
<InputOTPSlot index={3} />
|
||||||
|
</InputOTPGroup>
|
||||||
|
</InputOTP>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
27
apps/v4/components/label-demo.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
130
apps/v4/components/menubar-demo.tsx
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
34
apps/v4/components/mode-switcher.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { MoonIcon, SunIcon } from "lucide-react"
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
|
|
||||||
|
import { META_THEME_COLORS, useMetaColor } from "@/hooks/use-meta-color"
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
|
||||||
|
export function ModeSwitcher() {
|
||||||
|
const { setTheme, resolvedTheme } = useTheme()
|
||||||
|
const { setMetaColor } = useMetaColor()
|
||||||
|
|
||||||
|
const toggleTheme = React.useCallback(() => {
|
||||||
|
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||||
|
setMetaColor(
|
||||||
|
resolvedTheme === "dark"
|
||||||
|
? META_THEME_COLORS.light
|
||||||
|
: META_THEME_COLORS.dark
|
||||||
|
)
|
||||||
|
}, [resolvedTheme, setTheme, setMetaColor])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="group/toggle h-8 w-8 px-0"
|
||||||
|
onClick={toggleTheme}
|
||||||
|
>
|
||||||
|
<SunIcon className="hidden [html.dark_&]:block" />
|
||||||
|
<MoonIcon className="hidden [html.light_&]:block" />
|
||||||
|
<span className="sr-only">Toggle theme</span>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
27
apps/v4/components/mode-toggle.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
"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="ghost"
|
||||||
|
className="group/toggle h-8 w-8 px-0"
|
||||||
|
onClick={toggleTheme}
|
||||||
|
>
|
||||||
|
<SunIcon className="hidden [html.dark_&]:block" />
|
||||||
|
<MoonIcon className="hidden [html.light_&]:block" />
|
||||||
|
<span className="sr-only">Toggle theme</span>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
32
apps/v4/components/nav-header.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import Link from "next/link"
|
||||||
|
import { usePathname } from "next/navigation"
|
||||||
|
|
||||||
|
import {
|
||||||
|
NavigationMenu,
|
||||||
|
NavigationMenuItem,
|
||||||
|
NavigationMenuLink,
|
||||||
|
NavigationMenuList,
|
||||||
|
} from "@/registry/new-york-v4/ui/navigation-menu"
|
||||||
|
|
||||||
|
export function NavHeader() {
|
||||||
|
const pathname = usePathname()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavigationMenu>
|
||||||
|
<NavigationMenuList className="gap-2 *:data-[slot=navigation-menu-item]:h-7 **:data-[slot=navigation-menu-link]:py-1 **:data-[slot=navigation-menu-link]:font-medium">
|
||||||
|
<NavigationMenuItem>
|
||||||
|
<NavigationMenuLink asChild data-active={pathname === "/"}>
|
||||||
|
<Link href="/">Home</Link>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</NavigationMenuItem>
|
||||||
|
<NavigationMenuItem>
|
||||||
|
<NavigationMenuLink asChild data-active={pathname === "/charts"}>
|
||||||
|
<Link href="/charts">Charts</Link>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</NavigationMenuItem>
|
||||||
|
</NavigationMenuList>
|
||||||
|
</NavigationMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
114
apps/v4/components/nav-user.tsx
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import {
|
||||||
|
BadgeCheck,
|
||||||
|
Bell,
|
||||||
|
ChevronsUpDown,
|
||||||
|
CreditCard,
|
||||||
|
LogOut,
|
||||||
|
Sparkles,
|
||||||
|
} from "lucide-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">
|
||||||
|
<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-semibold">{user.name}</span>
|
||||||
|
<span className="truncate text-xs">{user.email}</span>
|
||||||
|
</div>
|
||||||
|
<ChevronsUpDown 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-semibold">{user.name}</span>
|
||||||
|
<span className="truncate text-xs">{user.email}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Sparkles />
|
||||||
|
Upgrade to Pro
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<BadgeCheck />
|
||||||
|
Account
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<CreditCard />
|
||||||
|
Billing
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Bell />
|
||||||
|
Notifications
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<LogOut />
|
||||||
|
Log out
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
201
apps/v4/components/navigation-menu-demo.tsx
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import Link from "next/link"
|
||||||
|
|
||||||
|
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 md: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="from-muted/50 to-muted flex h-full w-full flex-col justify-end rounded-md bg-linear-to-b 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-muted-foreground text-sm leading-tight">
|
||||||
|
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>
|
||||||
|
</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="text-muted-foreground line-clamp-2 text-sm leading-snug">
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
40
apps/v4/components/pagination-demo.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
62
apps/v4/components/popover-demo.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
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,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/popover"
|
||||||
|
|
||||||
|
export function PopoverDemo() {
|
||||||
|
return (
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button variant="outline">Open popover</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-80" align="start">
|
||||||
|
<div className="grid gap-4">
|
||||||
|
<div className="grid gap-1.5">
|
||||||
|
<h4 className="leading-none font-medium">Dimensions</h4>
|
||||||
|
<p className="text-muted-foreground text-sm">
|
||||||
|
Set the dimensions for the layer.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
16
apps/v4/components/progress-demo.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"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%]" />
|
||||||
|
}
|
||||||
62
apps/v4/components/radio-group-demo.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
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="hover:bg-accent/50 flex items-start gap-3 rounded-lg border p-3 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.5 font-normal">
|
||||||
|
<div className="font-medium">{plan.name}</div>
|
||||||
|
<div className="text-muted-foreground leading-snug">
|
||||||
|
{plan.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Label>
|
||||||
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
70
apps/v4/components/resizable-demo.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
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
|
||||||
|
direction="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 direction="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
|
||||||
|
direction="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
|
||||||
|
direction="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>
|
||||||
|
)
|
||||||
|
}
|
||||||
80
apps/v4/components/scroll-area-demo.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
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">
|
||||||
|
<div className="flex gap-4 p-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="text-muted-foreground pt-2 text-xs">
|
||||||
|
Photo by{" "}
|
||||||
|
<span className="text-foreground font-semibold">
|
||||||
|
{artwork.artist}
|
||||||
|
</span>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<ScrollBar orientation="horizontal" />
|
||||||
|
</ScrollArea>
|
||||||
|
)
|
||||||
|
}
|
||||||
93
apps/v4/components/select-demo.tsx
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
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-col 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
22
apps/v4/components/separator-demo.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
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-muted-foreground text-sm">
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
95
apps/v4/components/sheet-demo.tsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
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>
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
29
apps/v4/components/skeleton-demo.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Card, CardContent, CardHeader } from "@/registry/new-york-v4/ui/card"
|
||||||
|
import { Skeleton } from "@/registry/new-york-v4/ui/skeleton"
|
||||||
|
|
||||||
|
export function SkeletonDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex w-full max-w-3xl flex-col gap-6">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Skeleton className="size-10 shrink-0 rounded-full" />
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Skeleton className="h-4 w-[150px]" />
|
||||||
|
<Skeleton className="h-4 w-[100px]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full flex-col gap-4 md:flex-row">
|
||||||
|
{Array.from({ length: 3 }).map((_, index) => (
|
||||||
|
<Card key={index} className="w-full">
|
||||||
|
<CardHeader>
|
||||||
|
<Skeleton className="h-4 w-2/3" />
|
||||||
|
<Skeleton className="h-4 w-1/2" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Skeleton className="aspect-square w-full" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
44
apps/v4/components/slider-demo.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||||
|
import { Slider } from "@/registry/new-york-v4/ui/slider"
|
||||||
|
|
||||||
|
export function SliderDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex w-full max-w-sm flex-col flex-wrap gap-6 md:flex-row">
|
||||||
|
<Slider defaultValue={[50]} max={100} step={1} />
|
||||||
|
<Slider defaultValue={[25, 50]} max={100} step={1} />
|
||||||
|
<Slider defaultValue={[10, 20]} max={100} step={10} />
|
||||||
|
<div className="flex w-full items-center gap-6">
|
||||||
|
<Slider defaultValue={[50]} max={100} step={1} orientation="vertical" />
|
||||||
|
<Slider defaultValue={[25]} max={100} step={1} orientation="vertical" />
|
||||||
|
</div>
|
||||||
|
<SliderControlled />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SliderControlled() {
|
||||||
|
const [value, setValue] = React.useState([0.3, 0.7])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid w-full gap-3">
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<Label htmlFor="slider-demo-temperature">Temperature</Label>
|
||||||
|
<span className="text-muted-foreground text-sm">
|
||||||
|
{value.join(", ")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Slider
|
||||||
|
id="slider-demo-temperature"
|
||||||
|
value={value}
|
||||||
|
onValueChange={setValue}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.1}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
24
apps/v4/components/sonner-demo.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { toast } from "sonner"
|
||||||
|
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
|
||||||
|
export function SonnerDemo() {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() =>
|
||||||
|
toast("Event has been created", {
|
||||||
|
description: "Sunday, December 03, 2023 at 9:00 AM",
|
||||||
|
action: {
|
||||||
|
label: "Undo",
|
||||||
|
onClick: () => console.log("Undo"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Show Toast
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
34
apps/v4/components/switch-demo.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||||
|
import { Switch } from "@/registry/new-york-v4/ui/switch"
|
||||||
|
|
||||||
|
export function SwitchDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Switch id="switch-demo-airplane-mode" />
|
||||||
|
<Label htmlFor="switch-demo-airplane-mode">Airplane Mode</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Switch
|
||||||
|
id="switch-demo-bluetooth"
|
||||||
|
className="data-[state=checked]:bg-blue-500 dark:data-[state=checked]:bg-blue-600"
|
||||||
|
defaultChecked
|
||||||
|
/>
|
||||||
|
<Label htmlFor="switch-demo-bluetooth">Bluetooth</Label>
|
||||||
|
</div>
|
||||||
|
<Label className="flex items-center gap-6 rounded-lg border p-4 has-[[data-state=checked]]:border-blue-600">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<div className="font-medium">Share across devices</div>
|
||||||
|
<div className="text-muted-foreground text-sm font-normal">
|
||||||
|
Focus is shared across devices, and turns off when you leave the
|
||||||
|
app.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
id="switch-demo-focus-mode"
|
||||||
|
className="data-[state=checked]:bg-blue-500 dark:data-[state=checked]:bg-blue-600"
|
||||||
|
/>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
87
apps/v4/components/table-demo.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCaption,
|
||||||
|
TableCell,
|
||||||
|
TableFooter,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/registry/new-york-v4/ui/table"
|
||||||
|
|
||||||
|
const invoices = [
|
||||||
|
{
|
||||||
|
invoice: "INV001",
|
||||||
|
paymentStatus: "Paid",
|
||||||
|
totalAmount: "$250.00",
|
||||||
|
paymentMethod: "Credit Card",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
invoice: "INV002",
|
||||||
|
paymentStatus: "Pending",
|
||||||
|
totalAmount: "$150.00",
|
||||||
|
paymentMethod: "PayPal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
invoice: "INV003",
|
||||||
|
paymentStatus: "Unpaid",
|
||||||
|
totalAmount: "$350.00",
|
||||||
|
paymentMethod: "Bank Transfer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
invoice: "INV004",
|
||||||
|
paymentStatus: "Paid",
|
||||||
|
totalAmount: "$450.00",
|
||||||
|
paymentMethod: "Credit Card",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
invoice: "INV005",
|
||||||
|
paymentStatus: "Paid",
|
||||||
|
totalAmount: "$550.00",
|
||||||
|
paymentMethod: "PayPal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
invoice: "INV006",
|
||||||
|
paymentStatus: "Pending",
|
||||||
|
totalAmount: "$200.00",
|
||||||
|
paymentMethod: "Bank Transfer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
invoice: "INV007",
|
||||||
|
paymentStatus: "Unpaid",
|
||||||
|
totalAmount: "$300.00",
|
||||||
|
paymentMethod: "Credit Card",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function TableDemo() {
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<TableCaption>A list of your recent invoices.</TableCaption>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead className="w-[100px]">Invoice</TableHead>
|
||||||
|
<TableHead>Status</TableHead>
|
||||||
|
<TableHead>Method</TableHead>
|
||||||
|
<TableHead className="text-right">Amount</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{invoices.map((invoice) => (
|
||||||
|
<TableRow key={invoice.invoice}>
|
||||||
|
<TableCell className="font-medium">{invoice.invoice}</TableCell>
|
||||||
|
<TableCell>{invoice.paymentStatus}</TableCell>
|
||||||
|
<TableCell>{invoice.paymentMethod}</TableCell>
|
||||||
|
<TableCell className="text-right">{invoice.totalAmount}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={3}>Total</TableCell>
|
||||||
|
<TableCell className="text-right">$2,500.00</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
</Table>
|
||||||
|
)
|
||||||
|
}
|
||||||
106
apps/v4/components/tabs-demo.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import { AppWindowIcon, CodeIcon } from "lucide-react"
|
||||||
|
|
||||||
|
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"
|
||||||
|
import {
|
||||||
|
Tabs,
|
||||||
|
TabsContent,
|
||||||
|
TabsList,
|
||||||
|
TabsTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/tabs"
|
||||||
|
|
||||||
|
export function TabsDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<Tabs defaultValue="account" className="max-w-[400px]">
|
||||||
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
|
<TabsTrigger value="account">Account</TabsTrigger>
|
||||||
|
<TabsTrigger value="password">Password</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="account">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Account</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Make changes to your account here. Click save when you're
|
||||||
|
done.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="grid gap-6">
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="tabs-demo-name">Name</Label>
|
||||||
|
<Input id="tabs-demo-name" defaultValue="Pedro Duarte" />
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="tabs-demo-username">Username</Label>
|
||||||
|
<Input id="tabs-demo-username" defaultValue="@peduarte" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button>Save changes</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="password">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Password</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Change your password here. After saving, you'll be logged
|
||||||
|
out.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="grid gap-6">
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="tabs-demo-current">Current password</Label>
|
||||||
|
<Input id="tabs-demo-current" type="password" />
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="tabs-demo-new">New password</Label>
|
||||||
|
<Input id="tabs-demo-new" type="password" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button>Save password</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
<Tabs defaultValue="home">
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="home">Home</TabsTrigger>
|
||||||
|
<TabsTrigger value="settings">Settings</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<Tabs defaultValue="home">
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="home">Home</TabsTrigger>
|
||||||
|
<TabsTrigger value="settings" disabled>
|
||||||
|
Disabled
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
<Tabs defaultValue="preview">
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="preview">
|
||||||
|
<AppWindowIcon />
|
||||||
|
Preview
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="code">
|
||||||
|
<CodeIcon />
|
||||||
|
Code
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
89
apps/v4/components/team-switcher.tsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { ChevronsUpDown, Plus } from "lucide-react"
|
||||||
|
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuShortcut,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
||||||
|
import {
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
useSidebar,
|
||||||
|
} from "@/registry/new-york-v4/ui/sidebar"
|
||||||
|
|
||||||
|
export function TeamSwitcher({
|
||||||
|
teams,
|
||||||
|
}: {
|
||||||
|
teams: {
|
||||||
|
name: string
|
||||||
|
logo: React.ElementType
|
||||||
|
plan: string
|
||||||
|
}[]
|
||||||
|
}) {
|
||||||
|
const { isMobile } = useSidebar()
|
||||||
|
const [activeTeam, setActiveTeam] = React.useState(teams[0])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<SidebarMenuButton
|
||||||
|
size="lg"
|
||||||
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||||
|
>
|
||||||
|
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
||||||
|
<activeTeam.logo className="size-4" />
|
||||||
|
</div>
|
||||||
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
|
<span className="truncate font-semibold">
|
||||||
|
{activeTeam.name}
|
||||||
|
</span>
|
||||||
|
<span className="truncate text-xs">{activeTeam.plan}</span>
|
||||||
|
</div>
|
||||||
|
<ChevronsUpDown className="ml-auto" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||||
|
align="start"
|
||||||
|
side={isMobile ? "bottom" : "right"}
|
||||||
|
sideOffset={4}
|
||||||
|
>
|
||||||
|
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
||||||
|
Teams
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
{teams.map((team, index) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={team.name}
|
||||||
|
onClick={() => setActiveTeam(team)}
|
||||||
|
className="gap-2 p-2"
|
||||||
|
>
|
||||||
|
<div className="flex size-6 items-center justify-center rounded-xs border">
|
||||||
|
<team.logo className="size-4 shrink-0" />
|
||||||
|
</div>
|
||||||
|
{team.name}
|
||||||
|
<DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem className="gap-2 p-2">
|
||||||
|
<div className="bg-background flex size-6 items-center justify-center rounded-md border">
|
||||||
|
<Plus className="size-4" />
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground font-medium">Add team</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
40
apps/v4/components/textarea-demo.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||||
|
import { Textarea } from "@/registry/new-york-v4/ui/textarea"
|
||||||
|
|
||||||
|
export function TextareaDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex w-full flex-col gap-10">
|
||||||
|
<Textarea placeholder="Type your message here." />
|
||||||
|
<Textarea placeholder="Type your message here." aria-invalid="true" />
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="textarea-demo-message">Label</Label>
|
||||||
|
<Textarea
|
||||||
|
id="textarea-demo-message"
|
||||||
|
placeholder="Type your message here."
|
||||||
|
rows={6}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="textarea-demo-message-2">
|
||||||
|
With label and description
|
||||||
|
</Label>
|
||||||
|
<Textarea
|
||||||
|
id="textarea-demo-message-2"
|
||||||
|
placeholder="Type your message here."
|
||||||
|
rows={6}
|
||||||
|
/>
|
||||||
|
<div className="text-muted-foreground text-sm">
|
||||||
|
Type your message and press enter to send.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="textarea-demo-disabled">Disabled</Label>
|
||||||
|
<Textarea
|
||||||
|
id="textarea-demo-disabled"
|
||||||
|
placeholder="Type your message here."
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
11
apps/v4/components/theme-provider.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||||
|
|
||||||
|
export function ThemeProvider({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof NextThemesProvider>) {
|
||||||
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||||
|
}
|
||||||
35
apps/v4/components/toggle-demo.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { BoldIcon, BookmarkIcon, ItalicIcon, UnderlineIcon } from "lucide-react"
|
||||||
|
|
||||||
|
import { Toggle } from "@/registry/new-york-v4/ui/toggle"
|
||||||
|
|
||||||
|
export function ToggleDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap items-center gap-6">
|
||||||
|
<Toggle aria-label="Toggle italic">
|
||||||
|
<BoldIcon />
|
||||||
|
</Toggle>
|
||||||
|
<Toggle aria-label="Toggle italic" variant="default">
|
||||||
|
<UnderlineIcon />
|
||||||
|
</Toggle>
|
||||||
|
<Toggle aria-label="Toggle italic" variant="default" disabled>
|
||||||
|
Disabled
|
||||||
|
</Toggle>
|
||||||
|
<Toggle variant="outline" aria-label="Toggle italic">
|
||||||
|
<ItalicIcon />
|
||||||
|
Italic
|
||||||
|
</Toggle>
|
||||||
|
<Toggle
|
||||||
|
aria-label="Toggle book"
|
||||||
|
className="data-[state=on]:[&_svg]:fill-accent-foreground"
|
||||||
|
>
|
||||||
|
<BookmarkIcon />
|
||||||
|
</Toggle>
|
||||||
|
<Toggle variant="outline" aria-label="Toggle italic" size="sm">
|
||||||
|
Small
|
||||||
|
</Toggle>
|
||||||
|
<Toggle variant="outline" aria-label="Toggle italic" size="lg">
|
||||||
|
Large
|
||||||
|
</Toggle>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
75
apps/v4/components/toggle-group-demo.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react"
|
||||||
|
|
||||||
|
import {
|
||||||
|
ToggleGroup,
|
||||||
|
ToggleGroupItem,
|
||||||
|
} from "@/registry/new-york-v4/ui/toggle-group"
|
||||||
|
|
||||||
|
export function ToggleGroupDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center gap-6 md:flex-row">
|
||||||
|
<ToggleGroup type="multiple">
|
||||||
|
<ToggleGroupItem value="bold" aria-label="Toggle bold">
|
||||||
|
<BoldIcon />
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="italic" aria-label="Toggle italic">
|
||||||
|
<ItalicIcon />
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem
|
||||||
|
value="strikethrough"
|
||||||
|
aria-label="Toggle strikethrough"
|
||||||
|
>
|
||||||
|
<UnderlineIcon />
|
||||||
|
</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
<ToggleGroup
|
||||||
|
variant="outline"
|
||||||
|
type="single"
|
||||||
|
defaultValue="all"
|
||||||
|
className="*:data-[slot=toggle-group-item]:w-20"
|
||||||
|
>
|
||||||
|
<ToggleGroupItem value="all" aria-label="Toggle all">
|
||||||
|
All
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="missed" aria-label="Toggle missed">
|
||||||
|
Missed
|
||||||
|
</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
|
||||||
|
<ToggleGroup
|
||||||
|
variant="outline"
|
||||||
|
type="single"
|
||||||
|
size="sm"
|
||||||
|
defaultValue="last-24-hours"
|
||||||
|
className="*:data-[slot=toggle-group-item]:px-3"
|
||||||
|
>
|
||||||
|
<ToggleGroupItem
|
||||||
|
value="last-24-hours"
|
||||||
|
aria-label="Toggle last 24 hours"
|
||||||
|
>
|
||||||
|
Last 24 hours
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="last-7-days" aria-label="Toggle last 7 days">
|
||||||
|
Last 7 days
|
||||||
|
</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
|
||||||
|
<ToggleGroup
|
||||||
|
type="single"
|
||||||
|
size="sm"
|
||||||
|
defaultValue="last-24-hours"
|
||||||
|
className="*:data-[slot=toggle-group-item]:px-3"
|
||||||
|
>
|
||||||
|
<ToggleGroupItem
|
||||||
|
value="last-24-hours"
|
||||||
|
aria-label="Toggle last 24 hours"
|
||||||
|
>
|
||||||
|
Last 24 hours
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="last-7-days" aria-label="Toggle last 7 days">
|
||||||
|
Last 7 days
|
||||||
|
</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
49
apps/v4/components/tooltip-demo.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { InfoIcon } from "lucide-react"
|
||||||
|
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/tooltip"
|
||||||
|
|
||||||
|
export function TooltipDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-6 md:flex-row">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button variant="outline">Hover</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Add to library</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{["top", "right", "bottom", "left"].map((side) => (
|
||||||
|
<Tooltip key={side}>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button variant="outline" className="capitalize">
|
||||||
|
{side}
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side={side as "top" | "right" | "bottom" | "left"}>
|
||||||
|
<p>Add to library</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon">
|
||||||
|
<InfoIcon />
|
||||||
|
<span className="sr-only">Info</span>
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
To learn more about how this works, check out the docs. If you have
|
||||||
|
any questions, please reach out to us.
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
21
apps/v4/eslint.config.mjs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { dirname } from "path"
|
||||||
|
import { fileURLToPath } from "url"
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc"
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = dirname(__filename)
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
})
|
||||||
|
|
||||||
|
const eslintConfig = [
|
||||||
|
...compat.config({
|
||||||
|
extends: ["next/core-web-vitals", "next/typescript"],
|
||||||
|
rules: {
|
||||||
|
"@next/next/no-duplicate-head": "off",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
export default eslintConfig
|
||||||
28
apps/v4/hooks/use-meta-color.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
|
|
||||||
|
export const META_THEME_COLORS = {
|
||||||
|
light: "#ffffff",
|
||||||
|
dark: "#09090b",
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMetaColor() {
|
||||||
|
const { resolvedTheme } = useTheme()
|
||||||
|
|
||||||
|
const metaColor = React.useMemo(() => {
|
||||||
|
return resolvedTheme !== "dark"
|
||||||
|
? META_THEME_COLORS.light
|
||||||
|
: META_THEME_COLORS.dark
|
||||||
|
}, [resolvedTheme])
|
||||||
|
|
||||||
|
const setMetaColor = React.useCallback((color: string) => {
|
||||||
|
document
|
||||||
|
.querySelector('meta[name="theme-color"]')
|
||||||
|
?.setAttribute("content", color)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return {
|
||||||
|
metaColor,
|
||||||
|
setMetaColor,
|
||||||
|
}
|
||||||
|
}
|
||||||
133
apps/v4/lib/registry.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import { Index } from "@/__registry__"
|
||||||
|
import { registryItemSchema } from "shadcn/registry"
|
||||||
|
|
||||||
|
const memoizedIndex: typeof Index = Object.fromEntries(
|
||||||
|
Object.entries(Index).map(([style, items]) => [style, { ...items }])
|
||||||
|
)
|
||||||
|
|
||||||
|
export function getRegistryComponent(name: string) {
|
||||||
|
return memoizedIndex[name]?.component
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getRegistryItem(name: string) {
|
||||||
|
const item = memoizedIndex[name]
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert all file paths to object.
|
||||||
|
// TODO: remove when we migrate to new registry.
|
||||||
|
item.files = item.files.map((file: unknown) =>
|
||||||
|
typeof file === "string" ? { path: file } : file
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fail early before doing expensive file operations.
|
||||||
|
const result = registryItemSchema.safeParse(item)
|
||||||
|
if (!result.success) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const files: typeof result.data.files = []
|
||||||
|
// for (const file of item.files) {
|
||||||
|
// const content = await getFileContent(file)
|
||||||
|
// const relativePath = path.relative(process.cwd(), file.path)
|
||||||
|
|
||||||
|
// files.push({
|
||||||
|
// ...file,
|
||||||
|
// path: relativePath,
|
||||||
|
// content,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Get meta.
|
||||||
|
// Assume the first file is the main file.
|
||||||
|
// const meta = await getFileMeta(files[0].path)
|
||||||
|
|
||||||
|
// Fix file paths.
|
||||||
|
// files = fixFilePaths(files)
|
||||||
|
|
||||||
|
const parsed = registryItemSchema.safeParse({
|
||||||
|
...result.data,
|
||||||
|
files,
|
||||||
|
// meta,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!parsed.success) {
|
||||||
|
console.error(parsed.error.message)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fixImport(content: string) {
|
||||||
|
const regex = /@\/(.+?)\/((?:.*?\/)?(?:components|ui|hooks|lib))\/([\w-]+)/g
|
||||||
|
|
||||||
|
const replacement = (
|
||||||
|
match: string,
|
||||||
|
path: string,
|
||||||
|
type: string,
|
||||||
|
component: string
|
||||||
|
) => {
|
||||||
|
if (type.endsWith("components")) {
|
||||||
|
return `@/components/${component}`
|
||||||
|
} else if (type.endsWith("ui")) {
|
||||||
|
return `@/components/ui/${component}`
|
||||||
|
} else if (type.endsWith("hooks")) {
|
||||||
|
return `@/hooks/${component}`
|
||||||
|
} else if (type.endsWith("lib")) {
|
||||||
|
return `@/lib/${component}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
return content.replace(regex, replacement)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FileTree = {
|
||||||
|
name: string
|
||||||
|
path?: string
|
||||||
|
children?: FileTree[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createFileTreeForRegistryItemFiles(
|
||||||
|
files: Array<{ path: string; target?: string }>
|
||||||
|
) {
|
||||||
|
const root: FileTree[] = []
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const path = file.target ?? file.path
|
||||||
|
const parts = path.split("/")
|
||||||
|
let currentLevel = root
|
||||||
|
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
const part = parts[i]
|
||||||
|
const isFile = i === parts.length - 1
|
||||||
|
const existingNode = currentLevel.find((node) => node.name === part)
|
||||||
|
|
||||||
|
if (existingNode) {
|
||||||
|
if (isFile) {
|
||||||
|
// Update existing file node with full path
|
||||||
|
existingNode.path = path
|
||||||
|
} else {
|
||||||
|
// Move to next level in the tree
|
||||||
|
currentLevel = existingNode.children!
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const newNode: FileTree = isFile
|
||||||
|
? { name: part, path }
|
||||||
|
: { name: part, children: [] }
|
||||||
|
|
||||||
|
currentLevel.push(newNode)
|
||||||
|
|
||||||
|
if (!isFile) {
|
||||||
|
currentLevel = newNode.children!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return root
|
||||||
|
}
|
||||||
10
apps/v4/lib/utils.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { clsx, type ClassValue } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function absoluteUrl(path: string) {
|
||||||
|
return `${process.env.NEXT_PUBLIC_APP_URL}${path}`
|
||||||
|
}
|
||||||
21
apps/v4/next.config.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import type { NextConfig } from "next"
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
outputFileTracingIncludes: {
|
||||||
|
"/*": ["./registry/**/*"],
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "avatars.githubusercontent.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "images.unsplash.com",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default nextConfig
|
||||||
135
apps/v4/package.json
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
{
|
||||||
|
"name": "v4",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev --turbopack",
|
||||||
|
"build": "pnpm --filter=shadcn build && next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint",
|
||||||
|
"lint:fix": "next lint --fix",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"format:write": "prettier --write \"**/*.{ts,tsx,mdx}\" --cache",
|
||||||
|
"format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache",
|
||||||
|
"registry:build": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts && prettier --log-level silent --write \"registry/**/*.{ts,tsx,json,mdx}\" --cache"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hookform/resolvers": "^3.10.0",
|
||||||
|
"@radix-ui/react-accessible-icon": "^1.1.1",
|
||||||
|
"@radix-ui/react-accordion": "^1.2.2",
|
||||||
|
"@radix-ui/react-alert-dialog": "^1.1.5",
|
||||||
|
"@radix-ui/react-aspect-ratio": "^1.1.1",
|
||||||
|
"@radix-ui/react-avatar": "^1.1.2",
|
||||||
|
"@radix-ui/react-checkbox": "^1.1.3",
|
||||||
|
"@radix-ui/react-collapsible": "^1.1.2",
|
||||||
|
"@radix-ui/react-context-menu": "^2.2.5",
|
||||||
|
"@radix-ui/react-dialog": "^1.1.5",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.1.5",
|
||||||
|
"@radix-ui/react-hover-card": "^1.1.5",
|
||||||
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
|
"@radix-ui/react-menubar": "^1.1.5",
|
||||||
|
"@radix-ui/react-navigation-menu": "^1.2.4",
|
||||||
|
"@radix-ui/react-popover": "^1.1.5",
|
||||||
|
"@radix-ui/react-portal": "^1.1.3",
|
||||||
|
"@radix-ui/react-progress": "^1.1.1",
|
||||||
|
"@radix-ui/react-radio-group": "^1.2.2",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||||
|
"@radix-ui/react-select": "^2.1.5",
|
||||||
|
"@radix-ui/react-separator": "^1.1.1",
|
||||||
|
"@radix-ui/react-slider": "^1.2.2",
|
||||||
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
|
"@radix-ui/react-switch": "^1.1.2",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.2",
|
||||||
|
"@radix-ui/react-toast": "^1.2.5",
|
||||||
|
"@radix-ui/react-toggle": "^1.1.1",
|
||||||
|
"@radix-ui/react-toggle-group": "^1.1.1",
|
||||||
|
"@radix-ui/react-tooltip": "^1.1.7",
|
||||||
|
"@tailwindcss/postcss": "^4.0.1",
|
||||||
|
"@vercel/analytics": "^1.2.2",
|
||||||
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "^1.0.4",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
|
"embla-carousel-autoplay": "8.5.2",
|
||||||
|
"embla-carousel-react": "8.5.2",
|
||||||
|
"geist": "^1.2.2",
|
||||||
|
"input-otp": "^1.4.2",
|
||||||
|
"lucide-react": "0.474.0",
|
||||||
|
"next": "15.2.0-canary.33",
|
||||||
|
"next-themes": "^0.4.3",
|
||||||
|
"postcss": "^8.5.1",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-day-picker": "^8.7.1",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"react-hook-form": "^7.54.2",
|
||||||
|
"react-resizable-panels": "^2.1.7",
|
||||||
|
"recharts": "2.15.1",
|
||||||
|
"rimraf": "^6.0.1",
|
||||||
|
"shadcn": "2.3.0",
|
||||||
|
"sonner": "^1.7.4",
|
||||||
|
"tailwind-merge": "^3.0.1",
|
||||||
|
"tailwindcss": "^4.0.1",
|
||||||
|
"tailwindcss-animate": "^1.0.6",
|
||||||
|
"ts-morph": "^22.0.0",
|
||||||
|
"vaul": "1.1.2",
|
||||||
|
"zod": "^3.21.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@ianvs/prettier-plugin-sort-imports": "^3.7.2",
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"@typescript-eslint/parser": "^5.59.7",
|
||||||
|
"eslint": "^9",
|
||||||
|
"eslint-config-next": "15.2.0-canary.33",
|
||||||
|
"prettier": "^3.4.2",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"importOrder": [
|
||||||
|
"^(react/(.*)$)|^(react$)",
|
||||||
|
"^(next/(.*)$)|^(next$)",
|
||||||
|
"<THIRD_PARTY_MODULES>",
|
||||||
|
"",
|
||||||
|
"^@workspace/(.*)$",
|
||||||
|
"",
|
||||||
|
"^types$",
|
||||||
|
"^@/types/(.*)$",
|
||||||
|
"^@/config/(.*)$",
|
||||||
|
"^@/lib/(.*)$",
|
||||||
|
"^@/hooks/(.*)$",
|
||||||
|
"^@/components/ui/(.*)$",
|
||||||
|
"^@/components/(.*)$",
|
||||||
|
"^@/registry/(.*)$",
|
||||||
|
"^@/styles/(.*)$",
|
||||||
|
"^@/app/(.*)$",
|
||||||
|
"^@/www/(.*)$",
|
||||||
|
"",
|
||||||
|
"^[./]"
|
||||||
|
],
|
||||||
|
"importOrderSeparation": false,
|
||||||
|
"importOrderSortSpecifiers": true,
|
||||||
|
"importOrderBuiltinModulesToTop": true,
|
||||||
|
"importOrderParserPlugins": [
|
||||||
|
"typescript",
|
||||||
|
"jsx",
|
||||||
|
"decorators-legacy"
|
||||||
|
],
|
||||||
|
"importOrderMergeDuplicateImports": true,
|
||||||
|
"importOrderCombineTypeAndValueImports": true,
|
||||||
|
"plugins": [
|
||||||
|
"@ianvs/prettier-plugin-sort-imports",
|
||||||
|
"prettier-plugin-tailwindcss"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
6
apps/v4/postcss.config.mjs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
"@tailwindcss/postcss": {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
export default config
|
||||||
BIN
apps/v4/public/avatars/shadcn.jpg
Normal file
|
After Width: | Height: | Size: 23 KiB |
1
apps/v4/public/file.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
1
apps/v4/public/globe.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
1
apps/v4/public/next.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
1
apps/v4/public/placeholder.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
1
apps/v4/public/vercel.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 128 B |
1
apps/v4/public/window.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||||
|
After Width: | Height: | Size: 385 B |