Compare commits

..

50 Commits

Author SHA1 Message Date
github-actions[bot]
e75e7b3866 chore(release): version packages (#7830)
* chore(release): version packages

* deps: update lock

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-07-16 17:52:23 +04:00
shadcn
ed5237c231 fix: handling of shouldOverwriteCssVars (#7829)
* fix(shadcn): handling of shouldOverwriteCssVars

* chore: changeset
2025-07-16 17:44:25 +04:00
shadcn
f85ca066dc deps: update 2025-07-11 18:41:06 +04:00
github-actions[bot]
54e66d4450 chore(release): version packages (#7758)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-07-11 18:23:59 +04:00
shadcn
6c341c16ae feat: fix safe target and add docs (#7795)
* feat: fix safe target and add docs

* chore: add changeset

* fix: changelog
2025-07-11 18:19:34 +04:00
shadcn
06d03d64f4 feat(shadcn): add support for universal registry item (#7782)
* feat(shadcn): add support for universal registry item

* chore: changeset
2025-07-10 20:17:45 +04:00
shadcn
6407a3b330 fix: chart 2025-07-09 15:52:31 +04:00
shadcn
96b15f6090 feat: update command menu pages and filtering (#7771) 2025-07-09 12:43:50 +04:00
shadcn
2fe9cf6d26 fix: pin recharts until we upgrade to v3 (#7769)
* fix: pin recharts

* docs: add v3 callout
2025-07-09 12:10:40 +04:00
Ehsanullah Haidary
728cb4cfa5 fix(component) Date Picker Dropdown values not visible in chrome, opera and edge (#7724)
* fix(component) Date Picker Dropdown values not visible in chrome, opera and edge

* fix(component) bg-popover added to dropdown in calendar.tsx to fix Date Picker Dropdown values not visible

* chore: build registry

* style: fix

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-07-09 11:43:15 +04:00
shadcn
db93787712 feat(shadcn): implement registry safe path (#7757)
* feat(shadcn): implement registry safe path

* chore: changeset

* style(shadcn): formatting

* fix
2025-07-08 20:25:14 +04:00
shadcn
1cdd6c1645 Merge branch 'main' of github.com:shadcn-ui/ui 2025-07-07 21:03:25 +04:00
shadcn
4983c6e1f4 chore: changelog 2025-07-07 21:03:06 +04:00
github-actions[bot]
7443edcfb0 chore(release): version packages (#7719)
* chore(release): version packages

* deps: lock

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-07-07 20:52:36 +04:00
shadcn
9d9a33be52 fix: margin 2025-07-07 15:10:32 +04:00
shadcn
d544a7f7a5 feat: refactor registryDependencies resolution (#7720)
* feat(shadcn): refactor registry dependencies resolution

* chore: changeset

* fix

* style: fix some code style
2025-07-01 17:56:50 +04:00
shadcn
48fe0d709f feat(shadcn): add file support (#7717)
* feat(shadcn): add file support

* fix: format

* fix: types

* feat(shadcn): update init and add description

* docs: update docs for cli

* chore: add changeset
2025-07-01 17:06:17 +04:00
Kitsune
ed244ea0b5 fix(cli): detect vinxi-based frameworks (@tanstack/start, SolidStart, ...) (#6330)
* fix(cli): detect vinxi-based frameworks

* chore: add changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-07-01 16:58:07 +04:00
Wolfr
b8fede1742 docs(v4): link to obra figma kit (#7643) 2025-06-30 11:45:49 +04:00
github-actions[bot]
84d6c83bad chore(release): version packages (#7626)
* chore(release): version packages

* chore(release): version packages

* deps: update lock

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-06-18 16:01:20 +04:00
xabierlameiro.com
5b8ee41511 fix(cli): correct function name typo unnsetSpreadElements to unsetSpreadElements (#7609)
* fix(cli): correct function name typo unnsetSpreadElements to unsetSpreadElements

* chore: add changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-06-18 11:44:59 +04:00
shadcn
7c3d34cdc9 chore: fix changeset (#7640)
* fix(shadcn): update plugin handling

* style(shadcn): format fix

* docs(www): add docs for plugins

* chore: add changeset
2025-06-18 11:29:23 +04:00
shadcn
56c4c83511 fix(shadcn): update plugin handling (#7632)
* fix(shadcn): update plugin handling

* style(shadcn): format fix

* docs(www): add docs for plugins
2025-06-18 11:03:36 +04:00
shadcn
2821cb0e39 chore: move cli to deprecated (#7631) 2025-06-17 12:32:10 +04:00
Wolfr
3c87402de2 Add newly available Figma kit to docs (#7604)
Co-authored-by: shadcn <m@shadcn.com>
2025-06-16 16:21:26 +04:00
xabierlameiro.com
20a88e1f15 fix(components): resolve duplicate id conflict in calendar-24 component (#7611)
* fix(components): resolve duplicate id conflict in calendar-24 component

- Changed Button id from 'date' to 'date-picker'
- Changed Input id from 'time' to 'time-picker'
- Updated corresponding Label htmlFor attributes to match new unique IDs

Fixes #7561

* chore: rebuild registry after calendar-24 fixes
2025-06-16 16:13:51 +04:00
Zach Nugent
cb19ab8464 feat(shadcn): add support for updating dependencies with expo-cli for RN compatibility (#7540)
* feat(shadcn): add support for updating dependencies with expo-cli for RN compatibility

* feat(shadcn): add expo as a framework

* fix: update the contributing command for registry

* refactor(shadcn): update dependencies install functionality

* chore: changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-06-16 16:05:41 +04:00
github-actions[bot]
cf1851ca09 chore(release): version packages (#7625)
* chore(release): version packages

* deps: update lock

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-06-16 15:45:23 +04:00
Manuel Schiller
c86c27a2ff fix TanStack Start detection (#7601)
* fix tanstack start detection

* chore: changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-06-16 15:24:41 +04:00
Gaëtan H
8847126c65 chore(vscode): set custom Tailwind config path for monorepo UI (#7618)
Co-authored-by: shadcn <m@shadcn.com>
2025-06-16 15:18:27 +04:00
shadcn
65350857a4 ci: fix stale bot (#7624) 2025-06-16 15:02:53 +04:00
shadcn
40c7473c7e fix(www): update open-in-v0-cta.tsx 2025-06-14 06:20:16 +04:00
Taesu
4698ee960f chore: update react-day-picker version to match updated calendar component (#7585)
Co-authored-by: shadcn <m@shadcn.com>
2025-06-12 15:44:40 +04:00
github-actions[bot]
2ae0e5a07b chore(release): version packages (#7595)
* chore(release): version packages

* deps: install

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-06-11 20:06:18 +04:00
shadcn
431af4f7ff fix(shadcn): semicolon in code style (#7594)
* fix(shadcn): handle semicolon in code style

* chore: changeset

* fix: format
2025-06-11 19:54:04 +04:00
github-actions[bot]
c1357982e8 chore(release): version packages (#7591)
* chore(release): version packages

* deps: update

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-06-11 13:57:06 +04:00
shadcn
92cfb9a30e fix(shadcn): flaky create-project tests (#7590)
* fix(shadcn): flaky create-project tests

* fix

* fix
2025-06-11 13:50:35 +04:00
shadcn
c5d90c718a feat: add migrate radix command (#7586)
* feat(shadcn): add migrate-radix command

* feat(shadcn): fix and test edge cases

* test(shadcn): add tests for all primitives

* fix(shadcn): edge cases and add yes option

* fix

* chore(shadcn): add changeset

* style: fix code styles

* docs: update changelog

* fix: format

* feat: update changelog

* fix: format
2025-06-11 13:20:47 +04:00
github-actions[bot]
b1fd13ffb0 chore(release): version packages (#7574)
* chore(release): version packages

* deps: update

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-06-09 16:29:06 +04:00
shadcn
3119f94d47 feat: only show npm flag for react 18 deps (#7573)
* feat: only show npm flag for react 18 deps

* tests: update
2025-06-09 16:17:44 +04:00
shadcn
057d97dd25 docs: changelog 2025-06-09 16:00:30 +04:00
shadcn
a659e09353 feat: update calendar changelog 2025-06-09 15:18:04 +04:00
shadcn
82d94eee38 feat: calendar v2 (#7551)
* feat(v4): upgrade calendar

* feat: more calendar examples

* fix: remove grid

* feat: more examples

* feat: more examples

* fix

* feat: update examples

* fix: dark mode

* fix: calendar in dark mode

* fix: examples

* fix

* docs: update calendar docs

* feat: update cmdk

* fix: block viewer patterns

* feat: update new-york and default

* fix: docs and examples

* fix: command menu

* feat: remove blocks from cmdk

* fix

* fix: calendar 13

* fix: format

* fix

* feat: update calendar default
2025-06-06 20:09:53 +04:00
shadcn
9cbc6641d9 fix: docs nav 2025-05-31 14:42:35 +04:00
shadcn
e0bec146fa fix: async layout 2025-05-31 14:29:56 +04:00
shadcn
65223896da feat: layout toggle (#7515)
* feat: implement fixed layout

* feat: track layout

* fix
2025-05-31 14:21:19 +04:00
shadcn
16ee16b053 fix: themes page 2025-05-30 18:00:51 +04:00
shadcn
b5cf967848 feat: add themes page back (#7503)
* feat: add themes page back

* fix: values

* fix: redirect
2025-05-30 17:45:02 +04:00
shadcn
12b7833d70 fix: remove unused cn 2025-05-30 15:31:34 +04:00
shadcn
ec73150490 feat: add missing links 2025-05-30 15:29:18 +04:00
499 changed files with 25063 additions and 782 deletions

View File

@@ -18,15 +18,15 @@ jobs:
repo-token: ${{ secrets.STALE_TOKEN }}
ascending: true
days-before-issue-close: 7
days-before-issue-stale: 365 # ~2 years
days-before-issue-stale: 365
days-before-pr-stale: -1
days-before-pr-close: -1
remove-issue-stale-when-updated: true
stale-issue-label: "stale?"
exempt-issue-labels: "roadmap,next,bug"
stale-issue-message: "This issue has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless theres further input. If you believe this issue is still relevant, please leave a comment or provide updated details. Thank you."
close-issue-message: "This issue has been automatically closed due to one year of inactivity. If youre still experiencing a similar problem or have additional details to share, please open a new issue following our current issue template. Your updated report helps us investigate and address concerns more efficiently. Thank you for your understanding!"
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
exempt-issue-labels: "roadmap,next"
stale-issue-message: "This issue has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless theres further input. If you believe this issue is still relevant, please leave a comment or provide updated details. Thank you. (This is an automated message)"
close-issue-message: "This issue has been automatically closed due to one year of inactivity. If youre still experiencing a similar problem or have additional details to share, please open a new issue following our current issue template. Your updated report helps us investigate and address concerns more efficiently. Thank you for your understanding! (This is an automated message)"
operations-per-run: 300
- uses: actions/stale@v9
id: pr-state
name: "Mark stale PRs, close stale PRs"
@@ -36,10 +36,10 @@ jobs:
days-before-issue-close: -1
days-before-issue-stale: -1
days-before-pr-close: 7
days-before-pr-stale: 365 # PRs with no activity in over 90 days will be marked as stale
days-before-pr-stale: 365
remove-pr-stale-when-updated: true
exempt-pr-labels: "roadmap,nex,awaiting-approval,work-in-progress"
exempt-pr-labels: "roadmap,next,bug"
stale-pr-label: "stale?"
stale-pr-message: "This PR has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless theres further input. If you believe this PR is still relevant, please leave a comment or provide updated details. Thank you."
close-pr-message: "This PR has been automatically closed due to one year of inactivity. Thank you for your understanding!"
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
stale-pr-message: "This PR has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless theres further input. If you believe this PR is still relevant, please leave a comment or provide updated details. Thank you. (This is an automated message)"
close-pr-message: "This PR has been automatically closed due to one year of inactivity. Thank you for your understanding! (This is an automated message)"
operations-per-run: 300

View File

@@ -98,7 +98,7 @@ To run the CLI locally, you can follow the workflow:
1. Start by running the registry (main site) to make sure the components are up to date:
```bash
pnpm www:dev
pnpm v4:dev
```
2. Run the development script for the CLI:

2
apps/v4/.env.example Normal file
View File

@@ -0,0 +1,2 @@
NEXT_PUBLIC_V0_URL=https://v0.dev
NEXT_PUBLIC_APP_URL=http://localhost:4000

1
apps/v4/.gitignore vendored
View File

@@ -32,6 +32,7 @@ yarn-error.log*
# env files (can opt-in for committing if needed)
.env*
!.env.example
# vercel
.vercel

View File

@@ -4,3 +4,4 @@ node_modules
build
.contentlayer
registry/__index__.tsx
content/docs/components/calendar.mdx

View File

@@ -48,15 +48,23 @@ export default async function ChartPage({ params }: ChartPageProps) {
{type.charAt(0).toUpperCase() + type.slice(1)} Charts
</h2>
<div className="grid flex-1 scroll-mt-20 items-stretch gap-10 md:grid-cols-2 md:gap-6 lg:grid-cols-3 xl:gap-10">
{chartList.map((chart) => (
<ChartDisplay
key={chart.id}
name={chart.id}
className={cn(chart.fullWidth && "md:col-span-2 lg:col-span-3")}
>
<chart.component />
</ChartDisplay>
))}
{Array.from({ length: 12 }).map((_, index) => {
const chart = chartList[index]
return chart ? (
<ChartDisplay
key={chart.id}
name={chart.id}
className={cn(chart.fullWidth && "md:col-span-2 lg:col-span-3")}
>
<chart.component />
</ChartDisplay>
) : (
<div
key={`empty-${index}`}
className="hidden aspect-square w-full rounded-lg border border-dashed xl:block"
/>
)
})}
</div>
</div>
)

View File

@@ -47,7 +47,7 @@ export default function ChartsLayout({
children: React.ReactNode
}) {
return (
<div>
<>
<PageHeader>
<Announcement />
<PageHeaderHeading>{title}</PageHeaderHeading>
@@ -65,11 +65,11 @@ export default function ChartsLayout({
<ChartsNav />
<ThemeSelector className="mr-4 hidden md:flex" />
</PageNav>
<div className="container-wrapper section-soft">
<div className="container-wrapper section-soft flex-1">
<div className="container pb-6">
<section className="theme-container">{children}</section>
</div>
</div>
</div>
</>
)
}

View File

@@ -1,13 +1,18 @@
import Link from "next/link"
import { notFound } from "next/navigation"
import { mdxComponents } from "@/mdx-components"
import { IconArrowLeft, IconArrowRight } from "@tabler/icons-react"
import {
IconArrowLeft,
IconArrowRight,
IconArrowUpRight,
} from "@tabler/icons-react"
import { findNeighbour } from "fumadocs-core/server"
import { source } from "@/lib/source"
import { absoluteUrl } from "@/lib/utils"
import { DocsTableOfContents } from "@/components/docs-toc"
import { OpenInV0Cta } from "@/components/open-in-v0-cta"
import { Badge } from "@/registry/new-york-v4/ui/badge"
import { Button } from "@/registry/new-york-v4/ui/button"
export const revalidate = false
@@ -80,6 +85,9 @@ export default async function Page(props: {
const MDX = doc.body
const neighbours = await findNeighbour(source.pageTree, page.url)
// @ts-expect-error - revisit fumadocs types.
const links = doc.links
return (
<div
data-slot="docs"
@@ -90,11 +98,11 @@ export default async function Page(props: {
<div className="mx-auto flex w-full max-w-2xl min-w-0 flex-1 flex-col gap-8 px-4 py-6 text-neutral-800 md:px-0 lg:py-8 dark:text-neutral-300">
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<div className="flex items-start justify-between">
<h1 className="scroll-m-20 text-4xl font-semibold tracking-tight sm:text-3xl xl:text-4xl">
{doc.title}
</h1>
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 pt-1.5">
{neighbours.previous && (
<Button
variant="secondary"
@@ -129,6 +137,24 @@ export default async function Page(props: {
</p>
)}
</div>
{links ? (
<div className="flex items-center space-x-2 pt-4">
{links?.doc && (
<Badge asChild variant="secondary">
<Link href={links.doc} target="_blank" rel="noreferrer">
Docs <IconArrowUpRight />
</Link>
</Badge>
)}
{links?.api && (
<Badge asChild variant="secondary">
<Link href={links.api} target="_blank" rel="noreferrer">
API Reference <IconArrowUpRight />
</Link>
</Badge>
)}
</div>
) : null}
</div>
<div className="w-full flex-1 *:data-[slot=alert]:first:mt-0">
<MDX components={mdxComponents} />

View File

@@ -8,8 +8,8 @@ export default function DocsLayout({
children: React.ReactNode
}) {
return (
<div className="container-wrapper flex flex-1 flex-col">
<SidebarProvider className="min-h-min flex-1 items-start px-0 [--sidebar-width:220px] [--top-spacing:0] lg:grid lg:grid-cols-[var(--sidebar-width)_minmax(0,1fr)] lg:[--sidebar-width:240px] lg:[--top-spacing:calc(var(--spacing)*4)]">
<div className="container-wrapper flex flex-1 flex-col px-2">
<SidebarProvider className="3xl:fixed:container 3xl:fixed:px-3 min-h-min flex-1 items-start px-0 [--sidebar-width:220px] [--top-spacing:0] lg:grid lg:grid-cols-[var(--sidebar-width)_minmax(0,1fr)] lg:[--sidebar-width:240px] lg:[--top-spacing:calc(var(--spacing)*4)]">
<DocsSidebar tree={source.pageTree} />
<div className="h-full w-full">{children}</div>
</SidebarProvider>

View File

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

View File

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

View File

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

View File

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

View File

@@ -34,7 +34,7 @@ export async function generateMetadata({
const description = item.description
return {
title: `${item.name}${item.description ? ` - ${item.description}` : ""}`,
title: item.description,
description,
openGraph: {
title,

View File

@@ -1,9 +1,9 @@
import type { Metadata } from "next"
import { cookies } from "next/headers"
import { META_THEME_COLORS, siteConfig } from "@/lib/config"
import { fontVariables } from "@/lib/fonts"
import { cn } from "@/lib/utils"
import { LayoutProvider } from "@/hooks/use-layout"
import { ActiveThemeProvider } from "@/components/active-theme"
import { Analytics } from "@/components/analytics"
import { TailwindIndicator } from "@/components/tailwind-indicator"
@@ -58,14 +58,11 @@ export const metadata: Metadata = {
manifest: `${siteConfig.url}/site.webmanifest`,
}
export default async function RootLayout({
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
const cookieStore = await cookies()
const activeThemeValue = cookieStore.get("active_theme")?.value
return (
<html lang="en" suppressHydrationWarning>
<head>
@@ -76,6 +73,9 @@ export default async function RootLayout({
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}')
}
if (localStorage.layout) {
document.documentElement.classList.add('layout-' + localStorage.layout)
}
} catch (_) {}
`,
}}
@@ -84,18 +84,19 @@ export default async function RootLayout({
</head>
<body
className={cn(
"text-foreground group/body overscroll-none font-sans antialiased [--footer-height:calc(var(--spacing)*14)] [--header-height:calc(var(--spacing)*14)]",
activeThemeValue ? `theme-${activeThemeValue}` : "",
"text-foreground group/body overscroll-none font-sans antialiased [--footer-height:calc(var(--spacing)*14)] [--header-height:calc(var(--spacing)*14)] xl:[--footer-height:calc(var(--spacing)*24)]",
fontVariables
)}
>
<ThemeProvider>
<ActiveThemeProvider initialTheme={activeThemeValue}>
{children}
<TailwindIndicator />
<Toaster position="top-center" />
<Analytics />
</ActiveThemeProvider>
<LayoutProvider>
<ActiveThemeProvider>
{children}
<TailwindIndicator />
<Toaster position="top-center" />
<Analytics />
</ActiveThemeProvider>
</LayoutProvider>
</ThemeProvider>
</body>
</html>

View File

@@ -8,15 +8,8 @@ import {
useState,
} from "react"
const COOKIE_NAME = "active_theme"
const DEFAULT_THEME = "default"
function setThemeCookie(theme: string) {
if (typeof window === "undefined") return
document.cookie = `${COOKIE_NAME}=${theme}; path=/; max-age=31536000; SameSite=Lax; ${window.location.protocol === "https:" ? "Secure;" : ""}`
}
type ThemeContextType = {
activeTheme: string
setActiveTheme: (theme: string) => void
@@ -36,8 +29,6 @@ export function ActiveThemeProvider({
)
useEffect(() => {
setThemeCookie(activeTheme)
Array.from(document.body.classList)
.filter((className) => className.startsWith("theme-"))
.forEach((className) => {

View File

@@ -1,3 +1,14 @@
import Link from "next/link"
import { ArrowRightIcon } from "lucide-react"
import { Badge } from "@/registry/new-york-v4/ui/badge"
export function Announcement() {
return null
return (
<Badge asChild variant="secondary" className="rounded-full">
<Link href="/docs/components/calendar">
New Calendar Component <ArrowRightIcon />
</Link>
</Badge>
)
}

View File

@@ -7,7 +7,9 @@ import {
createFileTreeForRegistryItemFiles,
getRegistryItem,
} from "@/lib/registry"
import { cn } from "@/lib/utils"
import { BlockViewer } from "@/components/block-viewer"
import { ComponentPreview } from "@/components/component-preview"
export async function BlockDisplay({ name }: { name: string }) {
const item = await getCachedRegistryItem(name)
@@ -22,7 +24,16 @@ export async function BlockDisplay({ name }: { name: string }) {
])
return (
<BlockViewer item={item} tree={tree} highlightedFiles={highlightedFiles} />
<BlockViewer item={item} tree={tree} highlightedFiles={highlightedFiles}>
<ComponentPreview
name={item.name}
hideCode
className={cn(
"my-0 **:[.preview]:h-auto **:[.preview]:p-4 **:[.preview>.p-6]:p-0",
item.meta?.containerClassName
)}
/>
</BlockViewer>
)
}

View File

@@ -11,6 +11,7 @@ import {
Folder,
Fullscreen,
Monitor,
RotateCw,
Smartphone,
Tablet,
Terminal,
@@ -21,6 +22,7 @@ import { z } from "zod"
import { trackEvent } from "@/lib/events"
import { createFileTreeForRegistryItemFiles, FileTree } from "@/lib/registry"
import { cn } from "@/lib/utils"
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import { getIconForLanguageExtension } from "@/components/icons"
import { OpenInV0Button } from "@/components/open-in-v0-button"
@@ -66,6 +68,8 @@ type BlockViewerContext = {
highlightedContent: string
})[]
| null
iframeKey?: number
setIframeKey?: React.Dispatch<React.SetStateAction<number>>
}
const BlockViewerContext = React.createContext<BlockViewerContext | null>(null)
@@ -91,6 +95,7 @@ function BlockViewerProvider({
BlockViewerContext["activeFile"]
>(highlightedFiles?.[0].target ?? null)
const resizablePanelRef = React.useRef<ImperativePanelHandle>(null)
const [iframeKey, setIframeKey] = React.useState(0)
return (
<BlockViewerContext.Provider
@@ -103,12 +108,14 @@ function BlockViewerProvider({
setActiveFile,
tree,
highlightedFiles,
iframeKey,
setIframeKey,
}}
>
<div
id={item.name}
data-view={view}
className="group/block-view-wrapper flex min-w-0 flex-col-reverse items-stretch gap-4 overflow-hidden md:flex-col"
className="group/block-view-wrapper flex min-w-0 scroll-mt-24 flex-col-reverse items-stretch gap-4 overflow-hidden md:flex-col"
style={
{
"--height": item.meta?.iframeHeight ?? "930px",
@@ -122,30 +129,30 @@ function BlockViewerProvider({
}
function BlockViewerToolbar() {
const { setView, view, item, resizablePanelRef } = useBlockViewer()
const { setView, view, item, resizablePanelRef, setIframeKey } =
useBlockViewer()
const { copyToClipboard, isCopied } = useCopyToClipboard()
return (
<div className="flex w-full items-center gap-2 pl-2 md:pr-[14px]">
<div className="hidden w-full items-center gap-2 pl-2 md:pr-6 lg:flex">
<Tabs
value={view}
onValueChange={(value) => setView(value as "preview" | "code")}
className="hidden lg:flex"
>
<TabsList className="grid h-8 grid-cols-2 items-center rounded-md p-1 *:data-[slot=tabs-trigger]:h-6 *:data-[slot=tabs-trigger]:rounded-sm *:data-[slot=tabs-trigger]:px-2 *:data-[slot=tabs-trigger]:text-xs">
<TabsTrigger value="preview">Preview</TabsTrigger>
<TabsTrigger value="code">Code</TabsTrigger>
</TabsList>
</Tabs>
<Separator orientation="vertical" className="mx-2 hidden !h-4 lg:flex" />
<Separator orientation="vertical" className="mx-2 !h-4" />
<a
href={`#${item.name}`}
className="flex-1 text-center text-sm font-medium underline-offset-2 hover:underline md:flex-auto md:text-left"
>
{item.description}
{item.description?.replace(/\.$/, "")}
</a>
<div className="ml-auto hidden items-center gap-2 md:flex">
<div className="hidden h-8 items-center gap-1.5 rounded-md border p-1 shadow-none lg:flex">
<div className="ml-auto flex items-center gap-2">
<div className="h-8 items-center gap-1.5 rounded-md border p-1 shadow-none">
<ToggleGroup
type="single"
defaultValue="100"
@@ -179,15 +186,27 @@ function BlockViewerToolbar() {
<Fullscreen />
</Link>
</Button>
<Separator orientation="vertical" className="!h-4" />
<Button
size="icon"
variant="ghost"
className="size-6 rounded-sm p-0"
title="Refresh Preview"
onClick={() => {
if (setIframeKey) {
setIframeKey((k) => k + 1)
}
}}
>
<RotateCw />
<span className="sr-only">Refresh Preview</span>
</Button>
</ToggleGroup>
</div>
<Separator
orientation="vertical"
className="mx-1 hidden !h-4 lg:flex"
/>
<Separator orientation="vertical" className="mx-1 !h-4" />
<Button
variant="outline"
className="hidden w-fit gap-1 px-2 shadow-none md:flex"
className="w-fit gap-1 px-2 shadow-none"
size="sm"
onClick={() => {
copyToClipboard(`npx shadcn@latest add ${item.name}`)
@@ -196,50 +215,48 @@ function BlockViewerToolbar() {
{isCopied ? <Check /> : <Terminal />}
<span>npx shadcn add {item.name}</span>
</Button>
<Separator
orientation="vertical"
className="mx-1 hidden !h-4 xl:flex"
/>
<Separator orientation="vertical" className="mx-1 !h-4" />
<OpenInV0Button name={item.name} />
</div>
</div>
)
}
function BlockViewerView() {
const { item, resizablePanelRef } = useBlockViewer()
function BlockViewerIframe({ className }: { className?: string }) {
const { item, iframeKey } = useBlockViewer()
return (
<div className="group-data-[view=code]/block-view-wrapper:hidden md:h-[calc(var(--height)+10px)]">
<div className="grid w-full gap-4">
<ResizablePanelGroup direction="horizontal" className="relative z-10">
<iframe
key={iframeKey}
src={`/view/${item.name}`}
height={item.meta?.iframeHeight ?? 930}
loading="lazy"
className={cn(
"bg-background no-scrollbar relative z-20 w-full",
className
)}
/>
)
}
function BlockViewerView() {
const { resizablePanelRef } = useBlockViewer()
return (
<div className="hidden group-data-[view=code]/block-view-wrapper:hidden md:h-(--height) lg:flex">
<div className="relative grid w-full gap-4">
<div className="absolute inset-0 right-4 [background-image:radial-gradient(#d4d4d4_1px,transparent_1px)] [background-size:20px_20px] dark:[background-image:radial-gradient(#404040_1px,transparent_1px)]"></div>
<ResizablePanelGroup
direction="horizontal"
className="after:bg-surface/50 relative z-10 after:absolute after:inset-0 after:right-3 after:z-0 after:rounded-xl"
>
<ResizablePanel
ref={resizablePanelRef}
className="bg-background relative aspect-[4/2.5] overflow-hidden rounded-lg border md:aspect-auto md:rounded-xl"
defaultSize={100}
minSize={30}
>
<Image
src={`/r/styles/new-york-v4/${item.name}-light.png`}
alt={item.name}
data-block={item.name}
width={1440}
height={900}
className="object-cover md:hidden dark:hidden md:dark:hidden"
/>
<Image
src={`/r/styles/new-york-v4/${item.name}-dark.png`}
alt={item.name}
data-block={item.name}
width={1440}
height={900}
className="hidden object-cover md:hidden dark:block md:dark:hidden"
/>
<iframe
src={`/view/${item.name}`}
height={item.meta?.iframeHeight ?? 930}
className="bg-background no-scrollbar relative z-20 hidden w-full md:block"
/>
<BlockViewerIframe />
</ResizablePanel>
<ResizableHandle className="after:bg-border relative hidden w-3 bg-transparent p-0 after:absolute after:top-1/2 after:right-0 after:h-8 after:w-[6px] after:translate-x-[-1px] after:-translate-y-1/2 after:rounded-full after:transition-all after:hover:h-10 md:block" />
<ResizablePanel defaultSize={0} minSize={0} />
@@ -249,6 +266,45 @@ function BlockViewerView() {
)
}
function BlockViewerMobile({ children }: { children: React.ReactNode }) {
const { item } = useBlockViewer()
return (
<div className="flex flex-col gap-2 lg:hidden">
<div className="flex items-center gap-2 px-2">
<div className="line-clamp-1 text-sm font-medium">
{item.description}
</div>
<div className="text-muted-foreground ml-auto shrink-0 font-mono text-xs">
{item.name}
</div>
</div>
{item.meta?.mobile === "component" ? (
children
) : (
<div className="overflow-hidden rounded-xl border">
<Image
src={`/r/styles/new-york-v4/${item.name}-light.png`}
alt={item.name}
data-block={item.name}
width={1440}
height={900}
className="object-cover dark:hidden"
/>
<Image
src={`/r/styles/new-york-v4/${item.name}-dark.png`}
alt={item.name}
data-block={item.name}
width={1440}
height={900}
className="hidden object-cover dark:block"
/>
</div>
)}
</div>
)
}
function BlockViewerCode() {
const { activeFile, highlightedFiles } = useBlockViewer()
@@ -269,7 +325,7 @@ function BlockViewerCode() {
</div>
<figure
data-rehype-pretty-code-figure=""
className="mt-0 flex min-w-0 flex-1 flex-col rounded-xl border-none"
className="!mx-0 mt-0 flex min-w-0 flex-1 flex-col rounded-xl border-none"
>
<figcaption
className="text-code-foreground [&_svg]:text-code-foreground flex h-12 shrink-0 items-center gap-2 border-b px-4 py-2 [&_svg]:size-4 [&_svg]:opacity-70"
@@ -299,7 +355,7 @@ export function BlockViewerFileTree() {
}
return (
<SidebarProvider className="flex !min-h-full flex-col">
<SidebarProvider className="flex !min-h-full flex-col border-r">
<Sidebar collapsible="none" className="w-full flex-1">
<SidebarGroupLabel className="h-12 rounded-none border-b px-4 text-sm">
Files
@@ -414,8 +470,11 @@ function BlockViewer({
item,
tree,
highlightedFiles,
children,
...props
}: Pick<BlockViewerContext, "item" | "tree" | "highlightedFiles">) {
}: Pick<BlockViewerContext, "item" | "tree" | "highlightedFiles"> & {
children: React.ReactNode
}) {
return (
<BlockViewerProvider
item={item}
@@ -426,6 +485,7 @@ function BlockViewer({
<BlockViewerToolbar />
<BlockViewerView />
<BlockViewerCode />
<BlockViewerMobile>{children}</BlockViewerMobile>
</BlockViewerProvider>
)
}

View File

@@ -15,7 +15,7 @@ export function Callout({
return (
<Alert
className={cn(
"bg-surface text-surface-foreground mt-6 w-auto border-none md:-mx-4",
"bg-surface text-surface-foreground mt-6 w-auto border-none md:-mx-1",
className
)}
{...props}

View File

@@ -9,7 +9,7 @@ const start = new Date(2025, 5, 5)
export function CardsCalendar() {
return (
<Card className="max-w-[260px] p-0">
<Card className="hidden max-w-[260px] p-0 sm:flex">
<CardContent className="p-0">
<Calendar
numberOfMonths={1}

View File

@@ -51,7 +51,7 @@ export function ChartsNav({
<Link
href={link.href}
key={link.href}
data-active={pathname === link.href}
data-active={link.href.startsWith(pathname)}
className={cn(
"text-muted-foreground hover:text-primary data-[active=true]:text-primary flex h-7 shrink-0 items-center justify-center px-4 text-center text-base font-medium transition-colors"
)}

View File

@@ -22,7 +22,7 @@ export function CodeCollapsibleWrapper({
<Collapsible
open={isOpened}
onOpenChange={setIsOpened}
className={cn("group/collapsible relative md:-mx-4", className)}
className={cn("group/collapsible relative md:-mx-1", className)}
{...props}
>
<CollapsibleTrigger asChild>

View File

@@ -4,7 +4,7 @@ import * as React from "react"
import { useRouter } from "next/navigation"
import { type DialogProps } from "@radix-ui/react-dialog"
import { IconArrowRight } from "@tabler/icons-react"
import { CornerDownLeftIcon } from "lucide-react"
import { CornerDownLeftIcon, SquareDashedIcon } from "lucide-react"
import { type Color, type ColorPalette } from "@/lib/colors"
import { source } from "@/lib/source"
@@ -35,17 +35,21 @@ import { Separator } from "@/registry/new-york-v4/ui/separator"
export function CommandMenu({
tree,
colors,
blocks,
navItems,
...props
}: DialogProps & {
tree: typeof source.pageTree
colors: ColorPalette[]
blocks?: { name: string; description: string; categories: string[] }[]
navItems?: { href: string; label: string }[]
}) {
const router = useRouter()
const isMac = useIsMac()
const [config] = useConfig()
const [open, setOpen] = React.useState(false)
const [selectedType, setSelectedType] = React.useState<
"color" | "page" | "component" | null
"color" | "page" | "component" | "block" | null
>(null)
const [copyPayload, setCopyPayload] = React.useState("")
const packageManager = config.packageManager || "pnpm"
@@ -74,6 +78,14 @@ export function CommandMenu({
[setSelectedType, setCopyPayload]
)
const handleBlockHighlight = React.useCallback(
(block: { name: string; description: string; categories: string[] }) => {
setSelectedType("block")
setCopyPayload(`${packageManager} dlx shadcn@latest add ${block.name}`)
},
[setSelectedType, setCopyPayload, packageManager]
)
const runCommand = React.useCallback((command: () => unknown) => {
setOpen(false)
command()
@@ -104,6 +116,13 @@ export function CommandMenu({
})
}
if (selectedType === "block") {
copyToClipboardWithMeta(copyPayload, {
name: "copy_npm_command",
properties: { command: copyPayload, pm: packageManager },
})
}
if (selectedType === "page" || selectedType === "component") {
copyToClipboardWithMeta(copyPayload, {
name: "copy_npm_command",
@@ -145,12 +164,45 @@ export function CommandMenu({
<DialogTitle>Search documentation...</DialogTitle>
<DialogDescription>Search for a command to run...</DialogDescription>
</DialogHeader>
<Command className="**:data-[slot=command-input-wrapper]:bg-input/50 **:data-[slot=command-input-wrapper]:border-input rounded-none bg-transparent **:data-[slot=command-input]:!h-9 **:data-[slot=command-input]:py-0 **:data-[slot=command-input-wrapper]:mb-0 **:data-[slot=command-input-wrapper]:!h-9 **:data-[slot=command-input-wrapper]:rounded-md **:data-[slot=command-input-wrapper]:border">
<Command
className="**:data-[slot=command-input-wrapper]:bg-input/50 **:data-[slot=command-input-wrapper]:border-input rounded-none bg-transparent **:data-[slot=command-input]:!h-9 **:data-[slot=command-input]:py-0 **:data-[slot=command-input-wrapper]:mb-0 **:data-[slot=command-input-wrapper]:!h-9 **:data-[slot=command-input-wrapper]:rounded-md **:data-[slot=command-input-wrapper]:border"
filter={(value, search, keywords) => {
const extendValue = value + " " + (keywords?.join(" ") || "")
if (extendValue.toLowerCase().includes(search.toLowerCase())) {
return 1
}
return 0
}}
>
<CommandInput placeholder="Search documentation..." />
<CommandList className="no-scrollbar min-h-80 scroll-pt-2 scroll-pb-1.5">
<CommandEmpty className="text-muted-foreground py-12 text-center text-sm">
No results found.
</CommandEmpty>
{navItems && navItems.length > 0 && (
<CommandGroup
heading="Pages"
className="!p-0 [&_[cmdk-group-heading]]:scroll-mt-16 [&_[cmdk-group-heading]]:!p-3 [&_[cmdk-group-heading]]:!pb-1"
>
{navItems.map((item) => (
<CommandMenuItem
key={item.href}
value={`Navigation ${item.label}`}
keywords={["nav", "navigation", item.label.toLowerCase()]}
onHighlight={() => {
setSelectedType("page")
setCopyPayload("")
}}
onSelect={() => {
runCommand(() => router.push(item.href))
}}
>
<IconArrowRight />
{item.label}
</CommandMenuItem>
))}
</CommandGroup>
)}
{tree.children.map((group) => (
<CommandGroup
key={group.$id}
@@ -227,6 +279,41 @@ export function CommandMenu({
))}
</CommandGroup>
))}
{blocks?.length ? (
<CommandGroup
heading="Blocks"
className="!p-0 [&_[cmdk-group-heading]]:!p-3"
>
{blocks.map((block) => (
<CommandMenuItem
key={block.name}
value={block.name}
onHighlight={() => {
handleBlockHighlight(block)
}}
keywords={[
"block",
block.name,
block.description,
...block.categories,
]}
onSelect={() => {
runCommand(() =>
router.push(
`/blocks/${block.categories[0]}#${block.name}`
)
)
}}
>
<SquareDashedIcon />
{block.description}
<span className="text-muted-foreground ml-auto font-mono text-xs font-normal tabular-nums">
{block.name}
</span>
</CommandMenuItem>
))}
</CommandGroup>
) : null}
</CommandList>
</Command>
<div className="text-muted-foreground absolute inset-x-0 bottom-0 z-20 flex h-10 items-center gap-2 rounded-b-xl border-t border-t-neutral-100 bg-neutral-50 px-4 text-xs font-medium dark:border-t-neutral-700 dark:bg-neutral-800">

View File

@@ -22,7 +22,7 @@ export function ComponentPreviewTabs({
return (
<div
className={cn("group relative my-4 flex flex-col gap-2", className)}
className={cn("group relative mt-4 mb-12 flex flex-col gap-2", className)}
{...props}
>
<Tabs
@@ -51,7 +51,7 @@ export function ComponentPreviewTabs({
</Tabs>
<div
data-tab={tab}
className="data-[tab=code]:border-code relative rounded-lg border md:-mx-4"
className="data-[tab=code]:border-code relative rounded-lg border md:-mx-1"
>
<div
data-slot="preview"

View File

@@ -34,7 +34,7 @@ export function ComponentPreview({
if (type === "block") {
return (
<div className="relative aspect-[4/2.5] w-full overflow-hidden rounded-md border md:-mx-4">
<div className="relative aspect-[4/2.5] w-full overflow-hidden rounded-md border md:-mx-1">
<Image
src={`/r/styles/new-york-v4/${name}-light.png`}
alt={name}

View File

@@ -44,7 +44,7 @@ export function DocsSidebar({
<SidebarMenuButton
asChild
isActive={item.url === pathname}
className="data-[active=true]:bg-accent data-[active=true]:border-accent relative h-[30px] w-fit overflow-visible border border-transparent text-[0.8rem] font-medium after:absolute after:inset-x-0 after:-inset-y-1 after:z-0 after:rounded-md"
className="data-[active=true]:bg-accent data-[active=true]:border-accent 3xl:fixed:w-full 3xl:fixed:max-w-48 relative h-[30px] w-fit overflow-visible border border-transparent text-[0.8rem] font-medium after:absolute after:inset-x-0 after:-inset-y-1 after:z-0 after:rounded-md"
>
<Link href={item.url}>{item.name}</Link>
</SidebarMenuButton>

View File

@@ -24,6 +24,7 @@ export function ModeSwitcher() {
size="icon"
className="group/toggle extend-touch-target size-8"
onClick={toggleTheme}
title="Toggle theme"
>
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@@ -13,7 +13,7 @@ export function OpenInV0Cta({ className }: React.ComponentProps<"div">) {
Deploy your shadcn/ui app on Vercel
</div>
<div className="text-muted-foreground">
Trusted by OpenAI, Sonos, Chick-fil-A, and more.
Trusted by OpenAI, Sonos, Adobe, and more.
</div>
<div className="text-muted-foreground">
Vercel provides tools and infrastructure to deploy apps and features at

View File

@@ -0,0 +1,33 @@
"use client"
import * as React from "react"
import { GalleryHorizontalIcon } from "lucide-react"
import { trackEvent } from "@/lib/events"
import { cn } from "@/lib/utils"
import { useLayout } from "@/hooks/use-layout"
import { Button } from "@/registry/new-york-v4/ui/button"
export function SiteConfig({ className }: React.ComponentProps<typeof Button>) {
const { layout, setLayout } = useLayout()
return (
<Button
variant="ghost"
size="icon"
onClick={() => {
const newLayout = layout === "fixed" ? "full" : "fixed"
setLayout(newLayout)
trackEvent({
name: "set_layout",
properties: { layout: newLayout },
})
}}
className={cn("size-8", className)}
title="Toggle layout"
>
<span className="sr-only">Toggle layout</span>
<GalleryHorizontalIcon />
</Button>
)
}

View File

@@ -2,7 +2,7 @@ import { siteConfig } from "@/lib/config"
export function SiteFooter() {
return (
<footer className="group-has-[.section-soft]/body:bg-surface/40 dark:bg-transparent">
<footer className="group-has-[.section-soft]/body:bg-surface/40 3xl:fixed:bg-transparent dark:bg-transparent">
<div className="container-wrapper px-4 xl:px-6">
<div className="flex h-(--footer-height) items-center justify-between">
<div className="text-muted-foreground w-full text-center text-xs leading-loose sm:text-sm">
@@ -14,6 +14,15 @@ export function SiteFooter() {
className="font-medium underline underline-offset-4"
>
shadcn
</a>{" "}
at{" "}
<a
href="https://vercel.com/new?utm_source=shadcn_site&utm_medium=web&utm_campaign=docs_cta_deploy_now_callout"
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4"
>
Vercel
</a>
. The source code is available on{" "}
<a

View File

@@ -9,6 +9,8 @@ import { Icons } from "@/components/icons"
import { MainNav } from "@/components/main-nav"
import { MobileNav } from "@/components/mobile-nav"
import { ModeSwitcher } from "@/components/mode-switcher"
import { SiteConfig } from "@/components/site-config"
// import blocks from "@/registry/__blocks__.json"
import { Button } from "@/registry/new-york-v4/ui/button"
import { Separator } from "@/registry/new-york-v4/ui/separator"
@@ -18,8 +20,8 @@ export function SiteHeader() {
return (
<header className="bg-background sticky top-0 z-50 w-full">
<div className="container-wrapper px-6">
<div className="flex h-(--header-height) items-center gap-2 **:data-[slot=separator]:!h-4">
<div className="container-wrapper 3xl:fixed:px-0 px-6">
<div className="3xl:fixed:container flex h-(--header-height) items-center gap-2 **:data-[slot=separator]:!h-4">
<MobileNav
tree={pageTree}
items={siteConfig.navItems}
@@ -39,13 +41,19 @@ export function SiteHeader() {
<MainNav items={siteConfig.navItems} className="hidden lg:flex" />
<div className="ml-auto flex items-center gap-2 md:flex-1 md:justify-end">
<div className="hidden w-full flex-1 md:flex md:w-auto md:flex-none">
<CommandMenu tree={pageTree} colors={colors} />
<CommandMenu
tree={pageTree}
colors={colors}
navItems={siteConfig.navItems}
/>
</div>
<Separator
orientation="vertical"
className="ml-2 hidden lg:block"
/>
<GitHubLink />
<Separator orientation="vertical" className="3xl:flex hidden" />
<SiteConfig className="3xl:flex hidden" />
<Separator orientation="vertical" />
<ModeSwitcher />
</div>

View File

@@ -0,0 +1,569 @@
"use client"
import * as React from "react"
import template from "lodash/template"
import { CheckIcon, ClipboardIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { useThemeConfig } from "@/components/active-theme"
import { copyToClipboardWithMeta } from "@/components/copy-button"
import { Icons } from "@/components/icons"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/new-york-v4/ui/dialog"
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/registry/new-york-v4/ui/drawer"
import { Label } from "@/registry/new-york-v4/ui/label"
import { ScrollArea, ScrollBar } from "@/registry/new-york-v4/ui/scroll-area"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/new-york-v4/ui/tabs"
import {
BaseColor,
baseColors,
baseColorsOKLCH,
} from "@/registry/registry-base-colors"
interface BaseColorOKLCH {
light: Record<string, string>
dark: Record<string, string>
}
const THEMES = baseColors.filter(
(theme) => !["slate", "stone", "gray", "zinc"].includes(theme.name)
)
export function ThemeCustomizer({ className }: React.ComponentProps<"div">) {
const { activeTheme = "neutral", setActiveTheme } = useThemeConfig()
return (
<div className={cn("flex w-full items-center gap-2", className)}>
<ScrollArea className="hidden max-w-[96%] md:max-w-[600px] lg:flex lg:max-w-none">
<div className="flex items-center">
{THEMES.map((theme) => (
<Button
key={theme.name}
variant="link"
size="sm"
data-active={activeTheme === theme.name}
className="text-muted-foreground hover:text-primary data-[active=true]:text-primary flex h-7 cursor-pointer items-center justify-center px-4 text-center text-base font-medium capitalize transition-colors hover:no-underline"
onClick={() => setActiveTheme(theme.name)}
>
{theme.name === "neutral" ? "Default" : theme.name}
</Button>
))}
</div>
<ScrollBar orientation="horizontal" className="invisible" />
</ScrollArea>
<div className="flex items-center gap-2 lg:hidden">
<Label htmlFor="theme-selector" className="sr-only">
Theme
</Label>
<Select
value={activeTheme === "default" ? "neutral" : activeTheme}
onValueChange={setActiveTheme}
>
<SelectTrigger
id="theme-selector"
size="sm"
className="justify-start capitalize shadow-none *:data-[slot=select-value]:w-12 *:data-[slot=select-value]:capitalize"
>
<span className="font-medium">Theme:</span>
<SelectValue placeholder="Select a theme" />
</SelectTrigger>
<SelectContent align="end">
<SelectGroup>
{THEMES.map((theme) => (
<SelectItem
key={theme.name}
value={theme.name}
className="capitalize data-[state=checked]:opacity-50"
>
{theme.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<CopyCodeButton variant="secondary" size="sm" className="ml-auto" />
</div>
)
}
export function CopyCodeButton({
className,
...props
}: React.ComponentProps<typeof Button>) {
let { activeTheme: activeThemeName = "neutral" } = useThemeConfig()
activeThemeName = activeThemeName === "default" ? "neutral" : activeThemeName
return (
<>
<Drawer>
<DrawerTrigger asChild>
<Button className={cn("sm:hidden", className)} {...props}>
Copy Code
</Button>
</DrawerTrigger>
<DrawerContent className="h-auto">
<DrawerHeader>
<DrawerTitle className="capitalize">
{activeThemeName === "neutral" ? "Default" : activeThemeName}
</DrawerTitle>
<DrawerDescription>
Copy and paste the following code into your CSS file.
</DrawerDescription>
</DrawerHeader>
<CustomizerCode themeName={activeThemeName} />
</DrawerContent>
</Drawer>
<Dialog>
<DialogTrigger asChild>
<Button className={cn("hidden sm:flex", className)} {...props}>
Copy Code
</Button>
</DialogTrigger>
<DialogContent className="outline-none md:max-w-3xl">
<DialogHeader>
<DialogTitle className="capitalize">
{activeThemeName === "neutral" ? "Default" : activeThemeName}
</DialogTitle>
<DialogDescription>
Copy and paste the following code into your CSS file.
</DialogDescription>
</DialogHeader>
<CustomizerCode themeName={activeThemeName} />
</DialogContent>
</Dialog>
</>
)
}
function CustomizerCode({ themeName }: { themeName: string }) {
const [hasCopied, setHasCopied] = React.useState(false)
const [tailwindVersion, setTailwindVersion] = React.useState("v4")
const activeTheme = React.useMemo(
() => baseColors.find((theme) => theme.name === themeName),
[themeName]
)
const activeThemeOKLCH = React.useMemo(
() => baseColorsOKLCH[themeName as keyof typeof baseColorsOKLCH],
[themeName]
)
React.useEffect(() => {
if (hasCopied) {
setTimeout(() => {
setHasCopied(false)
}, 2000)
}
}, [hasCopied])
return (
<>
<Tabs
value={tailwindVersion}
onValueChange={setTailwindVersion}
className="min-w-0 px-4 pb-4 md:p-0"
>
<TabsList>
<TabsTrigger value="v4">Tailwind v4</TabsTrigger>
<TabsTrigger value="v3">Tailwind v3</TabsTrigger>
</TabsList>
<TabsContent value="v4">
<figure
data-rehype-pretty-code-figure
className="!mx-0 mt-0 rounded-lg"
>
<figcaption
className="text-code-foreground [&_svg]:text-code-foreground flex items-center gap-2 [&_svg]:size-4 [&_svg]:opacity-70"
data-rehype-pretty-code-title=""
data-language="css"
data-theme="github-dark github-light-default"
>
<Icons.css className="fill-foreground" />
app/globals.css
</figcaption>
<pre className="no-scrollbar max-h-[300px] min-w-0 overflow-x-auto px-4 py-3.5 outline-none has-[[data-highlighted-line]]:px-0 has-[[data-line-numbers]]:px-0 has-[[data-slot=tabs]]:p-0 md:max-h-[450px]">
<Button
data-slot="copy-button"
size="icon"
variant="ghost"
className="bg-code text-code-foreground absolute top-3 right-2 z-10 size-7 shadow-none hover:opacity-100 focus-visible:opacity-100"
onClick={() => {
copyToClipboardWithMeta(
tailwindVersion === "v3"
? getThemeCode(activeTheme, 0.65)
: getThemeCodeOKLCH(activeThemeOKLCH, 0.65),
{
name: "copy_theme_code",
properties: {
theme: themeName,
radius: 0.5,
},
}
)
setHasCopied(true)
}}
>
<span className="sr-only">Copy</span>
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
</Button>
<code data-line-numbers data-language="css">
<span data-line className="line text-code-foreground">
&nbsp;:root &#123;
</span>
<span data-line className="line text-code-foreground">
&nbsp;&nbsp;&nbsp;--radius: 0.65rem;
</span>
{Object.entries(activeThemeOKLCH?.light).map(([key, value]) => (
<span
data-line
className="line text-code-foreground"
key={key}
>
&nbsp;&nbsp;&nbsp;--{key}: {value};
</span>
))}
<span data-line className="line text-code-foreground">
&nbsp;&#125;
</span>
<span data-line className="line text-code-foreground">
&nbsp;
</span>
<span data-line className="line text-code-foreground">
&nbsp;.dark &#123;
</span>
{Object.entries(activeThemeOKLCH?.dark).map(([key, value]) => (
<span
data-line
className="line text-code-foreground"
key={key}
>
&nbsp;&nbsp;&nbsp;--{key}: {value};
</span>
))}
<span data-line className="line text-code-foreground">
&nbsp;&#125;
</span>
</code>
</pre>
</figure>
</TabsContent>
<TabsContent value="v3">
<figure
data-rehype-pretty-code-figure
className="!mx-0 mt-0 rounded-lg"
>
<figcaption
className="text-code-foreground [&_svg]:text-code-foreground flex items-center gap-2 [&_svg]:size-4 [&_svg]:opacity-70"
data-rehype-pretty-code-title=""
data-language="css"
data-theme="github-dark github-light-default"
>
<Icons.css className="fill-foreground" />
app/globals.css
</figcaption>
<pre className="no-scrollbar max-h-[300px] min-w-0 overflow-x-auto px-4 py-3.5 outline-none has-[[data-highlighted-line]]:px-0 has-[[data-line-numbers]]:px-0 has-[[data-slot=tabs]]:p-0 md:max-h-[450px]">
<Button
data-slot="copy-button"
size="icon"
variant="ghost"
className="bg-code text-code-foreground absolute top-3 right-2 z-10 size-7 shadow-none hover:opacity-100 focus-visible:opacity-100"
onClick={() => {
copyToClipboardWithMeta(
tailwindVersion === "v3"
? getThemeCode(activeTheme, 0.65)
: getThemeCodeOKLCH(activeThemeOKLCH, 0.65),
{
name: "copy_theme_code",
properties: {
theme: themeName,
radius: 0.5,
},
}
)
setHasCopied(true)
}}
>
<span className="sr-only">Copy</span>
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
</Button>
<code data-line-numbers data-language="css">
<span data-line className="line">
@layer base &#123;
</span>
<span data-line className="line">
&nbsp;&nbsp;:root &#123;
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--background:{" "}
{activeTheme?.cssVars.light["background"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--foreground:{" "}
{activeTheme?.cssVars.light["foreground"]};
</span>
{[
"card",
"popover",
"primary",
"secondary",
"muted",
"accent",
"destructive",
].map((prefix) => (
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
]
}
;
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}-foreground:{" "}
{
activeTheme?.cssVars.light[
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.light
]
}
;
</span>
</React.Fragment>
))}
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--border:{" "}
{activeTheme?.cssVars.light["border"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--input:{" "}
{activeTheme?.cssVars.light["input"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--ring:{" "}
{activeTheme?.cssVars.light["ring"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--radius: 0.5rem;
</span>
{["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"].map(
(prefix) => (
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
]
}
;
</span>
</React.Fragment>
)
)}
<span data-line className="line">
&nbsp;&nbsp;&#125;
</span>
<span data-line className="line">
&nbsp;
</span>
<span data-line className="line">
&nbsp;&nbsp;.dark &#123;
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--background:{" "}
{activeTheme?.cssVars.dark["background"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--foreground:{" "}
{activeTheme?.cssVars.dark["foreground"]};
</span>
{[
"card",
"popover",
"primary",
"secondary",
"muted",
"accent",
"destructive",
].map((prefix) => (
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
]
}
;
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}-foreground:{" "}
{
activeTheme?.cssVars.dark[
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.dark
]
}
;
</span>
</React.Fragment>
))}
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--border:{" "}
{activeTheme?.cssVars.dark["border"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--input:{" "}
{activeTheme?.cssVars.dark["input"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--ring:{" "}
{activeTheme?.cssVars.dark["ring"]};
</span>
{["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"].map(
(prefix) => (
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
]
}
;
</span>
</React.Fragment>
)
)}
<span data-line className="line">
&nbsp;&nbsp;&#125;
</span>
<span data-line className="line">
&#125;
</span>
</code>
</pre>
</figure>
</TabsContent>
</Tabs>
</>
)
}
function getThemeCodeOKLCH(theme: BaseColorOKLCH | undefined, radius: number) {
if (!theme) {
return ""
}
const rootSection =
":root {\n --radius: " +
radius +
"rem;\n" +
Object.entries(theme.light)
.map((entry) => " --" + entry[0] + ": " + entry[1] + ";")
.join("\n") +
"\n}\n\n.dark {\n" +
Object.entries(theme.dark)
.map((entry) => " --" + entry[0] + ": " + entry[1] + ";")
.join("\n") +
"\n}\n"
return rootSection
}
function getThemeCode(theme: BaseColor | undefined, radius: number) {
if (!theme) {
return ""
}
return template(BASE_STYLES_WITH_VARIABLES)({
colors: theme.cssVars,
radius: radius.toString(),
})
}
const BASE_STYLES_WITH_VARIABLES = `
@layer base {
:root {
--background: <%- colors.light["background"] %>;
--foreground: <%- colors.light["foreground"] %>;
--card: <%- colors.light["card"] %>;
--card-foreground: <%- colors.light["card-foreground"] %>;
--popover: <%- colors.light["popover"] %>;
--popover-foreground: <%- colors.light["popover-foreground"] %>;
--primary: <%- colors.light["primary"] %>;
--primary-foreground: <%- colors.light["primary-foreground"] %>;
--secondary: <%- colors.light["secondary"] %>;
--secondary-foreground: <%- colors.light["secondary-foreground"] %>;
--muted: <%- colors.light["muted"] %>;
--muted-foreground: <%- colors.light["muted-foreground"] %>;
--accent: <%- colors.light["accent"] %>;
--accent-foreground: <%- colors.light["accent-foreground"] %>;
--destructive: <%- colors.light["destructive"] %>;
--destructive-foreground: <%- colors.light["destructive-foreground"] %>;
--border: <%- colors.light["border"] %>;
--input: <%- colors.light["input"] %>;
--ring: <%- colors.light["ring"] %>;
--radius: <%- radius %>rem;
--chart-1: <%- colors.light["chart-1"] %>;
--chart-2: <%- colors.light["chart-2"] %>;
--chart-3: <%- colors.light["chart-3"] %>;
--chart-4: <%- colors.light["chart-4"] %>;
--chart-5: <%- colors.light["chart-5"] %>;
}
.dark {
--background: <%- colors.dark["background"] %>;
--foreground: <%- colors.dark["foreground"] %>;
--card: <%- colors.dark["card"] %>;
--card-foreground: <%- colors.dark["card-foreground"] %>;
--popover: <%- colors.dark["popover"] %>;
--popover-foreground: <%- colors.dark["popover-foreground"] %>;
--primary: <%- colors.dark["primary"] %>;
--primary-foreground: <%- colors.dark["primary-foreground"] %>;
--secondary: <%- colors.dark["secondary"] %>;
--secondary-foreground: <%- colors.dark["secondary-foreground"] %>;
--muted: <%- colors.dark["muted"] %>;
--muted-foreground: <%- colors.dark["muted-foreground"] %>;
--accent: <%- colors.dark["accent"] %>;
--accent-foreground: <%- colors.dark["accent-foreground"] %>;
--destructive: <%- colors.dark["destructive"] %>;
--destructive-foreground: <%- colors.dark["destructive-foreground"] %>;
--border: <%- colors.dark["border"] %>;
--input: <%- colors.dark["input"] %>;
--ring: <%- colors.dark["ring"] %>;
--chart-1: <%- colors.dark["chart-1"] %>;
--chart-2: <%- colors.dark["chart-2"] %>;
--chart-3: <%- colors.dark["chart-3"] %>;
--chart-4: <%- colors.dark["chart-4"] %>;
--chart-5: <%- colors.dark["chart-5"] %>;
}
}
`

View File

@@ -4,6 +4,64 @@ description: Latest updates and announcements.
toc: false
---
## July 2025 - Universal Registry Items
We've added support for universal registry items. This allows you to create registry items that can be distributed to any project i.e. no framework, no components.json, no tailwind, no react required.
This new registry item type unlocks a lot of new workflows. You can now distribute code, config, rules, docs, anything to any code project.
See the [docs](/docs/registry/examples) for more details and examples.
## July 2025 - Local File Support
The shadcn CLI now supports local files. Initialize projects and add components, themes, hooks, utils and more from local JSON files.
```bash
# Initialize a project from a local file
npx shadcn init ./template.json
# Add a component from a local file
npx shadcn add ./block.json
```
This feature enables powerful new workflows:
- **Zero setup** - No remote registries required
- **Faster development** - Test registry items locally before publishing
- **Enhanced workflow for agents and MCP** - Generate and run registry items locally
- **Private components** - Keep proprietary components local and private.
## June 2025 - `radix-ui`
We've added a new command to migrate to the new `radix-ui` package. This command will replace all `@radix-ui/react-*` imports with `radix-ui`.
```bash
npx shadcn@latest migrate radix
```
It will automatically update all imports in your `ui` components and install `radix-ui` as a dependency.
```diff showLineNumbers title="components/ui/alert-dialog.tsx"
- import * as AlertDialogPrimitive from "@radix-ui/react-dialog"
+ import { AlertDialog as AlertDialogPrimitive } from "radix-ui"
```
Make sure to test your components and project after running the command.
**Note:** To update imports for newly added components, run the migration command again.
## June 2025 - Calendar Component
We've upgraded the `Calendar` component to the latest version of [React DayPicker](https://daypicker.dev).
This is a major upgrade and includes a lot of new features and improvements. We've also built a collection of 30+ calendar blocks that you can use to build your own calendar components.
See all calendar blocks in the [Blocks Library](/blocks/calendar) page.
<Image src="/images/calendar-2.png" alt="Calendar" width={676} height={895} />
To upgrade your project to the latest version of the `Calendar` component, see the [upgrade guide](/docs/components/calendar#upgrade-guide).
## May 2025 - New Site
We've upgraded [ui.shadcn.com](https://ui.shadcn.com) to Next.js 15.3 and Tailwind v4. The site now uses the upgraded `new-york` components.
@@ -30,9 +88,9 @@ Learn more in the thread here: https://x.com/shadcn/status/1917597228513853603
We tagged shadcn 2.5.0 earlier this week. It comes with a pretty cool feature: **resolve anywhere**.
Registries can now place files anywhere in an app and well properly resolve imports. No need to stick to a fixed file structure. It can even add files outside the registry itself.
Registries can now place files anywhere in an app and we'll properly resolve imports. No need to stick to a fixed file structure. It can even add files outside the registry itself.
On install, we track all files and perform a multi-pass resolution to correctly handle imports and aliases. Its fast.
On install, we track all files and perform a multi-pass resolution to correctly handle imports and aliases. It's fast.
## March 2025 - Cross-framework Route Support
@@ -49,7 +107,7 @@ What's New:
- The CLI can now initialize projects with Tailwind v4.
- Full support for the new @theme directive and @theme inline option.
- All components are updated for Tailwind v4 and React 19.
- Weve removed the forwardRefs and adjusted the types.
- We've removed the forwardRefs and adjusted the types.
- Every primitive now has a data-slot attribute for styling.
- We've fixed and cleaned up the style of the components.
- We're deprecating the toast component in favor of sonner.
@@ -127,7 +185,7 @@ The new CLI is now available. It's a complete rewrite with a lot of new features
This is a major step towards distributing code that you and your LLMs can access and use.
1. First up, the cli now has support for all major React framework out of the box. Next.js, Remix, Vite and Laravel. And when you init into a new app, we update your existing Tailwind files instead of overriding.
2. A component now ship its own dependencies. Take the accordion for example, it can define its Tailwind keyframes. When you add it to your project, well update your tailwind.config.ts file accordingly.
2. A component now ship its own dependencies. Take the accordion for example, it can define its Tailwind keyframes. When you add it to your project, we'll update your tailwind.config.ts file accordingly.
3. You can also install remote components using url. `npx shadcn add https://acme.com/registry/navbar.json`.
4. We have also improve the init command. It does framework detection and can even init a brand new Next.js app in one command. `npx shadcn init`.
5. We have created a new schema that you can use to ship your own component registry. And since it has support for urls, you can even use it to distribute private components.

View File

@@ -21,15 +21,14 @@ Usage: shadcn init [options] [components...]
initialize your project and install dependencies
Arguments:
components the components to add or a url to the component.
components name, url or local path to component
Options:
-t, --template <template> the template to use. (next, next-monorepo)
-b, --base-color <base-color> the base color to use. (neutral, gray, zinc, stone, slate)
-y, --yes skip confirmation prompt. (default: true)
-f, --force force overwrite of existing configuration. (default: false)
-c, --cwd <cwd> the working directory. defaults to the current directory. (default:
"/Users/shadcn/Code/shadcn/ui/packages/shadcn")
-c, --cwd <cwd> the working directory. defaults to the current directory.
-s, --silent mute output. (default: false)
--src-dir use the src directory when creating a new project. (default: false)
--no-src-dir do not use the src directory when creating a new project.
@@ -54,12 +53,12 @@ Usage: shadcn add [options] [components...]
add a component to your project
Arguments:
components the components to add or a url to the component.
components name, url or local path to component
Options:
-y, --yes skip confirmation prompt. (default: false)
-o, --overwrite overwrite existing files. (default: false)
-c, --cwd <cwd> the working directory. defaults to the current directory. (default: "/Users/shadcn/Desktop")
-c, --cwd <cwd> the working directory. defaults to the current directory.
-a, --all add all available components (default: false)
-p, --path <path> the path to add the component to.
-s, --silent mute output. (default: false)
@@ -92,8 +91,7 @@ Arguments:
Options:
-o, --output <path> destination directory for json files (default: "./public/r")
-c, --cwd <cwd> the working directory. defaults to the current directory. (default:
"/Users/shadcn/Code/shadcn/ui/packages/shadcn")
-c, --cwd <cwd> the working directory. defaults to the current directory.
-h, --help display help for command
```

View File

@@ -15,3 +15,4 @@ description: Every component recreated in Figma. With customizable props, typogr
## Free
- [shadcn/ui design system](https://www.figma.com/community/file/1203061493325953101) by [Pietro Schirano](https://twitter.com/skirano) - A design companion for shadcn/ui. Each component was painstakingly crafted to perfectly match the code implementation.
- [Obra shadcn/ui](https://www.figma.com/community/file/1514746685758799870/obra-shadcn-ui) by [Obra Studio](https://obra.studio/) - Carefully crafted kit designed in the philosophy of shadcn, tracks v4, MIT licensed

View File

@@ -12,9 +12,11 @@ links:
description="A calendar showing the current date."
/>
## About
## Blocks
The `Calendar` component is built on top of [React DayPicker](https://react-day-picker.js.org).
We have built a collection of 30+ calendar blocks that you can use to build your own calendar components.
See all calendar blocks in the [Blocks Library](/blocks/calendar) page.
## Installation
@@ -39,7 +41,7 @@ npx shadcn@latest add calendar
<Step>Install the following dependencies:</Step>
```bash
npm install react-day-picker@8.10.1 date-fns
npm install react-day-picker date-fns
```
<Step>Add the `Button` component to your project.</Step>
@@ -72,19 +74,346 @@ return (
mode="single"
selected={date}
onSelect={setDate}
className="rounded-md border"
className="rounded-lg border"
/>
)
```
See the [React DayPicker](https://react-day-picker.js.org) documentation for more information.
## About
The `Calendar` component is built on top of [React DayPicker](https://react-day-picker.js.org).
## Customization
See the [React DayPicker](https://react-day-picker.js.org/docs/customization) documentation for more information on how to customize the `Calendar` component.
## Date Picker
You can use the `<Calendar>` component to build a date picker. See the [Date Picker](/docs/components/date-picker) page for more information.
## Persian / Hijri / Jalali Calendar
To use the Persian calendar, edit `components/ui/calendar.tsx` and replace `react-day-picker` with `react-day-picker/persian`.
```diff
- import { DayPicker } from "react-day-picker"
+ import { DayPicker } from "react-day-picker/persian"
```
<ComponentPreview
name="calendar-hijri"
title="Persian / Hijri / Jalali Calendar"
description="A Persian calendar."
/>
## Examples
### Range Calendar
<ComponentPreview
name="calendar-02"
title="Range Calendar"
description="A calendar showing the current date and range selection."
className="**:[.preview]:h-auto lg:**:[.preview]:h-[450px]"
/>
### Month and Year Selector
<ComponentPreview
name="calendar-13"
title="Month and Year Selector"
description="A calendar with month and year dropdowns."
/>
### Date of Birth Picker
<ComponentPreview
name="calendar-22"
title="Date of Birth Picker"
description="A calendar with date of birth picker."
/>
### Date and Time Picker
<ComponentPreview
name="calendar-24"
title="Date and Time Picker"
description="A calendar with date and time picker."
/>
### Natural Language Picker
This component uses the `chrono-node` library to parse natural language dates.
<ComponentPreview
name="calendar-29"
title="Natural Language Picker"
description="A calendar with natural language picker."
/>
### Form
<ComponentPreview name="calendar-form" />
## Upgrade Guide
### Tailwind v4
If you're already using Tailwind v4, you can upgrade to the latest version of the `Calendar` component by running the following command:
```bash
npx shadcn@latest add calendar
```
When you're prompted to overwrite the existing `Calendar` component, select `Yes`. **If you have made any changes to the `Calendar` component, you will need to merge your changes with the new version.**
This will update the `Calendar` component and `react-day-picker` to the latest version.
Next, follow the [React DayPicker](https://daypicker.dev/upgrading) upgrade guide to upgrade your existing components to the latest version.
#### Installing Blocks
After upgrading the `Calendar` component, you can install the new blocks by running the `shadcn@latest add` command.
```bash
npx shadcn@latest add calendar-02
```
This will install the latest version of the calendar blocks.
### Tailwind v3
If you're using Tailwind v3, you can upgrade to the latest version of the `Calendar` by copying the following code to your `calendar.tsx` file.
<CodeCollapsibleWrapper>
```tsx showLineNumbers title="components/ui/calendar.tsx"
"use client"
import * as React from "react"
import {
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from "lucide-react"
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/ui/button"
function Calendar({
className,
classNames,
showOutsideDays = true,
captionLayout = "label",
buttonVariant = "ghost",
formatters,
components,
...props
}: React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
}) {
const defaultClassNames = getDefaultClassNames()
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
"bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className
)}
captionLayout={captionLayout}
formatters={{
formatMonthDropdown: (date) =>
date.toLocaleString("default", { month: "short" }),
...formatters,
}}
classNames={{
root: cn("w-fit", defaultClassNames.root),
months: cn(
"relative flex flex-col gap-4 md:flex-row",
defaultClassNames.months
),
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
nav: cn(
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
defaultClassNames.nav
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
"h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
defaultClassNames.button_previous
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
"h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
defaultClassNames.button_next
),
month_caption: cn(
"flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]",
defaultClassNames.month_caption
),
dropdowns: cn(
"flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium",
defaultClassNames.dropdowns
),
dropdown_root: cn(
"has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
defaultClassNames.dropdown_root
),
dropdown: cn("absolute inset-0 opacity-0", defaultClassNames.dropdown),
caption_label: cn(
"select-none font-medium",
captionLayout === "label"
? "text-sm"
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
defaultClassNames.caption_label
),
table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
defaultClassNames.weekday
),
week: cn("mt-2 flex w-full", defaultClassNames.week),
week_number_header: cn(
"w-[--cell-size] select-none",
defaultClassNames.week_number_header
),
week_number: cn(
"text-muted-foreground select-none text-[0.8rem]",
defaultClassNames.week_number
),
day: cn(
"group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
defaultClassNames.day
),
range_start: cn(
"bg-accent rounded-l-md",
defaultClassNames.range_start
),
range_middle: cn("rounded-none", defaultClassNames.range_middle),
range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
today: cn(
"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
defaultClassNames.today
),
outside: cn(
"text-muted-foreground aria-selected:text-muted-foreground",
defaultClassNames.outside
),
disabled: cn(
"text-muted-foreground opacity-50",
defaultClassNames.disabled
),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
}}
components={{
Root: ({ className, rootRef, ...props }) => {
return (
<div
data-slot="calendar"
ref={rootRef}
className={cn(className)}
{...props}
/>
)
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return (
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
)
}
if (orientation === "right") {
return (
<ChevronRightIcon
className={cn("size-4", className)}
{...props}
/>
)
}
return (
<ChevronDownIcon className={cn("size-4", className)} {...props} />
)
},
DayButton: CalendarDayButton,
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
<div className="flex size-[--cell-size] items-center justify-center text-center">
{children}
</div>
</td>
)
},
...components,
}}
{...props}
/>
)
}
function CalendarDayButton({
className,
day,
modifiers,
...props
}: React.ComponentProps<typeof DayButton>) {
const defaultClassNames = getDefaultClassNames()
const ref = React.useRef<HTMLButtonElement>(null)
React.useEffect(() => {
if (modifiers.focused) ref.current?.focus()
}, [modifiers.focused])
return (
<Button
ref={ref}
variant="ghost"
size="icon"
data-day={day.date.toLocaleDateString()}
data-selected-single={
modifiers.selected &&
!modifiers.range_start &&
!modifiers.range_end &&
!modifiers.range_middle
}
data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
className={cn(
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70",
defaultClassNames.day,
className
)}
{...props}
/>
)
}
export { Calendar, CalendarDayButton }
```
</CodeCollapsibleWrapper>
**If you have made any changes to the `Calendar` component, you will need to merge your changes with the new version.**
Then follow the [React DayPicker](https://daypicker.dev/upgrading) upgrade guide to upgrade your dependencies and existing components to the latest version.
#### Installing Blocks
After upgrading the `Calendar` component, you can install the new blocks by running the `shadcn@latest add` command.
```bash
npx shadcn@latest add calendar-02
```
This will install the latest version of the calendar blocks.

View File

@@ -4,9 +4,15 @@ description: Beautiful charts. Built using Recharts. Copy and paste into your ap
component: true
---
<Callout>
**Note:** We're working on upgrading to Recharts v3. In the meantime, if you'd like to start testing v3, see the code in the comment [here](https://github.com/shadcn-ui/ui/issues/7669#issuecomment-2998299159). We'll have an official release soon.
</Callout>
<ComponentPreview
name="chart-bar-interactive"
className="theme-blue -mt-4 [&_.preview]:p-0 [&_.preview]:lg:min-h-[404px] [&_.preview>div]:w-full [&_.preview>div]:border-none [&_.preview>div]:shadow-none"
className="theme-blue [&_.preview]:h-auto [&_.preview]:p-0 [&_.preview]:lg:min-h-[404px] [&_.preview>div]:w-full [&_.preview>div]:border-none [&_.preview>div]:shadow-none"
hideCode
/>

View File

@@ -5,8 +5,9 @@ component: true
---
<ComponentPreview
name="date-picker-demo"
description="A date picker in a popover"
name="calendar-22"
title="Date of Birth Picker"
description="A calendar with date of birth picker."
/>
## Installation
@@ -40,23 +41,16 @@ export function DatePickerDemo() {
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"w-[280px] justify-start text-left font-normal",
!date && "text-muted-foreground"
)}
variant="outline"
data-empty={!date}
className="data-[empty=true]:text-muted-foreground w-[280px] justify-start text-left font-normal"
>
<CalendarIcon className="mr-2 h-4 w-4" />
<CalendarIcon />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
initialFocus
/>
<Calendar mode="single" selected={date} onSelect={setDate} />
</PopoverContent>
</Popover>
)
@@ -67,25 +61,38 @@ See the [React DayPicker](https://react-day-picker.js.org) documentation for mor
## Examples
### Date Picker
### Date of Birth Picker
<ComponentPreview
name="date-picker-demo"
description="A date picker in a popover"
name="calendar-22"
title="Date of Birth Picker"
description="A calendar with date of birth picker."
/>
### Date Range Picker
### Picker with Input
<ComponentPreview
name="date-picker-with-range"
description="A date range picker"
name="calendar-28"
title="Picker with Input"
description="A calendar with input and picker."
/>
### With Presets
### Date and Time Picker
<ComponentPreview
name="date-picker-with-presets"
description="A date picker with presets"
name="calendar-24"
title="Date and Time Picker"
description="A calendar with date and time picker."
/>
### Natural Language Picker
This component uses the `chrono-node` library to parse natural language dates.
<ComponentPreview
name="calendar-29"
title="Natural Language Picker"
description="A calendar with natural language picker."
/>
### Form

View File

@@ -328,6 +328,20 @@ Add custom theme variables to the `theme` object.
}
```
## Add custom plugins
```json title="example-plugin.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "custom-plugin",
"type": "registry:component",
"css": {
"@plugin @tailwindcss/typography": {},
"@plugin foo": {}
}
}
```
## Add custom animations
Note: you need to define both `@keyframes` in css and `theme` in cssVars to use animations.
@@ -354,3 +368,70 @@ Note: you need to define both `@keyframes` in css and `theme` in cssVars to use
}
}
```
## Universal Items
As of `2.9.0`, you can create universal items that can be installed without framework detection or components.json.
To make an item universal i.e framework agnostic, all the files in the item must have an explicit target.
Here's an example of a registry item that installs custom Cursor rules for _python_:
```json title=".cursor/rules/custom-python.mdc" showLineNumbers {9}
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "python-rules",
"type": "registry:item",
"files": [
{
"path": "/path/to/your/registry/default/custom-python.mdc",
"type": "registry:file",
"target": "~/.cursor/rules/custom-python.mdc",
"content": "..."
}
]
}
```
Here's another example for installation custom ESLint config:
```json title=".eslintrc.json" showLineNumbers {9}
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "my-eslint-config",
"type": "registry:item",
"files": [
{
"path": "/path/to/your/registry/default/custom-eslint.json",
"type": "registry:file",
"target": "~/.eslintrc.json",
"content": "..."
}
]
}
```
You can also have a universal item that installs multiple files:
```json title="my-custom-starter-template.json" showLineNumbers {9}
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "my-custom-start-template",
"type": "registry:item",
dependencies: ["better-auth"]
"files": [
{
"path": "/path/to/file-01.json",
"type": "registry:file",
"target": "~/file-01.json",
"content": "..."
},
{
"path": "/path/to/file-02.vue",
"type": "registry:file",
"target": "~/pages/file-02.vue",
"content": "..."
}
]
}
```

View File

@@ -1,15 +1,14 @@
---
title: Registry
description: Run your own component registry.
description: Run your own code registry.
---
<Callout>
**Note:** This feature is currently experimental. Help us improve it by
testing it out and sending feedback. If you have any questions, please [reach
out to us](https://github.com/shadcn-ui/ui/discussions).
</Callout>
You can use the `shadcn` CLI to run your own code registry. Running your own registry allows you to distribute your custom components, hooks, pages, config, rules and other files to any project.
You can use the `shadcn` CLI to run your own component registry. Running your own registry allows you to distribute your custom components, hooks, pages, and other files to any React project.
<Callout>
**Note:** The registry works with any project type and any framework, and is
not limited to React.
</Callout>
<figure className="flex flex-col gap-4">
<Image
@@ -27,12 +26,10 @@ You can use the `shadcn` CLI to run your own component registry. Running your ow
className="mt-6 hidden w-full overflow-hidden rounded-lg border shadow-sm dark:block"
/>
<figcaption className="text-center text-sm text-gray-500">
Distribute code to any React project.
A distribution system for code
</figcaption>
</figure>
Registry items are automatically compatible with the `shadcn` CLI and `Open in v0`.
## Requirements
You are free to design and host your custom registry as you see fit. The only requirement is that your registry items must be valid JSON files that conform to the [registry-item schema specification](/docs/registry/registry-item-json).

View File

@@ -107,6 +107,7 @@ The following types are supported:
| `registry:file` | Use for miscellaneous files. |
| `registry:style` | Use for registry styles. eg. `new-york` |
| `registry:theme` | Use for themes. |
| `registry:item` | Use for universal registry items. |
### author
@@ -260,11 +261,13 @@ Use to define CSS variables for your registry item.
### css
Use `css` to add new rules to the project's CSS file eg. `@layer base`, `@layer components`, `@utility`, `@keyframes`, etc.
Use `css` to add new rules to the project's CSS file eg. `@layer base`, `@layer components`, `@utility`, `@keyframes`, `@plugin`, etc.
```json title="registry-item.json" showLineNumbers
{
"css": {
"@plugin @tailwindcss/typography": {},
"@plugin foo": {},
"@layer base": {
"body": {
"font-size": "var(--text-base)",

View File

@@ -0,0 +1,160 @@
"use client"
import * as React from "react"
type Layout = "fixed" | "full"
interface LayoutProviderProps {
children: React.ReactNode
defaultLayout?: Layout
forcedLayout?: Layout
storageKey?: string
attribute?: string | string[]
value?: Record<string, string>
}
interface LayoutProviderState {
layout: Layout
setLayout: (layout: Layout | ((prev: Layout) => Layout)) => void
forcedLayout?: Layout
}
const isServer = typeof window === "undefined"
const LayoutContext = React.createContext<LayoutProviderState | undefined>(
undefined
)
const saveToLS = (storageKey: string, value: string) => {
try {
localStorage.setItem(storageKey, value)
} catch {
// Unsupported
}
}
const useLayout = () => {
const context = React.useContext(LayoutContext)
if (context === undefined) {
throw new Error("useLayout must be used within a LayoutProvider")
}
return context
}
const Layout = ({
forcedLayout,
storageKey = "layout",
defaultLayout = "full",
attribute = "class",
value,
children,
}: LayoutProviderProps) => {
const [layout, setLayoutState] = React.useState<Layout>(() => {
if (isServer) return defaultLayout
try {
const saved = localStorage.getItem(storageKey)
if (saved === "fixed" || saved === "full") {
return saved
}
return defaultLayout
} catch {
return defaultLayout
}
})
const attrs = !value ? ["layout-fixed", "layout-full"] : Object.values(value)
const applyLayout = React.useCallback(
(layout: Layout) => {
if (!layout) return
const name = value ? value[layout] : `layout-${layout}`
const d = document.documentElement
const handleAttribute = (attr: string) => {
if (attr === "class") {
d.classList.remove(...attrs)
if (name) d.classList.add(name)
} else if (attr.startsWith("data-")) {
if (name) {
d.setAttribute(attr, name)
} else {
d.removeAttribute(attr)
}
}
}
if (Array.isArray(attribute)) attribute.forEach(handleAttribute)
else handleAttribute(attribute)
},
[attrs, attribute, value]
)
const setLayout = React.useCallback(
(value: Layout | ((prev: Layout) => Layout)) => {
if (typeof value === "function") {
setLayoutState((prevLayout) => {
const newLayout = value(prevLayout)
saveToLS(storageKey, newLayout)
return newLayout
})
} else {
setLayoutState(value)
saveToLS(storageKey, value)
}
},
[storageKey]
)
// localStorage event handling
React.useEffect(() => {
const handleStorage = (e: StorageEvent) => {
if (e.key !== storageKey) return
if (!e.newValue) {
setLayout(defaultLayout)
} else if (e.newValue === "fixed" || e.newValue === "full") {
setLayoutState(e.newValue)
}
}
window.addEventListener("storage", handleStorage)
return () => window.removeEventListener("storage", handleStorage)
}, [setLayout, storageKey, defaultLayout])
// Apply layout on mount and when it changes
React.useEffect(() => {
const currentLayout = forcedLayout ?? layout
applyLayout(currentLayout)
}, [forcedLayout, layout, applyLayout])
// Prevent layout changes during hydration
const [isHydrated, setIsHydrated] = React.useState(false)
React.useEffect(() => {
setIsHydrated(true)
}, [])
const providerValue = React.useMemo(
() => ({
layout: isHydrated ? layout : defaultLayout,
setLayout,
forcedLayout,
}),
[layout, setLayout, forcedLayout, isHydrated, defaultLayout]
)
return (
<LayoutContext.Provider value={providerValue}>
{children}
</LayoutContext.Provider>
)
}
const LayoutProvider = (props: LayoutProviderProps) => {
const context = React.useContext(LayoutContext)
// Ignore nested context providers, just passthrough children
if (context) return <>{props.children}</>
return <Layout {...props} />
}
export { useLayout, LayoutProvider }

View File

@@ -0,0 +1,17 @@
import * as React from "react"
export function useIsMobile(mobileBreakpoint = 768) {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${mobileBreakpoint - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < mobileBreakpoint)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < mobileBreakpoint)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
}

View File

@@ -10,18 +10,26 @@ export async function getAllBlockIds(
],
categories: string[] = []
): Promise<string[]> {
const blocks = await getAllBlocks(types, categories)
return blocks.map((block) => block.name)
}
export async function getAllBlocks(
types: z.infer<typeof registryItemSchema>["type"][] = [
"registry:block",
"registry:internal",
],
categories: string[] = []
) {
const { Index } = await import("@/registry/__index__")
const index = z.record(registryItemSchema).parse(Index)
return Object.values(index)
.filter(
(block) =>
types.includes(block.type) &&
(categories.length === 0 ||
block.categories?.some((category) =>
categories.includes(category)
)) &&
!block.name.startsWith("chart-")
)
.map((block) => block.name)
return Object.values(index).filter(
(block) =>
types.includes(block.type) &&
(categories.length === 0 ||
block.categories?.some((category) => categories.includes(category))) &&
!block.name.startsWith("chart-")
)
}

View File

@@ -25,6 +25,10 @@ export const siteConfig = {
href: "/charts/area",
label: "Charts",
},
{
href: "/themes",
label: "Themes",
},
{
href: "/colors",
label: "Colors",

View File

@@ -15,6 +15,7 @@ const eventSchema = z.object({
"copy_chart_theme",
"copy_chart_data",
"copy_color",
"set_layout",
]),
// declare type AllowedPropertyValues = string | number | boolean | null
properties: z

View File

@@ -52,7 +52,7 @@ export const mdxComponents = {
.replace(/\?/g, "")
.toLowerCase()}
className={cn(
"font-heading mt-12 scroll-m-28 text-2xl font-medium tracking-tight first:mt-0 lg:mt-20 [&+p]:!mt-4",
"font-heading mt-12 scroll-m-28 text-2xl font-medium tracking-tight first:mt-0 lg:mt-20 [&+p]:!mt-4 *:[code]:text-2xl",
className
)}
{...props}
@@ -62,7 +62,7 @@ export const mdxComponents = {
h3: ({ className, ...props }: React.ComponentProps<"h3">) => (
<h3
className={cn(
"font-heading mt-8 scroll-m-28 text-xl font-semibold tracking-tight",
"font-heading mt-8 scroll-m-28 text-xl font-semibold tracking-tight *:[code]:text-xl",
className
)}
{...props}

View File

@@ -63,6 +63,11 @@ const nextConfig = {
destination: "/charts/area",
permanent: true,
},
{
source: "/view/styles/:style/:name",
destination: "/view/:name",
permanent: true,
},
]
},
}

View File

@@ -58,6 +58,7 @@
"@tanstack/react-table": "^8.9.1",
"@vercel/analytics": "^1.4.1",
"change-case": "^5.4.4",
"chrono-node": "^2.8.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
@@ -70,20 +71,22 @@
"fumadocs-ui": "^15.3.1",
"input-otp": "^1.4.2",
"jotai": "^2.1.0",
"little-date": "^1.0.0",
"lodash": "^4.17.21",
"lucide-react": "0.474.0",
"motion": "^12.12.1",
"next": "15.3.1",
"next-themes": "0.4.6",
"postcss": "^8.5.1",
"react": "19.1.0",
"react-day-picker": "^8.7.1",
"react-day-picker": "^9.7.0",
"react-dom": "19.1.0",
"react-hook-form": "^7.54.2",
"react-resizable-panels": "^2.1.7",
"recharts": "2.15.1",
"rehype-pretty-code": "^0.14.1",
"rimraf": "^6.0.1",
"shadcn": "2.6.0",
"shadcn": "2.9.1",
"shiki": "^1.10.1",
"sonner": "^2.0.0",
"tailwind-merge": "^3.0.1",
@@ -95,6 +98,7 @@
"@eslint/eslintrc": "^3",
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@tailwindcss/postcss": "^4",
"@types/lodash": "^4.17.7",
"@types/mdx": "^2.0.13",
"@types/node": "^20",
"@types/react": "19.1.2",

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

View File

@@ -137,7 +137,7 @@
"name": "calendar",
"type": "registry:ui",
"dependencies": [
"react-day-picker@8.10.1",
"react-day-picker@latest",
"date-fns"
],
"registryDependencies": [
@@ -180,7 +180,7 @@
"name": "chart",
"type": "registry:ui",
"dependencies": [
"recharts",
"recharts@2.15.4",
"lucide-react"
],
"registryDependencies": [

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-01",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "A simple calendar.",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "blocks/calendar-01.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\n\nexport default function Calendar01() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <Calendar\n mode=\"single\"\n defaultMonth={date}\n selected={date}\n onSelect={setDate}\n className=\"rounded-lg border shadow-sm\"\n />\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-4 py-12 items-start md:py-20 justify-center min-w-0",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-02",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Multiple months with single selection.",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "blocks/calendar-02.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\n\nexport default function Calendar02() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <Calendar\n mode=\"single\"\n defaultMonth={date}\n numberOfMonths={2}\n selected={date}\n onSelect={setDate}\n className=\"rounded-lg border shadow-sm\"\n />\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-4 py-12 items-start md:py-20 justify-center min-w-0",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-03",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Multiple months with multiple selection.",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "blocks/calendar-03.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\n\nexport default function Calendar03() {\n const [dates, setDates] = React.useState<Date[]>([\n new Date(2025, 5, 12),\n new Date(2025, 6, 24),\n ])\n\n return (\n <Calendar\n mode=\"multiple\"\n numberOfMonths={2}\n defaultMonth={dates[0]}\n required\n selected={dates}\n onSelect={setDates}\n max={5}\n className=\"rounded-lg border shadow-sm\"\n />\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-4 py-12 items-start md:py-20 justify-center min-w-0",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-04",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Single month with range selection",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "blocks/calendar-04.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DateRange } from \"react-day-picker\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\n\nexport default function Calendar04() {\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 9),\n to: new Date(2025, 5, 26),\n })\n\n return (\n <Calendar\n mode=\"range\"\n defaultMonth={dateRange?.from}\n selected={dateRange}\n onSelect={setDateRange}\n className=\"rounded-lg border shadow-sm\"\n />\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-4 py-12 items-start md:py-20 justify-center min-w-0 xl:pt-28",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-05",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Multiple months with range selection",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "blocks/calendar-05.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DateRange } from \"react-day-picker\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\n\nexport default function Calendar05() {\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 12),\n to: new Date(2025, 6, 15),\n })\n\n return (\n <Calendar\n mode=\"range\"\n defaultMonth={dateRange?.from}\n selected={dateRange}\n onSelect={setDateRange}\n numberOfMonths={2}\n className=\"rounded-lg border shadow-sm\"\n />\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-06",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Range selection with minimum days",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "blocks/calendar-06.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DateRange } from \"react-day-picker\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\n\nexport default function Calendar06() {\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 12),\n to: new Date(2025, 5, 26),\n })\n\n return (\n <div className=\"flex min-w-0 flex-col gap-2\">\n <Calendar\n mode=\"range\"\n defaultMonth={dateRange?.from}\n selected={dateRange}\n onSelect={setDateRange}\n numberOfMonths={1}\n min={5}\n className=\"rounded-lg border shadow-sm\"\n />\n <div className=\"text-muted-foreground text-center text-xs\">\n A minimum of 5 days is required\n </div>\n </div>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-07",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Range selection with minimum and maximum days",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "blocks/calendar-07.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DateRange } from \"react-day-picker\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\n\nexport default function Calendar07() {\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 18),\n to: new Date(2025, 6, 7),\n })\n\n return (\n <div className=\"flex min-w-0 flex-col gap-2\">\n <Calendar\n mode=\"range\"\n defaultMonth={dateRange?.from}\n selected={dateRange}\n onSelect={setDateRange}\n numberOfMonths={2}\n min={2}\n max={20}\n className=\"rounded-lg border shadow-sm\"\n />\n <div className=\"text-muted-foreground text-center text-xs\">\n Your stay must be between 2 and 20 nights\n </div>\n </div>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-08",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Calendar with disabled days",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "blocks/calendar-08.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\n\nexport default function Calendar08() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <Calendar\n mode=\"single\"\n defaultMonth={date}\n selected={date}\n onSelect={setDate}\n disabled={{\n before: new Date(2025, 5, 12),\n }}\n className=\"rounded-lg border shadow-sm\"\n />\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-09",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Calendar with disabled weekends",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "blocks/calendar-09.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DateRange } from \"react-day-picker\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\n\nexport default function Calendar09() {\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 17),\n to: new Date(2025, 5, 20),\n })\n\n return (\n <Calendar\n mode=\"range\"\n defaultMonth={dateRange?.from}\n selected={dateRange}\n onSelect={setDateRange}\n numberOfMonths={2}\n disabled={{ dayOfWeek: [0, 6] }}\n className=\"rounded-lg border shadow-sm\"\n excludeDisabled\n />\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,29 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-10",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Today button",
"registryDependencies": [
"calendar",
"card",
"button"
],
"files": [
{
"path": "blocks/calendar-10.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/registry/default/ui/card\"\n\nexport default function Calendar10() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n const [month, setMonth] = React.useState<Date | undefined>(new Date())\n\n return (\n <Card>\n <CardHeader className=\"relative\">\n <CardTitle>Appointment</CardTitle>\n <CardDescription>Find a date</CardDescription>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"absolute right-4 top-4\"\n onClick={() => {\n setMonth(new Date())\n setDate(new Date())\n }}\n >\n Today\n </Button>\n </CardHeader>\n <CardContent>\n <Calendar\n mode=\"single\"\n month={month}\n onMonthChange={setMonth}\n selected={date}\n onSelect={setDate}\n className=\"bg-transparent p-0\"\n />\n </CardContent>\n </Card>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-11",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Start and end of month",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "blocks/calendar-11.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DateRange } from \"react-day-picker\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\n\nexport default function Calendar11() {\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 17),\n to: new Date(2025, 5, 20),\n })\n\n return (\n <div className=\"flex min-w-0 flex-col gap-2\">\n <Calendar\n mode=\"range\"\n selected={dateRange}\n onSelect={setDateRange}\n numberOfMonths={2}\n startMonth={new Date(2025, 5, 1)}\n endMonth={new Date(2025, 6, 31)}\n disableNavigation\n className=\"rounded-lg border shadow-sm\"\n />\n <div className=\"text-muted-foreground text-center text-xs\">\n We are open in June and July only.\n </div>\n </div>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,29 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-12",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Localized calendar",
"registryDependencies": [
"calendar",
"card",
"select"
],
"files": [
{
"path": "blocks/calendar-12.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DateRange } from \"react-day-picker\"\nimport { enUS, es } from \"react-day-picker/locale\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/registry/default/ui/card\"\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/default/ui/select\"\n\nconst localizedStrings = {\n en: {\n title: \"Book an appointment\",\n description: \"Select the dates for your appointment\",\n },\n es: {\n title: \"Reserva una cita\",\n description: \"Selecciona las fechas para tu cita\",\n },\n} as const\n\nexport default function Calendar12() {\n const [locale, setLocale] =\n React.useState<keyof typeof localizedStrings>(\"es\")\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 8, 9),\n to: new Date(2025, 8, 17),\n })\n\n return (\n <Card>\n <CardHeader className=\"relative border-b\">\n <CardTitle>{localizedStrings[locale].title}</CardTitle>\n <CardDescription>\n {localizedStrings[locale].description}\n </CardDescription>\n <Select\n value={locale}\n onValueChange={(value) =>\n setLocale(value as keyof typeof localizedStrings)\n }\n >\n <SelectTrigger className=\"absolute right-4 top-4 w-[100px]\">\n <SelectValue placeholder=\"Language\" />\n </SelectTrigger>\n <SelectContent align=\"end\">\n <SelectItem value=\"es\">Español</SelectItem>\n <SelectItem value=\"en\">English</SelectItem>\n </SelectContent>\n </Select>\n </CardHeader>\n <CardContent className=\"pt-4\">\n <Calendar\n mode=\"range\"\n selected={dateRange}\n onSelect={setDateRange}\n defaultMonth={dateRange?.from}\n numberOfMonths={2}\n locale={locale === \"es\" ? es : enUS}\n className=\"bg-transparent p-0\"\n buttonVariant=\"outline\"\n />\n </CardContent>\n </Card>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,29 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-13",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "With Month and Year Dropdown",
"registryDependencies": [
"calendar",
"label",
"select"
],
"files": [
{
"path": "blocks/calendar-13.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/default/ui/select\"\n\nexport default function Calendar13() {\n const [dropdown, setDropdown] =\n React.useState<React.ComponentProps<typeof Calendar>[\"captionLayout\"]>(\n \"dropdown\"\n )\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <div className=\"flex flex-col gap-4\">\n <Calendar\n mode=\"single\"\n defaultMonth={date}\n selected={date}\n onSelect={setDate}\n captionLayout={dropdown}\n className=\"rounded-lg border shadow-sm\"\n />\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"dropdown\" className=\"px-1\">\n Dropdown\n </Label>\n <Select\n value={dropdown}\n onValueChange={(value) =>\n setDropdown(\n value as React.ComponentProps<typeof Calendar>[\"captionLayout\"]\n )\n }\n >\n <SelectTrigger id=\"dropdown\" className=\"bg-background w-full\">\n <SelectValue placeholder=\"Dropdown\" />\n </SelectTrigger>\n <SelectContent align=\"center\">\n <SelectItem value=\"dropdown\">Month and Year</SelectItem>\n <SelectItem value=\"dropdown-months\">Month Only</SelectItem>\n <SelectItem value=\"dropdown-years\">Year Only</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-14",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "With Booked/Unavailable Days",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "blocks/calendar-14.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\n\nexport default function Calendar14() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n const bookedDates = Array.from(\n { length: 12 },\n (_, i) => new Date(2025, 5, 15 + i)\n )\n\n return (\n <Calendar\n mode=\"single\"\n defaultMonth={date}\n selected={date}\n onSelect={setDate}\n disabled={bookedDates}\n modifiers={{\n booked: bookedDates,\n }}\n modifiersClassNames={{\n booked: \"[&>button]:line-through opacity-100\",\n }}\n className=\"rounded-lg border shadow-sm\"\n />\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-15",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "With Week Numbers",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "blocks/calendar-15.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\n\nexport default function Calendar15() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <Calendar\n mode=\"single\"\n defaultMonth={date}\n selected={date}\n onSelect={setDate}\n className=\"rounded-lg border shadow-sm\"\n showWeekNumber\n />\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,30 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-16",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "With time picker",
"registryDependencies": [
"calendar",
"card",
"input",
"label"
],
"files": [
{
"path": "blocks/calendar-16.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Clock2Icon } from \"lucide-react\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport { Card, CardContent, CardFooter } from \"@/registry/default/ui/card\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\n\nexport default function Calendar16() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <Card className=\"w-fit py-4\">\n <CardContent className=\"px-4\">\n <Calendar\n mode=\"single\"\n selected={date}\n onSelect={setDate}\n className=\"bg-transparent p-0\"\n />\n </CardContent>\n <CardFooter className=\"flex flex-col gap-6 border-t px-4 pb-0 pt-4\">\n <div className=\"flex w-full flex-col gap-3\">\n <Label htmlFor=\"time-from\">Start Time</Label>\n <div className=\"relative flex w-full items-center gap-2\">\n <Clock2Icon className=\"text-muted-foreground pointer-events-none absolute left-2.5 size-4 select-none\" />\n <Input\n id=\"time-from\"\n type=\"time\"\n step=\"1\"\n defaultValue=\"10:30:00\"\n className=\"appearance-none pl-8 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none\"\n />\n </div>\n </div>\n <div className=\"flex w-full flex-col gap-3\">\n <Label htmlFor=\"time-to\">End Time</Label>\n <div className=\"relative flex w-full items-center gap-2\">\n <Clock2Icon className=\"text-muted-foreground pointer-events-none absolute left-2.5 size-4 select-none\" />\n <Input\n id=\"time-to\"\n type=\"time\"\n step=\"1\"\n defaultValue=\"12:30:00\"\n className=\"appearance-none pl-8 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none\"\n />\n </div>\n </div>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start justify-center min-w-0",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,30 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-17",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "With time picker inline",
"registryDependencies": [
"calendar",
"card",
"input",
"label"
],
"files": [
{
"path": "blocks/calendar-17.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport { Card, CardContent, CardFooter } from \"@/registry/default/ui/card\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\n\nexport default function Calendar17() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <Card className=\"w-fit py-4\">\n <CardContent className=\"px-4\">\n <Calendar\n mode=\"single\"\n selected={date}\n onSelect={setDate}\n className=\"bg-transparent p-0 [--cell-size:2.8rem]\"\n />\n </CardContent>\n <CardFooter className=\"*:[div]:w-full flex gap-2 border-t px-4 pb-0 pt-4\">\n <div className=\"flex-1\">\n <Label htmlFor=\"time-from\" className=\"sr-only\">\n Start Time\n </Label>\n <Input\n id=\"time-from\"\n type=\"time\"\n step=\"1\"\n defaultValue=\"10:30:00\"\n className=\"appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none\"\n />\n </div>\n <span>-</span>\n <div className=\"flex-1\">\n <Label htmlFor=\"time-to\" className=\"sr-only\">\n End Time\n </Label>\n <Input\n id=\"time-to\"\n type=\"time\"\n step=\"1\"\n defaultValue=\"12:30:00\"\n className=\"appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none\"\n />\n </div>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-18",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Variable size",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "blocks/calendar-18.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/default/ui/calendar\"\n\nexport default function Calendar18() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <Calendar\n mode=\"single\"\n selected={date}\n onSelect={setDate}\n className=\"rounded-lg border [--cell-size:2.75rem] md:[--cell-size:3rem]\"\n buttonVariant=\"ghost\"\n />\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,33 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-19",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "With presets",
"dependencies": [
"date-fns"
],
"registryDependencies": [
"calendar",
"card",
"input",
"label"
],
"files": [
{
"path": "blocks/calendar-19.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { addDays } from \"date-fns\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport { Card, CardContent, CardFooter } from \"@/registry/default/ui/card\"\n\nexport default function Calendar19() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <Card className=\"max-w-[300px] py-4\">\n <CardContent className=\"px-4\">\n <Calendar\n mode=\"single\"\n selected={date}\n onSelect={setDate}\n defaultMonth={date}\n className=\"bg-transparent p-0 [--cell-size:2.375rem]\"\n />\n </CardContent>\n <CardFooter className=\"flex flex-wrap gap-2 border-t px-4 pb-0 pt-4\">\n {[\n { label: \"Today\", value: 0 },\n { label: \"Tomorrow\", value: 1 },\n { label: \"In 3 days\", value: 3 },\n { label: \"In a week\", value: 7 },\n { label: \"In 2 weeks\", value: 14 },\n ].map((preset) => (\n <Button\n key={preset.value}\n variant=\"outline\"\n size=\"sm\"\n className=\"flex-1\"\n onClick={() => {\n const newDate = addDays(new Date(), preset.value)\n setDate(newDate)\n }}\n >\n {preset.label}\n </Button>\n ))}\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start justify-center min-w-0",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,29 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-20",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "With time presets",
"registryDependencies": [
"calendar",
"card",
"button"
],
"files": [
{
"path": "blocks/calendar-20.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport { Card, CardContent, CardFooter } from \"@/registry/default/ui/card\"\n\nexport default function Calendar20() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n const [selectedTime, setSelectedTime] = React.useState<string | null>(\"10:00\")\n const timeSlots = Array.from({ length: 37 }, (_, i) => {\n const totalMinutes = i * 15\n const hour = Math.floor(totalMinutes / 60) + 9\n const minute = totalMinutes % 60\n return `${hour.toString().padStart(2, \"0\")}:${minute\n .toString()\n .padStart(2, \"0\")}`\n })\n\n const bookedDates = Array.from(\n { length: 3 },\n (_, i) => new Date(2025, 5, 17 + i)\n )\n\n return (\n <Card className=\"gap-0 p-0\">\n <CardContent className=\"relative p-0 md:pr-48\">\n <div className=\"p-6\">\n <Calendar\n mode=\"single\"\n selected={date}\n onSelect={setDate}\n defaultMonth={date}\n disabled={bookedDates}\n showOutsideDays={false}\n modifiers={{\n booked: bookedDates,\n }}\n modifiersClassNames={{\n booked: \"[&>button]:line-through opacity-100\",\n }}\n className=\"bg-transparent p-0 [--cell-size:2.5rem] md:[--cell-size:3rem]\"\n formatters={{\n formatWeekdayName: (date) => {\n return date.toLocaleString(\"en-US\", { weekday: \"short\" })\n },\n }}\n />\n </div>\n <div className=\"no-scrollbar inset-y-0 right-0 flex max-h-72 w-full scroll-pb-6 flex-col gap-4 overflow-y-auto border-t p-6 md:absolute md:max-h-none md:w-48 md:border-l md:border-t-0\">\n <div className=\"grid gap-2\">\n {timeSlots.map((time) => (\n <Button\n key={time}\n variant={selectedTime === time ? \"default\" : \"outline\"}\n onClick={() => setSelectedTime(time)}\n className=\"w-full shadow-none\"\n >\n {time}\n </Button>\n ))}\n </div>\n </div>\n </CardContent>\n <CardFooter className=\"flex flex-col gap-4 border-t !py-5 px-6 md:flex-row\">\n <div className=\"text-sm\">\n {date && selectedTime ? (\n <>\n Your meeting is booked for{\" \"}\n <span className=\"font-medium\">\n {\" \"}\n {date?.toLocaleDateString(\"en-US\", {\n weekday: \"long\",\n day: \"numeric\",\n month: \"long\",\n })}{\" \"}\n </span>\n at <span className=\"font-medium\">{selectedTime}</span>.\n </>\n ) : (\n <>Select a date and time for your meeting.</>\n )}\n </div>\n <Button\n disabled={!date || !selectedTime}\n className=\"w-full md:ml-auto md:w-auto\"\n variant=\"outline\"\n >\n Continue\n </Button>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start justify-center min-w-0",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-21",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Custom days and formatters",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "blocks/calendar-21.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { DateRange } from \"react-day-picker\"\n\nimport { Calendar, CalendarDayButton } from \"@/registry/default/ui/calendar\"\n\nexport default function Calendar21() {\n const [range, setRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 12),\n to: new Date(2025, 5, 17),\n })\n\n return (\n <Calendar\n mode=\"range\"\n defaultMonth={range?.from}\n selected={range}\n onSelect={setRange}\n numberOfMonths={1}\n captionLayout=\"dropdown\"\n className=\"rounded-lg border shadow-sm [--cell-size:2.75rem] md:[--cell-size:3rem]\"\n formatters={{\n formatMonthDropdown: (date) => {\n return date.toLocaleString(\"default\", { month: \"long\" })\n },\n }}\n components={{\n DayButton: ({ children, modifiers, day, ...props }) => {\n const isWeekend = day.date.getDay() === 0 || day.date.getDay() === 6\n\n return (\n <CalendarDayButton day={day} modifiers={modifiers} {...props}>\n {children}\n {!modifiers.outside && <span>{isWeekend ? \"$220\" : \"$100\"}</span>}\n </CalendarDayButton>\n )\n },\n }}\n />\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start justify-center min-w-0",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,30 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-22",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Date picker",
"registryDependencies": [
"calendar",
"popover",
"button",
"label"
],
"files": [
{
"path": "blocks/calendar-22.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronDownIcon } from \"lucide-react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/registry/default/ui/popover\"\n\nexport default function Calendar22() {\n const [open, setOpen] = React.useState(false)\n const [date, setDate] = React.useState<Date | undefined>(undefined)\n\n return (\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"date\" className=\"px-1\">\n Date of birth\n </Label>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n variant=\"outline\"\n id=\"date\"\n className=\"w-48 justify-between font-normal\"\n >\n {date ? date.toLocaleDateString() : \"Select date\"}\n <ChevronDownIcon />\n </Button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto overflow-hidden p-0\" align=\"start\">\n <Calendar\n mode=\"single\"\n selected={date}\n captionLayout=\"dropdown\"\n onSelect={(date) => {\n setDate(date)\n setOpen(false)\n }}\n />\n </PopoverContent>\n </Popover>\n </div>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,30 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-23",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Date range picker",
"registryDependencies": [
"calendar",
"popover",
"button",
"label"
],
"files": [
{
"path": "blocks/calendar-23.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronDownIcon } from \"lucide-react\"\nimport { type DateRange } from \"react-day-picker\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/registry/default/ui/popover\"\n\nexport default function Calendar23() {\n const [range, setRange] = React.useState<DateRange | undefined>(undefined)\n\n return (\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"dates\" className=\"px-1\">\n Select your stay\n </Label>\n <Popover>\n <PopoverTrigger asChild>\n <Button\n variant=\"outline\"\n id=\"dates\"\n className=\"w-56 justify-between font-normal\"\n >\n {range?.from && range?.to\n ? `${range.from.toLocaleDateString()} - ${range.to.toLocaleDateString()}`\n : \"Select date\"}\n <ChevronDownIcon />\n </Button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto overflow-hidden p-0\" align=\"start\">\n <Calendar\n mode=\"range\"\n selected={range}\n captionLayout=\"dropdown\"\n onSelect={(range) => {\n setRange(range)\n }}\n />\n </PopoverContent>\n </Popover>\n </div>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,30 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-24",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Date and Time picker",
"registryDependencies": [
"calendar",
"popover",
"button",
"label"
],
"files": [
{
"path": "blocks/calendar-24.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronDownIcon } from \"lucide-react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/registry/default/ui/popover\"\n\nexport default function Calendar24() {\n const [open, setOpen] = React.useState(false)\n const [date, setDate] = React.useState<Date | undefined>(undefined)\n\n return (\n <div className=\"flex gap-4\">\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"date\" className=\"px-1\">\n Date\n </Label>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n variant=\"outline\"\n id=\"date\"\n className=\"w-32 justify-between font-normal\"\n >\n {date ? date.toLocaleDateString() : \"Select date\"}\n <ChevronDownIcon />\n </Button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto overflow-hidden p-0\" align=\"start\">\n <Calendar\n mode=\"single\"\n selected={date}\n captionLayout=\"dropdown\"\n onSelect={(date) => {\n setDate(date)\n setOpen(false)\n }}\n />\n </PopoverContent>\n </Popover>\n </div>\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"time\" className=\"px-1\">\n Time\n </Label>\n <Input\n type=\"time\"\n id=\"time\"\n step=\"1\"\n defaultValue=\"10:30:00\"\n className=\"bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none\"\n />\n </div>\n </div>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,30 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-25",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Date and Time range picker",
"registryDependencies": [
"calendar",
"popover",
"button",
"label"
],
"files": [
{
"path": "blocks/calendar-25.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronDownIcon } from \"lucide-react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/registry/default/ui/popover\"\n\nexport default function Calendar25() {\n const [open, setOpen] = React.useState(false)\n const [date, setDate] = React.useState<Date | undefined>(undefined)\n\n return (\n <div className=\"flex flex-col gap-6\">\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"date\" className=\"px-1\">\n Date\n </Label>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n variant=\"outline\"\n id=\"date\"\n className=\"w-full justify-between font-normal\"\n >\n {date ? date.toLocaleDateString() : \"Select date\"}\n <ChevronDownIcon />\n </Button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto overflow-hidden p-0\" align=\"start\">\n <Calendar\n mode=\"single\"\n selected={date}\n captionLayout=\"dropdown\"\n onSelect={(date) => {\n setDate(date)\n setOpen(false)\n }}\n />\n </PopoverContent>\n </Popover>\n </div>\n <div className=\"flex gap-4\">\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"time-from\" className=\"px-1\">\n From\n </Label>\n <Input\n type=\"time\"\n id=\"time-from\"\n step=\"1\"\n defaultValue=\"10:30:00\"\n className=\"bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none\"\n />\n </div>\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"time-to\" className=\"px-1\">\n To\n </Label>\n <Input\n type=\"time\"\n id=\"time-to\"\n step=\"1\"\n defaultValue=\"12:30:00\"\n className=\"bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none\"\n />\n </div>\n </div>\n </div>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,31 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-26",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Date range picker with time",
"registryDependencies": [
"calendar",
"popover",
"button",
"input",
"label"
],
"files": [
{
"path": "blocks/calendar-26.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronDownIcon } from \"lucide-react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/registry/default/ui/popover\"\n\nexport default function Calendar26() {\n const [openFrom, setOpenFrom] = React.useState(false)\n const [openTo, setOpenTo] = React.useState(false)\n const [dateFrom, setDateFrom] = React.useState<Date | undefined>(\n new Date(\"2025-06-01\")\n )\n const [dateTo, setDateTo] = React.useState<Date | undefined>(\n new Date(\"2025-06-03\")\n )\n\n return (\n <div className=\"flex w-full max-w-64 min-w-0 flex-col gap-6\">\n <div className=\"flex gap-4\">\n <div className=\"flex flex-1 flex-col gap-3\">\n <Label htmlFor=\"date-from\" className=\"px-1\">\n Check-in\n </Label>\n <Popover open={openFrom} onOpenChange={setOpenFrom}>\n <PopoverTrigger asChild>\n <Button\n variant=\"outline\"\n id=\"date-from\"\n className=\"w-full justify-between font-normal\"\n >\n {dateFrom\n ? dateFrom.toLocaleDateString(\"en-US\", {\n day: \"2-digit\",\n month: \"short\",\n year: \"numeric\",\n })\n : \"Select date\"}\n <ChevronDownIcon />\n </Button>\n </PopoverTrigger>\n <PopoverContent\n className=\"w-auto overflow-hidden p-0\"\n align=\"start\"\n >\n <Calendar\n mode=\"single\"\n selected={dateFrom}\n captionLayout=\"dropdown\"\n onSelect={(date) => {\n setDateFrom(date)\n setOpenFrom(false)\n }}\n />\n </PopoverContent>\n </Popover>\n </div>\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"time-from\" className=\"invisible px-1\">\n From\n </Label>\n <Input\n type=\"time\"\n id=\"time-from\"\n step=\"1\"\n defaultValue=\"10:30:00\"\n className=\"bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none\"\n />\n </div>\n </div>\n <div className=\"flex gap-4\">\n <div className=\"flex flex-1 flex-col gap-3\">\n <Label htmlFor=\"date-to\" className=\"px-1\">\n Check-out\n </Label>\n <Popover open={openTo} onOpenChange={setOpenTo}>\n <PopoverTrigger asChild>\n <Button\n variant=\"outline\"\n id=\"date-to\"\n className=\"w-full justify-between font-normal\"\n >\n {dateTo\n ? dateTo.toLocaleDateString(\"en-US\", {\n day: \"2-digit\",\n month: \"short\",\n year: \"numeric\",\n })\n : \"Select date\"}\n <ChevronDownIcon />\n </Button>\n </PopoverTrigger>\n <PopoverContent\n className=\"w-auto overflow-hidden p-0\"\n align=\"start\"\n >\n <Calendar\n mode=\"single\"\n selected={dateTo}\n captionLayout=\"dropdown\"\n onSelect={(date) => {\n setDateTo(date)\n setOpenTo(false)\n }}\n disabled={dateFrom && { before: dateFrom }}\n />\n </PopoverContent>\n </Popover>\n </div>\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"time-to\" className=\"invisible px-1\">\n To\n </Label>\n <Input\n type=\"time\"\n id=\"time-to\"\n step=\"1\"\n defaultValue=\"12:30:00\"\n className=\"bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none\"\n />\n </div>\n </div>\n </div>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,31 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-28",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Input with date picker",
"registryDependencies": [
"calendar",
"input",
"label",
"popover",
"button"
],
"files": [
{
"path": "blocks/calendar-28.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CalendarIcon } from \"lucide-react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/registry/default/ui/popover\"\n\nfunction formatDate(date: Date | undefined) {\n if (!date) {\n return \"\"\n }\n\n return date.toLocaleDateString(\"en-US\", {\n day: \"2-digit\",\n month: \"long\",\n year: \"numeric\",\n })\n}\n\nfunction isValidDate(date: Date | undefined) {\n if (!date) {\n return false\n }\n return !isNaN(date.getTime())\n}\n\nexport default function Calendar28() {\n const [open, setOpen] = React.useState(false)\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(\"2025-06-01\")\n )\n const [month, setMonth] = React.useState<Date | undefined>(date)\n const [value, setValue] = React.useState(formatDate(date))\n\n return (\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"date\" className=\"px-1\">\n Subscription Date\n </Label>\n <div className=\"relative flex gap-2\">\n <Input\n id=\"date\"\n value={value}\n placeholder=\"June 01, 2025\"\n className=\"bg-background pr-10\"\n onChange={(e) => {\n const date = new Date(e.target.value)\n setValue(e.target.value)\n if (isValidDate(date)) {\n setDate(date)\n setMonth(date)\n }\n }}\n onKeyDown={(e) => {\n if (e.key === \"ArrowDown\") {\n e.preventDefault()\n setOpen(true)\n }\n }}\n />\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n id=\"date-picker\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"absolute right-2 top-1/2 h-6 w-6 -translate-y-1/2\"\n >\n <CalendarIcon className=\"size-3\" />\n <span className=\"sr-only\">Select date</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent\n className=\"w-auto overflow-hidden p-0\"\n align=\"end\"\n alignOffset={-8}\n sideOffset={10}\n >\n <Calendar\n mode=\"single\"\n selected={date}\n captionLayout=\"dropdown\"\n month={month}\n onMonthChange={setMonth}\n onSelect={(date) => {\n setDate(date)\n setValue(formatDate(date))\n setOpen(false)\n }}\n />\n </PopoverContent>\n </Popover>\n </div>\n </div>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,34 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-29",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Natural language date picker",
"dependencies": [
"chrono-node"
],
"registryDependencies": [
"calendar",
"input",
"label",
"popover",
"button"
],
"files": [
{
"path": "blocks/calendar-29.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { parseDate } from \"chrono-node\"\nimport { CalendarIcon } from \"lucide-react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/registry/default/ui/popover\"\n\nfunction formatDate(date: Date | undefined) {\n if (!date) {\n return \"\"\n }\n\n return date.toLocaleDateString(\"en-US\", {\n day: \"2-digit\",\n month: \"long\",\n year: \"numeric\",\n })\n}\n\nexport default function Calendar29() {\n const [open, setOpen] = React.useState(false)\n const [value, setValue] = React.useState(\"In 2 days\")\n const [date, setDate] = React.useState<Date | undefined>(\n parseDate(value) || undefined\n )\n const [month, setMonth] = React.useState<Date | undefined>(date)\n\n return (\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"date\" className=\"px-1\">\n Schedule Date\n </Label>\n <div className=\"relative flex gap-2\">\n <Input\n id=\"date\"\n value={value}\n placeholder=\"Tomorrow or next week\"\n className=\"bg-background pr-10\"\n onChange={(e) => {\n setValue(e.target.value)\n const date = parseDate(e.target.value)\n if (date) {\n setDate(date)\n setMonth(date)\n }\n }}\n onKeyDown={(e) => {\n if (e.key === \"ArrowDown\") {\n e.preventDefault()\n setOpen(true)\n }\n }}\n />\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n id=\"date-picker\"\n variant=\"ghost\"\n className=\"absolute top-1/2 right-2 size-6 -translate-y-1/2\"\n >\n <CalendarIcon className=\"size-3.5\" />\n <span className=\"sr-only\">Select date</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto overflow-hidden p-0\" align=\"end\">\n <Calendar\n mode=\"single\"\n selected={date}\n captionLayout=\"dropdown\"\n month={month}\n onMonthChange={setMonth}\n onSelect={(date) => {\n setDate(date)\n setValue(formatDate(date))\n setOpen(false)\n }}\n />\n </PopoverContent>\n </Popover>\n </div>\n <div className=\"text-muted-foreground px-1 text-sm\">\n Your post will be published on{\" \"}\n <span className=\"font-medium\">{formatDate(date)}</span>.\n </div>\n </div>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,31 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-30",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "With little-date",
"registryDependencies": [
"calendar",
"input",
"label",
"popover",
"button"
],
"files": [
{
"path": "blocks/calendar-30.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { formatDateRange } from \"little-date\"\nimport { ChevronDownIcon } from \"lucide-react\"\nimport { type DateRange } from \"react-day-picker\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/registry/default/ui/popover\"\n\nexport default function Calendar30() {\n const [range, setRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 4),\n to: new Date(2025, 5, 10),\n })\n\n return (\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"dates\" className=\"px-1\">\n Select your stay\n </Label>\n <Popover>\n <PopoverTrigger asChild>\n <Button\n variant=\"outline\"\n id=\"dates\"\n className=\"w-56 justify-between font-normal\"\n >\n {range?.from && range?.to\n ? formatDateRange(range.from, range.to, {\n includeTime: false,\n })\n : \"Select date\"}\n <ChevronDownIcon />\n </Button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto overflow-hidden p-0\" align=\"start\">\n <Calendar\n mode=\"range\"\n selected={range}\n captionLayout=\"dropdown\"\n onSelect={(range) => {\n setRange(range)\n }}\n />\n </PopoverContent>\n </Popover>\n </div>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,29 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-31",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "With event slots",
"registryDependencies": [
"calendar",
"card",
"button"
],
"files": [
{
"path": "blocks/calendar-31.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { formatDateRange } from \"little-date\"\nimport { PlusIcon } from \"lucide-react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport { Card, CardContent, CardFooter } from \"@/registry/default/ui/card\"\n\nconst events = [\n {\n title: \"Team Sync Meeting\",\n from: \"2025-06-12T09:00:00\",\n to: \"2025-06-12T10:00:00\",\n },\n {\n title: \"Design Review\",\n from: \"2025-06-12T11:30:00\",\n to: \"2025-06-12T12:30:00\",\n },\n {\n title: \"Client Presentation\",\n from: \"2025-06-12T14:00:00\",\n to: \"2025-06-12T15:00:00\",\n },\n]\n\nexport default function Calendar31() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <Card className=\"w-fit py-4\">\n <CardContent className=\"px-4\">\n <Calendar\n mode=\"single\"\n selected={date}\n onSelect={setDate}\n className=\"bg-transparent p-0\"\n required\n />\n </CardContent>\n <CardFooter className=\"flex flex-col items-start gap-3 border-t px-4 pb-0 pt-4\">\n <div className=\"flex w-full items-center justify-between px-1\">\n <div className=\"text-sm font-medium\">\n {date?.toLocaleDateString(\"en-US\", {\n day: \"numeric\",\n month: \"long\",\n year: \"numeric\",\n })}\n </div>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-6 w-6\"\n title=\"Add Event\"\n >\n <PlusIcon />\n <span className=\"sr-only\">Add Event</span>\n </Button>\n </div>\n <div className=\"flex w-full flex-col gap-2\">\n {events.map((event) => (\n <div\n key={event.title}\n className=\"bg-muted after:bg-primary/70 relative rounded-md p-2 pl-6 text-sm after:absolute after:inset-y-2 after:left-2 after:w-1 after:rounded-full\"\n >\n <div className=\"font-medium\">{event.title}</div>\n <div className=\"text-muted-foreground text-xs\">\n {formatDateRange(new Date(event.from), new Date(event.to))}\n </div>\n </div>\n ))}\n </div>\n </CardFooter>\n </Card>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "700px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,29 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-32",
"type": "registry:block",
"author": "shadcn (https://ui.shadcn.com)",
"description": "Date picker in a drawer",
"registryDependencies": [
"calendar",
"button",
"drawer"
],
"files": [
{
"path": "blocks/calendar-32.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CalendarPlusIcon } from \"lucide-react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Calendar } from \"@/registry/default/ui/calendar\"\nimport {\n Drawer,\n DrawerContent,\n DrawerDescription,\n DrawerHeader,\n DrawerTitle,\n DrawerTrigger,\n} from \"@/registry/default/ui/drawer\"\nimport { Label } from \"@/registry/default/ui/label\"\n\nexport default function Calendar32() {\n const [open, setOpen] = React.useState(false)\n const [date, setDate] = React.useState<Date | undefined>(undefined)\n\n return (\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"date\" className=\"px-1\">\n Date of birth\n </Label>\n <Drawer open={open} onOpenChange={setOpen}>\n <DrawerTrigger asChild>\n <Button\n variant=\"outline\"\n id=\"date\"\n className=\"w-48 justify-between font-normal\"\n >\n {date ? date.toLocaleDateString() : \"Select date\"}\n <CalendarPlusIcon />\n </Button>\n </DrawerTrigger>\n <DrawerContent className=\"w-auto overflow-hidden p-0\">\n <DrawerHeader className=\"sr-only\">\n <DrawerTitle>Select date</DrawerTitle>\n <DrawerDescription>Set your date of birth</DrawerDescription>\n </DrawerHeader>\n <Calendar\n mode=\"single\"\n selected={date}\n captionLayout=\"dropdown\"\n onSelect={(date) => {\n setDate(date)\n setOpen(false)\n }}\n className=\"mx-auto [--cell-size:clamp(0px,calc(100vw/7.5),52px)]\"\n />\n </DrawerContent>\n </Drawer>\n <div className=\"text-muted-foreground px-1 text-sm\">\n This example works best on mobile.\n </div>\n </div>\n )\n}\n",
"type": "registry:component",
"target": ""
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
"type": "registry:ui",
"author": "shadcn (https://ui.shadcn.com)",
"dependencies": [
"recharts",
"recharts@2.15.4",
"lucide-react"
],
"registryDependencies": [

View File

@@ -8,7 +8,7 @@
"files": [
{
"path": "registry/new-york-v4/examples/accordion-demo.tsx",
"content": "import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from \"@/registry/new-york-v4/ui/accordion\"\n\nexport default function AccordionDemo() {\n return (\n <Accordion type=\"single\" collapsible className=\"w-full\">\n <AccordionItem value=\"item-1\">\n <AccordionTrigger>Product Information</AccordionTrigger>\n <AccordionContent className=\"flex flex-col gap-4 text-balance\">\n <p>\n Our flagship product combines cutting-edge technology with sleek\n design. Built with premium materials, it offers unparalleled\n performance and reliability.\n </p>\n <p>\n Key features include advanced processing capabilities, and an\n intuitive user interface designed for both beginners and experts.\n </p>\n </AccordionContent>\n </AccordionItem>\n <AccordionItem value=\"item-2\">\n <AccordionTrigger>Shipping Details</AccordionTrigger>\n <AccordionContent className=\"flex flex-col gap-4 text-balance\">\n <p>\n We offer worldwide shipping through trusted courier partners.\n Standard delivery takes 3-5 business days, while express shipping\n ensures delivery within 1-2 business days.\n </p>\n <p>\n All orders are carefully packaged and fully insured. Track your\n shipment in real-time through our dedicated tracking portal.\n </p>\n </AccordionContent>\n </AccordionItem>\n <AccordionItem value=\"item-3\">\n <AccordionTrigger>Return Policy</AccordionTrigger>\n <AccordionContent className=\"flex flex-col gap-4 text-balance\">\n <p>\n We stand behind our products with a comprehensive 30-day return\n policy. If you&apos;re not completely satisfied, simply return the\n item in its original condition.\n </p>\n <p>\n Our hassle-free return process includes free return shipping and\n full refunds processed within 48 hours of receiving the returned\n item.\n </p>\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n )\n}\n",
"content": "import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from \"@/registry/new-york-v4/ui/accordion\"\n\nexport default function AccordionDemo() {\n return (\n <Accordion\n type=\"single\"\n collapsible\n className=\"w-full\"\n defaultValue=\"item-1\"\n >\n <AccordionItem value=\"item-1\">\n <AccordionTrigger>Product Information</AccordionTrigger>\n <AccordionContent className=\"flex flex-col gap-4 text-balance\">\n <p>\n Our flagship product combines cutting-edge technology with sleek\n design. Built with premium materials, it offers unparalleled\n performance and reliability.\n </p>\n <p>\n Key features include advanced processing capabilities, and an\n intuitive user interface designed for both beginners and experts.\n </p>\n </AccordionContent>\n </AccordionItem>\n <AccordionItem value=\"item-2\">\n <AccordionTrigger>Shipping Details</AccordionTrigger>\n <AccordionContent className=\"flex flex-col gap-4 text-balance\">\n <p>\n We offer worldwide shipping through trusted courier partners.\n Standard delivery takes 3-5 business days, while express shipping\n ensures delivery within 1-2 business days.\n </p>\n <p>\n All orders are carefully packaged and fully insured. Track your\n shipment in real-time through our dedicated tracking portal.\n </p>\n </AccordionContent>\n </AccordionItem>\n <AccordionItem value=\"item-3\">\n <AccordionTrigger>Return Policy</AccordionTrigger>\n <AccordionContent className=\"flex flex-col gap-4 text-balance\">\n <p>\n We stand behind our products with a comprehensive 30-day return\n policy. If you&apos;re not completely satisfied, simply return the\n item in its original condition.\n </p>\n <p>\n Our hassle-free return process includes free return shipping and\n full refunds processed within 48 hours of receiving the returned\n item.\n </p>\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n )\n}\n",
"type": "registry:example"
}
]

View File

@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-01",
"type": "registry:block",
"description": "A simple calendar.",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "registry/new-york-v4/blocks/calendar-01.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\n\nexport default function Calendar01() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <Calendar\n mode=\"single\"\n defaultMonth={date}\n selected={date}\n onSelect={setDate}\n className=\"rounded-lg border shadow-sm\"\n />\n )\n}\n",
"type": "registry:component"
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-4 py-12 items-start md:py-20 justify-center min-w-0",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-02",
"type": "registry:block",
"description": "Multiple months with single selection.",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "registry/new-york-v4/blocks/calendar-02.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\n\nexport default function Calendar02() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <Calendar\n mode=\"single\"\n defaultMonth={date}\n numberOfMonths={2}\n selected={date}\n onSelect={setDate}\n className=\"rounded-lg border shadow-sm\"\n />\n )\n}\n",
"type": "registry:component"
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-4 py-12 items-start md:py-20 justify-center min-w-0",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-03",
"type": "registry:block",
"description": "Multiple months with multiple selection.",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "registry/new-york-v4/blocks/calendar-03.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\n\nexport default function Calendar03() {\n const [dates, setDates] = React.useState<Date[]>([\n new Date(2025, 5, 12),\n new Date(2025, 6, 24),\n ])\n\n return (\n <Calendar\n mode=\"multiple\"\n numberOfMonths={2}\n defaultMonth={dates[0]}\n required\n selected={dates}\n onSelect={setDates}\n max={5}\n className=\"rounded-lg border shadow-sm\"\n />\n )\n}\n",
"type": "registry:component"
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-4 py-12 items-start md:py-20 justify-center min-w-0",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-04",
"type": "registry:block",
"description": "Single month with range selection",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "registry/new-york-v4/blocks/calendar-04.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DateRange } from \"react-day-picker\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\n\nexport default function Calendar04() {\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 9),\n to: new Date(2025, 5, 26),\n })\n\n return (\n <Calendar\n mode=\"range\"\n defaultMonth={dateRange?.from}\n selected={dateRange}\n onSelect={setDateRange}\n className=\"rounded-lg border shadow-sm\"\n />\n )\n}\n",
"type": "registry:component"
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-4 py-12 items-start md:py-20 justify-center min-w-0 xl:pt-28",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-05",
"type": "registry:block",
"description": "Multiple months with range selection",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "registry/new-york-v4/blocks/calendar-05.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DateRange } from \"react-day-picker\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\n\nexport default function Calendar05() {\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 12),\n to: new Date(2025, 6, 15),\n })\n\n return (\n <Calendar\n mode=\"range\"\n defaultMonth={dateRange?.from}\n selected={dateRange}\n onSelect={setDateRange}\n numberOfMonths={2}\n className=\"rounded-lg border shadow-sm\"\n />\n )\n}\n",
"type": "registry:component"
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-06",
"type": "registry:block",
"description": "Range selection with minimum days",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "registry/new-york-v4/blocks/calendar-06.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DateRange } from \"react-day-picker\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\n\nexport default function Calendar06() {\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 12),\n to: new Date(2025, 5, 26),\n })\n\n return (\n <div className=\"flex min-w-0 flex-col gap-2\">\n <Calendar\n mode=\"range\"\n defaultMonth={dateRange?.from}\n selected={dateRange}\n onSelect={setDateRange}\n numberOfMonths={1}\n min={5}\n className=\"rounded-lg border shadow-sm\"\n />\n <div className=\"text-muted-foreground text-center text-xs\">\n A minimum of 5 days is required\n </div>\n </div>\n )\n}\n",
"type": "registry:component"
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-07",
"type": "registry:block",
"description": "Range selection with minimum and maximum days",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "registry/new-york-v4/blocks/calendar-07.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DateRange } from \"react-day-picker\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\n\nexport default function Calendar07() {\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 18),\n to: new Date(2025, 6, 7),\n })\n\n return (\n <div className=\"flex min-w-0 flex-col gap-2\">\n <Calendar\n mode=\"range\"\n defaultMonth={dateRange?.from}\n selected={dateRange}\n onSelect={setDateRange}\n numberOfMonths={2}\n min={2}\n max={20}\n className=\"rounded-lg border shadow-sm\"\n />\n <div className=\"text-muted-foreground text-center text-xs\">\n Your stay must be between 2 and 20 nights\n </div>\n </div>\n )\n}\n",
"type": "registry:component"
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-08",
"type": "registry:block",
"description": "Calendar with disabled days",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "registry/new-york-v4/blocks/calendar-08.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\n\nexport default function Calendar08() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <Calendar\n mode=\"single\"\n defaultMonth={date}\n selected={date}\n onSelect={setDate}\n disabled={{\n before: new Date(2025, 5, 12),\n }}\n className=\"rounded-lg border shadow-sm\"\n />\n )\n}\n",
"type": "registry:component"
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-09",
"type": "registry:block",
"description": "Calendar with disabled weekends",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "registry/new-york-v4/blocks/calendar-09.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DateRange } from \"react-day-picker\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\n\nexport default function Calendar09() {\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 17),\n to: new Date(2025, 5, 20),\n })\n\n return (\n <Calendar\n mode=\"range\"\n defaultMonth={dateRange?.from}\n selected={dateRange}\n onSelect={setDateRange}\n numberOfMonths={2}\n disabled={{ dayOfWeek: [0, 6] }}\n className=\"rounded-lg border shadow-sm\"\n excludeDisabled\n />\n )\n}\n",
"type": "registry:component"
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-10",
"type": "registry:block",
"description": "Today button",
"registryDependencies": [
"calendar",
"card",
"button"
],
"files": [
{
"path": "registry/new-york-v4/blocks/calendar-10.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\nimport {\n Card,\n CardAction,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\n\nexport default function Calendar10() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n const [month, setMonth] = React.useState<Date | undefined>(new Date())\n\n return (\n <Card>\n <CardHeader>\n <CardTitle>Appointment</CardTitle>\n <CardDescription>Find a date</CardDescription>\n <CardAction>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => {\n setMonth(new Date())\n setDate(new Date())\n }}\n >\n Today\n </Button>\n </CardAction>\n </CardHeader>\n <CardContent>\n <Calendar\n mode=\"single\"\n month={month}\n onMonthChange={setMonth}\n selected={date}\n onSelect={setDate}\n className=\"bg-transparent p-0\"\n />\n </CardContent>\n </Card>\n )\n}\n",
"type": "registry:component"
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-11",
"type": "registry:block",
"description": "Start and end of month",
"registryDependencies": [
"calendar"
],
"files": [
{
"path": "registry/new-york-v4/blocks/calendar-11.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DateRange } from \"react-day-picker\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\n\nexport default function Calendar11() {\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 17),\n to: new Date(2025, 5, 20),\n })\n\n return (\n <div className=\"flex min-w-0 flex-col gap-2\">\n <Calendar\n mode=\"range\"\n selected={dateRange}\n onSelect={setDateRange}\n numberOfMonths={2}\n startMonth={new Date(2025, 5, 1)}\n endMonth={new Date(2025, 6, 31)}\n disableNavigation\n className=\"rounded-lg border shadow-sm\"\n />\n <div className=\"text-muted-foreground text-center text-xs\">\n We are open in June and July only.\n </div>\n </div>\n )\n}\n",
"type": "registry:component"
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-12",
"type": "registry:block",
"description": "Localized calendar",
"registryDependencies": [
"calendar",
"card",
"select"
],
"files": [
{
"path": "registry/new-york-v4/blocks/calendar-12.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type DateRange } from \"react-day-picker\"\nimport { enUS, es } from \"react-day-picker/locale\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\nimport {\n Card,\n CardAction,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/new-york-v4/ui/select\"\n\nconst localizedStrings = {\n en: {\n title: \"Book an appointment\",\n description: \"Select the dates for your appointment\",\n },\n es: {\n title: \"Reserva una cita\",\n description: \"Selecciona las fechas para tu cita\",\n },\n} as const\n\nexport default function Calendar12() {\n const [locale, setLocale] =\n React.useState<keyof typeof localizedStrings>(\"es\")\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 8, 9),\n to: new Date(2025, 8, 17),\n })\n\n return (\n <Card>\n <CardHeader className=\"border-b\">\n <CardTitle>{localizedStrings[locale].title}</CardTitle>\n <CardDescription>\n {localizedStrings[locale].description}\n </CardDescription>\n <CardAction>\n <Select\n value={locale}\n onValueChange={(value) =>\n setLocale(value as keyof typeof localizedStrings)\n }\n >\n <SelectTrigger className=\"w-[100px]\">\n <SelectValue placeholder=\"Language\" />\n </SelectTrigger>\n <SelectContent align=\"end\">\n <SelectItem value=\"es\">Español</SelectItem>\n <SelectItem value=\"en\">English</SelectItem>\n </SelectContent>\n </Select>\n </CardAction>\n </CardHeader>\n <CardContent>\n <Calendar\n mode=\"range\"\n selected={dateRange}\n onSelect={setDateRange}\n defaultMonth={dateRange?.from}\n numberOfMonths={2}\n locale={locale === \"es\" ? es : enUS}\n className=\"bg-transparent p-0\"\n buttonVariant=\"outline\"\n />\n </CardContent>\n </Card>\n )\n}\n",
"type": "registry:component"
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "calendar-13",
"type": "registry:block",
"description": "With Month and Year Dropdown",
"registryDependencies": [
"calendar",
"label",
"select"
],
"files": [
{
"path": "registry/new-york-v4/blocks/calendar-13.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\nimport { Label } from \"@/registry/new-york-v4/ui/label\"\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/new-york-v4/ui/select\"\n\nexport default function Calendar13() {\n const [dropdown, setDropdown] =\n React.useState<React.ComponentProps<typeof Calendar>[\"captionLayout\"]>(\n \"dropdown\"\n )\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <div className=\"flex flex-col gap-4\">\n <Calendar\n mode=\"single\"\n defaultMonth={date}\n selected={date}\n onSelect={setDate}\n captionLayout={dropdown}\n className=\"rounded-lg border shadow-sm\"\n />\n <div className=\"flex flex-col gap-3\">\n <Label htmlFor=\"dropdown\" className=\"px-1\">\n Dropdown\n </Label>\n <Select\n value={dropdown}\n onValueChange={(value) =>\n setDropdown(\n value as React.ComponentProps<typeof Calendar>[\"captionLayout\"]\n )\n }\n >\n <SelectTrigger\n id=\"dropdown\"\n size=\"sm\"\n className=\"bg-background w-full\"\n >\n <SelectValue placeholder=\"Dropdown\" />\n </SelectTrigger>\n <SelectContent align=\"center\">\n <SelectItem value=\"dropdown\">Month and Year</SelectItem>\n <SelectItem value=\"dropdown-months\">Month Only</SelectItem>\n <SelectItem value=\"dropdown-years\">Year Only</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>\n )\n}\n",
"type": "registry:component"
}
],
"meta": {
"iframeHeight": "600px",
"container": "w-full bg-surface min-h-svh flex px-6 py-12 items-start md:pt-20 justify-center min-w-0 xl:py-24",
"mobile": "component"
},
"categories": [
"calendar",
"date"
]
}

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