Compare commits

..

27 Commits

Author SHA1 Message Date
github-actions[bot]
11267f2fed chore(release): version packages (#7228)
* chore(release): version packages

* deps: update pnpm lock

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-04-23 16:28:59 +04:00
Richard Szalay
9ad24d6a16 chore(shadcn): add update-dependencies tests (#7067) 2025-04-22 15:20:00 +04:00
shadcn
e8468793fc chore: temporarily move tw-animate-css to dependencies 2025-04-22 05:07:56 +04:00
迷渡
6f702f5fbf fix: Add npm: specifier when install dependencies with Deno (#6899)
* fix: Add `npm:` specifier when install dependencies with Deno

* chore: changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-04-21 23:36:36 +04:00
shadcn
d0306774fe feat(shadcn): resolve imports from anywhere (#7220)
* feat(shadcn): resolve imports from anywhere

* fix: type errors

* fix: add debug

* feat: handle root paths

* fix: src prefix

* fix: tests

* chore: changeset
2025-04-19 13:31:04 +04:00
Neeraj Dalal
f1e5cc4666 🔥 feat(fix): mark "tw-animate-css" as devDep (#6985)
* chore: tweaks to build-registry.mts ~ 1 file 2+ 5-

apps/v4/scripts/build-registry.mts

* chore: tweaks to index.json ~ 1 file 4+ 9-

apps/www/public/r/styles/new-york-v4/index.json

* chore: tweaks to registry.json ~ 1 file 221+ 850-

apps/v4/registry.json

* chore: tweaks to index.json ~ 1 file 47+ 144-

apps/www/public/r/index.json

* chore: tweaks to registry.json ~ 1 file 852+ 221-

apps/v4/registry.json

* chore: tweaks to build-registry.mts ~ 1 file 7+ 2-

apps/v4/scripts/build-registry.mts

* chore: tweaks to build-registry.mts ~ 1 file 2+ 2-

apps/v4/scripts/build-registry.mts

* chore: tweaks to index.json ~ 1 file 11+ 4-

apps/www/public/r/styles/new-york-v4/index.json

* chore: tweaks to index.json ~ 1 file 1+ 1-

apps/www/public/r/index.json

* chore: tweaks to index.json ~ 1 file 143+ 46-

apps/www/public/r/index.json

* chore: tweaks to update-dependencies.ts ~ 1 file 20+

packages/shadcn/src/utils/updaters/update-dependencies.ts

* chore: tweaks to update-dependencies.ts ~ 1 file 1-

packages/shadcn/src/utils/updaters/update-dependencies.ts

* chore: tweaks to update-dependencies.ts ~ 1 file 19-

packages/shadcn/src/utils/updaters/update-dependencies.ts

* chore: tweaks to add-components.ts update-dependencies.ts ~ 2 files 21+ 2-

packages/shadcn/src/utils/add-components.ts
packages/shadcn/src/utils/updaters/update-dependencies.ts

* chore: tweaks to update-dependencies.ts ~ 1 file 29+ 28-

packages/shadcn/src/utils/updaters/update-dependencies.ts

* refactor: remove redundant code

tw-animate-css already has accordion animations

* chore: tweaks > migrate-icons.ts

packages/shadcn/src/migrations/migrate-icons.ts

* fix: formatting > add-components.ts update-dependencies.ts

packages/shadcn/src/utils/add-components.ts
packages/shadcn/src/utils/updaters/update-dependencies.ts

* chore: add changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-04-19 13:05:52 +04:00
Titouan V
e3ca257f6e chore(www): replace shadcn-ui deprecated mentions to shadcn (#7207) 2025-04-18 10:44:46 +04:00
github-actions[bot]
d1a36d3e17 chore(release): version packages (#7198)
* chore(release): version packages

* fix

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-04-15 16:30:10 +04:00
shadcn
617483fe9c fix(shadcn): do not throw for empty dir (#7196)
* fix(shadcn): do not throw for empty dir

* chore: changeset
2025-04-15 15:01:20 +04:00
shadcn
6d2728db2e deps: update 2025-04-07 15:19:36 +04:00
github-actions[bot]
625be136f4 chore(release): version packages (#6505)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-07 15:16:43 +04:00
shadcn
1bd209a4db feat: add oklch colors to themes (#7090)
* wip

* feat: add oklch theme

* fix: keys
2025-03-31 10:33:48 +04:00
shadcn
812e2300f1 fix(shadcn): only run preflight check if url 2025-03-30 05:43:29 +04:00
shadcn
b52fa4559c docs(www): update registry examples 2025-03-28 21:05:55 +04:00
shadcn
2fade2326a feat(shadcn): add css props to schema (#7072)
* feat(shadcn): add css props to schema

* fix: types
2025-03-28 17:48:59 +04:00
dependabot[bot]
754a66061d chore(deps): bump sharp from 0.31.3 to 0.32.6 (#7053)
Bumps [sharp](https://github.com/lovell/sharp) from 0.31.3 to 0.32.6.
- [Release notes](https://github.com/lovell/sharp/releases)
- [Changelog](https://github.com/lovell/sharp/blob/v0.32.6/docs/changelog.md)
- [Commits](https://github.com/lovell/sharp/compare/v0.31.3...v0.32.6)

---
updated-dependencies:
- dependency-name: sharp
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-27 22:24:16 +04:00
dependabot[bot]
cc53c2243e chore(deps): bump next from 15.2.0-canary.33 to 15.2.3 in /apps/v4 (#7049)
Bumps [next](https://github.com/vercel/next.js) from 15.2.0-canary.33 to 15.2.3.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.2.0-canary.33...v15.2.3)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-03-27 22:24:00 +04:00
nicognaw
99fbf4cb77 fix: code block command overflow (#7031) 2025-03-27 18:15:14 +04:00
dependabot[bot]
a82db8395d chore(deps): bump vite from 5.4.14 to 5.4.15 (#7051)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.14 to 5.4.15.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.15/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.15/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-27 18:12:46 +04:00
dependabot[bot]
79eed50e5b chore(deps): bump next from 15.2.0-canary.33 to 15.2.3 (#7052)
Bumps [next](https://github.com/vercel/next.js) from 15.2.0-canary.33 to 15.2.3.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.2.0-canary.33...v15.2.3)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-27 18:12:28 +04:00
dependabot[bot]
799a7f8aa7 chore(deps-dev): bump remark-gfm from 4.0.0 to 4.0.1 (#7048)
Bumps [remark-gfm](https://github.com/remarkjs/remark-gfm) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/remarkjs/remark-gfm/releases)
- [Commits](https://github.com/remarkjs/remark-gfm/compare/4.0.0...4.0.1)

---
updated-dependencies:
- dependency-name: remark-gfm
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-27 17:51:55 +04:00
dependabot[bot]
79a6f54d4e chore(deps): bump @radix-ui/react-scroll-area from 1.2.2 to 1.2.3 (#7045)
Bumps [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives) from 1.2.2 to 1.2.3.
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-scroll-area"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-27 17:51:19 +04:00
dependabot[bot]
26edb8275e chore(deps-dev): bump esbuild from 0.17.19 to 0.25.0 (#7043)
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.17.19 to 0.25.0.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2023.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.17.19...v0.25.0)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-27 17:50:57 +04:00
dependabot[bot]
e15e31e3cb chore(deps): bump vitest from 2.1.8 to 2.1.9 (#7042)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 2.1.8 to 2.1.9.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.9/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-27 17:50:41 +04:00
dependabot[bot]
d4247d52da chore(deps): bump next from 15.2.0 to 15.2.3 in /templates/monorepo-next (#7041)
Bumps [next](https://github.com/vercel/next.js) from 15.2.0 to 15.2.3.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.2.0...v15.2.3)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-27 17:50:22 +04:00
shadcn
9d908ae6ca docs: add security.md 2025-03-27 16:59:19 +04:00
shadcn
883ad8cbc4 chore: create dependabot config 2025-03-27 16:50:16 +04:00
74 changed files with 3805 additions and 1421 deletions

View File

@@ -1,5 +0,0 @@
---
"shadcn": minor
---
add support for TanStack Start

View File

@@ -1,5 +0,0 @@
---
"shadcn": patch
---
support for version detection in monorepo

View File

@@ -1,5 +0,0 @@
---
"shadcn": patch
---
upgrade @antfu/ni

View File

@@ -1,5 +0,0 @@
---
"shadcn": patch
---
allow silent mode with npm

View File

@@ -1,5 +0,0 @@
---
"shadcn": patch
---
do not add ring for v3

View File

@@ -1,5 +0,0 @@
---
"shadcn": minor
---
add theme vars support

View File

@@ -1,5 +0,0 @@
---
"shadcn": minor
---
add tailwind version detection

View File

@@ -1,5 +0,0 @@
---
"shadcn": minor
---
add --base-color flag

View File

@@ -1,5 +0,0 @@
---
"shadcn": minor
---
add support for tailwind v4

View File

@@ -1,5 +0,0 @@
---
"shadcn": minor
---
default to css vars. add --no-css-variables

View File

@@ -1,5 +0,0 @@
---
"shadcn": patch
---
cache registry calls

View File

@@ -1,5 +0,0 @@
---
"shadcn": minor
---
replace tailwindcss-animate with tw-animate-css

View File

@@ -1,5 +0,0 @@
---
"shadcn": patch
---
add --template flag

View File

@@ -1,5 +0,0 @@
---
"shadcn": minor
---
default for new-york for v4

View File

@@ -1,5 +0,0 @@
---
"shadcn": minor
---
fix handling of sidebar colors

View File

@@ -1,5 +0,0 @@
---
"shadcn": patch
---
do not overwrite user defined vars

View File

@@ -1,5 +0,0 @@
---
"shadcn": patch
---
fix cn import bug in monorepo

View File

@@ -1,5 +0,0 @@
---
"shadcn": patch
---
filter out deprecated from --all

View File

@@ -1,5 +0,0 @@
---
"shadcn": minor
---
add oklch base color

View File

@@ -1,5 +0,0 @@
---
"shadcn": minor
---
hotswap style for v4

View File

@@ -1,5 +0,0 @@
---
"shadcn": patch
---
check for empty css vars

View File

@@ -1,5 +0,0 @@
---
"shadcn": patch
---
only show deprecated for new projects

View File

@@ -1,5 +0,0 @@
---
"shadcn": minor
---
add warning for deprecated components

View File

@@ -1,5 +0,0 @@
---
"shadcn": patch
---
fix tanstack check

View File

@@ -1,5 +0,0 @@
---
"shadcn": minor
---
add support for route install for react-router and laravel

View File

@@ -1,5 +0,0 @@
---
"shadcn": minor
---
add theme prop to registry-item schema

6
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"

9
SECURITY.md Normal file
View File

@@ -0,0 +1,9 @@
# Security Policy
If you believe you have found a security vulnerability, we encourage you to let us know right away.
We will investigate all legitimate reports and do our best to quickly fix the problem.
Our preference is that you make use of GitHub's private vulnerability reporting feature to disclose potential security vulnerabilities in our Open Source Software.
To do this, please visit the security tab of the repository and click the "Report a vulnerability" button.

View File

@@ -39,7 +39,7 @@
"@radix-ui/react-portal": "^1.1.3",
"@radix-ui/react-progress": "^1.1.1",
"@radix-ui/react-radio-group": "^1.2.2",
"@radix-ui/react-scroll-area": "^1.2.2",
"@radix-ui/react-scroll-area": "^1.2.3",
"@radix-ui/react-select": "^2.1.5",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slider": "^1.2.2",
@@ -63,7 +63,7 @@
"geist": "^1.2.2",
"input-otp": "^1.4.2",
"lucide-react": "0.474.0",
"next": "15.2.0-canary.33",
"next": "15.2.3",
"next-themes": "^0.4.3",
"postcss": "^8.5.1",
"react": "^19.0.0",
@@ -73,7 +73,7 @@
"react-resizable-panels": "^2.1.7",
"recharts": "2.15.1",
"rimraf": "^6.0.1",
"shadcn": "2.3.0",
"shadcn": "2.5.0",
"sonner": "^2.0.0",
"tailwind-merge": "^3.0.1",
"tailwindcss": "^4.0.7",

View File

@@ -6,9 +6,9 @@
"name": "index",
"type": "registry:style",
"dependencies": [
"tw-animate-css",
"class-variance-authority",
"lucide-react"
"lucide-react",
"tw-animate-css"
],
"registryDependencies": [
"utils"
@@ -27,37 +27,7 @@
"path": "registry/new-york-v4/ui/accordion.tsx",
"type": "registry:ui"
}
],
"tailwind": {
"config": {
"theme": {
"extend": {
"keyframes": {
"accordion-down": {
"from": {
"height": "0"
},
"to": {
"height": "var(--radix-accordion-content-height)"
}
},
"accordion-up": {
"from": {
"height": "var(--radix-accordion-content-height)"
},
"to": {
"height": "0"
}
}
},
"animation": {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out"
}
}
}
}
}
]
},
{
"name": "alert",
@@ -2833,4 +2803,4 @@
]
}
]
}
}

View File

@@ -21,9 +21,9 @@ const registry = {
name: "index",
type: "registry:style",
dependencies: [
"tw-animate-css",
"class-variance-authority",
"lucide-react",
"tw-animate-css",
],
registryDependencies: ["utils"],
cssVars: {},

View File

@@ -7,7 +7,8 @@ import {
PageHeaderDescription,
PageHeaderHeading,
} from "@/components/page-header"
import { ThemeCustomizer } from "@/components/theme-customizer"
import { Customizer } from "@/components/theme-customizer"
import { Button } from "@/registry/new-york/ui/button"
const title = "Add colors. Make it yours."
const description =
@@ -48,10 +49,17 @@ export default function ThemesLayout({
<Announcement />
<PageHeaderHeading>{title}</PageHeaderHeading>
<PageHeaderDescription>{description}</PageHeaderDescription>
<PageActions>
<ThemeCustomizer />
</PageActions>
<div className="mt-2 rounded-full bg-blue-600 px-3 py-1 text-xs text-white">
New Theme Editor coming soon
</div>
</PageHeader>
<div id="themes" className="border-grid scroll-mt-24 border-b">
<div className="container-wrapper">
<div className="container flex items-center py-4">
<Customizer />
</div>
</div>
</div>
<div className="container-wrapper">
<div className="container py-6">
<section id="themes" className="scroll-mt-20">

View File

@@ -79,20 +79,22 @@ export function CodeBlockCommand({
})}
</TabsList>
</div>
{Object.entries(tabs).map(([key, value]) => {
return (
<TabsContent key={key} value={key} className="mt-0">
<pre className="px-4 py-5">
<code
className="relative font-mono text-sm leading-none"
data-language="bash"
>
{value}
</code>
</pre>
</TabsContent>
)
})}
<div className="overflow-x-auto">
{Object.entries(tabs).map(([key, value]) => {
return (
<TabsContent key={key} value={key} className="mt-0">
<pre className="px-4 py-5">
<code
className="relative font-mono text-sm leading-none"
data-language="bash"
>
{value}
</code>
</pre>
</TabsContent>
)
})}
</div>
</Tabs>
<Button
size="icon"

View File

@@ -2,7 +2,7 @@
import * as React from "react"
import template from "lodash/template"
import { Check, Copy, Moon, Repeat, Sun } from "lucide-react"
import { Check, ClipboardIcon, Copy } from "lucide-react"
import { useTheme } from "next-themes"
import { cn } from "@/lib/utils"
@@ -21,6 +21,9 @@ import {
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/registry/new-york/ui/drawer"
import { Label } from "@/registry/new-york/ui/label"
@@ -29,15 +32,28 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york/ui/popover"
import { Separator } from "@/registry/new-york/ui/separator"
import { Skeleton } from "@/registry/new-york/ui/skeleton"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york/ui/tooltip"
import { BaseColor, baseColors } from "@/registry/registry-base-colors"
BaseColor,
baseColors,
baseColorsOKLCH,
} from "@/registry/registry-base-colors"
import "@/styles/mdx.css"
import { toast } from "sonner"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/new-york/ui/tabs"
interface BaseColorOKLCH {
light: Record<string, string>
dark: Record<string, string>
}
export function ThemeCustomizer() {
const [config, setConfig] = useConfig()
@@ -78,9 +94,9 @@ export function ThemeCustomizer() {
)
}
function Customizer() {
export function Customizer() {
const [mounted, setMounted] = React.useState(false)
const { setTheme: setMode, resolvedTheme: mode } = useTheme()
const { resolvedTheme: mode } = useTheme()
const [config, setConfig] = useConfig()
React.useEffect(() => {
@@ -88,39 +104,11 @@ function Customizer() {
}, [])
return (
<ThemeWrapper
defaultTheme="zinc"
className="flex flex-col space-y-4 md:space-y-6"
>
<div className="flex items-start pt-4 md:pt-0">
<div className="space-y-1 pr-2">
<div className="font-semibold leading-none tracking-tight">
Theme Customizer
</div>
<div className="text-xs text-muted-foreground">
Customize your components colors.
</div>
</div>
<Button
variant="ghost"
size="icon"
className="ml-auto rounded-[0.5rem]"
onClick={() => {
setConfig({
...config,
theme: "zinc",
radius: 0.5,
})
}}
>
<Repeat />
<span className="sr-only">Reset</span>
</Button>
</div>
<div className="flex flex-1 flex-col space-y-4 md:space-y-6">
<div className="space-y-1.5">
<Label className="text-xs">Color</Label>
<div className="grid grid-cols-3 gap-2">
<ThemeWrapper defaultTheme="zinc">
<div className="grid w-full flex-1 grid-cols-2 flex-wrap items-start gap-2 sm:flex sm:items-center md:gap-6">
<div className="flex flex-col gap-2">
<Label className="sr-only text-xs">Color</Label>
<div className="flex flex-wrap gap-1 md:gap-2">
{baseColors
.filter(
(theme) =>
@@ -131,7 +119,7 @@ function Customizer() {
return mounted ? (
<Button
variant={"outline"}
variant="outline"
size="sm"
key={theme.name}
onClick={() => {
@@ -141,8 +129,8 @@ function Customizer() {
})
}}
className={cn(
"justify-start",
isActive && "border-2 border-primary"
"w-[32px] rounded-lg lg:px-2.5 xl:w-[86px]",
isActive && "border-primary/50 ring-[2px] ring-primary/30"
)}
style={
{
@@ -154,22 +142,28 @@ function Customizer() {
>
<span
className={cn(
"mr-1 flex h-5 w-5 shrink-0 -translate-x-1 items-center justify-center rounded-full bg-[--theme-primary]"
"flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-[--theme-primary]"
)}
>
{isActive && <Check className="h-4 w-4 text-white" />}
{isActive && <Check className="!size-2.5 text-white" />}
</span>
<span className="hidden xl:block">
{theme.label === "Zinc" ? "Default" : theme.label}
</span>
{theme.label}
</Button>
) : (
<Skeleton className="h-8 w-full" key={theme.name} />
<Skeleton
className="h-8 w-[32px] xl:w-[86px]"
key={theme.name}
/>
)
})}
</div>
</div>
<div className="space-y-1.5">
<Label className="text-xs">Radius</Label>
<div className="grid grid-cols-5 gap-2">
<Separator orientation="vertical" className="hidden h-6 sm:block" />
<div className="flex flex-col gap-2">
<Label className="sr-only text-xs">Radius</Label>
<div className="flex flex-wrap gap-1 md:gap-2">
{["0", "0.3", "0.5", "0.75", "1.0"].map((value) => {
return (
<Button
@@ -183,8 +177,9 @@ function Customizer() {
})
}}
className={cn(
"w-[40px] rounded-lg",
config.radius === parseFloat(value) &&
"border-2 border-primary"
"border-primary/50 ring-[2px] ring-primary/30"
)}
>
{value}
@@ -193,81 +188,50 @@ function Customizer() {
})}
</div>
</div>
<div className="space-y-1.5">
<Label className="text-xs">Mode</Label>
<div className="grid grid-cols-3 gap-2">
{mounted ? (
<>
<Button
variant={"outline"}
size="sm"
onClick={() => setMode("light")}
className={cn(mode === "light" && "border-2 border-primary")}
>
<Sun className="mr-1 -translate-x-1" />
Light
</Button>
<Button
variant={"outline"}
size="sm"
onClick={() => setMode("dark")}
className={cn(mode === "dark" && "border-2 border-primary")}
>
<Moon className="mr-1 -translate-x-1" />
Dark
</Button>
</>
) : (
<>
<Skeleton className="h-8 w-full" />
<Skeleton className="h-8 w-full" />
</>
)}
</div>
<div className="flex gap-2 sm:ml-auto">
<CopyCodeButton />
</div>
</div>
</ThemeWrapper>
)
}
function CopyCodeButton({
export function CopyCodeButton({
className,
...props
}: React.ComponentProps<typeof Button>) {
const [config] = useConfig()
const activeTheme = baseColors.find((theme) => theme.name === config.theme)
const [hasCopied, setHasCopied] = React.useState(false)
React.useEffect(() => {
setTimeout(() => {
setHasCopied(false)
}, 2000)
}, [hasCopied])
return (
<>
{activeTheme && (
<Button
onClick={() => {
copyToClipboardWithMeta(getThemeCode(activeTheme, config.radius), {
name: "copy_theme_code",
properties: {
theme: activeTheme.name,
radius: config.radius,
},
})
setHasCopied(true)
}}
className={cn("md:hidden", className)}
{...props}
>
{hasCopied ? <Check /> : <Copy />}
Copy code
</Button>
)}
<Drawer>
<DrawerTrigger asChild>
<Button
className={cn("h-8 rounded-lg shadow-none sm:hidden", className)}
{...props}
>
Copy
</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Theme</DrawerTitle>
<DrawerDescription>
Copy and paste the following code into your CSS file.
</DrawerDescription>
</DrawerHeader>
<ThemeWrapper defaultTheme="zinc" className="relative px-6">
<CustomizerCode />
</ThemeWrapper>
</DrawerContent>
</Drawer>
<Dialog>
<DialogTrigger asChild>
<Button className={cn("hidden md:flex", className)} {...props}>
<Button
className={cn(
"hidden h-8 rounded-lg shadow-none sm:flex",
className
)}
{...props}
>
Copy code
</Button>
</DialogTrigger>
@@ -280,28 +244,6 @@ function CopyCodeButton({
</DialogHeader>
<ThemeWrapper defaultTheme="zinc" className="relative">
<CustomizerCode />
{activeTheme && (
<Button
size="sm"
onClick={() => {
copyToClipboardWithMeta(
getThemeCode(activeTheme, config.radius),
{
name: "copy_theme_code",
properties: {
theme: activeTheme.name,
radius: config.radius,
},
}
)
setHasCopied(true)
}}
className="absolute right-4 top-4 bg-muted text-muted-foreground hover:bg-muted hover:text-muted-foreground"
>
{hasCopied ? <Check /> : <Copy />}
Copy
</Button>
)}
</ThemeWrapper>
</DialogContent>
</Dialog>
@@ -311,168 +253,269 @@ function CopyCodeButton({
function CustomizerCode() {
const [config] = useConfig()
const activeTheme = baseColors.find((theme) => theme.name === config.theme)
const [hasCopied, setHasCopied] = React.useState(false)
const [themeVersion, setThemeVersion] = React.useState("v4")
const activeTheme = React.useMemo(
() => baseColors.find((theme) => theme.name === config.theme),
[config.theme]
)
const activeThemeOKLCH = React.useMemo(
() => baseColorsOKLCH[config.theme as keyof typeof baseColorsOKLCH],
[config.theme]
)
React.useEffect(() => {
if (hasCopied) {
setTimeout(() => {
setHasCopied(false)
}, 2000)
}
}, [hasCopied])
return (
<ThemeWrapper defaultTheme="zinc" className="relative space-y-4">
<div data-rehype-pretty-code-fragment="">
<pre className="max-h-[450px] overflow-x-auto rounded-lg border bg-zinc-950 py-4 dark:bg-zinc-900">
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm">
<span className="line text-white">@layer base &#123;</span>
<span className="line text-white">&nbsp;&nbsp;:root &#123;</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--background:{" "}
{activeTheme?.cssVars.light["background"]};
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--foreground:{" "}
{activeTheme?.cssVars.light["foreground"]};
</span>
{[
"card",
"popover",
"primary",
"secondary",
"muted",
"accent",
"destructive",
].map((prefix) => (
<>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
]
}
;
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}-foreground:{" "}
{
activeTheme?.cssVars.light[
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.light
]
}
;
</span>
</>
))}
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--border:{" "}
{activeTheme?.cssVars.light["border"]};
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--input:{" "}
{activeTheme?.cssVars.light["input"]};
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--ring:{" "}
{activeTheme?.cssVars.light["ring"]};
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--radius: {config.radius}rem;
</span>
{["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"].map(
(prefix) => (
<>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
]
}
;
</span>
</>
<Tabs value={themeVersion} onValueChange={setThemeVersion}>
<div className="flex items-center justify-between">
<TabsList>
<TabsTrigger value="v4">Tailwind v4</TabsTrigger>
<TabsTrigger value="v3">v3</TabsTrigger>
</TabsList>
<Button
size="sm"
variant="outline"
onClick={() => {
copyToClipboardWithMeta(
themeVersion === "v3"
? getThemeCode(activeTheme, config.radius)
: getThemeCodeOKLCH(activeThemeOKLCH, config.radius),
{
name: "copy_theme_code",
properties: {
theme: config.theme,
radius: config.radius,
},
}
)
)}
<span className="line text-white">&nbsp;&nbsp;&#125;</span>
<span className="line text-white">&nbsp;</span>
<span className="line text-white">&nbsp;&nbsp;.dark &#123;</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--background:{" "}
{activeTheme?.cssVars.dark["background"]};
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--foreground:{" "}
{activeTheme?.cssVars.dark["foreground"]};
</span>
{[
"card",
"popover",
"primary",
"secondary",
"muted",
"accent",
"destructive",
].map((prefix) => (
<>
setHasCopied(true)
}}
className="absolute right-0 top-0 shadow-none"
>
{hasCopied ? <Check /> : <ClipboardIcon />}
Copy
</Button>
</div>
<TabsContent value="v4">
<div data-rehype-pretty-code-fragment="">
<pre className="max-h-[450px] overflow-x-auto rounded-lg border bg-zinc-950 py-4 dark:bg-zinc-900">
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm">
<span className="line text-white">&nbsp;:root &#123;</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
]
}
;
&nbsp;&nbsp;&nbsp;--radius: {config.radius}rem;
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}-foreground:{" "}
{
activeTheme?.cssVars.dark[
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.dark
]
}
;
</span>
</>
))}
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--border:{" "}
{activeTheme?.cssVars.dark["border"]};
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--input:{" "}
{activeTheme?.cssVars.dark["input"]};
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--ring:{" "}
{activeTheme?.cssVars.dark["ring"]};
</span>
{["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"].map(
(prefix) => (
<>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
]
}
;
{Object.entries(activeThemeOKLCH?.light).map(([key, value]) => (
<span className="line text-white" key={key}>
&nbsp;&nbsp;&nbsp;--{key}: {value};
</span>
</>
)
)}
<span className="line text-white">&nbsp;&nbsp;&#125;</span>
<span className="line text-white">&#125;</span>
</code>
</pre>
</div>
))}
<span className="line text-white">&nbsp;&#125;</span>
<span className="line text-white">&nbsp;</span>
<span className="line text-white">&nbsp;.dark &#123;</span>
{Object.entries(activeThemeOKLCH?.dark).map(([key, value]) => (
<span className="line text-white" key={key}>
&nbsp;&nbsp;&nbsp;--{key}: {value};
</span>
))}
<span className="line text-white">&nbsp;&#125;</span>
</code>
</pre>
</div>
</TabsContent>
<TabsContent value="v3">
<div data-rehype-pretty-code-fragment="">
<pre className="max-h-[450px] overflow-x-auto rounded-lg border bg-zinc-950 py-4 dark:bg-zinc-900">
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm">
<span className="line text-white">@layer base &#123;</span>
<span className="line text-white">
&nbsp;&nbsp;:root &#123;
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--background:{" "}
{activeTheme?.cssVars.light["background"]};
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--foreground:{" "}
{activeTheme?.cssVars.light["foreground"]};
</span>
{[
"card",
"popover",
"primary",
"secondary",
"muted",
"accent",
"destructive",
].map((prefix) => (
<>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
]
}
;
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}-foreground:{" "}
{
activeTheme?.cssVars.light[
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.light
]
}
;
</span>
</>
))}
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--border:{" "}
{activeTheme?.cssVars.light["border"]};
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--input:{" "}
{activeTheme?.cssVars.light["input"]};
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--ring:{" "}
{activeTheme?.cssVars.light["ring"]};
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--radius: {config.radius}rem;
</span>
{["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"].map(
(prefix) => (
<>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
]
}
;
</span>
</>
)
)}
<span className="line text-white">&nbsp;&nbsp;&#125;</span>
<span className="line text-white">&nbsp;</span>
<span className="line text-white">
&nbsp;&nbsp;.dark &#123;
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--background:{" "}
{activeTheme?.cssVars.dark["background"]};
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--foreground:{" "}
{activeTheme?.cssVars.dark["foreground"]};
</span>
{[
"card",
"popover",
"primary",
"secondary",
"muted",
"accent",
"destructive",
].map((prefix) => (
<>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
]
}
;
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}-foreground:{" "}
{
activeTheme?.cssVars.dark[
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.dark
]
}
;
</span>
</>
))}
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--border:{" "}
{activeTheme?.cssVars.dark["border"]};
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--input:{" "}
{activeTheme?.cssVars.dark["input"]};
</span>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--ring:{" "}
{activeTheme?.cssVars.dark["ring"]};
</span>
{["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"].map(
(prefix) => (
<>
<span className="line text-white">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
{
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
]
}
;
</span>
</>
)
)}
<span className="line text-white">&nbsp;&nbsp;&#125;</span>
<span className="line text-white">&#125;</span>
</code>
</pre>
</div>
</TabsContent>
</Tabs>
</ThemeWrapper>
)
}
function getThemeCode(theme: BaseColor, radius: number) {
function getThemeCodeOKLCH(theme: BaseColorOKLCH | undefined, radius: number) {
if (!theme) {
return ""
}
const rootSection =
":root {\n --radius: " +
radius +
"rem;\n" +
Object.entries(theme.light)
.map((entry) => " --" + entry[0] + ": " + entry[1] + ";")
.join("\n") +
"\n}\n\n.dark {\n" +
Object.entries(theme.dark)
.map((entry) => " --" + entry[0] + ": " + entry[1] + ";")
.join("\n") +
"\n}\n"
return rootSection
}
function getThemeCode(theme: BaseColor | undefined, radius: number) {
if (!theme) {
return ""
}
return template(BASE_STYLES_WITH_VARIABLES)({
colors: theme.cssVars,
radius,
radius: radius.toString(),
})
}

View File

@@ -433,6 +433,11 @@ export const docsConfig: DocsConfig = {
href: "/docs/registry/getting-started",
items: [],
},
{
title: "Examples",
href: "/docs/registry/examples",
items: [],
},
{
title: "Open in v0",
href: "/docs/registry/open-in-v0",

View File

@@ -321,7 +321,7 @@ I've been working on a new CLI for the past few weeks. It's a complete rewrite.
### `init`
```bash
npx shadcn-ui@latest init
npx shadcn@latest init
```
When you run the `init` command, you will be asked a few questions to configure `components.json`:
@@ -363,7 +363,7 @@ This means you can now use the CLI with any directory structure including `src`
### `add`
```bash
npx shadcn-ui@latest add
npx shadcn@latest add
```
The `add` command is now much more capable. You can now add UI components but also import more complex components (coming soon).
@@ -373,7 +373,7 @@ The CLI will automatically resolve all components and dependencies, format them
### `diff` (experimental)
```bash
npx shadcn-ui diff
npx shadcn diff
```
We're also introducing a new `diff` command to help you keep track of upstream updates.
@@ -383,7 +383,7 @@ You can use this command to see what has changed in the upstream repository and
Run the `diff` command to get a list of components that have updates available:
```bash
npx shadcn-ui diff
npx shadcn diff
```
```txt
@@ -398,7 +398,7 @@ The following components have updates available:
Then run `diff [component]` to see the changes:
```bash
npx shadcn-ui diff alert
npx shadcn diff alert
```
```diff /pl-12/

View File

@@ -79,7 +79,7 @@ export const onCreateWebpackConfig = ({ actions }) => {
### Run the CLI
Run the `shadcn-ui` init command to setup your project:
Run the `shadcn` init command to setup your project:
```bash
npx shadcn@latest init

View File

@@ -13,7 +13,7 @@ npx create-react-router@latest my-app
### Run the CLI
Run the `shadcn-ui` init command to setup your project:
Run the `shadcn` init command to setup your project:
```bash
npx shadcn@latest init

View File

@@ -26,7 +26,7 @@ npx create-remix@latest my-app
### Run the CLI
Run the `shadcn-ui` init command to setup your project:
Run the `shadcn` init command to setup your project:
```bash
npx shadcn@latest init

View File

@@ -0,0 +1,356 @@
---
title: Examples
description: "Examples of registry items: styles, components, css vars, etc."
---
## registry:style
### Custom style that extends shadcn/ui
The following registry item is a custom style that extends shadcn/ui. On `npx shadcn init`, it will:
- Install `@tabler/icons-react` as a dependency.
- Add the `login-01` block and `calendar` component to the project.
- Add the `editor` from a remote registry.
- Set the `font-sans` variable to `Inter, sans-serif`.
- Install a `brand` color in light and dark mode.
```json title="example-style.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "example-style",
"type": "registry:style",
"dependencies": ["@tabler/icons-react"],
"registryDependencies": [
"login-01",
"calendar",
"https://example.com/r/editor.json"
],
"cssVars": {
"theme": {
"font-sans": "Inter, sans-serif"
},
"light": {
"brand": "20 14.3% 4.1%"
},
"dark": {
"brand": "20 14.3% 4.1%"
}
}
}
```
### Custom style from scratch
The following registry item is a custom style that doesn't extend shadcn/ui. See the `extends: none` field.
It can be used to create a new style from scratch i.e custom components, css vars, dependencies, etc.
On `npx shadcn add`, the following will:
- Install `tailwind-merge` and `clsx` as dependencies.
- Add the `utils` registry item from the shadcn/ui registry.
- Add the `button`, `input`, `label`, and `select` components from a remote registry.
- Install new css vars: `main`, `bg`, `border`, `text`, `ring`.
```json title="example-style.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"extends": "none",
"name": "new-style",
"type": "registry:style",
"dependencies": ["tailwind-merge", "clsx"],
"registryDependencies": [
"utils",
"https://example.com/r/button.json",
"https://example.com/r/input.json",
"https://example.com/r/label.json",
"https://example.com/r/select.json"
],
"cssVars": {
"theme": {
"font-sans": "Inter, sans-serif",
}
"light": {
"main": "#88aaee",
"bg": "#dfe5f2",
"border": "#000",
"text": "#000",
"ring": "#000",
},
"dark": {
"main": "#88aaee",
"bg": "#272933",
"border": "#000",
"text": "#e6e6e6",
"ring": "#fff",
}
}
}
```
## registry:theme
### Custom theme
```json title="example-theme.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "custom-theme",
"type": "registry:theme",
"cssVars": {
"light": {
"background": "oklch(1 0 0)",
"foreground": "oklch(0.141 0.005 285.823)",
"primary": "oklch(0.546 0.245 262.881)",
"primary-foreground": "oklch(0.97 0.014 254.604)",
"ring": "oklch(0.746 0.16 232.661)",
"sidebar-primary": "oklch(0.546 0.245 262.881)",
"sidebar-primary-foreground": "oklch(0.97 0.014 254.604)",
"sidebar-ring": "oklch(0.746 0.16 232.661)"
},
"dark": {
"background": "oklch(1 0 0)",
"foreground": "oklch(0.141 0.005 285.823)",
"primary": "oklch(0.707 0.165 254.624)",
"primary-foreground": "oklch(0.97 0.014 254.604)",
"ring": "oklch(0.707 0.165 254.624)",
"sidebar-primary": "oklch(0.707 0.165 254.624)",
"sidebar-primary-foreground": "oklch(0.97 0.014 254.604)",
"sidebar-ring": "oklch(0.707 0.165 254.624)"
}
}
}
```
### Custom colors
The following style will init using shadcn/ui defaults and then add a custom `brand` color.
```json title="example-style.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "custom-style",
"type": "registry:style",
"cssVars": {
"light": {
"brand": "oklch(0.99 0.00 0)"
},
"dark": {
"brand": "oklch(0.14 0.00 286)"
}
}
}
```
## registry:block
### Custom block
This blocks installs the `login-01` block from the shadcn/ui registry.
```json title="login-01.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "login-01",
"type": "registry:block",
"description": "A simple login form.",
"registryDependencies": ["button", "card", "input", "label"],
"files": [
{
"path": "blocks/login-01/page.tsx",
"content": "import { LoginForm } ...",
"type": "registry:page",
"target": "app/login/page.tsx"
},
{
"path": "blocks/login-01/components/login-form.tsx",
"content": "...",
"type": "registry:component"
}
]
}
```
### Install a block and override primitives
You can install a block fromt the shadcn/ui registry and override the primitives using your custom ones.
On `npx shadcn add`, the following will:
- Add the `login-01` block from the shadcn/ui registry.
- Override the `button`, `input`, and `label` primitives with the ones from the remote registry.
```json title="example-style.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "custom-login",
"type": "registry:block",
"registryDependencies": [
"login-01",
"https://example.com/r/button.json",
"https://example.com/r/input.json",
"https://example.com/r/label.json"
]
}
```
## CSS Variables
### Custom Theme Variables
Add custom theme variables to the `theme` object.
```json title="example-theme.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "custom-theme",
"type": "registry:theme",
"cssVars": {
"theme": {
"font-heading": "Inter, sans-serif",
"shadow-card": "0 0 0 1px rgba(0, 0, 0, 0.1)"
}
}
}
```
### Override Tailwind CSS variables
```json title="example-theme.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "custom-theme",
"type": "registry:theme",
"cssVars": {
"theme": {
"spacing": "0.2rem",
"breakpoint-sm": "640px",
"breakpoint-md": "768px",
"breakpoint-lg": "1024px",
"breakpoint-xl": "1280px",
"breakpoint-2xl": "1536px"
}
}
}
```
## Add custom CSS
### Base styles
```json title="example-base.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "custom-style",
"type": "registry:style",
"css": {
"@layer base": {
"h1": {
"font-size": "var(--text-2xl)"
},
"h2": {
"font-size": "var(--text-xl)"
}
}
}
}
```
### Components
```json title="example-card.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "custom-card",
"type": "registry:component",
"css": {
"@layer components": {
"card": {
"background-color": "var(--color-white)",
"border-radius": "var(--rounded-lg)",
"padding": "var(--spacing-6)",
"box-shadow": "var(--shadow-xl)"
}
}
}
}
```
## Add custom utilities
### Simple utility
```json title="example-component.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "custom-component",
"type": "registry:component",
"css": {
"@utility content-auto": {
"content-visibility": "auto"
}
}
}
```
### Complex utility
```json title="example-utility.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "custom-component",
"type": "registry:component",
"css": {
"@utility scrollbar-hidden": {
"scrollbar-hidden": {
"&::-webkit-scrollbar": {
"display": "none"
}
}
}
}
}
```
### Functional utilities
```json title="example-functional.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "custom-component",
"type": "registry:component",
"css": {
"@utility tab-*": {
"tab-size": "var(--tab-size-*)"
}
}
}
```
## Add custom animations
Note: you need to define both `@keyframes` in css and `theme` in cssVars to use animations.
```json title="example-component.json" showLineNumbers
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "custom-component",
"type": "registry:component",
"cssVars": {
"theme": {
"--animate-wiggle": "wiggle 1s ease-in-out infinite"
}
},
"css": {
"@keyframes wiggle": {
"0%, 100%": {
"transform": "rotate(-3deg)"
},
"50%": {
"transform": "rotate(3deg)"
}
}
}
}
```

View File

@@ -36,6 +36,10 @@ The `registry-item.json` schema is used to define your custom registry items.
}
```
<div className="flex gap-2 items-center mt-6">
<Link href="/docs/registry/examples">See more examples</Link>
</div>
## Definitions
You can see the JSON Schema for `registry-item.json` [here](https://ui.shadcn.com/schema/registry-item.json).
@@ -254,6 +258,41 @@ Use to define CSS variables for your registry item.
}
```
### css
Use `css` to add new rules to the project's CSS file eg. `@layer base`, `@layer components`, `@utility`, `@keyframes`, etc.
```json title="registry-item.json" showLineNumbers
{
"css": {
"@layer base": {
"body": {
"font-size": "var(--text-base)",
"line-height": "1.5"
}
},
"@layer components": {
"button": {
"background-color": "var(--color-primary)",
"color": "var(--color-white)"
}
},
"@utility text-magic": {
"font-size": "var(--text-base)",
"line-height": "1.5"
},
"@keyframes wiggle": {
"0%, 100%": {
"transform": "rotate(-3deg)"
},
"50%": {
"transform": "rotate(3deg)"
}
}
}
}
```
### docs
Use `docs` to show custom documentation or message when installing your registry item via the CLI.

View File

@@ -113,7 +113,7 @@ export default makeSource({
[
rehypePrettyCode,
{
theme: "github-dark",
theme: "github-dark-default",
getHighlighter,
onVisitLine(node) {
// Prevent lines from collapsing in `display: grid` mode, and allow empty

View File

@@ -46,7 +46,7 @@
"@radix-ui/react-portal": "^1.0.4",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.0.4",
"@radix-ui/react-scroll-area": "^1.2.3",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slider": "^1.1.2",
@@ -86,8 +86,8 @@
"react-resizable-panels": "^2.0.22",
"react-wrap-balancer": "^0.4.1",
"recharts": "2.12.7",
"shadcn": "2.3.0",
"sharp": "^0.31.3",
"shadcn": "2.5.0",
"sharp": "^0.32.6",
"sonner": "^1.2.3",
"swr": "2.2.6-beta.3",
"tailwind-merge": "^1.12.0",
@@ -102,7 +102,7 @@
"@types/react": "^18.2.65",
"@types/react-color": "^3.0.6",
"@types/react-dom": "^18.2.22",
"esbuild": "^0.17.19",
"esbuild": "^0.25.0",
"eslint": "^8.41.0",
"mdast-util-toc": "^6.1.1",
"postcss": "^8.4.24",
@@ -112,7 +112,7 @@
"rehype-slug": "^5.1.0",
"remark": "^14.0.3",
"remark-code-import": "^1.2.0",
"remark-gfm": "^4.0.0",
"remark-gfm": "^4.0.1",
"rimraf": "^4.1.3",
"shiki": "^1.10.1",
"tailwindcss": "3.4.6",

View File

@@ -3,9 +3,9 @@
"name": "index",
"type": "registry:style",
"dependencies": [
"tw-animate-css",
"class-variance-authority",
"lucide-react"
"lucide-react",
"tw-animate-css"
],
"registryDependencies": [
"utils"

View File

@@ -156,6 +156,38 @@
}
}
},
"css": {
"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"
}
}
]
}
}
]
}
},
"meta": {
"type": "object",
"description": "Additional metadata for the registry item. This is an object with any key value pairs.",

View File

@@ -1102,3 +1102,550 @@ export const baseColorsV4 = {
},
},
} as const
export const baseColorsOKLCH = {
zinc: {
light: {
background: "oklch(1 0 0)", // --color-zinc-50
foreground: "oklch(0.141 0.005 285.823)", // --color-zinc-950
card: "oklch(1 0 0)", // --color-zinc-50
"card-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950
popover: "oklch(1 0 0)", // --color-zinc-50
"popover-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950
primary: "oklch(0.21 0.006 285.885)", // --color-zinc-900
"primary-foreground": "oklch(0.985 0 0)", // --color-zinc-50
secondary: "oklch(0.967 0.001 286.375)", // --color-zinc-100
"secondary-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900
muted: "oklch(0.967 0.001 286.375)", // --color-zinc-100
"muted-foreground": "oklch(0.552 0.016 285.938)", // --color-zinc-500
accent: "oklch(0.967 0.001 286.375)", // --color-zinc-100
"accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900
destructive: "oklch(0.577 0.245 27.325)", // --color-red-600
border: "oklch(0.92 0.004 286.32)", // --color-zinc-200
input: "oklch(0.92 0.004 286.32)", // --color-zinc-200
ring: "oklch(0.705 0.015 286.067)", // --color-zinc-400
"chart-1": "oklch(0.646 0.222 41.116)", // --color-orange-600
"chart-2": "oklch(0.6 0.118 184.704)", // --color-teal-600
"chart-3": "oklch(0.398 0.07 227.392)", // --color-cyan-900
"chart-4": "oklch(0.828 0.189 84.429)", // --color-amber-400
"chart-5": "oklch(0.769 0.188 70.08)", // --color-amber-500
sidebar: "oklch(0.985 0 0)", // --color-zinc-50
"sidebar-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950
"sidebar-primary": "oklch(0.21 0.006 285.885)", // --color-zinc-900
"sidebar-primary-foreground": "oklch(0.985 0 0)", // --color-zinc-50
"sidebar-accent": "oklch(0.967 0.001 286.375)", // --color-zinc-100
"sidebar-accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900
"sidebar-border": "oklch(0.92 0.004 286.32)", // --color-zinc-200
"sidebar-ring": "oklch(0.705 0.015 286.067)", // --color-zinc-400
},
dark: {
background: "oklch(0.141 0.005 285.823)", // --color-zinc-950
foreground: "oklch(0.985 0 0)", // --color-zinc-50
card: "oklch(0.21 0.006 285.885)", // --color-zinc-900
"card-foreground": "oklch(0.985 0 0)", // --color-zinc-50
popover: "oklch(0.21 0.006 285.885)", // --color-zinc-900
"popover-foreground": "oklch(0.985 0 0)", // --color-zinc-50
primary: "oklch(0.92 0.004 286.32)", // --color-zinc-200
"primary-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900
secondary: "oklch(0.274 0.006 286.033)", // --color-zinc-800
"secondary-foreground": "oklch(0.985 0 0)", // --color-zinc-50
muted: "oklch(0.274 0.006 286.033)", // --color-zinc-800
"muted-foreground": "oklch(0.705 0.015 286.067)", // --color-zinc-400
accent: "oklch(0.274 0.006 286.033)", // --color-zinc-800
"accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50
destructive: "oklch(0.704 0.191 22.216)", // --color-red-400
border: "oklch(1 0 0 / 10%)", // --color-white
input: "oklch(1 0 0 / 15%)", // --color-white
ring: "oklch(0.552 0.016 285.938)", // --color-zinc-500
"chart-1": "oklch(0.488 0.243 264.376)", // --color-blue-700
"chart-2": "oklch(0.696 0.17 162.48)", // --color-emerald-500
"chart-3": "oklch(0.769 0.188 70.08)", // --color-amber-500
"chart-4": "oklch(0.627 0.265 303.9)", // --color-purple-500
"chart-5": "oklch(0.645 0.246 16.439)", // --color-rose-500
sidebar: "oklch(0.21 0.006 285.885)", // --color-zinc-900
"sidebar-foreground": "oklch(0.985 0 0)", // --color-zinc-50
"sidebar-primary": "oklch(0.488 0.243 264.376)", // --color-blue-700
"sidebar-primary-foreground": "oklch(0.985 0 0)", // --color-zinc-50
"sidebar-accent": "oklch(0.274 0.006 286.033)", // --color-zinc-800
"sidebar-accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50
"sidebar-border": "oklch(1 0 0 / 10%)", // --color-white
"sidebar-ring": "oklch(0.552 0.016 285.938)", // --color-zinc-500
},
},
red: {
light: {
background: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
foreground: "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
card: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
"card-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
popover: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
"popover-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
primary: "oklch(0.637 0.237 25.331)", // --color-red-500 (approx HSL 0 72.2% 50.6%)
"primary-foreground": "oklch(0.971 0.013 17.38)", // --color-red-50 (approx HSL 0 85.7% 97.3%)
secondary: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"secondary-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
muted: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"muted-foreground": "oklch(0.552 0.016 285.938)", // --color-zinc-500 (from zinc)
accent: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
destructive: "oklch(0.577 0.245 27.325)", // --color-red-600 (from zinc)
border: "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
input: "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
ring: "oklch(0.637 0.237 25.331)", // --color-red-500 (approx HSL 0 72.2% 50.6%)
"chart-1": "oklch(0.646 0.222 41.116)", // --color-orange-600 (from zinc)
"chart-2": "oklch(0.6 0.118 184.704)", // --color-teal-600 (from zinc)
"chart-3": "oklch(0.398 0.07 227.392)", // --color-cyan-900 (from zinc)
"chart-4": "oklch(0.828 0.189 84.429)", // --color-amber-400 (from zinc)
"chart-5": "oklch(0.769 0.188 70.08)", // --color-amber-500 (from zinc)
sidebar: "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
"sidebar-primary": "oklch(0.637 0.237 25.331)", // --color-red-500 (approx HSL 0 72.2% 50.6%)
"sidebar-primary-foreground": "oklch(0.971 0.013 17.38)", // --color-red-50 (approx HSL 0 85.7% 97.3%)
"sidebar-accent": "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"sidebar-accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"sidebar-border": "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
"sidebar-ring": "oklch(0.637 0.237 25.331)", // --color-red-500 (approx HSL 0 72.2% 50.6%)
},
dark: {
background: "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
foreground: "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
card: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"card-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
popover: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"popover-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
primary: "oklch(0.637 0.237 25.331)", // --color-red-500 (approx HSL 0 72.2% 50.6%)
"primary-foreground": "oklch(0.971 0.013 17.38)", // --color-red-50 (approx HSL 0 85.7% 97.3%)
secondary: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"secondary-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
muted: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"muted-foreground": "oklch(0.705 0.015 286.067)", // --color-zinc-400 (from zinc)
accent: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
destructive: "oklch(0.704 0.191 22.216)", // --color-red-400 (from zinc)
border: "oklch(1 0 0 / 10%)", // --color-white (from zinc)
input: "oklch(1 0 0 / 15%)", // --color-white (from zinc)
ring: "oklch(0.637 0.237 25.331)", // --color-red-500 (approx HSL 0 72.2% 50.6%)
"chart-1": "oklch(0.488 0.243 264.376)", // --color-blue-700 (from zinc)
"chart-2": "oklch(0.696 0.17 162.48)", // --color-emerald-500 (from zinc)
"chart-3": "oklch(0.769 0.188 70.08)", // --color-amber-500 (from zinc)
"chart-4": "oklch(0.627 0.265 303.9)", // --color-purple-500 (from zinc)
"chart-5": "oklch(0.645 0.246 16.439)", // --color-rose-500 (from zinc)
sidebar: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"sidebar-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-primary": "oklch(0.637 0.237 25.331)", // --color-red-500 (approx HSL 0 72.2% 50.6%)
"sidebar-primary-foreground": "oklch(0.971 0.013 17.38)", // --color-red-50 (approx HSL 0 85.7% 97.3%)
"sidebar-accent": "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"sidebar-accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-border": "oklch(1 0 0 / 10%)", // --color-white (from zinc)
"sidebar-ring": "oklch(0.637 0.237 25.331)", // --color-red-500 (approx HSL 0 72.2% 50.6%)
},
},
rose: {
light: {
background: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
foreground: "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
card: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
"card-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
popover: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
"popover-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
primary: "oklch(0.645 0.246 16.439)", // --color-rose-500 (approx HSL 346.8 77.2% 49.8%)
"primary-foreground": "oklch(0.969 0.015 12.422)", // --color-rose-50 (approx HSL 355.7 100% 97.3%)
secondary: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"secondary-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
muted: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"muted-foreground": "oklch(0.552 0.016 285.938)", // --color-zinc-500 (from zinc)
accent: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
destructive: "oklch(0.577 0.245 27.325)", // --color-red-600 (from zinc)
border: "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
input: "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
ring: "oklch(0.645 0.246 16.439)", // --color-rose-500 (approx HSL 346.8 77.2% 49.8%)
"chart-1": "oklch(0.646 0.222 41.116)", // --color-orange-600 (from zinc)
"chart-2": "oklch(0.6 0.118 184.704)", // --color-teal-600 (from zinc)
"chart-3": "oklch(0.398 0.07 227.392)", // --color-cyan-900 (from zinc)
"chart-4": "oklch(0.828 0.189 84.429)", // --color-amber-400 (from zinc)
"chart-5": "oklch(0.769 0.188 70.08)", // --color-amber-500 (from zinc)
sidebar: "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
"sidebar-primary": "oklch(0.645 0.246 16.439)", // --color-rose-500 (approx HSL 346.8 77.2% 49.8%)
"sidebar-primary-foreground": "oklch(0.969 0.015 12.422)", // --color-rose-50 (approx HSL 355.7 100% 97.3%)
"sidebar-accent": "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"sidebar-accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"sidebar-border": "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
"sidebar-ring": "oklch(0.645 0.246 16.439)", // --color-rose-500 (approx HSL 346.8 77.2% 49.8%)
},
dark: {
background: "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
foreground: "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
card: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"card-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
popover: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"popover-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
primary: "oklch(0.645 0.246 16.439)", // --color-rose-500 (approx HSL 346.8 77.2% 49.8%)
"primary-foreground": "oklch(0.969 0.015 12.422)", // --color-rose-50 (approx HSL 355.7 100% 97.3%)
secondary: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"secondary-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
muted: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"muted-foreground": "oklch(0.705 0.015 286.067)", // --color-zinc-400 (from zinc)
accent: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
destructive: "oklch(0.704 0.191 22.216)", // --color-red-400 (from zinc)
border: "oklch(1 0 0 / 10%)", // --color-white (from zinc)
input: "oklch(1 0 0 / 15%)", // --color-white (from zinc)
ring: "oklch(0.645 0.246 16.439)", // --color-rose-500 (approx HSL 346.8 77.2% 49.8%)
"chart-1": "oklch(0.488 0.243 264.376)", // --color-blue-700 (from zinc)
"chart-2": "oklch(0.696 0.17 162.48)", // --color-emerald-500 (from zinc)
"chart-3": "oklch(0.769 0.188 70.08)", // --color-amber-500 (from zinc)
"chart-4": "oklch(0.627 0.265 303.9)", // --color-purple-500 (from zinc)
"chart-5": "oklch(0.645 0.246 16.439)", // --color-rose-500 (from zinc)
sidebar: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"sidebar-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-primary": "oklch(0.645 0.246 16.439)", // --color-rose-500 (approx HSL 346.8 77.2% 49.8%)
"sidebar-primary-foreground": "oklch(0.969 0.015 12.422)", // --color-rose-50 (approx HSL 355.7 100% 97.3%)
"sidebar-accent": "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"sidebar-accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-border": "oklch(1 0 0 / 10%)", // --color-white (from zinc)
"sidebar-ring": "oklch(0.645 0.246 16.439)", // --color-rose-500 (approx HSL 346.8 77.2% 49.8%)
},
},
orange: {
light: {
background: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
foreground: "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
card: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
"card-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
popover: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
"popover-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
primary: "oklch(0.705 0.213 47.604)", // --color-orange-500 (approx HSL 24.6 95% 53.1%)
"primary-foreground": "oklch(0.98 0.016 73.684)", // --color-orange-50 (approx HSL 60 9.1% 97.8%)
secondary: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"secondary-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
muted: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"muted-foreground": "oklch(0.552 0.016 285.938)", // --color-zinc-500 (from zinc)
accent: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
destructive: "oklch(0.577 0.245 27.325)", // --color-red-600 (from zinc)
border: "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
input: "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
ring: "oklch(0.705 0.213 47.604)", // --color-orange-500 (approx HSL 24.6 95% 53.1%)
"chart-1": "oklch(0.646 0.222 41.116)", // --color-orange-600 (from zinc)
"chart-2": "oklch(0.6 0.118 184.704)", // --color-teal-600 (from zinc)
"chart-3": "oklch(0.398 0.07 227.392)", // --color-cyan-900 (from zinc)
"chart-4": "oklch(0.828 0.189 84.429)", // --color-amber-400 (from zinc)
"chart-5": "oklch(0.769 0.188 70.08)", // --color-amber-500 (from zinc)
sidebar: "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
"sidebar-primary": "oklch(0.705 0.213 47.604)", // --color-orange-500 (approx HSL 24.6 95% 53.1%)
"sidebar-primary-foreground": "oklch(0.98 0.016 73.684)", // --color-orange-50 (approx HSL 60 9.1% 97.8%)
"sidebar-accent": "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"sidebar-accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"sidebar-border": "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
"sidebar-ring": "oklch(0.705 0.213 47.604)", // --color-orange-500 (approx HSL 24.6 95% 53.1%)
},
dark: {
background: "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
foreground: "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
card: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"card-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
popover: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"popover-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
primary: "oklch(0.646 0.222 41.116)", // --color-orange-600 (approx HSL 20.5 90.2% 48.2%)
"primary-foreground": "oklch(0.98 0.016 73.684)", // --color-orange-50 (approx HSL 60 9.1% 97.8%)
secondary: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"secondary-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
muted: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"muted-foreground": "oklch(0.705 0.015 286.067)", // --color-zinc-400 (from zinc)
accent: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
destructive: "oklch(0.704 0.191 22.216)", // --color-red-400 (from zinc)
border: "oklch(1 0 0 / 10%)", // --color-white (from zinc)
input: "oklch(1 0 0 / 15%)", // --color-white (from zinc)
ring: "oklch(0.646 0.222 41.116)", // --color-orange-600 (approx HSL 20.5 90.2% 48.2%)
"chart-1": "oklch(0.488 0.243 264.376)", // --color-blue-700 (from zinc)
"chart-2": "oklch(0.696 0.17 162.48)", // --color-emerald-500 (from zinc)
"chart-3": "oklch(0.769 0.188 70.08)", // --color-amber-500 (from zinc)
"chart-4": "oklch(0.627 0.265 303.9)", // --color-purple-500 (from zinc)
"chart-5": "oklch(0.645 0.246 16.439)", // --color-rose-500 (from zinc)
sidebar: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"sidebar-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-primary": "oklch(0.646 0.222 41.116)", // --color-orange-600 (approx HSL 20.5 90.2% 48.2%)
"sidebar-primary-foreground": "oklch(0.98 0.016 73.684)", // --color-orange-50 (approx HSL 60 9.1% 97.8%)
"sidebar-accent": "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"sidebar-accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-border": "oklch(1 0 0 / 10%)", // --color-white (from zinc)
"sidebar-ring": "oklch(0.646 0.222 41.116)", // --color-orange-600 (approx HSL 20.5 90.2% 48.2%)
},
},
green: {
light: {
background: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
foreground: "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
card: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
"card-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
popover: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
"popover-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
primary: "oklch(0.723 0.219 149.579)", // --color-green-500 (approx HSL 142.1 76.2% 36.3%)
"primary-foreground": "oklch(0.982 0.018 155.826)", // --color-green-50 (approx HSL 355.7 100% 97.3%)
secondary: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"secondary-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
muted: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"muted-foreground": "oklch(0.552 0.016 285.938)", // --color-zinc-500 (from zinc)
accent: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
destructive: "oklch(0.577 0.245 27.325)", // --color-red-600 (from zinc)
border: "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
input: "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
ring: "oklch(0.723 0.219 149.579)", // --color-green-500 (approx HSL 142.1 76.2% 36.3%)
"chart-1": "oklch(0.646 0.222 41.116)", // --color-orange-600 (from zinc)
"chart-2": "oklch(0.6 0.118 184.704)", // --color-teal-600 (from zinc)
"chart-3": "oklch(0.398 0.07 227.392)", // --color-cyan-900 (from zinc)
"chart-4": "oklch(0.828 0.189 84.429)", // --color-amber-400 (from zinc)
"chart-5": "oklch(0.769 0.188 70.08)", // --color-amber-500 (from zinc)
sidebar: "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
"sidebar-primary": "oklch(0.723 0.219 149.579)", // --color-green-500 (approx HSL 142.1 76.2% 36.3%)
"sidebar-primary-foreground": "oklch(0.982 0.018 155.826)", // --color-green-50 (approx HSL 355.7 100% 97.3%)
"sidebar-accent": "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"sidebar-accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"sidebar-border": "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
"sidebar-ring": "oklch(0.723 0.219 149.579)", // --color-green-500 (approx HSL 142.1 76.2% 36.3%)
},
dark: {
background: "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
foreground: "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
card: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"card-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
popover: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"popover-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
primary: "oklch(0.696 0.17 162.48)", // --color-emerald-500 (approx HSL 142.1 70.6% 45.3%)
"primary-foreground": "oklch(0.393 0.095 152.535)", // --color-green-900 (approx HSL 144.9 80.4% 10%)
secondary: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"secondary-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
muted: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"muted-foreground": "oklch(0.705 0.015 286.067)", // --color-zinc-400 (from zinc)
accent: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
destructive: "oklch(0.704 0.191 22.216)", // --color-red-400 (from zinc)
border: "oklch(1 0 0 / 10%)", // --color-white (from zinc)
input: "oklch(1 0 0 / 15%)", // --color-white (from zinc)
ring: "oklch(0.527 0.154 150.069)", // --color-green-700 (approx HSL 142.4 71.8% 29.2%)
"chart-1": "oklch(0.488 0.243 264.376)", // --color-blue-700 (from zinc)
"chart-2": "oklch(0.696 0.17 162.48)", // --color-emerald-500 (from zinc)
"chart-3": "oklch(0.769 0.188 70.08)", // --color-amber-500 (from zinc)
"chart-4": "oklch(0.627 0.265 303.9)", // --color-purple-500 (from zinc)
"chart-5": "oklch(0.645 0.246 16.439)", // --color-rose-500 (from zinc)
sidebar: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"sidebar-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-primary": "oklch(0.696 0.17 162.48)", // --color-emerald-500 (approx HSL 142.1 70.6% 45.3%)
"sidebar-primary-foreground": "oklch(0.393 0.095 152.535)", // --color-green-900 (approx HSL 144.9 80.4% 10%)
"sidebar-accent": "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"sidebar-accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-border": "oklch(1 0 0 / 10%)", // --color-white (from zinc)
"sidebar-ring": "oklch(0.527 0.154 150.069)", // --color-green-700 (approx HSL 142.4 71.8% 29.2%)
},
},
blue: {
light: {
background: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
foreground: "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
card: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
"card-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
popover: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
"popover-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
primary: "oklch(0.623 0.214 259.815)", // --color-blue-500 (approx HSL 221.2 83.2% 53.3%)
"primary-foreground": "oklch(0.97 0.014 254.604)", // --color-blue-50 (approx HSL 210 40% 98%)
secondary: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"secondary-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
muted: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"muted-foreground": "oklch(0.552 0.016 285.938)", // --color-zinc-500 (from zinc)
accent: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
destructive: "oklch(0.577 0.245 27.325)", // --color-red-600 (from zinc)
border: "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
input: "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
ring: "oklch(0.623 0.214 259.815)", // --color-blue-500 (approx HSL 221.2 83.2% 53.3%)
"chart-1": "oklch(0.646 0.222 41.116)", // --color-orange-600 (from zinc)
"chart-2": "oklch(0.6 0.118 184.704)", // --color-teal-600 (from zinc)
"chart-3": "oklch(0.398 0.07 227.392)", // --color-cyan-900 (from zinc)
"chart-4": "oklch(0.828 0.189 84.429)", // --color-amber-400 (from zinc)
"chart-5": "oklch(0.769 0.188 70.08)", // --color-amber-500 (from zinc)
sidebar: "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
"sidebar-primary": "oklch(0.623 0.214 259.815)", // --color-blue-500 (approx HSL 221.2 83.2% 53.3%)
"sidebar-primary-foreground": "oklch(0.97 0.014 254.604)", // --color-blue-50 (approx HSL 210 40% 98%)
"sidebar-accent": "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"sidebar-accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"sidebar-border": "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
"sidebar-ring": "oklch(0.623 0.214 259.815)", // --color-blue-500 (approx HSL 221.2 83.2% 53.3%)
},
dark: {
background: "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
foreground: "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
card: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"card-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
popover: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"popover-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
primary: "oklch(0.546 0.245 262.881)", // --color-blue-600 (approx HSL 217.2 91.2% 59.8%)
"primary-foreground": "oklch(0.379 0.146 265.522)", // --color-blue-900 (approx HSL 222.2 47.4% 11.2%)
secondary: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"secondary-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
muted: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"muted-foreground": "oklch(0.705 0.015 286.067)", // --color-zinc-400 (from zinc)
accent: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
destructive: "oklch(0.704 0.191 22.216)", // --color-red-400 (from zinc)
border: "oklch(1 0 0 / 10%)", // --color-white (from zinc)
input: "oklch(1 0 0 / 15%)", // --color-white (from zinc)
ring: "oklch(0.488 0.243 264.376)", // --color-blue-700 (approx HSL 224.3 76.3% 48%)
"chart-1": "oklch(0.488 0.243 264.376)", // --color-blue-700 (from zinc)
"chart-2": "oklch(0.696 0.17 162.48)", // --color-emerald-500 (from zinc)
"chart-3": "oklch(0.769 0.188 70.08)", // --color-amber-500 (from zinc)
"chart-4": "oklch(0.627 0.265 303.9)", // --color-purple-500 (from zinc)
"chart-5": "oklch(0.645 0.246 16.439)", // --color-rose-500 (from zinc)
sidebar: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"sidebar-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-primary": "oklch(0.546 0.245 262.881)", // --color-blue-600 (approx HSL 217.2 91.2% 59.8%)
"sidebar-primary-foreground": "oklch(0.379 0.146 265.522)", // --color-blue-900 (approx HSL 222.2 47.4% 11.2%)
"sidebar-accent": "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"sidebar-accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-border": "oklch(1 0 0 / 10%)", // --color-white (from zinc)
"sidebar-ring": "oklch(0.488 0.243 264.376)", // --color-blue-700 (approx HSL 224.3 76.3% 48%)
},
},
yellow: {
light: {
background: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
foreground: "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
card: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
"card-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
popover: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
"popover-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
primary: "oklch(0.795 0.184 86.047)", // --color-yellow-500 (approx HSL 47.9 95.8% 53.1%)
"primary-foreground": "oklch(0.421 0.095 57.708)", // --color-yellow-900 (approx HSL 26 83.3% 14.1%)
secondary: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"secondary-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
muted: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"muted-foreground": "oklch(0.552 0.016 285.938)", // --color-zinc-500 (from zinc)
accent: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
destructive: "oklch(0.577 0.245 27.325)", // --color-red-600 (from zinc)
border: "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
input: "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
ring: "oklch(0.795 0.184 86.047)", // --color-yellow-500 (approx HSL 47.9 95.8% 53.1%)
"chart-1": "oklch(0.646 0.222 41.116)", // --color-orange-600 (from zinc)
"chart-2": "oklch(0.6 0.118 184.704)", // --color-teal-600 (from zinc)
"chart-3": "oklch(0.398 0.07 227.392)", // --color-cyan-900 (from zinc)
"chart-4": "oklch(0.828 0.189 84.429)", // --color-amber-400 (from zinc)
"chart-5": "oklch(0.769 0.188 70.08)", // --color-amber-500 (from zinc)
sidebar: "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
"sidebar-primary": "oklch(0.795 0.184 86.047)", // --color-yellow-500 (approx HSL 47.9 95.8% 53.1%)
"sidebar-primary-foreground": "oklch(0.421 0.095 57.708)", // --color-yellow-900 (approx HSL 26 83.3% 14.1%)
"sidebar-accent": "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"sidebar-accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"sidebar-border": "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
"sidebar-ring": "oklch(0.795 0.184 86.047)", // --color-yellow-500 (approx HSL 47.9 95.8% 53.1%)
},
dark: {
background: "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
foreground: "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
card: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"card-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
popover: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"popover-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
primary: "oklch(0.795 0.184 86.047)", // --color-yellow-500 (approx HSL 47.9 95.8% 53.1%)
"primary-foreground": "oklch(0.421 0.095 57.708)", // --color-yellow-900 (approx HSL 26 83.3% 14.1%)
secondary: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"secondary-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
muted: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"muted-foreground": "oklch(0.705 0.015 286.067)", // --color-zinc-400 (from zinc)
accent: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
destructive: "oklch(0.704 0.191 22.216)", // --color-red-400 (from zinc)
border: "oklch(1 0 0 / 10%)", // --color-white (from zinc)
input: "oklch(1 0 0 / 15%)", // --color-white (from zinc)
ring: "oklch(0.554 0.135 66.442)", // --color-yellow-700 (approx HSL 35.5 91.7% 32.9%)
"chart-1": "oklch(0.488 0.243 264.376)", // --color-blue-700 (from zinc)
"chart-2": "oklch(0.696 0.17 162.48)", // --color-emerald-500 (from zinc)
"chart-3": "oklch(0.769 0.188 70.08)", // --color-amber-500 (from zinc)
"chart-4": "oklch(0.627 0.265 303.9)", // --color-purple-500 (from zinc)
"chart-5": "oklch(0.645 0.246 16.439)", // --color-rose-500 (from zinc)
sidebar: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"sidebar-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-primary": "oklch(0.795 0.184 86.047)", // --color-yellow-500 (approx HSL 47.9 95.8% 53.1%)
"sidebar-primary-foreground": "oklch(0.421 0.095 57.708)", // --color-yellow-900 (approx HSL 26 83.3% 14.1%)
"sidebar-accent": "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"sidebar-accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-border": "oklch(1 0 0 / 10%)", // --color-white (from zinc)
"sidebar-ring": "oklch(0.554 0.135 66.442)", // --color-yellow-700 (approx HSL 35.5 91.7% 32.9%)
},
},
violet: {
light: {
background: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
foreground: "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
card: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
"card-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
popover: "oklch(1 0 0)", // --color-zinc-50 (from zinc)
"popover-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
primary: "oklch(0.606 0.25 292.717)", // --color-violet-500 (approx HSL 262.1 83.3% 57.8%)
"primary-foreground": "oklch(0.969 0.016 293.756)", // --color-violet-50 (approx HSL 210 20% 98%)
secondary: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"secondary-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
muted: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"muted-foreground": "oklch(0.552 0.016 285.938)", // --color-zinc-500 (from zinc)
accent: "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
destructive: "oklch(0.577 0.245 27.325)", // --color-red-600 (from zinc)
border: "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
input: "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
ring: "oklch(0.606 0.25 292.717)", // --color-violet-500 (approx HSL 262.1 83.3% 57.8%)
"chart-1": "oklch(0.646 0.222 41.116)", // --color-orange-600 (from zinc)
"chart-2": "oklch(0.6 0.118 184.704)", // --color-teal-600 (from zinc)
"chart-3": "oklch(0.398 0.07 227.392)", // --color-cyan-900 (from zinc)
"chart-4": "oklch(0.828 0.189 84.429)", // --color-amber-400 (from zinc)
"chart-5": "oklch(0.769 0.188 70.08)", // --color-amber-500 (from zinc)
sidebar: "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-foreground": "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
"sidebar-primary": "oklch(0.606 0.25 292.717)", // --color-violet-500 (approx HSL 262.1 83.3% 57.8%)
"sidebar-primary-foreground": "oklch(0.969 0.016 293.756)", // --color-violet-50 (approx HSL 210 20% 98%)
"sidebar-accent": "oklch(0.967 0.001 286.375)", // --color-zinc-100 (from zinc)
"sidebar-accent-foreground": "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"sidebar-border": "oklch(0.92 0.004 286.32)", // --color-zinc-200 (from zinc)
"sidebar-ring": "oklch(0.606 0.25 292.717)", // --color-violet-500 (approx HSL 262.1 83.3% 57.8%)
},
dark: {
background: "oklch(0.141 0.005 285.823)", // --color-zinc-950 (from zinc)
foreground: "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
card: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"card-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
popover: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"popover-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
primary: "oklch(0.541 0.281 293.009)", // --color-violet-600 (approx HSL 263.4 70% 50.4%)
"primary-foreground": "oklch(0.969 0.016 293.756)", // --color-violet-50 (approx HSL 210 20% 98%)
secondary: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"secondary-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
muted: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"muted-foreground": "oklch(0.705 0.015 286.067)", // --color-zinc-400 (from zinc)
accent: "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
destructive: "oklch(0.704 0.191 22.216)", // --color-red-400 (from zinc)
border: "oklch(1 0 0 / 10%)", // --color-white (from zinc)
input: "oklch(1 0 0 / 15%)", // --color-white (from zinc)
ring: "oklch(0.541 0.281 293.009)", // --color-violet-600 (approx HSL 263.4 70% 50.4%)
"chart-1": "oklch(0.488 0.243 264.376)", // --color-blue-700 (from zinc)
"chart-2": "oklch(0.696 0.17 162.48)", // --color-emerald-500 (from zinc)
"chart-3": "oklch(0.769 0.188 70.08)", // --color-amber-500 (from zinc)
"chart-4": "oklch(0.627 0.265 303.9)", // --color-purple-500 (from zinc)
"chart-5": "oklch(0.645 0.246 16.439)", // --color-rose-500 (from zinc)
sidebar: "oklch(0.21 0.006 285.885)", // --color-zinc-900 (from zinc)
"sidebar-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-primary": "oklch(0.541 0.281 293.009)", // --color-violet-600 (approx HSL 263.4 70% 50.4%)
"sidebar-primary-foreground": "oklch(0.969 0.016 293.756)", // --color-violet-50 (approx HSL 210 20% 98%)
"sidebar-accent": "oklch(0.274 0.006 286.033)", // --color-zinc-800 (from zinc)
"sidebar-accent-foreground": "oklch(0.985 0 0)", // --color-zinc-50 (from zinc)
"sidebar-border": "oklch(1 0 0 / 10%)", // --color-white (from zinc)
"sidebar-ring": "oklch(0.541 0.281 293.009)", // --color-violet-600 (approx HSL 263.4 70% 50.4%)
},
},
}

View File

@@ -75,9 +75,9 @@
"tailwindcss-animate": "^1.0.5",
"tsx": "^4.1.4",
"turbo": "^1.9.9",
"vite": "^5.4.1",
"vite": "^5.4.15",
"vite-tsconfig-paths": "^4.2.0",
"vitest": "^2.0.5"
"vitest": "^2.1.9"
},
"devDependencies": {
"@types/node": "^20.11.27",

View File

@@ -1,5 +1,81 @@
# @shadcn/ui
## 2.5.0
### Minor Changes
- [#7220](https://github.com/shadcn-ui/ui/pull/7220) [`d0306774fe0ecc1eae9ef1e918bf7862e866a9e8`](https://github.com/shadcn-ui/ui/commit/d0306774fe0ecc1eae9ef1e918bf7862e866a9e8) Thanks [@shadcn](https://github.com/shadcn)! - resolve imports from anywhere
### Patch Changes
- [#6985](https://github.com/shadcn-ui/ui/pull/6985) [`f1e5cc4666ced2166a859660d769ccee16cde46e`](https://github.com/shadcn-ui/ui/commit/f1e5cc4666ced2166a859660d769ccee16cde46e) Thanks [@nrjdalal](https://github.com/nrjdalal)! - move tw-animate-css to devDependencies
- [#6899](https://github.com/shadcn-ui/ui/pull/6899) [`6f702f5fbf2b82a388e7da6ea08bcc84c2ec19c6`](https://github.com/shadcn-ui/ui/commit/6f702f5fbf2b82a388e7da6ea08bcc84c2ec19c6) Thanks [@justjavac](https://github.com/justjavac)! - add deno support
## 2.4.1
### Patch Changes
- [#7196](https://github.com/shadcn-ui/ui/pull/7196) [`617483fe9c26d607665fcaf79ee26e35d9825d7c`](https://github.com/shadcn-ui/ui/commit/617483fe9c26d607665fcaf79ee26e35d9825d7c) Thanks [@shadcn](https://github.com/shadcn)! - do not throw if empty dir
## 2.4.0
### Minor Changes
- [#6507](https://github.com/shadcn-ui/ui/pull/6507) [`5234c46722750f964d69c92ccbef2c4d260c211d`](https://github.com/shadcn-ui/ui/commit/5234c46722750f964d69c92ccbef2c4d260c211d) Thanks [@shadcn](https://github.com/shadcn)! - add support for TanStack Start
- [#6487](https://github.com/shadcn-ui/ui/pull/6487) [`5ef2bc5f455dfc394116267788c0514b696e13b0`](https://github.com/shadcn-ui/ui/commit/5ef2bc5f455dfc394116267788c0514b696e13b0) Thanks [@shadcn](https://github.com/shadcn)! - add theme vars support
- [#6478](https://github.com/shadcn-ui/ui/pull/6478) [`8f6a64f176defdb1f9c493598d952fb4e9844cd0`](https://github.com/shadcn-ui/ui/commit/8f6a64f176defdb1f9c493598d952fb4e9844cd0) Thanks [@shadcn](https://github.com/shadcn)! - add tailwind version detection
- [#6864](https://github.com/shadcn-ui/ui/pull/6864) [`19665adeeddc4ddb34e91fca219753d15bd46480`](https://github.com/shadcn-ui/ui/commit/19665adeeddc4ddb34e91fca219753d15bd46480) Thanks [@shadcn](https://github.com/shadcn)! - add --base-color flag
- [#6490](https://github.com/shadcn-ui/ui/pull/6490) [`9a14c1d0925d3df2c8f57a3381d212cc3e54f4a6`](https://github.com/shadcn-ui/ui/commit/9a14c1d0925d3df2c8f57a3381d212cc3e54f4a6) Thanks [@shadcn](https://github.com/shadcn)! - add support for tailwind v4
- [#6707](https://github.com/shadcn-ui/ui/pull/6707) [`3db8a07b3f132d396d7fb2e50da96156efcb4138`](https://github.com/shadcn-ui/ui/commit/3db8a07b3f132d396d7fb2e50da96156efcb4138) Thanks [@shadcn](https://github.com/shadcn)! - default to css vars. add --no-css-variables
- [#6968](https://github.com/shadcn-ui/ui/pull/6968) [`205bfc637e093717908ebd2c591b215672950558`](https://github.com/shadcn-ui/ui/commit/205bfc637e093717908ebd2c591b215672950558) Thanks [@shadcn](https://github.com/shadcn)! - replace tailwindcss-animate with tw-animate-css
- [#6574](https://github.com/shadcn-ui/ui/pull/6574) [`1e357cb20d6024b2bc9766fb15f61cb989eb7024`](https://github.com/shadcn-ui/ui/commit/1e357cb20d6024b2bc9766fb15f61cb989eb7024) Thanks [@shadcn](https://github.com/shadcn)! - default for new-york for v4
- [#6515](https://github.com/shadcn-ui/ui/pull/6515) [`d1eb24e23a973646d78cf101fa1e0a22861ac9fd`](https://github.com/shadcn-ui/ui/commit/d1eb24e23a973646d78cf101fa1e0a22861ac9fd) Thanks [@shadcn](https://github.com/shadcn)! - fix handling of sidebar colors
- [#6693](https://github.com/shadcn-ui/ui/pull/6693) [`3740373f99e39943514a45f5808ecb5f17faf700`](https://github.com/shadcn-ui/ui/commit/3740373f99e39943514a45f5808ecb5f17faf700) Thanks [@shadcn](https://github.com/shadcn)! - add oklch base color
- [#6571](https://github.com/shadcn-ui/ui/pull/6571) [`c74a094f14a6e338124709547932dbb20c8d1324`](https://github.com/shadcn-ui/ui/commit/c74a094f14a6e338124709547932dbb20c8d1324) Thanks [@shadcn](https://github.com/shadcn)! - hotswap style for v4
- [#6576](https://github.com/shadcn-ui/ui/pull/6576) [`9f4d65fc8fe72f632706fafd4036f63fd9317780`](https://github.com/shadcn-ui/ui/commit/9f4d65fc8fe72f632706fafd4036f63fd9317780) Thanks [@shadcn](https://github.com/shadcn)! - add warning for deprecated components
- [#6811](https://github.com/shadcn-ui/ui/pull/6811) [`bc7df68620f242ce6aa640839c80ddc8afc7e091`](https://github.com/shadcn-ui/ui/commit/bc7df68620f242ce6aa640839c80ddc8afc7e091) Thanks [@shadcn](https://github.com/shadcn)! - add support for route install for react-router and laravel
- [#7016](https://github.com/shadcn-ui/ui/pull/7016) [`b3b2fe2755e0ec1271c41a2a61b1a6933af42bc6`](https://github.com/shadcn-ui/ui/commit/b3b2fe2755e0ec1271c41a2a61b1a6933af42bc6) Thanks [@shadcn](https://github.com/shadcn)! - add theme prop to registry-item schema
### Patch Changes
- [#6724](https://github.com/shadcn-ui/ui/pull/6724) [`a3fe5074c1375cbd92e1ccdaab38d6808bfec696`](https://github.com/shadcn-ui/ui/commit/a3fe5074c1375cbd92e1ccdaab38d6808bfec696) Thanks [@Kaikaikaifang](https://github.com/Kaikaikaifang)! - support for version detection in monorepo
- [#6414](https://github.com/shadcn-ui/ui/pull/6414) [`202131cd7bf8829b962ae4027545afbdfe79e688`](https://github.com/shadcn-ui/ui/commit/202131cd7bf8829b962ae4027545afbdfe79e688) Thanks [@palmithor](https://github.com/palmithor)! - upgrade @antfu/ni
- [#6965](https://github.com/shadcn-ui/ui/pull/6965) [`69fc8e23cc0631aac6b708ba0481509f1125d3d7`](https://github.com/shadcn-ui/ui/commit/69fc8e23cc0631aac6b708ba0481509f1125d3d7) Thanks [@jherr](https://github.com/jherr)! - allow silent mode with npm
- [#6814](https://github.com/shadcn-ui/ui/pull/6814) [`8539dd6eec948e7a6218c7ca3372d2b1f349d7c0`](https://github.com/shadcn-ui/ui/commit/8539dd6eec948e7a6218c7ca3372d2b1f349d7c0) Thanks [@shadcn](https://github.com/shadcn)! - do not add ring for v3
- [#6732](https://github.com/shadcn-ui/ui/pull/6732) [`839afa714f61e2c0f83e9417354ea6ba8246c177`](https://github.com/shadcn-ui/ui/commit/839afa714f61e2c0f83e9417354ea6ba8246c177) Thanks [@shadcn](https://github.com/shadcn)! - cache registry calls
- [#6863](https://github.com/shadcn-ui/ui/pull/6863) [`c16c58d0f9e672edddd554269fdd0eb0d412cb9d`](https://github.com/shadcn-ui/ui/commit/c16c58d0f9e672edddd554269fdd0eb0d412cb9d) Thanks [@shadcn](https://github.com/shadcn)! - add --template flag
- [#6721](https://github.com/shadcn-ui/ui/pull/6721) [`a5122f9029c91963f493e7348ef7681dad4834e5`](https://github.com/shadcn-ui/ui/commit/a5122f9029c91963f493e7348ef7681dad4834e5) Thanks [@shadcn](https://github.com/shadcn)! - do not overwrite user defined vars
- [#6530](https://github.com/shadcn-ui/ui/pull/6530) [`d6159023ed0817adf14b4398874b1f5f05a73b02`](https://github.com/shadcn-ui/ui/commit/d6159023ed0817adf14b4398874b1f5f05a73b02) Thanks [@zwarunek](https://github.com/zwarunek)! - fix cn import bug in monorepo
- [#6617](https://github.com/shadcn-ui/ui/pull/6617) [`bd8533bd265de7765831d398f4db687483a0e53a`](https://github.com/shadcn-ui/ui/commit/bd8533bd265de7765831d398f4db687483a0e53a) Thanks [@shadcn](https://github.com/shadcn)! - filter out deprecated from --all
- [#6733](https://github.com/shadcn-ui/ui/pull/6733) [`779517a1d46f567c8e3fa8fcdea4c75c65ad4eb4`](https://github.com/shadcn-ui/ui/commit/779517a1d46f567c8e3fa8fcdea4c75c65ad4eb4) Thanks [@shadcn](https://github.com/shadcn)! - check for empty css vars
- [#6967](https://github.com/shadcn-ui/ui/pull/6967) [`9eae13639c10f0219872b5fd28f523a4c25f40df`](https://github.com/shadcn-ui/ui/commit/9eae13639c10f0219872b5fd28f523a4c25f40df) Thanks [@shadcn](https://github.com/shadcn)! - only show deprecated for new projects
- [#6590](https://github.com/shadcn-ui/ui/pull/6590) [`16d4d38f564c75d4187977275f04d5999ee9e2f4`](https://github.com/shadcn-ui/ui/commit/16d4d38f564c75d4187977275f04d5999ee9e2f4) Thanks [@prateekkumarweb](https://github.com/prateekkumarweb)! - fix tanstack check
## 2.2.0
### Minor Changes

View File

@@ -1,6 +1,6 @@
{
"name": "shadcn",
"version": "2.3.0",
"version": "2.5.0",
"description": "Add components to your apps.",
"publishConfig": {
"access": "public"

View File

@@ -1,7 +1,8 @@
import path from "path"
import { runInit } from "@/src/commands/init"
import { preFlightAdd } from "@/src/preflights/preflight-add"
import { getRegistryIndex } from "@/src/registry/api"
import { getRegistryIndex, getRegistryItem, isUrl } from "@/src/registry/api"
import { registryItemTypeSchema } from "@/src/registry/schema"
import { addComponents } from "@/src/utils/add-components"
import { createProject } from "@/src/utils/create-project"
import * as ERRORS from "@/src/utils/errors"
@@ -78,23 +79,31 @@ export const add = new Command()
...opts,
})
// Confirm if user is installing themes.
// For now, we assume a theme is prefixed with "theme-".
const isTheme = options.components?.some((component) =>
component.includes("theme-")
)
if (!options.yes && isTheme) {
let itemType: z.infer<typeof registryItemTypeSchema> | undefined
if (components.length > 0 && isUrl(components[0])) {
const item = await getRegistryItem(components[0], "")
itemType = item?.type
}
if (
!options.yes &&
(itemType === "registry:style" || itemType === "registry:theme")
) {
logger.break()
const { confirm } = await prompts({
type: "confirm",
name: "confirm",
message: highlighter.warn(
"You are about to install a new theme. \nExisting CSS variables will be overwritten. Continue?"
`You are about to install a new ${itemType.replace(
"registry:",
""
)}. \nExisting CSS variables and components will be overwritten. Continue?`
),
})
if (!confirm) {
logger.break()
logger.log("Theme installation cancelled.")
logger.log(`Installation cancelled.`)
logger.break()
process.exit(1)
}

View File

@@ -6,6 +6,7 @@ import {
getRegistryBaseColors,
getRegistryItem,
getRegistryStyles,
isUrl,
} from "@/src/registry/api"
import { addComponents } from "@/src/utils/add-components"
import { TEMPLATES, createProject } from "@/src/utils/create-project"
@@ -127,13 +128,15 @@ export const init = new Command()
// We need to check if we're initializing with a new style.
// We fetch the payload of the first item.
// This is okay since the request is cached and deduped.
const item = await getRegistryItem(components[0], "")
if (components.length > 0 && isUrl(components[0])) {
const item = await getRegistryItem(components[0], "")
// Skip base color if style.
// We set a default and let the style override it.
if (item?.type === "registry:style") {
options.baseColor = "neutral"
options.style = item.extends ?? "index"
// Skip base color if style.
// We set a default and let the style override it.
if (item?.type === "registry:style") {
options.baseColor = "neutral"
options.style = item.extends ?? "index"
}
}
await runInit(options)

View File

@@ -98,7 +98,7 @@ export async function migrateIcons(config: Config) {
}
if (targetLibrary.package) {
await updateDependencies([targetLibrary.package], config, {
await updateDependencies([targetLibrary.package], [], config, {
silent: false,
})
}

View File

@@ -307,6 +307,11 @@ export async function registryResolveItemsTree(
cssVars = deepmerge(cssVars, item.cssVars ?? {})
})
let css = {}
payload.forEach((item) => {
css = deepmerge(css, item.css ?? {})
})
let docs = ""
payload.forEach((item) => {
if (item.docs) {
@@ -324,6 +329,7 @@ export async function registryResolveItemsTree(
files: deepmerge.all(payload.map((item) => item.files ?? [])),
tailwind,
cssVars,
css,
docs,
})
} catch (error) {
@@ -467,7 +473,7 @@ function getRegistryUrl(path: string) {
return `${REGISTRY_URL}/${path}`
}
function isUrl(path: string) {
export function isUrl(path: string) {
try {
new URL(path)
return true

View File

@@ -51,6 +51,19 @@ 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())])
),
])
)
)
export const registryItemSchema = z.object({
$schema: z.string().optional(),
extends: z.string().optional(),
@@ -65,6 +78,7 @@ export const registryItemSchema = z.object({
files: z.array(registryItemFileSchema).optional(),
tailwind: registryItemTailwindSchema.optional(),
cssVars: registryItemCssVarsSchema.optional(),
css: registryItemCssSchema.optional(),
meta: z.record(z.string(), z.any()).optional(),
docs: z.string().optional(),
categories: z.array(z.string()).optional(),
@@ -111,5 +125,6 @@ export const registryResolvedItemsTreeSchema = registryItemSchema.pick({
files: true,
tailwind: true,
cssVars: true,
css: true,
docs: true,
})

View File

@@ -19,6 +19,7 @@ import { getProjectTailwindVersionFromConfig } from "@/src/utils/get-project-inf
import { handleError } from "@/src/utils/handle-error"
import { logger } from "@/src/utils/logger"
import { spinner } from "@/src/utils/spinner"
import { updateCss } from "@/src/utils/updaters/update-css"
import { updateCssVars } from "@/src/utils/updaters/update-css-vars"
import { updateDependencies } from "@/src/utils/updaters/update-dependencies"
import { updateFiles } from "@/src/utils/updaters/update-files"
@@ -97,7 +98,12 @@ async function addProjectComponents(
initIndex: options.style ? options.style === "index" : false,
})
await updateDependencies(tree.dependencies, config, {
// Add CSS updater
await updateCss(tree.css, config, {
silent: options.silent,
})
await updateDependencies(tree.dependencies, tree.devDependencies, config, {
silent: options.silent,
})
await updateFiles(tree.files, config, {
@@ -196,12 +202,27 @@ async function addWorkspaceComponents(
)
}
// 3. Update dependencies.
await updateDependencies(component.dependencies, targetConfig, {
silent: true,
})
// 3. Update CSS
if (component.css) {
await updateCss(component.css, targetConfig, {
silent: true,
})
filesUpdated.push(
path.relative(workspaceRoot, targetConfig.resolvedPaths.tailwindCss)
)
}
// 4. Update files.
// 4. Update dependencies.
await updateDependencies(
component.dependencies,
component.devDependencies,
targetConfig,
{
silent: true,
}
)
// 5. Update files.
const files = await updateFiles(component.files, targetConfig, {
overwrite: options.overwrite,
silent: true,

View File

@@ -154,7 +154,7 @@ export async function getTailwindVersion(
cwd: string
): Promise<ProjectInfo["tailwindVersion"]> {
const [packageInfo, config] = await Promise.all([
getPackageInfo(cwd),
getPackageInfo(cwd, false),
getConfig(cwd),
])

View File

@@ -1,78 +0,0 @@
import { describe, expect, test } from "vitest"
import { highlighter } from "../../src/utils/highlighter"
import { generateRandomString } from "../../test/fuzz-utils"
describe("fuzzing", () => {
test("should handle various input strings", () => {
const testCases = Array.from({ length: 100 }, () => ({
text: generateRandomString(Math.floor(Math.random() * 100) + 1),
}))
for (const { text } of testCases) {
try {
// Test each highlighter function
const errorResult = highlighter.error(text)
const warnResult = highlighter.warn(text)
const infoResult = highlighter.info(text)
const successResult = highlighter.success(text)
// All results should be strings
expect(typeof errorResult).toBe("string")
expect(typeof warnResult).toBe("string")
expect(typeof infoResult).toBe("string")
expect(typeof successResult).toBe("string")
// All results should have the same length as input
expect(errorResult.length).toBe(text.length)
expect(warnResult.length).toBe(text.length)
expect(infoResult.length).toBe(text.length)
expect(successResult.length).toBe(text.length)
} catch (error) {
console.error(`Failed with text: ${text}`, error)
throw error
}
}
})
test("should handle edge cases", () => {
const edgeCases = [
"", // Empty string
" ", // Single space
" ", // Multiple spaces
"0", // Zero as string
"null", // "null" as string
"undefined", // "undefined" as string
"NaN", // "NaN" as string
"true", // "true" as string
"false", // "false" as string
"[]", // Empty array as string
"{}", // Empty object as string
]
for (const text of edgeCases) {
try {
// Test each highlighter function
const errorResult = highlighter.error(text)
const warnResult = highlighter.warn(text)
const infoResult = highlighter.info(text)
const successResult = highlighter.success(text)
// All results should be strings
expect(typeof errorResult).toBe("string")
expect(typeof warnResult).toBe("string")
expect(typeof infoResult).toBe("string")
expect(typeof successResult).toBe("string")
// All results should have the same length as input
expect(errorResult.length).toBe(text.length)
expect(warnResult.length).toBe(text.length)
expect(infoResult.length).toBe(text.length)
expect(successResult.length).toBe(text.length)
} catch (error) {
console.error(`Failed with edge case: ${text}`, error)
throw error
}
}
})
})

View File

@@ -45,7 +45,7 @@ export async function updateCssVars(
cssFilepath
)
const cssVarsSpinner = spinner(
`Updating ${highlighter.info(cssFilepathRelative)}`,
`Updating CSS variables in ${highlighter.info(cssFilepathRelative)}`,
{
silent: options.silent,
}

View File

@@ -0,0 +1,324 @@
import { promises as fs } from "fs"
import path from "path"
import { registryItemCssSchema } from "@/src/registry/schema"
import { Config } from "@/src/utils/get-config"
import { highlighter } from "@/src/utils/highlighter"
import { spinner } from "@/src/utils/spinner"
import postcss from "postcss"
import AtRule from "postcss/lib/at-rule"
import Declaration from "postcss/lib/declaration"
import Root from "postcss/lib/root"
import Rule from "postcss/lib/rule"
import { z } from "zod"
export async function updateCss(
css: z.infer<typeof registryItemCssSchema> | undefined,
config: Config,
options: {
silent?: boolean
}
) {
if (
!config.resolvedPaths.tailwindCss ||
!css ||
Object.keys(css).length === 0
) {
return
}
options = {
silent: false,
...options,
}
const cssFilepath = config.resolvedPaths.tailwindCss
const cssFilepathRelative = path.relative(
config.resolvedPaths.cwd,
cssFilepath
)
const cssSpinner = spinner(
`Updating ${highlighter.info(cssFilepathRelative)}`,
{
silent: options.silent,
}
).start()
const raw = await fs.readFile(cssFilepath, "utf8")
let output = await transformCss(raw, css)
await fs.writeFile(cssFilepath, output, "utf8")
cssSpinner.succeed()
}
export async function transformCss(
input: string,
css: z.infer<typeof registryItemCssSchema>
) {
const plugins = [updateCssPlugin(css)]
const result = await postcss(plugins).process(input, {
from: undefined,
})
let output = result.css
output = output.replace(/\/\* ---break--- \*\//g, "")
output = output.replace(/(\n\s*\n)+/g, "\n\n")
output = output.trimEnd()
return output
}
function updateCssPlugin(css: z.infer<typeof registryItemCssSchema>) {
return {
postcssPlugin: "update-css",
Once(root: Root) {
for (const [selector, properties] of Object.entries(css)) {
if (selector.startsWith("@")) {
// Handle at-rules (@layer, @utility, etc.)
const atRuleMatch = selector.match(/@([a-zA-Z-]+)\s*(.*)/)
if (!atRuleMatch) continue
const [, name, params] = atRuleMatch
// Special handling for keyframes - place them under @theme inline
if (name === "keyframes") {
let themeInline = root.nodes?.find(
(node): node is AtRule =>
node.type === "atrule" &&
node.name === "theme" &&
node.params === "inline"
) as AtRule | undefined
if (!themeInline) {
themeInline = postcss.atRule({
name: "theme",
params: "inline",
raws: { semicolon: true, between: " ", before: "\n" },
})
root.append(themeInline)
root.insertBefore(
themeInline,
postcss.comment({ text: "---break---" })
)
}
const keyframesRule = postcss.atRule({
name: "keyframes",
params,
raws: { semicolon: true, between: " ", before: "\n " },
})
themeInline.append(keyframesRule)
if (typeof properties === "object") {
for (const [step, stepProps] of Object.entries(properties)) {
processRule(keyframesRule, step, stepProps)
}
}
}
// Special handling for utility classes to preserve property values
else if (name === "utility") {
const utilityAtRule = root.nodes?.find(
(node): node is AtRule =>
node.type === "atrule" &&
node.name === name &&
node.params === params
) as AtRule | undefined
if (!utilityAtRule) {
const atRule = postcss.atRule({
name,
params,
raws: { semicolon: true, between: " ", before: "\n" },
})
root.append(atRule)
root.insertBefore(
atRule,
postcss.comment({ text: "---break---" })
)
// Add declarations with their values preserved
if (typeof properties === "object") {
for (const [prop, value] of Object.entries(properties)) {
if (typeof value === "string") {
const decl = postcss.decl({
prop,
value: value,
raws: { semicolon: true, before: "\n " },
})
atRule.append(decl)
} else if (typeof value === "object") {
processRule(atRule, prop, value)
}
}
}
} else {
// Update existing utility class
if (typeof properties === "object") {
for (const [prop, value] of Object.entries(properties)) {
if (typeof value === "string") {
const existingDecl = utilityAtRule.nodes?.find(
(node): node is Declaration =>
node.type === "decl" && node.prop === prop
)
const decl = postcss.decl({
prop,
value: value,
raws: { semicolon: true, before: "\n " },
})
existingDecl
? existingDecl.replaceWith(decl)
: utilityAtRule.append(decl)
} else if (typeof value === "object") {
processRule(utilityAtRule, prop, value)
}
}
}
}
} else {
// Handle other at-rules normally
processAtRule(root, name, params, properties)
}
} else {
// Handle regular CSS rules
processRule(root, selector, properties)
}
}
},
}
}
function processAtRule(
root: Root | AtRule,
name: string,
params: string,
properties: any
) {
// Find or create the at-rule
let atRule = root.nodes?.find(
(node): node is AtRule =>
node.type === "atrule" && node.name === name && node.params === params
) as AtRule | undefined
if (!atRule) {
atRule = postcss.atRule({
name,
params,
raws: { semicolon: true, between: " ", before: "\n" },
})
root.append(atRule)
root.insertBefore(atRule, postcss.comment({ text: "---break---" }))
}
// Process children of this at-rule
if (typeof properties === "object") {
for (const [childSelector, childProps] of Object.entries(properties)) {
if (childSelector.startsWith("@")) {
// Nested at-rule
const nestedMatch = childSelector.match(/@([a-zA-Z-]+)\s*(.*)/)
if (nestedMatch) {
const [, nestedName, nestedParams] = nestedMatch
processAtRule(atRule, nestedName, nestedParams, childProps)
}
} else {
// CSS rule within at-rule
processRule(atRule, childSelector, childProps)
}
}
} else if (typeof properties === "string") {
// Direct string content for the at-rule
try {
// Parse the CSS string with PostCSS
const parsed = postcss.parse(`.temp{${properties}}`)
const tempRule = parsed.first as Rule
if (tempRule && tempRule.nodes) {
// Create a rule for the at-rule if needed
const rule = postcss.rule({
selector: "temp",
raws: { semicolon: true, between: " ", before: "\n " },
})
// Copy all declarations from the temp rule to our actual rule
tempRule.nodes.forEach((node) => {
if (node.type === "decl") {
const clone = node.clone()
clone.raws.before = "\n "
rule.append(clone)
}
})
// Only add the rule if it has declarations
if (rule.nodes?.length) {
atRule.append(rule)
}
}
} catch (error) {
console.error("Error parsing at-rule content:", properties, error)
throw error
}
}
}
function processRule(parent: Root | AtRule, selector: string, properties: any) {
let rule = parent.nodes?.find(
(node): node is Rule => node.type === "rule" && node.selector === selector
) as Rule | undefined
if (!rule) {
rule = postcss.rule({
selector,
raws: { semicolon: true, between: " ", before: "\n " },
})
parent.append(rule)
}
if (typeof properties === "object") {
for (const [prop, value] of Object.entries(properties)) {
if (typeof value === "string") {
const decl = postcss.decl({
prop,
value: value,
raws: { semicolon: true, before: "\n " },
})
// Replace existing property or add new one
const existingDecl = rule.nodes?.find(
(node): node is Declaration =>
node.type === "decl" && node.prop === prop
)
existingDecl ? existingDecl.replaceWith(decl) : rule.append(decl)
} else if (typeof value === "object") {
// Nested selector (including & selectors)
const nestedSelector = prop.startsWith("&")
? selector.replace(/^([^:]+)/, `$1${prop.substring(1)}`)
: prop // Use the original selector for other nested elements
processRule(parent, nestedSelector, value)
}
}
} else if (typeof properties === "string") {
// Direct string content for the rule
try {
// Parse the CSS string with PostCSS
const parsed = postcss.parse(`.temp{${properties}}`)
const tempRule = parsed.first as Rule
if (tempRule && tempRule.nodes) {
// Copy all declarations from the temp rule to our actual rule
tempRule.nodes.forEach((node) => {
if (node.type === "decl") {
const clone = node.clone()
clone.raws.before = "\n "
rule?.append(clone)
}
})
}
} catch (error) {
console.error("Error parsing rule content:", selector, properties, error)
throw error
}
}
}

View File

@@ -9,13 +9,16 @@ import prompts from "prompts"
export async function updateDependencies(
dependencies: RegistryItem["dependencies"],
devDependencies: RegistryItem["devDependencies"],
config: Config,
options: {
silent?: boolean
}
) {
dependencies = Array.from(new Set(dependencies))
if (!dependencies?.length) {
devDependencies = Array.from(new Set(devDependencies))
if (!dependencies?.length && !devDependencies?.length) {
return
}
@@ -59,23 +62,44 @@ export async function updateDependencies(
dependenciesSpinner?.start()
await execa(
packageManager,
[
packageManager === "npm" ? "install" : "add",
...(packageManager === "npm" && flag ? [`--${flag}`] : []),
...dependencies,
],
{
cwd: config.resolvedPaths.cwd,
}
)
if (dependencies?.length) {
await execa(
packageManager,
[
packageManager === "npm" ? "install" : "add",
...(packageManager === "npm" && flag ? [`--${flag}`] : []),
...(packageManager === "deno"
? dependencies.map((dep) => `npm:${dep}`)
: dependencies),
],
{
cwd: config.resolvedPaths.cwd,
}
)
}
if (devDependencies?.length) {
await execa(
packageManager,
[
packageManager === "npm" ? "install" : "add",
...(packageManager === "npm" && flag ? [`--${flag}`] : []),
"-D",
...(packageManager === "deno"
? devDependencies.map((dep) => `npm:${dep}`)
: devDependencies),
],
{
cwd: config.resolvedPaths.cwd,
}
)
}
dependenciesSpinner?.succeed()
}
function isUsingReact19(config: Config) {
const packageInfo = getPackageInfo(config.resolvedPaths.cwd)
const packageInfo = getPackageInfo(config.resolvedPaths.cwd, false)
if (!packageInfo?.dependencies?.react) {
return false

View File

@@ -1,4 +1,5 @@
import { existsSync, promises as fs } from "fs"
import { tmpdir } from "os"
import path, { basename } from "path"
import { getRegistryBaseColor } from "@/src/registry/api"
import { RegistryItem, registryItemFileSchema } from "@/src/registry/schema"
@@ -6,6 +7,7 @@ import { Config } from "@/src/utils/get-config"
import { ProjectInfo, getProjectInfo } from "@/src/utils/get-project-info"
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import { resolveImport } from "@/src/utils/resolve-import"
import { spinner } from "@/src/utils/spinner"
import { transform } from "@/src/utils/transformers"
import { transformCssVars } from "@/src/utils/transformers/transform-css-vars"
@@ -14,6 +16,8 @@ import { transformImport } from "@/src/utils/transformers/transform-import"
import { transformRsc } from "@/src/utils/transformers/transform-rsc"
import { transformTwPrefixes } from "@/src/utils/transformers/transform-tw-prefix"
import prompts from "prompts"
import { Project, ScriptKind } from "ts-morph"
import { loadConfig } from "tsconfig-paths"
import { z } from "zod"
export async function updateFiles(
@@ -50,9 +54,9 @@ export async function updateFiles(
getRegistryBaseColor(config.tailwind.baseColor),
])
const filesCreated = []
const filesUpdated = []
const filesSkipped = []
let filesCreated: string[] = []
let filesUpdated: string[] = []
let filesSkipped: string[] = []
for (const file of files) {
if (!file.content) {
@@ -153,11 +157,25 @@ export async function updateFiles(
: filesCreated.push(path.relative(config.resolvedPaths.cwd, filePath))
}
const allFiles = [...filesCreated, ...filesUpdated, ...filesSkipped]
const updatedFiles = await resolveImports(allFiles, config)
// Let's update filesUpdated with the updated files.
filesUpdated.push(...updatedFiles)
// If a file is in filesCreated and filesUpdated, we should remove it from filesUpdated.
filesUpdated = filesUpdated.filter((file) => !filesCreated.includes(file))
const hasUpdatedFiles = filesCreated.length || filesUpdated.length
if (!hasUpdatedFiles && !filesSkipped.length) {
filesCreatedSpinner?.info("No files updated.")
}
// Remove duplicates.
filesCreated = Array.from(new Set(filesCreated))
filesUpdated = Array.from(new Set(filesUpdated))
filesSkipped = Array.from(new Set(filesSkipped))
if (filesCreated.length) {
filesCreatedSpinner?.succeed(
`Created ${filesCreated.length} ${
@@ -371,3 +389,227 @@ export function resolvePageTarget(
return ""
}
async function resolveImports(filePaths: string[], config: Config) {
const project = new Project({
compilerOptions: {},
})
const projectInfo = await getProjectInfo(config.resolvedPaths.cwd)
const tsConfig = await loadConfig(config.resolvedPaths.cwd)
const updatedFiles = []
if (!projectInfo || tsConfig.resultType === "failed") {
return []
}
for (const filepath of filePaths) {
const resolvedPath = path.resolve(config.resolvedPaths.cwd, filepath)
// Check if the file exists.
if (!existsSync(resolvedPath)) {
continue
}
const content = await fs.readFile(resolvedPath, "utf-8")
const dir = await fs.mkdtemp(path.join(tmpdir(), "shadcn-"))
const sourceFile = project.createSourceFile(
path.join(dir, basename(resolvedPath)),
content,
{
scriptKind: ScriptKind.TSX,
}
)
const importDeclarations = sourceFile.getImportDeclarations()
for (const importDeclaration of importDeclarations) {
const moduleSpecifier = importDeclaration.getModuleSpecifierValue()
// Filter out non-local imports.
if (
projectInfo?.aliasPrefix &&
!moduleSpecifier.startsWith(`${projectInfo.aliasPrefix}/`)
) {
continue
}
// Find the probable import file path.
// This is where we expect to find the file on disk.
const probableImportFilePath = await resolveImport(
moduleSpecifier,
tsConfig
)
if (!probableImportFilePath) {
continue
}
// Find the actual import file path.
// This is the path where the file has been installed.
const resolvedImportFilePath = resolveModuleByProbablePath(
probableImportFilePath,
filePaths,
config
)
if (!resolvedImportFilePath) {
continue
}
// Convert the resolved import file path to an aliased import.
const newImport = toAliasedImport(
resolvedImportFilePath,
config,
projectInfo
)
if (!newImport || newImport === moduleSpecifier) {
continue
}
importDeclaration.setModuleSpecifier(newImport)
// Write the updated content to the file.
await fs.writeFile(resolvedPath, sourceFile.getFullText(), "utf-8")
// Track the updated file.
updatedFiles.push(filepath)
}
}
return updatedFiles
}
/**
* Given an absolute "probable" import path (no ext),
* plus an array of absolute file paths you already know about,
* return 0N matches (best match first), and also check disk for any missing ones.
*/
export function resolveModuleByProbablePath(
probableImportFilePath: string,
files: string[],
config: Config,
extensions: string[] = [".tsx", ".ts", ".js", ".jsx", ".css"]
) {
const cwd = path.normalize(config.resolvedPaths.cwd)
// 1) Build a set of POSIX-normalized, project-relative files
const relativeFiles = files.map((f) => f.split(path.sep).join(path.posix.sep))
const fileSet = new Set(relativeFiles)
// 2) Strip any existing extension off the absolute base path
const extInPath = path.extname(probableImportFilePath)
const hasExt = extInPath !== ""
const absBase = hasExt
? probableImportFilePath.slice(0, -extInPath.length)
: probableImportFilePath
// 3) Compute the project-relative "base" directory for strong matching
const relBaseRaw = path.relative(cwd, absBase)
const relBase = relBaseRaw.split(path.sep).join(path.posix.sep)
// 4) Decide which extensions to try
const tryExts = hasExt ? [extInPath] : extensions
// 5) Collect candidates
const candidates = new Set<string>()
// 5a) Fastpath: [base + ext] and [base/index + ext]
for (const e of tryExts) {
const absCand = absBase + e
const relCand = path.posix.normalize(path.relative(cwd, absCand))
if (fileSet.has(relCand) || existsSync(absCand)) {
candidates.add(relCand)
}
const absIdx = path.join(absBase, `index${e}`)
const relIdx = path.posix.normalize(path.relative(cwd, absIdx))
if (fileSet.has(relIdx) || existsSync(absIdx)) {
candidates.add(relIdx)
}
}
// 5b) Fallback: scan known files by basename
const name = path.basename(absBase)
for (const f of relativeFiles) {
if (tryExts.some((e) => f.endsWith(`/${name}${e}`))) {
candidates.add(f)
}
}
// 6) If no matches, bail
if (candidates.size === 0) return null
// 7) Sort by (1) extension priority, then (2) "strong" base match
const sorted = Array.from(candidates).sort((a, b) => {
// a) extension order
const aExt = path.posix.extname(a)
const bExt = path.posix.extname(b)
const ord = tryExts.indexOf(aExt) - tryExts.indexOf(bExt)
if (ord !== 0) return ord
// b) strong match if path starts with relBase
const aStrong = relBase && a.startsWith(relBase) ? -1 : 1
const bStrong = relBase && b.startsWith(relBase) ? -1 : 1
return aStrong - bStrong
})
// 8) Return the first (best) candidate
return sorted[0]
}
export function toAliasedImport(
filePath: string,
config: Config,
projectInfo: ProjectInfo
): string | null {
const abs = path.normalize(path.join(config.resolvedPaths.cwd, filePath))
// 1⃣ Find the longest matching alias root in resolvedPaths
// e.g. key="ui", root="/…/components/ui" beats key="components"
const matches = Object.entries(config.resolvedPaths)
.filter(
([, root]) => root && abs.startsWith(path.normalize(root + path.sep))
)
.sort((a, b) => b[1].length - a[1].length)
if (matches.length === 0) {
return null
}
const [aliasKey, rootDir] = matches[0]
// 2⃣ Compute the path UNDER that root
let rel = path.relative(rootDir, abs)
// force POSIX-style separators
rel = rel.split(path.sep).join("/") // e.g. "button/index.tsx"
// 3⃣ Strip code-file extensions, keep others (css, json, etc.)
const ext = path.posix.extname(rel)
const codeExts = [".ts", ".tsx", ".js", ".jsx"]
const keepExt = codeExts.includes(ext) ? "" : ext
let noExt = rel.slice(0, rel.length - ext.length)
// 4⃣ Collapse "/index" to its directory
if (noExt.endsWith("/index")) {
noExt = noExt.slice(0, -"/index".length)
}
// 5⃣ Build the aliased path
// config.aliases[aliasKey] is e.g. "@/components/ui"
const aliasBase =
aliasKey === "cwd"
? projectInfo.aliasPrefix
: config.aliases[aliasKey as keyof typeof config.aliases]
if (!aliasBase) {
return null
}
// if noExt is empty (i.e. file was exactly at the root), we import the root
let suffix = noExt === "" ? "" : `/${noExt}`
// Rremove /src from suffix.
// Alias will handle this.
suffix = suffix.replace("/src", "")
// 6⃣ Prepend the prefix from projectInfo (e.g. "@") if needed
// but usually config.aliases already include it.
return `${aliasBase}${suffix}${keepExt}`
}

View File

@@ -0,0 +1 @@
{}

0
packages/shadcn/test/fixtures/project-deno/deno.lock generated vendored Normal file
View File

View File

@@ -0,0 +1,13 @@
{
"name": "test-cli-npm-project",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "npm-project",
"version": "1.0.0",
"license": "MIT"
}
}
}

View File

@@ -0,0 +1,10 @@
{
"name": "test-cli-project-npm",
"version": "1.0.0",
"main": "index.js",
"author": "shadcn",
"license": "MIT",
"dependencies": {
"react": "19.0.0"
}
}

View File

@@ -1,16 +0,0 @@
export const generateRandomString = (length: number): string => {
const chars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;:,.<>?"
return Array.from(
{ length },
() => chars[Math.floor(Math.random() * chars.length)]
).join("")
}
export const generateRandomPath = (): string => {
const segments = Array.from(
{ length: Math.floor(Math.random() * 5) + 1 },
() => generateRandomString(Math.floor(Math.random() * 10) + 1)
)
return segments.join("/")
}

View File

@@ -1,9 +1,8 @@
import path from "path"
import { loadConfig, type ConfigLoaderSuccessResult } from "tsconfig-paths"
import { describe, expect, test } from "vitest"
import { expect, test } from "vitest"
import { resolveImport } from "../../src/utils/resolve-import"
import { generateRandomPath } from "../fuzz-utils"
test("resolve import", async () => {
expect(
@@ -80,88 +79,3 @@ test("resolve import without base url", async () => {
path.resolve(cwd, "foo/bar")
)
})
describe("fuzzing", () => {
test("should handle various import paths", async () => {
const generateRandomConfig = (): Pick<
ConfigLoaderSuccessResult,
"absoluteBaseUrl" | "paths"
> => ({
absoluteBaseUrl: generateRandomPath(),
paths: {
"@/*": [generateRandomPath()],
"@/components/*": [generateRandomPath()],
"@/lib/*": [generateRandomPath()],
},
})
const testCases = Array.from({ length: 100 }, () => ({
importPath: generateRandomPath(),
config: generateRandomConfig(),
}))
for (const { importPath, config } of testCases) {
try {
const result = await resolveImport(importPath, config)
// Should either return undefined or a valid path
if (result) {
expect(typeof result).toBe("string")
expect(result.length).toBeGreaterThan(0)
}
} catch (error) {
// Expected for invalid paths
expect(error).toBeDefined()
}
}
})
test("should handle edge cases", async () => {
const edgeCases: Array<{
importPath: string
config: Pick<ConfigLoaderSuccessResult, "absoluteBaseUrl" | "paths">
}> = [
{
importPath: "",
config: {
absoluteBaseUrl: "",
paths: {
"@/*": [""],
},
},
},
{
importPath: "/",
config: {
absoluteBaseUrl: "/",
paths: {
"@/*": ["/"],
},
},
},
{
importPath: "@/components/button",
config: {
absoluteBaseUrl: "/",
paths: {
"@/*": ["/"],
"@/components/*": ["/components"],
},
},
},
]
for (const { importPath, config } of edgeCases) {
try {
const result = await resolveImport(importPath, config)
// Should either return undefined or a valid path
if (result) {
expect(typeof result).toBe("string")
expect(result.length).toBeGreaterThan(0)
}
} catch (error) {
console.error(`Failed with edge case:`, { importPath, config }, error)
throw error
}
}
})
})

View File

@@ -2,6 +2,7 @@
exports[`registryResolveItemTree > should resolve index 1`] = `
{
"css": {},
"cssVars": {
"dark": {},
"light": {
@@ -87,6 +88,7 @@ export { Label }
exports[`registryResolveItemTree > should resolve items tree 1`] = `
{
"css": {},
"cssVars": {},
"dependencies": [
"@radix-ui/react-slot",
@@ -164,6 +166,7 @@ export { Button, buttonVariants }
exports[`registryResolveItemTree > should resolve multiple items tree 1`] = `
{
"css": {},
"cssVars": {},
"dependencies": [
"@radix-ui/react-slot",

View File

@@ -0,0 +1,331 @@
import { describe, expect, test } from "vitest"
import { transformCss } from "../../../src/utils/updaters/update-css"
describe("transformCss", () => {
test("should add utility classes", async () => {
const input = `@import "tailwindcss";`
const result = await transformCss(input, {
"@utility content-auto": {
"content-visibility": "auto",
},
})
expect(result).toMatchInlineSnapshot(`
"@import "tailwindcss";
@utility content-auto {
content-visibility: auto;
}"
`)
})
test("should add utility classes with pseudo-selectors", async () => {
const input = `@import "tailwindcss";`
const result = await transformCss(input, {
"@utility scrollbar-hidden": {
"&::-webkit-scrollbar": {
display: "none",
},
},
})
expect(result).toMatchInlineSnapshot(`
"@import "tailwindcss";
@utility scrollbar-hidden {
&::-webkit-scrollbar {
display: none;
}
}"
`)
})
test("should add parameterized utility classes", async () => {
const input = `@import "tailwindcss";`
const result = await transformCss(input, {
"@utility tab-*": {
"tab-size": "--value([integer])",
},
})
expect(result).toMatchInlineSnapshot(`
"@import "tailwindcss";
@utility tab-* {
tab-size: --value([integer]);
}"
`)
})
test("should add component styles", async () => {
const input = `@tailwind base;
@tailwind components;
@tailwind utilities;`
const result = await transformCss(input, {
"@layer components": {
".card": {
"background-color": "var(--color-white)",
"border-radius": "var(--rounded-lg)",
padding: "var(--spacing-6)",
"box-shadow": "var(--shadow-xl)",
},
},
})
expect(result).toMatchInlineSnapshot(`
"@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.card {
background-color: var(--color-white);
border-radius: var(--rounded-lg);
padding: var(--spacing-6);
box-shadow: var(--shadow-xl);
}
}"
`)
})
test("should add base styles", async () => {
const input = `@tailwind base;
@tailwind components;
@tailwind utilities;`
const result = await transformCss(input, {
"@layer base": {
h1: {
"font-size": "var(--text-2xl)",
},
h2: {
"font-size": "var(--text-xl)",
},
},
})
expect(result).toMatchInlineSnapshot(`
"@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
h1 {
font-size: var(--text-2xl);
}
h2 {
font-size: var(--text-xl);
}
}"
`)
})
test("should update existing rules", async () => {
const input = `@import "tailwindcss";
@layer components {
.card {
background-color: white;
padding: 1rem;
}
}`
const result = await transformCss(input, {
"@layer components": {
".card": {
"background-color": "var(--color-white)",
"border-radius": "var(--rounded-lg)",
"box-shadow": "var(--shadow-xl)",
},
},
})
expect(result).toMatchInlineSnapshot(`
"@import "tailwindcss";
@layer components {
.card {
background-color: var(--color-white);
padding: 1rem;
border-radius: var(--rounded-lg);
box-shadow: var(--shadow-xl);
}
}"
`)
})
test("should add multiple rules and types", async () => {
const input = `@tailwind base;
@tailwind components;
@tailwind utilities;`
const result = await transformCss(input, {
"@utility content-auto": {
"content-visibility": "auto",
},
"@layer components": {
".card": {
"background-color": "var(--color-white)",
"border-radius": "var(--rounded-lg)",
},
},
"@layer base": {
h1: {
"font-size": "var(--text-2xl)",
},
},
})
expect(result).toMatchInlineSnapshot(`
"@tailwind base;
@tailwind components;
@tailwind utilities;
@utility content-auto {
content-visibility: auto;
}
@layer components {
.card {
background-color: var(--color-white);
border-radius: var(--rounded-lg);
}
}
@layer base {
h1 {
font-size: var(--text-2xl);
}
}"
`)
})
test("should handle nested selectors with &", async () => {
const input = `@tailwind base;
@tailwind components;
@tailwind utilities;`
const result = await transformCss(input, {
"@layer components": {
".button": {
"background-color": "var(--color-primary)",
"&:hover": {
"background-color": "var(--color-primary-dark)",
},
"&:active": {
"background-color": "var(--color-primary-darker)",
},
},
},
})
expect(result).toMatchInlineSnapshot(`
"@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.button {
background-color: var(--color-primary);
}
.button:hover {
background-color: var(--color-primary-dark);
}
.button:active {
background-color: var(--color-primary-darker);
}
}"
`)
})
test("should handle direct string content", async () => {
const input = `@tailwind base;
@tailwind components;
@tailwind utilities;`
const result = await transformCss(input, {
"@layer base": {
body: "font-family: var(--font-sans); line-height: 1.5;",
},
})
expect(result).toMatchInlineSnapshot(`
"@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
body {
font-family: var(--font-sans);
line-height: 1.5;
}
}"
`)
})
test("should handle nested at-rules", async () => {
const input = `@tailwind base;
@tailwind components;
@tailwind utilities;`
const result = await transformCss(input, {
"@layer components": {
"@media (min-width: 768px)": {
".card": {
padding: "2rem",
},
},
},
})
expect(result).toMatchInlineSnapshot(`
"@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
@media (min-width: 768px) {
.card {
padding: 2rem;
}
}
}"
`)
})
test("should place keyframes under @theme inline directive", async () => {
const input = `@import "tailwindcss";`
const result = await transformCss(input, {
"@keyframes spin": {
"0%": {
transform: "rotate(0deg)",
},
"100%": {
transform: "rotate(360deg)",
},
},
})
expect(result).toMatchInlineSnapshot(`
"@import "tailwindcss";
@theme inline {
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
}"
`)
})
})

View File

@@ -0,0 +1,139 @@
import { vi, describe, afterEach, test, expect } from "vitest"
import { execa } from "execa"
import prompts from "prompts"
import { updateDependencies } from "../../../src/utils/updaters/update-dependencies"
import path from "path"
vi.mock("execa")
vi.mock("prompts")
describe("updateDependencies", () => {
afterEach(() => {
vi.restoreAllMocks()
})
test.each([
{
description: "npm without react 19 includes no additional flags",
options: { silent: true },
dependencies: ["first", "second", "third"],
devDependencies: ["fourth"],
config: {
resolvedPaths: {
cwd: path.resolve(__dirname, "../../fixtures/project-npm")
}
},
expectedPackageManager: "npm",
expectedArgs: ["install", "first", "second", "third"],
expectedDevArgs: ["install", "-D", "fourth"]
},
{
description: "npm with react 19 applies force prompt when silent",
options: { silent: true },
dependencies: ["first", "second", "third"],
devDependencies: ["fourth"],
config: {
resolvedPaths: {
cwd: path.resolve(__dirname, "../../fixtures/project-npm-react19")
}
},
expectedPackageManager: "npm",
expectedArgs: ["install", "--force", "first", "second", "third"],
expectedDevArgs: ["install", "--force", "-D", "fourth"]
},
{
description: "npm with react 19 prompts for flag when not silent",
flagPrompt: "legacy-peer-deps",
dependencies: ["first", "second", "third"],
devDependencies: ["fourth"],
config: {
resolvedPaths: {
cwd: path.resolve(__dirname, "../../fixtures/project-npm-react19")
}
},
expectedPackageManager: "npm",
expectedArgs: ["install", "--legacy-peer-deps", "first", "second", "third"],
expectedDevArgs: ["install", "--legacy-peer-deps", "-D", "fourth"]
},
{
description: "deno uses npm: package prefix",
dependencies: ["first", "second", "third"],
devDependencies: ["fourth"],
config: {
resolvedPaths: {
cwd: path.resolve(__dirname, "../../fixtures/project-deno")
}
},
expectedPackageManager: "deno",
expectedArgs: ["add", "npm:first", "npm:second", "npm:third"],
expectedDevArgs: ["add", "-D", "npm:fourth"]
},
{
description: "bun uses bun",
dependencies: ["first", "second", "third"],
devDependencies: ["fourth"],
config: {
resolvedPaths: {
cwd: path.resolve(__dirname, "../../fixtures/project-bun")
}
},
expectedPackageManager: "bun",
expectedArgs: ["add", "first", "second", "third"],
expectedDevArgs: ["add", "-D", "fourth"]
},
{
description: "pnpm uses pnpm",
dependencies: ["first", "second", "third"],
devDependencies: ["fourth"],
config: {
resolvedPaths: {
cwd: path.resolve(__dirname, "../../fixtures/project-pnpm")
}
},
expectedPackageManager: "pnpm",
expectedArgs: ["add", "first", "second", "third"],
expectedDevArgs: ["add", "-D", "fourth"]
},
{
description: "deduplicates input dependencies",
options: { silent: true },
dependencies: ["first", "first"],
devDependencies: ["second", "second"],
config: {
resolvedPaths: {
cwd: path.resolve(__dirname, "../../fixtures/project-npm")
}
},
expectedPackageManager: "npm",
expectedArgs: ["install", "first"],
expectedDevArgs: ["install", "-D", "second"]
}
])("$description", async ({ options, flagPrompt, config, dependencies, devDependencies, expectedPackageManager, expectedArgs, expectedDevArgs }) => {
vi.mocked(prompts).mockResolvedValue({ flag: flagPrompt })
await updateDependencies(
dependencies,
devDependencies,
config,
options ?? {}
)
if (flagPrompt) {
expect(prompts).toHaveBeenCalled()
}
expect(execa).toHaveBeenCalledWith(
expectedPackageManager,
expectedArgs,
{ cwd: config?.resolvedPaths.cwd }
)
expect(execa).toHaveBeenCalledWith(
expectedPackageManager,
expectedDevArgs,
{ cwd: config?.resolvedPaths.cwd }
)
})
})

View File

@@ -1,3 +1,4 @@
import { existsSync } from "fs"
import path from "path"
import { afterAll, afterEach, describe, expect, test, vi } from "vitest"
@@ -5,7 +6,9 @@ import { getConfig } from "../../../src/utils/get-config"
import {
findCommonRoot,
resolveFilePath,
resolveModuleByProbablePath,
resolveNestedFilePath,
toAliasedImport,
updateFiles,
} from "../../../src/utils/updaters/update-files"
@@ -809,3 +812,340 @@ return <div>Hello World</div>
`)
})
})
describe("resolveModuleByProbablePath", () => {
test("should resolve exact file match in provided files list", () => {
const files = [
"components/button.tsx",
"components/card.tsx",
"lib/utils.ts",
]
const config = {
resolvedPaths: {
cwd: "/foo/bar",
},
}
expect(
resolveModuleByProbablePath("/foo/bar/components/button", files, config)
).toBe("components/button.tsx")
})
test("should resolve index file", () => {
const files = ["components/button/index.tsx", "components/card.tsx"]
const config = {
resolvedPaths: {
cwd: "/foo/bar",
},
}
expect(
resolveModuleByProbablePath("/foo/bar/components/button", files, config)
).toBe("components/button/index.tsx")
})
test("should try different extensions", () => {
const files = ["components/button.jsx", "components/card.tsx"]
const config = {
resolvedPaths: {
cwd: "/foo/bar",
},
}
expect(
resolveModuleByProbablePath("/foo/bar/components/button", files, config)
).toBe("components/button.jsx")
})
test("should fallback to basename matching", () => {
const files = ["components/ui/button.tsx", "components/card.tsx"]
const config = {
resolvedPaths: {
cwd: "/foo/bar",
},
}
expect(
resolveModuleByProbablePath("/foo/bar/components/button", files, config)
).toBe("components/ui/button.tsx")
})
test("should return null when file not found", () => {
const files = ["components/card.tsx", "lib/utils.ts"]
const config = {
resolvedPaths: {
cwd: "/foo/bar",
},
}
expect(
resolveModuleByProbablePath("/foo/bar/components/button", files, config)
).toBeNull()
})
test("should sort by extension priority", () => {
const files = [
"components/button.jsx",
"components/button.tsx",
"components/button.js",
]
const config = {
resolvedPaths: {
cwd: "/foo/bar",
},
}
expect(
resolveModuleByProbablePath("/foo/bar/components/button", files, config, [
".tsx",
".jsx",
".js",
])
).toBe("components/button.tsx")
})
test("should preserve extension if specified in path", () => {
const files = ["components/button.tsx", "components/button.css"]
const config = {
resolvedPaths: {
cwd: "/foo/bar",
},
}
expect(
resolveModuleByProbablePath(
"/foo/bar/components/button.css",
files,
config
)
).toBe("components/button.css")
})
})
describe("toAliasedImport", () => {
test("should convert components path to aliased import", () => {
const filePath = "components/button.tsx"
const config = {
resolvedPaths: {
cwd: "/foo/bar",
components: "/foo/bar/components",
ui: "/foo/bar/components/ui",
lib: "/foo/bar/lib",
},
aliases: {
components: "@/components",
ui: "@/components/ui",
lib: "@/lib",
},
}
const projectInfo = {
aliasPrefix: "@",
}
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
"@/components/button"
)
})
test("should convert ui path to aliased import", () => {
const filePath = "components/ui/button.tsx"
const config = {
resolvedPaths: {
cwd: "/foo/bar",
components: "/foo/bar/components",
ui: "/foo/bar/components/ui",
lib: "/foo/bar/lib",
},
aliases: {
components: "@/components",
ui: "@/components/ui",
lib: "@/lib",
},
}
const projectInfo = {
aliasPrefix: "@",
}
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
"@/components/ui/button"
)
})
test("should collapse index files", () => {
const filePath = "components/ui/button/index.tsx"
const config = {
resolvedPaths: {
cwd: "/foo/bar",
components: "/foo/bar/components",
ui: "/foo/bar/components/ui",
lib: "/foo/bar/lib",
},
aliases: {
components: "@/components",
ui: "@/components/ui",
lib: "@/lib",
},
}
const projectInfo = {
aliasPrefix: "@",
}
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
"@/components/ui/button"
)
})
test("should return null when no matching alias found", () => {
const filePath = "src/pages/index.tsx"
const config = {
resolvedPaths: {
cwd: "/foo/bar",
components: "/foo/bar/components",
ui: "/foo/bar/components/ui",
lib: "/foo/bar/lib",
},
aliases: {
components: "@/components",
ui: "@/components/ui",
lib: "@/lib",
},
}
const projectInfo = {
aliasPrefix: "@",
}
expect(toAliasedImport(filePath, config, projectInfo)).toBe("@/pages")
})
test("should handle nested directories", () => {
const filePath = "components/forms/inputs/text-input.tsx"
const config = {
resolvedPaths: {
cwd: "/foo/bar",
components: "/foo/bar/components",
ui: "/foo/bar/components/ui",
lib: "/foo/bar/lib",
},
aliases: {
components: "@/components",
ui: "@/components/ui",
lib: "@/lib",
},
}
const projectInfo = {
aliasPrefix: "@",
}
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
"@/components/forms/inputs/text-input"
)
})
test("should keep non-code file extensions", () => {
const filePath = "components/styles/theme.css"
const config = {
resolvedPaths: {
cwd: "/foo/bar",
components: "/foo/bar/components",
ui: "/foo/bar/components/ui",
lib: "/foo/bar/lib",
},
aliases: {
components: "@/components",
ui: "@/components/ui",
lib: "@/lib",
},
}
const projectInfo = {
aliasPrefix: "@",
}
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
"@/components/styles/theme.css"
)
})
test("should prefer longer matching paths", () => {
const filePath = "components/ui/button.tsx"
const config = {
resolvedPaths: {
cwd: "/foo/bar",
components: "/foo/bar/components",
ui: "/foo/bar/components/ui",
},
aliases: {
components: "@/components",
ui: "@/ui",
},
}
const projectInfo = {
aliasPrefix: "@",
}
expect(toAliasedImport(filePath, config, projectInfo)).toBe("@/ui/button")
})
test("should support tilde (~) alias prefix", () => {
const filePath = "components/button.tsx"
const config = {
resolvedPaths: {
cwd: "/foo/bar",
components: "/foo/bar/components",
},
aliases: {
components: "~components",
},
}
const projectInfo = {
aliasPrefix: "~",
}
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
"~components/button"
)
})
test("should support @shadcn alias prefix", () => {
const filePath = "components/ui/button.tsx"
const config = {
resolvedPaths: {
cwd: "/foo/bar",
components: "/foo/bar/components",
ui: "/foo/bar/components/ui",
},
aliases: {
components: "@shadcn/components",
ui: "@shadcn/ui",
},
}
const projectInfo = {
aliasPrefix: "@shadcn",
}
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
"@shadcn/ui/button"
)
})
test("should support ~cn alias prefix", () => {
const filePath = "lib/utils/index.tsx"
const config = {
resolvedPaths: {
cwd: "/foo/bar",
lib: "/foo/bar/lib",
},
aliases: {
lib: "~cn/lib",
},
}
const projectInfo = {
aliasPrefix: "~cn",
}
expect(toAliasedImport(filePath, config, projectInfo)).toBe("~cn/lib/utils")
})
test("should use project alias prefix when aliasKey is cwd", () => {
const filePath = "src/pages/home.tsx"
const config = {
resolvedPaths: {
cwd: "/foo/bar",
components: "/foo/bar/components",
ui: "/foo/bar/components/ui",
lib: "/foo/bar/lib",
},
aliases: {
components: "@/components",
ui: "@/components/ui",
lib: "@/lib",
},
}
const projectInfo = {
aliasPrefix: "@",
}
expect(toAliasedImport(filePath, config, projectInfo)).toBe("@/pages/home")
})
})

1445
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@
"dependencies": {
"@workspace/ui": "workspace:*",
"lucide-react": "^0.475.0",
"next": "^15.2.0",
"next": "^15.2.3",
"next-themes": "^0.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0"

View File

@@ -33,8 +33,8 @@ importers:
specifier: ^0.475.0
version: 0.475.0(react@19.0.0)
next:
specifier: ^15.2.0
version: 15.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
specifier: ^15.2.3
version: 15.2.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
next-themes:
specifier: ^0.4.4
version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@@ -356,56 +356,56 @@ packages:
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@next/env@15.2.0':
resolution: {integrity: sha512-eMgJu1RBXxxqqnuRJQh5RozhskoNUDHBFybvi+Z+yK9qzKeG7dadhv/Vp1YooSZmCnegf7JxWuapV77necLZNA==}
'@next/env@15.2.3':
resolution: {integrity: sha512-a26KnbW9DFEUsSxAxKBORR/uD9THoYoKbkpFywMN/AFvboTt94b8+g/07T8J6ACsdLag8/PDU60ov4rPxRAixw==}
'@next/eslint-plugin-next@15.1.7':
resolution: {integrity: sha512-kRP7RjSxfTO13NE317ek3mSGzoZlI33nc/i5hs1KaWpK+egs85xg0DJ4p32QEiHnR0mVjuUfhRIun7awqfL7pQ==}
'@next/swc-darwin-arm64@15.2.0':
resolution: {integrity: sha512-rlp22GZwNJjFCyL7h5wz9vtpBVuCt3ZYjFWpEPBGzG712/uL1bbSkS675rVAUCRZ4hjoTJ26Q7IKhr5DfJrHDA==}
'@next/swc-darwin-arm64@15.2.3':
resolution: {integrity: sha512-uaBhA8aLbXLqwjnsHSkxs353WrRgQgiFjduDpc7YXEU0B54IKx3vU+cxQlYwPCyC8uYEEX7THhtQQsfHnvv8dw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@next/swc-darwin-x64@15.2.0':
resolution: {integrity: sha512-DiU85EqSHogCz80+sgsx90/ecygfCSGl5P3b4XDRVZpgujBm5lp4ts7YaHru7eVTyZMjHInzKr+w0/7+qDrvMA==}
'@next/swc-darwin-x64@15.2.3':
resolution: {integrity: sha512-pVwKvJ4Zk7h+4hwhqOUuMx7Ib02u3gDX3HXPKIShBi9JlYllI0nU6TWLbPT94dt7FSi6mSBhfc2JrHViwqbOdw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@next/swc-linux-arm64-gnu@15.2.0':
resolution: {integrity: sha512-VnpoMaGukiNWVxeqKHwi8MN47yKGyki5q+7ql/7p/3ifuU2341i/gDwGK1rivk0pVYbdv5D8z63uu9yMw0QhpQ==}
'@next/swc-linux-arm64-gnu@15.2.3':
resolution: {integrity: sha512-50ibWdn2RuFFkOEUmo9NCcQbbV9ViQOrUfG48zHBCONciHjaUKtHcYFiCwBVuzD08fzvzkWuuZkd4AqbvKO7UQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-arm64-musl@15.2.0':
resolution: {integrity: sha512-ka97/ssYE5nPH4Qs+8bd8RlYeNeUVBhcnsNUmFM6VWEob4jfN9FTr0NBhXVi1XEJpj3cMfgSRW+LdE3SUZbPrw==}
'@next/swc-linux-arm64-musl@15.2.3':
resolution: {integrity: sha512-2gAPA7P652D3HzR4cLyAuVYwYqjG0mt/3pHSWTCyKZq/N/dJcUAEoNQMyUmwTZWCJRKofB+JPuDVP2aD8w2J6Q==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-x64-gnu@15.2.0':
resolution: {integrity: sha512-zY1JduE4B3q0k2ZCE+DAF/1efjTXUsKP+VXRtrt/rJCTgDlUyyryx7aOgYXNc1d8gobys/Lof9P9ze8IyRDn7Q==}
'@next/swc-linux-x64-gnu@15.2.3':
resolution: {integrity: sha512-ODSKvrdMgAJOVU4qElflYy1KSZRM3M45JVbeZu42TINCMG3anp7YCBn80RkISV6bhzKwcUqLBAmOiWkaGtBA9w==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-linux-x64-musl@15.2.0':
resolution: {integrity: sha512-QqvLZpurBD46RhaVaVBepkVQzh8xtlUN00RlG4Iq1sBheNugamUNPuZEH1r9X1YGQo1KqAe1iiShF0acva3jHQ==}
'@next/swc-linux-x64-musl@15.2.3':
resolution: {integrity: sha512-ZR9kLwCWrlYxwEoytqPi1jhPd1TlsSJWAc+H/CJHmHkf2nD92MQpSRIURR1iNgA/kuFSdxB8xIPt4p/T78kwsg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-win32-arm64-msvc@15.2.0':
resolution: {integrity: sha512-ODZ0r9WMyylTHAN6pLtvUtQlGXBL9voljv6ujSlcsjOxhtXPI1Ag6AhZK0SE8hEpR1374WZZ5w33ChpJd5fsjw==}
'@next/swc-win32-arm64-msvc@15.2.3':
resolution: {integrity: sha512-+G2FrDcfm2YDbhDiObDU/qPriWeiz/9cRR0yMWJeTLGGX6/x8oryO3tt7HhodA1vZ8r2ddJPCjtLcpaVl7TE2Q==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@next/swc-win32-x64-msvc@15.2.0':
resolution: {integrity: sha512-8+4Z3Z7xa13NdUuUAcpVNA6o76lNPniBd9Xbo02bwXQXnZgFvEopwY2at5+z7yHl47X9qbZpvwatZ2BRo3EdZw==}
'@next/swc-win32-x64-msvc@15.2.3':
resolution: {integrity: sha512-gHYS9tc+G2W0ZC8rBL+H6RdtXIyk40uLiaos0yj5US85FNhbFEndMA2nW3z47nzOWiSvXTZ5kBClc3rD0zJg0w==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@@ -764,8 +764,8 @@ packages:
camel-case@3.0.0:
resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==}
caniuse-lite@1.0.30001700:
resolution: {integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==}
caniuse-lite@1.0.30001707:
resolution: {integrity: sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==}
chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
@@ -1629,6 +1629,11 @@ packages:
mute-stream@0.0.8:
resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
nanoid@3.3.8:
resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -1650,8 +1655,8 @@ packages:
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
next@15.2.0:
resolution: {integrity: sha512-VaiM7sZYX8KIAHBrRGSFytKknkrexNfGb8GlG6e93JqueCspuGte8i4ybn8z4ww1x3f2uzY4YpTaBEW4/hvsoQ==}
next@15.2.3:
resolution: {integrity: sha512-x6eDkZxk2rPpu46E1ZVUWIBhYCLszmUY6fvHBFcbzJ9dD+qRX6vcHusaqqDlnY+VngKzKbAiG2iRCkPbmi8f7w==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
hasBin: true
peerDependencies:
@@ -2480,34 +2485,34 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
'@next/env@15.2.0': {}
'@next/env@15.2.3': {}
'@next/eslint-plugin-next@15.1.7':
dependencies:
fast-glob: 3.3.1
'@next/swc-darwin-arm64@15.2.0':
'@next/swc-darwin-arm64@15.2.3':
optional: true
'@next/swc-darwin-x64@15.2.0':
'@next/swc-darwin-x64@15.2.3':
optional: true
'@next/swc-linux-arm64-gnu@15.2.0':
'@next/swc-linux-arm64-gnu@15.2.3':
optional: true
'@next/swc-linux-arm64-musl@15.2.0':
'@next/swc-linux-arm64-musl@15.2.3':
optional: true
'@next/swc-linux-x64-gnu@15.2.0':
'@next/swc-linux-x64-gnu@15.2.3':
optional: true
'@next/swc-linux-x64-musl@15.2.0':
'@next/swc-linux-x64-musl@15.2.3':
optional: true
'@next/swc-win32-arm64-msvc@15.2.0':
'@next/swc-win32-arm64-msvc@15.2.3':
optional: true
'@next/swc-win32-x64-msvc@15.2.0':
'@next/swc-win32-x64-msvc@15.2.3':
optional: true
'@nodelib/fs.scandir@2.1.5':
@@ -2925,7 +2930,7 @@ snapshots:
no-case: 2.3.2
upper-case: 1.1.3
caniuse-lite@1.0.30001700: {}
caniuse-lite@1.0.30001707: {}
chalk@2.4.2:
dependencies:
@@ -3938,6 +3943,8 @@ snapshots:
mute-stream@0.0.8: {}
nanoid@3.3.11: {}
nanoid@3.3.8: {}
natural-compare@1.4.0: {}
@@ -3951,26 +3958,26 @@ snapshots:
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
next@15.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
next@15.2.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
'@next/env': 15.2.0
'@next/env': 15.2.3
'@swc/counter': 0.1.3
'@swc/helpers': 0.5.15
busboy: 1.6.0
caniuse-lite: 1.0.30001700
caniuse-lite: 1.0.30001707
postcss: 8.4.31
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
styled-jsx: 5.1.6(react@19.0.0)
optionalDependencies:
'@next/swc-darwin-arm64': 15.2.0
'@next/swc-darwin-x64': 15.2.0
'@next/swc-linux-arm64-gnu': 15.2.0
'@next/swc-linux-arm64-musl': 15.2.0
'@next/swc-linux-x64-gnu': 15.2.0
'@next/swc-linux-x64-musl': 15.2.0
'@next/swc-win32-arm64-msvc': 15.2.0
'@next/swc-win32-x64-msvc': 15.2.0
'@next/swc-darwin-arm64': 15.2.3
'@next/swc-darwin-x64': 15.2.3
'@next/swc-linux-arm64-gnu': 15.2.3
'@next/swc-linux-arm64-musl': 15.2.3
'@next/swc-linux-x64-gnu': 15.2.3
'@next/swc-linux-x64-musl': 15.2.3
'@next/swc-win32-arm64-msvc': 15.2.3
'@next/swc-win32-x64-msvc': 15.2.3
sharp: 0.33.5
transitivePeerDependencies:
- '@babel/core'
@@ -4148,7 +4155,7 @@ snapshots:
postcss@8.4.31:
dependencies:
nanoid: 3.3.8
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1