mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-16 04:11:34 +00:00
Compare commits
32 Commits
shadcn/fro
...
shadcn-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62223c12cf | ||
|
|
6d467d2e1d | ||
|
|
893cddd2dc | ||
|
|
1781186def | ||
|
|
89b9a76368 | ||
|
|
0266253841 | ||
|
|
e5fda2c139 | ||
|
|
40b9de46e9 | ||
|
|
6d97ab0b9b | ||
|
|
d06e84a007 | ||
|
|
a29185c9cf | ||
|
|
84c801ac67 | ||
|
|
267d45ac7a | ||
|
|
caadc3d7e8 | ||
|
|
a4ee54836e | ||
|
|
7b5c919eae | ||
|
|
f1cacdc051 | ||
|
|
8cb8fb66b3 | ||
|
|
ef01cd4315 | ||
|
|
6cb2a1fd65 | ||
|
|
ee88d296f4 | ||
|
|
598f17812d | ||
|
|
0ae734bdb2 | ||
|
|
18bd8f07cb | ||
|
|
5fc9ced0fd | ||
|
|
b5dff005f6 | ||
|
|
c5c08bb773 | ||
|
|
5998e59839 | ||
|
|
4b7e38ab42 | ||
|
|
e2ba2d241e | ||
|
|
13e2a6c598 | ||
|
|
47c47eaed2 |
@@ -93,7 +93,7 @@ export function AppearanceSettings() {
|
||||
value={gpuCount}
|
||||
onChange={handleGpuInputChange}
|
||||
size={3}
|
||||
className="h-8 !w-14 font-mono"
|
||||
className="h-7 !w-14 font-mono"
|
||||
maxLength={3}
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/examples/radix/ui/avatar"
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarGroup,
|
||||
AvatarImage,
|
||||
} from "@/examples/radix/ui/avatar"
|
||||
import { Button } from "@/examples/radix/ui/button"
|
||||
import {
|
||||
Empty,
|
||||
@@ -12,10 +17,10 @@ import { PlusIcon } from "lucide-react"
|
||||
|
||||
export function EmptyAvatarGroup() {
|
||||
return (
|
||||
<Empty className="flex-none border">
|
||||
<Empty className="flex-none border py-10">
|
||||
<EmptyHeader>
|
||||
<EmptyMedia>
|
||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
||||
<AvatarGroup className="grayscale">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
@@ -34,7 +39,7 @@ export function EmptyAvatarGroup() {
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
</AvatarGroup>
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>No Team Members</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
|
||||
@@ -185,7 +185,7 @@ export function NotionPromptForm() {
|
||||
const hasMentions = mentions.length > 0
|
||||
|
||||
return (
|
||||
<form className="[--radius:1.2rem]">
|
||||
<form>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="notion-prompt" className="sr-only">
|
||||
Prompt
|
||||
@@ -217,7 +217,7 @@ export function NotionPromptForm() {
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Mention a person, page, or date</TooltipContent>
|
||||
</Tooltip>
|
||||
<PopoverContent className="p-0 [--radius:1.2rem]" align="start">
|
||||
<PopoverContent className="p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search pages..." />
|
||||
<CommandList>
|
||||
@@ -301,12 +301,8 @@ export function NotionPromptForm() {
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Select AI model</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent
|
||||
side="top"
|
||||
align="start"
|
||||
className="[--radius:1rem]"
|
||||
>
|
||||
<DropdownMenuGroup className="w-42">
|
||||
<DropdownMenuContent side="top" align="start" className="w-48">
|
||||
<DropdownMenuGroup className="w-48">
|
||||
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
||||
Select Agent Mode
|
||||
</DropdownMenuLabel>
|
||||
@@ -341,11 +337,7 @@ export function NotionPromptForm() {
|
||||
<IconWorld /> All Sources
|
||||
</InputGroupButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
side="top"
|
||||
align="end"
|
||||
className="[--radius:1rem]"
|
||||
>
|
||||
<DropdownMenuContent side="top" align="end" className="w-72">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
asChild
|
||||
|
||||
@@ -79,7 +79,10 @@ export default async function Page(props: {
|
||||
|
||||
const doc = page.data
|
||||
const MDX = doc.body
|
||||
const neighbours = findNeighbour(source.pageTree, page.url)
|
||||
const isChangelog = params.slug?.[0] === "changelog"
|
||||
const neighbours = isChangelog
|
||||
? { previous: null, next: null }
|
||||
: findNeighbour(source.pageTree, page.url)
|
||||
const raw = await page.data.getText("raw")
|
||||
|
||||
return (
|
||||
|
||||
140
apps/v4/app/(app)/docs/changelog/page.tsx
Normal file
140
apps/v4/app/(app)/docs/changelog/page.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import Link from "next/link"
|
||||
import { Button } from "@/examples/radix/ui/button"
|
||||
import { mdxComponents } from "@/mdx-components"
|
||||
import { IconRss } from "@tabler/icons-react"
|
||||
|
||||
import { getChangelogPages, type ChangelogPageData } from "@/lib/changelog"
|
||||
import { absoluteUrl } from "@/lib/utils"
|
||||
import { OpenInV0Cta } from "@/components/open-in-v0-cta"
|
||||
|
||||
export const revalidate = false
|
||||
export const dynamic = "force-static"
|
||||
|
||||
export function generateMetadata() {
|
||||
return {
|
||||
title: "Changelog",
|
||||
description: "Latest updates and announcements.",
|
||||
openGraph: {
|
||||
title: "Changelog",
|
||||
description: "Latest updates and announcements.",
|
||||
type: "article",
|
||||
url: absoluteUrl("/docs/changelog"),
|
||||
images: [
|
||||
{
|
||||
url: `/og?title=${encodeURIComponent(
|
||||
"Changelog"
|
||||
)}&description=${encodeURIComponent(
|
||||
"Latest updates and announcements."
|
||||
)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default function ChangelogPage() {
|
||||
const pages = getChangelogPages()
|
||||
const latestPages = pages.slice(0, 5)
|
||||
const olderPages = pages.slice(5)
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="docs"
|
||||
className="flex scroll-mt-24 items-stretch pb-8 text-[1.05rem] sm:text-[15px] xl:w-full"
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<div className="h-(--top-spacing) shrink-0" />
|
||||
<div className="mx-auto flex w-full max-w-[40rem] min-w-0 flex-1 flex-col gap-6 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 items-center justify-between">
|
||||
<h1 className="scroll-m-24 text-4xl font-semibold tracking-tight sm:text-3xl">
|
||||
Changelog
|
||||
</h1>
|
||||
<Button variant="secondary" size="sm" asChild>
|
||||
<a href="/rss.xml" target="_blank" rel="noopener noreferrer">
|
||||
<IconRss />
|
||||
RSS
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-muted-foreground text-[1.05rem] sm:text-base sm:text-balance md:max-w-[80%]">
|
||||
Latest updates and announcements.
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full flex-1 pb-16 sm:pb-0">
|
||||
{latestPages.map((page) => {
|
||||
const data = page.data as ChangelogPageData
|
||||
const MDX = page.data.body
|
||||
|
||||
return (
|
||||
<article key={page.url} className="mb-12 border-b pb-12">
|
||||
<h2 className="font-heading text-xl font-semibold tracking-tight">
|
||||
{data.title}
|
||||
</h2>
|
||||
<div className="prose-changelog mt-6 *:first:mt-0">
|
||||
<MDX components={mdxComponents} />
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
{olderPages.length > 0 && (
|
||||
<div id="more-updates" className="mb-24 scroll-mt-24">
|
||||
<h2 className="font-heading mb-6 text-xl font-semibold tracking-tight">
|
||||
More Updates
|
||||
</h2>
|
||||
<ul className="flex flex-col gap-4">
|
||||
{olderPages.map((page) => {
|
||||
const data = page.data as ChangelogPageData
|
||||
return (
|
||||
<li key={page.url} className="flex items-center gap-3">
|
||||
<Link
|
||||
href={page.url}
|
||||
className="font-medium hover:underline"
|
||||
>
|
||||
{data.title}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[90svh] w-72 flex-col gap-4 overflow-hidden overscroll-none pb-8 lg:flex">
|
||||
<div className="h-(--top-spacing) shrink-0"></div>
|
||||
<div className="no-scrollbar flex flex-col gap-8 overflow-y-auto px-8">
|
||||
<div className="flex flex-col gap-2 p-4 pt-0 text-sm">
|
||||
<p className="text-muted-foreground bg-background sticky top-0 h-6 text-xs font-medium">
|
||||
On This Page
|
||||
</p>
|
||||
{latestPages.map((page) => {
|
||||
const data = page.data as ChangelogPageData
|
||||
return (
|
||||
<Link
|
||||
key={page.url}
|
||||
href={page.url}
|
||||
className="text-muted-foreground hover:text-foreground text-[0.8rem] no-underline transition-colors"
|
||||
>
|
||||
{data.title}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
{olderPages.length > 0 && (
|
||||
<a
|
||||
href="#more-updates"
|
||||
className="text-muted-foreground hover:text-foreground text-[0.8rem] no-underline transition-colors"
|
||||
>
|
||||
More Updates
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden flex-1 flex-col gap-6 px-6 xl:flex">
|
||||
<OpenInV0Cta />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -45,18 +45,6 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
const designSystemConfig = parseResult.data
|
||||
const registryBase = buildRegistryBase(designSystemConfig)
|
||||
const validateResult = registryItemSchema.safeParse(registryBase)
|
||||
|
||||
if (!validateResult.success) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Invalid registry base item",
|
||||
details: validateResult.error.format(),
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
|
||||
track("create_open_in_v0", designSystemConfig)
|
||||
|
||||
@@ -75,28 +63,23 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
async function buildV0Payload(designSystemConfig: DesignSystemConfig) {
|
||||
const files: z.infer<typeof registryItemFileSchema>[] = []
|
||||
const registryBase = buildRegistryBase(designSystemConfig)
|
||||
|
||||
// Build globals.css file.
|
||||
files.push(buildGlobalsCss(designSystemConfig))
|
||||
|
||||
// Build layout.tsx file.
|
||||
files.push(buildLayoutFile(designSystemConfig))
|
||||
|
||||
// Build component files.
|
||||
const componentFiles = await buildComponentFiles(designSystemConfig)
|
||||
files.push(...componentFiles)
|
||||
// Build all files in parallel.
|
||||
const [globalsCss, layoutFile, componentFiles] = await Promise.all([
|
||||
buildGlobalsCss(registryBase),
|
||||
buildLayoutFile(designSystemConfig),
|
||||
buildComponentFiles(designSystemConfig),
|
||||
])
|
||||
|
||||
return registryItemSchema.parse({
|
||||
name: designSystemConfig.item ?? "Item",
|
||||
type: "registry:item",
|
||||
files,
|
||||
files: [globalsCss, layoutFile, ...componentFiles],
|
||||
})
|
||||
}
|
||||
|
||||
function buildGlobalsCss(designSystemConfig: DesignSystemConfig) {
|
||||
const registryBase = buildRegistryBase(designSystemConfig)
|
||||
|
||||
function buildGlobalsCss(registryBase: RegistryItem) {
|
||||
const lightVars = Object.entries(registryBase.cssVars?.light ?? {})
|
||||
.map(([key, value]) => ` --${key}: ${value};`)
|
||||
.join("\n")
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { TrashIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -6,6 +8,7 @@ import {
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogMedia,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/registry/new-york-v4/ui/alert-dialog"
|
||||
@@ -13,23 +16,66 @@ import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export function AlertDialogDemo() {
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="outline">Show Dialog</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete your
|
||||
account and remove your data from our servers.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction>Continue</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="outline">Default</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete your
|
||||
account and remove your data from our servers.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction>Continue</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="outline">With Media</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogMedia>
|
||||
<TrashIcon className="size-8" />
|
||||
</AlertDialogMedia>
|
||||
<AlertDialogTitle>Delete this item?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete the
|
||||
item from your account.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction variant="destructive">Delete</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="outline">Small Size</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent size="sm">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogMedia>
|
||||
<TrashIcon className="size-8" />
|
||||
</AlertDialogMedia>
|
||||
<AlertDialogTitle>Delete this item?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction variant="destructive">Delete</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,90 +1,174 @@
|
||||
import { PlusIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarBadge,
|
||||
AvatarFallback,
|
||||
AvatarGroup,
|
||||
AvatarGroupCount,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
|
||||
export function AvatarDemo() {
|
||||
return (
|
||||
<div className="flex flex-row flex-wrap items-center gap-4">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar className="size-12">
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar className="rounded-lg">
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
||||
<Avatar>
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* Sizes. */}
|
||||
<div className="flex flex-row flex-wrap items-center gap-4">
|
||||
<Avatar size="sm">
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/maxleiter.png"
|
||||
alt="@maxleiter"
|
||||
/>
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
<Avatar size="lg">
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
{/* Fallback. */}
|
||||
<div className="flex flex-row flex-wrap items-center gap-4">
|
||||
<Avatar size="sm">
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/maxleiter.png"
|
||||
alt="@maxleiter"
|
||||
/>
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
<Avatar size="lg">
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 hover:space-x-1 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale *:data-[slot=avatar]:transition-all *:data-[slot=avatar]:duration-300 *:data-[slot=avatar]:ease-in-out">
|
||||
{/* With badge. */}
|
||||
<div className="flex flex-row flex-wrap items-center gap-4">
|
||||
<Avatar size="sm">
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
<AvatarBadge />
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
<AvatarBadge />
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/maxleiter.png"
|
||||
alt="@maxleiter"
|
||||
/>
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
<Avatar size="lg">
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
<AvatarBadge>
|
||||
<PlusIcon />
|
||||
</AvatarBadge>
|
||||
</Avatar>
|
||||
</div>
|
||||
{/* Avatar group. */}
|
||||
<div className="flex flex-row flex-wrap items-center gap-4">
|
||||
<AvatarGroup>
|
||||
<Avatar size="sm">
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar size="sm">
|
||||
<AvatarImage
|
||||
src="https://github.com/maxleiter.png"
|
||||
alt="@maxleiter"
|
||||
/>
|
||||
<AvatarFallback>ML</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar size="sm">
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</AvatarGroup>
|
||||
<AvatarGroup>
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/maxleiter.png"
|
||||
alt="@maxleiter"
|
||||
/>
|
||||
<AvatarFallback>ML</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</AvatarGroup>
|
||||
<AvatarGroup>
|
||||
<Avatar size="lg">
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar size="lg">
|
||||
<AvatarImage
|
||||
src="https://github.com/maxleiter.png"
|
||||
alt="@maxleiter"
|
||||
/>
|
||||
<AvatarFallback>ML</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar size="lg">
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</AvatarGroup>
|
||||
</div>
|
||||
{/* Avatar group with count. */}
|
||||
<div className="flex flex-row flex-wrap items-center gap-4">
|
||||
<AvatarGroup>
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/maxleiter.png"
|
||||
alt="@maxleiter"
|
||||
/>
|
||||
<AvatarFallback>ML</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
<AvatarGroupCount>+3</AvatarGroupCount>
|
||||
</AvatarGroup>
|
||||
<AvatarGroup>
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/maxleiter.png"
|
||||
alt="@maxleiter"
|
||||
/>
|
||||
<AvatarFallback>ML</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
<AvatarGroupCount>
|
||||
<PlusIcon />
|
||||
</AvatarGroupCount>
|
||||
</AvatarGroup>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ export function BadgeDemo() {
|
||||
<Badge variant="secondary">Secondary</Badge>
|
||||
<Badge variant="destructive">Destructive</Badge>
|
||||
<Badge variant="outline">Outline</Badge>
|
||||
<Badge variant="ghost">Ghost</Badge>
|
||||
<Badge variant="link">Link</Badge>
|
||||
</div>
|
||||
<div className="flex w-full flex-wrap gap-2">
|
||||
<Badge variant="outline">
|
||||
<CheckIcon />
|
||||
Badge
|
||||
@@ -55,6 +59,16 @@ export function BadgeDemo() {
|
||||
Link <ArrowRightIcon />
|
||||
</a>
|
||||
</Badge>
|
||||
<Badge asChild variant="ghost">
|
||||
<a href="#">
|
||||
Link <ArrowRightIcon />
|
||||
</a>
|
||||
</Badge>
|
||||
<Badge asChild variant="link">
|
||||
<a href="#">
|
||||
Link <ArrowRightIcon />
|
||||
</a>
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ArrowRightIcon, Loader2Icon, SendIcon } from "lucide-react"
|
||||
import { ArrowRightIcon, Loader2Icon, PlusIcon, SendIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
@@ -6,22 +6,25 @@ export function ButtonDemo() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<Button>Button</Button>
|
||||
<Button variant="outline">Outline</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
<Button variant="destructive">Destructive</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="link">Link</Button>
|
||||
<Button variant="outline">
|
||||
<Button size="xs">Extra Small</Button>
|
||||
<Button variant="outline" size="xs">
|
||||
Outline
|
||||
</Button>
|
||||
<Button variant="ghost" size="xs">
|
||||
Ghost
|
||||
</Button>
|
||||
<Button variant="destructive" size="xs">
|
||||
Destructive
|
||||
</Button>
|
||||
<Button variant="secondary" size="xs">
|
||||
Secondary
|
||||
</Button>
|
||||
<Button variant="link" size="xs">
|
||||
Link
|
||||
</Button>
|
||||
<Button variant="outline" size="xs">
|
||||
<SendIcon /> Send
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
Learn More <ArrowRightIcon />
|
||||
</Button>
|
||||
<Button disabled variant="outline">
|
||||
<Loader2Icon className="animate-spin" />
|
||||
Please wait
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<Button size="sm">Small</Button>
|
||||
@@ -43,10 +46,21 @@ export function ButtonDemo() {
|
||||
<Button variant="outline" size="sm">
|
||||
<SendIcon /> Send
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<Button>Button</Button>
|
||||
<Button variant="outline">Outline</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
<Button variant="destructive">Destructive</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="link">Link</Button>
|
||||
<Button variant="outline">
|
||||
<SendIcon /> Send
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
Learn More <ArrowRightIcon />
|
||||
</Button>
|
||||
<Button disabled size="sm" variant="outline">
|
||||
<Button disabled variant="outline">
|
||||
<Loader2Icon className="animate-spin" />
|
||||
Please wait
|
||||
</Button>
|
||||
@@ -71,12 +85,19 @@ export function ButtonDemo() {
|
||||
<Button variant="outline" size="lg">
|
||||
<SendIcon /> Send
|
||||
</Button>
|
||||
<Button variant="outline" size="lg">
|
||||
Learn More <ArrowRightIcon />
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<Button size="icon-xs" variant="outline">
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
<Button disabled size="lg" variant="outline">
|
||||
<Loader2Icon className="animate-spin" />
|
||||
Please wait
|
||||
<Button size="icon-sm" variant="outline">
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
<Button size="icon" variant="outline">
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
<Button size="icon-lg" variant="outline">
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,405 +1,183 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
CheckIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronsUpDown,
|
||||
PlusCircleIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/registry/new-york-v4/ui/avatar"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from "@/registry/new-york-v4/ui/command"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york-v4/ui/popover"
|
||||
Combobox,
|
||||
ComboboxChip,
|
||||
ComboboxChips,
|
||||
ComboboxChipsInput,
|
||||
ComboboxCollection,
|
||||
ComboboxContent,
|
||||
ComboboxEmpty,
|
||||
ComboboxGroup,
|
||||
ComboboxInput,
|
||||
ComboboxItem,
|
||||
ComboboxLabel,
|
||||
ComboboxList,
|
||||
ComboboxTrigger,
|
||||
ComboboxValue,
|
||||
useComboboxAnchor,
|
||||
} from "@/registry/new-york-v4/ui/combobox"
|
||||
|
||||
const frameworks = [
|
||||
{
|
||||
value: "next.js",
|
||||
label: "Next.js",
|
||||
},
|
||||
{
|
||||
value: "sveltekit",
|
||||
label: "SvelteKit",
|
||||
},
|
||||
{
|
||||
value: "nuxt.js",
|
||||
label: "Nuxt.js",
|
||||
},
|
||||
{
|
||||
value: "remix",
|
||||
label: "Remix",
|
||||
},
|
||||
{
|
||||
value: "astro",
|
||||
label: "Astro",
|
||||
},
|
||||
]
|
||||
|
||||
type Framework = (typeof frameworks)[number]
|
||||
|
||||
const users = [
|
||||
{
|
||||
id: "1",
|
||||
username: "shadcn",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
username: "maxleiter",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
username: "evilrabbit",
|
||||
},
|
||||
"Next.js",
|
||||
"SvelteKit",
|
||||
"Nuxt.js",
|
||||
"Remix",
|
||||
"Astro",
|
||||
] as const
|
||||
|
||||
type User = (typeof users)[number]
|
||||
|
||||
const timezones = [
|
||||
{
|
||||
label: "Americas",
|
||||
timezones: [
|
||||
{ value: "America/New_York", label: "(GMT-5) New York" },
|
||||
{ value: "America/Los_Angeles", label: "(GMT-8) Los Angeles" },
|
||||
{ value: "America/Chicago", label: "(GMT-6) Chicago" },
|
||||
{ value: "America/Toronto", label: "(GMT-5) Toronto" },
|
||||
{ value: "America/Vancouver", label: "(GMT-8) Vancouver" },
|
||||
{ value: "America/Sao_Paulo", label: "(GMT-3) São Paulo" },
|
||||
],
|
||||
value: "Americas",
|
||||
items: ["(GMT-5) New York", "(GMT-8) Los Angeles", "(GMT-6) Chicago"],
|
||||
},
|
||||
{
|
||||
label: "Europe",
|
||||
timezones: [
|
||||
{ value: "Europe/London", label: "(GMT+0) London" },
|
||||
{ value: "Europe/Paris", label: "(GMT+1) Paris" },
|
||||
{ value: "Europe/Berlin", label: "(GMT+1) Berlin" },
|
||||
{ value: "Europe/Rome", label: "(GMT+1) Rome" },
|
||||
{ value: "Europe/Madrid", label: "(GMT+1) Madrid" },
|
||||
{ value: "Europe/Amsterdam", label: "(GMT+1) Amsterdam" },
|
||||
],
|
||||
value: "Europe",
|
||||
items: ["(GMT+0) London", "(GMT+1) Paris", "(GMT+1) Berlin"],
|
||||
},
|
||||
{
|
||||
label: "Asia/Pacific",
|
||||
timezones: [
|
||||
{ value: "Asia/Tokyo", label: "(GMT+9) Tokyo" },
|
||||
{ value: "Asia/Shanghai", label: "(GMT+8) Shanghai" },
|
||||
{ value: "Asia/Singapore", label: "(GMT+8) Singapore" },
|
||||
{ value: "Asia/Dubai", label: "(GMT+4) Dubai" },
|
||||
{ value: "Australia/Sydney", label: "(GMT+11) Sydney" },
|
||||
{ value: "Asia/Seoul", label: "(GMT+9) Seoul" },
|
||||
],
|
||||
value: "Asia/Pacific",
|
||||
items: ["(GMT+9) Tokyo", "(GMT+8) Shanghai", "(GMT+8) Singapore"],
|
||||
},
|
||||
] as const
|
||||
|
||||
type Timezone = (typeof timezones)[number]
|
||||
const countries = [
|
||||
{ code: "", value: "", label: "Select country" },
|
||||
{ code: "us", value: "united-states", label: "United States" },
|
||||
{ code: "ca", value: "canada", label: "Canada" },
|
||||
{ code: "gb", value: "united-kingdom", label: "United Kingdom" },
|
||||
{ code: "de", value: "germany", label: "Germany" },
|
||||
{ code: "fr", value: "france", label: "France" },
|
||||
{ code: "jp", value: "japan", label: "Japan" },
|
||||
]
|
||||
|
||||
export function ComboboxDemo() {
|
||||
return (
|
||||
<div className="flex w-full flex-wrap items-start gap-4">
|
||||
<FrameworkCombobox frameworks={[...frameworks]} />
|
||||
<UserCombobox users={[...users]} selectedUserId={users[0].id} />
|
||||
<TimezoneCombobox
|
||||
timezones={[...timezones]}
|
||||
selectedTimezone={timezones[0].timezones[0]}
|
||||
/>
|
||||
<ComboboxWithCheckbox frameworks={[...frameworks]} />
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
{/* Basic combobox. */}
|
||||
<div className="flex flex-wrap items-start gap-4">
|
||||
<Combobox items={frameworks}>
|
||||
<ComboboxInput placeholder="Select a framework" />
|
||||
<ComboboxContent>
|
||||
<ComboboxEmpty>No items found.</ComboboxEmpty>
|
||||
<ComboboxList>
|
||||
{(item) => (
|
||||
<ComboboxItem key={item} value={item}>
|
||||
{item}
|
||||
</ComboboxItem>
|
||||
)}
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
{/* With clear button. */}
|
||||
<div className="flex flex-wrap items-start gap-4">
|
||||
<Combobox items={frameworks} defaultValue={frameworks[0]}>
|
||||
<ComboboxInput placeholder="Select a framework" showClear />
|
||||
<ComboboxContent>
|
||||
<ComboboxEmpty>No items found.</ComboboxEmpty>
|
||||
<ComboboxList>
|
||||
{(item) => (
|
||||
<ComboboxItem key={item} value={item}>
|
||||
{item}
|
||||
</ComboboxItem>
|
||||
)}
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
{/* With groups. */}
|
||||
<div className="flex flex-wrap items-start gap-4">
|
||||
<Combobox items={timezones}>
|
||||
<ComboboxInput placeholder="Select a timezone" />
|
||||
<ComboboxContent>
|
||||
<ComboboxEmpty>No timezones found.</ComboboxEmpty>
|
||||
<ComboboxList>
|
||||
{(group) => (
|
||||
<ComboboxGroup key={group.value} items={group.items}>
|
||||
<ComboboxLabel>{group.value}</ComboboxLabel>
|
||||
<ComboboxCollection>
|
||||
{(item) => (
|
||||
<ComboboxItem key={item} value={item}>
|
||||
{item}
|
||||
</ComboboxItem>
|
||||
)}
|
||||
</ComboboxCollection>
|
||||
</ComboboxGroup>
|
||||
)}
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
{/* With trigger button. */}
|
||||
<div className="flex flex-wrap items-start gap-4">
|
||||
<Combobox items={countries} defaultValue={countries[0]}>
|
||||
<ComboboxTrigger
|
||||
render={
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-64 justify-between font-normal"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ComboboxValue />
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent>
|
||||
<ComboboxInput showTrigger={false} placeholder="Search" />
|
||||
<ComboboxEmpty>No items found.</ComboboxEmpty>
|
||||
<ComboboxList>
|
||||
{(item) => (
|
||||
<ComboboxItem key={item.code} value={item}>
|
||||
{item.label}
|
||||
</ComboboxItem>
|
||||
)}
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
{/* Multiple selection with chips. */}
|
||||
<ComboboxMultiple />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FrameworkCombobox({ frameworks }: { frameworks: Framework[] }) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [value, setValue] = React.useState("")
|
||||
function ComboboxMultiple() {
|
||||
const anchor = useComboboxAnchor()
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="w-full justify-between md:max-w-[200px]"
|
||||
>
|
||||
{value
|
||||
? frameworks.find((framework) => framework.value === value)?.label
|
||||
: "Select framework..."}
|
||||
<ChevronsUpDown className="text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-(--radix-popover-trigger-width) p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search framework..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No framework found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{frameworks.map((framework) => (
|
||||
<CommandItem
|
||||
key={framework.value}
|
||||
value={framework.value}
|
||||
onSelect={(currentValue) => {
|
||||
setValue(currentValue === value ? "" : currentValue)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{framework.label}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto",
|
||||
value === framework.value ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
function UserCombobox({
|
||||
users,
|
||||
selectedUserId,
|
||||
}: {
|
||||
users: User[]
|
||||
selectedUserId: string
|
||||
}) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [value, setValue] = React.useState(selectedUserId)
|
||||
|
||||
const selectedUser = React.useMemo(
|
||||
() => users.find((user) => user.id === value),
|
||||
[value, users]
|
||||
)
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="w-full justify-between px-2 md:max-w-[200px]"
|
||||
>
|
||||
{selectedUser ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar className="size-5">
|
||||
<AvatarImage
|
||||
src={`https://github.com/${selectedUser.username}.png`}
|
||||
/>
|
||||
<AvatarFallback>{selectedUser.username[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
{selectedUser.username}
|
||||
</div>
|
||||
) : (
|
||||
"Select user..."
|
||||
)}
|
||||
<ChevronsUpDown className="text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-(--radix-popover-trigger-width) p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search user..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No user found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{users.map((user) => (
|
||||
<CommandItem
|
||||
key={user.id}
|
||||
value={user.id}
|
||||
onSelect={(currentValue) => {
|
||||
setValue(currentValue === value ? "" : currentValue)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<Avatar className="size-5">
|
||||
<AvatarImage
|
||||
src={`https://github.com/${user.username}.png`}
|
||||
/>
|
||||
<AvatarFallback>{user.username[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
{user.username}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto",
|
||||
value === user.id ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup>
|
||||
<CommandItem>
|
||||
<PlusCircleIcon />
|
||||
Create user
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
function TimezoneCombobox({
|
||||
timezones,
|
||||
selectedTimezone,
|
||||
}: {
|
||||
timezones: Timezone[]
|
||||
selectedTimezone: Timezone["timezones"][number]
|
||||
}) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [value, setValue] = React.useState(selectedTimezone.value)
|
||||
|
||||
const selectedGroup = React.useMemo(
|
||||
() =>
|
||||
timezones.find((group) =>
|
||||
group.timezones.find((tz) => tz.value === value)
|
||||
),
|
||||
[value, timezones]
|
||||
)
|
||||
|
||||
const selectedTimezoneLabel = React.useMemo(
|
||||
() => selectedGroup?.timezones.find((tz) => tz.value === value)?.label,
|
||||
[value, selectedGroup]
|
||||
)
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-12 w-full justify-between px-2.5 md:max-w-[200px]"
|
||||
>
|
||||
{selectedTimezone ? (
|
||||
<div className="flex flex-col items-start gap-0.5">
|
||||
<span className="text-muted-foreground text-xs font-normal">
|
||||
{selectedGroup?.label}
|
||||
</span>
|
||||
<span>{selectedTimezoneLabel}</span>
|
||||
</div>
|
||||
) : (
|
||||
"Select timezone"
|
||||
)}
|
||||
<ChevronDownIcon className="text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search timezone..." />
|
||||
<CommandList className="scroll-pb-12">
|
||||
<CommandEmpty>No timezone found.</CommandEmpty>
|
||||
{timezones.map((region) => (
|
||||
<CommandGroup key={region.label} heading={region.label}>
|
||||
{region.timezones.map((timezone) => (
|
||||
<CommandItem
|
||||
key={timezone.value}
|
||||
value={timezone.value}
|
||||
onSelect={(currentValue) => {
|
||||
setValue(
|
||||
currentValue as Timezone["timezones"][number]["value"]
|
||||
)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{timezone.label}
|
||||
<CheckIcon
|
||||
className="ml-auto opacity-0 data-[selected=true]:opacity-100"
|
||||
data-selected={value === timezone.value}
|
||||
/>
|
||||
</CommandItem>
|
||||
<div className="flex flex-wrap items-start gap-4">
|
||||
<Combobox
|
||||
multiple
|
||||
autoHighlight
|
||||
items={frameworks}
|
||||
defaultValue={[frameworks[0]]}
|
||||
>
|
||||
<ComboboxChips ref={anchor}>
|
||||
<ComboboxValue>
|
||||
{(values) => (
|
||||
<React.Fragment>
|
||||
{values.map((value: string) => (
|
||||
<ComboboxChip key={value}>{value}</ComboboxChip>
|
||||
))}
|
||||
</CommandGroup>
|
||||
))}
|
||||
<CommandSeparator className="sticky bottom-10" />
|
||||
<CommandGroup className="bg-popover sticky bottom-0">
|
||||
<CommandItem>
|
||||
<PlusCircleIcon />
|
||||
Create timezone
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxWithCheckbox({ frameworks }: { frameworks: Framework[] }) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [selectedFrameworks, setSelectedFrameworks] = React.useState<
|
||||
Framework[]
|
||||
>([])
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="w-fit min-w-[280px] justify-between"
|
||||
>
|
||||
{selectedFrameworks.length > 0
|
||||
? selectedFrameworks.map((framework) => framework.label).join(", ")
|
||||
: "Select frameworks (multi-select)..."}
|
||||
<ChevronsUpDown className="text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[300px] p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search framework..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No framework found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{frameworks.map((framework) => (
|
||||
<CommandItem
|
||||
key={framework.value}
|
||||
value={framework.value}
|
||||
onSelect={(currentValue) => {
|
||||
setSelectedFrameworks(
|
||||
selectedFrameworks.some((f) => f.value === currentValue)
|
||||
? selectedFrameworks.filter(
|
||||
(f) => f.value !== currentValue
|
||||
)
|
||||
: [...selectedFrameworks, framework]
|
||||
)
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="border-input data-[selected=true]:border-primary data-[selected=true]:bg-primary data-[selected=true]:text-primary-foreground pointer-events-none size-4 shrink-0 rounded-[4px] border transition-all select-none *:[svg]:opacity-0 data-[selected=true]:*:[svg]:opacity-100"
|
||||
data-selected={selectedFrameworks.some(
|
||||
(f) => f.value === framework.value
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="size-3.5 text-current" />
|
||||
</div>
|
||||
{framework.label}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<ComboboxChipsInput placeholder="Add framework..." />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ComboboxValue>
|
||||
</ComboboxChips>
|
||||
<ComboboxContent anchor={anchor}>
|
||||
<ComboboxEmpty>No items found.</ComboboxEmpty>
|
||||
<ComboboxList>
|
||||
{(item) => (
|
||||
<ComboboxItem key={item} value={item}>
|
||||
{item}
|
||||
</ComboboxItem>
|
||||
)}
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,59 +4,64 @@ import { Label } from "@/registry/new-york-v4/ui/label"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverDescription,
|
||||
PopoverHeader,
|
||||
PopoverTitle,
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york-v4/ui/popover"
|
||||
|
||||
export function PopoverDemo() {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline">Open popover</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80" align="start">
|
||||
<div className="grid gap-4">
|
||||
<div className="grid gap-1.5">
|
||||
<h4 className="leading-none font-medium">Dimensions</h4>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Set the dimensions for the layer.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="width">Width</Label>
|
||||
<Input
|
||||
id="width"
|
||||
defaultValue="100%"
|
||||
className="col-span-2 h-8"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="maxWidth">Max. width</Label>
|
||||
<Input
|
||||
id="maxWidth"
|
||||
defaultValue="300px"
|
||||
className="col-span-2 h-8"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="height">Height</Label>
|
||||
<Input
|
||||
id="height"
|
||||
defaultValue="25px"
|
||||
className="col-span-2 h-8"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="maxHeight">Max. height</Label>
|
||||
<Input
|
||||
id="maxHeight"
|
||||
defaultValue="none"
|
||||
className="col-span-2 h-8"
|
||||
/>
|
||||
<div className="flex gap-4">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline">Open popover</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80" align="start">
|
||||
<div className="grid gap-4">
|
||||
<PopoverHeader>
|
||||
<PopoverTitle>Dimensions</PopoverTitle>
|
||||
<PopoverDescription>
|
||||
Set the dimensions for the layer.
|
||||
</PopoverDescription>
|
||||
</PopoverHeader>
|
||||
<div className="grid gap-2">
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="width">Width</Label>
|
||||
<Input
|
||||
id="width"
|
||||
defaultValue="100%"
|
||||
className="col-span-2 h-8"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="maxWidth">Max. width</Label>
|
||||
<Input
|
||||
id="maxWidth"
|
||||
defaultValue="300px"
|
||||
className="col-span-2 h-8"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="height">Height</Label>
|
||||
<Input
|
||||
id="height"
|
||||
defaultValue="25px"
|
||||
className="col-span-2 h-8"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="maxHeight">Max. height</Label>
|
||||
<Input
|
||||
id="maxHeight"
|
||||
defaultValue="none"
|
||||
className="col-span-2 h-8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,6 +47,27 @@ export function SheetDemo() {
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="outline">No Close Button</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent showCloseButton={false}>
|
||||
<SheetHeader>
|
||||
<SheetTitle>Custom Close</SheetTitle>
|
||||
<SheetDescription>
|
||||
This sheet has no default close button. Use the footer buttons
|
||||
instead.
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="flex-1 px-4" />
|
||||
<SheetFooter>
|
||||
<SheetClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</SheetClose>
|
||||
<Button type="submit">Save</Button>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<div className="flex gap-2">
|
||||
{SHEET_SIDES.map((side) => (
|
||||
<Sheet key={side}>
|
||||
|
||||
@@ -4,6 +4,17 @@ import { Switch } from "@/registry/new-york-v4/ui/switch"
|
||||
export function SwitchDemo() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* Sizes. */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch id="switch-demo-sm" size="sm" />
|
||||
<Label htmlFor="switch-demo-sm">Small</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch id="switch-demo-default" />
|
||||
<Label htmlFor="switch-demo-default">Default</Label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch id="switch-demo-airplane-mode" />
|
||||
<Label htmlFor="switch-demo-airplane-mode">Airplane Mode</Label>
|
||||
|
||||
@@ -101,6 +101,45 @@ export function TabsDemo() {
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
{/* Line variant. */}
|
||||
<Tabs defaultValue="preview">
|
||||
<TabsList variant="line">
|
||||
<TabsTrigger value="preview">
|
||||
<AppWindowIcon />
|
||||
Preview
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="code">
|
||||
<CodeIcon />
|
||||
Code
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
{/* Vertical orientation. */}
|
||||
<Tabs defaultValue="preview" orientation="vertical">
|
||||
<TabsList>
|
||||
<TabsTrigger value="preview">
|
||||
<AppWindowIcon />
|
||||
Preview
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="code">
|
||||
<CodeIcon />
|
||||
Code
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
{/* Vertical orientation with line variant. */}
|
||||
<Tabs defaultValue="preview" orientation="vertical">
|
||||
<TabsList variant="line">
|
||||
<TabsTrigger value="preview">
|
||||
<AppWindowIcon />
|
||||
Preview
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="code">
|
||||
<CodeIcon />
|
||||
Code
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,6 +57,11 @@ export const metadata: Metadata = {
|
||||
apple: "/apple-touch-icon.png",
|
||||
},
|
||||
manifest: `${siteConfig.url}/site.webmanifest`,
|
||||
alternates: {
|
||||
types: {
|
||||
"application/rss+xml": `${siteConfig.url}/rss.xml`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
|
||||
44
apps/v4/app/rss.xml/route.ts
Normal file
44
apps/v4/app/rss.xml/route.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { getChangelogPages, type ChangelogPageData } from "@/lib/changelog"
|
||||
import { siteConfig } from "@/lib/config"
|
||||
|
||||
export const revalidate = false
|
||||
|
||||
export async function GET() {
|
||||
const pages = getChangelogPages()
|
||||
|
||||
const items = pages
|
||||
.map((page) => {
|
||||
const data = page.data as ChangelogPageData
|
||||
const date = page.date?.toUTCString() ?? new Date().toUTCString()
|
||||
const link = `${siteConfig.url}/docs/${page.slugs.join("/")}`
|
||||
|
||||
return ` <item>
|
||||
<title><![CDATA[${data.title}]]></title>
|
||||
<link>${link}</link>
|
||||
<guid>${link}</guid>
|
||||
<description><![CDATA[${data.description || ""}]]></description>
|
||||
<pubDate>${date}</pubDate>
|
||||
</item>`
|
||||
})
|
||||
.join("\n")
|
||||
|
||||
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>${siteConfig.name} Changelog</title>
|
||||
<link>${siteConfig.url}</link>
|
||||
<description>${siteConfig.description}</description>
|
||||
<language>en-us</language>
|
||||
<atom:link href="${siteConfig.url}/rss.xml" rel="self" type="application/rss+xml"/>
|
||||
${items}
|
||||
</channel>
|
||||
</rss>`
|
||||
|
||||
return new NextResponse(xml, {
|
||||
headers: {
|
||||
"Content-Type": "application/rss+xml; charset=utf-8",
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
Dialog,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
@@ -534,10 +535,7 @@ function DialogContent({
|
||||
}) {
|
||||
return (
|
||||
<DialogPortal data-slot="dialog-portal">
|
||||
<DialogPrimitive.Overlay
|
||||
data-slot="dialog-overlay"
|
||||
className="fixed inset-0 z-50 bg-black/50"
|
||||
/>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Button } from "@/examples/base/ui/button"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
|
||||
export function ComponentPreviewTabs({
|
||||
className,
|
||||
@@ -13,6 +13,7 @@ export function ComponentPreviewTabs({
|
||||
chromeLessOnMobile = false,
|
||||
component,
|
||||
source,
|
||||
sourcePreview,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & {
|
||||
previewClassName?: string
|
||||
@@ -21,11 +22,13 @@ export function ComponentPreviewTabs({
|
||||
chromeLessOnMobile?: boolean
|
||||
component: React.ReactNode
|
||||
source: React.ReactNode
|
||||
sourcePreview?: React.ReactNode
|
||||
}) {
|
||||
const [isMobileCodeVisible, setIsMobileCodeVisible] = React.useState(false)
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="component-preview"
|
||||
className={cn(
|
||||
"group relative mt-4 mb-12 flex flex-col gap-2 overflow-hidden rounded-xl border",
|
||||
className
|
||||
@@ -47,29 +50,33 @@ export function ComponentPreviewTabs({
|
||||
<div
|
||||
data-slot="code"
|
||||
data-mobile-code-visible={isMobileCodeVisible}
|
||||
className="relative overflow-hidden data-[mobile-code-visible=false]:max-h-24 md:max-h-none data-[mobile-code-visible=false]:md:max-h-none [&_[data-rehype-pretty-code-figure]]:!m-0 [&_[data-rehype-pretty-code-figure]]:rounded-t-none [&_[data-rehype-pretty-code-figure]]:border-t [&_pre]:max-h-72"
|
||||
className="relative overflow-hidden [&_[data-rehype-pretty-code-figure]]:!m-0 [&_[data-rehype-pretty-code-figure]]:rounded-t-none [&_[data-rehype-pretty-code-figure]]:border-t [&_pre]:max-h-72"
|
||||
>
|
||||
{source}
|
||||
{!isMobileCodeVisible && (
|
||||
<div className="absolute inset-0 flex items-center justify-center md:hidden">
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(to top, var(--color-code), color-mix(in oklab, var(--color-code) 60%, transparent), transparent)",
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="bg-background text-foreground dark:bg-background dark:text-foreground relative z-10"
|
||||
onClick={() => {
|
||||
setIsMobileCodeVisible(true)
|
||||
}}
|
||||
>
|
||||
View Code
|
||||
</Button>
|
||||
{isMobileCodeVisible ? (
|
||||
source
|
||||
) : (
|
||||
<div className="relative">
|
||||
{sourcePreview}
|
||||
<div className="absolute inset-0 flex items-center justify-center pb-4">
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(to top, var(--color-code), color-mix(in oklab, var(--color-code) 60%, transparent), transparent)",
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="bg-background text-foreground dark:bg-background dark:text-foreground hover:bg-muted dark:hover:bg-muted relative z-10"
|
||||
onClick={() => {
|
||||
setIsMobileCodeVisible(true)
|
||||
}}
|
||||
>
|
||||
View Code
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -77,6 +77,14 @@ export function ComponentPreview({
|
||||
styleName={styleName}
|
||||
/>
|
||||
}
|
||||
sourcePreview={
|
||||
<ComponentSource
|
||||
name={name}
|
||||
collapsible={false}
|
||||
styleName={styleName}
|
||||
maxLines={3}
|
||||
/>
|
||||
}
|
||||
chromeLessOnMobile={chromeLessOnMobile}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -18,6 +18,7 @@ export async function ComponentSource({
|
||||
collapsible = true,
|
||||
className,
|
||||
styleName = "new-york-v4",
|
||||
maxLines,
|
||||
}: React.ComponentProps<"div"> & {
|
||||
name?: string
|
||||
src?: string
|
||||
@@ -25,6 +26,7 @@ export async function ComponentSource({
|
||||
language?: string
|
||||
collapsible?: boolean
|
||||
styleName?: string
|
||||
maxLines?: number
|
||||
}) {
|
||||
if (!name && !src) {
|
||||
return null
|
||||
@@ -51,6 +53,11 @@ export async function ComponentSource({
|
||||
code = await formatCode(code, styleName)
|
||||
code = code.replaceAll("/* eslint-disable react/no-children-prop */\n", "")
|
||||
|
||||
// Truncate code if maxLines is set.
|
||||
if (maxLines) {
|
||||
code = code.split("\n").slice(0, maxLines).join("\n")
|
||||
}
|
||||
|
||||
const lang = language ?? title?.split(".").pop() ?? "tsx"
|
||||
const highlightedCode = await highlightCode(code, lang)
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ const TOP_LEVEL_SECTIONS = [
|
||||
href: "/docs/changelog",
|
||||
},
|
||||
]
|
||||
const EXCLUDED_SECTIONS = ["installation", "dark-mode"]
|
||||
const EXCLUDED_SECTIONS = ["installation", "dark-mode", "changelog"]
|
||||
const EXCLUDED_PAGES = ["/docs", "/docs/changelog"]
|
||||
|
||||
export function DocsSidebar({
|
||||
|
||||
@@ -2,7 +2,7 @@ import { siteConfig } from "@/lib/config"
|
||||
|
||||
export function SiteFooter() {
|
||||
return (
|
||||
<footer className="group-has-[.section-soft]/body:bg-surface/40 3xl:fixed:bg-transparent group-has-[.docs-nav]/body:pb-20 group-has-[[data-slot=designer]]/body:hidden group-has-[[data-slot=docs]]/body:hidden group-has-[.docs-nav]/body:sm:pb-0 dark:bg-transparent dark:group-has-[.section-soft]/body:bg-surface/40">
|
||||
<footer className="group-has-[.section-soft]/body:bg-surface/40 3xl:fixed:bg-transparent dark:group-has-[.section-soft]/body:bg-surface/40 group-has-[.docs-nav]/body:pb-20 group-has-[[data-slot=designer]]/body:hidden group-has-[[data-slot=docs]]/body:hidden group-has-[.docs-nav]/body:sm:pb-0 dark:bg-transparent">
|
||||
<div className="container-wrapper px-4 xl:px-6">
|
||||
<div className="flex h-(--footer-height) items-center justify-between">
|
||||
<div className="text-muted-foreground w-full px-1 text-center text-xs leading-loose sm:text-sm">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,8 @@ description: Every component recreated in Figma. With customizable props, typogr
|
||||
|
||||
## Free
|
||||
|
||||
- [Obra shadcn/ui](https://www.figma.com/community/file/1514746685758799870/obra-shadcn-ui) by [Obra Studio](https://obra.studio/) - Carefully crafted kit designed in the philosophy of shadcn, tracks v4, MIT licensed
|
||||
- [Obra shadcn/ui](https://www.figma.com/community/file/1514746685758799870/obra-shadcn-ui) by [Obra Studio](https://obra.studio/) - Carefully crafted shadcn/ui kit, MIT licensed, maintained by team of designers, with free design to code plugin
|
||||
- [shadcn/ui components](https://www.figma.com/community/file/1342715840824755935) by [Sitsiilia Bergmann](https://x.com/sitsiilia) - A well-structured component library aligned with the shadcn component system, regularly maintained.
|
||||
- [shadcn/ui design system](https://www.figma.com/community/file/1203061493325953101) by [Pietro Schirano](https://twitter.com/skirano) - A design companion for shadcn/ui. Each component was painstakingly crafted to perfectly match the code implementation.
|
||||
|
||||
## Paid
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"javascript",
|
||||
"blocks",
|
||||
"figma",
|
||||
"changelog",
|
||||
"[Changelog](/docs/changelog)",
|
||||
"[llms.txt](/llms.txt)",
|
||||
"legacy"
|
||||
]
|
||||
|
||||
478
apps/v4/content/docs/changelog/2023-06-new-cli.mdx
Normal file
478
apps/v4/content/docs/changelog/2023-06-new-cli.mdx
Normal file
@@ -0,0 +1,478 @@
|
||||
---
|
||||
title: June 2023 - New CLI, Styles and more
|
||||
description: Complete CLI rewrite with new styles, theming options, and more.
|
||||
date: 2023-06-22
|
||||
---
|
||||
|
||||
I have a lot of updates to share with you today:
|
||||
|
||||
- [**New CLI**](#new-cli) - Rewrote the CLI from scratch. You can now add components, dependencies and configure import paths.
|
||||
- [**Theming**](#theming-with-css-variables-or-tailwind-colors) - Choose between using CSS variables or Tailwind CSS utility classes for theming.
|
||||
- [**Base color**](#base-color) - Configure the base color for your project. This will be used to generate the default color palette for your components.
|
||||
- [**React Server Components**](#react-server-components) - Opt out of using React Server Components. The CLI will automatically append or remove the `use client` directive.
|
||||
- [**Styles**](#styles) - Introducing a new concept called _Style_. A style comes with its own set of components, animations, icons and more.
|
||||
- [**Exit animations**](#exit-animations) - Added exit animations to all components.
|
||||
- [**Other updates**](#other-updates) - New `icon` button size, updated `sheet` component and more.
|
||||
- [**Updating your project**](#updating-your-project) - How to update your project to get the latest changes.
|
||||
|
||||
---
|
||||
|
||||
### New CLI
|
||||
|
||||
I've been working on a new CLI for the past few weeks. It's a complete rewrite. It comes with a lot of new features and improvements.
|
||||
|
||||
### `init`
|
||||
|
||||
```bash
|
||||
npx shadcn@latest init
|
||||
```
|
||||
|
||||
When you run the `init` command, you will be asked a few questions to configure `components.json`:
|
||||
|
||||
```txt showLineNumbers
|
||||
Which style would you like to use? › Default
|
||||
Which color would you like to use as base color? › Slate
|
||||
Where is your global CSS file? › › app/globals.css
|
||||
Do you want to use CSS variables for colors? › no / yes
|
||||
Where is your tailwind.config.js located? › tailwind.config.js
|
||||
Configure the import alias for components: › @/components
|
||||
Configure the import alias for utils: › @/lib/utils
|
||||
Are you using React Server Components? › no / yes
|
||||
```
|
||||
|
||||
This file contains all the information about your components: where to install them, the import paths, how they are styled...etc.
|
||||
|
||||
You can use this file to change the import path of a component, set a baseColor or change the styling method.
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"style": "default",
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/app/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true
|
||||
},
|
||||
"rsc": false,
|
||||
"aliases": {
|
||||
"utils": "~/lib/utils",
|
||||
"components": "~/components"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This means you can now use the CLI with any directory structure including `src` and `app` directories.
|
||||
|
||||
### `add`
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add
|
||||
```
|
||||
|
||||
The `add` command is now much more capable. You can now add UI components but also import more complex components (coming soon).
|
||||
|
||||
The CLI will automatically resolve all components and dependencies, format them based on your custom config and add them to your project.
|
||||
|
||||
### `diff` (experimental)
|
||||
|
||||
```bash
|
||||
npx shadcn diff
|
||||
```
|
||||
|
||||
We're also introducing a new `diff` command to help you keep track of upstream updates.
|
||||
|
||||
You can use this command to see what has changed in the upstream repository and update your project accordingly.
|
||||
|
||||
Run the `diff` command to get a list of components that have updates available:
|
||||
|
||||
```bash
|
||||
npx shadcn diff
|
||||
```
|
||||
|
||||
```txt
|
||||
The following components have updates available:
|
||||
- button
|
||||
- /path/to/my-app/components/ui/button.tsx
|
||||
- toast
|
||||
- /path/to/my-app/components/ui/use-toast.ts
|
||||
- /path/to/my-app/components/ui/toaster.tsx
|
||||
```
|
||||
|
||||
Then run `diff [component]` to see the changes:
|
||||
|
||||
```bash
|
||||
npx shadcn diff alert
|
||||
```
|
||||
|
||||
```diff /pl-12/
|
||||
const alertVariants = cva(
|
||||
- "relative w-full rounded-lg border",
|
||||
+ "relative w-full pl-12 rounded-lg border"
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Theming with CSS Variables or Tailwind Colors
|
||||
|
||||
You can choose between using CSS variables or Tailwind CSS utility classes for theming.
|
||||
|
||||
When you add new components, the CLI will automatically use the correct theming methods based on your `components.json` configuration.
|
||||
|
||||
#### Utility classes
|
||||
|
||||
```tsx /bg-zinc-950/ /text-zinc-50/ /dark:bg-white/ /dark:text-zinc-950/
|
||||
<div className="bg-zinc-950 dark:bg-white" />
|
||||
```
|
||||
|
||||
To use utility classes for theming set `tailwind.cssVariables` to `false` in your `components.json` file.
|
||||
|
||||
```json {6} title="components.json" showLineNumbers
|
||||
{
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### CSS Variables
|
||||
|
||||
```tsx /bg-background/ /text-foreground/
|
||||
<div className="bg-background text-foreground" />
|
||||
```
|
||||
|
||||
To use CSS variables classes for theming set `tailwind.cssVariables` to `true` in your `components.json` file.
|
||||
|
||||
```json {6} title="components.json" showLineNumbers
|
||||
{
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Base color
|
||||
|
||||
You can now configure the base color for your project. This will be used to generate the default color palette for your components.
|
||||
|
||||
```json {5} title="components.json" showLineNumbers
|
||||
{
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Choose between `gray`, `neutral`, `slate`, `stone` or `zinc`.
|
||||
|
||||
If you have `cssVariables` set to `true`, we will set the base colors as CSS variables in your `globals.css` file. If you have `cssVariables` set to `false`, we will inline the Tailwind CSS utility classes in your components.
|
||||
|
||||
---
|
||||
|
||||
### React Server Components
|
||||
|
||||
If you're using a framework that does not support React Server Components, you can now opt out by setting `rsc` to `false`. We will automatically append or remove the `use client` directive when adding components.
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"rsc": false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Styles
|
||||
|
||||
We are introducing a new concept called _Style_.
|
||||
|
||||
_You can think of style as the visual foundation: shapes, icons, animations & typography._ A style comes with its own set of components, animations, icons and more.
|
||||
|
||||
We are shipping two styles: `default` and `new-york` (with more coming soon).
|
||||
|
||||
<Image
|
||||
src="/images/style.jpg"
|
||||
width="716"
|
||||
height="402"
|
||||
alt="Default vs New York style"
|
||||
className="mt-6 overflow-hidden rounded-lg border"
|
||||
/>
|
||||
|
||||
The `default` style is the one you are used to. It's the one we've been using since the beginning of this project. It uses `lucide-react` for icons and `tailwindcss-animate` for animations.
|
||||
|
||||
The `new-york` style is a new style. It ships with smaller buttons, cards with shadows and a new set of icons from [Radix Icons](https://icons.radix-ui.com).
|
||||
|
||||
When you run the `init` command, you will be asked which style you would like to use. This is saved in your `components.json` file.
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"style": "new-york"
|
||||
}
|
||||
```
|
||||
|
||||
### Theming
|
||||
|
||||
Start with a style as the base then theme using CSS variables or Tailwind CSS utility classes to completely change the look of your components.
|
||||
|
||||
<Image
|
||||
src="/images/style-with-theming.jpg"
|
||||
width="716"
|
||||
height="402"
|
||||
alt="Style with theming"
|
||||
className="mt-6 overflow-hidden rounded-lg border"
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
### Exit animations
|
||||
|
||||
I added exit animations to all components. Click on the combobox below to see the subtle exit animation.
|
||||
|
||||
<ComponentPreview name="combobox-demo" className="[&_.preview]:items-start" />
|
||||
|
||||
The animations can be customized using utility classes.
|
||||
|
||||
---
|
||||
|
||||
### Other updates
|
||||
|
||||
### Button
|
||||
|
||||
- Added a new button size `icon`:
|
||||
|
||||
<ComponentPreview name="button-icon" />
|
||||
|
||||
### Sheet
|
||||
|
||||
- Renamed `position` to `side` to match the other elements.
|
||||
|
||||
<ComponentPreview name="sheet-side" />
|
||||
|
||||
- Removed the `size` props. Use `className="w-[200px] md:w-[450px]"` for responsive sizing.
|
||||
|
||||
---
|
||||
|
||||
### Updating your project
|
||||
|
||||
Since we follow a copy and paste approach, you will need to manually update your project to get the latest changes.
|
||||
|
||||
<Callout className="mt-4">
|
||||
Note: we are working on a [`diff`](#diff-experimental) command to help you
|
||||
keep track of upstream updates.
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
|
||||
### Add `components.json`
|
||||
|
||||
Creating a `components.json` file at the root:
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"style": "default",
|
||||
"rsc": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Update the values for `tailwind.css` and `aliases` to match your project structure.
|
||||
|
||||
### Button
|
||||
|
||||
Add the `icon` size to the `buttonVariants`:
|
||||
|
||||
```tsx {7} title="components/ui/button.tsx" showLineNumbers
|
||||
const buttonVariants = cva({
|
||||
variants: {
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Sheet
|
||||
|
||||
1. Replace the content of `sheet.tsx` with the following:
|
||||
|
||||
```tsx title="components/ui/sheet.tsx" showLineNumbers
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Sheet = SheetPrimitive.Root
|
||||
|
||||
const SheetTrigger = SheetPrimitive.Trigger
|
||||
|
||||
const SheetClose = SheetPrimitive.Close
|
||||
|
||||
const SheetPortal = ({
|
||||
className,
|
||||
...props
|
||||
}: SheetPrimitive.DialogPortalProps) => (
|
||||
<SheetPrimitive.Portal className={cn(className)} {...props} />
|
||||
)
|
||||
SheetPortal.displayName = SheetPrimitive.Portal.displayName
|
||||
|
||||
const SheetOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Overlay
|
||||
className={cn(
|
||||
"bg-background/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 backdrop-blur-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
))
|
||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
||||
|
||||
const sheetVariants = cva(
|
||||
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||
{
|
||||
variants: {
|
||||
side: {
|
||||
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
||||
bottom:
|
||||
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
||||
right:
|
||||
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
side: "right",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
interface SheetContentProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||
VariantProps<typeof sheetVariants> {}
|
||||
|
||||
const SheetContent = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||
SheetContentProps
|
||||
>(({ side = "right", className, children, ...props }, ref) => (
|
||||
<SheetPortal>
|
||||
<SheetOverlay />
|
||||
<SheetPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(sheetVariants({ side }), className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
))
|
||||
SheetContent.displayName = SheetPrimitive.Content.displayName
|
||||
|
||||
const SheetHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-2 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
SheetHeader.displayName = "SheetHeader"
|
||||
|
||||
const SheetFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
SheetFooter.displayName = "SheetFooter"
|
||||
|
||||
const SheetTitle = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-foreground text-lg font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
||||
|
||||
const SheetDescription = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetTrigger,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
}
|
||||
```
|
||||
|
||||
2. Rename `position` to `side`
|
||||
|
||||
```diff /position/ /side/
|
||||
- <Sheet position="right" />
|
||||
+ <Sheet side="right" />
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
### Thank you
|
||||
|
||||
I'd like to thank everyone who has been using this project, providing feedback and contributing to it. I really appreciate it. Thank you.
|
||||
45
apps/v4/content/docs/changelog/2023-07-javascript.mdx
Normal file
45
apps/v4/content/docs/changelog/2023-07-javascript.mdx
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: July 2023 - JavaScript
|
||||
description: JavaScript version of components available via the CLI.
|
||||
date: 2023-07-04
|
||||
---
|
||||
|
||||
This project and the components are written in TypeScript. **We recommend using TypeScript for your project as well**.
|
||||
|
||||
However we provide a JavaScript version of the components, available via the [cli](/docs/cli).
|
||||
|
||||
```txt
|
||||
Would you like to use TypeScript (recommended)? no
|
||||
```
|
||||
|
||||
To opt-out of TypeScript, you can use the `tsx` flag in your `components.json` file.
|
||||
|
||||
```json {10} title="components.json" showLineNumbers
|
||||
{
|
||||
"style": "default",
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/app/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true
|
||||
},
|
||||
"rsc": false,
|
||||
"tsx": false,
|
||||
"aliases": {
|
||||
"utils": "~/lib/utils",
|
||||
"components": "~/components"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To configure import aliases, you can use the following `jsconfig.json`:
|
||||
|
||||
```json {4} title="jsconfig.json" showLineNumbers
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
68
apps/v4/content/docs/changelog/2023-12-new-components.mdx
Normal file
68
apps/v4/content/docs/changelog/2023-12-new-components.mdx
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: December 2023 - New Components
|
||||
description: Carousel, Drawer, Pagination, Resizable, Sonner, and CLI updates.
|
||||
date: 2023-12-22
|
||||
---
|
||||
|
||||
We've added new components to shadcn/ui and made a lot of improvements to the CLI.
|
||||
|
||||
Here's a quick overview of what's new:
|
||||
|
||||
- [**Carousel**](#carousel) - A carousel component with motion, swipe gestures and keyboard support.
|
||||
- [**Drawer**](#drawer) - A drawer component that looks amazing on mobile.
|
||||
- [**Pagination**](#pagination) - A pagination component with page navigation, previous and next buttons.
|
||||
- [**Resizable**](#resizable) - A resizable component for building resizable panel groups and layouts.
|
||||
- [**Sonner**](#sonner) - The last toast component you'll ever need.
|
||||
- [**CLI updates**](#cli-updates) - Support for custom **Tailwind prefix** and `tailwind.config.ts`.
|
||||
|
||||
### Carousel
|
||||
|
||||
We've added a fully featured carousel component with motion, swipe gestures and keyboard support. Built on top of [Embla Carousel](https://www.embla-carousel.com).
|
||||
|
||||
It has support for infinite looping, autoplay, vertical orientation, and more.
|
||||
|
||||
<ComponentPreview name="carousel-demo" />
|
||||
|
||||
### Drawer
|
||||
|
||||
Oh the drawer component. Built on top of [Vaul](https://github.com/emilkowalski/vaul) by [emilkowalski](https://twitter.com/emilkowalski).
|
||||
|
||||
Try opening the following drawer on mobile. It looks amazing!
|
||||
|
||||
<ComponentPreview name="drawer-demo" />
|
||||
|
||||
### Pagination
|
||||
|
||||
We've added a pagination component with page navigation, previous and next buttons. Simple, flexible and works with your framework's `<Link />` component.
|
||||
|
||||
<ComponentPreview name="pagination-demo" />
|
||||
|
||||
### Resizable
|
||||
|
||||
Build resizable panel groups and layouts with this `<Resizable />` component.
|
||||
|
||||
<ComponentPreview name="resizable-demo-with-handle" />
|
||||
|
||||
`<Resizable />` is built using [react-resizable-panels](https://github.com/bvaughn/react-resizable-panels) by [bvaughn](https://github.com/bvaughn). It has support for mouse, touch and keyboard.
|
||||
|
||||
### Sonner
|
||||
|
||||
Another one by [emilkowalski](https://twitter.com/emilkowalski). The last toast component you'll ever need. Sonner is now availabe in shadcn/ui.
|
||||
|
||||
<ComponentPreview name="sonner-demo" />
|
||||
|
||||
### CLI updates
|
||||
|
||||
This has been one of the most requested features. You can now configure a custom Tailwind prefix and the cli will automatically prefix your utility classes when adding components.
|
||||
|
||||
This means you can now easily add shadcn/ui components to existing projects like Docusaurus, Nextra...etc. A drop-in for your existing design system with no conflict.
|
||||
|
||||
```tsx /tw-/
|
||||
<AlertDialog className="tw-grid tw-gap-4 tw-border tw-bg-background tw-shadow-lg" />
|
||||
```
|
||||
|
||||
It works with `cn`, `cva` and CSS variables.
|
||||
|
||||
The cli can now also detect `tailwind.config.ts` and add the TypeScript version of the config for you.
|
||||
|
||||
That's it. Happy Holidays.
|
||||
99
apps/v4/content/docs/changelog/2024-03-blocks.mdx
Normal file
99
apps/v4/content/docs/changelog/2024-03-blocks.mdx
Normal file
@@ -0,0 +1,99 @@
|
||||
---
|
||||
title: March 2024 - Introducing Blocks
|
||||
description: Ready-made components for dashboards and authentication pages.
|
||||
date: 2024-03-22
|
||||
---
|
||||
|
||||
One of the most requested features since launch has been layouts: admin dashboards with sidebar, marketing page sections, cards and more.
|
||||
|
||||
**Today, we're launching [**Blocks**](/blocks)**.
|
||||
|
||||
<a href="/blocks">
|
||||
<Image
|
||||
src="/images/dashboard-1.jpg"
|
||||
width="716"
|
||||
height="430"
|
||||
alt="Admin dashboard"
|
||||
className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/images/dashboard-1-dark.jpg"
|
||||
width="716"
|
||||
height="430"
|
||||
alt="Admin dashboard"
|
||||
className="mt-6 hidden w-full overflow-hidden rounded-lg border shadow-sm dark:block"
|
||||
/>
|
||||
<span className="sr-only">View the blocks library</span>
|
||||
</a>
|
||||
|
||||
Blocks are ready-made components that you can use to build your apps. They are fully responsive, accessible, and composable, meaning they are built using the same principles as the rest of the components in shadcn/ui.
|
||||
|
||||
We're starting with dashboard layouts and authentication pages, with plans to add more blocks in the coming weeks.
|
||||
|
||||
### Open Source
|
||||
|
||||
Blocks are open source. You can find the source on GitHub. Use them in your projects, customize them and contribute back.
|
||||
|
||||
<a href="/blocks">
|
||||
<Image
|
||||
src="/images/dashboard-2.jpg"
|
||||
width="716"
|
||||
height="420"
|
||||
alt="AI Playground"
|
||||
className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/images/dashboard-2-dark.jpg"
|
||||
width="716"
|
||||
height="420"
|
||||
alt="AI Playground"
|
||||
className="mt-6 hidden w-full overflow-hidden rounded-lg border shadow-sm dark:block"
|
||||
/>
|
||||
<span className="sr-only">View the blocks library</span>
|
||||
</a>
|
||||
|
||||
### Request a Block
|
||||
|
||||
We're also introducing a "Request a Block" feature. If there's a specific block you'd like to see, simply create a request on GitHub and the community can upvote and build it.
|
||||
|
||||
<a href="/blocks">
|
||||
<Image
|
||||
src="/images/dashboard-3.jpg"
|
||||
width="716"
|
||||
height="420"
|
||||
alt="Settings Page"
|
||||
className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/images/dashboard-3-dark.jpg"
|
||||
width="716"
|
||||
height="420"
|
||||
alt="Settings Page"
|
||||
className="mt-6 hidden w-full overflow-hidden rounded-lg border shadow-sm dark:block"
|
||||
/>
|
||||
<span className="sr-only">View the blocks library</span>
|
||||
</a>
|
||||
|
||||
### v0
|
||||
|
||||
If you have a [v0](https://v0.dev) account, you can use the **Edit in v0** feature to open the code on v0 for prompting and further generation.
|
||||
|
||||
<div className="bg-background mt-6 flex aspect-video w-full items-center justify-center overflow-hidden rounded-lg border shadow-sm">
|
||||
<svg
|
||||
viewBox="0 0 40 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="text-foreground h-40 w-40"
|
||||
>
|
||||
<path
|
||||
d="M23.3919 0H32.9188C36.7819 0 39.9136 3.13165 39.9136 6.99475V16.0805H36.0006V6.99475C36.0006 6.90167 35.9969 6.80925 35.9898 6.71766L26.4628 16.079C26.4949 16.08 26.5272 16.0805 26.5595 16.0805H36.0006V19.7762H26.5595C22.6964 19.7762 19.4788 16.6139 19.4788 12.7508V3.68923H23.3919V12.7508C23.3919 12.9253 23.4054 13.0977 23.4316 13.2668L33.1682 3.6995C33.0861 3.6927 33.003 3.68923 32.9188 3.68923H23.3919V0Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
<path
|
||||
d="M13.7688 19.0956L0 3.68759H5.53933L13.6231 12.7337V3.68759H17.7535V17.5746C17.7535 19.6705 15.1654 20.6584 13.7688 19.0956Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
That's it. _Looking forward to seeing what you build with Blocks_.
|
||||
25
apps/v4/content/docs/changelog/2024-03-breadcrumb-otp.mdx
Normal file
25
apps/v4/content/docs/changelog/2024-03-breadcrumb-otp.mdx
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: March 2024 - Breadcrumb and Input OTP
|
||||
description: New Breadcrumb and Input OTP components.
|
||||
date: 2024-03-08
|
||||
---
|
||||
|
||||
We've added a new Breadcrumb component and an Input OTP component.
|
||||
|
||||
### Breadcrumb
|
||||
|
||||
An accessible and flexible breadcrumb component. It has support for collapsed items, custom separators, bring-your-own routing `<Link />` and composable with other shadcn/ui components.
|
||||
|
||||
<ComponentPreview name="breadcrumb-demo" />
|
||||
|
||||
[See more examples](/docs/components/breadcrumb)
|
||||
|
||||
### Input OTP
|
||||
|
||||
A fully featured input OTP component. It has support for numeric and alphanumeric codes, custom length, copy-paste and accessible. Input OTP is built on top of [input-otp](https://github.com/guilhermerodz/input-otp) by [@guilherme_rodz](https://twitter.com/guilherme_rodz).
|
||||
|
||||
<ComponentPreview name="input-otp-demo" />
|
||||
|
||||
[Read the docs](/docs/components/input-otp)
|
||||
|
||||
If you have a [v0](https://v0.dev), the new components are available for generation.
|
||||
31
apps/v4/content/docs/changelog/2024-04-lift-mode.mdx
Normal file
31
apps/v4/content/docs/changelog/2024-04-lift-mode.mdx
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: April 2024 - Lift Mode
|
||||
description: A new mode for Blocks to lift smaller components for copy and paste.
|
||||
date: 2024-04-05
|
||||
---
|
||||
|
||||
We're introducing a new mode for [Blocks](/blocks) called **Lift Mode**.
|
||||
|
||||
Enable Lift Mode to automatically "lift" smaller components from a block template for copy and paste.
|
||||
|
||||
<a href="/blocks">
|
||||
<Image
|
||||
src="/images/lift-mode-light.png"
|
||||
width="1432"
|
||||
height="1050"
|
||||
alt="Lift Mode"
|
||||
className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/images/lift-mode-dark.png"
|
||||
width="1432"
|
||||
height="1069"
|
||||
alt="Lift Mode"
|
||||
className="mt-6 hidden w-full overflow-hidden rounded-lg border shadow-sm dark:block"
|
||||
/>
|
||||
<span className="sr-only">View the blocks library</span>
|
||||
</a>
|
||||
|
||||
With Lift Mode, you'll be able to copy the smaller components that make up a block template, like cards, buttons, and forms, and paste them directly into your project.
|
||||
|
||||
Visit the [Blocks](/blocks) page to try it out.
|
||||
45
apps/v4/content/docs/changelog/2024-08-npx-shadcn-init.mdx
Normal file
45
apps/v4/content/docs/changelog/2024-08-npx-shadcn-init.mdx
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: August 2024 - npx shadcn init
|
||||
description: Complete CLI rewrite with support for all major React frameworks.
|
||||
date: 2024-08-31
|
||||
---
|
||||
|
||||
The new CLI is now available. It's a complete rewrite with a lot of new features and improvements. You can now install components, themes, hooks, utils and more using `npx shadcn add`.
|
||||
|
||||
This is a major step towards distributing code that you and your LLMs can access and use.
|
||||
|
||||
1. First up, the cli now has support for all major React framework out of the box. Next.js, Remix, Vite and Laravel. And when you init into a new app, we update your existing Tailwind files instead of overriding.
|
||||
2. A component now ship its own dependencies. Take the accordion for example, it can define its Tailwind keyframes. When you add it to your project, we'll update your tailwind.config.ts file accordingly.
|
||||
3. You can also install remote components using url. `npx shadcn add https://acme.com/registry/navbar.json`.
|
||||
4. We have also improve the init command. It does framework detection and can even init a brand new Next.js app in one command. `npx shadcn init`.
|
||||
5. We have created a new schema that you can use to ship your own component registry. And since it has support for urls, you can even use it to distribute private components.
|
||||
6. And a few more updates like better error handling and monorepo support.
|
||||
|
||||
You can try the new cli today.
|
||||
|
||||
```bash
|
||||
npx shadcn init sidebar-01 login-01
|
||||
```
|
||||
|
||||
### Update Your Project
|
||||
|
||||
To update an existing project to use the new CLI, update your `components.json` file to include import aliases for your **components**, **utils**, **ui**, **lib** and **hooks**.
|
||||
|
||||
```json showLineNumbers {7-13} title="components.json"
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"tailwind": {
|
||||
// ...
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you're using a different import alias prefix eg `~`, replace `@` with your prefix.
|
||||
11
apps/v4/content/docs/changelog/2024-10-react-19.mdx
Normal file
11
apps/v4/content/docs/changelog/2024-10-react-19.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: October 2024 - React 19
|
||||
description: shadcn/ui is now compatible with React 19 and Next.js 15.
|
||||
date: 2024-10-29
|
||||
---
|
||||
|
||||
shadcn/ui is now compatible with React 19 and Next.js 15.
|
||||
|
||||
We published a guide to help you upgrade your project to React 19.
|
||||
|
||||
Read more [here](/docs/react-19).
|
||||
13
apps/v4/content/docs/changelog/2024-10-sidebar.mdx
Normal file
13
apps/v4/content/docs/changelog/2024-10-sidebar.mdx
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: October 2024 - Sidebar
|
||||
description: 25 components to help you build all kinds of sidebars.
|
||||
date: 2024-10-18
|
||||
---
|
||||
|
||||
Introducing sidebar.tsx: 25 components to help you build all kinds of sidebars.
|
||||
|
||||
I don't like building sidebars. So I built 30+ of them. All types. Then simplified the core into sidebar.tsx: a strong foundation to build on top of.
|
||||
|
||||
It works with Next.js, Remix, Vite & Laravel.
|
||||
|
||||
See the announcement [here](https://x.com/shadcn/status/1847359896557408461).
|
||||
13
apps/v4/content/docs/changelog/2024-11-icons.mdx
Normal file
13
apps/v4/content/docs/changelog/2024-11-icons.mdx
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: November 2024 - Icons
|
||||
description: The new-york style now uses Lucide as the default icon set.
|
||||
date: 2024-11-06
|
||||
---
|
||||
|
||||
An update on icons. The new-york style now uses Lucide as the default icon set.
|
||||
|
||||
- New projects will use Lucide by default
|
||||
- No breaking changes for existing projects
|
||||
- Use the CLI to (optionally) migrate primitives to Lucide
|
||||
|
||||
For more info on why we're doing this, see the [thread](https://x.com/shadcn/status/1853902179041702169).
|
||||
18
apps/v4/content/docs/changelog/2024-12-monorepo.mdx
Normal file
18
apps/v4/content/docs/changelog/2024-12-monorepo.mdx
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: December 2024 - Monorepo Support
|
||||
description: New monorepo support in the CLI.
|
||||
date: 2024-12-20
|
||||
---
|
||||
|
||||
Until now, using shadcn/ui in a monorepo was a bit of a pain. You could add
|
||||
components using the CLI, but you had to manage where the components
|
||||
were installed and manually fix import paths.
|
||||
|
||||
With the new monorepo support in the CLI, we've made it a lot easier to use
|
||||
shadcn/ui in a monorepo.
|
||||
|
||||
The CLI now understands the monorepo structure and will install the components,
|
||||
dependencies and registry dependencies to the correct paths and handle imports
|
||||
for you.
|
||||
|
||||
Read more in the [docs](/docs/monorepo).
|
||||
11
apps/v4/content/docs/changelog/2025-01-blocks.mdx
Normal file
11
apps/v4/content/docs/changelog/2025-01-blocks.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: January 2025 - Blocks Community
|
||||
description: Inviting the community to contribute to the blocks library.
|
||||
date: 2025-01-14
|
||||
---
|
||||
|
||||
We are inviting the community to contribute to the blocks library. Share your components and blocks with other developers and help build a library of high-quality, reusable components.
|
||||
|
||||
We'd love to see all types of blocks: applications, marketing, products, and more.
|
||||
|
||||
See the [docs](/docs/blocks) page to get started.
|
||||
13
apps/v4/content/docs/changelog/2025-02-registry-schema.mdx
Normal file
13
apps/v4/content/docs/changelog/2025-02-registry-schema.mdx
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: February 2025 - Updated Registry Schema
|
||||
description: Updated registry schema to support more features.
|
||||
date: 2025-02-06
|
||||
---
|
||||
|
||||
We're updating the registry schema to support more features.
|
||||
|
||||
Define code as a flat JSON file and distribute it via the CLI.
|
||||
|
||||
- Custom styles: bring your own design system, components & tokens
|
||||
- Extend, override, mix & match components from third-party registries and LLMs
|
||||
- Install themes, CSS vars, hooks, animations, and Tailwind layers & utilities
|
||||
22
apps/v4/content/docs/changelog/2025-02-tailwind-v4.mdx
Normal file
22
apps/v4/content/docs/changelog/2025-02-tailwind-v4.mdx
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: February 2025 - Tailwind v4
|
||||
description: First preview of Tailwind v4 and React 19 support.
|
||||
date: 2025-02-19
|
||||
---
|
||||
|
||||
We shipped the first preview of Tailwind v4 and React 19. Ready for you to try out. You can start using it today.
|
||||
|
||||
What's New:
|
||||
|
||||
- The CLI can now initialize projects with Tailwind v4.
|
||||
- Full support for the new @theme directive and @theme inline option.
|
||||
- All components are updated for Tailwind v4 and React 19.
|
||||
- We've removed the forwardRefs and adjusted the types.
|
||||
- Every primitive now has a data-slot attribute for styling.
|
||||
- We've fixed and cleaned up the style of the components.
|
||||
- We're deprecating the toast component in favor of sonner.
|
||||
- Buttons now use the default cursor.
|
||||
- We're deprecating the default style. New projects will use new-york.
|
||||
- HSL colors are now converted to OKLCH.
|
||||
|
||||
Read more in the [docs](/docs/tailwind-v4).
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: March 2025 - Cross-framework Route Support
|
||||
description: The shadcn CLI can now auto-detect your framework and adapts routes for you.
|
||||
date: 2025-04-09
|
||||
---
|
||||
|
||||
The shadcn CLI can now auto-detect your framework and adapts routes for you.
|
||||
|
||||
Works with all frameworks including Laravel, Vite and React Router.
|
||||
17
apps/v4/content/docs/changelog/2025-04-mcp.mdx
Normal file
17
apps/v4/content/docs/changelog/2025-04-mcp.mdx
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: April 2025 - MCP
|
||||
description: Zero-config MCP support for shadcn/ui registry.
|
||||
date: 2025-04-30
|
||||
---
|
||||
|
||||
We're working on zero-config MCP support for shadcn/ui registry. One command `npx shadcn registry:mcp` to make any registry mcp-compatible.
|
||||
|
||||
<Image
|
||||
src="/images/mcp.jpeg"
|
||||
width="1432"
|
||||
height="1050"
|
||||
alt="Lift Mode"
|
||||
className="mt-6 w-full overflow-hidden rounded-lg border"
|
||||
/>
|
||||
|
||||
Learn more in the [thread here](https://x.com/shadcn/status/1917597228513853603).
|
||||
11
apps/v4/content/docs/changelog/2025-04-shadcn-2-5.mdx
Normal file
11
apps/v4/content/docs/changelog/2025-04-shadcn-2-5.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: March 2025 - shadcn 2.5.0
|
||||
description: Resolve anywhere - registries can now place files anywhere in an app.
|
||||
date: 2025-04-26
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
On install, we track all files and perform a multi-pass resolution to correctly handle imports and aliases. It's fast.
|
||||
11
apps/v4/content/docs/changelog/2025-05-new-site.mdx
Normal file
11
apps/v4/content/docs/changelog/2025-05-new-site.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: May 2025 - New Site
|
||||
description: Upgraded ui.shadcn.com to Next.js 15.3 and Tailwind v4.
|
||||
date: 2025-05-30
|
||||
---
|
||||
|
||||
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 also made some minor design updates to make the site faster and easier to navigate.
|
||||
|
||||
This upgrade unlocks a lot of new features that we're working on. More to come.
|
||||
15
apps/v4/content/docs/changelog/2025-06-calendar.mdx
Normal file
15
apps/v4/content/docs/changelog/2025-06-calendar.mdx
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: June 2025 - Calendar Component
|
||||
description: Upgraded Calendar component with React DayPicker and 30+ calendar blocks.
|
||||
date: 2025-06-06
|
||||
---
|
||||
|
||||
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).
|
||||
22
apps/v4/content/docs/changelog/2025-06-radix-ui.mdx
Normal file
22
apps/v4/content/docs/changelog/2025-06-radix-ui.mdx
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: June 2025 - radix-ui Migration
|
||||
description: A new command to migrate to the new radix-ui package.
|
||||
date: 2025-06-11
|
||||
---
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: July 2025 - Local File Support
|
||||
description: Initialize projects and add components from local JSON files.
|
||||
date: 2025-07-07
|
||||
---
|
||||
|
||||
The shadcn CLI now supports local files. Initialize projects and add components, themes, hooks, utils and more from local JSON files.
|
||||
|
||||
```bash
|
||||
# Initialize a project from a local file
|
||||
npx shadcn init ./template.json
|
||||
|
||||
# Add a component from a local file
|
||||
npx shadcn add ./block.json
|
||||
```
|
||||
|
||||
This feature enables powerful new workflows:
|
||||
|
||||
- **Zero setup** - No remote registries required
|
||||
- **Faster development** - Test registry items locally before publishing
|
||||
- **Enhanced workflow for agents and MCP** - Generate and run registry items locally
|
||||
- **Private components** - Keep proprietary components local and private.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: July 2025 - Universal Registry Items
|
||||
description: Create registry items that can be distributed to any project.
|
||||
date: 2025-07-11
|
||||
---
|
||||
|
||||
We've added support for universal registry items. This allows you to create registry items that can be distributed to any project i.e. no framework, no components.json, no tailwind, no react required.
|
||||
|
||||
This new registry item type unlocks a lot of new workflows. You can now distribute code, config, rules, docs, anything to any code project.
|
||||
|
||||
See the [docs](/docs/registry/examples) for more details and examples.
|
||||
206
apps/v4/content/docs/changelog/2025-08-cli-3-mcp.mdx
Normal file
206
apps/v4/content/docs/changelog/2025-08-cli-3-mcp.mdx
Normal file
@@ -0,0 +1,206 @@
|
||||
---
|
||||
title: August 2025 - shadcn CLI 3.0 and MCP Server
|
||||
description: Namespaced registries, advanced authentication, new commands and a completely rewritten registry engine.
|
||||
date: 2025-08-27
|
||||
---
|
||||
|
||||
We just shipped shadcn CLI 3.0 with support for namespaced registries, advanced authentication, new commands and a completely rewritten registry engine.
|
||||
|
||||
### What's New
|
||||
|
||||
- [Namespaced Registries](#namespaced-registries) - Install components using `@registry/name` format.
|
||||
- [Private Registries](#private-registries) - Secure your registry with advanced authentication.
|
||||
- [Search & Discovery](#search--discovery) - New commands to find and view code before installing.
|
||||
- [MCP Server](#mcp-server) - MCP server for all registries.
|
||||
- [Faster Everything](#faster-everything) - Completely rewritten registry resolution.
|
||||
- [Improved Error Handling](#improved-error-handling) - Better error messages for users and LLMs.
|
||||
- [Upgrade Guide](#upgrade-guide) - Migration notes for existing users.
|
||||
|
||||
### Namespaced Registries
|
||||
|
||||
The biggest change in 3.0 is namespaced registries. You can now install components from registries: a community registry, your company's private registry or internal registry, using the `@registry/name` format.
|
||||
|
||||
This makes it easier to distribute code across teams and projects.
|
||||
|
||||
Configure registries in your `components.json`:
|
||||
|
||||
```json title="components.json"
|
||||
{
|
||||
"registries": {
|
||||
"@acme": "https://acme.com/r/{name}.json",
|
||||
"@internal": {
|
||||
"url": "https://registry.company.com/{name}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${REGISTRY_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then use the `@registry/name` format to install components:
|
||||
|
||||
```bash
|
||||
npx shadcn add @acme/button @internal/auth-system
|
||||
```
|
||||
|
||||
It's completely decentralized. There's no central registrar. Create any namespace you want and organize components however makes sense for your team.
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"registries": {
|
||||
"@design": "https://registry.company.com/create/{name}.json",
|
||||
"@engineering": "https://registry.company.com/eng/{name}.json",
|
||||
"@marketing": "https://registry.company.com/marketing/{name}.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Components can even depend on resources from different registries. Everything gets resolved and installed automatically from the right sources.
|
||||
|
||||
```json title="registry-item.json" showLineNumbers
|
||||
{
|
||||
"name": "dashboard",
|
||||
"type": "registry:block",
|
||||
"registryDependencies": [
|
||||
"@shadcn/card", // From default registry
|
||||
"@v0/chart", // From v0 registry
|
||||
"@acme/data-table", // From acme registry
|
||||
"@lib/data-fetcher", // Utility library
|
||||
"@ai/analytics-prompt" // AI prompt resource
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Private Registries
|
||||
|
||||
Need to keep your components private? We've got you covered. Configure authentication with tokens, API keys, or custom headers:
|
||||
|
||||
```json title="components.json"
|
||||
{
|
||||
"registries": {
|
||||
"@private": {
|
||||
"url": "https://registry.company.com/{name}.json",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${REGISTRY_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Your private components stay private. Perfect for enterprise teams with proprietary UI libraries.
|
||||
|
||||
We support all major authentication methods: basic auth, bearer token, api key query params and custom headers.
|
||||
|
||||
See the [authentication docs](/docs/registry/authentication) for more details.
|
||||
|
||||
### Search & Discovery
|
||||
|
||||
Three new commands make it easy to find exactly what you need:
|
||||
|
||||
1. View items from the registry before installing
|
||||
|
||||
```bash
|
||||
npx shadcn view @acme/auth-system
|
||||
```
|
||||
|
||||
2. Search items from registries
|
||||
|
||||
```bash
|
||||
npx shadcn search @tweakcn -q "dark"
|
||||
```
|
||||
|
||||
3. List all items from a registry
|
||||
|
||||
```bash
|
||||
npx shadcn list @acme
|
||||
```
|
||||
|
||||
Preview components before installing them. Search across multiple registries. See the code and all dependencies upfront.
|
||||
|
||||
### MCP Server
|
||||
|
||||
<Image
|
||||
src="/images/mcp.jpeg"
|
||||
width="1432"
|
||||
height="1050"
|
||||
alt="Lift Mode"
|
||||
className="mt-6 w-full overflow-hidden rounded-lg border"
|
||||
/>
|
||||
|
||||
Back in April, we [introduced](https://x.com/shadcn/status/1917597228513853603) the first version of the MCP server. Since then, we've taken everything we learned and built a better MCP server.
|
||||
|
||||
Here's what's new:
|
||||
|
||||
- Works with all registries. Zero config
|
||||
- One command to add to your favorite MCP clients
|
||||
- We improved the underlying tools
|
||||
- Better integration with the CLI and registries
|
||||
- Support for multiple registries in the same project
|
||||
|
||||
Add the MCP server to your project:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest mcp init
|
||||
```
|
||||
|
||||
See the [docs](/docs/mcp) for more details.
|
||||
|
||||
### Faster Everything
|
||||
|
||||
We completely rewrote the registry resolution engine from scratch. It's faster, smarter, and handles even the trickiest dependency trees.
|
||||
|
||||
- Up to 3x faster dependency resolution
|
||||
- Smarter file deduplication and merging
|
||||
- Better monorepo support out of the box
|
||||
- Updated `build` command for registry authors
|
||||
|
||||
### Improved Error Handling
|
||||
|
||||
Registry developers can now provide custom error messages to help guide users (and LLMs) when things go wrong. The CLI displays helpful, actionable errors for common issues:
|
||||
|
||||
```txt
|
||||
Unknown registry "@acme". Make sure it is defined in components.json as follows:
|
||||
{
|
||||
"registries": {
|
||||
"@acme": "[URL_TO_REGISTRY]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Missing environment variables? The CLI tells you exactly what's needed:
|
||||
|
||||
```txt
|
||||
Registry "@private" requires the following environment variables:
|
||||
• REGISTRY_TOKEN
|
||||
|
||||
Set the required environment variables to your .env or .env.local file.
|
||||
```
|
||||
|
||||
Registry authors can provide custom error messages in their responses to help users and AI agents understand and fix issues quickly.
|
||||
|
||||
```txt
|
||||
Error:
|
||||
You are not authorized to access the item at http://example.com/r/component.
|
||||
|
||||
Message:
|
||||
[Unauthorized] Your API key has expired. Renew it at https://example.com/api/renew-key.
|
||||
```
|
||||
|
||||
### Upgrade Guide
|
||||
|
||||
Here's the best part: there are no breaking changes for users. Your existing `components.json` works exactly the same. All your installed components work exactly the same.
|
||||
|
||||
For developers, if you're using the programmatic APIs directly, we've deprecated a few functions in favor of better ones:
|
||||
|
||||
- `fetchRegistry` → `getRegistry`
|
||||
- `resolveRegistryTree` → `resolveRegistryItems`
|
||||
- Schema moved from `shadcn/registry` to `shadcn/schema` package
|
||||
|
||||
```diff
|
||||
- import { registryItemSchema } from "shadcn/registry"
|
||||
+ import { registryItemSchema } from "shadcn/schema"
|
||||
```
|
||||
|
||||
That's it. Seriously. Everything else just works.
|
||||
19
apps/v4/content/docs/changelog/2025-09-registry-index.mdx
Normal file
19
apps/v4/content/docs/changelog/2025-09-registry-index.mdx
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: September 2025 - Registry Index
|
||||
description: An index of open source registries that you can install items from.
|
||||
date: 2025-09-02
|
||||
---
|
||||
|
||||
We've created an index of open source registries that you can install items from.
|
||||
|
||||
You can search, view and add items from the registry index without configuring the `.components.json` file.
|
||||
|
||||
They'll be automatically added to your `components.json` file for you.
|
||||
|
||||
```bash
|
||||
npx shadcn add @ai-elements/prompt-input
|
||||
```
|
||||
|
||||
The full list of registries is available at [https://ui.shadcn.com/r/registries.json](https://ui.shadcn.com/r/registries.json).
|
||||
|
||||
To add a registry to the index, submit a PR to the `shadcn/ui` repository. See the [registry index documentation](/docs/registry/registry-index) for more details.
|
||||
399
apps/v4/content/docs/changelog/2025-10-new-components.mdx
Normal file
399
apps/v4/content/docs/changelog/2025-10-new-components.mdx
Normal file
@@ -0,0 +1,399 @@
|
||||
---
|
||||
title: October 2025 - New Components
|
||||
description: Spinner, Kbd, Button Group, Input Group, Field, Item, and Empty components.
|
||||
date: 2025-10-03
|
||||
---
|
||||
|
||||
For this round of components, I looked at what we build every day, the boring stuff we rebuild over and over, and made reusable abstractions you can actually use.
|
||||
|
||||
**These components work with every component library, Radix, Base UI, React Aria, you name it. Copy and paste to your projects.**
|
||||
|
||||
- [Spinner](#spinner): An indicator to show a loading state.
|
||||
- [Kbd](#kbd): Display a keyboard key or group of keys.
|
||||
- [Button Group](#button-group): A group of buttons for actions and split buttons.
|
||||
- [Input Group](#input-group): Input with icons, buttons, labels and more.
|
||||
- [Field](#field): One component. All your forms.
|
||||
- [Item](#item): Display lists of items, cards, and more.
|
||||
- [Empty](#empty): Use this one for empty states.
|
||||
|
||||
### Spinner
|
||||
|
||||
Okay let's start with the easiest ones: **Spinner** and **Kbd**. Pretty basic. We all know what they do.
|
||||
|
||||
Here's how you render a spinner:
|
||||
|
||||
```tsx
|
||||
import { Spinner } from "@/components/ui/spinner"
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Spinner />
|
||||
```
|
||||
|
||||
Here's what it looks like:
|
||||
|
||||
<ComponentPreview
|
||||
name="spinner-basic"
|
||||
|
||||
/>
|
||||
|
||||
Here's what it looks like in a button:
|
||||
|
||||
<ComponentPreview
|
||||
name="spinner-button"
|
||||
|
||||
/>
|
||||
|
||||
You can edit the code and replace it with your own spinner.
|
||||
|
||||
<ComponentPreview
|
||||
name="spinner-custom"
|
||||
|
||||
/>
|
||||
|
||||
### Kbd
|
||||
|
||||
Kbd is a component that renders a keyboard key.
|
||||
|
||||
```tsx
|
||||
import { Kbd, KbdGroup } from "@/components/ui/kbd"
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Kbd>Ctrl</Kbd>
|
||||
```
|
||||
|
||||
Use `KbdGroup` to group keyboard keys together.
|
||||
|
||||
```tsx showLineNumbers
|
||||
<KbdGroup>
|
||||
<Kbd>Ctrl</Kbd>
|
||||
<Kbd>B</Kbd>
|
||||
</KbdGroup>
|
||||
```
|
||||
|
||||
<ComponentPreview
|
||||
name="kbd-demo"
|
||||
|
||||
/>
|
||||
|
||||
You can add it to buttons, tooltips, input groups, and more.
|
||||
|
||||
### Button Group
|
||||
|
||||
I got a lot of requests for this one: Button Group. It's a container that groups related buttons together with consistent styling. Great for action groups, split buttons, and more.
|
||||
|
||||
<ComponentPreview
|
||||
name="button-group-demo"
|
||||
|
||||
/>
|
||||
|
||||
Here's the code:
|
||||
|
||||
```tsx
|
||||
import { ButtonGroup } from "@/components/ui/button-group"
|
||||
```
|
||||
|
||||
```tsx showLineNumbers
|
||||
<ButtonGroup>
|
||||
<Button>Button 1</Button>
|
||||
<Button>Button 2</Button>
|
||||
</ButtonGroup>
|
||||
```
|
||||
|
||||
You can nest button groups to create more complex layouts with spacing.
|
||||
|
||||
```tsx showLineNumbers
|
||||
<ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button>Button 1</Button>
|
||||
<Button>Button 2</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button>Button 3</Button>
|
||||
<Button>Button 4</Button>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
```
|
||||
|
||||
Use `ButtonGroupSeparator` to create split buttons. Classic dropdown pattern.
|
||||
|
||||
<ComponentPreview
|
||||
name="button-group-dropdown"
|
||||
|
||||
/>
|
||||
|
||||
You can also use it to add prefix or suffix buttons and text to inputs.
|
||||
|
||||
<ComponentPreview
|
||||
name="button-group-select"
|
||||
|
||||
/>
|
||||
|
||||
```tsx showLineNumbers
|
||||
<ButtonGroup>
|
||||
<ButtonGroupText>Prefix</ButtonGroupText>
|
||||
<Input placeholder="Type something here..." />
|
||||
<Button>Button</Button>
|
||||
</ButtonGroup>
|
||||
```
|
||||
|
||||
### Input Group
|
||||
|
||||
Input Group lets you add icons, buttons, and more to your inputs. You know, all those little bits you always need around your inputs.
|
||||
|
||||
```tsx
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
} from "@/components/ui/input-group"
|
||||
```
|
||||
|
||||
```tsx showLineNumbers
|
||||
<InputGroup>
|
||||
<InputGroupInput placeholder="Search..." />
|
||||
<InputGroupAddon>
|
||||
<SearchIcon />
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
```
|
||||
|
||||
Here's a preview with icons:
|
||||
|
||||
<ComponentPreview
|
||||
name="input-group-icon"
|
||||
className="[&_.preview]:h-[300px] [&_pre]:!h-[300px]"
|
||||
/>
|
||||
|
||||
You can also add buttons to the input group.
|
||||
|
||||
<ComponentPreview
|
||||
name="input-group-button"
|
||||
className="[&_.preview]:h-[300px] [&_pre]:!h-[300px]"
|
||||
/>
|
||||
|
||||
Or text, labels, tooltips,...
|
||||
|
||||
<ComponentPreview
|
||||
name="input-group-text"
|
||||
className="[&_.preview]:h-[350px] [&_pre]:!h-[350px]"
|
||||
/>
|
||||
|
||||
It also works with textareas so you can build really complex components with lots of knobs and dials or yet another prompt form.
|
||||
|
||||
<ComponentPreview
|
||||
name="input-group-textarea"
|
||||
className="[&_.preview]:h-[450px] [&_pre]:!h-[450px]"
|
||||
/>
|
||||
|
||||
Oh here are some cool ones with spinners:
|
||||
|
||||
<ComponentPreview
|
||||
name="input-group-spinner"
|
||||
className="[&_.preview]:h-[350px] [&_pre]:!h-[350px]"
|
||||
/>
|
||||
|
||||
### Field
|
||||
|
||||
Introducing **Field**, a component for building really complex forms. The abstraction here is beautiful.
|
||||
|
||||
It took me a long time to get it right but I made it work with all your form libraries: Server Actions, React Hook Form, TanStack Form, Bring Your Own Form.
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldLabel,
|
||||
} from "@/components/ui/field"
|
||||
```
|
||||
|
||||
Here's a basic field with an input:
|
||||
|
||||
```tsx showLineNumbers
|
||||
<Field>
|
||||
<FieldLabel htmlFor="username">Username</FieldLabel>
|
||||
<Input id="username" placeholder="Max Leiter" />
|
||||
<FieldDescription>
|
||||
Choose a unique username for your account.
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
```
|
||||
|
||||
<ComponentPreview
|
||||
name="field-input"
|
||||
className="[&_.preview]:h-[350px] [&_pre]:!h-[350px]"
|
||||
/>
|
||||
|
||||
It works with all form controls. Inputs, textareas, selects, checkboxes, radios, switches, sliders, you name it. Here's a full example:
|
||||
|
||||
<ComponentPreview
|
||||
name="field-demo"
|
||||
className="[&_.preview]:h-[850px] [&_pre]:!h-[850px]"
|
||||
/>
|
||||
|
||||
Here are some checkbox fields:
|
||||
|
||||
<ComponentPreview
|
||||
name="field-checkbox"
|
||||
className="[&_.preview]:h-[500px] [&_pre]:!h-[500px]"
|
||||
/>
|
||||
|
||||
You can group fields together using `FieldGroup` and `FieldSet`. Perfect for
|
||||
multi-section forms.
|
||||
|
||||
```tsx showLineNumbers
|
||||
<FieldSet>
|
||||
<FieldLegend />
|
||||
<FieldGroup>
|
||||
<Field />
|
||||
<Field />
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
```
|
||||
|
||||
<ComponentPreview
|
||||
name="field-fieldset"
|
||||
className="[&_.preview]:h-[500px] [&_pre]:!h-[500px]"
|
||||
/>
|
||||
|
||||
Making it responsive is easy. Use `orientation="responsive"` and it switches
|
||||
between vertical and horizontal layouts based on container width. Done.
|
||||
|
||||
<ComponentPreview
|
||||
name="field-responsive"
|
||||
className="[&_.preview]:h-[600px] [&_pre]:!h-[600px]"
|
||||
/>
|
||||
|
||||
Wait here's more. Wrap your fields in `FieldLabel` to create a selectable field group. Really easy. And it looks great.
|
||||
|
||||
<ComponentPreview
|
||||
name="field-choice-card"
|
||||
className="[&_.preview]:h-[600px] [&_pre]:!h-[600px]"
|
||||
/>
|
||||
|
||||
### Item
|
||||
|
||||
This one is a straightforward flex container that can house nearly any type of content.
|
||||
|
||||
I've built this so many times that I decided to create a component for it. Now I use it all the time. I use it to display lists of items, cards, and more.
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Item,
|
||||
ItemContent,
|
||||
ItemDescription,
|
||||
ItemMedia,
|
||||
ItemTitle,
|
||||
} from "@/components/ui/item"
|
||||
```
|
||||
|
||||
Here's a basic item:
|
||||
|
||||
```tsx showLineNumbers
|
||||
<Item>
|
||||
<ItemMedia variant="icon">
|
||||
<HomeIcon />
|
||||
</ItemMedia>
|
||||
<ItemContent>
|
||||
<ItemTitle>Dashboard</ItemTitle>
|
||||
<ItemDescription>Overview of your account and activity.</ItemDescription>
|
||||
</ItemContent>
|
||||
</Item>
|
||||
```
|
||||
|
||||
<ComponentPreview
|
||||
name="item-demo"
|
||||
className="[&_.preview]:h-[300px] [&_.preview]:p-4 [&_pre]:!h-[300px]"
|
||||
/>
|
||||
|
||||
You can add icons, avatars, or images to the item.
|
||||
|
||||
<ComponentPreview
|
||||
name="item-icon"
|
||||
className="[&_.preview]:h-[300px] [&_.preview]:p-4 [&_pre]:!h-[300px]"
|
||||
/>
|
||||
|
||||
<ComponentPreview
|
||||
name="item-avatar"
|
||||
className="[&_.preview]:h-[300px] [&_.preview]:p-4 [&_pre]:!h-[300px]"
|
||||
/>
|
||||
|
||||
And here's what a list of items looks like with `ItemGroup`:
|
||||
|
||||
<ComponentPreview
|
||||
name="item-group"
|
||||
className="[&_.preview]:h-[500px] [&_.preview]:p-4 [&_pre]:!h-[500px]"
|
||||
/>
|
||||
|
||||
Need it as a link? Use the `asChild` prop:
|
||||
|
||||
```tsx showLineNumbers
|
||||
<Item asChild>
|
||||
<a href="/dashboard">
|
||||
<ItemMedia variant="icon">
|
||||
<HomeIcon />
|
||||
</ItemMedia>
|
||||
<ItemContent>
|
||||
<ItemTitle>Dashboard</ItemTitle>
|
||||
<ItemDescription>Overview of your account and activity.</ItemDescription>
|
||||
</ItemContent>
|
||||
</a>
|
||||
</Item>
|
||||
```
|
||||
|
||||
<ComponentPreview
|
||||
name="item-link"
|
||||
className="[&_.preview]:h-[400px] [&_.preview]:p-4 [&_pre]:!h-[400px]"
|
||||
/>
|
||||
|
||||
### Empty
|
||||
|
||||
Okay last one: **Empty**. Use this to display empty states in your app.
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Empty,
|
||||
EmptyContent,
|
||||
EmptyDescription,
|
||||
EmptyMedia,
|
||||
EmptyTitle,
|
||||
} from "@/components/ui/empty"
|
||||
```
|
||||
|
||||
Here's how you use it:
|
||||
|
||||
```tsx showLineNumbers
|
||||
<Empty>
|
||||
<EmptyMedia variant="icon">
|
||||
<InboxIcon />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>No messages</EmptyTitle>
|
||||
<EmptyDescription>You don't have any messages yet.</EmptyDescription>
|
||||
<EmptyContent>
|
||||
<Button>Send a message</Button>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
```
|
||||
|
||||
<ComponentPreview
|
||||
name="empty-demo"
|
||||
className="[&_.preview]:h-[400px] [&_.preview]:p-4 [&_pre]:!h-[400px]"
|
||||
/>
|
||||
|
||||
You can use it with avatars:
|
||||
|
||||
<ComponentPreview
|
||||
name="empty-avatar"
|
||||
className="[&_.preview]:h-[400px] [&_pre]:!h-[400px]"
|
||||
/>
|
||||
|
||||
Or with input groups for things like search results or email subscriptions:
|
||||
|
||||
<ComponentPreview
|
||||
name="empty-input-group"
|
||||
className="[&_.preview]:h-[450px] [&_pre]:!h-[450px]"
|
||||
/>
|
||||
|
||||
That's it. Seven new components. Works with all your libraries. Ready for your projects.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: October 2025 - Registry Directory
|
||||
description: A list of code registries you can browse and pull code and components from.
|
||||
date: 2025-10-28
|
||||
---
|
||||
|
||||
We just published the Registry Directory: a list of code registries you can browse and pull code and components from.
|
||||
|
||||
https://ui.shadcn.com/docs/directory
|
||||
|
||||
Built into the CLI. No config required.
|
||||
46
apps/v4/content/docs/changelog/2025-12-shadcn-create.mdx
Normal file
46
apps/v4/content/docs/changelog/2025-12-shadcn-create.mdx
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: December 2025 - npx shadcn create
|
||||
description: Customize everything. Pick your component library, icons, base color, theme, fonts and create your own version of shadcn/ui.
|
||||
date: 2025-12-12
|
||||
---
|
||||
|
||||
From the very first commit, the goal of shadcn/ui was to make it customizable.
|
||||
|
||||
The idea is to give you solid defaults, spacing, color tokens, animations, accessibility, and then let you take it from there. Tweak the code. Add new components. Change the colors. Build your own version.
|
||||
|
||||
But somewhere along the way, all apps started looking the same. I guess the defaults were a little _too_ good. My bad.
|
||||
|
||||
Today, we're changing that: **npx shadcn create**.
|
||||
|
||||
Customize Everything. Pick your component library, icons, base color, theme, fonts and create your own version of shadcn/ui.
|
||||
|
||||
We're starting with **5 new visual styles,** designed to help your UI actually feel like _your_ UI.
|
||||
|
||||
- **Vega** – The classic shadcn/ui look.
|
||||
- **Nova** – Reduced padding and margins for compact layouts.
|
||||
- **Maia** – Soft and rounded, with generous spacing.
|
||||
- **Lyra** – Boxy and sharp. Pairs well with mono fonts.
|
||||
- **Mira** – Compact. Made for dense interfaces.
|
||||
|
||||
**This goes beyond theming**.
|
||||
|
||||
Your config doesn't just change colors, it rewrites the component code to match your setup. Fonts, spacing, structure, even the libraries you use, everything adapts to your preferences.
|
||||
|
||||
The new CLI takes care of it all.
|
||||
|
||||
Start with a component library. Choose between Radix or Base UI.
|
||||
|
||||
We rebuilt every component for Base UI, keeping the same abstraction.
|
||||
They are fully compatible with your existing components, even those pulled from remote registries.
|
||||
|
||||
When you pull down components, we auto-detect your library and apply the right transformations.
|
||||
|
||||
**It's time to build something that doesn't look like everything else.**
|
||||
|
||||
Now available for Next.js, Vite, TanStack Start and v0.
|
||||
|
||||
<Button asChild>
|
||||
<Link href="/create" className="mt-6 no-underline!">
|
||||
Get Started
|
||||
</Link>
|
||||
</Button>
|
||||
45
apps/v4/content/docs/changelog/2026-01-base-ui.mdx
Normal file
45
apps/v4/content/docs/changelog/2026-01-base-ui.mdx
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: January 2026 - Base UI Documentation
|
||||
description: Full documentation for Base UI components.
|
||||
date: 2026-01-20
|
||||
---
|
||||
|
||||
We've shipped full documentation for Base UI components.
|
||||
|
||||
<div className="bg-muted/50 mt-6 flex w-full items-center justify-center rounded-lg border py-32">
|
||||
<svg width="17" height="24" viewBox="0 0 17 24" className="size-12">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M9.5001 7.01537C9.2245 6.99837 9 7.22385 9 7.49999V23C13.4183 23 17 19.4183 17 15C17 10.7497 13.6854 7.27351 9.5001 7.01537Z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M8 9.8V12V23C3.58172 23 0 19.0601 0 14.2V12V1C4.41828 1 8 4.93989 8 9.8Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
When we launched `npx shadcn create` in December, we introduced the ability to choose between Radix and Base UI as your component library. Today, we're following up with complete documentation for all Base UI components.
|
||||
|
||||
### What's New
|
||||
|
||||
- **Full Base UI docs** - Every component now has dedicated documentation for Base UI, covering usage, props, and examples.
|
||||
- **Rebuilt examples** - All component examples have been rebuilt for both Radix and Base UI. Switch between them to see the implementation differences.
|
||||
- **Side-by-side comparison** - The docs make it easy to compare how components work across both libraries.
|
||||
|
||||
### Same Abstraction, Different Primitives
|
||||
|
||||
The goal remains the same: give you a consistent API regardless of which primitive library you choose. The components look and behave the same way. Only the underlying implementation changes.
|
||||
|
||||
```tsx
|
||||
// Works the same whether you're using Radix or Base UI.
|
||||
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"
|
||||
```
|
||||
|
||||
If you're starting a new project, run `npx shadcn create` and pick your preferred library. The CLI handles the rest.
|
||||
|
||||
<Button asChild size="sm">
|
||||
<Link href="/create" className="mt-6 no-underline!">
|
||||
Try shadcn/create
|
||||
</Link>
|
||||
</Button>
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: January 2026 - Inline Start and End Styles
|
||||
description: We've updated the styles for Base UI components to support inline-start and inline-end side values.
|
||||
date: 2026-01-23
|
||||
---
|
||||
|
||||
We've updated the styles for Base UI components to support `inline-start` and `inline-end` side values. The following components now support these values:
|
||||
|
||||
- Tooltip
|
||||
- Popover
|
||||
- Combobox
|
||||
- Context Menu
|
||||
- Dropdown Menu
|
||||
- Hover Card
|
||||
- Menubar
|
||||
- Select
|
||||
|
||||
### What Changed
|
||||
|
||||
We added new Tailwind classes to handle the logical side values:
|
||||
|
||||
```diff
|
||||
<PopoverPrimitive.Popup
|
||||
className={cn(
|
||||
"... data-[side=bottom]:slide-in-from-top-2
|
||||
data-[side=left]:slide-in-from-right-2
|
||||
data-[side=right]:slide-in-from-left-2
|
||||
data-[side=top]:slide-in-from-bottom-2
|
||||
+ data-[side=inline-start]:slide-in-from-right-2
|
||||
+ data-[side=inline-end]:slide-in-from-left-2 ...",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```tsx
|
||||
<Popover>
|
||||
<PopoverTrigger>Open</PopoverTrigger>
|
||||
<PopoverContent side="inline-start">
|
||||
{/* Opens on the left in LTR, right in RTL */}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
```
|
||||
|
||||
### LLM Prompt
|
||||
|
||||
Ask your LLM to update your components by running the following prompt:
|
||||
|
||||
```txt showLineNumbers
|
||||
Add inline-start and inline-end support to my shadcn/ui components. Add the following Tailwind classes to each component:
|
||||
|
||||
| File | Component | Add Classes |
|
||||
|------|-----------|-------------|
|
||||
| tooltip.tsx | TooltipContent | `data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2` |
|
||||
| tooltip.tsx | TooltipArrow | `data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2
|
||||
data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2` |
|
||||
| popover.tsx | PopoverContent | `data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2` |
|
||||
| hover-card.tsx | HoverCardContent | `data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2` |
|
||||
| select.tsx | SelectContent | `data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2
|
||||
data-[align-trigger=true]:animate-none` and add `data-align-trigger={alignItemWithTrigger}` attribute |
|
||||
| combobox.tsx | ComboboxContent | `data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2` |
|
||||
| dropdown-menu.tsx | DropdownMenuContent | `data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2` |
|
||||
| context-menu.tsx | ContextMenuContent | `data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2` |
|
||||
| menubar.tsx | MenubarContent | `data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2` |
|
||||
|
||||
Add these classes next to the existing `data-[side=top]`, `data-[side=bottom]`, `data-[side=left]`, `data-[side=right]` classes.
|
||||
```
|
||||
6
apps/v4/content/docs/changelog/index.mdx
Normal file
6
apps/v4/content/docs/changelog/index.mdx
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Changelog
|
||||
description: Latest updates and announcements.
|
||||
---
|
||||
|
||||
Latest updates and announcements for shadcn/ui.
|
||||
4
apps/v4/content/docs/changelog/meta.json
Normal file
4
apps/v4/content/docs/changelog/meta.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"title": "Changelog",
|
||||
"pages": []
|
||||
}
|
||||
@@ -139,6 +139,8 @@ To create a button group, use the `ButtonGroup` component. See the [Button Group
|
||||
|
||||
You can use the `render` prop on `<Button />` to make another component look like a button. Here's an example of a link that looks like a button.
|
||||
|
||||
Remember to set the `nativeButton` prop to `false` if you're returning an element that is not a button.
|
||||
|
||||
<ComponentPreview styleName="base-nova" name="button-render" />
|
||||
|
||||
## API Reference
|
||||
|
||||
@@ -110,4 +110,4 @@ You can combine the `Dialog` and `Drawer` components to create a responsive dial
|
||||
|
||||
## API Reference
|
||||
|
||||
See the [Vaul documentation](https://vaul.emilkowal.ski/components/drawer) for the full API reference.
|
||||
See the [Vaul documentation](https://vaul.emilkowal.ski/getting-started) for the full API reference.
|
||||
|
||||
@@ -5,8 +5,8 @@ featured: true
|
||||
base: base
|
||||
component: true
|
||||
links:
|
||||
doc: https://base-ui.com/react/components/dropdown-menu
|
||||
api: https://base-ui.com/react/components/dropdown-menu#api-reference
|
||||
doc: https://base-ui.com/react/components/menu
|
||||
api: https://base-ui.com/react/components/menu#api-reference
|
||||
---
|
||||
|
||||
<ComponentPreview
|
||||
@@ -162,4 +162,4 @@ A richer example combining groups, icons, and submenus.
|
||||
|
||||
## API Reference
|
||||
|
||||
See the [Base UI documentation](https://base-ui.com/react/components/dropdown-menu) for the full API reference.
|
||||
See the [Base UI documentation](https://base-ui.com/react/components/menu) for the full API reference.
|
||||
|
||||
@@ -37,7 +37,7 @@ npx shadcn@latest add navigation-menu
|
||||
<Step>Install the following dependencies:</Step>
|
||||
|
||||
```bash
|
||||
npm install @base-ui-components/react
|
||||
npm install @base-ui/react
|
||||
```
|
||||
|
||||
<Step>Copy and paste the following code into your project.</Step>
|
||||
@@ -45,6 +45,7 @@ npm install @base-ui-components/react
|
||||
<ComponentSource
|
||||
name="navigation-menu"
|
||||
title="components/ui/navigation-menu.tsx"
|
||||
styleName="base-nova"
|
||||
/>
|
||||
|
||||
<Step>Update the import paths to match your project setup.</Step>
|
||||
|
||||
@@ -110,4 +110,4 @@ You can combine the `Dialog` and `Drawer` components to create a responsive dial
|
||||
|
||||
## API Reference
|
||||
|
||||
See the [Vaul documentation](https://vaul.emilkowal.ski/components/drawer) for the full API reference.
|
||||
See the [Vaul documentation](https://vaul.emilkowal.ski/getting-started) for the full API reference.
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import Link from "next/link"
|
||||
import { Button } from "@/examples/base/ui/button"
|
||||
|
||||
export default function ButtonRender() {
|
||||
return <Button render={<Link href="#" />}>Login</Button>
|
||||
return (
|
||||
<Button nativeButton={false} render={<a href="#" />}>
|
||||
Login
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client"
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/examples/base/ui/avatar"
|
||||
import { Button } from "@/examples/base/ui/button"
|
||||
import {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/examples/base/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/examples/base/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/examples/base/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/examples/base/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/examples/base/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/examples/base/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
||||
@@ -125,7 +125,7 @@ function ComboboxContent({
|
||||
data-slot="combobox-content"
|
||||
data-chips={!!anchor}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:border-input/30 cn-menu-target group/combobox-content relative max-h-(--available-height) max-h-72 w-(--anchor-width) max-w-(--available-width) min-w-36 min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg shadow-md ring-1 duration-100 data-[chips=true]:min-w-(--anchor-width) *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:shadow-none",
|
||||
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:border-input/30 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 cn-menu-target group/combobox-content relative max-h-(--available-height) max-h-72 w-(--anchor-width) max-w-(--available-width) min-w-36 min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg shadow-md ring-1 duration-100 data-[chips=true]:min-w-(--anchor-width) *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:shadow-none",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -53,7 +53,7 @@ function ContextMenuContent({
|
||||
<ContextMenuPrimitive.Popup
|
||||
data-slot="context-menu-content"
|
||||
className={cn(
|
||||
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground cn-menu-target z-50 max-h-(--available-height) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg p-1 shadow-md ring-1 duration-100 outline-none",
|
||||
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 cn-menu-target z-50 max-h-(--available-height) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg p-1 shadow-md ring-1 duration-100 outline-none",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -126,7 +126,7 @@ function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
|
||||
return (
|
||||
<DialogPrimitive.Title
|
||||
data-slot="dialog-title"
|
||||
className={cn("text-sm leading-none font-medium", className)}
|
||||
className={cn("text-base leading-none font-medium", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -42,7 +42,7 @@ function DropdownMenuContent({
|
||||
<MenuPrimitive.Popup
|
||||
data-slot="dropdown-menu-content"
|
||||
className={cn(
|
||||
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground cn-menu-target z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg p-1 shadow-md ring-1 duration-100 outline-none data-closed:overflow-hidden",
|
||||
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 cn-menu-target z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg p-1 shadow-md ring-1 duration-100 outline-none data-closed:overflow-hidden",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -37,7 +37,7 @@ function HoverCardContent({
|
||||
<PreviewCardPrimitive.Popup
|
||||
data-slot="hover-card-content"
|
||||
className={cn(
|
||||
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground z-50 w-64 origin-(--transform-origin) rounded-lg p-2.5 text-sm shadow-md ring-1 outline-hidden duration-100",
|
||||
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 w-64 origin-(--transform-origin) rounded-lg p-2.5 text-sm shadow-md ring-1 outline-hidden duration-100",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -27,7 +27,7 @@ function Menubar({ className, ...props }: MenubarPrimitive.Props) {
|
||||
<MenubarPrimitive
|
||||
data-slot="menubar"
|
||||
className={cn(
|
||||
"bg-background flex h-8 items-center gap-0.5 rounded-lg border p-1",
|
||||
"bg-background flex h-8 items-center gap-0.5 rounded-lg border p-[3px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -59,7 +59,7 @@ function MenubarTrigger({
|
||||
<DropdownMenuTrigger
|
||||
data-slot="menubar-trigger"
|
||||
className={cn(
|
||||
"hover:bg-muted aria-expanded:bg-muted flex items-center rounded-sm px-1.5 py-px text-sm font-medium outline-hidden select-none",
|
||||
"hover:bg-muted aria-expanded:bg-muted flex items-center rounded-sm px-1.5 py-[2px] text-sm font-medium outline-hidden select-none",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -81,7 +81,7 @@ function MenubarContent({
|
||||
alignOffset={alignOffset}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 cn-menu-target min-w-36 rounded-lg p-1 shadow-md ring-1 duration-100",
|
||||
"bg-popover text-popover-foreground data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 cn-menu-target min-w-36 rounded-lg p-1 shadow-md ring-1 duration-100",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -36,7 +36,7 @@ function PopoverContent({
|
||||
<PopoverPrimitive.Popup
|
||||
data-slot="popover-content"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg p-2.5 text-sm shadow-md ring-1 outline-hidden duration-100",
|
||||
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg p-2.5 text-sm shadow-md ring-1 outline-hidden duration-100",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -89,8 +89,9 @@ function SelectContent({
|
||||
>
|
||||
<SelectPrimitive.Popup
|
||||
data-slot="select-content"
|
||||
data-align-trigger={alignItemWithTrigger}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 cn-menu-target relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg shadow-md ring-1 duration-100",
|
||||
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 cn-menu-target relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg shadow-md ring-1 duration-100 data-[align-trigger=true]:animate-none",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -152,14 +152,14 @@ function SidebarProvider({
|
||||
function Sidebar({
|
||||
side = "left",
|
||||
variant = "sidebar",
|
||||
collapsible = "offExamples",
|
||||
collapsible = "offcanvas",
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & {
|
||||
side?: "left" | "right"
|
||||
variant?: "sidebar" | "floating" | "inset"
|
||||
collapsible?: "offExamples" | "icon" | "none"
|
||||
collapsible?: "offcanvas" | "icon" | "none"
|
||||
}) {
|
||||
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
|
||||
|
||||
@@ -217,7 +217,7 @@ function Sidebar({
|
||||
data-slot="sidebar-gap"
|
||||
className={cn(
|
||||
"relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
|
||||
"group-data-[collapsible=offExamples]:w-0",
|
||||
"group-data-[collapsible=offcanvas]:w-0",
|
||||
"group-data-[side=right]:rotate-180",
|
||||
variant === "floating" || variant === "inset"
|
||||
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
|
||||
@@ -229,8 +229,8 @@ function Sidebar({
|
||||
className={cn(
|
||||
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
|
||||
side === "left"
|
||||
? "left-0 group-data-[collapsible=offExamples]:left-[calc(var(--sidebar-width)*-1)]"
|
||||
: "right-0 group-data-[collapsible=offExamples]:right-[calc(var(--sidebar-width)*-1)]",
|
||||
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
||||
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
||||
// Adjust the padding for floating and inset variants.
|
||||
variant === "floating" || variant === "inset"
|
||||
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
||||
@@ -298,9 +298,9 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
|
||||
"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
|
||||
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
|
||||
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
|
||||
"hover:group-data-[collapsible=offExamples]:bg-sidebar group-data-[collapsible=offExamples]:translate-x-0 group-data-[collapsible=offExamples]:after:left-full",
|
||||
"[[data-side=left][data-collapsible=offExamples]_&]:-right-2",
|
||||
"[[data-side=right][data-collapsible=offExamples]_&]:-left-2",
|
||||
"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
|
||||
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
|
||||
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -53,13 +53,13 @@ function TooltipContent({
|
||||
<TooltipPrimitive.Popup
|
||||
data-slot="tooltip-content"
|
||||
className={cn(
|
||||
"data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-foreground text-background z-50 w-fit max-w-xs origin-(--transform-origin) rounded-md px-3 py-1.5 text-xs",
|
||||
"data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 bg-foreground text-background z-50 w-fit max-w-xs origin-(--transform-origin) rounded-md px-3 py-1.5 text-xs",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
|
||||
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
|
||||
</TooltipPrimitive.Popup>
|
||||
</TooltipPrimitive.Positioner>
|
||||
</TooltipPrimitive.Portal>
|
||||
|
||||
@@ -134,7 +134,7 @@ function DialogTitle({
|
||||
return (
|
||||
<DialogPrimitive.Title
|
||||
data-slot="dialog-title"
|
||||
className={cn("text-sm leading-none font-medium", className)}
|
||||
className={cn("text-base leading-none font-medium", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ function Menubar({
|
||||
<MenubarPrimitive.Root
|
||||
data-slot="menubar"
|
||||
className={cn(
|
||||
"bg-background flex h-8 items-center gap-0.5 rounded-lg border p-1",
|
||||
"bg-background flex h-8 items-center gap-0.5 rounded-lg border p-[3px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -56,7 +56,7 @@ function MenubarTrigger({
|
||||
<MenubarPrimitive.Trigger
|
||||
data-slot="menubar-trigger"
|
||||
className={cn(
|
||||
"hover:bg-muted aria-expanded:bg-muted flex items-center rounded-sm px-1.5 py-px text-sm font-medium outline-hidden select-none",
|
||||
"hover:bg-muted aria-expanded:bg-muted flex items-center rounded-sm px-1.5 py-[2px] text-sm font-medium outline-hidden select-none",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -75,8 +75,9 @@ function SelectContent({
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
data-slot="select-content"
|
||||
data-align-trigger={position === "item-aligned"}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 cn-menu-target relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg shadow-md ring-1 duration-100",
|
||||
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 cn-menu-target relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg shadow-md ring-1 duration-100 data-[align-trigger=true]:animate-none",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
|
||||
@@ -151,14 +151,14 @@ function SidebarProvider({
|
||||
function Sidebar({
|
||||
side = "left",
|
||||
variant = "sidebar",
|
||||
collapsible = "offExamples",
|
||||
collapsible = "offcanvas",
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & {
|
||||
side?: "left" | "right"
|
||||
variant?: "sidebar" | "floating" | "inset"
|
||||
collapsible?: "offExamples" | "icon" | "none"
|
||||
collapsible?: "offcanvas" | "icon" | "none"
|
||||
}) {
|
||||
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
|
||||
|
||||
@@ -216,7 +216,7 @@ function Sidebar({
|
||||
data-slot="sidebar-gap"
|
||||
className={cn(
|
||||
"relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
|
||||
"group-data-[collapsible=offExamples]:w-0",
|
||||
"group-data-[collapsible=offcanvas]:w-0",
|
||||
"group-data-[side=right]:rotate-180",
|
||||
variant === "floating" || variant === "inset"
|
||||
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
|
||||
@@ -228,8 +228,8 @@ function Sidebar({
|
||||
className={cn(
|
||||
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
|
||||
side === "left"
|
||||
? "left-0 group-data-[collapsible=offExamples]:left-[calc(var(--sidebar-width)*-1)]"
|
||||
: "right-0 group-data-[collapsible=offExamples]:right-[calc(var(--sidebar-width)*-1)]",
|
||||
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
||||
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
||||
// Adjust the padding for floating and inset variants.
|
||||
variant === "floating" || variant === "inset"
|
||||
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
||||
@@ -297,9 +297,9 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
|
||||
"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
|
||||
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
|
||||
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
|
||||
"hover:group-data-[collapsible=offExamples]:bg-sidebar group-data-[collapsible=offExamples]:translate-x-0 group-data-[collapsible=offExamples]:after:left-full",
|
||||
"[[data-side=left][data-collapsible=offExamples]_&]:-right-2",
|
||||
"[[data-side=right][data-collapsible=offExamples]_&]:-left-2",
|
||||
"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
|
||||
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
|
||||
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
50
apps/v4/lib/changelog.ts
Normal file
50
apps/v4/lib/changelog.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import fm from "front-matter"
|
||||
|
||||
import { source } from "@/lib/source"
|
||||
|
||||
export type ChangelogPageData = {
|
||||
title: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type ChangelogPage = ReturnType<typeof source.getPages>[number] & {
|
||||
date: Date | null
|
||||
}
|
||||
|
||||
// Reads the date from the frontmatter of a changelog file.
|
||||
export function getDateFromFile(slugs: string[]) {
|
||||
const filePath = path.join(
|
||||
process.cwd(),
|
||||
"content/docs",
|
||||
...slugs.slice(0, -1),
|
||||
`${slugs[slugs.length - 1]}.mdx`
|
||||
)
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, "utf-8")
|
||||
const { attributes } = fm<{ date?: string | Date }>(content)
|
||||
if (attributes.date) {
|
||||
return new Date(attributes.date)
|
||||
}
|
||||
} catch {
|
||||
// File not found or parse error.
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Gets all changelog pages sorted by date descending.
|
||||
export function getChangelogPages() {
|
||||
return source
|
||||
.getPages()
|
||||
.filter((page) => page.slugs[0] === "changelog" && page.slugs.length > 1)
|
||||
.map((page) => ({
|
||||
...page,
|
||||
date: getDateFromFile(page.slugs),
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
const dateA = a.date?.getTime() ?? 0
|
||||
const dateB = b.date?.getTime() ?? 0
|
||||
return dateB - dateA
|
||||
})
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export async function highlightCode(code: string, language: string = "tsx") {
|
||||
{
|
||||
pre(node) {
|
||||
node.properties["class"] =
|
||||
"no-scrollbar min-w-0 overflow-x-auto overscroll-none 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 !bg-transparent"
|
||||
"no-scrollbar min-w-0 overflow-x-auto overflow-y-auto overscroll-x-contain overscroll-y-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 !bg-transparent"
|
||||
},
|
||||
code(node) {
|
||||
node.properties["data-line-numbers"] = ""
|
||||
|
||||
@@ -190,7 +190,7 @@ export const mdxComponents = {
|
||||
return (
|
||||
<pre
|
||||
className={cn(
|
||||
"no-scrollbar min-w-0 overflow-x-auto overscroll-none 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",
|
||||
"no-scrollbar min-w-0 overflow-x-auto overflow-y-auto overscroll-x-contain overscroll-y-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",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
"input-otp": "^1.4.2",
|
||||
"jotai": "^2.15.0",
|
||||
"little-date": "^1.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash": "^4.17.23",
|
||||
"lru-cache": "^11.2.4",
|
||||
"lucide-react": "0.474.0",
|
||||
"motion": "^12.12.1",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
{
|
||||
"name": "@ai-elements",
|
||||
"homepage": "https://ai-sdk.dev/elements",
|
||||
"url": "https://registry.ai-sdk.dev/{name}.json",
|
||||
"url": "https://ai-sdk.dev/elements/api/registry/{name}.json",
|
||||
"description": "Pre-built components like conversations, messages and more to help you build AI-native applications faster."
|
||||
},
|
||||
{
|
||||
@@ -86,7 +86,7 @@
|
||||
{
|
||||
"name": "@basecn",
|
||||
"homepage": "https://basecn.dev",
|
||||
"url": "https://basecn.dev/r/{name}",
|
||||
"url": "https://basecn.dev/r/{name}.json",
|
||||
"description": "Beautifully crafted shadcn/ui components powered by Base UI"
|
||||
},
|
||||
{
|
||||
@@ -164,7 +164,7 @@
|
||||
{
|
||||
"name": "@elements",
|
||||
"homepage": "https://www.tryelements.dev",
|
||||
"url": "https://www.tryelements.dev/r/registry.json",
|
||||
"url": "https://www.tryelements.dev/r/{name}.json",
|
||||
"description": "Full-stack shadcn/ui components that go beyond UI. Add auth, monetization, uploads, and AI to your app in seconds."
|
||||
},
|
||||
{
|
||||
@@ -176,7 +176,7 @@
|
||||
{
|
||||
"name": "@efferd",
|
||||
"homepage": "https://efferd.com/",
|
||||
"url": "https://efferd.com/r/registry.json",
|
||||
"url": "https://efferd.com/r/{name}.json",
|
||||
"description": "A collection of beautifully crafted Shadcn/UI blocks, designed to help developers build modern websites with ease."
|
||||
},
|
||||
{
|
||||
@@ -347,6 +347,12 @@
|
||||
"url": "https://ui.paceui.com/r/{name}.json",
|
||||
"description": "Animated components and building blocks built for smooth interaction and rich detail. Copy, customise, and create without the extra setup."
|
||||
},
|
||||
{
|
||||
"name": "@pastecn",
|
||||
"homepage": "https://pastecn.com",
|
||||
"url": "https://pastecn.com/r/{name}",
|
||||
"description": "pastebin + shadcn = pastecn. Paste your code and get a shadcn-compatible registry URL instantly."
|
||||
},
|
||||
{
|
||||
"name": "@paykit-sdk",
|
||||
"homepage": "https://www.usepaykit.dev",
|
||||
@@ -362,7 +368,7 @@
|
||||
{
|
||||
"name": "@prompt-kit",
|
||||
"homepage": "https://www.prompt-kit.com",
|
||||
"url": "https://www.prompt-kit.com/c/registry.json",
|
||||
"url": "https://www.prompt-kit.com/c/{name}.json",
|
||||
"description": "Core building blocks for AI apps. High-quality, accessible, and customizable components for AI interfaces."
|
||||
},
|
||||
{
|
||||
@@ -425,6 +431,12 @@
|
||||
"url": "https://roiui.com/r/{name}.json",
|
||||
"description": "Roi UI is a library that offers UI components and blocks built with Base UI primitives. Some blocks and components use motion (framer). Everything is open-source and will be forever."
|
||||
},
|
||||
{
|
||||
"name": "@satoriui",
|
||||
"homepage": "https://satoriui.site",
|
||||
"url": "https://satoriui.site/r/{name}.json",
|
||||
"description": "A comprehensive suite of high-fidelity interaction components. It offers motion-driven components that designed with motion-react and tailwindcss, that blends seamlessly."
|
||||
},
|
||||
{
|
||||
"name": "@solaceui",
|
||||
"homepage": "https://www.solaceui.com",
|
||||
@@ -614,7 +626,7 @@
|
||||
{
|
||||
"name": "@beste-ui",
|
||||
"homepage": "https://ui.beste.co",
|
||||
"url": "https://ui.beste.co/r/registry.json",
|
||||
"url": "https://ui.beste.co/r/{name}.json",
|
||||
"description": "Production-ready UI blocks for landing pages, dashboards, and web apps."
|
||||
},
|
||||
{
|
||||
@@ -682,5 +694,11 @@
|
||||
"homepage": "https://typedora-ui.netlify.app",
|
||||
"url": "https://typedora-ui.netlify.app/r/{name}.json",
|
||||
"description": "Typedora UI is a next-generation extension layer for shadcn/ui, designed to bring full type-safety to your UI components."
|
||||
},
|
||||
{
|
||||
"name": "@shadcn-space",
|
||||
"homepage": "https://shadcnspace.com/",
|
||||
"url": "https://shadcnspace.com/r/{name}.json",
|
||||
"description": "An open-source collection of production-ready Shadcn UI blocks, components, and templates for websites, admin dashboards and modern React web projects, built with Tailwind CSS and ready to copy-paste or install via the Shadcn CLI."
|
||||
}
|
||||
]
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,7 +11,7 @@
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/base-lyra/examples/hover-card-example.tsx",
|
||||
"content": "import {\n Example,\n ExampleWrapper,\n} from \"@/registry/base-lyra/components/example\"\nimport { Button } from \"@/registry/base-lyra/ui/button\"\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from \"@/registry/base-lyra/ui/dialog\"\nimport {\n HoverCard,\n HoverCardContent,\n HoverCardTrigger,\n} from \"@/registry/base-lyra/ui/hover-card\"\n\nexport default function HoverCardExample() {\n return (\n <ExampleWrapper>\n <HoverCardSides />\n <HoverCardInDialog />\n </ExampleWrapper>\n )\n}\n\nconst HOVER_CARD_SIDES = [\"top\", \"right\", \"bottom\", \"left\"] as const\n\nfunction HoverCardSides() {\n return (\n <Example title=\"Sides\">\n <div className=\"flex flex-wrap items-center justify-center gap-4\">\n {HOVER_CARD_SIDES.map((side) => (\n <HoverCard key={side}>\n <HoverCardTrigger\n delay={100}\n closeDelay={100}\n render={<Button variant=\"outline\" className=\"capitalize\" />}\n >\n {side}\n </HoverCardTrigger>\n <HoverCardContent side={side}>\n <div className=\"style-lyra:gap-1 style-nova:gap-1.5 style-vega:gap-2 style-maia:gap-2 style-mira:gap-1 flex flex-col\">\n <h4 className=\"font-medium\">Hover Card</h4>\n <p>\n This hover card appears on the {side} side of the trigger.\n </p>\n </div>\n </HoverCardContent>\n </HoverCard>\n ))}\n </div>\n </Example>\n )\n}\n\nfunction HoverCardInDialog() {\n return (\n <Example title=\"In Dialog\">\n <Dialog>\n <DialogTrigger render={<Button variant=\"outline\" />}>\n Open Dialog\n </DialogTrigger>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Hover Card Example</DialogTitle>\n <DialogDescription>\n Hover over the button below to see the hover card.\n </DialogDescription>\n </DialogHeader>\n <HoverCard>\n <HoverCardTrigger\n delay={100}\n closeDelay={100}\n render={<Button variant=\"outline\" className=\"w-fit\" />}\n >\n Hover me\n </HoverCardTrigger>\n <HoverCardContent>\n <div className=\"style-lyra:gap-1 style-nova:gap-1.5 style-vega:gap-2 style-maia:gap-2 style-mira:gap-1 flex flex-col\">\n <h4 className=\"font-medium\">Hover Card</h4>\n <p>\n This hover card appears inside a dialog. Hover over the button\n to see it.\n </p>\n </div>\n </HoverCardContent>\n </HoverCard>\n </DialogContent>\n </Dialog>\n </Example>\n )\n}\n",
|
||||
"content": "import {\n Example,\n ExampleWrapper,\n} from \"@/registry/base-lyra/components/example\"\nimport { Button } from \"@/registry/base-lyra/ui/button\"\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from \"@/registry/base-lyra/ui/dialog\"\nimport {\n HoverCard,\n HoverCardContent,\n HoverCardTrigger,\n} from \"@/registry/base-lyra/ui/hover-card\"\n\nexport default function HoverCardExample() {\n return (\n <ExampleWrapper>\n <HoverCardSides />\n <HoverCardInDialog />\n </ExampleWrapper>\n )\n}\n\nconst HOVER_CARD_SIDES = [\n \"inline-start\",\n \"left\",\n \"top\",\n \"bottom\",\n \"right\",\n \"inline-end\",\n] as const\n\nfunction HoverCardSides() {\n return (\n <Example title=\"Sides\" containerClassName=\"col-span-2\">\n <div className=\"flex flex-wrap items-center justify-center gap-2\">\n {HOVER_CARD_SIDES.map((side) => (\n <HoverCard key={side}>\n <HoverCardTrigger\n delay={100}\n closeDelay={100}\n render={<Button variant=\"outline\" className=\"capitalize\" />}\n >\n {side.replace(\"-\", \" \")}\n </HoverCardTrigger>\n <HoverCardContent side={side}>\n <div className=\"style-lyra:gap-1 style-nova:gap-1.5 style-vega:gap-2 style-maia:gap-2 style-mira:gap-1 flex flex-col\">\n <h4 className=\"font-medium\">Hover Card</h4>\n <p>\n This hover card appears on the {side.replace(\"-\", \" \")} side\n of the trigger.\n </p>\n </div>\n </HoverCardContent>\n </HoverCard>\n ))}\n </div>\n </Example>\n )\n}\n\nfunction HoverCardInDialog() {\n return (\n <Example title=\"In Dialog\">\n <Dialog>\n <DialogTrigger render={<Button variant=\"outline\" />}>\n Open Dialog\n </DialogTrigger>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Hover Card Example</DialogTitle>\n <DialogDescription>\n Hover over the button below to see the hover card.\n </DialogDescription>\n </DialogHeader>\n <HoverCard>\n <HoverCardTrigger\n delay={100}\n closeDelay={100}\n render={<Button variant=\"outline\" className=\"w-fit\" />}\n >\n Hover me\n </HoverCardTrigger>\n <HoverCardContent>\n <div className=\"style-lyra:gap-1 style-nova:gap-1.5 style-vega:gap-2 style-maia:gap-2 style-mira:gap-1 flex flex-col\">\n <h4 className=\"font-medium\">Hover Card</h4>\n <p>\n This hover card appears inside a dialog. Hover over the button\n to see it.\n </p>\n </div>\n </HoverCardContent>\n </HoverCard>\n </DialogContent>\n </Dialog>\n </Example>\n )\n}\n",
|
||||
"type": "registry:example"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/base-lyra/ui/hover-card.tsx",
|
||||
"content": "\"use client\"\n\nimport { PreviewCard as PreviewCardPrimitive } from \"@base-ui/react/preview-card\"\n\nimport { cn } from \"@/registry/base-lyra/lib/utils\"\n\nfunction HoverCard({ ...props }: PreviewCardPrimitive.Root.Props) {\n return <PreviewCardPrimitive.Root data-slot=\"hover-card\" {...props} />\n}\n\nfunction HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) {\n return (\n <PreviewCardPrimitive.Trigger data-slot=\"hover-card-trigger\" {...props} />\n )\n}\n\nfunction HoverCardContent({\n className,\n side = \"bottom\",\n sideOffset = 4,\n align = \"center\",\n alignOffset = 4,\n ...props\n}: PreviewCardPrimitive.Popup.Props &\n Pick<\n PreviewCardPrimitive.Positioner.Props,\n \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\"\n >) {\n return (\n <PreviewCardPrimitive.Portal data-slot=\"hover-card-portal\">\n <PreviewCardPrimitive.Positioner\n align={align}\n alignOffset={alignOffset}\n side={side}\n sideOffset={sideOffset}\n className=\"isolate z-50\"\n >\n <PreviewCardPrimitive.Popup\n data-slot=\"hover-card-content\"\n className={cn(\n \"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground w-64 rounded-none p-2.5 text-xs/relaxed shadow-md ring-1 duration-100 z-50 origin-(--transform-origin) outline-hidden\",\n className\n )}\n {...props}\n />\n </PreviewCardPrimitive.Positioner>\n </PreviewCardPrimitive.Portal>\n )\n}\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent }\n",
|
||||
"content": "\"use client\"\n\nimport { PreviewCard as PreviewCardPrimitive } from \"@base-ui/react/preview-card\"\n\nimport { cn } from \"@/registry/base-lyra/lib/utils\"\n\nfunction HoverCard({ ...props }: PreviewCardPrimitive.Root.Props) {\n return <PreviewCardPrimitive.Root data-slot=\"hover-card\" {...props} />\n}\n\nfunction HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) {\n return (\n <PreviewCardPrimitive.Trigger data-slot=\"hover-card-trigger\" {...props} />\n )\n}\n\nfunction HoverCardContent({\n className,\n side = \"bottom\",\n sideOffset = 4,\n align = \"center\",\n alignOffset = 4,\n ...props\n}: PreviewCardPrimitive.Popup.Props &\n Pick<\n PreviewCardPrimitive.Positioner.Props,\n \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\"\n >) {\n return (\n <PreviewCardPrimitive.Portal data-slot=\"hover-card-portal\">\n <PreviewCardPrimitive.Positioner\n align={align}\n alignOffset={alignOffset}\n side={side}\n sideOffset={sideOffset}\n className=\"isolate z-50\"\n >\n <PreviewCardPrimitive.Popup\n data-slot=\"hover-card-content\"\n className={cn(\n \"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground w-64 rounded-none p-2.5 text-xs/relaxed shadow-md ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 origin-(--transform-origin) outline-hidden\",\n className\n )}\n {...props}\n />\n </PreviewCardPrimitive.Positioner>\n </PreviewCardPrimitive.Portal>\n )\n}\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent }\n",
|
||||
"type": "registry:ui"
|
||||
}
|
||||
],
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user