mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-14 03:11:33 +00:00
Compare commits
50 Commits
shadcn@2.6
...
shadcn@2.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e75e7b3866 | ||
|
|
ed5237c231 | ||
|
|
f85ca066dc | ||
|
|
54e66d4450 | ||
|
|
6c341c16ae | ||
|
|
06d03d64f4 | ||
|
|
6407a3b330 | ||
|
|
96b15f6090 | ||
|
|
2fe9cf6d26 | ||
|
|
728cb4cfa5 | ||
|
|
db93787712 | ||
|
|
1cdd6c1645 | ||
|
|
4983c6e1f4 | ||
|
|
7443edcfb0 | ||
|
|
9d9a33be52 | ||
|
|
d544a7f7a5 | ||
|
|
48fe0d709f | ||
|
|
ed244ea0b5 | ||
|
|
b8fede1742 | ||
|
|
84d6c83bad | ||
|
|
5b8ee41511 | ||
|
|
7c3d34cdc9 | ||
|
|
56c4c83511 | ||
|
|
2821cb0e39 | ||
|
|
3c87402de2 | ||
|
|
20a88e1f15 | ||
|
|
cb19ab8464 | ||
|
|
cf1851ca09 | ||
|
|
c86c27a2ff | ||
|
|
8847126c65 | ||
|
|
65350857a4 | ||
|
|
40c7473c7e | ||
|
|
4698ee960f | ||
|
|
2ae0e5a07b | ||
|
|
431af4f7ff | ||
|
|
c1357982e8 | ||
|
|
92cfb9a30e | ||
|
|
c5d90c718a | ||
|
|
b1fd13ffb0 | ||
|
|
3119f94d47 | ||
|
|
057d97dd25 | ||
|
|
a659e09353 | ||
|
|
82d94eee38 | ||
|
|
9cbc6641d9 | ||
|
|
e0bec146fa | ||
|
|
65223896da | ||
|
|
16ee16b053 | ||
|
|
b5cf967848 | ||
|
|
12b7833d70 | ||
|
|
ec73150490 |
20
.github/workflows/issue-stale.yml
vendored
20
.github/workflows/issue-stale.yml
vendored
@@ -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 there’s 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 you’re 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 there’s 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 you’re 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 there’s 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 there’s 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
|
||||
|
||||
@@ -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
2
apps/v4/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
NEXT_PUBLIC_V0_URL=https://v0.dev
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:4000
|
||||
1
apps/v4/.gitignore
vendored
1
apps/v4/.gitignore
vendored
@@ -32,6 +32,7 @@ yarn-error.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
@@ -4,3 +4,4 @@ node_modules
|
||||
build
|
||||
.contentlayer
|
||||
registry/__index__.tsx
|
||||
content/docs/components/calendar.mdx
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
64
apps/v4/app/(app)/themes/layout.tsx
Normal file
64
apps/v4/app/(app)/themes/layout.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
22
apps/v4/app/(app)/themes/page.tsx
Normal file
22
apps/v4/app/(app)/themes/page.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
33
apps/v4/components/site-config.tsx
Normal file
33
apps/v4/components/site-config.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
569
apps/v4/components/theme-customizer.tsx
Normal file
569
apps/v4/components/theme-customizer.tsx
Normal 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">
|
||||
:root {
|
||||
</span>
|
||||
<span data-line className="line text-code-foreground">
|
||||
--radius: 0.65rem;
|
||||
</span>
|
||||
{Object.entries(activeThemeOKLCH?.light).map(([key, value]) => (
|
||||
<span
|
||||
data-line
|
||||
className="line text-code-foreground"
|
||||
key={key}
|
||||
>
|
||||
--{key}: {value};
|
||||
</span>
|
||||
))}
|
||||
<span data-line className="line text-code-foreground">
|
||||
}
|
||||
</span>
|
||||
<span data-line className="line text-code-foreground">
|
||||
|
||||
</span>
|
||||
<span data-line className="line text-code-foreground">
|
||||
.dark {
|
||||
</span>
|
||||
{Object.entries(activeThemeOKLCH?.dark).map(([key, value]) => (
|
||||
<span
|
||||
data-line
|
||||
className="line text-code-foreground"
|
||||
key={key}
|
||||
>
|
||||
--{key}: {value};
|
||||
</span>
|
||||
))}
|
||||
<span data-line className="line text-code-foreground">
|
||||
}
|
||||
</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 {
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
:root {
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--background:{" "}
|
||||
{activeTheme?.cssVars.light["background"]};
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--foreground:{" "}
|
||||
{activeTheme?.cssVars.light["foreground"]};
|
||||
</span>
|
||||
{[
|
||||
"card",
|
||||
"popover",
|
||||
"primary",
|
||||
"secondary",
|
||||
"muted",
|
||||
"accent",
|
||||
"destructive",
|
||||
].map((prefix) => (
|
||||
<React.Fragment key={prefix}>
|
||||
<span data-line className="line">
|
||||
--{prefix}:{" "}
|
||||
{
|
||||
activeTheme?.cssVars.light[
|
||||
prefix as keyof typeof activeTheme.cssVars.light
|
||||
]
|
||||
}
|
||||
;
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--{prefix}-foreground:{" "}
|
||||
{
|
||||
activeTheme?.cssVars.light[
|
||||
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.light
|
||||
]
|
||||
}
|
||||
;
|
||||
</span>
|
||||
</React.Fragment>
|
||||
))}
|
||||
<span data-line className="line">
|
||||
--border:{" "}
|
||||
{activeTheme?.cssVars.light["border"]};
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--input:{" "}
|
||||
{activeTheme?.cssVars.light["input"]};
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--ring:{" "}
|
||||
{activeTheme?.cssVars.light["ring"]};
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--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">
|
||||
--{prefix}:{" "}
|
||||
{
|
||||
activeTheme?.cssVars.light[
|
||||
prefix as keyof typeof activeTheme.cssVars.light
|
||||
]
|
||||
}
|
||||
;
|
||||
</span>
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
<span data-line className="line">
|
||||
}
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
.dark {
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--background:{" "}
|
||||
{activeTheme?.cssVars.dark["background"]};
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--foreground:{" "}
|
||||
{activeTheme?.cssVars.dark["foreground"]};
|
||||
</span>
|
||||
{[
|
||||
"card",
|
||||
"popover",
|
||||
"primary",
|
||||
"secondary",
|
||||
"muted",
|
||||
"accent",
|
||||
"destructive",
|
||||
].map((prefix) => (
|
||||
<React.Fragment key={prefix}>
|
||||
<span data-line className="line">
|
||||
--{prefix}:{" "}
|
||||
{
|
||||
activeTheme?.cssVars.dark[
|
||||
prefix as keyof typeof activeTheme.cssVars.dark
|
||||
]
|
||||
}
|
||||
;
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--{prefix}-foreground:{" "}
|
||||
{
|
||||
activeTheme?.cssVars.dark[
|
||||
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.dark
|
||||
]
|
||||
}
|
||||
;
|
||||
</span>
|
||||
</React.Fragment>
|
||||
))}
|
||||
<span data-line className="line">
|
||||
--border:{" "}
|
||||
{activeTheme?.cssVars.dark["border"]};
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--input:{" "}
|
||||
{activeTheme?.cssVars.dark["input"]};
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
--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">
|
||||
--{prefix}:{" "}
|
||||
{
|
||||
activeTheme?.cssVars.dark[
|
||||
prefix as keyof typeof activeTheme.cssVars.dark
|
||||
]
|
||||
}
|
||||
;
|
||||
</span>
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
<span data-line className="line">
|
||||
}
|
||||
</span>
|
||||
<span data-line className="line">
|
||||
}
|
||||
</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"] %>;
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -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 we’ll 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. It’s 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.
|
||||
- We’ve 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, we’ll 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.
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": "..."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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)",
|
||||
|
||||
160
apps/v4/hooks/use-layout.tsx
Normal file
160
apps/v4/hooks/use-layout.tsx
Normal 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 }
|
||||
17
apps/v4/hooks/use-mobile.ts
Normal file
17
apps/v4/hooks/use-mobile.ts
Normal 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
|
||||
}
|
||||
@@ -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-")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ export const siteConfig = {
|
||||
href: "/charts/area",
|
||||
label: "Charts",
|
||||
},
|
||||
{
|
||||
href: "/themes",
|
||||
label: "Themes",
|
||||
},
|
||||
{
|
||||
href: "/colors",
|
||||
label: "Colors",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -63,6 +63,11 @@ const nextConfig = {
|
||||
destination: "/charts/area",
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: "/view/styles/:style/:name",
|
||||
destination: "/view/:name",
|
||||
permanent: true,
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
BIN
apps/v4/public/images/calendar-2.png
Normal file
BIN
apps/v4/public/images/calendar-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 229 KiB |
@@ -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": [
|
||||
|
||||
27
apps/v4/public/r/styles/default/calendar-01.json
Normal file
27
apps/v4/public/r/styles/default/calendar-01.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/default/calendar-02.json
Normal file
27
apps/v4/public/r/styles/default/calendar-02.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/default/calendar-03.json
Normal file
27
apps/v4/public/r/styles/default/calendar-03.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/default/calendar-04.json
Normal file
27
apps/v4/public/r/styles/default/calendar-04.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/default/calendar-05.json
Normal file
27
apps/v4/public/r/styles/default/calendar-05.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/default/calendar-06.json
Normal file
27
apps/v4/public/r/styles/default/calendar-06.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/default/calendar-07.json
Normal file
27
apps/v4/public/r/styles/default/calendar-07.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/default/calendar-08.json
Normal file
27
apps/v4/public/r/styles/default/calendar-08.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/default/calendar-09.json
Normal file
27
apps/v4/public/r/styles/default/calendar-09.json
Normal 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"
|
||||
]
|
||||
}
|
||||
29
apps/v4/public/r/styles/default/calendar-10.json
Normal file
29
apps/v4/public/r/styles/default/calendar-10.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/default/calendar-11.json
Normal file
27
apps/v4/public/r/styles/default/calendar-11.json
Normal 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"
|
||||
]
|
||||
}
|
||||
29
apps/v4/public/r/styles/default/calendar-12.json
Normal file
29
apps/v4/public/r/styles/default/calendar-12.json
Normal 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"
|
||||
]
|
||||
}
|
||||
29
apps/v4/public/r/styles/default/calendar-13.json
Normal file
29
apps/v4/public/r/styles/default/calendar-13.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/default/calendar-14.json
Normal file
27
apps/v4/public/r/styles/default/calendar-14.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/default/calendar-15.json
Normal file
27
apps/v4/public/r/styles/default/calendar-15.json
Normal 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"
|
||||
]
|
||||
}
|
||||
30
apps/v4/public/r/styles/default/calendar-16.json
Normal file
30
apps/v4/public/r/styles/default/calendar-16.json
Normal 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"
|
||||
]
|
||||
}
|
||||
30
apps/v4/public/r/styles/default/calendar-17.json
Normal file
30
apps/v4/public/r/styles/default/calendar-17.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/default/calendar-18.json
Normal file
27
apps/v4/public/r/styles/default/calendar-18.json
Normal 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"
|
||||
]
|
||||
}
|
||||
33
apps/v4/public/r/styles/default/calendar-19.json
Normal file
33
apps/v4/public/r/styles/default/calendar-19.json
Normal 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"
|
||||
]
|
||||
}
|
||||
29
apps/v4/public/r/styles/default/calendar-20.json
Normal file
29
apps/v4/public/r/styles/default/calendar-20.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/default/calendar-21.json
Normal file
27
apps/v4/public/r/styles/default/calendar-21.json
Normal 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"
|
||||
]
|
||||
}
|
||||
30
apps/v4/public/r/styles/default/calendar-22.json
Normal file
30
apps/v4/public/r/styles/default/calendar-22.json
Normal 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"
|
||||
]
|
||||
}
|
||||
30
apps/v4/public/r/styles/default/calendar-23.json
Normal file
30
apps/v4/public/r/styles/default/calendar-23.json
Normal 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"
|
||||
]
|
||||
}
|
||||
30
apps/v4/public/r/styles/default/calendar-24.json
Normal file
30
apps/v4/public/r/styles/default/calendar-24.json
Normal 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"
|
||||
]
|
||||
}
|
||||
30
apps/v4/public/r/styles/default/calendar-25.json
Normal file
30
apps/v4/public/r/styles/default/calendar-25.json
Normal 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"
|
||||
]
|
||||
}
|
||||
31
apps/v4/public/r/styles/default/calendar-26.json
Normal file
31
apps/v4/public/r/styles/default/calendar-26.json
Normal 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"
|
||||
]
|
||||
}
|
||||
31
apps/v4/public/r/styles/default/calendar-27.json
Normal file
31
apps/v4/public/r/styles/default/calendar-27.json
Normal file
File diff suppressed because one or more lines are too long
31
apps/v4/public/r/styles/default/calendar-28.json
Normal file
31
apps/v4/public/r/styles/default/calendar-28.json
Normal 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"
|
||||
]
|
||||
}
|
||||
34
apps/v4/public/r/styles/default/calendar-29.json
Normal file
34
apps/v4/public/r/styles/default/calendar-29.json
Normal 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"
|
||||
]
|
||||
}
|
||||
31
apps/v4/public/r/styles/default/calendar-30.json
Normal file
31
apps/v4/public/r/styles/default/calendar-30.json
Normal 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"
|
||||
]
|
||||
}
|
||||
29
apps/v4/public/r/styles/default/calendar-31.json
Normal file
29
apps/v4/public/r/styles/default/calendar-31.json
Normal 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"
|
||||
]
|
||||
}
|
||||
29
apps/v4/public/r/styles/default/calendar-32.json
Normal file
29
apps/v4/public/r/styles/default/calendar-32.json
Normal 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
@@ -4,7 +4,7 @@
|
||||
"type": "registry:ui",
|
||||
"author": "shadcn (https://ui.shadcn.com)",
|
||||
"dependencies": [
|
||||
"recharts",
|
||||
"recharts@2.15.4",
|
||||
"lucide-react"
|
||||
],
|
||||
"registryDependencies": [
|
||||
|
||||
@@ -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'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'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"
|
||||
}
|
||||
]
|
||||
|
||||
25
apps/v4/public/r/styles/new-york-v4/calendar-01.json
Normal file
25
apps/v4/public/r/styles/new-york-v4/calendar-01.json
Normal 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"
|
||||
]
|
||||
}
|
||||
25
apps/v4/public/r/styles/new-york-v4/calendar-02.json
Normal file
25
apps/v4/public/r/styles/new-york-v4/calendar-02.json
Normal 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"
|
||||
]
|
||||
}
|
||||
25
apps/v4/public/r/styles/new-york-v4/calendar-03.json
Normal file
25
apps/v4/public/r/styles/new-york-v4/calendar-03.json
Normal 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"
|
||||
]
|
||||
}
|
||||
25
apps/v4/public/r/styles/new-york-v4/calendar-04.json
Normal file
25
apps/v4/public/r/styles/new-york-v4/calendar-04.json
Normal 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"
|
||||
]
|
||||
}
|
||||
25
apps/v4/public/r/styles/new-york-v4/calendar-05.json
Normal file
25
apps/v4/public/r/styles/new-york-v4/calendar-05.json
Normal 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"
|
||||
]
|
||||
}
|
||||
25
apps/v4/public/r/styles/new-york-v4/calendar-06.json
Normal file
25
apps/v4/public/r/styles/new-york-v4/calendar-06.json
Normal 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"
|
||||
]
|
||||
}
|
||||
25
apps/v4/public/r/styles/new-york-v4/calendar-07.json
Normal file
25
apps/v4/public/r/styles/new-york-v4/calendar-07.json
Normal 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"
|
||||
]
|
||||
}
|
||||
25
apps/v4/public/r/styles/new-york-v4/calendar-08.json
Normal file
25
apps/v4/public/r/styles/new-york-v4/calendar-08.json
Normal 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"
|
||||
]
|
||||
}
|
||||
25
apps/v4/public/r/styles/new-york-v4/calendar-09.json
Normal file
25
apps/v4/public/r/styles/new-york-v4/calendar-09.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/new-york-v4/calendar-10.json
Normal file
27
apps/v4/public/r/styles/new-york-v4/calendar-10.json
Normal 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"
|
||||
]
|
||||
}
|
||||
25
apps/v4/public/r/styles/new-york-v4/calendar-11.json
Normal file
25
apps/v4/public/r/styles/new-york-v4/calendar-11.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/new-york-v4/calendar-12.json
Normal file
27
apps/v4/public/r/styles/new-york-v4/calendar-12.json
Normal 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"
|
||||
]
|
||||
}
|
||||
27
apps/v4/public/r/styles/new-york-v4/calendar-13.json
Normal file
27
apps/v4/public/r/styles/new-york-v4/calendar-13.json
Normal 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
Reference in New Issue
Block a user