mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-22 20:25:44 +00:00
Compare commits
31 Commits
shadcn@2.6
...
shadcn@2.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 }}
|
repo-token: ${{ secrets.STALE_TOKEN }}
|
||||||
ascending: true
|
ascending: true
|
||||||
days-before-issue-close: 7
|
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-stale: -1
|
||||||
days-before-pr-close: -1
|
days-before-pr-close: -1
|
||||||
remove-issue-stale-when-updated: true
|
remove-issue-stale-when-updated: true
|
||||||
stale-issue-label: "stale?"
|
stale-issue-label: "stale?"
|
||||||
exempt-issue-labels: "roadmap,next,bug"
|
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."
|
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!"
|
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 # 1 operation per 100 issues, the rest is to label/comment/close
|
operations-per-run: 300
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v9
|
||||||
id: pr-state
|
id: pr-state
|
||||||
name: "Mark stale PRs, close stale PRs"
|
name: "Mark stale PRs, close stale PRs"
|
||||||
@@ -36,10 +36,10 @@ jobs:
|
|||||||
days-before-issue-close: -1
|
days-before-issue-close: -1
|
||||||
days-before-issue-stale: -1
|
days-before-issue-stale: -1
|
||||||
days-before-pr-close: 7
|
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
|
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-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."
|
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!"
|
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 # 1 operation per 100 issues, the rest is to label/comment/close
|
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:
|
1. Start by running the registry (main site) to make sure the components are up to date:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm www:dev
|
pnpm v4:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Run the development script for the CLI:
|
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 files (can opt-in for committing if needed)
|
||||||
.env*
|
.env*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ node_modules
|
|||||||
build
|
build
|
||||||
.contentlayer
|
.contentlayer
|
||||||
registry/__index__.tsx
|
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
|
{type.charAt(0).toUpperCase() + type.slice(1)} Charts
|
||||||
</h2>
|
</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">
|
<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) => (
|
{Array.from({ length: 12 }).map((_, index) => {
|
||||||
<ChartDisplay
|
const chart = chartList[index]
|
||||||
key={chart.id}
|
return chart ? (
|
||||||
name={chart.id}
|
<ChartDisplay
|
||||||
className={cn(chart.fullWidth && "md:col-span-2 lg:col-span-3")}
|
key={chart.id}
|
||||||
>
|
name={chart.id}
|
||||||
<chart.component />
|
className={cn(chart.fullWidth && "md:col-span-2 lg:col-span-3")}
|
||||||
</ChartDisplay>
|
>
|
||||||
))}
|
<chart.component />
|
||||||
|
</ChartDisplay>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
key={`empty-${index}`}
|
||||||
|
className="hidden aspect-square w-full rounded-lg border border-dashed xl:block"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export default function ChartsLayout({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<PageHeader>
|
<PageHeader>
|
||||||
<Announcement />
|
<Announcement />
|
||||||
<PageHeaderHeading>{title}</PageHeaderHeading>
|
<PageHeaderHeading>{title}</PageHeaderHeading>
|
||||||
@@ -65,11 +65,11 @@ export default function ChartsLayout({
|
|||||||
<ChartsNav />
|
<ChartsNav />
|
||||||
<ThemeSelector className="mr-4 hidden md:flex" />
|
<ThemeSelector className="mr-4 hidden md:flex" />
|
||||||
</PageNav>
|
</PageNav>
|
||||||
<div className="container-wrapper section-soft">
|
<div className="container-wrapper section-soft flex-1">
|
||||||
<div className="container pb-6">
|
<div className="container pb-6">
|
||||||
<section className="theme-container">{children}</section>
|
<section className="theme-container">{children}</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { notFound } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
import { mdxComponents } from "@/mdx-components"
|
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 { findNeighbour } from "fumadocs-core/server"
|
||||||
|
|
||||||
import { source } from "@/lib/source"
|
import { source } from "@/lib/source"
|
||||||
import { absoluteUrl } from "@/lib/utils"
|
import { absoluteUrl } from "@/lib/utils"
|
||||||
import { DocsTableOfContents } from "@/components/docs-toc"
|
import { DocsTableOfContents } from "@/components/docs-toc"
|
||||||
import { OpenInV0Cta } from "@/components/open-in-v0-cta"
|
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"
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
|
||||||
export const revalidate = false
|
export const revalidate = false
|
||||||
@@ -80,6 +85,9 @@ export default async function Page(props: {
|
|||||||
const MDX = doc.body
|
const MDX = doc.body
|
||||||
const neighbours = await findNeighbour(source.pageTree, page.url)
|
const neighbours = await findNeighbour(source.pageTree, page.url)
|
||||||
|
|
||||||
|
// @ts-expect-error - revisit fumadocs types.
|
||||||
|
const links = doc.links
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="docs"
|
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="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 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">
|
<h1 className="scroll-m-20 text-4xl font-semibold tracking-tight sm:text-3xl xl:text-4xl">
|
||||||
{doc.title}
|
{doc.title}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 pt-1.5">
|
||||||
{neighbours.previous && (
|
{neighbours.previous && (
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -129,6 +137,24 @@ export default async function Page(props: {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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>
|
||||||
<div className="w-full flex-1 *:data-[slot=alert]:first:mt-0">
|
<div className="w-full flex-1 *:data-[slot=alert]:first:mt-0">
|
||||||
<MDX components={mdxComponents} />
|
<MDX components={mdxComponents} />
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ export default function DocsLayout({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="container-wrapper flex flex-1 flex-col">
|
<div className="container-wrapper flex flex-1 flex-col px-2">
|
||||||
<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)]">
|
<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} />
|
<DocsSidebar tree={source.pageTree} />
|
||||||
<div className="h-full w-full">{children}</div>
|
<div className="h-full w-full">{children}</div>
|
||||||
</SidebarProvider>
|
</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 * as React from "react"
|
||||||
import { addDays } from "date-fns"
|
import { addDays } from "date-fns"
|
||||||
|
import { Clock2Icon } from "lucide-react"
|
||||||
import { type DateRange } from "react-day-picker"
|
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() {
|
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 (
|
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
|
<Calendar
|
||||||
mode="single"
|
mode="single"
|
||||||
selected={date}
|
selected={date}
|
||||||
onSelect={setDate}
|
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
|
<Calendar
|
||||||
mode="range"
|
mode="range"
|
||||||
defaultMonth={dateRange?.from}
|
defaultMonth={dateRange?.from}
|
||||||
@@ -32,16 +70,207 @@ export function CalendarDemo() {
|
|||||||
onSelect={setDateRange}
|
onSelect={setDateRange}
|
||||||
numberOfMonths={2}
|
numberOfMonths={2}
|
||||||
disabled={(date) => date > new Date() || date < new Date("1900-01-01")}
|
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
|
<Calendar
|
||||||
mode="range"
|
mode="range"
|
||||||
defaultMonth={range?.from}
|
defaultMonth={range?.from}
|
||||||
selected={range}
|
selected={range}
|
||||||
onSelect={setRange}
|
onSelect={setRange}
|
||||||
numberOfMonths={3}
|
numberOfMonths={3}
|
||||||
className="hidden rounded-md border shadow-sm @4xl:flex [&>div]:gap-5"
|
locale={es}
|
||||||
|
fixedWeeks
|
||||||
|
className="rounded-lg border shadow-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 { DateRange } from "react-day-picker"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import { useIsMobile } from "@/hooks/use-mobile"
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
|
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
|
||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerDescription,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerTitle,
|
||||||
|
DrawerTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/drawer"
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
@@ -18,6 +27,7 @@ export function DatePickerDemo() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-start gap-4 md:flex-row">
|
<div className="flex flex-col items-start gap-4 md:flex-row">
|
||||||
<DatePickerSimple />
|
<DatePickerSimple />
|
||||||
|
<DataPickerWithDropdowns />
|
||||||
<DatePickerWithRange />
|
<DatePickerWithRange />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -41,12 +51,7 @@ function DatePickerSimple() {
|
|||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
<Calendar
|
<Calendar mode="single" selected={date} onSelect={setDate} />
|
||||||
mode="single"
|
|
||||||
selected={date}
|
|
||||||
onSelect={setDate}
|
|
||||||
initialFocus
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
)
|
)
|
||||||
@@ -86,7 +91,6 @@ function DatePickerWithRange() {
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
<Calendar
|
<Calendar
|
||||||
initialFocus
|
|
||||||
mode="range"
|
mode="range"
|
||||||
defaultMonth={date?.from}
|
defaultMonth={date?.from}
|
||||||
selected={date}
|
selected={date}
|
||||||
@@ -97,3 +101,79 @@ function DatePickerWithRange() {
|
|||||||
</Popover>
|
</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
|
const description = item.description
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: `${item.name}${item.description ? ` - ${item.description}` : ""}`,
|
title: item.description,
|
||||||
description,
|
description,
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title,
|
title,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { Metadata } from "next"
|
import type { Metadata } from "next"
|
||||||
import { cookies } from "next/headers"
|
|
||||||
|
|
||||||
import { META_THEME_COLORS, siteConfig } from "@/lib/config"
|
import { META_THEME_COLORS, siteConfig } from "@/lib/config"
|
||||||
import { fontVariables } from "@/lib/fonts"
|
import { fontVariables } from "@/lib/fonts"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import { LayoutProvider } from "@/hooks/use-layout"
|
||||||
import { ActiveThemeProvider } from "@/components/active-theme"
|
import { ActiveThemeProvider } from "@/components/active-theme"
|
||||||
import { Analytics } from "@/components/analytics"
|
import { Analytics } from "@/components/analytics"
|
||||||
import { TailwindIndicator } from "@/components/tailwind-indicator"
|
import { TailwindIndicator } from "@/components/tailwind-indicator"
|
||||||
@@ -58,14 +58,11 @@ export const metadata: Metadata = {
|
|||||||
manifest: `${siteConfig.url}/site.webmanifest`,
|
manifest: `${siteConfig.url}/site.webmanifest`,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}>) {
|
}>) {
|
||||||
const cookieStore = await cookies()
|
|
||||||
const activeThemeValue = cookieStore.get("active_theme")?.value
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<head>
|
<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)) {
|
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}')
|
document.querySelector('meta[name="theme-color"]').setAttribute('content', '${META_THEME_COLORS.dark}')
|
||||||
}
|
}
|
||||||
|
if (localStorage.layout) {
|
||||||
|
document.documentElement.classList.add('layout-' + localStorage.layout)
|
||||||
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
`,
|
`,
|
||||||
}}
|
}}
|
||||||
@@ -84,18 +84,19 @@ export default async function RootLayout({
|
|||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-foreground group/body overscroll-none font-sans antialiased [--footer-height:calc(var(--spacing)*14)] [--header-height:calc(var(--spacing)*14)]",
|
"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)]",
|
||||||
activeThemeValue ? `theme-${activeThemeValue}` : "",
|
|
||||||
fontVariables
|
fontVariables
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<ActiveThemeProvider initialTheme={activeThemeValue}>
|
<LayoutProvider>
|
||||||
{children}
|
<ActiveThemeProvider>
|
||||||
<TailwindIndicator />
|
{children}
|
||||||
<Toaster position="top-center" />
|
<TailwindIndicator />
|
||||||
<Analytics />
|
<Toaster position="top-center" />
|
||||||
</ActiveThemeProvider>
|
<Analytics />
|
||||||
|
</ActiveThemeProvider>
|
||||||
|
</LayoutProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -8,15 +8,8 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from "react"
|
} from "react"
|
||||||
|
|
||||||
const COOKIE_NAME = "active_theme"
|
|
||||||
const DEFAULT_THEME = "default"
|
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 = {
|
type ThemeContextType = {
|
||||||
activeTheme: string
|
activeTheme: string
|
||||||
setActiveTheme: (theme: string) => void
|
setActiveTheme: (theme: string) => void
|
||||||
@@ -36,8 +29,6 @@ export function ActiveThemeProvider({
|
|||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setThemeCookie(activeTheme)
|
|
||||||
|
|
||||||
Array.from(document.body.classList)
|
Array.from(document.body.classList)
|
||||||
.filter((className) => className.startsWith("theme-"))
|
.filter((className) => className.startsWith("theme-"))
|
||||||
.forEach((className) => {
|
.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() {
|
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,
|
createFileTreeForRegistryItemFiles,
|
||||||
getRegistryItem,
|
getRegistryItem,
|
||||||
} from "@/lib/registry"
|
} from "@/lib/registry"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
import { BlockViewer } from "@/components/block-viewer"
|
import { BlockViewer } from "@/components/block-viewer"
|
||||||
|
import { ComponentPreview } from "@/components/component-preview"
|
||||||
|
|
||||||
export async function BlockDisplay({ name }: { name: string }) {
|
export async function BlockDisplay({ name }: { name: string }) {
|
||||||
const item = await getCachedRegistryItem(name)
|
const item = await getCachedRegistryItem(name)
|
||||||
@@ -22,7 +24,16 @@ export async function BlockDisplay({ name }: { name: string }) {
|
|||||||
])
|
])
|
||||||
|
|
||||||
return (
|
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,
|
Folder,
|
||||||
Fullscreen,
|
Fullscreen,
|
||||||
Monitor,
|
Monitor,
|
||||||
|
RotateCw,
|
||||||
Smartphone,
|
Smartphone,
|
||||||
Tablet,
|
Tablet,
|
||||||
Terminal,
|
Terminal,
|
||||||
@@ -21,6 +22,7 @@ import { z } from "zod"
|
|||||||
|
|
||||||
import { trackEvent } from "@/lib/events"
|
import { trackEvent } from "@/lib/events"
|
||||||
import { createFileTreeForRegistryItemFiles, FileTree } from "@/lib/registry"
|
import { createFileTreeForRegistryItemFiles, FileTree } from "@/lib/registry"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
||||||
import { getIconForLanguageExtension } from "@/components/icons"
|
import { getIconForLanguageExtension } from "@/components/icons"
|
||||||
import { OpenInV0Button } from "@/components/open-in-v0-button"
|
import { OpenInV0Button } from "@/components/open-in-v0-button"
|
||||||
@@ -66,6 +68,8 @@ type BlockViewerContext = {
|
|||||||
highlightedContent: string
|
highlightedContent: string
|
||||||
})[]
|
})[]
|
||||||
| null
|
| null
|
||||||
|
iframeKey?: number
|
||||||
|
setIframeKey?: React.Dispatch<React.SetStateAction<number>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const BlockViewerContext = React.createContext<BlockViewerContext | null>(null)
|
const BlockViewerContext = React.createContext<BlockViewerContext | null>(null)
|
||||||
@@ -91,6 +95,7 @@ function BlockViewerProvider({
|
|||||||
BlockViewerContext["activeFile"]
|
BlockViewerContext["activeFile"]
|
||||||
>(highlightedFiles?.[0].target ?? null)
|
>(highlightedFiles?.[0].target ?? null)
|
||||||
const resizablePanelRef = React.useRef<ImperativePanelHandle>(null)
|
const resizablePanelRef = React.useRef<ImperativePanelHandle>(null)
|
||||||
|
const [iframeKey, setIframeKey] = React.useState(0)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BlockViewerContext.Provider
|
<BlockViewerContext.Provider
|
||||||
@@ -103,12 +108,14 @@ function BlockViewerProvider({
|
|||||||
setActiveFile,
|
setActiveFile,
|
||||||
tree,
|
tree,
|
||||||
highlightedFiles,
|
highlightedFiles,
|
||||||
|
iframeKey,
|
||||||
|
setIframeKey,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id={item.name}
|
id={item.name}
|
||||||
data-view={view}
|
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={
|
style={
|
||||||
{
|
{
|
||||||
"--height": item.meta?.iframeHeight ?? "930px",
|
"--height": item.meta?.iframeHeight ?? "930px",
|
||||||
@@ -122,30 +129,30 @@ function BlockViewerProvider({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function BlockViewerToolbar() {
|
function BlockViewerToolbar() {
|
||||||
const { setView, view, item, resizablePanelRef } = useBlockViewer()
|
const { setView, view, item, resizablePanelRef, setIframeKey } =
|
||||||
|
useBlockViewer()
|
||||||
const { copyToClipboard, isCopied } = useCopyToClipboard()
|
const { copyToClipboard, isCopied } = useCopyToClipboard()
|
||||||
|
|
||||||
return (
|
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
|
<Tabs
|
||||||
value={view}
|
value={view}
|
||||||
onValueChange={(value) => setView(value as "preview" | "code")}
|
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">
|
<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="preview">Preview</TabsTrigger>
|
||||||
<TabsTrigger value="code">Code</TabsTrigger>
|
<TabsTrigger value="code">Code</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<Separator orientation="vertical" className="mx-2 hidden !h-4 lg:flex" />
|
<Separator orientation="vertical" className="mx-2 !h-4" />
|
||||||
<a
|
<a
|
||||||
href={`#${item.name}`}
|
href={`#${item.name}`}
|
||||||
className="flex-1 text-center text-sm font-medium underline-offset-2 hover:underline md:flex-auto md:text-left"
|
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>
|
</a>
|
||||||
<div className="ml-auto hidden items-center gap-2 md:flex">
|
<div className="ml-auto flex items-center gap-2">
|
||||||
<div className="hidden h-8 items-center gap-1.5 rounded-md border p-1 shadow-none lg:flex">
|
<div className="h-8 items-center gap-1.5 rounded-md border p-1 shadow-none">
|
||||||
<ToggleGroup
|
<ToggleGroup
|
||||||
type="single"
|
type="single"
|
||||||
defaultValue="100"
|
defaultValue="100"
|
||||||
@@ -179,15 +186,27 @@ function BlockViewerToolbar() {
|
|||||||
<Fullscreen />
|
<Fullscreen />
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</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>
|
</ToggleGroup>
|
||||||
</div>
|
</div>
|
||||||
<Separator
|
<Separator orientation="vertical" className="mx-1 !h-4" />
|
||||||
orientation="vertical"
|
|
||||||
className="mx-1 hidden !h-4 lg:flex"
|
|
||||||
/>
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
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"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
copyToClipboard(`npx shadcn@latest add ${item.name}`)
|
copyToClipboard(`npx shadcn@latest add ${item.name}`)
|
||||||
@@ -196,50 +215,48 @@ function BlockViewerToolbar() {
|
|||||||
{isCopied ? <Check /> : <Terminal />}
|
{isCopied ? <Check /> : <Terminal />}
|
||||||
<span>npx shadcn add {item.name}</span>
|
<span>npx shadcn add {item.name}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Separator
|
<Separator orientation="vertical" className="mx-1 !h-4" />
|
||||||
orientation="vertical"
|
|
||||||
className="mx-1 hidden !h-4 xl:flex"
|
|
||||||
/>
|
|
||||||
<OpenInV0Button name={item.name} />
|
<OpenInV0Button name={item.name} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function BlockViewerView() {
|
function BlockViewerIframe({ className }: { className?: string }) {
|
||||||
const { item, resizablePanelRef } = useBlockViewer()
|
const { item, iframeKey } = useBlockViewer()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group-data-[view=code]/block-view-wrapper:hidden md:h-[calc(var(--height)+10px)]">
|
<iframe
|
||||||
<div className="grid w-full gap-4">
|
key={iframeKey}
|
||||||
<ResizablePanelGroup direction="horizontal" className="relative z-10">
|
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
|
<ResizablePanel
|
||||||
ref={resizablePanelRef}
|
ref={resizablePanelRef}
|
||||||
className="bg-background relative aspect-[4/2.5] overflow-hidden rounded-lg border md:aspect-auto md:rounded-xl"
|
className="bg-background relative aspect-[4/2.5] overflow-hidden rounded-lg border md:aspect-auto md:rounded-xl"
|
||||||
defaultSize={100}
|
defaultSize={100}
|
||||||
minSize={30}
|
minSize={30}
|
||||||
>
|
>
|
||||||
<Image
|
<BlockViewerIframe />
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</ResizablePanel>
|
</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" />
|
<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} />
|
<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() {
|
function BlockViewerCode() {
|
||||||
const { activeFile, highlightedFiles } = useBlockViewer()
|
const { activeFile, highlightedFiles } = useBlockViewer()
|
||||||
|
|
||||||
@@ -269,7 +325,7 @@ function BlockViewerCode() {
|
|||||||
</div>
|
</div>
|
||||||
<figure
|
<figure
|
||||||
data-rehype-pretty-code-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
|
<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"
|
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 (
|
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">
|
<Sidebar collapsible="none" className="w-full flex-1">
|
||||||
<SidebarGroupLabel className="h-12 rounded-none border-b px-4 text-sm">
|
<SidebarGroupLabel className="h-12 rounded-none border-b px-4 text-sm">
|
||||||
Files
|
Files
|
||||||
@@ -414,8 +470,11 @@ function BlockViewer({
|
|||||||
item,
|
item,
|
||||||
tree,
|
tree,
|
||||||
highlightedFiles,
|
highlightedFiles,
|
||||||
|
children,
|
||||||
...props
|
...props
|
||||||
}: Pick<BlockViewerContext, "item" | "tree" | "highlightedFiles">) {
|
}: Pick<BlockViewerContext, "item" | "tree" | "highlightedFiles"> & {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<BlockViewerProvider
|
<BlockViewerProvider
|
||||||
item={item}
|
item={item}
|
||||||
@@ -426,6 +485,7 @@ function BlockViewer({
|
|||||||
<BlockViewerToolbar />
|
<BlockViewerToolbar />
|
||||||
<BlockViewerView />
|
<BlockViewerView />
|
||||||
<BlockViewerCode />
|
<BlockViewerCode />
|
||||||
|
<BlockViewerMobile>{children}</BlockViewerMobile>
|
||||||
</BlockViewerProvider>
|
</BlockViewerProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const start = new Date(2025, 5, 5)
|
|||||||
|
|
||||||
export function CardsCalendar() {
|
export function CardsCalendar() {
|
||||||
return (
|
return (
|
||||||
<Card className="max-w-[260px] p-0">
|
<Card className="hidden max-w-[260px] p-0 sm:flex">
|
||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
<Calendar
|
<Calendar
|
||||||
numberOfMonths={1}
|
numberOfMonths={1}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function ChartsNav({
|
|||||||
<Link
|
<Link
|
||||||
href={link.href}
|
href={link.href}
|
||||||
key={link.href}
|
key={link.href}
|
||||||
data-active={pathname === link.href}
|
data-active={link.href.startsWith(pathname)}
|
||||||
className={cn(
|
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"
|
"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"
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as React from "react"
|
|||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { type DialogProps } from "@radix-ui/react-dialog"
|
import { type DialogProps } from "@radix-ui/react-dialog"
|
||||||
import { IconArrowRight } from "@tabler/icons-react"
|
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 { type Color, type ColorPalette } from "@/lib/colors"
|
||||||
import { source } from "@/lib/source"
|
import { source } from "@/lib/source"
|
||||||
@@ -35,17 +35,19 @@ import { Separator } from "@/registry/new-york-v4/ui/separator"
|
|||||||
export function CommandMenu({
|
export function CommandMenu({
|
||||||
tree,
|
tree,
|
||||||
colors,
|
colors,
|
||||||
|
blocks,
|
||||||
...props
|
...props
|
||||||
}: DialogProps & {
|
}: DialogProps & {
|
||||||
tree: typeof source.pageTree
|
tree: typeof source.pageTree
|
||||||
colors: ColorPalette[]
|
colors: ColorPalette[]
|
||||||
|
blocks?: { name: string; description: string; categories: string[] }[]
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const isMac = useIsMac()
|
const isMac = useIsMac()
|
||||||
const [config] = useConfig()
|
const [config] = useConfig()
|
||||||
const [open, setOpen] = React.useState(false)
|
const [open, setOpen] = React.useState(false)
|
||||||
const [selectedType, setSelectedType] = React.useState<
|
const [selectedType, setSelectedType] = React.useState<
|
||||||
"color" | "page" | "component" | null
|
"color" | "page" | "component" | "block" | null
|
||||||
>(null)
|
>(null)
|
||||||
const [copyPayload, setCopyPayload] = React.useState("")
|
const [copyPayload, setCopyPayload] = React.useState("")
|
||||||
const packageManager = config.packageManager || "pnpm"
|
const packageManager = config.packageManager || "pnpm"
|
||||||
@@ -74,6 +76,14 @@ export function CommandMenu({
|
|||||||
[setSelectedType, setCopyPayload]
|
[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) => {
|
const runCommand = React.useCallback((command: () => unknown) => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
command()
|
command()
|
||||||
@@ -104,6 +114,13 @@ export function CommandMenu({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedType === "block") {
|
||||||
|
copyToClipboardWithMeta(copyPayload, {
|
||||||
|
name: "copy_npm_command",
|
||||||
|
properties: { command: copyPayload, pm: packageManager },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedType === "page" || selectedType === "component") {
|
if (selectedType === "page" || selectedType === "component") {
|
||||||
copyToClipboardWithMeta(copyPayload, {
|
copyToClipboardWithMeta(copyPayload, {
|
||||||
name: "copy_npm_command",
|
name: "copy_npm_command",
|
||||||
@@ -227,6 +244,41 @@ export function CommandMenu({
|
|||||||
))}
|
))}
|
||||||
</CommandGroup>
|
</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>
|
</CommandList>
|
||||||
</Command>
|
</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">
|
<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 (
|
return (
|
||||||
<div
|
<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}
|
{...props}
|
||||||
>
|
>
|
||||||
<Tabs
|
<Tabs
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function DocsSidebar({
|
|||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
asChild
|
asChild
|
||||||
isActive={item.url === pathname}
|
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>
|
<Link href={item.url}>{item.name}</Link>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export function ModeSwitcher() {
|
|||||||
size="icon"
|
size="icon"
|
||||||
className="group/toggle extend-touch-target size-8"
|
className="group/toggle extend-touch-target size-8"
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
|
title="Toggle theme"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/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
|
Deploy your shadcn/ui app on Vercel
|
||||||
</div>
|
</div>
|
||||||
<div className="text-muted-foreground">
|
<div className="text-muted-foreground">
|
||||||
Trusted by OpenAI, Sonos, Chick-fil-A, and more.
|
Trusted by OpenAI, Sonos, Adobe, and more.
|
||||||
</div>
|
</div>
|
||||||
<div className="text-muted-foreground">
|
<div className="text-muted-foreground">
|
||||||
Vercel provides tools and infrastructure to deploy apps and features at
|
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() {
|
export function SiteFooter() {
|
||||||
return (
|
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="container-wrapper px-4 xl:px-6">
|
||||||
<div className="flex h-(--footer-height) items-center justify-between">
|
<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">
|
<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"
|
className="font-medium underline underline-offset-4"
|
||||||
>
|
>
|
||||||
shadcn
|
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>
|
</a>
|
||||||
. The source code is available on{" "}
|
. The source code is available on{" "}
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { Icons } from "@/components/icons"
|
|||||||
import { MainNav } from "@/components/main-nav"
|
import { MainNav } from "@/components/main-nav"
|
||||||
import { MobileNav } from "@/components/mobile-nav"
|
import { MobileNav } from "@/components/mobile-nav"
|
||||||
import { ModeSwitcher } from "@/components/mode-switcher"
|
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 { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||||
|
|
||||||
@@ -18,8 +20,8 @@ export function SiteHeader() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="bg-background sticky top-0 z-50 w-full">
|
<header className="bg-background sticky top-0 z-50 w-full">
|
||||||
<div className="container-wrapper px-6">
|
<div className="container-wrapper 3xl:fixed:px-0 px-6">
|
||||||
<div className="flex h-(--header-height) items-center gap-2 **:data-[slot=separator]:!h-4">
|
<div className="3xl:fixed:container flex h-(--header-height) items-center gap-2 **:data-[slot=separator]:!h-4">
|
||||||
<MobileNav
|
<MobileNav
|
||||||
tree={pageTree}
|
tree={pageTree}
|
||||||
items={siteConfig.navItems}
|
items={siteConfig.navItems}
|
||||||
@@ -46,6 +48,8 @@ export function SiteHeader() {
|
|||||||
className="ml-2 hidden lg:block"
|
className="ml-2 hidden lg:block"
|
||||||
/>
|
/>
|
||||||
<GitHubLink />
|
<GitHubLink />
|
||||||
|
<Separator orientation="vertical" className="3xl:flex hidden" />
|
||||||
|
<SiteConfig className="3xl:flex hidden" />
|
||||||
<Separator orientation="vertical" />
|
<Separator orientation="vertical" />
|
||||||
<ModeSwitcher />
|
<ModeSwitcher />
|
||||||
</div>
|
</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,37 @@ description: Latest updates and announcements.
|
|||||||
toc: false
|
toc: false
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 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
|
## 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.
|
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 +61,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**.
|
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
|
## March 2025 - Cross-framework Route Support
|
||||||
|
|
||||||
@@ -49,7 +80,7 @@ What's New:
|
|||||||
- The CLI can now initialize projects with Tailwind v4.
|
- The CLI can now initialize projects with Tailwind v4.
|
||||||
- Full support for the new @theme directive and @theme inline option.
|
- Full support for the new @theme directive and @theme inline option.
|
||||||
- All components are updated for Tailwind v4 and React 19.
|
- 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.
|
- Every primitive now has a data-slot attribute for styling.
|
||||||
- We've fixed and cleaned up the style of the components.
|
- We've fixed and cleaned up the style of the components.
|
||||||
- We're deprecating the toast component in favor of sonner.
|
- We're deprecating the toast component in favor of sonner.
|
||||||
@@ -127,7 +158,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.
|
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.
|
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`.
|
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`.
|
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.
|
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.
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ links:
|
|||||||
description="A calendar showing the current date."
|
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
|
## Installation
|
||||||
|
|
||||||
@@ -39,7 +41,7 @@ npx shadcn@latest add calendar
|
|||||||
<Step>Install the following dependencies:</Step>
|
<Step>Install the following dependencies:</Step>
|
||||||
|
|
||||||
```bash
|
```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>
|
<Step>Add the `Button` component to your project.</Step>
|
||||||
@@ -72,19 +74,346 @@ return (
|
|||||||
mode="single"
|
mode="single"
|
||||||
selected={date}
|
selected={date}
|
||||||
onSelect={setDate}
|
onSelect={setDate}
|
||||||
className="rounded-md border"
|
className="rounded-lg border"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
See the [React DayPicker](https://react-day-picker.js.org) documentation for more information.
|
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
|
## 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.
|
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
|
## 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
|
### Form
|
||||||
|
|
||||||
<ComponentPreview name="calendar-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.
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ component: true
|
|||||||
---
|
---
|
||||||
|
|
||||||
<ComponentPreview
|
<ComponentPreview
|
||||||
name="date-picker-demo"
|
name="calendar-22"
|
||||||
description="A date picker in a popover"
|
title="Date of Birth Picker"
|
||||||
|
description="A calendar with date of birth picker."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@@ -40,23 +41,16 @@ export function DatePickerDemo() {
|
|||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant={"outline"}
|
variant="outline"
|
||||||
className={cn(
|
data-empty={!date}
|
||||||
"w-[280px] justify-start text-left font-normal",
|
className="data-[empty=true]:text-muted-foreground w-[280px] justify-start text-left font-normal"
|
||||||
!date && "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
<CalendarIcon />
|
||||||
{date ? format(date, "PPP") : <span>Pick a date</span>}
|
{date ? format(date, "PPP") : <span>Pick a date</span>}
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto p-0">
|
<PopoverContent className="w-auto p-0">
|
||||||
<Calendar
|
<Calendar mode="single" selected={date} onSelect={setDate} />
|
||||||
mode="single"
|
|
||||||
selected={date}
|
|
||||||
onSelect={setDate}
|
|
||||||
initialFocus
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
)
|
)
|
||||||
@@ -67,25 +61,38 @@ See the [React DayPicker](https://react-day-picker.js.org) documentation for mor
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Date Picker
|
### Date of Birth Picker
|
||||||
|
|
||||||
<ComponentPreview
|
<ComponentPreview
|
||||||
name="date-picker-demo"
|
name="calendar-22"
|
||||||
description="A date picker in a popover"
|
title="Date of Birth Picker"
|
||||||
|
description="A calendar with date of birth picker."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
### Date Range Picker
|
### Picker with Input
|
||||||
|
|
||||||
<ComponentPreview
|
<ComponentPreview
|
||||||
name="date-picker-with-range"
|
name="calendar-28"
|
||||||
description="A date range picker"
|
title="Picker with Input"
|
||||||
|
description="A calendar with input and picker."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
### With Presets
|
### Date and Time Picker
|
||||||
|
|
||||||
<ComponentPreview
|
<ComponentPreview
|
||||||
name="date-picker-with-presets"
|
name="calendar-24"
|
||||||
description="A date picker with presets"
|
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
|
### 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
|
## Add custom animations
|
||||||
|
|
||||||
Note: you need to define both `@keyframes` in css and `theme` in cssVars to use animations.
|
Note: you need to define both `@keyframes` in css and `theme` in cssVars to use animations.
|
||||||
|
|||||||
@@ -260,11 +260,13 @@ Use to define CSS variables for your registry item.
|
|||||||
|
|
||||||
### css
|
### 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
|
```json title="registry-item.json" showLineNumbers
|
||||||
{
|
{
|
||||||
"css": {
|
"css": {
|
||||||
|
"@plugin @tailwindcss/typography": {},
|
||||||
|
"@plugin foo": {},
|
||||||
"@layer base": {
|
"@layer base": {
|
||||||
"body": {
|
"body": {
|
||||||
"font-size": "var(--text-base)",
|
"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[] = []
|
categories: string[] = []
|
||||||
): Promise<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 } = await import("@/registry/__index__")
|
||||||
const index = z.record(registryItemSchema).parse(Index)
|
const index = z.record(registryItemSchema).parse(Index)
|
||||||
|
|
||||||
return Object.values(index)
|
return Object.values(index).filter(
|
||||||
.filter(
|
(block) =>
|
||||||
(block) =>
|
types.includes(block.type) &&
|
||||||
types.includes(block.type) &&
|
(categories.length === 0 ||
|
||||||
(categories.length === 0 ||
|
block.categories?.some((category) => categories.includes(category))) &&
|
||||||
block.categories?.some((category) =>
|
!block.name.startsWith("chart-")
|
||||||
categories.includes(category)
|
)
|
||||||
)) &&
|
|
||||||
!block.name.startsWith("chart-")
|
|
||||||
)
|
|
||||||
.map((block) => block.name)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ export const siteConfig = {
|
|||||||
href: "/charts/area",
|
href: "/charts/area",
|
||||||
label: "Charts",
|
label: "Charts",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
href: "/themes",
|
||||||
|
label: "Themes",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
href: "/colors",
|
href: "/colors",
|
||||||
label: "Colors",
|
label: "Colors",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const eventSchema = z.object({
|
|||||||
"copy_chart_theme",
|
"copy_chart_theme",
|
||||||
"copy_chart_data",
|
"copy_chart_data",
|
||||||
"copy_color",
|
"copy_color",
|
||||||
|
"set_layout",
|
||||||
]),
|
]),
|
||||||
// declare type AllowedPropertyValues = string | number | boolean | null
|
// declare type AllowedPropertyValues = string | number | boolean | null
|
||||||
properties: z
|
properties: z
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const mdxComponents = {
|
|||||||
.replace(/\?/g, "")
|
.replace(/\?/g, "")
|
||||||
.toLowerCase()}
|
.toLowerCase()}
|
||||||
className={cn(
|
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
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -62,7 +62,7 @@ export const mdxComponents = {
|
|||||||
h3: ({ className, ...props }: React.ComponentProps<"h3">) => (
|
h3: ({ className, ...props }: React.ComponentProps<"h3">) => (
|
||||||
<h3
|
<h3
|
||||||
className={cn(
|
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
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ const nextConfig = {
|
|||||||
destination: "/charts/area",
|
destination: "/charts/area",
|
||||||
permanent: true,
|
permanent: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: "/view/styles/:style/:name",
|
||||||
|
destination: "/view/:name",
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
"@tanstack/react-table": "^8.9.1",
|
"@tanstack/react-table": "^8.9.1",
|
||||||
"@vercel/analytics": "^1.4.1",
|
"@vercel/analytics": "^1.4.1",
|
||||||
"change-case": "^5.4.4",
|
"change-case": "^5.4.4",
|
||||||
|
"chrono-node": "^2.8.2",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
@@ -70,20 +71,22 @@
|
|||||||
"fumadocs-ui": "^15.3.1",
|
"fumadocs-ui": "^15.3.1",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"jotai": "^2.1.0",
|
"jotai": "^2.1.0",
|
||||||
|
"little-date": "^1.0.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"lucide-react": "0.474.0",
|
"lucide-react": "0.474.0",
|
||||||
"motion": "^12.12.1",
|
"motion": "^12.12.1",
|
||||||
"next": "15.3.1",
|
"next": "15.3.1",
|
||||||
"next-themes": "0.4.6",
|
"next-themes": "0.4.6",
|
||||||
"postcss": "^8.5.1",
|
"postcss": "^8.5.1",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-day-picker": "^8.7.1",
|
"react-day-picker": "^9.7.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-resizable-panels": "^2.1.7",
|
"react-resizable-panels": "^2.1.7",
|
||||||
"recharts": "2.15.1",
|
"recharts": "2.15.1",
|
||||||
"rehype-pretty-code": "^0.14.1",
|
"rehype-pretty-code": "^0.14.1",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"shadcn": "2.6.0",
|
"shadcn": "2.7.0",
|
||||||
"shiki": "^1.10.1",
|
"shiki": "^1.10.1",
|
||||||
"sonner": "^2.0.0",
|
"sonner": "^2.0.0",
|
||||||
"tailwind-merge": "^3.0.1",
|
"tailwind-merge": "^3.0.1",
|
||||||
@@ -95,6 +98,7 @@
|
|||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
|
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/lodash": "^4.17.7",
|
||||||
"@types/mdx": "^2.0.13",
|
"@types/mdx": "^2.0.13",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "19.1.2",
|
"@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",
|
"name": "calendar",
|
||||||
"type": "registry:ui",
|
"type": "registry:ui",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"react-day-picker@8.10.1",
|
"react-day-picker@latest",
|
||||||
"date-fns"
|
"date-fns"
|
||||||
],
|
],
|
||||||
"registryDependencies": [
|
"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
@@ -8,7 +8,7 @@
|
|||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"path": "registry/new-york-v4/examples/accordion-demo.tsx",
|
"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"
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
25
apps/v4/public/r/styles/new-york-v4/calendar-14.json
Normal file
25
apps/v4/public/r/styles/new-york-v4/calendar-14.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||||
|
"name": "calendar-14",
|
||||||
|
"type": "registry:block",
|
||||||
|
"description": "With Booked/Unavailable Days",
|
||||||
|
"registryDependencies": [
|
||||||
|
"calendar"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/calendar-14.tsx",
|
||||||
|
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/new-york-v4/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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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-15.json
Normal file
25
apps/v4/public/r/styles/new-york-v4/calendar-15.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||||
|
"name": "calendar-15",
|
||||||
|
"type": "registry:block",
|
||||||
|
"description": "With Week Numbers",
|
||||||
|
"registryDependencies": [
|
||||||
|
"calendar"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/calendar-15.tsx",
|
||||||
|
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/new-york-v4/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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
28
apps/v4/public/r/styles/new-york-v4/calendar-16.json
Normal file
28
apps/v4/public/r/styles/new-york-v4/calendar-16.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||||
|
"name": "calendar-16",
|
||||||
|
"type": "registry:block",
|
||||||
|
"description": "With time picker",
|
||||||
|
"registryDependencies": [
|
||||||
|
"calendar",
|
||||||
|
"card",
|
||||||
|
"input",
|
||||||
|
"label"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/calendar-16.tsx",
|
||||||
|
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Clock2Icon } from \"lucide-react\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\nimport { Card, CardContent, CardFooter } from \"@/registry/new-york-v4/ui/card\"\nimport { Input } from \"@/registry/new-york-v4/ui/input\"\nimport { Label } from \"@/registry/new-york-v4/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 !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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
28
apps/v4/public/r/styles/new-york-v4/calendar-17.json
Normal file
28
apps/v4/public/r/styles/new-york-v4/calendar-17.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||||
|
"name": "calendar-17",
|
||||||
|
"type": "registry:block",
|
||||||
|
"description": "With time picker inline",
|
||||||
|
"registryDependencies": [
|
||||||
|
"calendar",
|
||||||
|
"card",
|
||||||
|
"input",
|
||||||
|
"label"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/calendar-17.tsx",
|
||||||
|
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\nimport { Card, CardContent, CardFooter } from \"@/registry/new-york-v4/ui/card\"\nimport { Input } from \"@/registry/new-york-v4/ui/input\"\nimport { Label } from \"@/registry/new-york-v4/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:--spacing(10.5)]\"\n />\n </CardContent>\n <CardFooter className=\"flex gap-2 border-t px-4 !pt-4 *:[div]:w-full\">\n <div>\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>\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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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-18.json
Normal file
25
apps/v4/public/r/styles/new-york-v4/calendar-18.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||||
|
"name": "calendar-18",
|
||||||
|
"type": "registry:block",
|
||||||
|
"description": "Variable size",
|
||||||
|
"registryDependencies": [
|
||||||
|
"calendar"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/calendar-18.tsx",
|
||||||
|
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/new-york-v4/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:--spacing(11)] md:[--cell-size:--spacing(12)]\"\n buttonVariant=\"ghost\"\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"
|
||||||
|
]
|
||||||
|
}
|
||||||
31
apps/v4/public/r/styles/new-york-v4/calendar-19.json
Normal file
31
apps/v4/public/r/styles/new-york-v4/calendar-19.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||||
|
"name": "calendar-19",
|
||||||
|
"type": "registry:block",
|
||||||
|
"description": "With presets",
|
||||||
|
"dependencies": [
|
||||||
|
"date-fns"
|
||||||
|
],
|
||||||
|
"registryDependencies": [
|
||||||
|
"calendar",
|
||||||
|
"card",
|
||||||
|
"input",
|
||||||
|
"label"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/calendar-19.tsx",
|
||||||
|
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { addDays } from \"date-fns\"\n\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\nimport { Card, CardContent, CardFooter } from \"@/registry/new-york-v4/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:--spacing(9.5)]\"\n />\n </CardContent>\n <CardFooter className=\"flex flex-wrap gap-2 border-t px-4 !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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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/new-york-v4/calendar-20.json
Normal file
27
apps/v4/public/r/styles/new-york-v4/calendar-20.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||||
|
"name": "calendar-20",
|
||||||
|
"type": "registry:block",
|
||||||
|
"description": "With time presets",
|
||||||
|
"registryDependencies": [
|
||||||
|
"calendar",
|
||||||
|
"card",
|
||||||
|
"button"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/blocks/calendar-20.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 { Card, CardContent, CardFooter } from \"@/registry/new-york-v4/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.toString().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:--spacing(10)] md:[--cell-size:--spacing(12)]\"\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-t-0 md:border-l\">\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 px-6 !py-5 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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
25
apps/v4/public/r/styles/new-york-v4/calendar-21.json
Normal file
25
apps/v4/public/r/styles/new-york-v4/calendar-21.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||||
|
"name": "calendar-21",
|
||||||
|
"type": "registry:block",
|
||||||
|
"description": "Custom days and formatters",
|
||||||
|
"registryDependencies": [
|
||||||
|
"calendar"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/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/new-york-v4/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:--spacing(11)] md:[--cell-size:--spacing(13)]\"\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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user