mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-21 06:41:32 +00:00
Compare commits
28 Commits
shadcn@3.2
...
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 |
@@ -30,9 +30,13 @@ const TOP_LEVEL_SECTIONS = [
|
||||
name: "MCP Server",
|
||||
href: "/docs/mcp",
|
||||
},
|
||||
{
|
||||
name: "Changelog",
|
||||
href: "/docs/changelog",
|
||||
},
|
||||
]
|
||||
const EXCLUDED_SECTIONS = ["installation", "dark-mode"]
|
||||
const EXCLUDED_PAGES = ["/docs"]
|
||||
const EXCLUDED_PAGES = ["/docs", "/docs/changelog"]
|
||||
|
||||
export function DocsSidebar({
|
||||
tree,
|
||||
|
||||
@@ -28,6 +28,10 @@ const TOP_LEVEL_SECTIONS = [
|
||||
name: "MCP Server",
|
||||
href: "/docs/mcp",
|
||||
},
|
||||
{
|
||||
name: "Changelog",
|
||||
href: "/docs/changelog",
|
||||
},
|
||||
]
|
||||
|
||||
export function MobileNav({
|
||||
|
||||
@@ -4,6 +4,24 @@ 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.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -41,7 +41,7 @@ Registry namespaces are prefixed with `@` and provide a way to organize and refe
|
||||
|
||||
## Decentralized Namespace System
|
||||
|
||||
We intentionally designed the namespace system to be decentralized. There is [central registrar](/docs/registry/registrar) for open source namespaces but you are free to create and use any namespace you want.
|
||||
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.
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
"recharts": "2.15.1",
|
||||
"rehype-pretty-code": "^0.14.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"shadcn": "3.2.1",
|
||||
"shadcn": "3.3.0",
|
||||
"shiki": "^1.10.1",
|
||||
"sonner": "^2.0.0",
|
||||
"tailwind-merge": "^3.0.1",
|
||||
|
||||
@@ -1,19 +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",
|
||||
"@magic-ui": "https://magicui.design/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"
|
||||
"@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
File diff suppressed because one or more lines are too long
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
"react-resizable-panels": "^2.0.22",
|
||||
"react-wrap-balancer": "^0.4.1",
|
||||
"recharts": "2.12.7",
|
||||
"shadcn": "3.2.1",
|
||||
"shadcn": "3.3.0",
|
||||
"sharp": "^0.32.6",
|
||||
"sonner": "^1.2.3",
|
||||
"swr": "2.2.6-beta.3",
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "shadcn",
|
||||
"version": "3.2.1",
|
||||
"version": "3.3.0",
|
||||
"description": "Add components to your apps.",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from "@/src/registry/context"
|
||||
import {
|
||||
ConfigParseError,
|
||||
RegistriesIndexParseError,
|
||||
RegistryInvalidNamespaceError,
|
||||
RegistryNotFoundError,
|
||||
RegistryParseError,
|
||||
@@ -277,15 +278,24 @@ export async function getItemTargetPath(
|
||||
)
|
||||
}
|
||||
|
||||
export async function fetchRegistries() {
|
||||
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 {
|
||||
// TODO: Do we want this inside /r?
|
||||
const url = `${REGISTRY_URL}/registries.json`
|
||||
const [data] = await fetchRegistry([url], {
|
||||
useCache: process.env.NODE_ENV !== "development",
|
||||
})
|
||||
return registriesIndexSchema.parse(data)
|
||||
} catch {
|
||||
return null
|
||||
} 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"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from "path"
|
||||
import { fetchRegistries } from "@/src/registry/api"
|
||||
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"
|
||||
@@ -39,7 +39,10 @@ export async function ensureRegistriesInConfig(
|
||||
|
||||
// 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 fetchRegistries()
|
||||
const registryIndex = await getRegistriesIndex({
|
||||
useCache: process.env.NODE_ENV !== "development",
|
||||
})
|
||||
|
||||
if (!registryIndex) {
|
||||
return {
|
||||
config,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@ export default defineConfig({
|
||||
"**/fixtures/**",
|
||||
"**/templates/**",
|
||||
],
|
||||
testTimeout: 8000,
|
||||
},
|
||||
plugins: [
|
||||
tsconfigPaths({
|
||||
|
||||
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.2.1
|
||||
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.2.1
|
||||
specifier: 3.3.0
|
||||
version: link:../../packages/shadcn
|
||||
sharp:
|
||||
specifier: ^0.32.6
|
||||
|
||||
Reference in New Issue
Block a user