Compare commits

...

24 Commits

Author SHA1 Message Date
shadcn
0826d58d3a chore: ignore errors 2025-05-29 13:36:19 +04:00
shadcn
995b6fd1a8 fix: format 2025-05-29 12:59:39 +04:00
shadcn
410998525b fix: to v3 2025-05-29 12:47:58 +04:00
shadcn
0f18c2775a Merge branch 'main' into v2 2025-05-29 12:45:59 +04:00
Thibault Le Ouay
b84c990e42 fix registry item schema (#7486) 2025-05-28 16:52:24 +04:00
Joshua
2773f9e2e2 fix(tailwind-prefix): resolve prefixing issue for Tailwind CSS v4 compatibility (including tests) (#6885)
* WIP

* fix(tailwind-prefix): resolve prefixing issue for Tailwind CSS v4 compatibility
1. Fixed incorrect prefix application causing issues with Tailwind CSS v4.
2. Optimized implementation using map() for better performance and readability.

* fix(tailwind-prefix): fix test transform-tw-prefixt

* fix(tailwind-prefix): fix test apply-prefix

* fix(tailwind-prefix): add backwards compatibility for applyPrefix

* fix(tailwind-prefix): added changeset

---------

Co-authored-by: Al-Amin Islam Nerob <nerobit.786@gmail.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-05-19 11:31:12 +04:00
shadcn
c41c6ece86 fix: move tw-animate-css to devDependencies (#7251) 2025-04-23 16:45:23 +04:00
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
shadcn
a6034127f9 fix: lint 2025-03-28 11:40:06 +04:00
shadcn
eaf8156fc0 chore: add banner 2025-03-28 11:31:48 +04:00
79 changed files with 3189 additions and 663 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

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
resolved prefixing issue for Tailwind CSS v4 compatibility

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

View File

@@ -2,11 +2,11 @@
import * as React from "react"
import {
closestCenter,
DndContext,
KeyboardSensor,
MouseSensor,
TouchSensor,
closestCenter,
useSensor,
useSensors,
type DragEndEvent,
@@ -14,8 +14,8 @@ import {
} from "@dnd-kit/core"
import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
import {
SortableContext,
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from "@dnd-kit/sortable"
@@ -37,9 +37,6 @@ import {
import {
ColumnDef,
ColumnFiltersState,
Row,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFacetedRowModel,
@@ -47,7 +44,10 @@ import {
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
Row,
SortingState,
useReactTable,
VisibilityState,
} from "@tanstack/react-table"
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
import { toast } from "sonner"

View File

@@ -8,6 +8,7 @@ import { Toaster } from "@/registry/new-york-v4/ui/sonner"
import { siteConfig } from "@/www/config/site"
import "./globals.css"
import { cn } from "@/lib/utils"
import { ActiveThemeProvider } from "@/components/active-theme"

View File

@@ -1,8 +1,8 @@
"use client"
import {
ReactNode,
createContext,
ReactNode,
useContext,
useEffect,
useState,

View File

@@ -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,10 +6,12 @@
"name": "index",
"type": "registry:style",
"dependencies": [
"tw-animate-css",
"class-variance-authority",
"lucide-react"
],
"devDependencies": [
"tw-animate-css"
],
"registryDependencies": [
"utils"
],
@@ -27,37 +29,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",

View File

@@ -2,11 +2,11 @@
import * as React from "react"
import {
closestCenter,
DndContext,
KeyboardSensor,
MouseSensor,
TouchSensor,
closestCenter,
useSensor,
useSensors,
type DragEndEvent,
@@ -14,8 +14,8 @@ import {
} from "@dnd-kit/core"
import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
import {
SortableContext,
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from "@dnd-kit/sortable"
@@ -37,9 +37,6 @@ import {
import {
ColumnDef,
ColumnFiltersState,
Row,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFacetedRowModel,
@@ -47,7 +44,10 @@ import {
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
Row,
SortingState,
useReactTable,
VisibilityState,
} from "@tanstack/react-table"
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
import { toast } from "sonner"

View File

@@ -39,7 +39,7 @@ function ResizableHandle({
<ResizablePrimitive.PanelResizeHandle
data-slot="resizable-handle"
className={cn(
"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
className
)}
{...props}

View File

@@ -2,7 +2,7 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { VariantProps, cva } from "class-variance-authority"
import { cva, VariantProps } from "class-variance-authority"
import { PanelLeftIcon } from "lucide-react"
import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile"

View File

@@ -20,11 +20,8 @@ const registry = {
{
name: "index",
type: "registry:style",
dependencies: [
"tw-animate-css",
"class-variance-authority",
"lucide-react",
],
dependencies: ["class-variance-authority", "lucide-react"],
devDependencies: ["tw-animate-css"],
registryDependencies: ["utils"],
cssVars: {},
files: [],
@@ -78,6 +75,11 @@ const registry = {
if (item.name === "dashboard-01") {
item.dependencies?.push("@tabler/icons-react")
}
if (item.name === "accordion" && "tailwind" in item) {
delete item.tailwind
}
return item
})
),

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

@@ -1,5 +1,7 @@
import "@/styles/globals.css"
import { Metadata, Viewport } from "next"
import Link from "next/link"
import { ArrowRightIcon } from "lucide-react"
import { META_THEME_COLORS, siteConfig } from "@/config/site"
import { fontMono, fontSans } from "@/lib/fonts"
@@ -91,7 +93,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
</head>
<body
className={cn(
"min-h-svh bg-background font-sans antialiased",
"bg-background min-h-svh font-sans antialiased",
fontSans.variable,
fontMono.variable
)}
@@ -104,7 +106,16 @@ export default function RootLayout({ children }: RootLayoutProps) {
enableColorScheme
>
<div vaul-drawer-wrapper="">
<div className="relative flex min-h-svh flex-col bg-background">
<div className="bg-background relative flex min-h-svh flex-col">
<div className="bg-muted text-muted-foreground sticky top-0 z-[100] flex h-10 items-center justify-center gap-2 px-4 text-sm">
You are viewing the v3 docs.{" "}
<Link
href="https://ui.shadcn.com"
className="text-primary flex items-center gap-1 underline"
>
Switch to latest <ArrowRightIcon className="size-3" />
</Link>
</div>
{children}
</div>
</div>

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

@@ -2,6 +2,12 @@ import { createContentlayerPlugin } from "next-contentlayer2"
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
experimental: {
outputFileTracingIncludes: {
"/blocks/*": ["./registry/**/*"],

View File

@@ -86,7 +86,7 @@
"react-resizable-panels": "^2.0.22",
"react-wrap-balancer": "^0.4.1",
"recharts": "2.12.7",
"shadcn": "2.3.0",
"shadcn": "2.5.0",
"sharp": "^0.32.6",
"sonner": "^1.2.3",
"swr": "2.2.6-beta.3",

View File

@@ -11,35 +11,5 @@
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AccordionPrimitive from \"@radix-ui/react-accordion\"\nimport { ChevronDownIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Accordion({\n ...props\n}: React.ComponentProps<typeof AccordionPrimitive.Root>) {\n return <AccordionPrimitive.Root data-slot=\"accordion\" {...props} />\n}\n\nfunction AccordionItem({\n className,\n ...props\n}: React.ComponentProps<typeof AccordionPrimitive.Item>) {\n return (\n <AccordionPrimitive.Item\n data-slot=\"accordion-item\"\n className={cn(\"border-b last:border-b-0\", className)}\n {...props}\n />\n )\n}\n\nfunction AccordionTrigger({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {\n return (\n <AccordionPrimitive.Header className=\"flex\">\n <AccordionPrimitive.Trigger\n data-slot=\"accordion-trigger\"\n className={cn(\n \"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180\",\n className\n )}\n {...props}\n >\n {children}\n <ChevronDownIcon className=\"text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200\" />\n </AccordionPrimitive.Trigger>\n </AccordionPrimitive.Header>\n )\n}\n\nfunction AccordionContent({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof AccordionPrimitive.Content>) {\n return (\n <AccordionPrimitive.Content\n data-slot=\"accordion-content\"\n className=\"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm\"\n {...props}\n >\n <div className={cn(\"pt-0 pb-4\", className)}>{children}</div>\n </AccordionPrimitive.Content>\n )\n}\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent }\n",
"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"
}
}
}
}
}
]
}

View File

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

View File

@@ -78,7 +78,8 @@
"registry:hook",
"registry:theme",
"registry:page",
"registry:file"
"registry:file",
"registry:style"
],
"description": "The type of the file. This is used to determine the type of the file when resolved for a project."
},
@@ -156,6 +157,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

@@ -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,6 +1,10 @@
import { Transformer } from "@/src/utils/transformers"
import { SyntaxKind } from "ts-morph"
import {
TailwindVersion,
getProjectTailwindVersionFromConfig,
} from "../get-project-info"
import { splitClassName } from "./transform-css-vars"
export const transformTwPrefixes: Transformer = async ({
@@ -10,6 +14,7 @@ export const transformTwPrefixes: Transformer = async ({
if (!config.tailwind?.prefix) {
return sourceFile
}
const tailwindVersion = await getProjectTailwindVersionFromConfig(config)
// Find the cva function calls.
sourceFile
@@ -23,7 +28,8 @@ export const transformTwPrefixes: Transformer = async ({
defaultClassNames.replaceWithText(
`"${applyPrefix(
defaultClassNames.getText()?.replace(/"|'/g, ""),
config.tailwind.prefix
config.tailwind.prefix,
tailwindVersion
)}"`
)
}
@@ -47,7 +53,8 @@ export const transformTwPrefixes: Transformer = async ({
classNames?.replaceWithText(
`"${applyPrefix(
classNames.getText()?.replace(/"|'/g, ""),
config.tailwind.prefix
config.tailwind.prefix,
tailwindVersion
)}"`
)
}
@@ -66,7 +73,8 @@ export const transformTwPrefixes: Transformer = async ({
value.replaceWithText(
`"${applyPrefix(
value.getText()?.replace(/"|'/g, ""),
config.tailwind.prefix
config.tailwind.prefix,
tailwindVersion
)}"`
)
}
@@ -92,7 +100,8 @@ export const transformTwPrefixes: Transformer = async ({
node.replaceWithText(
`"${applyPrefix(
node.getText()?.replace(/"|'/g, ""),
config.tailwind.prefix
config.tailwind.prefix,
tailwindVersion
)}"`
)
})
@@ -102,7 +111,8 @@ export const transformTwPrefixes: Transformer = async ({
node.replaceWithText(
`"${applyPrefix(
node.getText()?.replace(/"|'/g, ""),
config.tailwind.prefix
config.tailwind.prefix,
tailwindVersion
)}"`
)
}
@@ -131,7 +141,8 @@ export const transformTwPrefixes: Transformer = async ({
node.replaceWithText(
`"${applyPrefix(
node.getText()?.replace(/"|'/g, ""),
config.tailwind.prefix
config.tailwind.prefix,
tailwindVersion
)}"`
)
})
@@ -141,7 +152,8 @@ export const transformTwPrefixes: Transformer = async ({
arg.replaceWithText(
`"${applyPrefix(
arg.getText()?.replace(/"|'/g, ""),
config.tailwind.prefix
config.tailwind.prefix,
tailwindVersion
)}"`
)
}
@@ -156,7 +168,8 @@ export const transformTwPrefixes: Transformer = async ({
classNames.replaceWithText(
`"${applyPrefix(
classNames.getText()?.replace(/"|'/g, ""),
config.tailwind.prefix
config.tailwind.prefix,
tailwindVersion
)}"`
)
}
@@ -170,30 +183,49 @@ export const transformTwPrefixes: Transformer = async ({
return sourceFile
}
export function applyPrefix(input: string, prefix: string = "") {
const classNames = input.split(" ")
const prefixed: string[] = []
for (let className of classNames) {
const [variant, value, modifier] = splitClassName(className)
if (variant) {
modifier
? prefixed.push(`${variant}:${prefix}${value}/${modifier}`)
: prefixed.push(`${variant}:${prefix}${value}`)
} else {
modifier
? prefixed.push(`${prefix}${value}/${modifier}`)
: prefixed.push(`${prefix}${value}`)
}
export function applyPrefix(
input: string,
prefix: string = "",
tailwindVersion: TailwindVersion
) {
if (tailwindVersion === "v3") {
return input
.split(" ")
.map((className) => {
const [variant, value, modifier] = splitClassName(className)
if (variant) {
return modifier
? `${variant}:${prefix}${value}/${modifier}`
: `${variant}:${prefix}${value}`
} else {
return modifier
? `${prefix}${value}/${modifier}`
: `${prefix}${value}`
}
})
.join(" ")
}
return prefixed.join(" ")
return input
.split(" ")
.map((className) =>
className.indexOf(`${prefix}:`) === 0
? className
: `${prefix}:${className.trim()}`
)
.join(" ")
}
export function applyPrefixesCss(css: string, prefix: string) {
export function applyPrefixesCss(
css: string,
prefix: string,
tailwindVersion: TailwindVersion
) {
const lines = css.split("\n")
for (let line of lines) {
if (line.includes("@apply")) {
const originalTWCls = line.replace("@apply", "").trim()
const prefixedTwCls = applyPrefix(originalTWCls, prefix)
const prefixedTwCls = applyPrefix(originalTWCls, prefix, tailwindVersion)
css = css.replace(originalTWCls, prefixedTwCls)
}
}

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

@@ -3,7 +3,7 @@
exports[`transform tailwind prefix 1`] = `
"import * as React from "react"
export function Foo() {
return <div className="tw-bg-background hover:tw-bg-muted tw-text-primary-foreground sm:focus:tw-text-accent-foreground">foo</div>
return <div className="tw:bg-background hover:tw:bg-muted tw:text-primary-foreground sm:focus:tw:text-accent-foreground">foo</div>
}
"
`;
@@ -11,7 +11,7 @@ exports[`transform tailwind prefix 1`] = `
exports[`transform tailwind prefix 2`] = `
"import * as React from "react"
export function Foo() {
return <div className="tw-bg-white hover:tw-bg-stone-100 tw-text-stone-50 sm:focus:tw-text-stone-900 dark:tw-bg-stone-950 dark:hover:tw-bg-stone-800 dark:tw-text-stone-900 dark:sm:focus:tw-text-stone-50">foo</div>
return <div className="tw:bg-white hover:tw:bg-stone-100 tw:text-stone-50 sm:focus:tw:text-stone-900 dark:tw:bg-stone-950 dark:hover:tw:bg-stone-800 dark:tw:text-stone-900 dark:sm:focus:tw:text-stone-50">foo</div>
}
"
`;
@@ -19,7 +19,7 @@ export function Foo() {
exports[`transform tailwind prefix 3`] = `
"import * as React from "react"
export function Foo() {
return <div className={cn("tw-bg-white hover:tw-bg-stone-100 dark:tw-bg-stone-950 dark:hover:tw-bg-stone-800", true && "tw-text-stone-50 sm:focus:tw-text-stone-900 dark:tw-text-stone-900 dark:sm:focus:tw-text-stone-50")}>foo</div>
return <div className={cn("tw:bg-white hover:tw:bg-stone-100 dark:tw:bg-stone-950 dark:hover:tw:bg-stone-800", true && "tw:text-stone-50 sm:focus:tw:text-stone-900 dark:tw:text-stone-900 dark:sm:focus:tw:text-stone-50")}>foo</div>
}
"
`;
@@ -27,7 +27,7 @@ export function Foo() {
exports[`transform tailwind prefix 4`] = `
"import * as React from "react"
export function Foo() {
return <div className={cn("tw-bg-background hover:tw-bg-muted", true && "tw-text-primary-foreground sm:focus:tw-text-accent-foreground")}>foo</div>
return <div className={cn("tw:bg-background hover:tw:bg-muted", true && "tw:text-primary-foreground sm:focus:tw:text-accent-foreground")}>foo</div>
}
"
`;
@@ -105,10 +105,10 @@ exports[`transform tailwind prefix 5`] = `
@layer base {
* {
@apply tw-border-border;
@apply tw::border-border;
}
body {
@apply tw-bg-background tw-text-foreground;
@apply tw::bg-background tw::text-foreground;
}
}"
`;

View File

@@ -2,7 +2,7 @@ import { describe, expect, test } from "vitest"
import { applyPrefix } from "../../src/utils/transformers/transform-tw-prefix"
describe("apply tailwind prefix", () => {
describe("apply tailwind prefix v3", () => {
test.each([
{
input: "bg-slate-800 text-gray-500",
@@ -37,6 +37,45 @@ describe("apply tailwind prefix", () => {
"tw-absolute tw-right-4 tw-top-4 tw-bg-primary tw-rounded-sm tw-opacity-70 tw-ring-offset-background tw-transition-opacity hover:tw-opacity-100 focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-2 disabled:tw-pointer-events-none data-[state=open]:tw-bg-secondary",
},
])(`applyTwPrefix($input) -> $output`, ({ input, output }) => {
expect(applyPrefix(input, "tw-")).toBe(output)
expect(applyPrefix(input, "tw-", "v3")).toBe(output)
})
})
describe("apply tailwind prefix v4", () => {
test.each([
{
input: "bg-slate-800 text-gray-500",
output: "tw:bg-slate-800 tw:text-gray-500",
},
{
input: "hover:dark:bg-background dark:text-foreground",
output: "tw:hover:dark:bg-background tw:dark:text-foreground",
},
{
input:
"rounded-lg border border-slate-200 bg-white text-slate-950 shadow-sm dark:border-slate-800 dark:bg-slate-950 dark:text-slate-50",
output:
"tw:rounded-lg tw:border tw:border-slate-200 tw:bg-white tw:text-slate-950 tw:shadow-sm tw:dark:border-slate-800 tw:dark:bg-slate-950 tw:dark:text-slate-50",
},
{
input:
"text-red-500 border-red-500/50 dark:border-red-500 [&>svg]:text-red-500 text-red-500 dark:text-red-900 dark:border-red-900/50 dark:dark:border-red-900 dark:[&>svg]:text-red-900 dark:text-red-900",
output:
"tw:text-red-500 tw:border-red-500/50 tw:dark:border-red-500 tw:[&>svg]:text-red-500 tw:text-red-500 tw:dark:text-red-900 tw:dark:border-red-900/50 tw:dark:dark:border-red-900 tw:dark:[&>svg]:text-red-900 tw:dark:text-red-900",
},
{
input:
"flex h-full w-full items-center justify-center rounded-full bg-muted",
output:
"tw:flex tw:h-full tw:w-full tw:items-center tw:justify-center tw:rounded-full tw:bg-muted",
},
{
input:
"absolute right-4 top-4 bg-primary rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary",
output:
"tw:absolute tw:right-4 tw:top-4 tw:bg-primary tw:rounded-sm tw:opacity-70 tw:ring-offset-background tw:transition-opacity tw:hover:opacity-100 tw:focus:outline-none tw:focus:ring-2 tw:focus:ring-ring tw:focus:ring-offset-2 tw:disabled:pointer-events-none tw:data-[state=open]:bg-secondary",
},
])(`applyTwPrefix($input) -> $output`, ({ input, output }) => {
expect(applyPrefix(input, "tw", "v4")).toBe(output)
})
})

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

@@ -16,7 +16,7 @@ test("transform tailwind prefix", async () => {
config: {
tailwind: {
baseColor: "stone",
prefix: "tw-",
prefix: "tw:",
},
aliases: {
components: "@/components",
@@ -39,7 +39,7 @@ export function Foo() {
tailwind: {
baseColor: "stone",
cssVariables: false,
prefix: "tw-",
prefix: "tw:",
},
aliases: {
components: "@/components",
@@ -62,7 +62,7 @@ export function Foo() {
tailwind: {
baseColor: "stone",
cssVariables: false,
prefix: "tw-",
prefix: "tw:",
},
aliases: {
components: "@/components",
@@ -85,7 +85,7 @@ export function Foo() {
tailwind: {
baseColor: "stone",
cssVariables: false,
prefix: "tw-",
prefix: "tw:",
},
aliases: {
components: "@/components",
@@ -99,7 +99,8 @@ export function Foo() {
expect(
applyPrefixesCss(
"@tailwind base;\n@tailwind components;\n@tailwind utilities;\n \n@layer base {\n :root {\n --background: 0 0% 100%;\n --foreground: 224 71.4% 4.1%;\n \n --muted: 220 14.3% 95.9%;\n --muted-foreground: 220 8.9% 46.1%;\n \n --popover: 0 0% 100%;\n --popover-foreground: 224 71.4% 4.1%;\n \n --card: 0 0% 100%;\n --card-foreground: 224 71.4% 4.1%;\n \n --border: 220 13% 91%;\n --input: 220 13% 91%;\n \n --primary: 220.9 39.3% 11%;\n --primary-foreground: 210 20% 98%;\n \n --secondary: 220 14.3% 95.9%;\n --secondary-foreground: 220.9 39.3% 11%;\n \n --accent: 220 14.3% 95.9%;\n --accent-foreground: 220.9 39.3% 11%;\n \n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 210 20% 98%;\n \n --ring: 217.9 10.6% 64.9%;\n \n --radius: 0.5rem;\n }\n \n .dark {\n --background: 224 71.4% 4.1%;\n --foreground: 210 20% 98%;\n \n --muted: 215 27.9% 16.9%;\n --muted-foreground: 217.9 10.6% 64.9%;\n \n --popover: 224 71.4% 4.1%;\n --popover-foreground: 210 20% 98%;\n \n --card: 224 71.4% 4.1%;\n --card-foreground: 210 20% 98%;\n \n --border: 215 27.9% 16.9%;\n --input: 215 27.9% 16.9%;\n \n --primary: 210 20% 98%;\n --primary-foreground: 220.9 39.3% 11%;\n \n --secondary: 215 27.9% 16.9%;\n --secondary-foreground: 210 20% 98%;\n \n --accent: 215 27.9% 16.9%;\n --accent-foreground: 210 20% 98%;\n \n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 0 85.7% 97.3%;\n \n --ring: 215 27.9% 16.9%;\n }\n}\n \n@layer base {\n * {\n @apply border-border;\n }\n body {\n @apply bg-background text-foreground;\n }\n}",
"tw-"
"tw:",
"v4"
)
).toMatchSnapshot()
})

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")
})
})

171
pnpm-lock.yaml generated
View File

@@ -58,7 +58,7 @@ importers:
version: 7.37.4(eslint@8.57.1)
eslint-plugin-tailwindcss:
specifier: 3.13.1
version: 3.13.1(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.7.3)))
version: 3.13.1(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.17.16)(typescript@5.7.3)))
postcss:
specifier: ^8.4.24
version: 8.5.1
@@ -73,10 +73,10 @@ importers:
version: 23.11.1(typescript@5.7.3)
tailwindcss:
specifier: 3.4.6
version: 3.4.6(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.7.3))
version: 3.4.6(ts-node@10.9.2(@types/node@20.17.16)(typescript@5.7.3))
tailwindcss-animate:
specifier: ^1.0.5
version: 1.0.7(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.7.3)))
version: 1.0.7(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.17.16)(typescript@5.7.3)))
tsx:
specifier: ^4.1.4
version: 4.19.2
@@ -283,7 +283,7 @@ importers:
specifier: ^6.0.1
version: 6.0.1
shadcn:
specifier: 2.3.0
specifier: 2.5.0
version: link:../../packages/shadcn
sonner:
specifier: ^2.0.0
@@ -542,7 +542,7 @@ importers:
specifier: 2.12.7
version: 2.12.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
shadcn:
specifier: 2.3.0
specifier: 2.5.0
version: link:../../packages/shadcn
sharp:
specifier: ^0.32.6
@@ -689,7 +689,7 @@ importers:
version: 4.1.5
msw:
specifier: ^2.7.1
version: 2.7.1(@types/node@20.17.16)(typescript@4.9.5)
version: 2.7.1(@types/node@22.13.0)(typescript@4.9.5)
node-fetch:
specifier: ^3.3.0
version: 3.3.2
@@ -738,7 +738,7 @@ importers:
version: 6.0.1
tsup:
specifier: ^6.6.3
version: 6.7.0(postcss@8.5.1)(ts-node@10.9.2(@types/node@20.17.16)(typescript@4.9.5))(typescript@4.9.5)
version: 6.7.0(postcss@8.5.1)(ts-node@10.9.2(@types/node@22.13.0)(typescript@4.9.5))(typescript@4.9.5)
type-fest:
specifier: ^3.8.0
version: 3.13.1
@@ -8813,7 +8813,7 @@ snapshots:
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
resolve-from: 5.0.0
ts-node: 10.9.2(@types/node@20.17.16)(typescript@5.7.3)
ts-node: 10.9.2(@types/node@20.5.1)(typescript@5.7.3)
typescript: 5.7.3
transitivePeerDependencies:
- '@swc/core'
@@ -9595,6 +9595,14 @@ snapshots:
'@inquirer/type': 3.0.4(@types/node@20.17.16)
optionalDependencies:
'@types/node': 20.17.16
optional: true
'@inquirer/confirm@5.1.6(@types/node@22.13.0)':
dependencies:
'@inquirer/core': 10.1.7(@types/node@22.13.0)
'@inquirer/type': 3.0.4(@types/node@22.13.0)
optionalDependencies:
'@types/node': 22.13.0
'@inquirer/core@10.1.7(@types/node@20.17.16)':
dependencies:
@@ -9608,12 +9616,31 @@ snapshots:
yoctocolors-cjs: 2.1.2
optionalDependencies:
'@types/node': 20.17.16
optional: true
'@inquirer/core@10.1.7(@types/node@22.13.0)':
dependencies:
'@inquirer/figures': 1.0.10
'@inquirer/type': 3.0.4(@types/node@22.13.0)
ansi-escapes: 4.3.2
cli-width: 4.1.0
mute-stream: 2.0.0
signal-exit: 4.1.0
wrap-ansi: 6.2.0
yoctocolors-cjs: 2.1.2
optionalDependencies:
'@types/node': 22.13.0
'@inquirer/figures@1.0.10': {}
'@inquirer/type@3.0.4(@types/node@20.17.16)':
optionalDependencies:
'@types/node': 20.17.16
optional: true
'@inquirer/type@3.0.4(@types/node@22.13.0)':
optionalDependencies:
'@types/node': 22.13.0
'@isaacs/cliui@8.0.2':
dependencies:
@@ -12610,7 +12637,7 @@ snapshots:
dependencies:
'@types/node': 20.5.1
cosmiconfig: 8.3.6(typescript@5.7.3)
ts-node: 10.9.2(@types/node@20.17.16)(typescript@5.7.3)
ts-node: 10.9.2(@types/node@20.5.1)(typescript@5.7.3)
typescript: 5.7.3
cosmiconfig@8.3.6(typescript@4.9.5):
@@ -13329,7 +13356,7 @@ snapshots:
debug: 4.3.7
enhanced-resolve: 5.16.1
eslint: 8.57.1
eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.61.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.61.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1)
eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.61.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1)
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.61.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1)
fast-glob: 3.3.3
get-tsconfig: 4.7.5
@@ -13346,7 +13373,7 @@ snapshots:
debug: 4.3.7
enhanced-resolve: 5.16.1
eslint: 9.19.0(jiti@2.4.2)
eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.61.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.61.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2))
eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.61.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.19.0(jiti@2.4.2))
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.61.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1)(eslint@9.19.0(jiti@2.4.2))
fast-glob: 3.3.3
get-tsconfig: 4.7.5
@@ -13358,7 +13385,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
eslint-module-utils@2.12.0(@typescript-eslint/parser@5.61.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.61.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1):
eslint-module-utils@2.12.0(@typescript-eslint/parser@5.61.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -13369,7 +13396,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.12.0(@typescript-eslint/parser@5.61.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.61.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)):
eslint-module-utils@2.12.0(@typescript-eslint/parser@5.61.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.19.0(jiti@2.4.2)):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -13391,7 +13418,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.61.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.61.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1)
eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.61.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1)
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -13420,7 +13447,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.19.0(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.61.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.61.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2))
eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.61.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.19.0(jiti@2.4.2))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -13528,11 +13555,11 @@ snapshots:
string.prototype.matchall: 4.0.12
string.prototype.repeat: 1.0.0
eslint-plugin-tailwindcss@3.13.1(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.7.3))):
eslint-plugin-tailwindcss@3.13.1(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.17.16)(typescript@5.7.3))):
dependencies:
fast-glob: 3.3.3
postcss: 8.5.1
tailwindcss: 3.4.6(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.7.3))
tailwindcss: 3.4.6(ts-node@10.9.2(@types/node@20.17.16)(typescript@5.7.3))
eslint-plugin-turbo@1.13.4(eslint@8.57.1):
dependencies:
@@ -15636,31 +15663,6 @@ snapshots:
ms@2.1.3: {}
msw@2.7.1(@types/node@20.17.16)(typescript@4.9.5):
dependencies:
'@bundled-es-modules/cookie': 2.0.1
'@bundled-es-modules/statuses': 1.0.1
'@bundled-es-modules/tough-cookie': 0.1.6
'@inquirer/confirm': 5.1.6(@types/node@20.17.16)
'@mswjs/interceptors': 0.37.6
'@open-draft/deferred-promise': 2.2.0
'@open-draft/until': 2.1.0
'@types/cookie': 0.6.0
'@types/statuses': 2.0.5
graphql: 16.10.0
headers-polyfill: 4.0.3
is-node-process: 1.2.0
outvariant: 1.4.3
path-to-regexp: 6.3.0
picocolors: 1.1.1
strict-event-emitter: 0.5.1
type-fest: 4.33.0
yargs: 17.7.2
optionalDependencies:
typescript: 4.9.5
transitivePeerDependencies:
- '@types/node'
msw@2.7.1(@types/node@20.17.16)(typescript@5.7.3):
dependencies:
'@bundled-es-modules/cookie': 2.0.1
@@ -15687,6 +15689,31 @@ snapshots:
- '@types/node'
optional: true
msw@2.7.1(@types/node@22.13.0)(typescript@4.9.5):
dependencies:
'@bundled-es-modules/cookie': 2.0.1
'@bundled-es-modules/statuses': 1.0.1
'@bundled-es-modules/tough-cookie': 0.1.6
'@inquirer/confirm': 5.1.6(@types/node@22.13.0)
'@mswjs/interceptors': 0.37.6
'@open-draft/deferred-promise': 2.2.0
'@open-draft/until': 2.1.0
'@types/cookie': 0.6.0
'@types/statuses': 2.0.5
graphql: 16.10.0
headers-polyfill: 4.0.3
is-node-process: 1.2.0
outvariant: 1.4.3
path-to-regexp: 6.3.0
picocolors: 1.1.1
strict-event-emitter: 0.5.1
type-fest: 4.33.0
yargs: 17.7.2
optionalDependencies:
typescript: 4.9.5
transitivePeerDependencies:
- '@types/node'
mute-stream@2.0.0: {}
mz@2.7.0:
@@ -16090,13 +16117,13 @@ snapshots:
camelcase-css: 2.0.1
postcss: 8.5.1
postcss-load-config@3.1.4(postcss@8.5.1)(ts-node@10.9.2(@types/node@20.17.16)(typescript@4.9.5)):
postcss-load-config@3.1.4(postcss@8.5.1)(ts-node@10.9.2(@types/node@22.13.0)(typescript@4.9.5)):
dependencies:
lilconfig: 2.1.0
yaml: 1.10.2
optionalDependencies:
postcss: 8.5.1
ts-node: 10.9.2(@types/node@20.17.16)(typescript@4.9.5)
ts-node: 10.9.2(@types/node@22.13.0)(typescript@4.9.5)
postcss-load-config@3.1.4(postcss@8.5.1)(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3)):
dependencies:
@@ -16114,13 +16141,13 @@ snapshots:
postcss: 8.5.1
ts-node: 10.9.2(@types/node@17.0.45)(typescript@5.7.3)
postcss-load-config@4.0.2(postcss@8.5.1)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.7.3)):
postcss-load-config@4.0.2(postcss@8.5.1)(ts-node@10.9.2(@types/node@20.17.16)(typescript@5.7.3)):
dependencies:
lilconfig: 3.1.3
yaml: 2.7.0
optionalDependencies:
postcss: 8.5.1
ts-node: 10.9.2(@types/node@20.17.16)(typescript@5.7.3)
ts-node: 10.9.2(@types/node@20.5.1)(typescript@5.7.3)
postcss-nested@6.2.0(postcss@8.5.1):
dependencies:
@@ -17316,9 +17343,9 @@ snapshots:
tailwind-merge@3.0.1: {}
tailwindcss-animate@1.0.7(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.7.3))):
tailwindcss-animate@1.0.7(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.17.16)(typescript@5.7.3))):
dependencies:
tailwindcss: 3.4.6(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.7.3))
tailwindcss: 3.4.6(ts-node@10.9.2(@types/node@20.17.16)(typescript@5.7.3))
tailwindcss@3.4.6(ts-node@10.9.2(@types/node@17.0.45)(typescript@5.7.3)):
dependencies:
@@ -17347,7 +17374,7 @@ snapshots:
transitivePeerDependencies:
- ts-node
tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.7.3)):
tailwindcss@3.4.6(ts-node@10.9.2(@types/node@20.17.16)(typescript@5.7.3)):
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
@@ -17366,7 +17393,7 @@ snapshots:
postcss: 8.5.1
postcss-import: 15.1.0(postcss@8.5.1)
postcss-js: 4.0.1(postcss@8.5.1)
postcss-load-config: 4.0.2(postcss@8.5.1)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.7.3))
postcss-load-config: 4.0.2(postcss@8.5.1)(ts-node@10.9.2(@types/node@20.17.16)(typescript@5.7.3))
postcss-nested: 6.2.0(postcss@8.5.1)
postcss-selector-parser: 6.1.2
resolve: 1.22.10
@@ -17534,14 +17561,32 @@ snapshots:
yn: 3.1.1
optional: true
ts-node@10.9.2(@types/node@20.17.16)(typescript@4.9.5):
ts-node@10.9.2(@types/node@20.5.1)(typescript@5.7.3):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 20.17.16
'@types/node': 20.5.1
acorn: 8.14.0
acorn-walk: 8.3.4
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
typescript: 5.7.3
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
ts-node@10.9.2(@types/node@22.13.0)(typescript@4.9.5):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 22.13.0
acorn: 8.14.0
acorn-walk: 8.3.4
arg: 4.1.3
@@ -17553,24 +17598,6 @@ snapshots:
yn: 3.1.1
optional: true
ts-node@10.9.2(@types/node@20.17.16)(typescript@5.7.3):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 20.17.16
acorn: 8.14.0
acorn-walk: 8.3.4
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
typescript: 5.7.3
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3):
dependencies:
'@cspotcode/source-map-support': 0.8.1
@@ -17615,7 +17642,7 @@ snapshots:
tslib@2.8.1: {}
tsup@6.7.0(postcss@8.5.1)(ts-node@10.9.2(@types/node@20.17.16)(typescript@4.9.5))(typescript@4.9.5):
tsup@6.7.0(postcss@8.5.1)(ts-node@10.9.2(@types/node@22.13.0)(typescript@4.9.5))(typescript@4.9.5):
dependencies:
bundle-require: 4.2.1(esbuild@0.17.19)
cac: 6.7.14
@@ -17625,7 +17652,7 @@ snapshots:
execa: 5.1.1
globby: 11.1.0
joycon: 3.1.1
postcss-load-config: 3.1.4(postcss@8.5.1)(ts-node@10.9.2(@types/node@20.17.16)(typescript@4.9.5))
postcss-load-config: 3.1.4(postcss@8.5.1)(ts-node@10.9.2(@types/node@22.13.0)(typescript@4.9.5))
resolve-from: 5.0.0
rollup: 3.29.5
source-map: 0.8.0-beta.0