mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-17 21:01:51 +00:00
Compare commits
52 Commits
shadcn@3.0
...
shadcn@3.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9f3ce1988 | ||
|
|
cdf58be7e1 | ||
|
|
fae1a81add | ||
|
|
fc6d909ba2 | ||
|
|
590b9be610 | ||
|
|
41eb9d5c46 | ||
|
|
b7c28199be | ||
|
|
7869defd42 | ||
|
|
6daa5215cc | ||
|
|
722fb81b95 | ||
|
|
543be31722 | ||
|
|
09b90cd5c2 | ||
|
|
c95959a9b3 | ||
|
|
08820ce5ee | ||
|
|
cb96e58992 | ||
|
|
fce5926265 | ||
|
|
f7c0f81258 | ||
|
|
960b22b301 | ||
|
|
6f057c9cc3 | ||
|
|
615a32d97a | ||
|
|
bfe6e1946c | ||
|
|
baaa82e4e7 | ||
|
|
caeed7bd65 | ||
|
|
61254f0c3f | ||
|
|
3dcd797f2c | ||
|
|
b76f5cdbf7 | ||
|
|
fcb1e2ca50 | ||
|
|
df94537e0f | ||
|
|
275e3a2d59 | ||
|
|
e5402f9a20 | ||
|
|
04668da018 | ||
|
|
0805751703 | ||
|
|
9ecb19cf2e | ||
|
|
9c5eb0d20f | ||
|
|
2752ce11d8 | ||
|
|
d972caa853 | ||
|
|
00b2f0796e | ||
|
|
3ed9af5757 | ||
|
|
a4237e38f7 | ||
|
|
1178d40352 | ||
|
|
cc612359ee | ||
|
|
4d0272a659 | ||
|
|
a15534bdb7 | ||
|
|
62c41c3271 | ||
|
|
851c0fa0d1 | ||
|
|
e84c819977 | ||
|
|
64f8baf9aa | ||
|
|
4b44c6489a | ||
|
|
f9021e9388 | ||
|
|
b1e3d4b740 | ||
|
|
084fb927a1 | ||
|
|
7304ef2105 |
54
.github/workflows/validate-registries.yml
vendored
Normal file
54
.github/workflows/validate-registries.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: Validate Registries
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/v4/public/r/registries.json"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "apps/v4/public/r/registries.json"
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
name: pnpm validate:registries
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 9.0.6
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
run: |
|
||||
echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build packages
|
||||
run: pnpm build --filter=shadcn
|
||||
|
||||
- name: Validate registries
|
||||
run: pnpm --filter=v4 validate:registries
|
||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -3,12 +3,7 @@
|
||||
{ "pattern": "apps/*/" },
|
||||
{ "pattern": "packages/*/" }
|
||||
],
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["cva\\(((?:[^()]|\\([^()]*\\))*)\\)", "[\"'`]?([^\"'`]+)[\"'`]?"],
|
||||
["cn\\(((?:[^()]|\\([^()]*\\))*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
// "cva\\(([^)]*)\\)",
|
||||
// "[\"'`]([^\"'`]*).*?[\"'`]"
|
||||
],
|
||||
"tailwindCSS.classFunctions": ["cva", "cn"],
|
||||
"vitest.debugExclude": [
|
||||
"<node_internals>/**",
|
||||
"**/node_modules/**",
|
||||
|
||||
@@ -193,7 +193,7 @@ export default async function Page(props: {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[calc(100svh-var(--header-height)-var(--footer-height))] w-72 flex-col gap-4 overflow-hidden overscroll-none pb-8 xl:flex">
|
||||
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[calc(100svh-var(--footer-height)+2rem)] w-72 flex-col gap-4 overflow-hidden overscroll-none pb-8 xl:flex">
|
||||
<div className="h-(--top-spacing) shrink-0" />
|
||||
{/* @ts-expect-error - revisit fumadocs types. */}
|
||||
{doc.toc?.length ? (
|
||||
|
||||
@@ -6,8 +6,8 @@ import { Badge } from "@/registry/new-york-v4/ui/badge"
|
||||
export function Announcement() {
|
||||
return (
|
||||
<Badge asChild variant="secondary" className="rounded-full">
|
||||
<Link href="/docs/components/calendar">
|
||||
New Calendar Component <ArrowRightIcon />
|
||||
<Link href="/docs/changelog">
|
||||
Now available: shadcn CLI 3.0 and MCP Server <ArrowRightIcon />
|
||||
</Link>
|
||||
</Badge>
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import { IconArrowRight } from "@tabler/icons-react"
|
||||
import { CornerDownLeftIcon, SquareDashedIcon } from "lucide-react"
|
||||
|
||||
import { type Color, type ColorPalette } from "@/lib/colors"
|
||||
import { showMcpDocs } from "@/lib/flags"
|
||||
import { source } from "@/lib/source"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useConfig } from "@/hooks/use-config"
|
||||
@@ -214,6 +215,10 @@ export function CommandMenu({
|
||||
if (item.type === "page") {
|
||||
const isComponent = item.url.includes("/components/")
|
||||
|
||||
if (!showMcpDocs && item.url.includes("/mcp")) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandMenuItem
|
||||
key={item.url}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
|
||||
import { showMcpDocs } from "@/lib/flags"
|
||||
import type { source } from "@/lib/source"
|
||||
import {
|
||||
Sidebar,
|
||||
@@ -15,6 +16,28 @@ import {
|
||||
SidebarMenuItem,
|
||||
} from "@/registry/new-york-v4/ui/sidebar"
|
||||
|
||||
const TOP_LEVEL_SECTIONS = [
|
||||
{ name: "Get Started", href: "/docs" },
|
||||
{
|
||||
name: "Components",
|
||||
href: "/docs/components",
|
||||
},
|
||||
{
|
||||
name: "Registry",
|
||||
href: "/docs/registry",
|
||||
},
|
||||
{
|
||||
name: "MCP Server",
|
||||
href: "/docs/mcp",
|
||||
},
|
||||
{
|
||||
name: "Changelog",
|
||||
href: "/docs/changelog",
|
||||
},
|
||||
]
|
||||
const EXCLUDED_SECTIONS = ["installation", "dark-mode"]
|
||||
const EXCLUDED_PAGES = ["/docs", "/docs/changelog"]
|
||||
|
||||
export function DocsSidebar({
|
||||
tree,
|
||||
...props
|
||||
@@ -23,40 +46,90 @@ export function DocsSidebar({
|
||||
|
||||
return (
|
||||
<Sidebar
|
||||
className="sticky top-[calc(var(--header-height)+1px)] z-30 hidden h-[calc(100svh-var(--header-height)-var(--footer-height))] bg-transparent lg:flex"
|
||||
className="sticky top-[calc(var(--header-height)+1px)] z-30 hidden h-[calc(100svh-var(--footer-height)+2rem)] bg-transparent lg:flex"
|
||||
collapsible="none"
|
||||
{...props}
|
||||
>
|
||||
<SidebarContent className="no-scrollbar px-2 pb-12">
|
||||
<SidebarContent className="no-scrollbar overflow-x-hidden px-2 pb-12">
|
||||
<div className="h-(--top-spacing) shrink-0" />
|
||||
{tree.children.map((item) => (
|
||||
<SidebarGroup key={item.$id}>
|
||||
<SidebarGroupLabel className="text-muted-foreground font-medium">
|
||||
{item.name}
|
||||
</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
{item.type === "folder" && (
|
||||
<SidebarMenu className="gap-0.5">
|
||||
{item.children.map((item) => {
|
||||
return (
|
||||
item.type === "page" && (
|
||||
<SidebarMenuItem key={item.url}>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={item.url === pathname}
|
||||
className="data-[active=true]:bg-accent data-[active=true]:border-accent 3xl:fixed:w-full 3xl:fixed:max-w-48 relative h-[30px] w-fit overflow-visible border border-transparent text-[0.8rem] font-medium after:absolute after:inset-x-0 after:-inset-y-1 after:z-0 after:rounded-md"
|
||||
>
|
||||
<Link href={item.url}>{item.name}</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel className="text-muted-foreground font-medium">
|
||||
Sections
|
||||
</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{TOP_LEVEL_SECTIONS.map(({ name, href }) => {
|
||||
if (!showMcpDocs && href.includes("/mcp")) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<SidebarMenuItem key={name}>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={
|
||||
href === "/docs"
|
||||
? pathname === href
|
||||
: pathname.startsWith(href)
|
||||
}
|
||||
className="data-[active=true]:bg-accent data-[active=true]:border-accent 3xl:fixed:w-full 3xl:fixed:max-w-48 relative h-[30px] w-fit overflow-visible border border-transparent text-[0.8rem] font-medium after:absolute after:inset-x-0 after:-inset-y-1 after:z-0 after:rounded-md"
|
||||
>
|
||||
<Link href={href}>
|
||||
<span className="absolute inset-0 flex w-(--sidebar-width) bg-transparent" />
|
||||
{name}
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)
|
||||
})}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
{tree.children.map((item) => {
|
||||
if (EXCLUDED_SECTIONS.includes(item.$id ?? "")) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarGroup key={item.$id}>
|
||||
<SidebarGroupLabel className="text-muted-foreground font-medium">
|
||||
{item.name}
|
||||
</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
{item.type === "folder" && (
|
||||
<SidebarMenu className="gap-0.5">
|
||||
{item.children.map((item) => {
|
||||
if (
|
||||
!showMcpDocs &&
|
||||
item.type === "page" &&
|
||||
item.url?.includes("/mcp")
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
item.type === "page" &&
|
||||
!EXCLUDED_PAGES.includes(item.url) && (
|
||||
<SidebarMenuItem key={item.url}>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={item.url === pathname}
|
||||
className="data-[active=true]:bg-accent data-[active=true]:border-accent 3xl:fixed:w-full 3xl:fixed:max-w-48 relative h-[30px] w-fit overflow-visible border border-transparent text-[0.8rem] font-medium after:absolute after:inset-x-0 after:-inset-y-1 after:z-0 after:rounded-md"
|
||||
>
|
||||
<Link href={item.url}>
|
||||
<span className="absolute inset-0 flex w-(--sidebar-width) bg-transparent" />
|
||||
{item.name}
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)
|
||||
)
|
||||
)
|
||||
})}
|
||||
</SidebarMenu>
|
||||
)}
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
))}
|
||||
})}
|
||||
</SidebarMenu>
|
||||
)}
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
})}
|
||||
</SidebarContent>
|
||||
</Sidebar>
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as React from "react"
|
||||
import Link, { LinkProps } from "next/link"
|
||||
import { useRouter } from "next/navigation"
|
||||
|
||||
import { showMcpDocs } from "@/lib/flags"
|
||||
import { source } from "@/lib/source"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
@@ -13,6 +14,26 @@ import {
|
||||
PopoverTrigger,
|
||||
} from "@/registry/new-york-v4/ui/popover"
|
||||
|
||||
const TOP_LEVEL_SECTIONS = [
|
||||
{ name: "Get Started", href: "/docs" },
|
||||
{
|
||||
name: "Components",
|
||||
href: "/docs/components",
|
||||
},
|
||||
{
|
||||
name: "Registry",
|
||||
href: "/docs/registry",
|
||||
},
|
||||
{
|
||||
name: "MCP Server",
|
||||
href: "/docs/mcp",
|
||||
},
|
||||
{
|
||||
name: "Changelog",
|
||||
href: "/docs/changelog",
|
||||
},
|
||||
]
|
||||
|
||||
export function MobileNav({
|
||||
tree,
|
||||
items,
|
||||
@@ -79,6 +100,23 @@ export function MobileNav({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="text-muted-foreground text-sm font-medium">
|
||||
Sections
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{TOP_LEVEL_SECTIONS.map(({ name, href }) => {
|
||||
if (!showMcpDocs && href.includes("/mcp")) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<MobileLink key={name} href={href} onOpenChange={setOpen}>
|
||||
{name}
|
||||
</MobileLink>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-8">
|
||||
{tree?.children?.map((group, index) => {
|
||||
if (group.type === "folder") {
|
||||
@@ -90,6 +128,9 @@ export function MobileNav({
|
||||
<div className="flex flex-col gap-3">
|
||||
{group.children.map((item) => {
|
||||
if (item.type === "page") {
|
||||
if (!showMcpDocs && item.url.includes("/mcp")) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<MobileLink
|
||||
key={`${item.url}-${index}`}
|
||||
|
||||
@@ -4,6 +4,229 @@ description: Latest updates and announcements.
|
||||
toc: false
|
||||
---
|
||||
|
||||
## September 2025 - Registry Index
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## August 2025 - shadcn CLI 3.0 and MCP Server
|
||||
|
||||
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/design/{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.
|
||||
|
||||
---
|
||||
|
||||
## July 2025 - Universal Registry Items
|
||||
|
||||
We've added support for universal registry items. This allows you to create registry items that can be distributed to any project i.e. no framework, no components.json, no tailwind, no react required.
|
||||
@@ -12,6 +235,8 @@ This new registry item type unlocks a lot of new workflows. You can now distribu
|
||||
|
||||
See the [docs](/docs/registry/examples) for more details and examples.
|
||||
|
||||
---
|
||||
|
||||
## July 2025 - Local File Support
|
||||
|
||||
The shadcn CLI now supports local files. Initialize projects and add components, themes, hooks, utils and more from local JSON files.
|
||||
@@ -31,6 +256,8 @@ This feature enables powerful new workflows:
|
||||
- **Enhanced workflow for agents and MCP** - Generate and run registry items locally
|
||||
- **Private components** - Keep proprietary components local and private.
|
||||
|
||||
---
|
||||
|
||||
## June 2025 - `radix-ui`
|
||||
|
||||
We've added a new command to migrate to the new `radix-ui` package. This command will replace all `@radix-ui/react-*` imports with `radix-ui`.
|
||||
@@ -82,7 +309,7 @@ We're working on zero-config MCP support for shadcn/ui registry. One command `np
|
||||
className="mt-6 w-full overflow-hidden rounded-lg border"
|
||||
/>
|
||||
|
||||
Learn more in the thread here: https://x.com/shadcn/status/1917597228513853603
|
||||
Learn more in the [thread here](https://x.com/shadcn/status/1917597228513853603).
|
||||
|
||||
## March 2025 - shadcn 2.5.0
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ The `init` command installs dependencies, adds the `cn` util and configures CSS
|
||||
npx shadcn@latest init
|
||||
```
|
||||
|
||||
### Options
|
||||
**Options**
|
||||
|
||||
```bash
|
||||
Usage: shadcn init [options] [components...]
|
||||
@@ -34,9 +34,12 @@ Options:
|
||||
--no-src-dir do not use the src directory when creating a new project.
|
||||
--css-variables use css variables for theming. (default: true)
|
||||
--no-css-variables do not use css variables for theming.
|
||||
--no-base-style do not install the base shadcn style
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## add
|
||||
|
||||
Use the `add` command to add components and dependencies to your project.
|
||||
@@ -45,7 +48,7 @@ Use the `add` command to add components and dependencies to your project.
|
||||
npx shadcn@latest add [component]
|
||||
```
|
||||
|
||||
### Options
|
||||
**Options**
|
||||
|
||||
```bash
|
||||
Usage: shadcn add [options] [components...]
|
||||
@@ -69,6 +72,126 @@ Options:
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## view
|
||||
|
||||
Use the `view` command to view items from the registry before installing them.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest view [item]
|
||||
```
|
||||
|
||||
You can view multiple items at once:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest view button card dialog
|
||||
```
|
||||
|
||||
Or view items from namespaced registries:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest view @acme/auth @v0/dashboard
|
||||
```
|
||||
|
||||
**Options**
|
||||
|
||||
```bash
|
||||
Usage: shadcn view [options] <items...>
|
||||
|
||||
view items from the registry
|
||||
|
||||
Arguments:
|
||||
items the item names or URLs to view
|
||||
|
||||
Options:
|
||||
-c, --cwd <cwd> the working directory. defaults to the current directory.
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## search
|
||||
|
||||
Use the `search` command to search for items from registries.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest search [registry]
|
||||
```
|
||||
|
||||
You can search with a query:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest search @shadcn -q "button"
|
||||
```
|
||||
|
||||
Or search multiple registries at once:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest search @shadcn @v0 @acme
|
||||
```
|
||||
|
||||
The `list` command is an alias for `search`:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest list @acme
|
||||
```
|
||||
|
||||
**Options**
|
||||
|
||||
```bash
|
||||
Usage: shadcn search|list [options] <registries...>
|
||||
|
||||
search items from registries
|
||||
|
||||
Arguments:
|
||||
registries the registry names or urls to search items from. Names
|
||||
must be prefixed with @.
|
||||
|
||||
Options:
|
||||
-c, --cwd <cwd> the working directory. defaults to the current directory.
|
||||
-q, --query <query> query string
|
||||
-l, --limit <number> maximum number of items to display per registry (default: "100")
|
||||
-o, --offset <number> number of items to skip (default: "0")
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## list
|
||||
|
||||
Use the `list` command to list all items from a registry.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest list @acme
|
||||
```
|
||||
|
||||
**Options**
|
||||
|
||||
```bash
|
||||
Usage: shadcn list [options] <registries...>
|
||||
|
||||
list items from registries
|
||||
|
||||
Arguments:
|
||||
registries the registry names or urls to list items from. Names
|
||||
must be prefixed with @.
|
||||
```
|
||||
|
||||
**Options**
|
||||
|
||||
```bash
|
||||
Usage: shadcn list [options] <registries...>
|
||||
|
||||
list items from registries
|
||||
|
||||
Arguments:
|
||||
registries the registry names or urls to list items from. Names
|
||||
must be prefixed with @.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## build
|
||||
|
||||
Use the `build` command to generate the registry JSON files.
|
||||
@@ -79,7 +202,7 @@ npx shadcn@latest build
|
||||
|
||||
This command reads the `registry.json` file and generates the registry JSON files in the `public/r` directory.
|
||||
|
||||
### Options
|
||||
**Options**
|
||||
|
||||
```bash
|
||||
Usage: shadcn build [options] [registry]
|
||||
|
||||
@@ -210,3 +210,94 @@ Import alias for `hooks` such as `use-media-query` or `use-toast`.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## registries
|
||||
|
||||
Configure multiple resource registries for your project. This allows you to install components, libraries, utilities, and other resources from various sources including private registries.
|
||||
|
||||
See the <Link href="/docs/registry/namespace">Namespaced Registries</Link> documentation for detailed information.
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
Configure registries with URL templates:
|
||||
|
||||
```json title="components.json"
|
||||
{
|
||||
"registries": {
|
||||
"@v0": "https://v0.dev/chat/b/{name}",
|
||||
"@acme": "https://registry.acme.com/{name}.json",
|
||||
"@internal": "https://internal.company.com/{name}.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `{name}` placeholder is replaced with the resource name when installing.
|
||||
|
||||
### Advanced Configuration with Authentication
|
||||
|
||||
For private registries that require authentication:
|
||||
|
||||
```json title="components.json"
|
||||
{
|
||||
"registries": {
|
||||
"@private": {
|
||||
"url": "https://api.company.com/registry/{name}.json",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${REGISTRY_TOKEN}",
|
||||
"X-API-Key": "${API_KEY}"
|
||||
},
|
||||
"params": {
|
||||
"version": "latest"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Environment variables in the format `${VAR_NAME}` are automatically expanded from your environment.
|
||||
|
||||
### Using Namespaced Registries
|
||||
|
||||
Once configured, install resources using the namespace syntax:
|
||||
|
||||
```bash
|
||||
# Install from a configured registry
|
||||
npx shadcn@latest add @v0/dashboard
|
||||
|
||||
# Install from private registry
|
||||
npx shadcn@latest add @private/button
|
||||
|
||||
# Install multiple resources
|
||||
npx shadcn@latest add @acme/header @internal/auth-utils
|
||||
```
|
||||
|
||||
### Example: Multiple Registry Setup
|
||||
|
||||
```json title="components.json"
|
||||
{
|
||||
"registries": {
|
||||
"@shadcn": "https://ui.shadcn.com/r/{name}.json",
|
||||
"@company-ui": {
|
||||
"url": "https://registry.company.com/ui/{name}.json",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${COMPANY_TOKEN}"
|
||||
}
|
||||
},
|
||||
"@team": {
|
||||
"url": "https://team.company.com/{name}.json",
|
||||
"params": {
|
||||
"team": "frontend",
|
||||
"version": "${REGISTRY_VERSION}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This configuration allows you to:
|
||||
|
||||
- Install public components from shadcn/ui
|
||||
- Access private company UI components with authentication
|
||||
- Use team-specific resources with versioning
|
||||
|
||||
For more information about authentication, see the <Link href="/docs/registry/authentication">Authentication</Link> documentation.
|
||||
|
||||
281
apps/v4/content/docs/(root)/mcp.mdx
Normal file
281
apps/v4/content/docs/(root)/mcp.mdx
Normal file
@@ -0,0 +1,281 @@
|
||||
---
|
||||
title: MCP Server
|
||||
description: Use the shadcn MCP server to browse, search, and install components from registries.
|
||||
---
|
||||
|
||||
The shadcn MCP Server allows AI assistants to interact with items from registries. You can browse available components, search for specific ones, and install them directly into your project using natural language.
|
||||
|
||||
For example, you can ask an AI assistant to "Build a landing page using components from the acme registry" or "Find me a login form from the shadcn registry".
|
||||
|
||||
Registries are configured in your project's `components.json` file.
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"registries": {
|
||||
"@acme": "https://acme.com/r/{name}.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
Select your MCP client and follow the instructions to configure the shadcn MCP server. If you'd like to do it manually, see the [Configuration](#configuration) section.
|
||||
|
||||
<Tabs defaultValue="claude">
|
||||
<TabsList>
|
||||
<TabsTrigger value="claude">Claude Code</TabsTrigger>
|
||||
<TabsTrigger value="cursor">Cursor</TabsTrigger>
|
||||
<TabsTrigger value="vscode">VS Code</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="claude" className="mt-4">
|
||||
**Run the following command** in your project:
|
||||
```bash
|
||||
npx shadcn@latest mcp init --client claude
|
||||
```
|
||||
|
||||
**Restart Claude Code** and try the following prompts:
|
||||
- Show me all available components in the shadcn registry
|
||||
- Add the button, dialog and card components to my project
|
||||
- Create a contact form using components from the shadcn registry
|
||||
|
||||
**Note:** You can use `/mcp` command in Claude Code to debug the MCP server.
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="cursor" className="mt-4">
|
||||
**Run the following command** in your project:
|
||||
```bash
|
||||
npx shadcn@latest mcp init --client cursor
|
||||
```
|
||||
|
||||
Open **Cursor Settings** and **Enable the MCP server** for shadcn. Then try the following prompts:
|
||||
- Show me all available components in the shadcn registry
|
||||
- Add the button, dialog and card components to my project
|
||||
- Create a contact form using components from the shadcn registry
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="vscode" className="mt-4">
|
||||
**Run the following command** in your project:
|
||||
```bash
|
||||
npx shadcn@latest mcp init --client vscode
|
||||
```
|
||||
|
||||
Open `.vscode/mcp.json` and click **Start** next to the shadcn server. Then try the following prompts with GitHub Copilot:
|
||||
- Show me all available components in the shadcn registry
|
||||
- Add the button, dialog and card components to my project
|
||||
- Create a contact form using components from the shadcn registry
|
||||
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## What is MCP?
|
||||
|
||||
[Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open protocol that enables AI assistants to securely connect to external data sources and tools. With the shadcn MCP server, your AI assistant gains direct access to:
|
||||
|
||||
- **Browse Components** - List all available components, blocks, and templates from any configured registry
|
||||
- **Search Across Registries** - Find specific components by name or functionality across multiple sources
|
||||
- **Install with Natural Language** - Add components using simple conversational prompts like "add a login form"
|
||||
- **Support for Multiple Registries** - Access public registries, private company libraries, and third-party sources
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
The MCP server acts as a bridge between your AI assistant, component registries and the shadcn CLI.
|
||||
|
||||
1. **Registry Connection** - MCP connects to configured registries (shadcn/ui, private registries, third-party sources)
|
||||
2. **Natural Language** - You describe what you need in plain English
|
||||
3. **AI Processing** - The assistant translates your request into registry commands
|
||||
4. **Component Delivery** - Resources are fetched and installed in your project
|
||||
|
||||
---
|
||||
|
||||
## Supported Registries
|
||||
|
||||
The shadcn MCP server works out of the box with any shadcn-compatible registry.
|
||||
|
||||
- **shadcn/ui Registry** - The default registry with all shadcn/ui components
|
||||
- **Third-Party Registries** - Any registry following the shadcn registry specification
|
||||
- **Private Registries** - Your company's internal component libraries
|
||||
- **Namespaced Registries** - Multiple registries configured with `@namespace` syntax
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
You can use any MCP client to interact with the shadcn MCP server. Here are the instructions for the most popular ones.
|
||||
|
||||
### Claude Code
|
||||
|
||||
To use the shadcn MCP server with Claude Code, add the following configuration to your project's `.mcp.json` file:
|
||||
|
||||
```json title=".mcp.json" showLineNumbers
|
||||
{
|
||||
"mcpServers": {
|
||||
"shadcn": {
|
||||
"command": "npx",
|
||||
"args": ["shadcn@latest", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After adding the configuration, restart Claude Code and run `/mcp` to see the shadcn MCP server in the list. If you see `Connected`, you're good to go.
|
||||
|
||||
See the [Claude Code MCP documentation](https://docs.anthropic.com/en/docs/claude-code/mcp) for more details.
|
||||
|
||||
### Cursor
|
||||
|
||||
To configure MCP in Cursor, add the shadcn server to your project's `.cursor/mcp.json` configuration file:
|
||||
|
||||
```json title=".cursor/mcp.json" showLineNumbers
|
||||
{
|
||||
"mcpServers": {
|
||||
"shadcn": {
|
||||
"command": "npx",
|
||||
"args": ["shadcn@latest", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After adding the configuration, enable the shadcn MCP server in Cursor Settings.
|
||||
|
||||
Once enabled, you should see a green dot next to the shadcn server in the MCP server list and a list of available tools.
|
||||
|
||||
See the [Cursor MCP documentation](https://docs.cursor.com/en/context/mcp#using-mcp-json) for more details.
|
||||
|
||||
### VS Code
|
||||
|
||||
To configure MCP in VS Code with GitHub Copilot, add the shadcn server to your project's `.vscode/mcp.json` configuration file:
|
||||
|
||||
```json title=".vscode/mcp.json" showLineNumbers
|
||||
{
|
||||
"mcpServers": {
|
||||
"shadcn": {
|
||||
"command": "npx",
|
||||
"args": ["shadcn@latest", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After adding the configuration, open `.vscode/mcp.json` and click **Start** next to the shadcn server.
|
||||
|
||||
See the [VS Code MCP documentation](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) for more details.
|
||||
|
||||
---
|
||||
|
||||
## Configuring Registries
|
||||
|
||||
The MCP server supports multiple registries through your project's `components.json` configuration. This allows you to access components from various sources including private registries and third-party providers.
|
||||
|
||||
Configure additional registries in your `components.json`:
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"registries": {
|
||||
"@acme": "https://registry.acme.com/{name}.json",
|
||||
"@internal": {
|
||||
"url": "https://internal.company.com/{name}.json",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${REGISTRY_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Callout>
|
||||
**Note:** No configuration is needed to access the standard shadcn/ui
|
||||
registry.
|
||||
</Callout>
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
For private registries requiring authentication, set environment variables in your `.env.local`:
|
||||
|
||||
```bash title=".env.local"
|
||||
REGISTRY_TOKEN=your_token_here
|
||||
API_KEY=your_api_key_here
|
||||
```
|
||||
|
||||
For more details on registry authentication, see the [Authentication documentation](/docs/registry/authentication).
|
||||
|
||||
---
|
||||
|
||||
## Example Prompts
|
||||
|
||||
Once the MCP server is configured, you can use natural language to interact with registries. Try one of the following prompts:
|
||||
|
||||
### Browse & Search
|
||||
|
||||
- Show me all available components in the shadcn registry
|
||||
- Find me a login form from the shadcn registry
|
||||
|
||||
### Install Items
|
||||
|
||||
- Add the button component to my project
|
||||
- Create a login form using shadcn components
|
||||
- Install the Cursor rules from the acme registry
|
||||
|
||||
### Work with Namespaces
|
||||
|
||||
- Show me components from acme registry
|
||||
- Install @internal/auth-form
|
||||
- Build me a landing page using hero, features and testimonials sections from the acme registry
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### MCP Not Responding
|
||||
|
||||
If the MCP server isn't responding to prompts:
|
||||
|
||||
1. **Check Configuration** - Verify the MCP server is properly configured and enabled in your MCP client
|
||||
2. **Restart MCP Client** - Restart your MCP client after configuration changes
|
||||
3. **Verify Installation** - Ensure `shadcn` is installed in your project
|
||||
4. **Check Network** - Confirm you can access the configured registries
|
||||
|
||||
### Registry Access Issues
|
||||
|
||||
If components aren't loading from registries:
|
||||
|
||||
1. **Check components.json** - Verify registry URLs are correct
|
||||
2. **Test Authentication** - Ensure environment variables are set for private registries
|
||||
3. **Verify Registry** - Confirm the registry is online and accessible
|
||||
4. **Check Namespace** - Ensure namespace syntax is correct (`@namespace/component`)
|
||||
|
||||
### Installation Failures
|
||||
|
||||
If components fail to install:
|
||||
|
||||
1. **Check Project Setup** - Ensure you have a valid `components.json` file
|
||||
2. **Verify Paths** - Confirm the target directories exist
|
||||
3. **Check Permissions** - Ensure write permissions for component directories
|
||||
4. **Review Dependencies** - Check that required dependencies are installed
|
||||
|
||||
### No Tools or Prompts
|
||||
|
||||
If you see the `No tools or prompts` message, try the following:
|
||||
|
||||
1. **Clear the npx cache** - Run `npx clear-npx-cache`
|
||||
2. **Re-enable the MCP server** - Try to re-enable the MCP server in your MCP client
|
||||
3. **Check Logs** - In Cursor, you can see the logs under View -> Output and select `MCP: project-*` in the dropdown.
|
||||
|
||||
---
|
||||
|
||||
## Learn More
|
||||
|
||||
- [Registry Documentation](/docs/registry) - Complete guide to shadcn registries
|
||||
- [Namespaces](/docs/registry/namespace) - Configure multiple registry sources
|
||||
- [Authentication](/docs/registry/authentication) - Secure your private registries
|
||||
- [MCP Specification](https://modelcontextprotocol.io) - Learn about Model Context Protocol
|
||||
@@ -5,7 +5,7 @@ description: Adding dark mode to your vite app.
|
||||
|
||||
## Create a theme provider
|
||||
|
||||
```tsx title="components/theme-provider.tsx"
|
||||
```tsx title="components/theme-provider.tsx" showLineNumbers
|
||||
import { createContext, useContext, useEffect, useState } from "react"
|
||||
|
||||
type Theme = "dark" | "light" | "system"
|
||||
@@ -85,7 +85,7 @@ export const useTheme = () => {
|
||||
|
||||
Add the `ThemeProvider` to your root layout.
|
||||
|
||||
```tsx {1,5-7} title="App.tsx"
|
||||
```tsx {1,5-7} title="App.tsx" showLineNumbers
|
||||
import { ThemeProvider } from "@/components/theme-provider"
|
||||
|
||||
function App() {
|
||||
@@ -103,7 +103,7 @@ export default App
|
||||
|
||||
Place a mode toggle on your site to toggle between light and dark mode.
|
||||
|
||||
```tsx title="components/mode-toggle.tsx"
|
||||
```tsx title="components/mode-toggle.tsx" showLineNumbers
|
||||
import { Moon, Sun } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
399
apps/v4/content/docs/registry/authentication.mdx
Normal file
399
apps/v4/content/docs/registry/authentication.mdx
Normal file
@@ -0,0 +1,399 @@
|
||||
---
|
||||
title: Authentication
|
||||
description: Secure your registry with authentication for private and personalized components.
|
||||
---
|
||||
|
||||
Authentication lets you run private registries, control who can access your components, and give different teams or users different content. This guide shows common authentication patterns and how to set them up.
|
||||
|
||||
Authentication enables these use cases:
|
||||
|
||||
- **Private Components**: Keep your business logic and internal components secure
|
||||
- **Team-Specific Resources**: Give different teams different components
|
||||
- **Access Control**: Limit who can see sensitive or experimental components
|
||||
- **Usage Analytics**: See who's using which components in your organization
|
||||
- **Licensing**: Control who gets premium or licensed components
|
||||
|
||||
## Common Authentication Patterns
|
||||
|
||||
### Token-Based Authentication
|
||||
|
||||
The most common approach uses Bearer tokens or API keys:
|
||||
|
||||
```json title="components.json"
|
||||
{
|
||||
"registries": {
|
||||
"@private": {
|
||||
"url": "https://registry.company.com/{name}.json",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${REGISTRY_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Set your token in environment variables:
|
||||
|
||||
```bash title=".env.local"
|
||||
REGISTRY_TOKEN=your_secret_token_here
|
||||
```
|
||||
|
||||
### API Key Authentication
|
||||
|
||||
Some registries use API keys in headers:
|
||||
|
||||
```json title="components.json"
|
||||
{
|
||||
"registries": {
|
||||
"@company": {
|
||||
"url": "https://api.company.com/registry/{name}.json",
|
||||
"headers": {
|
||||
"X-API-Key": "${API_KEY}",
|
||||
"X-Workspace-Id": "${WORKSPACE_ID}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Query Parameter Authentication
|
||||
|
||||
For simpler setups, use query parameters:
|
||||
|
||||
```json title="components.json"
|
||||
{
|
||||
"registries": {
|
||||
"@internal": {
|
||||
"url": "https://registry.company.com/{name}.json",
|
||||
"params": {
|
||||
"token": "${ACCESS_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This creates: `https://registry.company.com/button.json?token=your_token`
|
||||
|
||||
## Server-Side Implementation
|
||||
|
||||
Here's how to add authentication to your registry server:
|
||||
|
||||
### Next.js API Route Example
|
||||
|
||||
```typescript title="app/api/registry/[name]/route.ts"
|
||||
import { NextRequest, NextResponse } from "next/server"
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { name: string } }
|
||||
) {
|
||||
// Get token from Authorization header.
|
||||
const authHeader = request.headers.get("authorization")
|
||||
const token = authHeader?.replace("Bearer ", "")
|
||||
|
||||
// Or from query parameters.
|
||||
const queryToken = request.nextUrl.searchParams.get("token")
|
||||
|
||||
// Check if token is valid.
|
||||
if (!isValidToken(token || queryToken)) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||
}
|
||||
|
||||
// Check if token can access this component.
|
||||
if (!hasAccessToComponent(token, params.name)) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 })
|
||||
}
|
||||
|
||||
// Return the component.
|
||||
const component = await getComponent(params.name)
|
||||
return NextResponse.json(component)
|
||||
}
|
||||
|
||||
function isValidToken(token: string | null) {
|
||||
// Add your token validation logic here.
|
||||
// Check against database, JWT validation, etc.
|
||||
return token === process.env.VALID_TOKEN
|
||||
}
|
||||
|
||||
function hasAccessToComponent(token: string, componentName: string) {
|
||||
// Add role-based access control here.
|
||||
// Check if token can access specific component.
|
||||
return true // Your logic here.
|
||||
}
|
||||
```
|
||||
|
||||
### Express.js Example
|
||||
|
||||
```javascript title="server.js"
|
||||
app.get("/registry/:name.json", (req, res) => {
|
||||
const token = req.headers.authorization?.replace("Bearer ", "")
|
||||
|
||||
if (!isValidToken(token)) {
|
||||
return res.status(401).json({ error: "Unauthorized" })
|
||||
}
|
||||
|
||||
const component = getComponent(req.params.name)
|
||||
if (!component) {
|
||||
return res.status(404).json({ error: "Component not found" })
|
||||
}
|
||||
|
||||
res.json(component)
|
||||
})
|
||||
```
|
||||
|
||||
## Advanced Authentication Patterns
|
||||
|
||||
### Team-Based Access
|
||||
|
||||
Give different teams different components:
|
||||
|
||||
```typescript title="api/registry/route.ts"
|
||||
async function GET(request: NextRequest) {
|
||||
const token = extractToken(request)
|
||||
const team = await getTeamFromToken(token)
|
||||
|
||||
// Get components for this team.
|
||||
const components = await getComponentsForTeam(team)
|
||||
return NextResponse.json(components)
|
||||
}
|
||||
```
|
||||
|
||||
### User-Personalized Registries
|
||||
|
||||
Give users components based on their preferences:
|
||||
|
||||
```typescript
|
||||
async function GET(request: NextRequest) {
|
||||
const user = await authenticateUser(request)
|
||||
|
||||
// Get user's style and framework preferences.
|
||||
const preferences = await getUserPreferences(user.id)
|
||||
|
||||
// Get personalized component version.
|
||||
const component = await getPersonalizedComponent(params.name, preferences)
|
||||
|
||||
return NextResponse.json(component)
|
||||
}
|
||||
```
|
||||
|
||||
### Temporary Access Tokens
|
||||
|
||||
Use expiring tokens for better security:
|
||||
|
||||
```typescript
|
||||
interface TemporaryToken {
|
||||
token: string
|
||||
expiresAt: Date
|
||||
scope: string[]
|
||||
}
|
||||
|
||||
async function validateTemporaryToken(token: string) {
|
||||
const tokenData = await getTokenData(token)
|
||||
|
||||
if (!tokenData) return false
|
||||
if (new Date() > tokenData.expiresAt) return false
|
||||
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-Registry Authentication
|
||||
|
||||
With [namespaced registries](/docs/registry/namespace), you can set up multiple registries with different authentication:
|
||||
|
||||
```json title="components.json"
|
||||
{
|
||||
"registries": {
|
||||
"@public": "https://public.company.com/{name}.json",
|
||||
"@internal": {
|
||||
"url": "https://internal.company.com/{name}.json",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${INTERNAL_TOKEN}"
|
||||
}
|
||||
},
|
||||
"@premium": {
|
||||
"url": "https://premium.company.com/{name}.json",
|
||||
"headers": {
|
||||
"X-License-Key": "${LICENSE_KEY}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This lets you:
|
||||
|
||||
- Mix public and private registries
|
||||
- Use different authentication per registry
|
||||
- Organize components by access level
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Use Environment Variables
|
||||
|
||||
Never commit tokens to version control. Always use environment variables:
|
||||
|
||||
```bash title=".env.local"
|
||||
REGISTRY_TOKEN=your_secret_token_here
|
||||
API_KEY=your_api_key_here
|
||||
```
|
||||
|
||||
Then reference them in `components.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"registries": {
|
||||
"@private": {
|
||||
"url": "https://registry.company.com/{name}.json",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${REGISTRY_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Use HTTPS
|
||||
|
||||
Always use HTTPS URLs for registries to protect your tokens in transit:
|
||||
|
||||
```json
|
||||
{
|
||||
"@secure": "https://registry.company.com/{name}.json" // ✅
|
||||
"@insecure": "http://registry.company.com/{name}.json" // ❌
|
||||
}
|
||||
```
|
||||
|
||||
### Add Rate Limiting
|
||||
|
||||
Protect your registry from abuse:
|
||||
|
||||
```typescript
|
||||
import rateLimit from "express-rate-limit"
|
||||
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 100, // limit each IP to 100 requests per windowMs
|
||||
})
|
||||
|
||||
app.use("/registry", limiter)
|
||||
```
|
||||
|
||||
### Rotate Tokens
|
||||
|
||||
Change access tokens regularly:
|
||||
|
||||
```typescript
|
||||
// Create new token with expiration.
|
||||
function generateToken() {
|
||||
const token = crypto.randomBytes(32).toString("hex")
|
||||
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // 30 days.
|
||||
|
||||
return { token, expiresAt }
|
||||
}
|
||||
```
|
||||
|
||||
### Log Access
|
||||
|
||||
Track registry access for security and analytics:
|
||||
|
||||
```typescript
|
||||
async function logAccess(request: Request, component: string, userId: string) {
|
||||
await db.accessLog.create({
|
||||
timestamp: new Date(),
|
||||
userId,
|
||||
component,
|
||||
ip: request.ip,
|
||||
userAgent: request.headers["user-agent"],
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Authentication
|
||||
|
||||
Test your authenticated registry locally:
|
||||
|
||||
```bash
|
||||
# Test with curl.
|
||||
curl -H "Authorization: Bearer your_token" \
|
||||
https://registry.company.com/button.json
|
||||
|
||||
# Test with the CLI.
|
||||
REGISTRY_TOKEN=your_token npx shadcn@latest add @private/button
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The shadcn CLI handles authentication errors gracefully:
|
||||
|
||||
- **401 Unauthorized**: Token is invalid or missing
|
||||
- **403 Forbidden**: Token lacks permission for this resource
|
||||
- **429 Too Many Requests**: Rate limit exceeded
|
||||
|
||||
### Custom Error Messages
|
||||
|
||||
Your registry server can return custom error messages in the response body, and the CLI will display them to users:
|
||||
|
||||
```typescript
|
||||
// Registry server returns custom error
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
message:
|
||||
"Your subscription has expired. Please renew at company.com/billing",
|
||||
},
|
||||
{ status: 403 }
|
||||
)
|
||||
```
|
||||
|
||||
The user will see:
|
||||
|
||||
```txt
|
||||
Your subscription has expired. Please renew at company.com/billing
|
||||
```
|
||||
|
||||
This helps provide context-specific guidance:
|
||||
|
||||
```typescript
|
||||
// Different error messages for different scenarios
|
||||
if (!token) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
message:
|
||||
"Authentication required. Set REGISTRY_TOKEN in your .env.local file",
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
if (isExpiredToken(token)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
message: "Token expired. Request a new token at company.com/tokens",
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
if (!hasTeamAccess(token, component)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Forbidden",
|
||||
message: `Component '${component}' is restricted to the Design team`,
|
||||
},
|
||||
{ status: 403 }
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
To set up authentication with multiple registries and advanced patterns, see the [Namespaced Registries](/docs/registry/namespace) documentation. It covers:
|
||||
|
||||
- Setting up multiple authenticated registries
|
||||
- Using different authentication per namespace
|
||||
- Cross-registry dependency resolution
|
||||
- Advanced authentication patterns
|
||||
@@ -328,16 +328,153 @@ Add custom theme variables to the `theme` object.
|
||||
}
|
||||
```
|
||||
|
||||
## Add CSS imports
|
||||
|
||||
Use `@import` to add CSS imports to your registry item. The imports will be placed at the top of the CSS file.
|
||||
|
||||
### Basic import
|
||||
|
||||
```json title="example-import.json" showLineNumbers
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "custom-import",
|
||||
"type": "registry:component",
|
||||
"css": {
|
||||
"@import \"tailwindcss\"": {},
|
||||
"@import \"./styles/base.css\"": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Import with url() syntax
|
||||
|
||||
```json title="example-url-import.json" showLineNumbers
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "font-import",
|
||||
"type": "registry:item",
|
||||
"css": {
|
||||
"@import url(\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap\")": {},
|
||||
"@import url('./local-styles.css')": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Import with media queries
|
||||
|
||||
```json title="example-media-import.json" showLineNumbers
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "responsive-import",
|
||||
"type": "registry:item",
|
||||
"css": {
|
||||
"@import \"print-styles.css\" print": {},
|
||||
"@import url(\"mobile.css\") screen and (max-width: 768px)": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Add custom plugins
|
||||
|
||||
Use `@plugin` to add Tailwind plugins to your registry item. Plugins will be automatically placed after imports and before other content.
|
||||
|
||||
**Important:** When using plugins from npm packages, you must also add them to the `dependencies` array.
|
||||
|
||||
### Basic plugin usage
|
||||
|
||||
```json title="example-plugin.json" showLineNumbers
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "custom-plugin",
|
||||
"type": "registry:item",
|
||||
"css": {
|
||||
"@plugin \"@tailwindcss/typography\"": {},
|
||||
"@plugin \"foo\"": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Plugin with npm dependency
|
||||
|
||||
When using plugins from npm packages like `@tailwindcss/typography`, include them in the dependencies.
|
||||
|
||||
```json title="example-typography.json" showLineNumbers
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "typography-component",
|
||||
"type": "registry:item",
|
||||
"dependencies": ["@tailwindcss/typography"],
|
||||
"css": {
|
||||
"@plugin \"@tailwindcss/typography\"": {},
|
||||
"@layer components": {
|
||||
".prose": {
|
||||
"max-width": "65ch"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Scoped and file-based plugins
|
||||
|
||||
```json title="example-scoped-plugin.json" showLineNumbers
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "scoped-plugins",
|
||||
"type": "registry:component",
|
||||
"css": {
|
||||
"@plugin @tailwindcss/typography": {},
|
||||
"@plugin foo": {}
|
||||
"@plugin \"@headlessui/tailwindcss\"": {},
|
||||
"@plugin \"tailwindcss/plugin\"": {},
|
||||
"@plugin \"./custom-plugin.js\"": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple plugins with automatic ordering
|
||||
|
||||
When you add multiple plugins, they are automatically grouped together and deduplicated.
|
||||
|
||||
```json title="example-multiple-plugins.json" showLineNumbers
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "multiple-plugins",
|
||||
"type": "registry:item",
|
||||
"dependencies": [
|
||||
"@tailwindcss/typography",
|
||||
"@tailwindcss/forms",
|
||||
"tw-animate-css"
|
||||
],
|
||||
"css": {
|
||||
"@plugin \"@tailwindcss/typography\"": {},
|
||||
"@plugin \"@tailwindcss/forms\"": {},
|
||||
"@plugin \"tw-animate-css\"": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Combined imports and plugins
|
||||
|
||||
When using both `@import` and `@plugin` directives, imports are placed first, followed by plugins, then other CSS content.
|
||||
|
||||
```json title="example-combined.json" showLineNumbers
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "combined-example",
|
||||
"type": "registry:item",
|
||||
"dependencies": ["@tailwindcss/typography", "tw-animate-css"],
|
||||
"css": {
|
||||
"@import \"tailwindcss\"": {},
|
||||
"@import url(\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap\")": {},
|
||||
"@plugin \"@tailwindcss/typography\"": {},
|
||||
"@plugin \"tw-animate-css\"": {},
|
||||
"@layer base": {
|
||||
"body": {
|
||||
"font-family": "Inter, sans-serif"
|
||||
}
|
||||
},
|
||||
"@utility content-auto": {
|
||||
"content-visibility": "auto"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -3,23 +3,27 @@ title: Getting Started
|
||||
description: Learn how to get setup and run your own component registry.
|
||||
---
|
||||
|
||||
This guide will walk you through the process of setting up your own component registry.
|
||||
|
||||
It assumes you already have a project with components and would like to turn it into a registry.
|
||||
This guide will walk you through the process of setting up your own component registry. It assumes you already have a project with components and would like to turn it into a registry.
|
||||
|
||||
If you're starting a new registry project, you can use the [registry template](https://github.com/shadcn-ui/registry-template) as a starting point. We have already configured it for you.
|
||||
|
||||
## Requirements
|
||||
|
||||
You are free to design and host your custom registry as you see fit. The only requirement is that your registry items must be valid JSON files that conform to the [registry-item schema specification](/docs/registry/registry-item-json).
|
||||
|
||||
If you'd like to see an example of a registry, we have a [template project](https://github.com/shadcn-ui/registry-template) for you to use as a starting point.
|
||||
|
||||
## registry.json
|
||||
|
||||
The `registry.json` file is only required if you're using the `shadcn` CLI to build your registry.
|
||||
The `registry.json` is the entry point for the registry. It contains the registry's name, homepage, and defines all the items present in the registry.
|
||||
|
||||
If you're using a different build system, you can skip this step as long as your build system produces valid JSON files that conform to the [registry-item schema specification](/docs/registry/registry-item-json).
|
||||
Your registry must have this file (or JSON payload) present at the root of the registry endpoint. The registry endpoint is the URL where your registry is hosted.
|
||||
|
||||
<Steps>
|
||||
The `shadcn` CLI will automatically generate this file for you when you run the `build` command.
|
||||
|
||||
### Add a registry.json file
|
||||
## Add a registry.json file
|
||||
|
||||
Create a `registry.json` file in the root of your project. Your project can be a Next.js, Remix, Vite, or any other project that supports React.
|
||||
Create a `registry.json` file in the root of your project. Your project can be a Next.js, Vite, Vue, Svelte, PHP or any other framework as long as it supports serving JSON over HTTP.
|
||||
|
||||
```json title="registry.json" showLineNumbers
|
||||
{
|
||||
@@ -34,12 +38,8 @@ Create a `registry.json` file in the root of your project. Your project can be a
|
||||
|
||||
This `registry.json` file must conform to the [registry schema specification](/docs/registry/registry-json).
|
||||
|
||||
</Steps>
|
||||
|
||||
## Add a registry item
|
||||
|
||||
<Steps>
|
||||
|
||||
### Create your component
|
||||
|
||||
Add your first component. Here's an example of a simple `<HelloWorld />` component:
|
||||
@@ -98,16 +98,10 @@ For every file you add, you must specify the `path` and `type` of the file. The
|
||||
|
||||
You can read more about the registry item schema and file types in the [registry item schema docs](/docs/registry/registry-item-json).
|
||||
|
||||
</Steps>
|
||||
|
||||
## Build your registry
|
||||
|
||||
<Steps>
|
||||
|
||||
### Install the shadcn CLI
|
||||
|
||||
Note: the `build` command is currently only available in the `shadcn@canary` version of the CLI.
|
||||
|
||||
```bash
|
||||
npm install shadcn@canary
|
||||
```
|
||||
@@ -140,8 +134,6 @@ You can change the output directory by passing the `--output` option. See the [s
|
||||
|
||||
</Callout>
|
||||
|
||||
</Steps>
|
||||
|
||||
## Serve your registry
|
||||
|
||||
If you're running your registry on Next.js, you can now serve your registry by running the `next` server. The command might differ for other frameworks.
|
||||
@@ -156,24 +148,13 @@ Your files will now be served at `http://localhost:3000/r/[NAME].json` eg. `http
|
||||
|
||||
To make your registry available to other developers, you can publish it by deploying your project to a public URL.
|
||||
|
||||
## Adding Auth
|
||||
|
||||
The `shadcn` CLI does not offer a built-in way to add auth to your registry. We recommend handling authorization on your registry server.
|
||||
|
||||
A common simple approach is to use a `token` query parameter to authenticate requests to your registry. e.g. `http://localhost:3000/r/hello-world.json?token=[SECURE_TOKEN_HERE]`.
|
||||
|
||||
Use the secure token to authenticate requests and return a 401 Unauthorized response if the token is invalid. Both the `shadcn` CLI and `Open in v0` will handle the 401 response and display a message to the user.
|
||||
|
||||
<Callout className="mt-6">
|
||||
**Note:** Make sure to encrypt and expire tokens.
|
||||
</Callout>
|
||||
|
||||
## Guidelines
|
||||
|
||||
Here are some guidelines to follow when building components for a registry.
|
||||
|
||||
- Place your registry item in the `registry/[STYLE]/[NAME]` directory. I'm using `new-york` as an example. It can be anything you want as long as it's nested under the `registry` directory.
|
||||
- The following properties are required for the block definition: `name`, `description`, `type` and `files`.
|
||||
- It is recommended to add a proper name and description to your registry item. This helps LLMs understand the component and its purpose.
|
||||
- Make sure to list all registry dependencies in `registryDependencies`. A registry dependency is the name of the component in the registry eg. `input`, `button`, `card`, etc or a URL to a registry item eg. `http://localhost:3000/r/editor.json`.
|
||||
- Make sure to list all dependencies in `dependencies`. A dependency is the name of the package in the registry eg. `zod`, `sonner`, etc. To set a version, you can use the `name@version` format eg. `zod@^3.20.0`.
|
||||
- **Imports should always use the `@/registry` path.** eg. `import { HelloWorld } from "@/registry/new-york/hello-world/hello-world"`
|
||||
@@ -186,3 +167,7 @@ To install a registry item using the `shadcn` CLI, use the `add` command followe
|
||||
```bash
|
||||
npx shadcn@latest add http://localhost:3000/r/hello-world.json
|
||||
```
|
||||
|
||||
See the [Namespaced
|
||||
Registries](/docs/registry/namespace) docs for more information on
|
||||
how to install registry items from a namespaced registry.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Registry
|
||||
title: Introduction
|
||||
description: Run your own code registry.
|
||||
---
|
||||
|
||||
@@ -30,8 +30,50 @@ You can use the `shadcn` CLI to run your own code registry. Running your own reg
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
## Requirements
|
||||
Ready to create your own registry? In the next section, we'll walk you through setting up your own custom registry step-by-step, from creating your first component to publishing it for others to use.
|
||||
|
||||
You are free to design and host your custom registry as you see fit. The only requirement is that your registry items must be valid JSON files that conform to the [registry-item schema specification](/docs/registry/registry-item-json).
|
||||
<div className="mt-6 grid gap-4 sm:grid-cols-2">
|
||||
<LinkedCard href="/docs/registry/getting-started" className="items-start text-sm md:p-6">
|
||||
<div className="font-medium">Getting Started</div>
|
||||
<div className="text-muted-foreground">
|
||||
Set up and build your own registry
|
||||
</div>
|
||||
</LinkedCard>
|
||||
|
||||
If you'd like to see an example of a registry, we have a [template project](https://github.com/shadcn-ui/registry-template) for you to use as a starting point.
|
||||
<LinkedCard
|
||||
href="/docs/registry/authentication"
|
||||
className="items-start text-sm md:p-6"
|
||||
>
|
||||
<div className="font-medium">Authentication</div>
|
||||
<div className="text-muted-foreground">
|
||||
Secure your registry with authentication
|
||||
</div>
|
||||
</LinkedCard>
|
||||
<LinkedCard
|
||||
href="/docs/registry/namespace"
|
||||
className="items-start text-sm md:p-6"
|
||||
>
|
||||
<div className="font-medium">Namespaces</div>
|
||||
<div className="text-muted-foreground">
|
||||
Configure registries with namespaces
|
||||
</div>
|
||||
</LinkedCard>
|
||||
<LinkedCard
|
||||
href="/docs/registry/examples"
|
||||
className="items-start text-sm md:p-6"
|
||||
>
|
||||
<div className="font-medium">Examples</div>
|
||||
<div className="text-muted-foreground">
|
||||
Registry item examples and configurations
|
||||
</div>
|
||||
</LinkedCard>
|
||||
<LinkedCard
|
||||
href="/docs/registry/registry-json"
|
||||
className="items-start text-sm md:p-6"
|
||||
>
|
||||
<div className="font-medium">Schema</div>
|
||||
<div className="text-muted-foreground">
|
||||
Schema specification for registry.json
|
||||
</div>
|
||||
</LinkedCard>
|
||||
</div>
|
||||
|
||||
109
apps/v4/content/docs/registry/mcp.mdx
Normal file
109
apps/v4/content/docs/registry/mcp.mdx
Normal file
@@ -0,0 +1,109 @@
|
||||
---
|
||||
title: MCP Server
|
||||
description: MCP support for registry developers
|
||||
---
|
||||
|
||||
The [shadcn MCP server](/docs/mcp) works out of the box with any shadcn-compatible registry. You do not need to do anything special to enable MCP support for your registry.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The MCP server works by requesting your registry index. Make sure you have a registry item file at the root of your registry named `registry`.
|
||||
|
||||
For example, if your registry is hosted at `https://acme.com/r/[name].json`, you should have a file at `https://acme.com/r/registry.json` or `https://acme.com/r/registry` if you're using a JSON file extension.
|
||||
|
||||
This file must be a valid JSON file that conforms to the [registry schema](/docs/registry/registry-json).
|
||||
|
||||
---
|
||||
|
||||
## Configuring MCP
|
||||
|
||||
Ask your registry consumers to configure your registry in their `components.json` file and install the shadcn MCP server:
|
||||
|
||||
<Tabs defaultValue="claude">
|
||||
<TabsList>
|
||||
<TabsTrigger value="claude">Claude Code</TabsTrigger>
|
||||
<TabsTrigger value="cursor">Cursor</TabsTrigger>
|
||||
<TabsTrigger value="vscode">VS Code</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="claude" className="mt-4">
|
||||
**Configure your registry** in your `components.json` file:
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"registries": {
|
||||
"@acme": "https://acme.com/r/{name}.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Run the following command** in your project:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest mcp init --client claude
|
||||
```
|
||||
|
||||
**Restart Claude Code** and try the following prompts:
|
||||
- Show me the components in the acme registry
|
||||
- Create a landing page using items from the acme registry
|
||||
|
||||
**Note:** You can use `/mcp` command in Claude Code to debug the MCP server.
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="cursor" className="mt-4">
|
||||
**Configure your registry** in your `components.json` file:
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"registries": {
|
||||
"@acme": "https://acme.com/r/{name}.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
**Run the following command** in your project:
|
||||
```bash
|
||||
npx shadcn@latest mcp init --client cursor
|
||||
```
|
||||
|
||||
Open **Cursor Settings** and **Enable the MCP server** for shadcn. Then try the following prompts:
|
||||
- Show me the components in the acme registry
|
||||
- Create a landing page using items from the acme registry
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="vscode" className="mt-4">
|
||||
**Configure your registry** in your `components.json` file:
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"registries": {
|
||||
"@acme": "https://acme.com/r/{name}.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
**Run the following command** in your project:
|
||||
```bash
|
||||
npx shadcn@latest mcp init --client vscode
|
||||
```
|
||||
|
||||
Open `.vscode/mcp.json` and click **Start** next to the shadcn server. Then try the following prompts with GitHub Copilot:
|
||||
- Show me the components in the acme registry
|
||||
- Create a landing page using items from the acme registry
|
||||
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
You can read more about the MCP server in the [MCP documentation](/docs/mcp).
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
Here are some best practices for MCP-compatible registries:
|
||||
|
||||
1. **Clear Descriptions**: Add concise, informative descriptions that help AI assistants understand what a registry item is for and how to use it.
|
||||
2. **Proper Dependencies**: List all `dependencies` accurately so MCP can install them automatically.
|
||||
3. **Registry Dependencies**: Use `registryDependencies` to indicate relationships between items.
|
||||
4. **Consistent Naming**: Use kebab-case for component names and maintain consistency across your registry.
|
||||
@@ -1,10 +1,14 @@
|
||||
{
|
||||
"title": "Registry",
|
||||
"pages": [
|
||||
"index",
|
||||
"getting-started",
|
||||
"faq",
|
||||
"open-in-v0",
|
||||
"namespace",
|
||||
"authentication",
|
||||
"examples",
|
||||
"mcp",
|
||||
"registry-index",
|
||||
"open-in-v0",
|
||||
"registry-json",
|
||||
"registry-item-json"
|
||||
]
|
||||
|
||||
913
apps/v4/content/docs/registry/namespace.mdx
Normal file
913
apps/v4/content/docs/registry/namespace.mdx
Normal file
@@ -0,0 +1,913 @@
|
||||
---
|
||||
title: Namespaces
|
||||
description: Configure and use multiple resource registries with namespace support.
|
||||
---
|
||||
|
||||
Namespaced registries let you configure multiple resource sources in one project. This means you can install components, libraries, utilities, AI prompts, configuration files, and other resources from various registries, whether they're public, third-party, or your own custom private libraries.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Decentralized Namespace System](#decentralized-namespace-system)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Registry Naming Convention](#registry-naming-convention)
|
||||
- [Configuration](#configuration)
|
||||
- [Authentication & Security](#authentication--security)
|
||||
- [Versioning](#versioning)
|
||||
- [Dependency Resolution](#dependency-resolution)
|
||||
- [Built-in Registries](#built-in-registries)
|
||||
- [CLI Commands](#cli-commands)
|
||||
- [Error Handling](#error-handling)
|
||||
- [Creating Your Own Registry](#creating-your-own-registry)
|
||||
- [Example Configurations](#example-configurations)
|
||||
- [Technical Details](#technical-details)
|
||||
- [Best Practices](#best-practices)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Registry namespaces are prefixed with `@` and provide a way to organize and reference resources from different sources. Resources can be any type of content: components, libraries, utilities, hooks, AI prompts, configuration files, themes, and more. For example:
|
||||
|
||||
- `@shadcn/button` - UI component from the shadcn registry
|
||||
- `@v0/dashboard` - Dashboard component from the v0 registry
|
||||
- `@ai-elements/input` - AI prompt input from an AI elements registry
|
||||
- `@acme/auth-utils` - Authentication utilities from your company's private registry
|
||||
- `@ai/chatbot-rules` - AI prompt rules from an AI resources registry
|
||||
- `@themes/dark-mode` - Theme configuration from a themes registry
|
||||
|
||||
---
|
||||
|
||||
## Decentralized Namespace System
|
||||
|
||||
We intentionally designed the namespace system to be decentralized. There is a [central open source registry index](/docs/registry/registry-index) for open source namespaces but you are free to create and use any namespace you want.
|
||||
|
||||
This decentralized approach gives you complete flexibility to organize your resources however makes sense for your organization.
|
||||
|
||||
You can create multiple registries for different purposes:
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"registries": {
|
||||
"@acme-ui": "https://registry.acme.com/ui/{name}.json",
|
||||
"@acme-docs": "https://registry.acme.com/docs/{name}.json",
|
||||
"@acme-ai": "https://registry.acme.com/ai/{name}.json",
|
||||
"@acme-themes": "https://registry.acme.com/themes/{name}.json",
|
||||
"@acme-internal": {
|
||||
"url": "https://internal.acme.com/registry/{name}.json",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${INTERNAL_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This allows you to:
|
||||
|
||||
- **Organize by type**: Separate UI components, documentation, AI resources, etc.
|
||||
- **Organize by team**: Different teams can maintain their own registries
|
||||
- **Organize by visibility**: Public vs. private resources
|
||||
- **Organize by version**: Stable vs. experimental registries
|
||||
- **No naming conflicts**: Since there's no central authority, you don't need to worry about namespace collisions
|
||||
|
||||
### Examples of Multi-Registry Setups
|
||||
|
||||
#### By Resource Type
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"@components": "https://cdn.company.com/components/{name}.json",
|
||||
"@hooks": "https://cdn.company.com/hooks/{name}.json",
|
||||
"@utils": "https://cdn.company.com/utils/{name}.json",
|
||||
"@prompts": "https://cdn.company.com/ai-prompts/{name}.json"
|
||||
}
|
||||
```
|
||||
|
||||
#### By Team or Department
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"@design": "https://design.company.com/registry/{name}.json",
|
||||
"@engineering": "https://eng.company.com/registry/{name}.json",
|
||||
"@marketing": "https://marketing.company.com/registry/{name}.json"
|
||||
}
|
||||
```
|
||||
|
||||
#### By Stability
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"@stable": "https://registry.company.com/stable/{name}.json",
|
||||
"@latest": "https://registry.company.com/beta/{name}.json",
|
||||
"@experimental": "https://registry.company.com/experimental/{name}.json"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installing Resources
|
||||
|
||||
Once configured, you can install resources using the namespace syntax:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @v0/dashboard
|
||||
```
|
||||
|
||||
or multiple resources at once:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @acme/header @lib/auth-utils @ai/chatbot-rules
|
||||
```
|
||||
|
||||
### Quick Configuration
|
||||
|
||||
Add registries to your `components.json`:
|
||||
|
||||
```json title="components.json"
|
||||
{
|
||||
"registries": {
|
||||
"@v0": "https://v0.dev/chat/b/{name}",
|
||||
"@acme": "https://registry.acme.com/resources/{name}.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then start installing:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @acme/button
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Registry Naming Convention
|
||||
|
||||
Registry names must follow these rules:
|
||||
|
||||
- Start with `@` symbol
|
||||
- Contain only alphanumeric characters, hyphens, and underscores
|
||||
- Examples of valid names: `@v0`, `@acme-ui`, `@my_company`
|
||||
|
||||
The pattern for referencing resources is: `@namespace/resource-name`
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Namespaced registries are configured in your `components.json` file under the `registries` field.
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
The simplest way to configure a registry is with a URL template string:
|
||||
|
||||
```json title="components.json"
|
||||
{
|
||||
"registries": {
|
||||
"@v0": "https://v0.dev/chat/b/{name}",
|
||||
"@acme": "https://registry.acme.com/resources/{name}.json",
|
||||
"@lib": "https://lib.company.com/utilities/{name}",
|
||||
"@ai": "https://ai-resources.com/r/{name}.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** The `{name}` placeholder in the URL is automatically parsed and replaced with the resource name when you run `npx shadcn@latest add @namespace/resource-name`. For example, `@acme/button` becomes `https://registry.acme.com/resources/button.json`. See [URL Pattern System](#url-pattern-system) for more details.
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
For registries that require authentication or additional parameters, use the object format:
|
||||
|
||||
```json title="components.json"
|
||||
{
|
||||
"registries": {
|
||||
"@private": {
|
||||
"url": "https://api.company.com/registry/{name}.json",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${REGISTRY_TOKEN}",
|
||||
"X-API-Key": "${API_KEY}"
|
||||
},
|
||||
"params": {
|
||||
"version": "latest",
|
||||
"format": "json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** Environment variables in the format `${VAR_NAME}` are automatically expanded from your environment (process.env). This works in URLs, headers, and params. For example, `${REGISTRY_TOKEN}` will be replaced with the value of `process.env.REGISTRY_TOKEN`. See [Authentication & Security](#authentication--security) for more details on using environment variables.
|
||||
|
||||
---
|
||||
|
||||
### URL Pattern System
|
||||
|
||||
Registry URLs support the following placeholders:
|
||||
|
||||
### `{name}` Placeholder (required)
|
||||
|
||||
The `{name}` placeholder is replaced with the resource name:
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"@acme": "https://registry.acme.com/{name}.json"
|
||||
}
|
||||
```
|
||||
|
||||
When installing `@acme/button`, the URL becomes: `https://registry.acme.com/button.json`
|
||||
When installing `@acme/auth-utils`, the URL becomes: `https://registry.acme.com/auth-utils.json`
|
||||
|
||||
### `{style}` Placeholder (optional)
|
||||
|
||||
The `{style}` placeholder is replaced with the current style configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"@themes": "https://registry.example.com/{style}/{name}.json"
|
||||
}
|
||||
```
|
||||
|
||||
With style set to `new-york`, installing `@themes/card` resolves to: `https://registry.example.com/new-york/card.json`
|
||||
|
||||
The style placeholder is optional. Use this when you want to serve different versions of the same resource. For example, you can serve a different version of a component for each style.
|
||||
|
||||
---
|
||||
|
||||
## Authentication & Security
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Use environment variables to securely store credentials:
|
||||
|
||||
```json title="components.json"
|
||||
{
|
||||
"registries": {
|
||||
"@private": {
|
||||
"url": "https://api.company.com/registry/{name}.json",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${REGISTRY_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then set the environment variable:
|
||||
|
||||
```bash title=".env.local"
|
||||
REGISTRY_TOKEN=your_secret_token_here
|
||||
```
|
||||
|
||||
### Authentication Methods
|
||||
|
||||
#### Bearer Token (OAuth 2.0)
|
||||
|
||||
```json
|
||||
{
|
||||
"@github": {
|
||||
"url": "https://api.github.com/repos/org/registry/contents/{name}.json",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${GITHUB_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### API Key in Headers
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"@private": {
|
||||
"url": "https://api.company.com/registry/{name}",
|
||||
"headers": {
|
||||
"X-API-Key": "${API_KEY}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Basic Authentication
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"@internal": {
|
||||
"url": "https://registry.company.com/{name}.json",
|
||||
"headers": {
|
||||
"Authorization": "Basic ${BASE64_CREDENTIALS}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Query Parameter Authentication
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"@secure": {
|
||||
"url": "https://registry.example.com/{name}.json",
|
||||
"params": {
|
||||
"api_key": "${API_KEY}",
|
||||
"client_id": "${CLIENT_ID}",
|
||||
"signature": "${REQUEST_SIGNATURE}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Multiple Authentication Methods
|
||||
|
||||
Some registries require multiple authentication methods:
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"@enterprise": {
|
||||
"url": "https://api.enterprise.com/v2/registry/{name}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${ACCESS_TOKEN}",
|
||||
"X-API-Key": "${API_KEY}",
|
||||
"X-Workspace-Id": "${WORKSPACE_ID}"
|
||||
},
|
||||
"params": {
|
||||
"version": "latest"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Security Considerations
|
||||
|
||||
When working with namespaced registries, especially third-party or public ones, security is paramount. Here's how we handle security:
|
||||
|
||||
### Resource Validation
|
||||
|
||||
All resources fetched from registries are validated against our registry item schema before installation. This ensures:
|
||||
|
||||
- **Structure validation**: Resources must conform to the expected JSON schema
|
||||
- **Type safety**: Resource types are validated (`registry:ui`, `registry:lib`, etc.)
|
||||
- **No arbitrary code execution**: Resources are data files, not executable scripts
|
||||
|
||||
### Environment Variable Security
|
||||
|
||||
Environment variables used for authentication are:
|
||||
|
||||
- **Never logged**: The CLI never logs or displays environment variable values
|
||||
- **Expanded at runtime**: Variables are only expanded when needed, not stored
|
||||
- **Isolated per registry**: Each registry maintains its own authentication context
|
||||
|
||||
Example of secure configuration:
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"registries": {
|
||||
"@private": {
|
||||
"url": "https://api.company.com/registry/{name}.json",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${PRIVATE_REGISTRY_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Never commit actual tokens to version control. Use `.env.local`:
|
||||
|
||||
```bash title=".env.local"
|
||||
PRIVATE_REGISTRY_TOKEN=actual_token_here
|
||||
```
|
||||
|
||||
### HTTPS Enforcement
|
||||
|
||||
We strongly recommend using HTTPS for all registry URLs:
|
||||
|
||||
- **Encrypted transport**: Prevents man-in-the-middle attacks
|
||||
- **Certificate validation**: Ensures you're connecting to the legitimate registry
|
||||
- **Credential protection**: Headers and tokens are encrypted in transit
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"registries": {
|
||||
"@secure": "https://registry.example.com/{name}.json", // ✅ Good
|
||||
"@insecure": "http://registry.example.com/{name}.json" // ❌ Avoid
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Content Security
|
||||
|
||||
Resources from registries are treated as data, not code:
|
||||
|
||||
1. **JSON parsing only**: Resources must be valid JSON
|
||||
2. **Schema validation**: Must match the registry item schema
|
||||
3. **File path restrictions**: Files can only be written to configured paths
|
||||
4. **No script execution**: The CLI doesn't execute any code from registry resources
|
||||
|
||||
### Registry Trust Model
|
||||
|
||||
The namespace system operates on a trust model:
|
||||
|
||||
- **You trust what you install**: Only add registries you trust to your configuration
|
||||
- **Explicit configuration**: Registries must be explicitly configured in `components.json`
|
||||
- **No automatic registry discovery**: The CLI never automatically adds registries
|
||||
- **Dependency transparency**: All dependencies are clearly listed in registry items
|
||||
|
||||
### Best Practices for Registry Operators
|
||||
|
||||
If you're running your own registry:
|
||||
|
||||
1. **Use HTTPS always**: Never serve registry content over HTTP
|
||||
2. **Implement authentication**: Require API keys or tokens for private registries
|
||||
3. **Rate limiting**: Protect your registry from abuse
|
||||
4. **Content validation**: Validate resources before serving them
|
||||
|
||||
Example secure registry setup:
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"@company": {
|
||||
"url": "https://registry.company.com/v1/{name}.json",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${COMPANY_TOKEN}",
|
||||
"X-Registry-Version": "1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Inspecting Resources Before Installation
|
||||
|
||||
The CLI provides transparency about what's being installed. You can see the payload of a registry item using the following command:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest view @acme/button
|
||||
```
|
||||
|
||||
This will output the payload of the registry item to the console.
|
||||
|
||||
---
|
||||
|
||||
## Dependency Resolution
|
||||
|
||||
### Basic Dependency Resolution
|
||||
|
||||
Resources can have dependencies across different registries:
|
||||
|
||||
```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
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The CLI automatically resolves and installs all dependencies from their respective registries.
|
||||
|
||||
### Advanced Dependency Resolution
|
||||
|
||||
Understanding how dependencies are resolved internally is important if you're developing registries or need to customize third-party resources.
|
||||
|
||||
### How Resolution Works
|
||||
|
||||
When you run `npx shadcn@latest add @namespace/resource`, the CLI does the following:
|
||||
|
||||
1. **Clears registry context** to start fresh
|
||||
2. **Fetches the main resource** from the specified registry
|
||||
3. **Recursively resolves dependencies** from their respective registries
|
||||
4. **Applies topological sorting** to ensure proper installation order
|
||||
5. **Deduplicates files** based on target paths (last one wins)
|
||||
6. **Deep merges configurations** (tailwind, cssVars, css, envVars)
|
||||
|
||||
This means that if you run the following command:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @acme/auth @custom/login-form
|
||||
```
|
||||
|
||||
The `login-form.ts` from `@custom/login-form` will override the `login-form.ts` from `@acme/auth` because it's resolved last.
|
||||
|
||||
### Overriding Third-Party Resources
|
||||
|
||||
You can leverage the dependency resolution process to override any third-party resource by adding them to your custom resource under `registryDependencies` and overriding with your own custom values.
|
||||
|
||||
#### Example: Customizing a Third-Party Button
|
||||
|
||||
Let's say you want to customize a button from a vendor registry:
|
||||
|
||||
**1. Original vendor button** (`@vendor/button`):
|
||||
|
||||
```json title="button.json" showLineNumbers
|
||||
{
|
||||
"name": "button",
|
||||
"type": "registry:ui",
|
||||
"files": [
|
||||
{
|
||||
"path": "components/ui/button.tsx",
|
||||
"type": "registry:ui",
|
||||
"content": "// Vendor's button implementation\nexport function Button() { ... }"
|
||||
}
|
||||
],
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"--button-bg": "blue"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**2. Create your custom override** (`@my-company/custom-button`):
|
||||
|
||||
```json title="custom-button.json" showLineNumbers
|
||||
{
|
||||
"name": "custom-button",
|
||||
"type": "registry:ui",
|
||||
"registryDependencies": [
|
||||
"@vendor/button" // Import original first
|
||||
],
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"--button-bg": "purple" // Override the color
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3. Install your custom version**:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @my-company/custom-button
|
||||
```
|
||||
|
||||
This installs the original button from `@vendor/button` and then overrides the `cssVars` with your own custom values.
|
||||
|
||||
### Advanced Override Patterns
|
||||
|
||||
#### Extending Without Replacing
|
||||
|
||||
Keep the original and add extensions:
|
||||
|
||||
```json title="extended-table.json" showLineNumbers
|
||||
{
|
||||
"name": "extended-table",
|
||||
"registryDependencies": ["@vendor/table"],
|
||||
"files": [
|
||||
{
|
||||
"path": "components/ui/table-extended.tsx",
|
||||
"content": "import { Table } from '@vendor/table'\n// Add your extensions\nexport function ExtendedTable() { ... }"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This will install the original table from `@vendor/table` and then add your extensions to `components/ui/table-extended.tsx`.
|
||||
|
||||
#### Partial Override (Multi-file Resources)
|
||||
|
||||
Override only specific files from a complex component:
|
||||
|
||||
```json title="custom-auth.json" showLineNumbers
|
||||
{
|
||||
"name": "custom-auth",
|
||||
"registryDependencies": [
|
||||
"@vendor/auth" // Has multiple files
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "lib/auth-server.ts",
|
||||
"type": "registry:lib",
|
||||
"content": "// Your custom auth server"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Resolution Order Example
|
||||
|
||||
When you install `@custom/dashboard` that depends on multiple resources:
|
||||
|
||||
```json title="dashboard.json" showLineNumbers
|
||||
{
|
||||
"name": "dashboard",
|
||||
"registryDependencies": [
|
||||
"@shadcn/card", // 1. Resolved first
|
||||
"@vendor/chart", // 2. Resolved second
|
||||
"@custom/card" // 3. Resolved last (overrides @shadcn/card)
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Resolution order:
|
||||
|
||||
1. `@shadcn/card` - installs to `components/ui/card.tsx`
|
||||
2. `@vendor/chart` - installs to `components/ui/chart.tsx`
|
||||
3. `@custom/card` - overwrites `components/ui/card.tsx` (if same target)
|
||||
|
||||
### Key Resolution Features
|
||||
|
||||
1. **Source Tracking**: Each resource knows which registry it came from, avoiding naming conflicts
|
||||
2. **Circular Dependency Prevention**: Automatically detects and prevents circular dependencies
|
||||
3. **Smart Installation Order**: Dependencies are installed first, then the resources that use them
|
||||
|
||||
---
|
||||
|
||||
## Versioning
|
||||
|
||||
You can implement versioning for your registry resources using query parameters. This allows users to pin specific versions or use different release channels.
|
||||
|
||||
### Basic Version Parameter
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"@versioned": {
|
||||
"url": "https://registry.example.com/{name}",
|
||||
"params": {
|
||||
"version": "v2"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This resolves `@versioned/button` to: `https://registry.example.com/button?version=v2`
|
||||
|
||||
### Dynamic Version Selection
|
||||
|
||||
Use environment variables to control versions across your project:
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"@stable": {
|
||||
"url": "https://registry.company.com/{name}",
|
||||
"params": {
|
||||
"version": "${REGISTRY_VERSION}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This allows you to:
|
||||
|
||||
- Set `REGISTRY_VERSION=v1.2.3` in production
|
||||
- Override per environment (dev, staging, prod)
|
||||
|
||||
### Semantic Versioning
|
||||
|
||||
Implement semantic versioning with range support:
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"@npm-style": {
|
||||
"url": "https://registry.example.com/{name}",
|
||||
"params": {
|
||||
"semver": "^2.0.0",
|
||||
"prerelease": "${ALLOW_PRERELEASE}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Version Resolution Best Practices
|
||||
|
||||
1. **Use environment variables** for version control across environments
|
||||
2. **Provide sensible defaults** using the `${VAR:-default}` syntax
|
||||
3. **Document version schemes** clearly for registry users
|
||||
4. **Support version pinning** for reproducible builds
|
||||
5. **Implement version discovery** endpoints (e.g., `/versions/{name}`)
|
||||
6. **Cache versioned resources** appropriately with proper cache headers
|
||||
|
||||
---
|
||||
|
||||
## CLI Commands
|
||||
|
||||
The shadcn CLI provides several commands for working with namespaced registries:
|
||||
|
||||
### Adding Resources
|
||||
|
||||
Install resources from any configured registry:
|
||||
|
||||
```bash
|
||||
# Install from a specific registry
|
||||
npx shadcn@latest add @v0/dashboard
|
||||
|
||||
# Install multiple resources
|
||||
npx shadcn@latest add @acme/button @lib/utils @ai/prompt
|
||||
|
||||
# Install from URL directly
|
||||
npx shadcn@latest add https://registry.example.com/button.json
|
||||
|
||||
# Install from local file
|
||||
npx shadcn@latest add ./local-registry/button.json
|
||||
```
|
||||
|
||||
### Viewing Resources
|
||||
|
||||
Inspect registry items before installation:
|
||||
|
||||
```bash
|
||||
# View a resource from a registry
|
||||
npx shadcn@latest view @acme/button
|
||||
|
||||
# View multiple resources
|
||||
npx shadcn@latest view @v0/dashboard @shadcn/card
|
||||
|
||||
# View from URL
|
||||
npx shadcn@latest view https://registry.example.com/button.json
|
||||
```
|
||||
|
||||
The `view` command displays:
|
||||
|
||||
- Resource metadata (name, type, description)
|
||||
- Dependencies and registry dependencies
|
||||
- File contents that will be installed
|
||||
- CSS variables and Tailwind configuration
|
||||
- Required environment variables
|
||||
|
||||
### Searching Registries
|
||||
|
||||
Search for available resources in registries:
|
||||
|
||||
```bash
|
||||
# Search a specific registry
|
||||
npx shadcn@latest search @v0
|
||||
|
||||
# Search with query
|
||||
npx shadcn@latest search @acme --query "auth"
|
||||
|
||||
# Search multiple registries
|
||||
npx shadcn@latest search @v0 @acme @lib
|
||||
|
||||
# Limit results
|
||||
npx shadcn@latest search @v0 --limit 10 --offset 20
|
||||
|
||||
# List all items (alias for search)
|
||||
npx shadcn@latest list @acme
|
||||
```
|
||||
|
||||
Search results include:
|
||||
|
||||
- Resource name and type
|
||||
- Description
|
||||
- Registry source
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Registry Not Configured
|
||||
|
||||
If you reference a registry that isn't configured:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @non-existent/component
|
||||
```
|
||||
|
||||
Error:
|
||||
|
||||
```txt
|
||||
Unknown registry "@non-existent". Make sure it is defined in components.json as follows:
|
||||
{
|
||||
"registries": {
|
||||
"@non-existent": "[URL_TO_REGISTRY]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Missing Environment Variables
|
||||
|
||||
If required environment variables are not set:
|
||||
|
||||
```txt
|
||||
Registry "@private" requires the following environment variables:
|
||||
|
||||
• REGISTRY_TOKEN
|
||||
|
||||
Set the required environment variables to your .env or .env.local file.
|
||||
```
|
||||
|
||||
### Resource Not Found
|
||||
|
||||
404 Not Found:
|
||||
|
||||
```txt
|
||||
The item at https://registry.company.com/button.json was not found. It may not exist at the registry.
|
||||
```
|
||||
|
||||
This usually means:
|
||||
|
||||
- The resource name is misspelled
|
||||
- The resource doesn't exist in the registry
|
||||
- The registry URL pattern is incorrect
|
||||
|
||||
### Authentication Failures
|
||||
|
||||
401 Unauthorized:
|
||||
|
||||
```txt
|
||||
You are not authorized to access the item at https://api.company.com/button.json
|
||||
Check your authentication credentials and environment variables.
|
||||
```
|
||||
|
||||
403 Forbidden:
|
||||
|
||||
```txt
|
||||
Access forbidden for https://api.company.com/button.json
|
||||
Verify your API key has the necessary permissions.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Creating Your Own Registry
|
||||
|
||||
To make your registry compatible with the namespace system, you can serve any type of resource - components, libraries, utilities, AI prompts, themes, configurations, or any other shareable code/content:
|
||||
|
||||
1. **Implement the registry item schema**: Your registry must return JSON that conforms to the [registry item schema](/docs/registry/registry-item-json).
|
||||
|
||||
2. **Support the URL pattern**: Include `{name}` in your URL template where the resource name will be inserted.
|
||||
|
||||
3. **Define resource types**: Use appropriate `type` fields to identify your resources (e.g., `registry:ui`, `registry:lib`, `registry:ai`, `registry:theme`, etc.).
|
||||
|
||||
4. **Handle authentication** (if needed): Accept authentication via headers or query parameters.
|
||||
|
||||
5. **Document your namespace**: Provide clear instructions for users to configure your registry:
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"registries": {
|
||||
"@your-registry": "https://your-domain.com/r/{name}.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Parser Pattern
|
||||
|
||||
The namespace parser uses the following regex pattern:
|
||||
|
||||
```regex title="namespace-parser.js"
|
||||
/^(@[a-zA-Z0-9](?:[a-zA-Z0-9-_]*[a-zA-Z0-9])?)\/(.+)$/
|
||||
```
|
||||
|
||||
This ensures valid namespace formatting and proper component name extraction.
|
||||
|
||||
### Resolution Process
|
||||
|
||||
1. **Parse**: Extract namespace and component name from `@namespace/component`
|
||||
2. **Lookup**: Find registry configuration for `@namespace`
|
||||
3. **Build URL**: Replace placeholders with actual values
|
||||
4. **Set Headers**: Apply authentication headers if configured
|
||||
5. **Fetch**: Retrieve component from the resolved URL
|
||||
6. **Validate**: Ensure response matches registry item schema
|
||||
7. **Resolve Dependencies**: Recursively fetch any registry dependencies
|
||||
|
||||
### Cross-Registry Dependencies
|
||||
|
||||
When a component has dependencies from different registries, the resolver:
|
||||
|
||||
1. Maintains separate authentication contexts for each registry
|
||||
2. Resolves each dependency from its respective source
|
||||
3. Deduplicates files based on target paths
|
||||
4. Merges configurations (tailwind, cssVars, etc.) from all sources
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use environment variables** for sensitive data like API keys and tokens
|
||||
2. **Namespace your registry** with a unique, descriptive name
|
||||
3. **Document authentication requirements** clearly for users
|
||||
4. **Implement proper error responses** with helpful messages
|
||||
5. **Cache registry responses** when possible to improve performance
|
||||
6. **Support style variants** if your components have multiple themes
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Resources not found
|
||||
|
||||
- Verify the registry URL is correct and accessible
|
||||
- Check that the `{name}` placeholder is included in the URL
|
||||
- Ensure the resource exists in the registry
|
||||
- Confirm the resource type matches what the registry provides
|
||||
|
||||
### Authentication issues
|
||||
|
||||
- Confirm environment variables are set correctly
|
||||
- Verify API keys/tokens are valid and not expired
|
||||
- Check that headers are being sent in the correct format
|
||||
|
||||
### Dependency conflicts
|
||||
|
||||
- Review resources with the same name from different registries
|
||||
- Use fully qualified names (`@namespace/resource`) to avoid ambiguity
|
||||
- Check for circular dependencies between registries
|
||||
- Ensure resource types are compatible when mixing registries
|
||||
@@ -8,8 +8,8 @@ If your registry is hosted and publicly accessible via a URL, you can open a reg
|
||||
eg. [https://v0.dev/chat/api/open?url=https://ui.shadcn.com/r/styles/new-york/login-01.json](https://v0.dev/chat/api/open?url=https://ui.shadcn.com/r/styles/new-york/login-01.json)
|
||||
|
||||
<Callout className="mt-6">
|
||||
**Note:** The `Open in v0` button does not support `cssVars` and `tailwind`
|
||||
properties.
|
||||
**Important:** `Open in v0` does not support `cssVars`, `css`, `envVars`,
|
||||
namespaced registries, or advanced authentication methods.
|
||||
</Callout>
|
||||
|
||||
## Button
|
||||
@@ -61,4 +61,46 @@ export function OpenInV0Button({ url }: { url: string }) {
|
||||
|
||||
## Authentication
|
||||
|
||||
See the [Adding Auth](/docs/registry/getting-started#adding-auth) section for more information on how to authenticate requests to your registry and Open in v0.
|
||||
Open in v0 only supports query parameter authentication. It does not support namespaced registries or advanced authentication methods like Bearer tokens or API keys in headers.
|
||||
|
||||
### Using Query Parameter Authentication
|
||||
|
||||
To add authentication to your registry for Open in v0, use a `token` query parameter:
|
||||
|
||||
```
|
||||
https://registry.company.com/r/hello-world.json?token=your_secure_token_here
|
||||
```
|
||||
|
||||
When implementing this on your registry server:
|
||||
|
||||
1. Check for the `token` query parameter
|
||||
2. Validate the token against your authentication system
|
||||
3. Return a `401 Unauthorized` response if the token is invalid or missing
|
||||
4. Both the shadcn CLI and Open in v0 will handle the 401 response and display an appropriate message to users
|
||||
|
||||
### Example Implementation
|
||||
|
||||
```typescript
|
||||
// Next.js API route example
|
||||
export async function GET(request: NextRequest) {
|
||||
const token = request.nextUrl.searchParams.get("token")
|
||||
|
||||
if (!isValidToken(token)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
message: "Invalid or missing token",
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
// Return the registry item
|
||||
return NextResponse.json(registryItem)
|
||||
}
|
||||
```
|
||||
|
||||
<Callout className="mt-6">
|
||||
**Security Note:** Make sure to encrypt and expire tokens. Never expose
|
||||
production tokens in documentation or examples.
|
||||
</Callout>
|
||||
|
||||
79
apps/v4/content/docs/registry/registry-index.mdx
Normal file
79
apps/v4/content/docs/registry/registry-index.mdx
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: Index
|
||||
description: Open Source Registry Index
|
||||
---
|
||||
|
||||
The open source registry index is a list of all the open source registries that are available to use out of the box.
|
||||
|
||||
When you run `shadcn add` or `shadcn search`, the CLI will automatically check the registry index for the registry you are looking for and add it to your `components.json` file.
|
||||
|
||||
You can see the full list at [https://ui.shadcn.com/r/registries.json](https://ui.shadcn.com/r/registries.json).
|
||||
|
||||
## Adding a Registry
|
||||
|
||||
You can submit a PR to add a registry to the index by adding it to the [registries.json](https://github.com/shadcn-ui/ui/blob/main/apps/v4/public/r/registries.json) file.
|
||||
|
||||
Here's an example of how to add a registry to the index:
|
||||
|
||||
```json title="registries.json" showLineNumbers
|
||||
{
|
||||
"@acme": "https://registry.acme.com/r/{name}.json",
|
||||
"@example": "https://example.com/r/{name}"
|
||||
}
|
||||
```
|
||||
|
||||
### Requirements
|
||||
|
||||
1. The registry must be open source and publicly accessible.
|
||||
2. The registry must be a valid JSON file that conforms to the [registry schema specification](/docs/registry/registry-json).
|
||||
3. The registry is expected to be a flat registry with no nested items i.e `/registry.json` and `/component-name.json` files are expected to be in the root of the registry.
|
||||
4. The `files` array, if present, must NOT include a `content` property.
|
||||
|
||||
Here's an example of a valid registry:
|
||||
|
||||
```json title="registry.json" showLineNumbers
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry.json",
|
||||
"name": "acme",
|
||||
"homepage": "https://acme.com",
|
||||
"items": [
|
||||
{
|
||||
"name": "login-form",
|
||||
"type": "registry:component",
|
||||
"title": "Login Form",
|
||||
"description": "A login form component.",
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/new-york/auth/login-form.tsx",
|
||||
"type": "registry:component"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "example-login-form",
|
||||
"type": "registry:component",
|
||||
"title": "Example Login Form",
|
||||
"description": "An example showing how to use the login form component.",
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/new-york/examples/example-login-form.tsx",
|
||||
"type": "registry:component"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Validation
|
||||
|
||||
At the root of the `shadcn/ui` project, you can run the following command to validate the `registries.json` file.
|
||||
|
||||
```bash
|
||||
pnpm validate:registries
|
||||
```
|
||||
|
||||
This will validate the registries.json file and output any errors.
|
||||
|
||||
Once you have submitted your PR, it will be validated and reviewed by the team.
|
||||
@@ -12,6 +12,12 @@ The `registry-item.json` schema is used to define your custom registry items.
|
||||
"type": "registry:block",
|
||||
"title": "Hello World",
|
||||
"description": "A simple hello world component.",
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"@acme/input-form",
|
||||
"https://example.com/r/foo"
|
||||
],
|
||||
"dependencies": ["is-even@3.0.0", "motion"],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/new-york/hello-world/hello-world.tsx",
|
||||
@@ -140,17 +146,17 @@ Use `@version` to specify the version of your registry item.
|
||||
|
||||
### registryDependencies
|
||||
|
||||
Used for registry dependencies. Can be names or URLs. Use the name of the item to reference shadcn/ui components and urls to reference other registries.
|
||||
Used for registry dependencies. Can be names, namespaced or URLs.
|
||||
|
||||
- For `shadcn/ui` registry items such as `button`, `input`, `select`, etc use the name eg. `['button', 'input', 'select']`.
|
||||
- For namespaced registry items such as `@acme` use the name eg. `['@acme/input-form']`.
|
||||
- For custom registry items use the URL of the registry item eg. `['https://example.com/r/hello-world.json']`.
|
||||
|
||||
```json title="registry-item.json" showLineNumbers
|
||||
{
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"input",
|
||||
"select",
|
||||
"@acme/input-form",
|
||||
"https://example.com/r/editor.json"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -16,6 +16,12 @@ The `registry.json` schema is used to define your custom component registry.
|
||||
"type": "registry:block",
|
||||
"title": "Hello World",
|
||||
"description": "A simple hello world component.",
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"@acme/input-form",
|
||||
"https://example.com/r/foo"
|
||||
],
|
||||
"dependencies": ["is-even@3.0.0", "motion"],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/new-york/hello-world/hello-world.tsx",
|
||||
@@ -73,6 +79,12 @@ The `items` in your registry. Each item must implement the [registry-item schema
|
||||
"type": "registry:block",
|
||||
"title": "Hello World",
|
||||
"description": "A simple hello world component.",
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"@acme/input-form",
|
||||
"https://example.com/r/foo"
|
||||
],
|
||||
"dependencies": ["is-even@3.0.0", "motion"],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/new-york/hello-world/hello-world.tsx",
|
||||
|
||||
1
apps/v4/lib/flags.ts
Normal file
1
apps/v4/lib/flags.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const showMcpDocs = true
|
||||
@@ -52,7 +52,7 @@ export const mdxComponents = {
|
||||
.replace(/\?/g, "")
|
||||
.toLowerCase()}
|
||||
className={cn(
|
||||
"font-heading mt-12 scroll-m-28 text-2xl font-medium tracking-tight first:mt-0 lg:mt-20 [&+p]:!mt-4 *:[code]:text-2xl",
|
||||
"font-heading mt-8 scroll-m-28 text-xl font-medium tracking-tight first:mt-0 lg:mt-8 [&+p]:!mt-4 *:[code]:text-xl",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -62,7 +62,7 @@ export const mdxComponents = {
|
||||
h3: ({ className, ...props }: React.ComponentProps<"h3">) => (
|
||||
<h3
|
||||
className={cn(
|
||||
"font-heading mt-8 scroll-m-28 text-xl font-semibold tracking-tight *:[code]:text-xl",
|
||||
"font-heading mt-8 scroll-m-28 text-lg font-medium tracking-tight *:[code]:text-xl",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -71,7 +71,7 @@ export const mdxComponents = {
|
||||
h4: ({ className, ...props }: React.ComponentProps<"h4">) => (
|
||||
<h4
|
||||
className={cn(
|
||||
"font-heading mt-8 scroll-m-28 text-lg font-medium tracking-tight",
|
||||
"font-heading mt-8 scroll-m-28 text-base font-medium tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -80,7 +80,7 @@ export const mdxComponents = {
|
||||
h5: ({ className, ...props }: React.ComponentProps<"h5">) => (
|
||||
<h5
|
||||
className={cn(
|
||||
"mt-8 scroll-m-28 text-lg font-medium tracking-tight",
|
||||
"mt-8 scroll-m-28 text-base font-medium tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -228,7 +228,7 @@ export const mdxComponents = {
|
||||
return (
|
||||
<code
|
||||
className={cn(
|
||||
"bg-muted relative rounded-md px-[0.3rem] py-[0.2rem] font-mono text-[0.8rem] outline-none",
|
||||
"bg-muted relative rounded-md px-[0.3rem] py-[0.2rem] font-mono text-[0.8rem] break-words outline-none",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -298,7 +298,7 @@ export const mdxComponents = {
|
||||
}: React.ComponentProps<typeof TabsList>) => (
|
||||
<TabsList
|
||||
className={cn(
|
||||
"justify-start gap-4 rounded-none bg-transparent px-2 md:px-0",
|
||||
"justify-start gap-4 rounded-none bg-transparent px-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -310,7 +310,7 @@ export const mdxComponents = {
|
||||
}: React.ComponentProps<typeof TabsTrigger>) => (
|
||||
<TabsTrigger
|
||||
className={cn(
|
||||
"text-muted-foreground data-[state=active]:text-foreground px-0 text-base data-[state=active]:shadow-none dark:data-[state=active]:border-transparent dark:data-[state=active]:bg-transparent",
|
||||
"text-muted-foreground data-[state=active]:text-foreground data-[state=active]:border-primary dark:data-[state=active]:border-primary hover:text-primary rounded-none border-0 border-b-2 border-transparent bg-transparent px-0 pb-3 text-base data-[state=active]:bg-transparent data-[state=active]:shadow-none dark:data-[state=active]:bg-transparent",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -73,6 +73,11 @@ const nextConfig = {
|
||||
destination: "/docs/:path*.md",
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: "/mcp",
|
||||
destination: "/docs/mcp",
|
||||
permanent: false,
|
||||
},
|
||||
]
|
||||
},
|
||||
rewrites() {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache",
|
||||
"registry:build": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts && prettier --log-level silent --write \"registry/**/*.{ts,tsx,json,mdx}\" --cache",
|
||||
"registry:capture": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/capture-registry.mts",
|
||||
"validate:registries": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/validate-registries.mts",
|
||||
"postinstall": "fumadocs-mdx"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -86,7 +87,7 @@
|
||||
"recharts": "2.15.1",
|
||||
"rehype-pretty-code": "^0.14.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"shadcn": "3.0.0",
|
||||
"shadcn": "3.3.0",
|
||||
"shiki": "^1.10.1",
|
||||
"sonner": "^2.0.0",
|
||||
"tailwind-merge": "^3.0.1",
|
||||
|
||||
36
apps/v4/public/r/registries.json
Normal file
36
apps/v4/public/r/registries.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"@aceternity": "https://ui.aceternity.com/registry/{name}.json",
|
||||
"@ai-elements": "https://registry.ai-sdk.dev/{name}.json",
|
||||
"@alexcarpenter": "https://ui.alexcarpenter.me/r/{name}.json",
|
||||
"@alpine": "https://alpine-registry.vercel.app/r/{name}.json",
|
||||
"@animate-ui": "https://animate-ui.com/r/{name}.json",
|
||||
"@blocks": "https://blocks.so/r/{name}.json",
|
||||
"@clerk": "https://clerk.com/r/{name}.json",
|
||||
"@cult-ui": "https://cult-ui.com/r/{name}.json",
|
||||
"@kibo-ui": "https://www.kibo-ui.com/r/{name}.json",
|
||||
"@kokonutui": "https://kokonutui.com/r/{name}.json",
|
||||
"@magicui": "https://magicui.design/r/{name}.json",
|
||||
"@motion-primitives": "https://motion-primitives.com/c/{name}.json",
|
||||
"@originui": "https://originui.com/r/{name}.json",
|
||||
"@prompt-kit": "https://prompt-kit.com/c/{name}.json",
|
||||
"@tailark": "https://tailark.com/r/{name}.json",
|
||||
"@react-bits": "https://reactbits.dev/r/{name}.json",
|
||||
"@reui": "https://reui.io/r/{name}.json",
|
||||
"@heseui": "https://www.heseui.com/r/{name}.json",
|
||||
"@paceui-ui": "https://ui.paceui.com/r/{name}.json",
|
||||
"@basecn": "https://basecn.dev/r/{name}.json",
|
||||
"@ncdai": "https://chanhdai.com/r/{name}.json",
|
||||
"@8bitcn": "https://8bitcn.com/r/{name}.json",
|
||||
"@billingsdk": "https://billingsdk.com/r/{name}.json",
|
||||
"@elements": "https://tryelements.dev/r/{name}.json",
|
||||
"@nativeui": "https://nativeui.io/registry/{name}.json",
|
||||
"@smoothui": "https://smoothui.dev/r/{name}.json",
|
||||
"@formcn": "https://formcn.dev/r/{name}.json",
|
||||
"@limeplay": "https://limeplay.winoffrg.dev/r/{name}.json",
|
||||
"@skiper-ui": "https://skiper-ui.com/registry/{name}.json",
|
||||
"@shadcn-editor": "https://shadcn-editor.vercel.app/r/{name}.json",
|
||||
"@rigidui": "https://rigidui.com/r/{name}.json",
|
||||
"@retroui": "https://retroui.dev/r/{name}.json",
|
||||
"@wds": "https://wds-shadcn-registry.netlify.app/r/{name}.json",
|
||||
"@97cn": "https://97cn.itzik.co/r/{name}.json"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -3593,176 +3593,367 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "theme-daylight",
|
||||
"name": "theme-stone",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "36 39% 88%",
|
||||
"foreground": "36 45% 15%",
|
||||
"primary": "36 45% 70%",
|
||||
"primary-foreground": "36 45% 11%",
|
||||
"secondary": "40 35% 77%",
|
||||
"secondary-foreground": "36 45% 25%",
|
||||
"accent": "36 64% 57%",
|
||||
"accent-foreground": "36 72% 17%",
|
||||
"destructive": "0 84% 37%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"muted": "36 33% 75%",
|
||||
"muted-foreground": "36 45% 25%",
|
||||
"card": "36 46% 82%",
|
||||
"card-foreground": "36 45% 20%",
|
||||
"popover": "0 0% 100%",
|
||||
"popover-foreground": "240 10% 3.9%",
|
||||
"border": "36 45% 60%",
|
||||
"input": "36 45% 60%",
|
||||
"ring": "36 45% 30%",
|
||||
"chart-1": "25 34% 28%",
|
||||
"chart-2": "26 36% 34%",
|
||||
"chart-3": "28 40% 40%",
|
||||
"chart-4": "31 41% 48%",
|
||||
"chart-5": "35 43% 53%"
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.147 0.004 49.25)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"primary": "oklch(0.216 0.006 56.043)",
|
||||
"primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"secondary": "oklch(0.97 0.001 106.424)",
|
||||
"secondary-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"muted": "oklch(0.97 0.001 106.424)",
|
||||
"muted-foreground": "oklch(0.553 0.013 58.071)",
|
||||
"accent": "oklch(0.97 0.001 106.424)",
|
||||
"accent-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.923 0.003 48.717)",
|
||||
"input": "oklch(0.923 0.003 48.717)",
|
||||
"ring": "oklch(0.709 0.01 56.259)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"sidebar-primary": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-accent": "oklch(0.97 0.001 106.424)",
|
||||
"sidebar-accent-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-border": "oklch(0.923 0.003 48.717)",
|
||||
"sidebar-ring": "oklch(0.709 0.01 56.259)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "36 39% 88%",
|
||||
"foreground": "36 45% 15%",
|
||||
"primary": "36 45% 70%",
|
||||
"primary-foreground": "36 45% 11%",
|
||||
"secondary": "40 35% 77%",
|
||||
"secondary-foreground": "36 45% 25%",
|
||||
"accent": "36 64% 57%",
|
||||
"accent-foreground": "36 72% 17%",
|
||||
"destructive": "0 84% 37%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"muted": "36 33% 75%",
|
||||
"muted-foreground": "36 45% 25%",
|
||||
"card": "36 46% 82%",
|
||||
"card-foreground": "36 45% 20%",
|
||||
"popover": "0 0% 100%",
|
||||
"popover-foreground": "240 10% 3.9%",
|
||||
"border": "36 45% 60%",
|
||||
"input": "36 45% 60%",
|
||||
"ring": "36 45% 30%",
|
||||
"chart-1": "25 34% 28%",
|
||||
"chart-2": "26 36% 34%",
|
||||
"chart-3": "28 40% 40%",
|
||||
"chart-4": "31 41% 48%",
|
||||
"chart-5": "35 43% 53%"
|
||||
"background": "oklch(0.147 0.004 49.25)",
|
||||
"foreground": "oklch(0.985 0.001 106.423)",
|
||||
"card": "oklch(0.216 0.006 56.043)",
|
||||
"card-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"popover": "oklch(0.216 0.006 56.043)",
|
||||
"popover-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"primary": "oklch(0.923 0.003 48.717)",
|
||||
"primary-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"secondary": "oklch(0.268 0.007 34.298)",
|
||||
"secondary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"muted": "oklch(0.268 0.007 34.298)",
|
||||
"muted-foreground": "oklch(0.709 0.01 56.259)",
|
||||
"accent": "oklch(0.268 0.007 34.298)",
|
||||
"accent-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.553 0.013 58.071)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-accent": "oklch(0.268 0.007 34.298)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.553 0.013 58.071)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "theme-midnight",
|
||||
"name": "theme-zinc",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "240 5% 6%",
|
||||
"foreground": "60 5% 90%",
|
||||
"primary": "240 0% 90%",
|
||||
"primary-foreground": "60 0% 0%",
|
||||
"secondary": "240 4% 15%",
|
||||
"secondary-foreground": "60 5% 85%",
|
||||
"accent": "240 0% 13%",
|
||||
"accent-foreground": "60 0% 100%",
|
||||
"destructive": "0 60% 50%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"muted": "240 5% 25%",
|
||||
"muted-foreground": "60 5% 85%",
|
||||
"card": "240 4% 10%",
|
||||
"card-foreground": "60 5% 90%",
|
||||
"popover": "240 5% 15%",
|
||||
"popover-foreground": "60 5% 85%",
|
||||
"border": "240 6% 20%",
|
||||
"input": "240 6% 20%",
|
||||
"ring": "240 5% 90%",
|
||||
"chart-1": "359 2% 90%",
|
||||
"chart-2": "240 1% 74%",
|
||||
"chart-3": "240 1% 58%",
|
||||
"chart-4": "240 1% 42%",
|
||||
"chart-5": "240 2% 26%"
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.141 0.005 285.823)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"primary": "oklch(0.21 0.006 285.885)",
|
||||
"primary-foreground": "oklch(0.985 0 0)",
|
||||
"secondary": "oklch(0.967 0.001 286.375)",
|
||||
"secondary-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"muted": "oklch(0.967 0.001 286.375)",
|
||||
"muted-foreground": "oklch(0.552 0.016 285.938)",
|
||||
"accent": "oklch(0.967 0.001 286.375)",
|
||||
"accent-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.92 0.004 286.32)",
|
||||
"input": "oklch(0.92 0.004 286.32)",
|
||||
"ring": "oklch(0.705 0.015 286.067)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0 0)",
|
||||
"sidebar-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"sidebar-primary": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.967 0.001 286.375)",
|
||||
"sidebar-accent-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-border": "oklch(0.92 0.004 286.32)",
|
||||
"sidebar-ring": "oklch(0.705 0.015 286.067)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "240 5% 6%",
|
||||
"foreground": "60 5% 90%",
|
||||
"primary": "240 0% 90%",
|
||||
"primary-foreground": "60 0% 0%",
|
||||
"secondary": "240 4% 15%",
|
||||
"secondary-foreground": "60 5% 85%",
|
||||
"accent": "240 0% 13%",
|
||||
"accent-foreground": "60 0% 100%",
|
||||
"destructive": "0 60% 50%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"muted": "240 5% 25%",
|
||||
"muted-foreground": "60 5% 85%",
|
||||
"card": "240 4% 10%",
|
||||
"card-foreground": "60 5% 90%",
|
||||
"popover": "240 5% 15%",
|
||||
"popover-foreground": "60 5% 85%",
|
||||
"border": "240 6% 20%",
|
||||
"input": "240 6% 20%",
|
||||
"ring": "240 5% 90%",
|
||||
"chart-1": "359 2% 90%",
|
||||
"chart-2": "240 1% 74%",
|
||||
"chart-3": "240 1% 58%",
|
||||
"chart-4": "240 1% 42%",
|
||||
"chart-5": "240 2% 26%"
|
||||
"background": "oklch(0.141 0.005 285.823)",
|
||||
"foreground": "oklch(0.985 0 0)",
|
||||
"card": "oklch(0.21 0.006 285.885)",
|
||||
"card-foreground": "oklch(0.985 0 0)",
|
||||
"popover": "oklch(0.21 0.006 285.885)",
|
||||
"popover-foreground": "oklch(0.985 0 0)",
|
||||
"primary": "oklch(0.92 0.004 286.32)",
|
||||
"primary-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"secondary": "oklch(0.274 0.006 286.033)",
|
||||
"secondary-foreground": "oklch(0.985 0 0)",
|
||||
"muted": "oklch(0.274 0.006 286.033)",
|
||||
"muted-foreground": "oklch(0.705 0.015 286.067)",
|
||||
"accent": "oklch(0.274 0.006 286.033)",
|
||||
"accent-foreground": "oklch(0.985 0 0)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.552 0.016 285.938)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.274 0.006 286.033)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.552 0.016 285.938)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "theme-emerald",
|
||||
"name": "theme-neutral",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "240 10% 3.9%",
|
||||
"card": "0 0% 100%",
|
||||
"card-foreground": "240 10% 3.9%",
|
||||
"popover": "0 0% 100%",
|
||||
"popover-foreground": "240 10% 3.9%",
|
||||
"primary": "142 86% 28%",
|
||||
"primary-foreground": "356 29% 98%",
|
||||
"secondary": "240 4.8% 95.9%",
|
||||
"secondary-foreground": "240 5.9% 10%",
|
||||
"muted": "240 4.8% 95.9%",
|
||||
"muted-foreground": "240 3.8% 45%",
|
||||
"accent": "240 4.8% 95.9%",
|
||||
"accent-foreground": "240 5.9% 10%",
|
||||
"destructive": "0 72% 51%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"border": "240 5.9% 90%",
|
||||
"input": "240 5.9% 90%",
|
||||
"ring": "142 86% 28%",
|
||||
"chart-1": "139 65% 20%",
|
||||
"chart-2": "140 74% 44%",
|
||||
"chart-3": "142 88% 28%",
|
||||
"chart-4": "137 55% 15%",
|
||||
"chart-5": "141 40% 9%"
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.145 0 0)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.145 0 0)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.145 0 0)",
|
||||
"primary": "oklch(0.205 0 0)",
|
||||
"primary-foreground": "oklch(0.985 0 0)",
|
||||
"secondary": "oklch(0.97 0 0)",
|
||||
"secondary-foreground": "oklch(0.205 0 0)",
|
||||
"muted": "oklch(0.97 0 0)",
|
||||
"muted-foreground": "oklch(0.556 0 0)",
|
||||
"accent": "oklch(0.97 0 0)",
|
||||
"accent-foreground": "oklch(0.205 0 0)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.922 0 0)",
|
||||
"input": "oklch(0.922 0 0)",
|
||||
"ring": "oklch(0.708 0 0)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0 0)",
|
||||
"sidebar-foreground": "oklch(0.145 0 0)",
|
||||
"sidebar-primary": "oklch(0.205 0 0)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.97 0 0)",
|
||||
"sidebar-accent-foreground": "oklch(0.205 0 0)",
|
||||
"sidebar-border": "oklch(0.922 0 0)",
|
||||
"sidebar-ring": "oklch(0.708 0 0)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "240 10% 3.9%",
|
||||
"foreground": "0 0% 98%",
|
||||
"card": "240 10% 3.9%",
|
||||
"card-foreground": "0 0% 98%",
|
||||
"popover": "240 10% 3.9%",
|
||||
"popover-foreground": "0 0% 98%",
|
||||
"primary": "142 86% 28%",
|
||||
"primary-foreground": "356 29% 98%",
|
||||
"secondary": "240 4.8% 95.9%",
|
||||
"secondary-foreground": "240 5.9% 10%",
|
||||
"muted": "240 3.7% 15.9%",
|
||||
"muted-foreground": "240 5% 64.9%",
|
||||
"accent": "240 3.7% 15.9%",
|
||||
"accent-foreground": "0 0% 98%",
|
||||
"destructive": "0 72% 51%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"border": "240 3.7% 15.9%",
|
||||
"input": "240 3.7% 15.9%",
|
||||
"ring": "142 86% 28%",
|
||||
"chart-1": "142 88% 28%",
|
||||
"chart-2": "139 65% 20%",
|
||||
"chart-3": "140 74% 24%",
|
||||
"chart-4": "137 55% 15%",
|
||||
"chart-5": "141 40% 9%"
|
||||
"background": "oklch(0.145 0 0)",
|
||||
"foreground": "oklch(0.985 0 0)",
|
||||
"card": "oklch(0.205 0 0)",
|
||||
"card-foreground": "oklch(0.985 0 0)",
|
||||
"popover": "oklch(0.205 0 0)",
|
||||
"popover-foreground": "oklch(0.985 0 0)",
|
||||
"primary": "oklch(0.922 0 0)",
|
||||
"primary-foreground": "oklch(0.205 0 0)",
|
||||
"secondary": "oklch(0.269 0 0)",
|
||||
"secondary-foreground": "oklch(0.985 0 0)",
|
||||
"muted": "oklch(0.269 0 0)",
|
||||
"muted-foreground": "oklch(0.708 0 0)",
|
||||
"accent": "oklch(0.269 0 0)",
|
||||
"accent-foreground": "oklch(0.985 0 0)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.556 0 0)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.205 0 0)",
|
||||
"sidebar-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.269 0 0)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.556 0 0)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "theme-gray",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.13 0.028 261.692)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"primary": "oklch(0.21 0.034 264.665)",
|
||||
"primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"secondary": "oklch(0.967 0.003 264.542)",
|
||||
"secondary-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"muted": "oklch(0.967 0.003 264.542)",
|
||||
"muted-foreground": "oklch(0.551 0.027 264.364)",
|
||||
"accent": "oklch(0.967 0.003 264.542)",
|
||||
"accent-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.928 0.006 264.531)",
|
||||
"input": "oklch(0.928 0.006 264.531)",
|
||||
"ring": "oklch(0.707 0.022 261.325)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"sidebar-primary": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-accent": "oklch(0.967 0.003 264.542)",
|
||||
"sidebar-accent-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-border": "oklch(0.928 0.006 264.531)",
|
||||
"sidebar-ring": "oklch(0.707 0.022 261.325)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.13 0.028 261.692)",
|
||||
"foreground": "oklch(0.985 0.002 247.839)",
|
||||
"card": "oklch(0.21 0.034 264.665)",
|
||||
"card-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"popover": "oklch(0.21 0.034 264.665)",
|
||||
"popover-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"primary": "oklch(0.928 0.006 264.531)",
|
||||
"primary-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"secondary": "oklch(0.278 0.033 256.848)",
|
||||
"secondary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"muted": "oklch(0.278 0.033 256.848)",
|
||||
"muted-foreground": "oklch(0.707 0.022 261.325)",
|
||||
"accent": "oklch(0.278 0.033 256.848)",
|
||||
"accent-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.551 0.027 264.364)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-accent": "oklch(0.278 0.033 256.848)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.551 0.027 264.364)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "theme-slate",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.129 0.042 264.695)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"primary": "oklch(0.208 0.042 265.755)",
|
||||
"primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"secondary": "oklch(0.968 0.007 247.896)",
|
||||
"secondary-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"muted": "oklch(0.968 0.007 247.896)",
|
||||
"muted-foreground": "oklch(0.554 0.046 257.417)",
|
||||
"accent": "oklch(0.968 0.007 247.896)",
|
||||
"accent-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.929 0.013 255.508)",
|
||||
"input": "oklch(0.929 0.013 255.508)",
|
||||
"ring": "oklch(0.704 0.04 256.788)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"sidebar-primary": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-accent": "oklch(0.968 0.007 247.896)",
|
||||
"sidebar-accent-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-border": "oklch(0.929 0.013 255.508)",
|
||||
"sidebar-ring": "oklch(0.704 0.04 256.788)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.129 0.042 264.695)",
|
||||
"foreground": "oklch(0.984 0.003 247.858)",
|
||||
"card": "oklch(0.208 0.042 265.755)",
|
||||
"card-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"popover": "oklch(0.208 0.042 265.755)",
|
||||
"popover-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"primary": "oklch(0.929 0.013 255.508)",
|
||||
"primary-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"secondary": "oklch(0.279 0.041 260.031)",
|
||||
"secondary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"muted": "oklch(0.279 0.041 260.031)",
|
||||
"muted-foreground": "oklch(0.704 0.04 256.788)",
|
||||
"accent": "oklch(0.279 0.041 260.031)",
|
||||
"accent-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.551 0.027 264.364)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-accent": "oklch(0.279 0.041 260.031)",
|
||||
"sidebar-accent-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.551 0.027 264.364)"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
74
apps/v4/public/r/styles/new-york-v4/theme-gray.json
Normal file
74
apps/v4/public/r/styles/new-york-v4/theme-gray.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "theme-gray",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.13 0.028 261.692)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"primary": "oklch(0.21 0.034 264.665)",
|
||||
"primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"secondary": "oklch(0.967 0.003 264.542)",
|
||||
"secondary-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"muted": "oklch(0.967 0.003 264.542)",
|
||||
"muted-foreground": "oklch(0.551 0.027 264.364)",
|
||||
"accent": "oklch(0.967 0.003 264.542)",
|
||||
"accent-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.928 0.006 264.531)",
|
||||
"input": "oklch(0.928 0.006 264.531)",
|
||||
"ring": "oklch(0.707 0.022 261.325)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"sidebar-primary": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-accent": "oklch(0.967 0.003 264.542)",
|
||||
"sidebar-accent-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-border": "oklch(0.928 0.006 264.531)",
|
||||
"sidebar-ring": "oklch(0.707 0.022 261.325)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.13 0.028 261.692)",
|
||||
"foreground": "oklch(0.985 0.002 247.839)",
|
||||
"card": "oklch(0.21 0.034 264.665)",
|
||||
"card-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"popover": "oklch(0.21 0.034 264.665)",
|
||||
"popover-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"primary": "oklch(0.928 0.006 264.531)",
|
||||
"primary-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"secondary": "oklch(0.278 0.033 256.848)",
|
||||
"secondary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"muted": "oklch(0.278 0.033 256.848)",
|
||||
"muted-foreground": "oklch(0.707 0.022 261.325)",
|
||||
"accent": "oklch(0.278 0.033 256.848)",
|
||||
"accent-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.551 0.027 264.364)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-accent": "oklch(0.278 0.033 256.848)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.551 0.027 264.364)"
|
||||
}
|
||||
}
|
||||
}
|
||||
74
apps/v4/public/r/styles/new-york-v4/theme-neutral.json
Normal file
74
apps/v4/public/r/styles/new-york-v4/theme-neutral.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "theme-neutral",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.145 0 0)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.145 0 0)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.145 0 0)",
|
||||
"primary": "oklch(0.205 0 0)",
|
||||
"primary-foreground": "oklch(0.985 0 0)",
|
||||
"secondary": "oklch(0.97 0 0)",
|
||||
"secondary-foreground": "oklch(0.205 0 0)",
|
||||
"muted": "oklch(0.97 0 0)",
|
||||
"muted-foreground": "oklch(0.556 0 0)",
|
||||
"accent": "oklch(0.97 0 0)",
|
||||
"accent-foreground": "oklch(0.205 0 0)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.922 0 0)",
|
||||
"input": "oklch(0.922 0 0)",
|
||||
"ring": "oklch(0.708 0 0)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0 0)",
|
||||
"sidebar-foreground": "oklch(0.145 0 0)",
|
||||
"sidebar-primary": "oklch(0.205 0 0)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.97 0 0)",
|
||||
"sidebar-accent-foreground": "oklch(0.205 0 0)",
|
||||
"sidebar-border": "oklch(0.922 0 0)",
|
||||
"sidebar-ring": "oklch(0.708 0 0)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.145 0 0)",
|
||||
"foreground": "oklch(0.985 0 0)",
|
||||
"card": "oklch(0.205 0 0)",
|
||||
"card-foreground": "oklch(0.985 0 0)",
|
||||
"popover": "oklch(0.205 0 0)",
|
||||
"popover-foreground": "oklch(0.985 0 0)",
|
||||
"primary": "oklch(0.922 0 0)",
|
||||
"primary-foreground": "oklch(0.205 0 0)",
|
||||
"secondary": "oklch(0.269 0 0)",
|
||||
"secondary-foreground": "oklch(0.985 0 0)",
|
||||
"muted": "oklch(0.269 0 0)",
|
||||
"muted-foreground": "oklch(0.708 0 0)",
|
||||
"accent": "oklch(0.269 0 0)",
|
||||
"accent-foreground": "oklch(0.985 0 0)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.556 0 0)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.205 0 0)",
|
||||
"sidebar-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.269 0 0)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.556 0 0)"
|
||||
}
|
||||
}
|
||||
}
|
||||
74
apps/v4/public/r/styles/new-york-v4/theme-slate.json
Normal file
74
apps/v4/public/r/styles/new-york-v4/theme-slate.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "theme-slate",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.129 0.042 264.695)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"primary": "oklch(0.208 0.042 265.755)",
|
||||
"primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"secondary": "oklch(0.968 0.007 247.896)",
|
||||
"secondary-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"muted": "oklch(0.968 0.007 247.896)",
|
||||
"muted-foreground": "oklch(0.554 0.046 257.417)",
|
||||
"accent": "oklch(0.968 0.007 247.896)",
|
||||
"accent-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.929 0.013 255.508)",
|
||||
"input": "oklch(0.929 0.013 255.508)",
|
||||
"ring": "oklch(0.704 0.04 256.788)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"sidebar-primary": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-accent": "oklch(0.968 0.007 247.896)",
|
||||
"sidebar-accent-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-border": "oklch(0.929 0.013 255.508)",
|
||||
"sidebar-ring": "oklch(0.704 0.04 256.788)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.129 0.042 264.695)",
|
||||
"foreground": "oklch(0.984 0.003 247.858)",
|
||||
"card": "oklch(0.208 0.042 265.755)",
|
||||
"card-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"popover": "oklch(0.208 0.042 265.755)",
|
||||
"popover-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"primary": "oklch(0.929 0.013 255.508)",
|
||||
"primary-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"secondary": "oklch(0.279 0.041 260.031)",
|
||||
"secondary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"muted": "oklch(0.279 0.041 260.031)",
|
||||
"muted-foreground": "oklch(0.704 0.04 256.788)",
|
||||
"accent": "oklch(0.279 0.041 260.031)",
|
||||
"accent-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.551 0.027 264.364)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-accent": "oklch(0.279 0.041 260.031)",
|
||||
"sidebar-accent-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.551 0.027 264.364)"
|
||||
}
|
||||
}
|
||||
}
|
||||
74
apps/v4/public/r/styles/new-york-v4/theme-stone.json
Normal file
74
apps/v4/public/r/styles/new-york-v4/theme-stone.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "theme-stone",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.147 0.004 49.25)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"primary": "oklch(0.216 0.006 56.043)",
|
||||
"primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"secondary": "oklch(0.97 0.001 106.424)",
|
||||
"secondary-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"muted": "oklch(0.97 0.001 106.424)",
|
||||
"muted-foreground": "oklch(0.553 0.013 58.071)",
|
||||
"accent": "oklch(0.97 0.001 106.424)",
|
||||
"accent-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.923 0.003 48.717)",
|
||||
"input": "oklch(0.923 0.003 48.717)",
|
||||
"ring": "oklch(0.709 0.01 56.259)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"sidebar-primary": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-accent": "oklch(0.97 0.001 106.424)",
|
||||
"sidebar-accent-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-border": "oklch(0.923 0.003 48.717)",
|
||||
"sidebar-ring": "oklch(0.709 0.01 56.259)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.147 0.004 49.25)",
|
||||
"foreground": "oklch(0.985 0.001 106.423)",
|
||||
"card": "oklch(0.216 0.006 56.043)",
|
||||
"card-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"popover": "oklch(0.216 0.006 56.043)",
|
||||
"popover-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"primary": "oklch(0.923 0.003 48.717)",
|
||||
"primary-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"secondary": "oklch(0.268 0.007 34.298)",
|
||||
"secondary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"muted": "oklch(0.268 0.007 34.298)",
|
||||
"muted-foreground": "oklch(0.709 0.01 56.259)",
|
||||
"accent": "oklch(0.268 0.007 34.298)",
|
||||
"accent-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.553 0.013 58.071)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-accent": "oklch(0.268 0.007 34.298)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.553 0.013 58.071)"
|
||||
}
|
||||
}
|
||||
}
|
||||
74
apps/v4/public/r/styles/new-york-v4/theme-zinc.json
Normal file
74
apps/v4/public/r/styles/new-york-v4/theme-zinc.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "theme-zinc",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.141 0.005 285.823)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"primary": "oklch(0.21 0.006 285.885)",
|
||||
"primary-foreground": "oklch(0.985 0 0)",
|
||||
"secondary": "oklch(0.967 0.001 286.375)",
|
||||
"secondary-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"muted": "oklch(0.967 0.001 286.375)",
|
||||
"muted-foreground": "oklch(0.552 0.016 285.938)",
|
||||
"accent": "oklch(0.967 0.001 286.375)",
|
||||
"accent-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.92 0.004 286.32)",
|
||||
"input": "oklch(0.92 0.004 286.32)",
|
||||
"ring": "oklch(0.705 0.015 286.067)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0 0)",
|
||||
"sidebar-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"sidebar-primary": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.967 0.001 286.375)",
|
||||
"sidebar-accent-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-border": "oklch(0.92 0.004 286.32)",
|
||||
"sidebar-ring": "oklch(0.705 0.015 286.067)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.141 0.005 285.823)",
|
||||
"foreground": "oklch(0.985 0 0)",
|
||||
"card": "oklch(0.21 0.006 285.885)",
|
||||
"card-foreground": "oklch(0.985 0 0)",
|
||||
"popover": "oklch(0.21 0.006 285.885)",
|
||||
"popover-foreground": "oklch(0.985 0 0)",
|
||||
"primary": "oklch(0.92 0.004 286.32)",
|
||||
"primary-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"secondary": "oklch(0.274 0.006 286.033)",
|
||||
"secondary-foreground": "oklch(0.985 0 0)",
|
||||
"muted": "oklch(0.274 0.006 286.033)",
|
||||
"muted-foreground": "oklch(0.705 0.015 286.067)",
|
||||
"accent": "oklch(0.274 0.006 286.033)",
|
||||
"accent-foreground": "oklch(0.985 0 0)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.552 0.016 285.938)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.274 0.006 286.033)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.552 0.016 285.938)"
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -163,32 +163,7 @@
|
||||
"type": "object",
|
||||
"description": "CSS definitions to be added to the project's CSS file. Supports at-rules, selectors, nested rules, utilities, layers, and more.",
|
||||
"additionalProperties": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Direct CSS string (e.g., 'font-family: sans-serif; line-height: 1.5;')"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"description": "CSS properties or nested selectors",
|
||||
"additionalProperties": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "CSS property value (e.g., 'blue', 'var(--color-primary)')"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"description": "Nested selector or rule with properties",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"description": "CSS property value for nested rule"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/cssValue"
|
||||
}
|
||||
},
|
||||
"envVars": {
|
||||
@@ -219,5 +194,22 @@
|
||||
"description": "The name of the registry item to extend. This is used to extend the base shadcn/ui style. Set to none to start fresh. This is available for registry:style items only."
|
||||
}
|
||||
},
|
||||
"required": ["name", "type"]
|
||||
"required": ["name", "type"],
|
||||
"definitions": {
|
||||
"cssValue": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "CSS property value or direct CSS string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"description": "Nested CSS properties, selectors, or at-rules. Empty objects are allowed for at-rules with no body.",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/cssValue"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3593,176 +3593,367 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "theme-daylight",
|
||||
"name": "theme-stone",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "36 39% 88%",
|
||||
"foreground": "36 45% 15%",
|
||||
"primary": "36 45% 70%",
|
||||
"primary-foreground": "36 45% 11%",
|
||||
"secondary": "40 35% 77%",
|
||||
"secondary-foreground": "36 45% 25%",
|
||||
"accent": "36 64% 57%",
|
||||
"accent-foreground": "36 72% 17%",
|
||||
"destructive": "0 84% 37%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"muted": "36 33% 75%",
|
||||
"muted-foreground": "36 45% 25%",
|
||||
"card": "36 46% 82%",
|
||||
"card-foreground": "36 45% 20%",
|
||||
"popover": "0 0% 100%",
|
||||
"popover-foreground": "240 10% 3.9%",
|
||||
"border": "36 45% 60%",
|
||||
"input": "36 45% 60%",
|
||||
"ring": "36 45% 30%",
|
||||
"chart-1": "25 34% 28%",
|
||||
"chart-2": "26 36% 34%",
|
||||
"chart-3": "28 40% 40%",
|
||||
"chart-4": "31 41% 48%",
|
||||
"chart-5": "35 43% 53%"
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.147 0.004 49.25)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"primary": "oklch(0.216 0.006 56.043)",
|
||||
"primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"secondary": "oklch(0.97 0.001 106.424)",
|
||||
"secondary-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"muted": "oklch(0.97 0.001 106.424)",
|
||||
"muted-foreground": "oklch(0.553 0.013 58.071)",
|
||||
"accent": "oklch(0.97 0.001 106.424)",
|
||||
"accent-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.923 0.003 48.717)",
|
||||
"input": "oklch(0.923 0.003 48.717)",
|
||||
"ring": "oklch(0.709 0.01 56.259)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"sidebar-primary": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-accent": "oklch(0.97 0.001 106.424)",
|
||||
"sidebar-accent-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-border": "oklch(0.923 0.003 48.717)",
|
||||
"sidebar-ring": "oklch(0.709 0.01 56.259)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "36 39% 88%",
|
||||
"foreground": "36 45% 15%",
|
||||
"primary": "36 45% 70%",
|
||||
"primary-foreground": "36 45% 11%",
|
||||
"secondary": "40 35% 77%",
|
||||
"secondary-foreground": "36 45% 25%",
|
||||
"accent": "36 64% 57%",
|
||||
"accent-foreground": "36 72% 17%",
|
||||
"destructive": "0 84% 37%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"muted": "36 33% 75%",
|
||||
"muted-foreground": "36 45% 25%",
|
||||
"card": "36 46% 82%",
|
||||
"card-foreground": "36 45% 20%",
|
||||
"popover": "0 0% 100%",
|
||||
"popover-foreground": "240 10% 3.9%",
|
||||
"border": "36 45% 60%",
|
||||
"input": "36 45% 60%",
|
||||
"ring": "36 45% 30%",
|
||||
"chart-1": "25 34% 28%",
|
||||
"chart-2": "26 36% 34%",
|
||||
"chart-3": "28 40% 40%",
|
||||
"chart-4": "31 41% 48%",
|
||||
"chart-5": "35 43% 53%"
|
||||
"background": "oklch(0.147 0.004 49.25)",
|
||||
"foreground": "oklch(0.985 0.001 106.423)",
|
||||
"card": "oklch(0.216 0.006 56.043)",
|
||||
"card-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"popover": "oklch(0.216 0.006 56.043)",
|
||||
"popover-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"primary": "oklch(0.923 0.003 48.717)",
|
||||
"primary-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"secondary": "oklch(0.268 0.007 34.298)",
|
||||
"secondary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"muted": "oklch(0.268 0.007 34.298)",
|
||||
"muted-foreground": "oklch(0.709 0.01 56.259)",
|
||||
"accent": "oklch(0.268 0.007 34.298)",
|
||||
"accent-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.553 0.013 58.071)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-accent": "oklch(0.268 0.007 34.298)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.553 0.013 58.071)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "theme-midnight",
|
||||
"name": "theme-zinc",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "240 5% 6%",
|
||||
"foreground": "60 5% 90%",
|
||||
"primary": "240 0% 90%",
|
||||
"primary-foreground": "60 0% 0%",
|
||||
"secondary": "240 4% 15%",
|
||||
"secondary-foreground": "60 5% 85%",
|
||||
"accent": "240 0% 13%",
|
||||
"accent-foreground": "60 0% 100%",
|
||||
"destructive": "0 60% 50%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"muted": "240 5% 25%",
|
||||
"muted-foreground": "60 5% 85%",
|
||||
"card": "240 4% 10%",
|
||||
"card-foreground": "60 5% 90%",
|
||||
"popover": "240 5% 15%",
|
||||
"popover-foreground": "60 5% 85%",
|
||||
"border": "240 6% 20%",
|
||||
"input": "240 6% 20%",
|
||||
"ring": "240 5% 90%",
|
||||
"chart-1": "359 2% 90%",
|
||||
"chart-2": "240 1% 74%",
|
||||
"chart-3": "240 1% 58%",
|
||||
"chart-4": "240 1% 42%",
|
||||
"chart-5": "240 2% 26%"
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.141 0.005 285.823)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"primary": "oklch(0.21 0.006 285.885)",
|
||||
"primary-foreground": "oklch(0.985 0 0)",
|
||||
"secondary": "oklch(0.967 0.001 286.375)",
|
||||
"secondary-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"muted": "oklch(0.967 0.001 286.375)",
|
||||
"muted-foreground": "oklch(0.552 0.016 285.938)",
|
||||
"accent": "oklch(0.967 0.001 286.375)",
|
||||
"accent-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.92 0.004 286.32)",
|
||||
"input": "oklch(0.92 0.004 286.32)",
|
||||
"ring": "oklch(0.705 0.015 286.067)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0 0)",
|
||||
"sidebar-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"sidebar-primary": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.967 0.001 286.375)",
|
||||
"sidebar-accent-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-border": "oklch(0.92 0.004 286.32)",
|
||||
"sidebar-ring": "oklch(0.705 0.015 286.067)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "240 5% 6%",
|
||||
"foreground": "60 5% 90%",
|
||||
"primary": "240 0% 90%",
|
||||
"primary-foreground": "60 0% 0%",
|
||||
"secondary": "240 4% 15%",
|
||||
"secondary-foreground": "60 5% 85%",
|
||||
"accent": "240 0% 13%",
|
||||
"accent-foreground": "60 0% 100%",
|
||||
"destructive": "0 60% 50%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"muted": "240 5% 25%",
|
||||
"muted-foreground": "60 5% 85%",
|
||||
"card": "240 4% 10%",
|
||||
"card-foreground": "60 5% 90%",
|
||||
"popover": "240 5% 15%",
|
||||
"popover-foreground": "60 5% 85%",
|
||||
"border": "240 6% 20%",
|
||||
"input": "240 6% 20%",
|
||||
"ring": "240 5% 90%",
|
||||
"chart-1": "359 2% 90%",
|
||||
"chart-2": "240 1% 74%",
|
||||
"chart-3": "240 1% 58%",
|
||||
"chart-4": "240 1% 42%",
|
||||
"chart-5": "240 2% 26%"
|
||||
"background": "oklch(0.141 0.005 285.823)",
|
||||
"foreground": "oklch(0.985 0 0)",
|
||||
"card": "oklch(0.21 0.006 285.885)",
|
||||
"card-foreground": "oklch(0.985 0 0)",
|
||||
"popover": "oklch(0.21 0.006 285.885)",
|
||||
"popover-foreground": "oklch(0.985 0 0)",
|
||||
"primary": "oklch(0.92 0.004 286.32)",
|
||||
"primary-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"secondary": "oklch(0.274 0.006 286.033)",
|
||||
"secondary-foreground": "oklch(0.985 0 0)",
|
||||
"muted": "oklch(0.274 0.006 286.033)",
|
||||
"muted-foreground": "oklch(0.705 0.015 286.067)",
|
||||
"accent": "oklch(0.274 0.006 286.033)",
|
||||
"accent-foreground": "oklch(0.985 0 0)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.552 0.016 285.938)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.274 0.006 286.033)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.552 0.016 285.938)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "theme-emerald",
|
||||
"name": "theme-neutral",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "240 10% 3.9%",
|
||||
"card": "0 0% 100%",
|
||||
"card-foreground": "240 10% 3.9%",
|
||||
"popover": "0 0% 100%",
|
||||
"popover-foreground": "240 10% 3.9%",
|
||||
"primary": "142 86% 28%",
|
||||
"primary-foreground": "356 29% 98%",
|
||||
"secondary": "240 4.8% 95.9%",
|
||||
"secondary-foreground": "240 5.9% 10%",
|
||||
"muted": "240 4.8% 95.9%",
|
||||
"muted-foreground": "240 3.8% 45%",
|
||||
"accent": "240 4.8% 95.9%",
|
||||
"accent-foreground": "240 5.9% 10%",
|
||||
"destructive": "0 72% 51%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"border": "240 5.9% 90%",
|
||||
"input": "240 5.9% 90%",
|
||||
"ring": "142 86% 28%",
|
||||
"chart-1": "139 65% 20%",
|
||||
"chart-2": "140 74% 44%",
|
||||
"chart-3": "142 88% 28%",
|
||||
"chart-4": "137 55% 15%",
|
||||
"chart-5": "141 40% 9%"
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.145 0 0)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.145 0 0)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.145 0 0)",
|
||||
"primary": "oklch(0.205 0 0)",
|
||||
"primary-foreground": "oklch(0.985 0 0)",
|
||||
"secondary": "oklch(0.97 0 0)",
|
||||
"secondary-foreground": "oklch(0.205 0 0)",
|
||||
"muted": "oklch(0.97 0 0)",
|
||||
"muted-foreground": "oklch(0.556 0 0)",
|
||||
"accent": "oklch(0.97 0 0)",
|
||||
"accent-foreground": "oklch(0.205 0 0)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.922 0 0)",
|
||||
"input": "oklch(0.922 0 0)",
|
||||
"ring": "oklch(0.708 0 0)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0 0)",
|
||||
"sidebar-foreground": "oklch(0.145 0 0)",
|
||||
"sidebar-primary": "oklch(0.205 0 0)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.97 0 0)",
|
||||
"sidebar-accent-foreground": "oklch(0.205 0 0)",
|
||||
"sidebar-border": "oklch(0.922 0 0)",
|
||||
"sidebar-ring": "oklch(0.708 0 0)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "240 10% 3.9%",
|
||||
"foreground": "0 0% 98%",
|
||||
"card": "240 10% 3.9%",
|
||||
"card-foreground": "0 0% 98%",
|
||||
"popover": "240 10% 3.9%",
|
||||
"popover-foreground": "0 0% 98%",
|
||||
"primary": "142 86% 28%",
|
||||
"primary-foreground": "356 29% 98%",
|
||||
"secondary": "240 4.8% 95.9%",
|
||||
"secondary-foreground": "240 5.9% 10%",
|
||||
"muted": "240 3.7% 15.9%",
|
||||
"muted-foreground": "240 5% 64.9%",
|
||||
"accent": "240 3.7% 15.9%",
|
||||
"accent-foreground": "0 0% 98%",
|
||||
"destructive": "0 72% 51%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"border": "240 3.7% 15.9%",
|
||||
"input": "240 3.7% 15.9%",
|
||||
"ring": "142 86% 28%",
|
||||
"chart-1": "142 88% 28%",
|
||||
"chart-2": "139 65% 20%",
|
||||
"chart-3": "140 74% 24%",
|
||||
"chart-4": "137 55% 15%",
|
||||
"chart-5": "141 40% 9%"
|
||||
"background": "oklch(0.145 0 0)",
|
||||
"foreground": "oklch(0.985 0 0)",
|
||||
"card": "oklch(0.205 0 0)",
|
||||
"card-foreground": "oklch(0.985 0 0)",
|
||||
"popover": "oklch(0.205 0 0)",
|
||||
"popover-foreground": "oklch(0.985 0 0)",
|
||||
"primary": "oklch(0.922 0 0)",
|
||||
"primary-foreground": "oklch(0.205 0 0)",
|
||||
"secondary": "oklch(0.269 0 0)",
|
||||
"secondary-foreground": "oklch(0.985 0 0)",
|
||||
"muted": "oklch(0.269 0 0)",
|
||||
"muted-foreground": "oklch(0.708 0 0)",
|
||||
"accent": "oklch(0.269 0 0)",
|
||||
"accent-foreground": "oklch(0.985 0 0)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.556 0 0)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.205 0 0)",
|
||||
"sidebar-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.269 0 0)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.556 0 0)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "theme-gray",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.13 0.028 261.692)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"primary": "oklch(0.21 0.034 264.665)",
|
||||
"primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"secondary": "oklch(0.967 0.003 264.542)",
|
||||
"secondary-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"muted": "oklch(0.967 0.003 264.542)",
|
||||
"muted-foreground": "oklch(0.551 0.027 264.364)",
|
||||
"accent": "oklch(0.967 0.003 264.542)",
|
||||
"accent-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.928 0.006 264.531)",
|
||||
"input": "oklch(0.928 0.006 264.531)",
|
||||
"ring": "oklch(0.707 0.022 261.325)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"sidebar-primary": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-accent": "oklch(0.967 0.003 264.542)",
|
||||
"sidebar-accent-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-border": "oklch(0.928 0.006 264.531)",
|
||||
"sidebar-ring": "oklch(0.707 0.022 261.325)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.13 0.028 261.692)",
|
||||
"foreground": "oklch(0.985 0.002 247.839)",
|
||||
"card": "oklch(0.21 0.034 264.665)",
|
||||
"card-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"popover": "oklch(0.21 0.034 264.665)",
|
||||
"popover-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"primary": "oklch(0.928 0.006 264.531)",
|
||||
"primary-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"secondary": "oklch(0.278 0.033 256.848)",
|
||||
"secondary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"muted": "oklch(0.278 0.033 256.848)",
|
||||
"muted-foreground": "oklch(0.707 0.022 261.325)",
|
||||
"accent": "oklch(0.278 0.033 256.848)",
|
||||
"accent-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.551 0.027 264.364)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-accent": "oklch(0.278 0.033 256.848)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.551 0.027 264.364)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "theme-slate",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.129 0.042 264.695)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"primary": "oklch(0.208 0.042 265.755)",
|
||||
"primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"secondary": "oklch(0.968 0.007 247.896)",
|
||||
"secondary-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"muted": "oklch(0.968 0.007 247.896)",
|
||||
"muted-foreground": "oklch(0.554 0.046 257.417)",
|
||||
"accent": "oklch(0.968 0.007 247.896)",
|
||||
"accent-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.929 0.013 255.508)",
|
||||
"input": "oklch(0.929 0.013 255.508)",
|
||||
"ring": "oklch(0.704 0.04 256.788)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"sidebar-primary": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-accent": "oklch(0.968 0.007 247.896)",
|
||||
"sidebar-accent-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-border": "oklch(0.929 0.013 255.508)",
|
||||
"sidebar-ring": "oklch(0.704 0.04 256.788)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.129 0.042 264.695)",
|
||||
"foreground": "oklch(0.984 0.003 247.858)",
|
||||
"card": "oklch(0.208 0.042 265.755)",
|
||||
"card-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"popover": "oklch(0.208 0.042 265.755)",
|
||||
"popover-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"primary": "oklch(0.929 0.013 255.508)",
|
||||
"primary-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"secondary": "oklch(0.279 0.041 260.031)",
|
||||
"secondary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"muted": "oklch(0.279 0.041 260.031)",
|
||||
"muted-foreground": "oklch(0.704 0.04 256.788)",
|
||||
"accent": "oklch(0.279 0.041 260.031)",
|
||||
"accent-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.551 0.027 264.364)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-accent": "oklch(0.279 0.041 260.031)",
|
||||
"sidebar-accent-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.551 0.027 264.364)"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -179,70 +179,72 @@ function ChartTooltipContent({
|
||||
>
|
||||
{!nestLabel ? tooltipLabel : null}
|
||||
<div className="grid gap-1.5">
|
||||
{payload.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const indicatorColor = color || item.payload.fill || item.color
|
||||
{payload
|
||||
.filter((item) => item.type !== "none")
|
||||
.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const indicatorColor = color || item.payload.fill || item.color
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.dataKey}
|
||||
className={cn(
|
||||
"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
|
||||
indicator === "dot" && "items-center"
|
||||
)}
|
||||
>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
formatter(item.value, item.name, item, index, item.payload)
|
||||
) : (
|
||||
<>
|
||||
{itemConfig?.icon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
return (
|
||||
<div
|
||||
key={item.dataKey}
|
||||
className={cn(
|
||||
"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
|
||||
indicator === "dot" && "items-center"
|
||||
)}
|
||||
>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
formatter(item.value, item.name, item, index, item.payload)
|
||||
) : (
|
||||
<>
|
||||
{itemConfig?.icon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
}
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--color-bg": indicatorColor,
|
||||
"--color-border": indicatorColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--color-bg": indicatorColor,
|
||||
"--color-border": indicatorColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center"
|
||||
/>
|
||||
)
|
||||
)}
|
||||
>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-muted-foreground">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center"
|
||||
)}
|
||||
>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-muted-foreground">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="text-foreground font-mono font-medium tabular-nums">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="text-foreground font-mono font-medium tabular-nums">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -275,31 +277,33 @@ function ChartLegendContent({
|
||||
className
|
||||
)}
|
||||
>
|
||||
{payload.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
{payload
|
||||
.filter((item) => item.type !== "none")
|
||||
.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
|
||||
)}
|
||||
>
|
||||
{itemConfig?.icon && !hideIcon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{itemConfig?.label}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
|
||||
)}
|
||||
>
|
||||
{itemConfig?.icon && !hideIcon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{itemConfig?.label}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,178 +1,14 @@
|
||||
import { type Registry } from "shadcn/schema"
|
||||
|
||||
export const themes: Registry["items"] = [
|
||||
{
|
||||
name: "theme-daylight",
|
||||
type: "registry:theme",
|
||||
cssVars: {
|
||||
light: {
|
||||
background: "36 39% 88%",
|
||||
foreground: "36 45% 15%",
|
||||
primary: "36 45% 70%",
|
||||
"primary-foreground": "36 45% 11%",
|
||||
secondary: "40 35% 77%",
|
||||
"secondary-foreground": "36 45% 25%",
|
||||
accent: "36 64% 57%",
|
||||
"accent-foreground": "36 72% 17%",
|
||||
destructive: "0 84% 37%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
muted: "36 33% 75%",
|
||||
"muted-foreground": "36 45% 25%",
|
||||
card: "36 46% 82%",
|
||||
"card-foreground": "36 45% 20%",
|
||||
popover: "0 0% 100%",
|
||||
"popover-foreground": "240 10% 3.9%",
|
||||
border: "36 45% 60%",
|
||||
input: "36 45% 60%",
|
||||
ring: "36 45% 30%",
|
||||
"chart-1": "25 34% 28%",
|
||||
"chart-2": "26 36% 34%",
|
||||
"chart-3": "28 40% 40%",
|
||||
"chart-4": "31 41% 48%",
|
||||
"chart-5": "35 43% 53%",
|
||||
},
|
||||
dark: {
|
||||
background: "36 39% 88%",
|
||||
foreground: "36 45% 15%",
|
||||
primary: "36 45% 70%",
|
||||
"primary-foreground": "36 45% 11%",
|
||||
secondary: "40 35% 77%",
|
||||
"secondary-foreground": "36 45% 25%",
|
||||
accent: "36 64% 57%",
|
||||
"accent-foreground": "36 72% 17%",
|
||||
destructive: "0 84% 37%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
muted: "36 33% 75%",
|
||||
"muted-foreground": "36 45% 25%",
|
||||
card: "36 46% 82%",
|
||||
"card-foreground": "36 45% 20%",
|
||||
popover: "0 0% 100%",
|
||||
"popover-foreground": "240 10% 3.9%",
|
||||
border: "36 45% 60%",
|
||||
input: "36 45% 60%",
|
||||
ring: "36 45% 30%",
|
||||
"chart-1": "25 34% 28%",
|
||||
"chart-2": "26 36% 34%",
|
||||
"chart-3": "28 40% 40%",
|
||||
"chart-4": "31 41% 48%",
|
||||
"chart-5": "35 43% 53%",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "theme-midnight",
|
||||
type: "registry:theme",
|
||||
cssVars: {
|
||||
light: {
|
||||
background: "240 5% 6%",
|
||||
foreground: "60 5% 90%",
|
||||
primary: "240 0% 90%",
|
||||
"primary-foreground": "60 0% 0%",
|
||||
secondary: "240 4% 15%",
|
||||
"secondary-foreground": "60 5% 85%",
|
||||
accent: "240 0% 13%",
|
||||
"accent-foreground": "60 0% 100%",
|
||||
destructive: "0 60% 50%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
muted: "240 5% 25%",
|
||||
"muted-foreground": "60 5% 85%",
|
||||
card: "240 4% 10%",
|
||||
"card-foreground": "60 5% 90%",
|
||||
popover: "240 5% 15%",
|
||||
"popover-foreground": "60 5% 85%",
|
||||
border: "240 6% 20%",
|
||||
input: "240 6% 20%",
|
||||
ring: "240 5% 90%",
|
||||
"chart-1": "359 2% 90%",
|
||||
"chart-2": "240 1% 74%",
|
||||
"chart-3": "240 1% 58%",
|
||||
"chart-4": "240 1% 42%",
|
||||
"chart-5": "240 2% 26%",
|
||||
},
|
||||
dark: {
|
||||
background: "240 5% 6%",
|
||||
foreground: "60 5% 90%",
|
||||
primary: "240 0% 90%",
|
||||
"primary-foreground": "60 0% 0%",
|
||||
secondary: "240 4% 15%",
|
||||
"secondary-foreground": "60 5% 85%",
|
||||
accent: "240 0% 13%",
|
||||
"accent-foreground": "60 0% 100%",
|
||||
destructive: "0 60% 50%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
muted: "240 5% 25%",
|
||||
"muted-foreground": "60 5% 85%",
|
||||
card: "240 4% 10%",
|
||||
"card-foreground": "60 5% 90%",
|
||||
popover: "240 5% 15%",
|
||||
"popover-foreground": "60 5% 85%",
|
||||
border: "240 6% 20%",
|
||||
input: "240 6% 20%",
|
||||
ring: "240 5% 90%",
|
||||
"chart-1": "359 2% 90%",
|
||||
"chart-2": "240 1% 74%",
|
||||
"chart-3": "240 1% 58%",
|
||||
"chart-4": "240 1% 42%",
|
||||
"chart-5": "240 2% 26%",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "theme-emerald",
|
||||
type: "registry:theme",
|
||||
cssVars: {
|
||||
light: {
|
||||
background: "0 0% 100%",
|
||||
foreground: "240 10% 3.9%",
|
||||
card: "0 0% 100%",
|
||||
"card-foreground": "240 10% 3.9%",
|
||||
popover: "0 0% 100%",
|
||||
"popover-foreground": "240 10% 3.9%",
|
||||
primary: "142 86% 28%",
|
||||
"primary-foreground": "356 29% 98%",
|
||||
secondary: "240 4.8% 95.9%",
|
||||
"secondary-foreground": "240 5.9% 10%",
|
||||
muted: "240 4.8% 95.9%",
|
||||
"muted-foreground": "240 3.8% 45%",
|
||||
accent: "240 4.8% 95.9%",
|
||||
"accent-foreground": "240 5.9% 10%",
|
||||
destructive: "0 72% 51%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
border: "240 5.9% 90%",
|
||||
input: "240 5.9% 90%",
|
||||
ring: "142 86% 28%",
|
||||
"chart-1": "139 65% 20%",
|
||||
"chart-2": "140 74% 44%",
|
||||
"chart-3": "142 88% 28%",
|
||||
"chart-4": "137 55% 15%",
|
||||
"chart-5": "141 40% 9%",
|
||||
},
|
||||
dark: {
|
||||
background: "240 10% 3.9%",
|
||||
foreground: "0 0% 98%",
|
||||
card: "240 10% 3.9%",
|
||||
"card-foreground": "0 0% 98%",
|
||||
popover: "240 10% 3.9%",
|
||||
"popover-foreground": "0 0% 98%",
|
||||
primary: "142 86% 28%",
|
||||
"primary-foreground": "356 29% 98%",
|
||||
secondary: "240 4.8% 95.9%",
|
||||
"secondary-foreground": "240 5.9% 10%",
|
||||
muted: "240 3.7% 15.9%",
|
||||
"muted-foreground": "240 5% 64.9%",
|
||||
accent: "240 3.7% 15.9%",
|
||||
"accent-foreground": "0 0% 98%",
|
||||
destructive: "0 72% 51%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
border: "240 3.7% 15.9%",
|
||||
input: "240 3.7% 15.9%",
|
||||
ring: "142 86% 28%",
|
||||
"chart-1": "142 88% 28%",
|
||||
"chart-2": "139 65% 20%",
|
||||
"chart-3": "140 74% 24%",
|
||||
"chart-4": "137 55% 15%",
|
||||
"chart-5": "141 40% 9%",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
import { baseColorsV4 } from "@/registry/registry-base-colors"
|
||||
|
||||
// Create a theme for each color in the base colors.
|
||||
export const themes: Registry["items"] = Object.keys(baseColorsV4).map(
|
||||
(color) => {
|
||||
return {
|
||||
name: `theme-${color}`,
|
||||
type: "registry:theme",
|
||||
cssVars: baseColorsV4[color as keyof typeof baseColorsV4],
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -107,10 +107,15 @@ async function buildRegistryJsonFile() {
|
||||
|
||||
async function buildRegistry() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Use local shadcn copy.
|
||||
const process = exec(
|
||||
`pnpm dlx shadcn build registry.json --output ../www/public/r/styles/new-york-v4`
|
||||
`node ../../packages/shadcn/dist/index.js build registry.json --output ../www/public/r/styles/new-york-v4`
|
||||
)
|
||||
|
||||
// exec(
|
||||
// `pnpm dlx shadcn build registry.json --output ../www/public/r/styles/new-york-v4`
|
||||
// )
|
||||
|
||||
process.on("exit", (code) => {
|
||||
if (code === 0) {
|
||||
resolve(undefined)
|
||||
|
||||
58
apps/v4/scripts/validate-registries.mts
Normal file
58
apps/v4/scripts/validate-registries.mts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { promises as fs } from "fs"
|
||||
import path from "path"
|
||||
import { registrySchema } from "shadcn/schema"
|
||||
import { z } from "zod"
|
||||
|
||||
const registriesIndexSchema = z.record(
|
||||
z.string().regex(/^@[a-zA-Z0-9][a-zA-Z0-9-_]*$/),
|
||||
z.string().refine((url) => url.includes("{name}"))
|
||||
)
|
||||
|
||||
async function main() {
|
||||
// 1. Validate the registries.json file.
|
||||
const registriesFile = path.join(process.cwd(), "public/r/registries.json")
|
||||
const content = await fs.readFile(registriesFile, "utf-8")
|
||||
const data = JSON.parse(content)
|
||||
const registries = registriesIndexSchema.parse(data)
|
||||
|
||||
// 2. Validate each registry endpoint.
|
||||
const errors: string[] = []
|
||||
for (const [name, url] of Object.entries(registries)) {
|
||||
try {
|
||||
const testUrl = url.replace("{name}", "registry")
|
||||
const response = await fetch(testUrl)
|
||||
|
||||
if (!response.ok) {
|
||||
errors.push(`${name}: HTTP ${response.status}`)
|
||||
continue
|
||||
}
|
||||
|
||||
const json = await response.json()
|
||||
registrySchema.parse(json)
|
||||
console.log(`✅ ${name}`)
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
errors.push(`${name}: ${error.message}`)
|
||||
continue
|
||||
}
|
||||
|
||||
errors.push(
|
||||
`${name}: ${error instanceof Error ? error.message : String(error)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error("\n❌ Validation failed:")
|
||||
errors.forEach((err) => console.error(` ${err}`))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log("\n✅ All registries passed validation.")
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("❌ Error:", error instanceof Error ? error.message : error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -248,7 +248,7 @@
|
||||
font-size: var(--text-sm);
|
||||
outline: none;
|
||||
position: relative;
|
||||
@apply md:-mx-1;
|
||||
@apply -mx-1 md:-mx-1;
|
||||
|
||||
&:has([data-rehype-pretty-code-title]) [data-slot="copy-button"] {
|
||||
top: calc(var(--spacing) * 1.5) !important;
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
"react-resizable-panels": "^2.0.22",
|
||||
"react-wrap-balancer": "^0.4.1",
|
||||
"recharts": "2.12.7",
|
||||
"shadcn": "3.0.0",
|
||||
"shadcn": "3.3.0",
|
||||
"sharp": "^0.32.6",
|
||||
"sonner": "^1.2.3",
|
||||
"swr": "2.2.6-beta.3",
|
||||
|
||||
@@ -3593,176 +3593,367 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "theme-daylight",
|
||||
"name": "theme-stone",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "36 39% 88%",
|
||||
"foreground": "36 45% 15%",
|
||||
"primary": "36 45% 70%",
|
||||
"primary-foreground": "36 45% 11%",
|
||||
"secondary": "40 35% 77%",
|
||||
"secondary-foreground": "36 45% 25%",
|
||||
"accent": "36 64% 57%",
|
||||
"accent-foreground": "36 72% 17%",
|
||||
"destructive": "0 84% 37%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"muted": "36 33% 75%",
|
||||
"muted-foreground": "36 45% 25%",
|
||||
"card": "36 46% 82%",
|
||||
"card-foreground": "36 45% 20%",
|
||||
"popover": "0 0% 100%",
|
||||
"popover-foreground": "240 10% 3.9%",
|
||||
"border": "36 45% 60%",
|
||||
"input": "36 45% 60%",
|
||||
"ring": "36 45% 30%",
|
||||
"chart-1": "25 34% 28%",
|
||||
"chart-2": "26 36% 34%",
|
||||
"chart-3": "28 40% 40%",
|
||||
"chart-4": "31 41% 48%",
|
||||
"chart-5": "35 43% 53%"
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.147 0.004 49.25)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"primary": "oklch(0.216 0.006 56.043)",
|
||||
"primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"secondary": "oklch(0.97 0.001 106.424)",
|
||||
"secondary-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"muted": "oklch(0.97 0.001 106.424)",
|
||||
"muted-foreground": "oklch(0.553 0.013 58.071)",
|
||||
"accent": "oklch(0.97 0.001 106.424)",
|
||||
"accent-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.923 0.003 48.717)",
|
||||
"input": "oklch(0.923 0.003 48.717)",
|
||||
"ring": "oklch(0.709 0.01 56.259)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"sidebar-primary": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-accent": "oklch(0.97 0.001 106.424)",
|
||||
"sidebar-accent-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-border": "oklch(0.923 0.003 48.717)",
|
||||
"sidebar-ring": "oklch(0.709 0.01 56.259)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "36 39% 88%",
|
||||
"foreground": "36 45% 15%",
|
||||
"primary": "36 45% 70%",
|
||||
"primary-foreground": "36 45% 11%",
|
||||
"secondary": "40 35% 77%",
|
||||
"secondary-foreground": "36 45% 25%",
|
||||
"accent": "36 64% 57%",
|
||||
"accent-foreground": "36 72% 17%",
|
||||
"destructive": "0 84% 37%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"muted": "36 33% 75%",
|
||||
"muted-foreground": "36 45% 25%",
|
||||
"card": "36 46% 82%",
|
||||
"card-foreground": "36 45% 20%",
|
||||
"popover": "0 0% 100%",
|
||||
"popover-foreground": "240 10% 3.9%",
|
||||
"border": "36 45% 60%",
|
||||
"input": "36 45% 60%",
|
||||
"ring": "36 45% 30%",
|
||||
"chart-1": "25 34% 28%",
|
||||
"chart-2": "26 36% 34%",
|
||||
"chart-3": "28 40% 40%",
|
||||
"chart-4": "31 41% 48%",
|
||||
"chart-5": "35 43% 53%"
|
||||
"background": "oklch(0.147 0.004 49.25)",
|
||||
"foreground": "oklch(0.985 0.001 106.423)",
|
||||
"card": "oklch(0.216 0.006 56.043)",
|
||||
"card-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"popover": "oklch(0.216 0.006 56.043)",
|
||||
"popover-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"primary": "oklch(0.923 0.003 48.717)",
|
||||
"primary-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"secondary": "oklch(0.268 0.007 34.298)",
|
||||
"secondary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"muted": "oklch(0.268 0.007 34.298)",
|
||||
"muted-foreground": "oklch(0.709 0.01 56.259)",
|
||||
"accent": "oklch(0.268 0.007 34.298)",
|
||||
"accent-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.553 0.013 58.071)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-accent": "oklch(0.268 0.007 34.298)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.553 0.013 58.071)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "theme-midnight",
|
||||
"name": "theme-zinc",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "240 5% 6%",
|
||||
"foreground": "60 5% 90%",
|
||||
"primary": "240 0% 90%",
|
||||
"primary-foreground": "60 0% 0%",
|
||||
"secondary": "240 4% 15%",
|
||||
"secondary-foreground": "60 5% 85%",
|
||||
"accent": "240 0% 13%",
|
||||
"accent-foreground": "60 0% 100%",
|
||||
"destructive": "0 60% 50%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"muted": "240 5% 25%",
|
||||
"muted-foreground": "60 5% 85%",
|
||||
"card": "240 4% 10%",
|
||||
"card-foreground": "60 5% 90%",
|
||||
"popover": "240 5% 15%",
|
||||
"popover-foreground": "60 5% 85%",
|
||||
"border": "240 6% 20%",
|
||||
"input": "240 6% 20%",
|
||||
"ring": "240 5% 90%",
|
||||
"chart-1": "359 2% 90%",
|
||||
"chart-2": "240 1% 74%",
|
||||
"chart-3": "240 1% 58%",
|
||||
"chart-4": "240 1% 42%",
|
||||
"chart-5": "240 2% 26%"
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.141 0.005 285.823)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"primary": "oklch(0.21 0.006 285.885)",
|
||||
"primary-foreground": "oklch(0.985 0 0)",
|
||||
"secondary": "oklch(0.967 0.001 286.375)",
|
||||
"secondary-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"muted": "oklch(0.967 0.001 286.375)",
|
||||
"muted-foreground": "oklch(0.552 0.016 285.938)",
|
||||
"accent": "oklch(0.967 0.001 286.375)",
|
||||
"accent-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.92 0.004 286.32)",
|
||||
"input": "oklch(0.92 0.004 286.32)",
|
||||
"ring": "oklch(0.705 0.015 286.067)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0 0)",
|
||||
"sidebar-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"sidebar-primary": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.967 0.001 286.375)",
|
||||
"sidebar-accent-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-border": "oklch(0.92 0.004 286.32)",
|
||||
"sidebar-ring": "oklch(0.705 0.015 286.067)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "240 5% 6%",
|
||||
"foreground": "60 5% 90%",
|
||||
"primary": "240 0% 90%",
|
||||
"primary-foreground": "60 0% 0%",
|
||||
"secondary": "240 4% 15%",
|
||||
"secondary-foreground": "60 5% 85%",
|
||||
"accent": "240 0% 13%",
|
||||
"accent-foreground": "60 0% 100%",
|
||||
"destructive": "0 60% 50%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"muted": "240 5% 25%",
|
||||
"muted-foreground": "60 5% 85%",
|
||||
"card": "240 4% 10%",
|
||||
"card-foreground": "60 5% 90%",
|
||||
"popover": "240 5% 15%",
|
||||
"popover-foreground": "60 5% 85%",
|
||||
"border": "240 6% 20%",
|
||||
"input": "240 6% 20%",
|
||||
"ring": "240 5% 90%",
|
||||
"chart-1": "359 2% 90%",
|
||||
"chart-2": "240 1% 74%",
|
||||
"chart-3": "240 1% 58%",
|
||||
"chart-4": "240 1% 42%",
|
||||
"chart-5": "240 2% 26%"
|
||||
"background": "oklch(0.141 0.005 285.823)",
|
||||
"foreground": "oklch(0.985 0 0)",
|
||||
"card": "oklch(0.21 0.006 285.885)",
|
||||
"card-foreground": "oklch(0.985 0 0)",
|
||||
"popover": "oklch(0.21 0.006 285.885)",
|
||||
"popover-foreground": "oklch(0.985 0 0)",
|
||||
"primary": "oklch(0.92 0.004 286.32)",
|
||||
"primary-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"secondary": "oklch(0.274 0.006 286.033)",
|
||||
"secondary-foreground": "oklch(0.985 0 0)",
|
||||
"muted": "oklch(0.274 0.006 286.033)",
|
||||
"muted-foreground": "oklch(0.705 0.015 286.067)",
|
||||
"accent": "oklch(0.274 0.006 286.033)",
|
||||
"accent-foreground": "oklch(0.985 0 0)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.552 0.016 285.938)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.274 0.006 286.033)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.552 0.016 285.938)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "theme-emerald",
|
||||
"name": "theme-neutral",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "240 10% 3.9%",
|
||||
"card": "0 0% 100%",
|
||||
"card-foreground": "240 10% 3.9%",
|
||||
"popover": "0 0% 100%",
|
||||
"popover-foreground": "240 10% 3.9%",
|
||||
"primary": "142 86% 28%",
|
||||
"primary-foreground": "356 29% 98%",
|
||||
"secondary": "240 4.8% 95.9%",
|
||||
"secondary-foreground": "240 5.9% 10%",
|
||||
"muted": "240 4.8% 95.9%",
|
||||
"muted-foreground": "240 3.8% 45%",
|
||||
"accent": "240 4.8% 95.9%",
|
||||
"accent-foreground": "240 5.9% 10%",
|
||||
"destructive": "0 72% 51%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"border": "240 5.9% 90%",
|
||||
"input": "240 5.9% 90%",
|
||||
"ring": "142 86% 28%",
|
||||
"chart-1": "139 65% 20%",
|
||||
"chart-2": "140 74% 44%",
|
||||
"chart-3": "142 88% 28%",
|
||||
"chart-4": "137 55% 15%",
|
||||
"chart-5": "141 40% 9%"
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.145 0 0)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.145 0 0)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.145 0 0)",
|
||||
"primary": "oklch(0.205 0 0)",
|
||||
"primary-foreground": "oklch(0.985 0 0)",
|
||||
"secondary": "oklch(0.97 0 0)",
|
||||
"secondary-foreground": "oklch(0.205 0 0)",
|
||||
"muted": "oklch(0.97 0 0)",
|
||||
"muted-foreground": "oklch(0.556 0 0)",
|
||||
"accent": "oklch(0.97 0 0)",
|
||||
"accent-foreground": "oklch(0.205 0 0)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.922 0 0)",
|
||||
"input": "oklch(0.922 0 0)",
|
||||
"ring": "oklch(0.708 0 0)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0 0)",
|
||||
"sidebar-foreground": "oklch(0.145 0 0)",
|
||||
"sidebar-primary": "oklch(0.205 0 0)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.97 0 0)",
|
||||
"sidebar-accent-foreground": "oklch(0.205 0 0)",
|
||||
"sidebar-border": "oklch(0.922 0 0)",
|
||||
"sidebar-ring": "oklch(0.708 0 0)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "240 10% 3.9%",
|
||||
"foreground": "0 0% 98%",
|
||||
"card": "240 10% 3.9%",
|
||||
"card-foreground": "0 0% 98%",
|
||||
"popover": "240 10% 3.9%",
|
||||
"popover-foreground": "0 0% 98%",
|
||||
"primary": "142 86% 28%",
|
||||
"primary-foreground": "356 29% 98%",
|
||||
"secondary": "240 4.8% 95.9%",
|
||||
"secondary-foreground": "240 5.9% 10%",
|
||||
"muted": "240 3.7% 15.9%",
|
||||
"muted-foreground": "240 5% 64.9%",
|
||||
"accent": "240 3.7% 15.9%",
|
||||
"accent-foreground": "0 0% 98%",
|
||||
"destructive": "0 72% 51%",
|
||||
"destructive-foreground": "0 0% 98%",
|
||||
"border": "240 3.7% 15.9%",
|
||||
"input": "240 3.7% 15.9%",
|
||||
"ring": "142 86% 28%",
|
||||
"chart-1": "142 88% 28%",
|
||||
"chart-2": "139 65% 20%",
|
||||
"chart-3": "140 74% 24%",
|
||||
"chart-4": "137 55% 15%",
|
||||
"chart-5": "141 40% 9%"
|
||||
"background": "oklch(0.145 0 0)",
|
||||
"foreground": "oklch(0.985 0 0)",
|
||||
"card": "oklch(0.205 0 0)",
|
||||
"card-foreground": "oklch(0.985 0 0)",
|
||||
"popover": "oklch(0.205 0 0)",
|
||||
"popover-foreground": "oklch(0.985 0 0)",
|
||||
"primary": "oklch(0.922 0 0)",
|
||||
"primary-foreground": "oklch(0.205 0 0)",
|
||||
"secondary": "oklch(0.269 0 0)",
|
||||
"secondary-foreground": "oklch(0.985 0 0)",
|
||||
"muted": "oklch(0.269 0 0)",
|
||||
"muted-foreground": "oklch(0.708 0 0)",
|
||||
"accent": "oklch(0.269 0 0)",
|
||||
"accent-foreground": "oklch(0.985 0 0)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.556 0 0)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.205 0 0)",
|
||||
"sidebar-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.269 0 0)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.556 0 0)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "theme-gray",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.13 0.028 261.692)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"primary": "oklch(0.21 0.034 264.665)",
|
||||
"primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"secondary": "oklch(0.967 0.003 264.542)",
|
||||
"secondary-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"muted": "oklch(0.967 0.003 264.542)",
|
||||
"muted-foreground": "oklch(0.551 0.027 264.364)",
|
||||
"accent": "oklch(0.967 0.003 264.542)",
|
||||
"accent-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.928 0.006 264.531)",
|
||||
"input": "oklch(0.928 0.006 264.531)",
|
||||
"ring": "oklch(0.707 0.022 261.325)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"sidebar-primary": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-accent": "oklch(0.967 0.003 264.542)",
|
||||
"sidebar-accent-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-border": "oklch(0.928 0.006 264.531)",
|
||||
"sidebar-ring": "oklch(0.707 0.022 261.325)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.13 0.028 261.692)",
|
||||
"foreground": "oklch(0.985 0.002 247.839)",
|
||||
"card": "oklch(0.21 0.034 264.665)",
|
||||
"card-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"popover": "oklch(0.21 0.034 264.665)",
|
||||
"popover-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"primary": "oklch(0.928 0.006 264.531)",
|
||||
"primary-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"secondary": "oklch(0.278 0.033 256.848)",
|
||||
"secondary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"muted": "oklch(0.278 0.033 256.848)",
|
||||
"muted-foreground": "oklch(0.707 0.022 261.325)",
|
||||
"accent": "oklch(0.278 0.033 256.848)",
|
||||
"accent-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.551 0.027 264.364)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-accent": "oklch(0.278 0.033 256.848)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.551 0.027 264.364)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "theme-slate",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.129 0.042 264.695)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"primary": "oklch(0.208 0.042 265.755)",
|
||||
"primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"secondary": "oklch(0.968 0.007 247.896)",
|
||||
"secondary-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"muted": "oklch(0.968 0.007 247.896)",
|
||||
"muted-foreground": "oklch(0.554 0.046 257.417)",
|
||||
"accent": "oklch(0.968 0.007 247.896)",
|
||||
"accent-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.929 0.013 255.508)",
|
||||
"input": "oklch(0.929 0.013 255.508)",
|
||||
"ring": "oklch(0.704 0.04 256.788)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"sidebar-primary": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-accent": "oklch(0.968 0.007 247.896)",
|
||||
"sidebar-accent-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-border": "oklch(0.929 0.013 255.508)",
|
||||
"sidebar-ring": "oklch(0.704 0.04 256.788)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.129 0.042 264.695)",
|
||||
"foreground": "oklch(0.984 0.003 247.858)",
|
||||
"card": "oklch(0.208 0.042 265.755)",
|
||||
"card-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"popover": "oklch(0.208 0.042 265.755)",
|
||||
"popover-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"primary": "oklch(0.929 0.013 255.508)",
|
||||
"primary-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"secondary": "oklch(0.279 0.041 260.031)",
|
||||
"secondary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"muted": "oklch(0.279 0.041 260.031)",
|
||||
"muted-foreground": "oklch(0.704 0.04 256.788)",
|
||||
"accent": "oklch(0.279 0.041 260.031)",
|
||||
"accent-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.551 0.027 264.364)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-accent": "oklch(0.279 0.041 260.031)",
|
||||
"sidebar-accent-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.551 0.027 264.364)"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
74
apps/www/public/r/styles/new-york-v4/theme-gray.json
Normal file
74
apps/www/public/r/styles/new-york-v4/theme-gray.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "theme-gray",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.13 0.028 261.692)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"primary": "oklch(0.21 0.034 264.665)",
|
||||
"primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"secondary": "oklch(0.967 0.003 264.542)",
|
||||
"secondary-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"muted": "oklch(0.967 0.003 264.542)",
|
||||
"muted-foreground": "oklch(0.551 0.027 264.364)",
|
||||
"accent": "oklch(0.967 0.003 264.542)",
|
||||
"accent-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.928 0.006 264.531)",
|
||||
"input": "oklch(0.928 0.006 264.531)",
|
||||
"ring": "oklch(0.707 0.022 261.325)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-foreground": "oklch(0.13 0.028 261.692)",
|
||||
"sidebar-primary": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-accent": "oklch(0.967 0.003 264.542)",
|
||||
"sidebar-accent-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-border": "oklch(0.928 0.006 264.531)",
|
||||
"sidebar-ring": "oklch(0.707 0.022 261.325)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.13 0.028 261.692)",
|
||||
"foreground": "oklch(0.985 0.002 247.839)",
|
||||
"card": "oklch(0.21 0.034 264.665)",
|
||||
"card-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"popover": "oklch(0.21 0.034 264.665)",
|
||||
"popover-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"primary": "oklch(0.928 0.006 264.531)",
|
||||
"primary-foreground": "oklch(0.21 0.034 264.665)",
|
||||
"secondary": "oklch(0.278 0.033 256.848)",
|
||||
"secondary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"muted": "oklch(0.278 0.033 256.848)",
|
||||
"muted-foreground": "oklch(0.707 0.022 261.325)",
|
||||
"accent": "oklch(0.278 0.033 256.848)",
|
||||
"accent-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.551 0.027 264.364)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.21 0.034 264.665)",
|
||||
"sidebar-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-accent": "oklch(0.278 0.033 256.848)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0.002 247.839)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.551 0.027 264.364)"
|
||||
}
|
||||
}
|
||||
}
|
||||
74
apps/www/public/r/styles/new-york-v4/theme-neutral.json
Normal file
74
apps/www/public/r/styles/new-york-v4/theme-neutral.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "theme-neutral",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.145 0 0)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.145 0 0)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.145 0 0)",
|
||||
"primary": "oklch(0.205 0 0)",
|
||||
"primary-foreground": "oklch(0.985 0 0)",
|
||||
"secondary": "oklch(0.97 0 0)",
|
||||
"secondary-foreground": "oklch(0.205 0 0)",
|
||||
"muted": "oklch(0.97 0 0)",
|
||||
"muted-foreground": "oklch(0.556 0 0)",
|
||||
"accent": "oklch(0.97 0 0)",
|
||||
"accent-foreground": "oklch(0.205 0 0)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.922 0 0)",
|
||||
"input": "oklch(0.922 0 0)",
|
||||
"ring": "oklch(0.708 0 0)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0 0)",
|
||||
"sidebar-foreground": "oklch(0.145 0 0)",
|
||||
"sidebar-primary": "oklch(0.205 0 0)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.97 0 0)",
|
||||
"sidebar-accent-foreground": "oklch(0.205 0 0)",
|
||||
"sidebar-border": "oklch(0.922 0 0)",
|
||||
"sidebar-ring": "oklch(0.708 0 0)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.145 0 0)",
|
||||
"foreground": "oklch(0.985 0 0)",
|
||||
"card": "oklch(0.205 0 0)",
|
||||
"card-foreground": "oklch(0.985 0 0)",
|
||||
"popover": "oklch(0.205 0 0)",
|
||||
"popover-foreground": "oklch(0.985 0 0)",
|
||||
"primary": "oklch(0.922 0 0)",
|
||||
"primary-foreground": "oklch(0.205 0 0)",
|
||||
"secondary": "oklch(0.269 0 0)",
|
||||
"secondary-foreground": "oklch(0.985 0 0)",
|
||||
"muted": "oklch(0.269 0 0)",
|
||||
"muted-foreground": "oklch(0.708 0 0)",
|
||||
"accent": "oklch(0.269 0 0)",
|
||||
"accent-foreground": "oklch(0.985 0 0)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.556 0 0)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.205 0 0)",
|
||||
"sidebar-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.269 0 0)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.556 0 0)"
|
||||
}
|
||||
}
|
||||
}
|
||||
74
apps/www/public/r/styles/new-york-v4/theme-slate.json
Normal file
74
apps/www/public/r/styles/new-york-v4/theme-slate.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "theme-slate",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.129 0.042 264.695)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"primary": "oklch(0.208 0.042 265.755)",
|
||||
"primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"secondary": "oklch(0.968 0.007 247.896)",
|
||||
"secondary-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"muted": "oklch(0.968 0.007 247.896)",
|
||||
"muted-foreground": "oklch(0.554 0.046 257.417)",
|
||||
"accent": "oklch(0.968 0.007 247.896)",
|
||||
"accent-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.929 0.013 255.508)",
|
||||
"input": "oklch(0.929 0.013 255.508)",
|
||||
"ring": "oklch(0.704 0.04 256.788)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-foreground": "oklch(0.129 0.042 264.695)",
|
||||
"sidebar-primary": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-accent": "oklch(0.968 0.007 247.896)",
|
||||
"sidebar-accent-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-border": "oklch(0.929 0.013 255.508)",
|
||||
"sidebar-ring": "oklch(0.704 0.04 256.788)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.129 0.042 264.695)",
|
||||
"foreground": "oklch(0.984 0.003 247.858)",
|
||||
"card": "oklch(0.208 0.042 265.755)",
|
||||
"card-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"popover": "oklch(0.208 0.042 265.755)",
|
||||
"popover-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"primary": "oklch(0.929 0.013 255.508)",
|
||||
"primary-foreground": "oklch(0.208 0.042 265.755)",
|
||||
"secondary": "oklch(0.279 0.041 260.031)",
|
||||
"secondary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"muted": "oklch(0.279 0.041 260.031)",
|
||||
"muted-foreground": "oklch(0.704 0.04 256.788)",
|
||||
"accent": "oklch(0.279 0.041 260.031)",
|
||||
"accent-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.551 0.027 264.364)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.208 0.042 265.755)",
|
||||
"sidebar-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-accent": "oklch(0.279 0.041 260.031)",
|
||||
"sidebar-accent-foreground": "oklch(0.984 0.003 247.858)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.551 0.027 264.364)"
|
||||
}
|
||||
}
|
||||
}
|
||||
74
apps/www/public/r/styles/new-york-v4/theme-stone.json
Normal file
74
apps/www/public/r/styles/new-york-v4/theme-stone.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "theme-stone",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.147 0.004 49.25)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"primary": "oklch(0.216 0.006 56.043)",
|
||||
"primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"secondary": "oklch(0.97 0.001 106.424)",
|
||||
"secondary-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"muted": "oklch(0.97 0.001 106.424)",
|
||||
"muted-foreground": "oklch(0.553 0.013 58.071)",
|
||||
"accent": "oklch(0.97 0.001 106.424)",
|
||||
"accent-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.923 0.003 48.717)",
|
||||
"input": "oklch(0.923 0.003 48.717)",
|
||||
"ring": "oklch(0.709 0.01 56.259)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-foreground": "oklch(0.147 0.004 49.25)",
|
||||
"sidebar-primary": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-accent": "oklch(0.97 0.001 106.424)",
|
||||
"sidebar-accent-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-border": "oklch(0.923 0.003 48.717)",
|
||||
"sidebar-ring": "oklch(0.709 0.01 56.259)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.147 0.004 49.25)",
|
||||
"foreground": "oklch(0.985 0.001 106.423)",
|
||||
"card": "oklch(0.216 0.006 56.043)",
|
||||
"card-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"popover": "oklch(0.216 0.006 56.043)",
|
||||
"popover-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"primary": "oklch(0.923 0.003 48.717)",
|
||||
"primary-foreground": "oklch(0.216 0.006 56.043)",
|
||||
"secondary": "oklch(0.268 0.007 34.298)",
|
||||
"secondary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"muted": "oklch(0.268 0.007 34.298)",
|
||||
"muted-foreground": "oklch(0.709 0.01 56.259)",
|
||||
"accent": "oklch(0.268 0.007 34.298)",
|
||||
"accent-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.553 0.013 58.071)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.216 0.006 56.043)",
|
||||
"sidebar-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-accent": "oklch(0.268 0.007 34.298)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0.001 106.423)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.553 0.013 58.071)"
|
||||
}
|
||||
}
|
||||
}
|
||||
74
apps/www/public/r/styles/new-york-v4/theme-zinc.json
Normal file
74
apps/www/public/r/styles/new-york-v4/theme-zinc.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "theme-zinc",
|
||||
"type": "registry:theme",
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"background": "oklch(1 0 0)",
|
||||
"foreground": "oklch(0.141 0.005 285.823)",
|
||||
"card": "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"popover": "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"primary": "oklch(0.21 0.006 285.885)",
|
||||
"primary-foreground": "oklch(0.985 0 0)",
|
||||
"secondary": "oklch(0.967 0.001 286.375)",
|
||||
"secondary-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"muted": "oklch(0.967 0.001 286.375)",
|
||||
"muted-foreground": "oklch(0.552 0.016 285.938)",
|
||||
"accent": "oklch(0.967 0.001 286.375)",
|
||||
"accent-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"destructive": "oklch(0.577 0.245 27.325)",
|
||||
"border": "oklch(0.92 0.004 286.32)",
|
||||
"input": "oklch(0.92 0.004 286.32)",
|
||||
"ring": "oklch(0.705 0.015 286.067)",
|
||||
"chart-1": "oklch(0.646 0.222 41.116)",
|
||||
"chart-2": "oklch(0.6 0.118 184.704)",
|
||||
"chart-3": "oklch(0.398 0.07 227.392)",
|
||||
"chart-4": "oklch(0.828 0.189 84.429)",
|
||||
"chart-5": "oklch(0.769 0.188 70.08)",
|
||||
"radius": "0.625rem",
|
||||
"sidebar": "oklch(0.985 0 0)",
|
||||
"sidebar-foreground": "oklch(0.141 0.005 285.823)",
|
||||
"sidebar-primary": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.967 0.001 286.375)",
|
||||
"sidebar-accent-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-border": "oklch(0.92 0.004 286.32)",
|
||||
"sidebar-ring": "oklch(0.705 0.015 286.067)"
|
||||
},
|
||||
"dark": {
|
||||
"background": "oklch(0.141 0.005 285.823)",
|
||||
"foreground": "oklch(0.985 0 0)",
|
||||
"card": "oklch(0.21 0.006 285.885)",
|
||||
"card-foreground": "oklch(0.985 0 0)",
|
||||
"popover": "oklch(0.21 0.006 285.885)",
|
||||
"popover-foreground": "oklch(0.985 0 0)",
|
||||
"primary": "oklch(0.92 0.004 286.32)",
|
||||
"primary-foreground": "oklch(0.21 0.006 285.885)",
|
||||
"secondary": "oklch(0.274 0.006 286.033)",
|
||||
"secondary-foreground": "oklch(0.985 0 0)",
|
||||
"muted": "oklch(0.274 0.006 286.033)",
|
||||
"muted-foreground": "oklch(0.705 0.015 286.067)",
|
||||
"accent": "oklch(0.274 0.006 286.033)",
|
||||
"accent-foreground": "oklch(0.985 0 0)",
|
||||
"destructive": "oklch(0.704 0.191 22.216)",
|
||||
"border": "oklch(1 0 0 / 10%)",
|
||||
"input": "oklch(1 0 0 / 15%)",
|
||||
"ring": "oklch(0.552 0.016 285.938)",
|
||||
"chart-1": "oklch(0.488 0.243 264.376)",
|
||||
"chart-2": "oklch(0.696 0.17 162.48)",
|
||||
"chart-3": "oklch(0.769 0.188 70.08)",
|
||||
"chart-4": "oklch(0.627 0.265 303.9)",
|
||||
"chart-5": "oklch(0.645 0.246 16.439)",
|
||||
"sidebar": "oklch(0.21 0.006 285.885)",
|
||||
"sidebar-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.274 0.006 286.033)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.552 0.016 285.938)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,70 +185,72 @@ const ChartTooltipContent = React.forwardRef<
|
||||
>
|
||||
{!nestLabel ? tooltipLabel : null}
|
||||
<div className="grid gap-1.5">
|
||||
{payload.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const indicatorColor = color || item.payload.fill || item.color
|
||||
{payload
|
||||
.filter((item) => item.type !== "none")
|
||||
.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const indicatorColor = color || item.payload.fill || item.color
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.dataKey}
|
||||
className={cn(
|
||||
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
||||
indicator === "dot" && "items-center"
|
||||
)}
|
||||
>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
formatter(item.value, item.name, item, index, item.payload)
|
||||
) : (
|
||||
<>
|
||||
{itemConfig?.icon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
return (
|
||||
<div
|
||||
key={item.dataKey}
|
||||
className={cn(
|
||||
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
||||
indicator === "dot" && "items-center"
|
||||
)}
|
||||
>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
formatter(item.value, item.name, item, index, item.payload)
|
||||
) : (
|
||||
<>
|
||||
{itemConfig?.icon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
}
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--color-bg": indicatorColor,
|
||||
"--color-border": indicatorColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--color-bg": indicatorColor,
|
||||
"--color-border": indicatorColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center"
|
||||
/>
|
||||
)
|
||||
)}
|
||||
>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-muted-foreground">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center"
|
||||
)}
|
||||
>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-muted-foreground">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="font-mono font-medium tabular-nums text-foreground">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="font-mono font-medium tabular-nums text-foreground">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -285,31 +287,33 @@ const ChartLegendContent = React.forwardRef<
|
||||
className
|
||||
)}
|
||||
>
|
||||
{payload.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
{payload
|
||||
.filter((item) => item.type !== "none")
|
||||
.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{itemConfig?.icon && !hideIcon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{itemConfig?.label}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{itemConfig?.icon && !hideIcon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{itemConfig?.label}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -185,70 +185,72 @@ const ChartTooltipContent = React.forwardRef<
|
||||
>
|
||||
{!nestLabel ? tooltipLabel : null}
|
||||
<div className="grid gap-1.5">
|
||||
{payload.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const indicatorColor = color || item.payload.fill || item.color
|
||||
{payload
|
||||
.filter((item) => item.type !== "none")
|
||||
.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const indicatorColor = color || item.payload.fill || item.color
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.dataKey}
|
||||
className={cn(
|
||||
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
||||
indicator === "dot" && "items-center"
|
||||
)}
|
||||
>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
formatter(item.value, item.name, item, index, item.payload)
|
||||
) : (
|
||||
<>
|
||||
{itemConfig?.icon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
return (
|
||||
<div
|
||||
key={item.dataKey}
|
||||
className={cn(
|
||||
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
||||
indicator === "dot" && "items-center"
|
||||
)}
|
||||
>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
formatter(item.value, item.name, item, index, item.payload)
|
||||
) : (
|
||||
<>
|
||||
{itemConfig?.icon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
}
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--color-bg": indicatorColor,
|
||||
"--color-border": indicatorColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--color-bg": indicatorColor,
|
||||
"--color-border": indicatorColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center"
|
||||
/>
|
||||
)
|
||||
)}
|
||||
>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-muted-foreground">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center"
|
||||
)}
|
||||
>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-muted-foreground">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="font-mono font-medium tabular-nums text-foreground">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="font-mono font-medium tabular-nums text-foreground">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -285,31 +287,33 @@ const ChartLegendContent = React.forwardRef<
|
||||
className
|
||||
)}
|
||||
>
|
||||
{payload.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
{payload
|
||||
.filter((item) => item.type !== "none")
|
||||
.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{itemConfig?.icon && !hideIcon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{itemConfig?.label}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{itemConfig?.icon && !hideIcon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{itemConfig?.label}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
"pub:beta": "cd packages/shadcn && pnpm pub:beta",
|
||||
"pub:release": "cd packages/shadcn && pnpm pub:release",
|
||||
"test:dev": "turbo run test --filter=!shadcn-ui --force",
|
||||
"test": "start-server-and-test v4:dev http://localhost:4000 test:dev"
|
||||
"test": "start-server-and-test v4:dev http://localhost:4000 test:dev",
|
||||
"validate:registries": "pnpm --filter=v4 validate:registries"
|
||||
},
|
||||
"packageManager": "pnpm@9.0.6",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
# @shadcn/ui
|
||||
|
||||
## 3.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#8216](https://github.com/shadcn-ui/ui/pull/8216) [`fc6d909ba23ac1ba09cf32087f0524aca398b5aa`](https://github.com/shadcn-ui/ui/commit/fc6d909ba23ac1ba09cf32087f0524aca398b5aa) Thanks [@shadcn](https://github.com/shadcn)! - add getRegistriesIndex
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8186](https://github.com/shadcn-ui/ui/pull/8186) [`cdf58be7e1ed25bf1dd19a1c60612c5e89b82a60`](https://github.com/shadcn-ui/ui/commit/cdf58be7e1ed25bf1dd19a1c60612c5e89b82a60) Thanks [@imskyleen](https://github.com/imskyleen)! - fix transformCssVars function with prefix
|
||||
|
||||
- [#8036](https://github.com/shadcn-ui/ui/pull/8036) [`fae1a81addb22429c103d5d08813e1c80779d5fb`](https://github.com/shadcn-ui/ui/commit/fae1a81addb22429c103d5d08813e1c80779d5fb) Thanks [@fuma-nama](https://github.com/fuma-nama)! - fix async imports not being transformed when installing components
|
||||
|
||||
## 3.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8147](https://github.com/shadcn-ui/ui/pull/8147) [`e5402f9a20f070e92e7384c1ae08e6bfb79cd7a9`](https://github.com/shadcn-ui/ui/commit/e5402f9a20f070e92e7384c1ae08e6bfb79cd7a9) Thanks [@shadcn](https://github.com/shadcn)! - fix recursive namespacing
|
||||
|
||||
## 3.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#8128](https://github.com/shadcn-ui/ui/pull/8128) [`9c5eb0d20f0b75b28dccee219bf74fc9cd2019c6`](https://github.com/shadcn-ui/ui/commit/9c5eb0d20f0b75b28dccee219bf74fc9cd2019c6) Thanks [@shadcn](https://github.com/shadcn)! - add support for registries index
|
||||
|
||||
## 3.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#8110](https://github.com/shadcn-ui/ui/pull/8110) [`64f8baf9aa7562d4d2170863c29c6ae723f31df3`](https://github.com/shadcn-ui/ui/commit/64f8baf9aa7562d4d2170863c29c6ae723f31df3) Thanks [@shadcn](https://github.com/shadcn)! - allow no files items
|
||||
|
||||
- [#8109](https://github.com/shadcn-ui/ui/pull/8109) [`e84c819977046f555daa9a8af5736ad7725fb4da`](https://github.com/shadcn-ui/ui/commit/e84c819977046f555daa9a8af5736ad7725fb4da) Thanks [@shadcn](https://github.com/shadcn)! - update handling of import and apply at rules
|
||||
|
||||
## 3.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "shadcn",
|
||||
"version": "3.0.0",
|
||||
"version": "3.3.0",
|
||||
"description": "Add components to your apps.",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -14,6 +14,7 @@ import { getProjectInfo } from "@/src/utils/get-project-info"
|
||||
import { handleError } from "@/src/utils/handle-error"
|
||||
import { highlighter } from "@/src/utils/highlighter"
|
||||
import { logger } from "@/src/utils/logger"
|
||||
import { ensureRegistriesInConfig } from "@/src/utils/registries"
|
||||
import { updateAppIndex } from "@/src/utils/update-app-index"
|
||||
import { Command } from "commander"
|
||||
import prompts from "prompts"
|
||||
@@ -76,6 +77,17 @@ export const add = new Command()
|
||||
})
|
||||
}
|
||||
|
||||
let hasNewRegistries = false
|
||||
if (components.length > 0) {
|
||||
const { config: updatedConfig, newRegistries } =
|
||||
await ensureRegistriesInConfig(components, initialConfig, {
|
||||
silent: options.silent,
|
||||
writeFile: false,
|
||||
})
|
||||
initialConfig = updatedConfig
|
||||
hasNewRegistries = newRegistries.length > 0
|
||||
}
|
||||
|
||||
if (components.length > 0) {
|
||||
const [registryItem] = await getRegistryItems([components[0]], {
|
||||
config: initialConfig,
|
||||
@@ -134,6 +146,7 @@ export const add = new Command()
|
||||
let { errors, config } = await preFlightAdd(options)
|
||||
|
||||
// No components.json file. Prompt the user to run init.
|
||||
let initHasRun = false
|
||||
if (errors[ERRORS.MISSING_CONFIG]) {
|
||||
const { proceed } = await prompts({
|
||||
type: "confirm",
|
||||
@@ -155,15 +168,18 @@ export const add = new Command()
|
||||
force: true,
|
||||
defaults: false,
|
||||
skipPreflight: false,
|
||||
silent: true,
|
||||
silent: options.silent || !hasNewRegistries,
|
||||
isNewProject: false,
|
||||
srcDir: options.srcDir,
|
||||
cssVariables: options.cssVariables,
|
||||
baseStyle: true,
|
||||
components: options.components,
|
||||
})
|
||||
initHasRun = true
|
||||
}
|
||||
|
||||
let shouldUpdateAppIndex = false
|
||||
|
||||
if (errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT]) {
|
||||
const { projectPath, template } = await createProject({
|
||||
cwd: options.cwd,
|
||||
@@ -187,12 +203,14 @@ export const add = new Command()
|
||||
force: true,
|
||||
defaults: false,
|
||||
skipPreflight: true,
|
||||
silent: true,
|
||||
silent: !hasNewRegistries && options.silent,
|
||||
isNewProject: true,
|
||||
srcDir: options.srcDir,
|
||||
cssVariables: options.cssVariables,
|
||||
baseStyle: true,
|
||||
components: options.components,
|
||||
})
|
||||
initHasRun = true
|
||||
|
||||
shouldUpdateAppIndex =
|
||||
options.components?.length === 1 &&
|
||||
@@ -206,7 +224,18 @@ export const add = new Command()
|
||||
)
|
||||
}
|
||||
|
||||
await addComponents(options.components, config, options)
|
||||
const { config: updatedConfig } = await ensureRegistriesInConfig(
|
||||
options.components,
|
||||
config,
|
||||
{
|
||||
silent: options.silent || hasNewRegistries,
|
||||
}
|
||||
)
|
||||
config = updatedConfig
|
||||
|
||||
if (!initHasRun) {
|
||||
await addComponents(options.components, config, options)
|
||||
}
|
||||
|
||||
// If we're adding a single component and it's from the v0 registry,
|
||||
// let's update the app/page.tsx file to import the component.
|
||||
|
||||
@@ -53,10 +53,6 @@ export const build = new Command()
|
||||
|
||||
const buildSpinner = spinner("Building registry...")
|
||||
for (const registryItem of result.data.items) {
|
||||
if (!registryItem.files) {
|
||||
continue
|
||||
}
|
||||
|
||||
buildSpinner.start(`Building ${registryItem.name}...`)
|
||||
|
||||
// Add the schema to the registry item.
|
||||
@@ -64,7 +60,7 @@ export const build = new Command()
|
||||
"https://ui.shadcn.com/schema/registry-item.json"
|
||||
|
||||
// Loop through each file in the files array.
|
||||
for (const file of registryItem.files) {
|
||||
for (const file of registryItem.files ?? []) {
|
||||
file["content"] = await fs.readFile(
|
||||
path.resolve(resolvePaths.cwd, file.path),
|
||||
"utf-8"
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "@/src/registry/api"
|
||||
import { buildUrlAndHeadersForRegistryItem } from "@/src/registry/builder"
|
||||
import { configWithDefaults } from "@/src/registry/config"
|
||||
import { BASE_COLORS } from "@/src/registry/constants"
|
||||
import { BASE_COLORS, BUILTIN_REGISTRIES } from "@/src/registry/constants"
|
||||
import { clearRegistryContext } from "@/src/registry/context"
|
||||
import { rawConfigSchema } from "@/src/schema"
|
||||
import { addComponents } from "@/src/utils/add-components"
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
import { handleError } from "@/src/utils/handle-error"
|
||||
import { highlighter } from "@/src/utils/highlighter"
|
||||
import { logger } from "@/src/utils/logger"
|
||||
import { ensureRegistriesInConfig } from "@/src/utils/registries"
|
||||
import { spinner } from "@/src/utils/spinner"
|
||||
import { updateTailwindContent } from "@/src/utils/updaters/update-tailwind-content"
|
||||
import { Command } from "commander"
|
||||
@@ -175,6 +176,16 @@ export const init = new Command()
|
||||
createFileBackup(componentsJsonPath)
|
||||
}
|
||||
|
||||
// Ensure all registries used in components are configured.
|
||||
const { config: updatedConfig } = await ensureRegistriesInConfig(
|
||||
components,
|
||||
shadowConfig,
|
||||
{
|
||||
silent: true,
|
||||
}
|
||||
)
|
||||
shadowConfig = updatedConfig
|
||||
|
||||
// This forces a shadowConfig validation early in the process.
|
||||
buildUrlAndHeadersForRegistryItem(components[0], shadowConfig)
|
||||
|
||||
@@ -266,6 +277,31 @@ export async function runInit(
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the list of components to be added.
|
||||
const components = [
|
||||
// "index" is the default shadcn style.
|
||||
// Why index? Because when style is true, we read style from components.json and fetch that.
|
||||
// i.e new-york from components.json then fetch /styles/new-york/index.
|
||||
// TODO: Fix this so that we can extend any style i.e --style=new-york.
|
||||
...(options.baseStyle ? ["index"] : []),
|
||||
...(options.components ?? []),
|
||||
]
|
||||
|
||||
// Ensure registries are configured for the components we're about to add.
|
||||
const fullConfigForRegistry = await resolveConfigPaths(options.cwd, config)
|
||||
const { config: configWithRegistries } = await ensureRegistriesInConfig(
|
||||
components,
|
||||
fullConfigForRegistry,
|
||||
{
|
||||
silent: true,
|
||||
}
|
||||
)
|
||||
|
||||
// Update config with any new registries found.
|
||||
if (configWithRegistries.registries) {
|
||||
config.registries = configWithRegistries.registries
|
||||
}
|
||||
|
||||
const componentSpinner = spinner(`Writing components.json.`).start()
|
||||
const targetPath = path.resolve(options.cwd, "components.json")
|
||||
const backupPath = `${targetPath}${FILE_BACKUP_SUFFIX}`
|
||||
@@ -279,20 +315,20 @@ export async function runInit(
|
||||
config = { ...merged, registries }
|
||||
}
|
||||
|
||||
// Make sure to filter out built-in registries.
|
||||
// TODO: fix this in ensureRegistriesInConfig.
|
||||
config.registries = Object.fromEntries(
|
||||
Object.entries(config.registries || {}).filter(
|
||||
([key]) => !Object.keys(BUILTIN_REGISTRIES).includes(key)
|
||||
)
|
||||
)
|
||||
|
||||
// Write components.json.
|
||||
await fs.writeFile(targetPath, `${JSON.stringify(config, null, 2)}\n`, "utf8")
|
||||
componentSpinner.succeed()
|
||||
|
||||
// Add components.
|
||||
const fullConfig = await resolveConfigPaths(options.cwd, config)
|
||||
const components = [
|
||||
// "index" is the default shadcn style.
|
||||
// Why index? Because when style is true, we read style from components.json and fetch that.
|
||||
// i.e new-york from components.json then fetch /styles/new-york/index.
|
||||
// TODO: Fix this so that we can extend any style i.e --style=new-york.
|
||||
...(options.baseStyle ? ["index"] : []),
|
||||
...(options.components ?? []),
|
||||
]
|
||||
await addComponents(components, fullConfig, {
|
||||
// Init will always overwrite files.
|
||||
overwrite: true,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { rawConfigSchema } from "@/src/schema"
|
||||
import { loadEnvFiles } from "@/src/utils/env-loader"
|
||||
import { createConfig, getConfig } from "@/src/utils/get-config"
|
||||
import { handleError } from "@/src/utils/handle-error"
|
||||
import { ensureRegistriesInConfig } from "@/src/utils/registries"
|
||||
import { Command } from "commander"
|
||||
import fsExtra from "fs-extra"
|
||||
import { z } from "zod"
|
||||
@@ -84,6 +85,19 @@ export const search = new Command()
|
||||
// Use shadow config if getConfig fails (partial components.json).
|
||||
}
|
||||
|
||||
const { config: updatedConfig, newRegistries } =
|
||||
await ensureRegistriesInConfig(
|
||||
registries.map((registry) => `${registry}/registry`),
|
||||
config,
|
||||
{
|
||||
silent: true,
|
||||
writeFile: false,
|
||||
}
|
||||
)
|
||||
if (newRegistries.length > 0) {
|
||||
config.registries = updatedConfig.registries
|
||||
}
|
||||
|
||||
// Validate registries early for better error messages.
|
||||
validateRegistryConfigForItems(registries, config)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { rawConfigSchema } from "@/src/schema"
|
||||
import { loadEnvFiles } from "@/src/utils/env-loader"
|
||||
import { getConfig } from "@/src/utils/get-config"
|
||||
import { handleError } from "@/src/utils/handle-error"
|
||||
import { ensureRegistriesInConfig } from "@/src/utils/registries"
|
||||
import { Command } from "commander"
|
||||
import fsExtra from "fs-extra"
|
||||
import { z } from "zod"
|
||||
@@ -54,6 +55,15 @@ export const view = new Command()
|
||||
// Use shadow config if getConfig fails (partial components.json).
|
||||
}
|
||||
|
||||
const { config: updatedConfig, newRegistries } =
|
||||
await ensureRegistriesInConfig(items, config, {
|
||||
silent: true,
|
||||
writeFile: false,
|
||||
})
|
||||
if (newRegistries.length > 0) {
|
||||
config.registries = updatedConfig.registries
|
||||
}
|
||||
|
||||
// Validate registries early for better error messages.
|
||||
validateRegistryConfigForItems(items, config)
|
||||
|
||||
|
||||
@@ -27,7 +27,13 @@ import {
|
||||
} from "vitest"
|
||||
import { z } from "zod"
|
||||
|
||||
import { getRegistriesConfig, getRegistry, getRegistryItems } from "./api"
|
||||
import {
|
||||
getRegistriesConfig,
|
||||
getRegistriesIndex,
|
||||
getRegistry,
|
||||
getRegistryItems,
|
||||
} from "./api"
|
||||
import { RegistriesIndexParseError } from "./errors"
|
||||
|
||||
vi.mock("@/src/utils/handle-error", () => ({
|
||||
handleError: vi.fn(),
|
||||
@@ -96,6 +102,13 @@ const server = setupServer(
|
||||
},
|
||||
],
|
||||
})
|
||||
}),
|
||||
http.get(`${REGISTRY_URL}/registries.json`, () => {
|
||||
return HttpResponse.json({
|
||||
"@shadcn": "https://ui.shadcn.com/r/styles/{style}/{name}.json",
|
||||
"@example": "https://example.com/registry/styles/{style}/{name}.json",
|
||||
"@test": "https://test.com/registry/{name}.json",
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
@@ -1650,4 +1663,75 @@ describe("getRegistriesConfig", () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("getRegistriesIndex", () => {
|
||||
it("should fetch and parse the registries index successfully", async () => {
|
||||
const result = await getRegistriesIndex()
|
||||
|
||||
expect(result).toEqual({
|
||||
"@shadcn": "https://ui.shadcn.com/r/styles/{style}/{name}.json",
|
||||
"@example": "https://example.com/registry/styles/{style}/{name}.json",
|
||||
"@test": "https://test.com/registry/{name}.json",
|
||||
})
|
||||
})
|
||||
|
||||
it("should respect cache options", async () => {
|
||||
// Test with cache disabled
|
||||
const result1 = await getRegistriesIndex({ useCache: false })
|
||||
expect(result1).toBeDefined()
|
||||
|
||||
// Test with cache enabled
|
||||
const result2 = await getRegistriesIndex({ useCache: true })
|
||||
expect(result2).toBeDefined()
|
||||
|
||||
// Results should be the same
|
||||
expect(result1).toEqual(result2)
|
||||
})
|
||||
|
||||
it("should use default cache behavior when no options provided", async () => {
|
||||
const result = await getRegistriesIndex()
|
||||
expect(result).toBeDefined()
|
||||
expect(typeof result).toBe("object")
|
||||
})
|
||||
|
||||
it("should handle network errors properly", async () => {
|
||||
server.use(
|
||||
http.get(`${REGISTRY_URL}/registries.json`, () => {
|
||||
return new HttpResponse(null, { status: 500 })
|
||||
})
|
||||
)
|
||||
|
||||
await expect(getRegistriesIndex({ useCache: false })).rejects.toThrow()
|
||||
|
||||
try {
|
||||
await getRegistriesIndex({ useCache: false })
|
||||
} catch (error) {
|
||||
expect(error).not.toBeInstanceOf(RegistriesIndexParseError)
|
||||
}
|
||||
})
|
||||
|
||||
it("should handle invalid JSON response", async () => {
|
||||
server.use(
|
||||
http.get(`${REGISTRY_URL}/registries.json`, () => {
|
||||
return HttpResponse.json({
|
||||
"invalid-namespace": "some-url",
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
await expect(getRegistriesIndex({ useCache: false })).rejects.toThrow(
|
||||
RegistriesIndexParseError
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle network timeout", async () => {
|
||||
server.use(
|
||||
http.get(`${REGISTRY_URL}/registries.json`, () => {
|
||||
return HttpResponse.error()
|
||||
})
|
||||
)
|
||||
|
||||
await expect(getRegistriesIndex({ useCache: false })).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import path from "path"
|
||||
import { buildUrlAndHeadersForRegistryItem } from "@/src/registry/builder"
|
||||
import { configWithDefaults } from "@/src/registry/config"
|
||||
import { BASE_COLORS, BUILTIN_REGISTRIES } from "@/src/registry/constants"
|
||||
import {
|
||||
BASE_COLORS,
|
||||
BUILTIN_REGISTRIES,
|
||||
REGISTRY_URL,
|
||||
} from "@/src/registry/constants"
|
||||
import {
|
||||
clearRegistryContext,
|
||||
setRegistryHeaders,
|
||||
} from "@/src/registry/context"
|
||||
import {
|
||||
ConfigParseError,
|
||||
RegistriesIndexParseError,
|
||||
RegistryInvalidNamespaceError,
|
||||
RegistryNotFoundError,
|
||||
RegistryParseError,
|
||||
@@ -20,7 +25,7 @@ import {
|
||||
import { isUrl } from "@/src/registry/utils"
|
||||
import {
|
||||
iconsSchema,
|
||||
rawConfigSchema,
|
||||
registriesIndexSchema,
|
||||
registryBaseColorSchema,
|
||||
registryConfigSchema,
|
||||
registryIndexSchema,
|
||||
@@ -272,3 +277,25 @@ export async function getItemTargetPath(
|
||||
type
|
||||
)
|
||||
}
|
||||
|
||||
export async function getRegistriesIndex(options?: { useCache?: boolean }) {
|
||||
options = {
|
||||
useCache: true,
|
||||
...options,
|
||||
}
|
||||
|
||||
const url = `${REGISTRY_URL}/registries.json`
|
||||
const [data] = await fetchRegistry([url], {
|
||||
useCache: options.useCache,
|
||||
})
|
||||
|
||||
try {
|
||||
return registriesIndexSchema.parse(data)
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
throw new RegistriesIndexParseError(error)
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,3 +285,41 @@ export class ConfigParseError extends RegistryError {
|
||||
this.name = "ConfigParseError"
|
||||
}
|
||||
}
|
||||
|
||||
export class RegistriesIndexParseError extends RegistryError {
|
||||
public readonly parseError: unknown
|
||||
|
||||
constructor(parseError: unknown) {
|
||||
let message = "Failed to parse registries index"
|
||||
|
||||
if (parseError instanceof z.ZodError) {
|
||||
const invalidNamespaces = parseError.errors
|
||||
.filter((e) => e.path.length > 0)
|
||||
.map((e) => `"${e.path[0]}"`)
|
||||
.filter((v, i, arr) => arr.indexOf(v) === i) // remove duplicates
|
||||
|
||||
if (invalidNamespaces.length > 0) {
|
||||
message = `Failed to parse registries index. Invalid registry namespace(s): ${invalidNamespaces.join(
|
||||
", "
|
||||
)}\n${parseError.errors
|
||||
.map((e) => ` - ${e.path.join(".")}: ${e.message}`)
|
||||
.join("\n")}`
|
||||
} else {
|
||||
message = `Failed to parse registries index:\n${parseError.errors
|
||||
.map((e) => ` - ${e.path.join(".")}: ${e.message}`)
|
||||
.join("\n")}`
|
||||
}
|
||||
}
|
||||
|
||||
super(message, {
|
||||
code: RegistryErrorCode.PARSE_ERROR,
|
||||
cause: parseError,
|
||||
context: { parseError },
|
||||
suggestion:
|
||||
"The registries index may be corrupted or have invalid registry namespace format. Registry names must start with @ (e.g., @shadcn, @example).",
|
||||
})
|
||||
|
||||
this.parseError = parseError
|
||||
this.name = "RegistriesIndexParseError"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export { getRegistryItems, resolveRegistryItems, getRegistry } from "./api"
|
||||
export {
|
||||
getRegistryItems,
|
||||
resolveRegistryItems,
|
||||
getRegistry,
|
||||
getRegistriesIndex,
|
||||
} from "./api"
|
||||
|
||||
export { searchRegistries } from "./search"
|
||||
|
||||
@@ -11,6 +16,7 @@ export {
|
||||
RegistryNotConfiguredError,
|
||||
RegistryLocalFileError,
|
||||
RegistryParseError,
|
||||
RegistriesIndexParseError,
|
||||
RegistryMissingEnvironmentVariablesError,
|
||||
RegistryInvalidNamespaceError,
|
||||
} from "./errors"
|
||||
|
||||
469
packages/shadcn/src/registry/namespaces.test.ts
Normal file
469
packages/shadcn/src/registry/namespaces.test.ts
Normal file
@@ -0,0 +1,469 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
import { Config } from "../utils/get-config"
|
||||
import { BUILTIN_REGISTRIES } from "./constants"
|
||||
import { RegistryNotConfiguredError } from "./errors"
|
||||
import { resolveRegistryNamespaces } from "./namespaces"
|
||||
import * as resolver from "./resolver"
|
||||
|
||||
// Mock the resolver module.
|
||||
vi.mock("./resolver", () => ({
|
||||
fetchRegistryItems: vi.fn(),
|
||||
}))
|
||||
|
||||
// Test utility function to check namespace configuration.
|
||||
function checkNamespaceConfiguration(
|
||||
namespaces: string[],
|
||||
config: Config
|
||||
): { configured: string[]; missing: string[] } {
|
||||
const configured: string[] = []
|
||||
const missing: string[] = []
|
||||
|
||||
for (const namespace of namespaces) {
|
||||
if (BUILTIN_REGISTRIES[namespace] || config.registries?.[namespace]) {
|
||||
configured.push(namespace)
|
||||
} else {
|
||||
missing.push(namespace)
|
||||
}
|
||||
}
|
||||
|
||||
return { configured, missing }
|
||||
}
|
||||
|
||||
describe("resolveRegistryNamespaces", () => {
|
||||
const mockConfig: Config = {
|
||||
style: "default",
|
||||
tailwind: {
|
||||
config: "tailwind.config.js",
|
||||
css: "app/globals.css",
|
||||
baseColor: "slate",
|
||||
cssVariables: true,
|
||||
},
|
||||
rsc: true,
|
||||
tsx: true,
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
utils: "@/lib/utils",
|
||||
ui: "@/components/ui",
|
||||
lib: "@/lib",
|
||||
hooks: "@/hooks",
|
||||
},
|
||||
resolvedPaths: {
|
||||
cwd: "/test",
|
||||
tailwindConfig: "/test/tailwind.config.js",
|
||||
tailwindCss: "/test/app/globals.css",
|
||||
utils: "/test/lib/utils",
|
||||
components: "/test/components",
|
||||
ui: "/test/components/ui",
|
||||
lib: "/test/lib",
|
||||
hooks: "/test/hooks",
|
||||
},
|
||||
registries: {
|
||||
...BUILTIN_REGISTRIES,
|
||||
"@foo": "https://foo.com/registry/{name}",
|
||||
"@bar": "https://bar.com/registry/{name}",
|
||||
},
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should discover namespaces from direct components", async () => {
|
||||
const mockFetchRegistryItems = vi.mocked(resolver.fetchRegistryItems)
|
||||
mockFetchRegistryItems.mockResolvedValue([
|
||||
{ name: "button", type: "registry:ui", files: [], dependencies: [] },
|
||||
])
|
||||
|
||||
const namespaces = await resolveRegistryNamespaces(
|
||||
["@foo/button", "@bar/card"],
|
||||
mockConfig
|
||||
)
|
||||
|
||||
expect(namespaces).toEqual(["@foo", "@bar"])
|
||||
})
|
||||
|
||||
it("should skip built-in registries", async () => {
|
||||
const mockFetchRegistryItems = vi.mocked(resolver.fetchRegistryItems)
|
||||
mockFetchRegistryItems.mockResolvedValue([
|
||||
{ name: "button", type: "registry:ui", files: [], dependencies: [] },
|
||||
])
|
||||
|
||||
const namespaces = await resolveRegistryNamespaces(
|
||||
["@shadcn/button", "@foo/card"],
|
||||
mockConfig
|
||||
)
|
||||
|
||||
expect(namespaces).toEqual(["@foo"])
|
||||
})
|
||||
|
||||
it("should discover namespaces from registry dependencies", async () => {
|
||||
const mockFetchRegistryItems = vi.mocked(resolver.fetchRegistryItems)
|
||||
mockFetchRegistryItems
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
name: "dialog",
|
||||
type: "registry:ui",
|
||||
files: [],
|
||||
dependencies: [],
|
||||
registryDependencies: ["@bar/button", "@baz/modal"],
|
||||
},
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
{ name: "button", type: "registry:ui", files: [], dependencies: [] },
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
{ name: "modal", type: "registry:ui", files: [], dependencies: [] },
|
||||
])
|
||||
|
||||
const namespaces = await resolveRegistryNamespaces(
|
||||
["@foo/dialog"],
|
||||
mockConfig
|
||||
)
|
||||
|
||||
expect(namespaces).toContain("@foo")
|
||||
expect(namespaces).toContain("@bar")
|
||||
expect(namespaces).toContain("@baz")
|
||||
})
|
||||
|
||||
it("should handle circular dependencies", async () => {
|
||||
const mockFetchRegistryItems = vi.mocked(resolver.fetchRegistryItems)
|
||||
mockFetchRegistryItems
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
name: "comp-a",
|
||||
type: "registry:ui",
|
||||
files: [],
|
||||
dependencies: [],
|
||||
registryDependencies: ["@bar/comp-b"],
|
||||
},
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
name: "comp-b",
|
||||
type: "registry:ui",
|
||||
files: [],
|
||||
dependencies: [],
|
||||
registryDependencies: ["@foo/comp-a"],
|
||||
},
|
||||
])
|
||||
|
||||
const namespaces = await resolveRegistryNamespaces(
|
||||
["@foo/comp-a"],
|
||||
mockConfig
|
||||
)
|
||||
|
||||
expect(namespaces).toEqual(["@foo", "@bar"])
|
||||
// Should only fetch each component once despite circular reference.
|
||||
expect(mockFetchRegistryItems).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it("should handle RegistryNotConfiguredError gracefully", async () => {
|
||||
const mockFetchRegistryItems = vi.mocked(resolver.fetchRegistryItems)
|
||||
mockFetchRegistryItems.mockRejectedValue(
|
||||
new RegistryNotConfiguredError("@unknown")
|
||||
)
|
||||
|
||||
const namespaces = await resolveRegistryNamespaces(
|
||||
["@unknown/button"],
|
||||
mockConfig
|
||||
)
|
||||
|
||||
expect(namespaces).toEqual(["@unknown"])
|
||||
})
|
||||
|
||||
it("should continue processing on other errors", async () => {
|
||||
const mockFetchRegistryItems = vi.mocked(resolver.fetchRegistryItems)
|
||||
mockFetchRegistryItems
|
||||
.mockRejectedValueOnce(new Error("Network error"))
|
||||
.mockResolvedValueOnce([
|
||||
{ name: "card", type: "registry:ui", files: [], dependencies: [] },
|
||||
])
|
||||
|
||||
const namespaces = await resolveRegistryNamespaces(
|
||||
["@foo/button", "@bar/card"],
|
||||
mockConfig
|
||||
)
|
||||
|
||||
// Should still discover both @foo and @bar.
|
||||
// @foo from the initial parse, @bar from successful fetch.
|
||||
expect(namespaces).toContain("@foo")
|
||||
expect(namespaces).toContain("@bar")
|
||||
expect(namespaces).toHaveLength(2)
|
||||
})
|
||||
|
||||
it("should handle deeply nested dependencies", async () => {
|
||||
const mockFetchRegistryItems = vi.mocked(resolver.fetchRegistryItems)
|
||||
mockFetchRegistryItems
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
name: "level-1",
|
||||
type: "registry:ui",
|
||||
files: [],
|
||||
dependencies: [],
|
||||
registryDependencies: ["@level2/component"],
|
||||
},
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
name: "component",
|
||||
type: "registry:ui",
|
||||
files: [],
|
||||
dependencies: [],
|
||||
registryDependencies: ["@level3/deep"],
|
||||
},
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
name: "deep",
|
||||
type: "registry:ui",
|
||||
files: [],
|
||||
dependencies: [],
|
||||
},
|
||||
])
|
||||
|
||||
const namespaces = await resolveRegistryNamespaces(
|
||||
["@level1/level-1"],
|
||||
mockConfig
|
||||
)
|
||||
|
||||
expect(namespaces).toEqual(["@level1", "@level2", "@level3"])
|
||||
})
|
||||
|
||||
it("should return unique namespaces", async () => {
|
||||
const mockFetchRegistryItems = vi.mocked(resolver.fetchRegistryItems)
|
||||
mockFetchRegistryItems
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
name: "comp-a",
|
||||
type: "registry:ui",
|
||||
files: [],
|
||||
dependencies: [],
|
||||
registryDependencies: ["@foo/shared", "@bar/shared"],
|
||||
},
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
{ name: "shared", type: "registry:ui", files: [], dependencies: [] },
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
{ name: "shared", type: "registry:ui", files: [], dependencies: [] },
|
||||
])
|
||||
|
||||
const namespaces = await resolveRegistryNamespaces(
|
||||
["@foo/comp-a", "@foo/comp-b", "@bar/comp-c"],
|
||||
mockConfig
|
||||
)
|
||||
|
||||
// Should not have duplicate @foo.
|
||||
expect(namespaces).toEqual(["@foo", "@bar"])
|
||||
})
|
||||
|
||||
it("should handle components without namespace", async () => {
|
||||
const mockFetchRegistryItems = vi.mocked(resolver.fetchRegistryItems)
|
||||
mockFetchRegistryItems.mockResolvedValue([
|
||||
{ name: "button", type: "registry:ui", files: [], dependencies: [] },
|
||||
])
|
||||
|
||||
const namespaces = await resolveRegistryNamespaces(
|
||||
["button", "@foo/card"],
|
||||
mockConfig
|
||||
)
|
||||
|
||||
expect(namespaces).toEqual(["@foo"])
|
||||
})
|
||||
|
||||
it("should handle empty input", async () => {
|
||||
const namespaces = await resolveRegistryNamespaces([], mockConfig)
|
||||
|
||||
expect(namespaces).toEqual([])
|
||||
expect(resolver.fetchRegistryItems).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should discover namespaces from components without namespaces but with registryDependencies", async () => {
|
||||
const mockFetchRegistryItems = vi.mocked(resolver.fetchRegistryItems)
|
||||
mockFetchRegistryItems
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
name: "my-component",
|
||||
type: "registry:ui",
|
||||
files: [],
|
||||
dependencies: [],
|
||||
registryDependencies: ["@foo/dep1", "@bar/dep2"],
|
||||
},
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
name: "dep1",
|
||||
type: "registry:ui",
|
||||
files: [],
|
||||
dependencies: [],
|
||||
},
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
name: "dep2",
|
||||
type: "registry:ui",
|
||||
files: [],
|
||||
dependencies: [],
|
||||
},
|
||||
])
|
||||
|
||||
const namespaces = await resolveRegistryNamespaces(
|
||||
["button"], // Component without namespace
|
||||
mockConfig
|
||||
)
|
||||
|
||||
// Should discover namespaces from registryDependencies even though "button" has no namespace.
|
||||
expect(namespaces).toEqual(["@foo", "@bar"])
|
||||
})
|
||||
|
||||
it("should discover namespaces from URL components with registryDependencies", async () => {
|
||||
const mockFetchRegistryItems = vi.mocked(resolver.fetchRegistryItems)
|
||||
mockFetchRegistryItems
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
name: "to-8bitcn",
|
||||
type: "registry:item",
|
||||
files: [],
|
||||
dependencies: [],
|
||||
registryDependencies: ["@8bitcn/button"],
|
||||
},
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
name: "branch",
|
||||
type: "registry:ui",
|
||||
files: [],
|
||||
dependencies: [],
|
||||
},
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
name: "button",
|
||||
type: "registry:ui",
|
||||
files: [],
|
||||
dependencies: [],
|
||||
},
|
||||
])
|
||||
|
||||
const namespaces = await resolveRegistryNamespaces(
|
||||
["https://api.npoint.io/2e006917dca7f7367495", "@ai-elements/branch"],
|
||||
mockConfig
|
||||
)
|
||||
|
||||
expect(namespaces).toContain("@8bitcn")
|
||||
expect(namespaces).toContain("@ai-elements")
|
||||
|
||||
// Verify fetchRegistryItems was called with the correct arguments.
|
||||
expect(mockFetchRegistryItems).toHaveBeenCalledWith(
|
||||
["https://api.npoint.io/2e006917dca7f7367495"],
|
||||
mockConfig,
|
||||
{ useCache: true }
|
||||
)
|
||||
expect(mockFetchRegistryItems).toHaveBeenCalledWith(
|
||||
["@ai-elements/branch"],
|
||||
mockConfig,
|
||||
{ useCache: true }
|
||||
)
|
||||
expect(mockFetchRegistryItems).toHaveBeenCalledWith(
|
||||
["@8bitcn/button"],
|
||||
mockConfig,
|
||||
{ useCache: true }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("checkNamespaceConfiguration", () => {
|
||||
const mockConfig: Config = {
|
||||
style: "default",
|
||||
tailwind: {
|
||||
config: "tailwind.config.js",
|
||||
css: "app/globals.css",
|
||||
baseColor: "slate",
|
||||
cssVariables: true,
|
||||
},
|
||||
rsc: true,
|
||||
tsx: true,
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
utils: "@/lib/utils",
|
||||
ui: "@/components/ui",
|
||||
lib: "@/lib",
|
||||
hooks: "@/hooks",
|
||||
},
|
||||
resolvedPaths: {
|
||||
cwd: "/test",
|
||||
tailwindConfig: "/test/tailwind.config.js",
|
||||
tailwindCss: "/test/app/globals.css",
|
||||
utils: "/test/lib/utils",
|
||||
components: "/test/components",
|
||||
ui: "/test/components/ui",
|
||||
lib: "/test/lib",
|
||||
hooks: "/test/hooks",
|
||||
},
|
||||
registries: {
|
||||
...BUILTIN_REGISTRIES,
|
||||
"@foo": "https://foo.com/registry/{name}",
|
||||
"@bar": "https://bar.com/registry/{name}",
|
||||
},
|
||||
}
|
||||
|
||||
it("should identify configured namespaces", () => {
|
||||
const result = checkNamespaceConfiguration(["@foo", "@bar"], mockConfig)
|
||||
|
||||
expect(result.configured).toEqual(["@foo", "@bar"])
|
||||
expect(result.missing).toEqual([])
|
||||
})
|
||||
|
||||
it("should identify missing namespaces", () => {
|
||||
const result = checkNamespaceConfiguration(
|
||||
["@foo", "@unknown", "@missing"],
|
||||
mockConfig
|
||||
)
|
||||
|
||||
expect(result.configured).toEqual(["@foo"])
|
||||
expect(result.missing).toEqual(["@unknown", "@missing"])
|
||||
})
|
||||
|
||||
it("should handle built-in registries as configured", () => {
|
||||
const result = checkNamespaceConfiguration(["@shadcn", "@foo"], mockConfig)
|
||||
|
||||
expect(result.configured).toEqual(["@shadcn", "@foo"])
|
||||
expect(result.missing).toEqual([])
|
||||
})
|
||||
|
||||
it("should handle empty input", () => {
|
||||
const result = checkNamespaceConfiguration([], mockConfig)
|
||||
|
||||
expect(result.configured).toEqual([])
|
||||
expect(result.missing).toEqual([])
|
||||
})
|
||||
|
||||
it("should handle config without registries", () => {
|
||||
const configWithoutRegistries: Config = {
|
||||
...mockConfig,
|
||||
registries: undefined,
|
||||
}
|
||||
|
||||
const result = checkNamespaceConfiguration(
|
||||
["@foo", "@bar"],
|
||||
configWithoutRegistries
|
||||
)
|
||||
|
||||
expect(result.configured).toEqual([])
|
||||
expect(result.missing).toEqual(["@foo", "@bar"])
|
||||
})
|
||||
|
||||
it("should handle mixed configured and missing namespaces", () => {
|
||||
const result = checkNamespaceConfiguration(
|
||||
["@shadcn", "@foo", "@unknown", "@bar", "@missing"],
|
||||
mockConfig
|
||||
)
|
||||
|
||||
expect(result.configured).toContain("@shadcn")
|
||||
expect(result.configured).toContain("@foo")
|
||||
expect(result.configured).toContain("@bar")
|
||||
expect(result.missing).toContain("@unknown")
|
||||
expect(result.missing).toContain("@missing")
|
||||
})
|
||||
})
|
||||
63
packages/shadcn/src/registry/namespaces.ts
Normal file
63
packages/shadcn/src/registry/namespaces.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { BUILTIN_REGISTRIES } from "@/src/registry/constants"
|
||||
import { RegistryNotConfiguredError } from "@/src/registry/errors"
|
||||
import { parseRegistryAndItemFromString } from "@/src/registry/parser"
|
||||
import { fetchRegistryItems } from "@/src/registry/resolver"
|
||||
import { Config } from "@/src/utils/get-config"
|
||||
|
||||
// Recursively discovers all registry namespaces including nested ones.
|
||||
export async function resolveRegistryNamespaces(
|
||||
components: string[],
|
||||
config: Config
|
||||
) {
|
||||
const discoveredNamespaces = new Set<string>()
|
||||
const visitedItems = new Set<string>()
|
||||
const itemsToProcess = [...components]
|
||||
|
||||
while (itemsToProcess.length > 0) {
|
||||
const currentItem = itemsToProcess.shift()!
|
||||
|
||||
if (visitedItems.has(currentItem)) {
|
||||
continue
|
||||
}
|
||||
visitedItems.add(currentItem)
|
||||
|
||||
const { registry } = parseRegistryAndItemFromString(currentItem)
|
||||
if (registry && !BUILTIN_REGISTRIES[registry]) {
|
||||
discoveredNamespaces.add(registry)
|
||||
}
|
||||
|
||||
try {
|
||||
const [item] = await fetchRegistryItems([currentItem], config, {
|
||||
useCache: true,
|
||||
})
|
||||
|
||||
if (item?.registryDependencies) {
|
||||
for (const dep of item.registryDependencies) {
|
||||
const { registry: depRegistry } = parseRegistryAndItemFromString(dep)
|
||||
if (depRegistry && !BUILTIN_REGISTRIES[depRegistry]) {
|
||||
discoveredNamespaces.add(depRegistry)
|
||||
}
|
||||
|
||||
if (!visitedItems.has(dep)) {
|
||||
itemsToProcess.push(dep)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// If a registry is not configured, we still track it.
|
||||
if (error instanceof RegistryNotConfiguredError) {
|
||||
const { registry } = parseRegistryAndItemFromString(currentItem)
|
||||
if (registry && !BUILTIN_REGISTRIES[registry]) {
|
||||
discoveredNamespaces.add(registry)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// For other errors (network, parsing, etc.), we skip this item
|
||||
// but continue processing others to discover as many namespaces as possible.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(discoveredNamespaces)
|
||||
}
|
||||
@@ -426,7 +426,16 @@ describe("resolveRegistryItems with URL dependencies", () => {
|
||||
const mockConfig = {
|
||||
style: "new-york",
|
||||
tailwind: { baseColor: "neutral", cssVariables: true },
|
||||
resolvedPaths: { cwd: process.cwd() },
|
||||
resolvedPaths: {
|
||||
cwd: process.cwd(),
|
||||
tailwindConfig: "./tailwind.config.js",
|
||||
tailwindCss: "./globals.css",
|
||||
utils: "./lib/utils",
|
||||
components: "./components",
|
||||
lib: "./lib",
|
||||
hooks: "./hooks",
|
||||
ui: "./components/ui",
|
||||
},
|
||||
} as any
|
||||
|
||||
const result = await resolveRegistryTree([tempFile], mockConfig)
|
||||
@@ -489,7 +498,16 @@ describe("resolveRegistryItems with URL dependencies", () => {
|
||||
const mockConfig = {
|
||||
style: "new-york",
|
||||
tailwind: { baseColor: "neutral", cssVariables: true },
|
||||
resolvedPaths: { cwd: process.cwd() },
|
||||
resolvedPaths: {
|
||||
cwd: process.cwd(),
|
||||
tailwindConfig: "./tailwind.config.js",
|
||||
tailwindCss: "./globals.css",
|
||||
utils: "./lib/utils",
|
||||
components: "./components",
|
||||
lib: "./lib",
|
||||
hooks: "./hooks",
|
||||
ui: "./components/ui",
|
||||
},
|
||||
registries: {
|
||||
"@custom": {
|
||||
url: "https://custom-registry.com/{name}.json",
|
||||
|
||||
@@ -52,19 +52,13 @@ export const registryItemCssVarsSchema = z.object({
|
||||
dark: z.record(z.string(), z.string()).optional(),
|
||||
})
|
||||
|
||||
export const registryItemCssSchema = z.record(
|
||||
z.string(),
|
||||
z.lazy(() =>
|
||||
z.union([
|
||||
z.string(),
|
||||
z.record(
|
||||
z.string(),
|
||||
z.union([z.string(), z.record(z.string(), z.string())])
|
||||
),
|
||||
])
|
||||
)
|
||||
// Recursive type for CSS properties that supports empty objects at any level.
|
||||
const cssValueSchema: z.ZodType<any> = z.lazy(() =>
|
||||
z.union([z.string(), z.record(z.string(), cssValueSchema)])
|
||||
)
|
||||
|
||||
export const registryItemCssSchema = z.record(z.string(), cssValueSchema)
|
||||
|
||||
export const registryItemEnvVarsSchema = z.record(z.string(), z.string())
|
||||
|
||||
export const registryItemSchema = z.object({
|
||||
@@ -215,3 +209,8 @@ export const searchResultsSchema = z.object({
|
||||
}),
|
||||
items: z.array(searchResultItemSchema),
|
||||
})
|
||||
|
||||
export const registriesIndexSchema = z.record(
|
||||
z.string().regex(/^@[a-zA-Z0-9][a-zA-Z0-9-_]*$/),
|
||||
z.string()
|
||||
)
|
||||
|
||||
@@ -274,6 +274,10 @@ export function createConfig(partial?: DeepPartial<Config>): Config {
|
||||
...defaultConfig.aliases,
|
||||
...(partial.aliases || {}),
|
||||
},
|
||||
registries: {
|
||||
...defaultConfig.registries,
|
||||
...(partial.registries || {}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
101
packages/shadcn/src/utils/registries.ts
Normal file
101
packages/shadcn/src/utils/registries.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import path from "path"
|
||||
import { getRegistriesIndex } from "@/src/registry/api"
|
||||
import { BUILTIN_REGISTRIES } from "@/src/registry/constants"
|
||||
import { resolveRegistryNamespaces } from "@/src/registry/namespaces"
|
||||
import { rawConfigSchema } from "@/src/registry/schema"
|
||||
import { Config } from "@/src/utils/get-config"
|
||||
import { spinner } from "@/src/utils/spinner"
|
||||
import fs from "fs-extra"
|
||||
|
||||
export async function ensureRegistriesInConfig(
|
||||
components: string[],
|
||||
config: Config,
|
||||
options: {
|
||||
silent?: boolean
|
||||
writeFile?: boolean
|
||||
} = {}
|
||||
) {
|
||||
options = {
|
||||
silent: false,
|
||||
writeFile: true,
|
||||
...options,
|
||||
}
|
||||
|
||||
// Use resolveRegistryNamespaces to discover all namespaces including dependencies.
|
||||
const registryNames = await resolveRegistryNamespaces(components, config)
|
||||
|
||||
const missingRegistries = registryNames.filter(
|
||||
(registry) =>
|
||||
!config.registries?.[registry] &&
|
||||
!Object.keys(BUILTIN_REGISTRIES).includes(registry)
|
||||
)
|
||||
|
||||
if (missingRegistries.length === 0) {
|
||||
return {
|
||||
config,
|
||||
newRegistries: [],
|
||||
}
|
||||
}
|
||||
|
||||
// We'll fail silently if we can't fetch the registry index.
|
||||
// The error handling by caller will guide user to add the missing registries.
|
||||
const registryIndex = await getRegistriesIndex({
|
||||
useCache: process.env.NODE_ENV !== "development",
|
||||
})
|
||||
|
||||
if (!registryIndex) {
|
||||
return {
|
||||
config,
|
||||
newRegistries: [],
|
||||
}
|
||||
}
|
||||
|
||||
const foundRegistries: Record<string, string> = {}
|
||||
for (const registry of missingRegistries) {
|
||||
if (registryIndex[registry]) {
|
||||
foundRegistries[registry] = registryIndex[registry]
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(foundRegistries).length === 0) {
|
||||
return {
|
||||
config,
|
||||
newRegistries: [],
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out built-in registries from existing config before merging
|
||||
const existingRegistries = Object.fromEntries(
|
||||
Object.entries(config.registries || {}).filter(
|
||||
([key]) => !Object.keys(BUILTIN_REGISTRIES).includes(key)
|
||||
)
|
||||
)
|
||||
|
||||
const newConfigWithRegistries = {
|
||||
...config,
|
||||
registries: {
|
||||
...existingRegistries,
|
||||
...foundRegistries,
|
||||
},
|
||||
}
|
||||
|
||||
if (options.writeFile) {
|
||||
const { resolvedPaths, ...configWithoutResolvedPaths } =
|
||||
newConfigWithRegistries
|
||||
const configSpinner = spinner("Updating components.json.", {
|
||||
silent: options.silent,
|
||||
}).start()
|
||||
const updatedConfig = rawConfigSchema.parse(configWithoutResolvedPaths)
|
||||
await fs.writeFile(
|
||||
path.resolve(config.resolvedPaths.cwd, "components.json"),
|
||||
JSON.stringify(updatedConfig, null, 2) + "\n",
|
||||
"utf-8"
|
||||
)
|
||||
configSpinner.succeed()
|
||||
}
|
||||
|
||||
return {
|
||||
config: newConfigWithRegistries,
|
||||
newRegistries: Object.keys(foundRegistries),
|
||||
}
|
||||
}
|
||||
@@ -31,13 +31,10 @@ export const transformCssVars: Transformer = async ({
|
||||
// }
|
||||
// }
|
||||
sourceFile.getDescendantsOfKind(SyntaxKind.StringLiteral).forEach((node) => {
|
||||
const value = node.getText()
|
||||
if (value) {
|
||||
const valueWithColorMapping = applyColorMapping(
|
||||
value.replace(/"/g, ""),
|
||||
baseColor.inlineColors
|
||||
)
|
||||
node.replaceWithText(`"${valueWithColorMapping.trim()}"`)
|
||||
const raw = node.getLiteralText()
|
||||
const mapped = applyColorMapping(raw, baseColor.inlineColors).trim()
|
||||
if (mapped !== raw) {
|
||||
node.setLiteralValue(mapped)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Config } from "@/src/utils/get-config"
|
||||
import { Transformer } from "@/src/utils/transformers"
|
||||
import { SyntaxKind } from "ts-morph"
|
||||
|
||||
export const transformImport: Transformer = async ({
|
||||
sourceFile,
|
||||
@@ -9,32 +10,34 @@ export const transformImport: Transformer = async ({
|
||||
const workspaceAlias = config.aliases?.utils?.split("/")[0]?.slice(1)
|
||||
const utilsImport = `@${workspaceAlias}/lib/utils`
|
||||
|
||||
const importDeclarations = sourceFile.getImportDeclarations()
|
||||
|
||||
if (![".tsx", ".ts", ".jsx", ".js"].includes(sourceFile.getExtension())) {
|
||||
return sourceFile
|
||||
}
|
||||
|
||||
for (const importDeclaration of importDeclarations) {
|
||||
const moduleSpecifier = updateImportAliases(
|
||||
importDeclaration.getModuleSpecifierValue(),
|
||||
for (const specifier of sourceFile.getImportStringLiterals()) {
|
||||
const updated = updateImportAliases(
|
||||
specifier.getLiteralValue(),
|
||||
config,
|
||||
isRemote
|
||||
)
|
||||
|
||||
importDeclaration.setModuleSpecifier(moduleSpecifier)
|
||||
specifier.setLiteralValue(updated)
|
||||
|
||||
// Replace `import { cn } from "@/lib/utils"`
|
||||
if (utilsImport === moduleSpecifier || moduleSpecifier === "@/lib/utils") {
|
||||
const namedImports = importDeclaration.getNamedImports()
|
||||
const cnImport = namedImports.find((i) => i.getName() === "cn")
|
||||
if (cnImport) {
|
||||
importDeclaration.setModuleSpecifier(
|
||||
utilsImport === moduleSpecifier
|
||||
? moduleSpecifier.replace(utilsImport, config.aliases.utils)
|
||||
: config.aliases.utils
|
||||
)
|
||||
}
|
||||
if (utilsImport === updated || updated === "@/lib/utils") {
|
||||
const importDeclaration = specifier.getFirstAncestorByKind(
|
||||
SyntaxKind.ImportDeclaration
|
||||
)
|
||||
const isCnImport = importDeclaration
|
||||
?.getNamedImports()
|
||||
.some((namedImport) => namedImport.getName() === "cn")
|
||||
|
||||
if (!isCnImport) continue
|
||||
|
||||
specifier.setLiteralValue(
|
||||
utilsImport === updated
|
||||
? updated.replace(utilsImport, config.aliases.utils)
|
||||
: config.aliases.utils
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,21 @@ export async function transformCss(
|
||||
})
|
||||
|
||||
let output = result.css
|
||||
|
||||
// PostCSS doesn't add semicolons to at-rules without bodies when they're the last node.
|
||||
// We need to manually ensure they have semicolons.
|
||||
const root = result.root
|
||||
if (root.nodes && root.nodes.length > 0) {
|
||||
const lastNode = root.nodes[root.nodes.length - 1]
|
||||
if (
|
||||
lastNode.type === "atrule" &&
|
||||
!lastNode.nodes &&
|
||||
!output.trimEnd().endsWith(";")
|
||||
) {
|
||||
output = output.trimEnd() + ";"
|
||||
}
|
||||
}
|
||||
|
||||
output = output.replace(/\/\* ---break--- \*\//g, "")
|
||||
output = output.replace(/(\n\s*\n)+/g, "\n\n")
|
||||
output = output.trimEnd()
|
||||
@@ -79,20 +94,77 @@ function updateCssPlugin(css: z.infer<typeof registryItemCssSchema>) {
|
||||
|
||||
const [, name, params] = atRuleMatch
|
||||
|
||||
// Special handling for plugins - place them after imports
|
||||
if (name === "plugin") {
|
||||
// Find existing plugin with same params
|
||||
const existingPlugin = root.nodes?.find(
|
||||
// Special handling for imports - place them at the top.
|
||||
if (name === "import") {
|
||||
// Check if this import already exists.
|
||||
const existingImport = root.nodes?.find(
|
||||
(node): node is AtRule =>
|
||||
node.type === "atrule" &&
|
||||
node.name === "plugin" &&
|
||||
node.name === "import" &&
|
||||
node.params === params
|
||||
)
|
||||
|
||||
if (!existingImport) {
|
||||
const importRule = postcss.atRule({
|
||||
name: "import",
|
||||
params,
|
||||
raws: { semicolon: true },
|
||||
})
|
||||
|
||||
// Find the last import to insert after, or insert at beginning.
|
||||
const importNodes = root.nodes?.filter(
|
||||
(node): node is AtRule =>
|
||||
node.type === "atrule" && node.name === "import"
|
||||
)
|
||||
|
||||
if (importNodes && importNodes.length > 0) {
|
||||
// Insert after the last existing import.
|
||||
const lastImport = importNodes[importNodes.length - 1]
|
||||
importRule.raws.before = "\n"
|
||||
root.insertAfter(lastImport, importRule)
|
||||
} else {
|
||||
// No imports exist, insert at the very beginning.
|
||||
// Check if the file is empty.
|
||||
if (!root.nodes || root.nodes.length === 0) {
|
||||
importRule.raws.before = ""
|
||||
} else {
|
||||
importRule.raws.before = ""
|
||||
}
|
||||
root.prepend(importRule)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Special handling for plugins - place them after imports.
|
||||
else if (name === "plugin") {
|
||||
// Ensure plugin name is quoted if not already.
|
||||
let quotedParams = params
|
||||
if (params && !params.startsWith('"') && !params.startsWith("'")) {
|
||||
quotedParams = `"${params}"`
|
||||
}
|
||||
|
||||
// Normalize params for comparison (remove quotes).
|
||||
const normalizeParams = (p: string) => {
|
||||
if (p.startsWith('"') && p.endsWith('"')) {
|
||||
return p.slice(1, -1)
|
||||
}
|
||||
if (p.startsWith("'") && p.endsWith("'")) {
|
||||
return p.slice(1, -1)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Find existing plugin with same normalized params.
|
||||
const existingPlugin = root.nodes?.find((node): node is AtRule => {
|
||||
if (node.type !== "atrule" || node.name !== "plugin") {
|
||||
return false
|
||||
}
|
||||
return normalizeParams(node.params) === normalizeParams(params)
|
||||
})
|
||||
|
||||
if (!existingPlugin) {
|
||||
const pluginRule = postcss.atRule({
|
||||
name: "plugin",
|
||||
params,
|
||||
params: quotedParams,
|
||||
raws: { semicolon: true, before: "\n" },
|
||||
})
|
||||
|
||||
@@ -141,7 +213,34 @@ function updateCssPlugin(css: z.infer<typeof registryItemCssSchema>) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Special handling for keyframes - place them under @theme inline
|
||||
// Check if this is any at-rule with no body (empty object).
|
||||
else if (
|
||||
typeof properties === "object" &&
|
||||
Object.keys(properties).length === 0
|
||||
) {
|
||||
// Handle any at-rule with no body (e.g., @apply, @tailwind, etc.).
|
||||
const atRule = root.nodes?.find(
|
||||
(node): node is AtRule =>
|
||||
node.type === "atrule" &&
|
||||
node.name === name &&
|
||||
node.params === params
|
||||
) as AtRule | undefined
|
||||
|
||||
if (!atRule) {
|
||||
const newAtRule = postcss.atRule({
|
||||
name,
|
||||
params,
|
||||
raws: { semicolon: true },
|
||||
})
|
||||
|
||||
root.append(newAtRule)
|
||||
root.insertBefore(
|
||||
newAtRule,
|
||||
postcss.comment({ text: "---break---" })
|
||||
)
|
||||
}
|
||||
}
|
||||
// Special handling for keyframes - place them under @theme inline.
|
||||
else if (name === "keyframes") {
|
||||
let themeInline = root.nodes?.find(
|
||||
(node): node is AtRule =>
|
||||
@@ -339,14 +438,32 @@ function processRule(parent: Root | AtRule, selector: string, properties: any) {
|
||||
|
||||
if (typeof properties === "object") {
|
||||
for (const [prop, value] of Object.entries(properties)) {
|
||||
if (typeof value === "string") {
|
||||
// Check if this is any at-rule with empty object (no body).
|
||||
if (
|
||||
prop.startsWith("@") &&
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
Object.keys(value).length === 0
|
||||
) {
|
||||
// Parse the at-rule.
|
||||
const atRuleMatch = prop.match(/@([a-zA-Z-]+)\s*(.*)/)
|
||||
if (atRuleMatch) {
|
||||
const [, atRuleName, atRuleParams] = atRuleMatch
|
||||
const atRule = postcss.atRule({
|
||||
name: atRuleName,
|
||||
params: atRuleParams,
|
||||
raws: { semicolon: true, before: "\n " },
|
||||
})
|
||||
rule.append(atRule)
|
||||
}
|
||||
} else if (typeof value === "string") {
|
||||
const decl = postcss.decl({
|
||||
prop,
|
||||
value: value,
|
||||
raws: { semicolon: true, before: "\n " },
|
||||
})
|
||||
|
||||
// Replace existing property or add new one
|
||||
// Replace existing property or add new one.
|
||||
const existingDecl = rule.nodes?.find(
|
||||
(node): node is Declaration =>
|
||||
node.type === "decl" && node.prop === prop
|
||||
@@ -354,10 +471,10 @@ function processRule(parent: Root | AtRule, selector: string, properties: any) {
|
||||
|
||||
existingDecl ? existingDecl.replaceWith(decl) : rule.append(decl)
|
||||
} else if (typeof value === "object") {
|
||||
// Nested selector (including & selectors)
|
||||
// Nested selector (including & selectors).
|
||||
const nestedSelector = prop.startsWith("&")
|
||||
? selector.replace(/^([^:]+)/, `$1${prop.substring(1)}`)
|
||||
: prop // Use the original selector for other nested elements
|
||||
: prop // Use the original selector for other nested elements.
|
||||
processRule(parent, nestedSelector, value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ exports[`transform css vars 2`] = `
|
||||
"import * as React from "react"
|
||||
export function Foo() {
|
||||
return <div className="bg-white hover:bg-stone-100 text-stone-50 sm:focus:text-stone-900 dark:bg-stone-950 dark:hover:bg-stone-800 dark:text-stone-900 dark:sm:focus:text-stone-50">foo</div>
|
||||
}""
|
||||
}"
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -20,7 +20,7 @@ exports[`transform css vars 3`] = `
|
||||
"import * as React from "react"
|
||||
export function Foo() {
|
||||
return <div className={cn("bg-white hover:bg-stone-100 dark:bg-stone-950 dark:hover:bg-stone-800", true && "text-stone-50 sm:focus:text-stone-900 dark:text-stone-900 dark:sm:focus:text-stone-50")}>foo</div>
|
||||
}""
|
||||
}"
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -28,6 +28,6 @@ exports[`transform css vars 4`] = `
|
||||
"import * as React from "react"
|
||||
export function Foo() {
|
||||
return <div className={cn("bg-white border border-stone-200 dark:bg-stone-950 dark:border-stone-800")}>foo</div>
|
||||
}""
|
||||
}"
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -1,5 +1,57 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`transform async/dynamic imports 1`] = `
|
||||
"import * as React from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
async function loadComponent() {
|
||||
const { cn } = await import("@/lib/utils")
|
||||
const module = await import("@/components/ui/card")
|
||||
return module
|
||||
}
|
||||
|
||||
function lazyLoad() {
|
||||
return import("@/components/ui/dialog").then(module => module)
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform async/dynamic imports 2`] = `
|
||||
"import { Button } from "~/components/ui/button"
|
||||
|
||||
async function loadUtils() {
|
||||
const utils = await import("~/lib/utils")
|
||||
const { cn } = await import("~/lib/utils")
|
||||
return { utils, cn }
|
||||
}
|
||||
|
||||
const dialogPromise = import("~/components/ui/dialog")
|
||||
const cardModule = import("~/components/ui/card")
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform dynamic imports with cn utility 1`] = `
|
||||
"async function loadCn() {
|
||||
const { cn } = await import("@/lib/utils")
|
||||
return cn
|
||||
}
|
||||
|
||||
async function loadMultiple() {
|
||||
const utils1 = await import("@/lib/utils")
|
||||
const { cn, twMerge } = await import("@/lib/utils")
|
||||
const other = await import("@/lib/other")
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform dynamic imports with cn utility 2`] = `
|
||||
"async function loadWorkspaceCn() {
|
||||
const { cn } = await import("@workspace/lib/utils")
|
||||
return cn
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform import 1`] = `
|
||||
"import * as React from "react"
|
||||
import { Foo } from "bar"
|
||||
@@ -91,3 +143,14 @@ import { Foo } from "bar"
|
||||
import { cn } from "@repo/ui/lib/utils"
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform re-exports with dynamic imports 1`] = `
|
||||
"export { cn } from "@/lib/utils"
|
||||
export { Button } from "@/components/ui/button"
|
||||
|
||||
async function load() {
|
||||
const module = await import("@/components/ui/card")
|
||||
return module
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -27,7 +27,7 @@ export function Foo() {
|
||||
exports[`transform tailwind prefix 4`] = `
|
||||
"import * as React from "react"
|
||||
export function Foo() {
|
||||
return <div className={cn("tw:bg-background hover:tw:bg-muted", true && "tw:text-primary-foreground sm:focus:tw:text-accent-foreground")}>foo</div>
|
||||
return <div className={cn("tw:bg-white hover:tw:bg-stone-100 dark:tw:bg-stone-950 dark:hover:tw:bg-stone-800", true && "tw:text-stone-50 sm:focus:tw:text-stone-900 dark:tw:text-stone-900 dark:sm:focus:tw:text-stone-50")}>foo</div>
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -144,7 +144,6 @@ import { Foo } from "bar"
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
|
||||
test("transform import for monorepo", async () => {
|
||||
expect(
|
||||
await transform({
|
||||
@@ -196,3 +195,122 @@ import { Foo } from "bar"
|
||||
})
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test("transform async/dynamic imports", async () => {
|
||||
expect(
|
||||
await transform({
|
||||
filename: "test.ts",
|
||||
raw: `import * as React from "react"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
async function loadComponent() {
|
||||
const { cn } = await import("@/lib/utils")
|
||||
const module = await import("@/registry/new-york/ui/card")
|
||||
return module
|
||||
}
|
||||
|
||||
function lazyLoad() {
|
||||
return import("@/registry/new-york/ui/dialog").then(module => module)
|
||||
}
|
||||
`,
|
||||
config: {
|
||||
tsx: true,
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
utils: "@/lib/utils",
|
||||
},
|
||||
},
|
||||
})
|
||||
).toMatchSnapshot()
|
||||
|
||||
expect(
|
||||
await transform({
|
||||
filename: "test.ts",
|
||||
raw: `import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
async function loadUtils() {
|
||||
const utils = await import("@/lib/utils")
|
||||
const { cn } = await import("@/lib/utils")
|
||||
return { utils, cn }
|
||||
}
|
||||
|
||||
const dialogPromise = import("@/registry/new-york/ui/dialog")
|
||||
const cardModule = import("@/registry/new-york/ui/card")
|
||||
`,
|
||||
config: {
|
||||
tsx: true,
|
||||
aliases: {
|
||||
components: "~/components",
|
||||
utils: "~/lib/utils",
|
||||
},
|
||||
},
|
||||
})
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test("transform dynamic imports with cn utility", async () => {
|
||||
expect(
|
||||
await transform({
|
||||
filename: "test.ts",
|
||||
raw: `async function loadCn() {
|
||||
const { cn } = await import("@/lib/utils")
|
||||
return cn
|
||||
}
|
||||
|
||||
async function loadMultiple() {
|
||||
const utils1 = await import("@/lib/utils")
|
||||
const { cn, twMerge } = await import("@/lib/utils")
|
||||
const other = await import("@/lib/other")
|
||||
}
|
||||
`,
|
||||
config: {
|
||||
tsx: true,
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
utils: "@/lib/utils",
|
||||
},
|
||||
},
|
||||
})
|
||||
).toMatchSnapshot()
|
||||
|
||||
expect(
|
||||
await transform({
|
||||
filename: "test.ts",
|
||||
raw: `async function loadWorkspaceCn() {
|
||||
const { cn } = await import("@/lib/utils")
|
||||
return cn
|
||||
}
|
||||
`,
|
||||
config: {
|
||||
tsx: true,
|
||||
aliases: {
|
||||
components: "@workspace/ui/components",
|
||||
utils: "@workspace/ui/lib/utils",
|
||||
},
|
||||
},
|
||||
})
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test("transform re-exports with dynamic imports", async () => {
|
||||
expect(
|
||||
await transform({
|
||||
filename: "test.ts",
|
||||
raw: `export { cn } from "@/lib/utils"
|
||||
export { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
async function load() {
|
||||
const module = await import("@/registry/new-york/ui/card")
|
||||
return module
|
||||
}
|
||||
`,
|
||||
config: {
|
||||
tsx: true,
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
utils: "@/lib/utils",
|
||||
},
|
||||
},
|
||||
})
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
@@ -333,13 +333,13 @@ describe("transformCss", () => {
|
||||
const input = `@import "tailwindcss";`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
"@plugin foo": {},
|
||||
'@plugin "foo"': {},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
|
||||
@plugin foo;"
|
||||
@plugin \"foo\";"
|
||||
`)
|
||||
})
|
||||
|
||||
@@ -357,8 +357,8 @@ describe("transformCss", () => {
|
||||
}`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
"@plugin foo": {},
|
||||
"@plugin bar": {},
|
||||
'@plugin "foo"': {},
|
||||
'@plugin "bar"': {},
|
||||
"@layer components": {
|
||||
".card": {
|
||||
padding: "1rem",
|
||||
@@ -369,8 +369,8 @@ describe("transformCss", () => {
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
|
||||
@plugin foo;
|
||||
@plugin bar;
|
||||
@plugin \"foo\";
|
||||
@plugin \"bar\";
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@@ -393,7 +393,7 @@ describe("transformCss", () => {
|
||||
test("should not add duplicate plugins", async () => {
|
||||
const input = `@import "tailwindcss";
|
||||
|
||||
@plugin foo;
|
||||
@plugin "foo";
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@@ -402,16 +402,16 @@ describe("transformCss", () => {
|
||||
}`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
"@plugin foo": {},
|
||||
"@plugin bar": {},
|
||||
'@plugin "foo"': {},
|
||||
'@plugin "bar"': {},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
|
||||
@plugin foo;
|
||||
@plugin \"foo\";
|
||||
|
||||
@plugin bar;
|
||||
@plugin \"bar\";
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@@ -429,12 +429,12 @@ describe("transformCss", () => {
|
||||
}`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
"@plugin foo": {},
|
||||
'@plugin "foo"': {},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"
|
||||
@plugin foo;
|
||||
@plugin \"foo\";
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@@ -448,15 +448,15 @@ describe("transformCss", () => {
|
||||
const input = `@import "tailwindcss";`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
"@plugin @tailwindcss/typography": {},
|
||||
"@plugin ./custom-plugin.js": {},
|
||||
'@plugin "@tailwindcss/typography"': {},
|
||||
'@plugin "./custom-plugin.js"': {},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
|
||||
@plugin @tailwindcss/typography;
|
||||
@plugin ./custom-plugin.js;"
|
||||
@plugin \"@tailwindcss/typography\";
|
||||
@plugin \"./custom-plugin.js\";"
|
||||
`)
|
||||
})
|
||||
|
||||
@@ -464,15 +464,15 @@ describe("transformCss", () => {
|
||||
const input = `@import "tailwindcss";`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
"@plugin tailwindcss/plugin": {},
|
||||
"@plugin @headlessui/tailwindcss": {},
|
||||
'@plugin "tailwindcss/plugin"': {},
|
||||
'@plugin "@headlessui/tailwindcss"': {},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
|
||||
@plugin tailwindcss/plugin;
|
||||
@plugin @headlessui/tailwindcss;"
|
||||
@plugin \"tailwindcss/plugin\";
|
||||
@plugin \"@headlessui/tailwindcss\";"
|
||||
`)
|
||||
})
|
||||
|
||||
@@ -481,16 +481,16 @@ describe("transformCss", () => {
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
"@plugin foo": {},
|
||||
"@plugin bar": {},
|
||||
'@plugin "foo"': {},
|
||||
'@plugin "bar"': {},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");
|
||||
|
||||
@plugin foo;
|
||||
@plugin bar;"
|
||||
@plugin \"foo\";
|
||||
@plugin \"bar\";"
|
||||
`)
|
||||
})
|
||||
|
||||
@@ -498,33 +498,271 @@ describe("transformCss", () => {
|
||||
const input = ``
|
||||
|
||||
const result = await transformCss(input, {
|
||||
"@plugin foo": {},
|
||||
'@plugin "foo"': {},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"
|
||||
@plugin foo"
|
||||
@plugin \"foo\""
|
||||
`)
|
||||
})
|
||||
|
||||
test("should maintain plugin order with existing plugins", async () => {
|
||||
const input = `@import "tailwindcss";
|
||||
|
||||
@plugin existing-plugin;
|
||||
@plugin another-existing;`
|
||||
@plugin "existing-plugin";
|
||||
@plugin "another-existing";`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
"@plugin new-plugin": {},
|
||||
"@plugin final-plugin": {},
|
||||
'@plugin "new-plugin"': {},
|
||||
'@plugin "final-plugin"': {},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
|
||||
@plugin existing-plugin;
|
||||
@plugin another-existing;
|
||||
@plugin new-plugin;
|
||||
@plugin final-plugin;"
|
||||
@plugin \"existing-plugin\";
|
||||
@plugin \"another-existing\";
|
||||
@plugin \"new-plugin\";
|
||||
@plugin \"final-plugin\";"
|
||||
`)
|
||||
})
|
||||
|
||||
test("should automatically add quotes to unquoted plugin names", async () => {
|
||||
const input = `@import "tailwindcss";`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
"@plugin foo-bar": {},
|
||||
"@plugin baz": {},
|
||||
"@plugin @tailwindcss/typography": {},
|
||||
"@plugin ./custom-plugin.js": {},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
|
||||
@plugin "foo-bar";
|
||||
@plugin "baz";
|
||||
@plugin "@tailwindcss/typography";
|
||||
@plugin "./custom-plugin.js";"
|
||||
`)
|
||||
})
|
||||
|
||||
test("should detect duplicate plugins regardless of quotes", async () => {
|
||||
const input = `@import "tailwindcss";
|
||||
|
||||
@plugin foo;
|
||||
@plugin "bar";`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
"@plugin foo": {}, // Should detect this as duplicate
|
||||
"@plugin bar": {}, // Should detect this as duplicate
|
||||
"@plugin baz": {}, // Should add this one
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
|
||||
@plugin foo;
|
||||
@plugin "bar";
|
||||
@plugin "baz";"
|
||||
`)
|
||||
})
|
||||
|
||||
test("should not double-quote already quoted plugin names", async () => {
|
||||
const input = `@import "tailwindcss";`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
'@plugin "already-quoted"': {},
|
||||
"@plugin 'single-quoted'": {},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
|
||||
@plugin "already-quoted";
|
||||
@plugin 'single-quoted';"
|
||||
`)
|
||||
})
|
||||
|
||||
test("should add @import statements", async () => {
|
||||
const input = ``
|
||||
|
||||
const result = await transformCss(input, {
|
||||
'@import "tailwindcss"': {},
|
||||
'@import "./styles/base.css"': {},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import \"tailwindcss\";
|
||||
@import \"./styles/base.css\";"
|
||||
`)
|
||||
})
|
||||
|
||||
test("should add @import with url() syntax", async () => {
|
||||
const input = `@import "tailwindcss";`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
'@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap")':
|
||||
{},
|
||||
"@import url('./local-styles.css')": {},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");
|
||||
@import url('./local-styles.css');"
|
||||
`)
|
||||
})
|
||||
|
||||
test("should not duplicate existing @import statements", async () => {
|
||||
const input = `@import "tailwindcss";
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");
|
||||
@import "./styles/base.css";`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
'@import "tailwindcss"': {}, // Should not duplicate
|
||||
'@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap")':
|
||||
{}, // Should not duplicate
|
||||
'@import "./styles/base.css"': {}, // Should not duplicate
|
||||
'@import "./styles/new.css"': {}, // Should add this one
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");
|
||||
@import "./styles/base.css";
|
||||
@import "./styles/new.css";"
|
||||
`)
|
||||
})
|
||||
|
||||
test("should handle @import with media queries", async () => {
|
||||
const input = `@import "tailwindcss";`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
'@import "print-styles.css" print': {},
|
||||
'@import url("mobile.css") screen and (max-width: 768px)': {},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
@import "print-styles.css" print;
|
||||
@import url("mobile.css") screen and (max-width: 768px);"
|
||||
`)
|
||||
})
|
||||
|
||||
test("should place imports before plugins and other content", async () => {
|
||||
const input = `@layer base {
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
}`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
'@import "tailwindcss"': {},
|
||||
'@plugin "foo"': {},
|
||||
"@layer components": {
|
||||
".card": {
|
||||
padding: "1rem",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
|
||||
@plugin "foo";
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test("should handle @apply within rules", async () => {
|
||||
const input = `@import "tailwindcss";`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
"@layer base": {
|
||||
"*": {
|
||||
"@apply border-border outline-ring/70": {},
|
||||
},
|
||||
h1: {
|
||||
"@apply text-2xl font-bold": {},
|
||||
color: "red",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"@import "tailwindcss";
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/70;
|
||||
}
|
||||
h1 {
|
||||
@apply text-2xl font-bold;
|
||||
color: red;
|
||||
}
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test("should handle at-rules with empty body", async () => {
|
||||
const input = ``
|
||||
|
||||
const result = await transformCss(input, {
|
||||
"@tailwind base": {},
|
||||
"@tailwind components": {},
|
||||
"@tailwind utilities": {},
|
||||
"@layer components": {
|
||||
".btn": {
|
||||
"@apply px-4 py-2 rounded": {},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"
|
||||
@tailwind base;
|
||||
|
||||
@tailwind components;
|
||||
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.btn {
|
||||
@apply px-4 py-2 rounded;
|
||||
}
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test("should handle empty CSS rules", async () => {
|
||||
const input = ``
|
||||
|
||||
const result = await transformCss(input, {
|
||||
".empty-rule": {},
|
||||
".rule-with-content": {
|
||||
padding: "1rem",
|
||||
},
|
||||
})
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"
|
||||
.empty-rule {}
|
||||
.rule-with-content {
|
||||
padding: 1rem;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
@@ -548,8 +786,8 @@ describe("transformCss", () => {
|
||||
}`
|
||||
|
||||
const result = await transformCss(input, {
|
||||
"@plugin @tailwindcss/typography": {},
|
||||
"@plugin ./custom": {},
|
||||
'@plugin "@tailwindcss/typography"': {},
|
||||
'@plugin "./custom"': {},
|
||||
"@layer components": {
|
||||
".btn": {
|
||||
padding: "0.5rem 1rem",
|
||||
@@ -571,8 +809,8 @@ describe("transformCss", () => {
|
||||
"@import "tailwindcss";
|
||||
@import url("fonts.css");
|
||||
|
||||
@plugin @tailwindcss/typography;
|
||||
@plugin ./custom;
|
||||
@plugin \"@tailwindcss/typography\";
|
||||
@plugin \"./custom\";
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
|
||||
@@ -9,6 +9,7 @@ export default defineConfig({
|
||||
"**/fixtures/**",
|
||||
"**/templates/**",
|
||||
],
|
||||
testTimeout: 8000,
|
||||
},
|
||||
plugins: [
|
||||
tsconfigPaths({
|
||||
|
||||
@@ -556,9 +556,13 @@ describe("registries", () => {
|
||||
"@two": "http://localhost:5555/registry/{name}",
|
||||
})
|
||||
|
||||
const output = await npxShadcn(fixturePath, ["add", "@two/one", "@one/foo"])
|
||||
const output = await npxShadcn(fixturePath, [
|
||||
"add",
|
||||
"@two/one",
|
||||
"@foobar/foo",
|
||||
])
|
||||
|
||||
expect(output.stdout).toContain('Unknown registry "@one"')
|
||||
expect(output.stdout).toContain('Unknown registry "@foobar"')
|
||||
})
|
||||
|
||||
it("should show an error when authentication is not configured", async () => {
|
||||
|
||||
@@ -13,7 +13,18 @@ export async function createRegistryServer(
|
||||
}
|
||||
) {
|
||||
const server = createServer((request, response) => {
|
||||
const urlWithoutQuery = request.url?.split("?")[0]?.replace(/\.json$/, "")
|
||||
const urlRaw = request.url?.split("?")[0]
|
||||
|
||||
// Handle registries.json endpoint (don't strip .json for this one)
|
||||
if (urlRaw?.endsWith("/registries.json")) {
|
||||
response.writeHead(200, { "Content-Type": "application/json" })
|
||||
// Return empty registry index for tests - we want to test manual configuration.
|
||||
response.end(JSON.stringify({}))
|
||||
return
|
||||
}
|
||||
|
||||
// For other endpoints, strip .json extension
|
||||
const urlWithoutQuery = urlRaw?.replace(/\.json$/, "")
|
||||
|
||||
if (urlWithoutQuery?.includes("icons/index")) {
|
||||
response.writeHead(200, { "Content-Type": "application/json" })
|
||||
|
||||
4
pnpm-lock.yaml
generated
4
pnpm-lock.yaml
generated
@@ -325,7 +325,7 @@ importers:
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1
|
||||
shadcn:
|
||||
specifier: 3.0.0
|
||||
specifier: 3.3.0
|
||||
version: link:../../packages/shadcn
|
||||
shiki:
|
||||
specifier: ^1.10.1
|
||||
@@ -605,7 +605,7 @@ importers:
|
||||
specifier: 2.12.7
|
||||
version: 2.12.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
shadcn:
|
||||
specifier: 3.0.0
|
||||
specifier: 3.3.0
|
||||
version: link:../../packages/shadcn
|
||||
sharp:
|
||||
specifier: ^0.32.6
|
||||
|
||||
1
registries.json
Symbolic link
1
registries.json
Symbolic link
@@ -0,0 +1 @@
|
||||
apps/v4/public/r/registries.json
|
||||
Reference in New Issue
Block a user