feat(cli): tree resolver

This commit is contained in:
shadcn
2024-08-19 16:37:35 +04:00
parent 2744218d71
commit f15a22073f
293 changed files with 6772 additions and 633 deletions

View File

@@ -41,7 +41,8 @@
"pub:beta": "pnpm build && pnpm publish --no-git-checks --access public --tag beta",
"pub:next": "pnpm build && pnpm publish --no-git-checks --access public --tag next",
"pub:release": "pnpm build && pnpm publish --access public",
"test": "vitest run"
"test": "vitest run",
"test:dev": "COMPONENTS_REGISTRY_URL=http://localhost:3003 vitest run"
},
"dependencies": {
"@antfu/ni": "^0.21.4",

View File

@@ -68,6 +68,11 @@ export const add = new Command()
const registryIndex = await getRegistryIndex()
if (!registryIndex) {
handleError(new Error("Failed to fetch registry index."))
process.exit(1)
}
let selectedComponents = options.all
? registryIndex.map((entry) => entry.name)
: options.components
@@ -98,7 +103,7 @@ export const add = new Command()
const payload = await fetchTree(config.style, tree)
const baseColor = await getRegistryBaseColor(config.tailwind.baseColor)
if (!payload.length) {
if (!payload?.length) {
logger.warn("Selected components not found. Exiting.")
process.exit(0)
}
@@ -133,11 +138,11 @@ export const add = new Command()
await fs.mkdir(targetDir, { recursive: true })
}
const existingComponent = item.files.filter((file) =>
existsSync(path.resolve(targetDir, file.name))
const existingComponent = item.files?.filter((file) =>
existsSync(path.resolve(targetDir, file.path))
)
if (existingComponent.length && !options.overwrite) {
if (existingComponent?.length && !options.overwrite) {
if (selectedComponents.includes(item.name)) {
spinner.stop()
const { overwrite } = await prompts({
@@ -162,12 +167,16 @@ export const add = new Command()
}
}
for (const file of item.files) {
let filePath = path.resolve(targetDir, file.name)
for (const file of item.files ?? []) {
let filePath = path.resolve(targetDir, file.path)
// Run transformers.
if (!file.content) {
continue
}
const content = await transform({
filename: file.name,
filename: file.path,
raw: file.content,
config,
baseColor,

View File

@@ -59,13 +59,18 @@ export const diff = new Command()
const registryIndex = await getRegistryIndex()
if (!registryIndex) {
handleError(new Error("Failed to fetch registry index."))
process.exit(1)
}
if (!options.component) {
const targetDir = config.resolvedPaths.components
// Find all components that exist in the project.
const projectComponents = registryIndex.filter((item) => {
for (const file of item.files) {
const filePath = path.resolve(targetDir, file)
for (const file of item.files ?? []) {
const filePath = path.resolve(targetDir, file.path)
if (existsSync(filePath)) {
return true
}
@@ -139,6 +144,10 @@ async function diffComponent(
const payload = await fetchTree(config.style, [component])
const baseColor = await getRegistryBaseColor(config.tailwind.baseColor)
if (!payload) {
return []
}
const changes = []
for (const item of payload) {
@@ -148,8 +157,8 @@ async function diffComponent(
continue
}
for (const file of item.files) {
const filePath = path.resolve(targetDir, file.name)
for (const file of item.files ?? []) {
const filePath = path.resolve(targetDir, file.path)
if (!existsSync(filePath)) {
continue
@@ -157,8 +166,12 @@ async function diffComponent(
const fileContent = await fs.readFile(filePath, "utf8")
if (!file.content) {
continue
}
const registryContent = await transform({
filename: file.name,
filename: file.path,
raw: file.content,
config,
baseColor,
@@ -167,7 +180,6 @@ async function diffComponent(
const patch = diffLines(registryContent as string, fileContent)
if (patch.length > 1) {
changes.push({
file: file.name,
filePath,
patch,
})

View File

@@ -19,10 +19,11 @@ import {
getRegistryBaseColors,
getRegistryItem,
getRegistryStyles,
registryResolveItemsTree,
} from "@/src/utils/registry"
import { updateDependencies } from "@/src/utils/updaters/update-dependencies"
import { updateDestinations } from "@/src/utils/updaters/update-destinations"
import { updateFiles } from "@/src/utils/updaters/update-files"
import { updateRegistryDependencies } from "@/src/utils/updaters/update-registry-dependencies"
import {
buildTailwindThemeColorsFromCssVars,
updateTailwindConfig,
@@ -97,10 +98,10 @@ export const init = new Command()
if (!opts.force && !opts.defaults) {
logger.info("")
}
const spinner = ora(`Writing components.json...`).start()
const spinner = ora(`Writing components.json.`).start()
const targetPath = path.resolve(cwd, "components.json")
await fs.writeFile(targetPath, JSON.stringify(config, null, 2), "utf8")
spinner.succeed(`Writing components.json.`)
spinner.succeed()
const fullConfig = await resolveConfigPaths(cwd, config)
@@ -291,60 +292,25 @@ export async function promptForMinimalConfig(
}
export async function runInit(config: Config) {
const initializersSpinner = ora(`Initializing project...`)?.start()
await updateDestinations(config)
const [payload, baseColor] = await Promise.all([
getRegistryItem(config.style, "index"),
getRegistryBaseColor(config.tailwind.baseColor),
])
if (!payload) {
const initializersSpinner = ora(`Initializing project.`)?.start()
const tree = await registryResolveItemsTree(["index"], config)
if (!tree) {
initializersSpinner?.fail()
logger.error(`Something went wrong during the initialization process.`)
process.exit(1)
}
// Inline the base color in the tailwind config.
if (config.tailwind.cssVariables && baseColor) {
payload.cssVars = {
light: {
...baseColor.cssVars.light,
...payload.cssVars?.light,
},
dark: {
...baseColor.cssVars.dark,
...payload.cssVars?.dark,
},
}
console.log(tree.tailwind?.config?.theme)
// Move the css vars to the tailwind config.
if (payload.tailwind?.config && baseColor.cssVars?.light) {
payload.tailwind.config = deepmerge(payload.tailwind.config, {
theme: {
extend: {
colors: buildTailwindThemeColorsFromCssVars(
baseColor.cssVars.light
),
},
},
})
}
}
await updateTailwindConfig(tree.tailwind?.config, config)
await updateTailwindCss(tree.cssVars, config)
initializersSpinner?.succeed()
if (payload.tailwind?.config) {
await updateTailwindConfig(payload.tailwind?.config, config)
}
if (payload.cssVars) {
await updateTailwindCss(payload.cssVars, config)
}
await updateFiles(payload.files, config)
initializersSpinner?.succeed(`Initializing project.`)
// Install dependencies.
if (payload.dependencies) {
const dependenciesSpinner = ora(`Installing dependencies...`)?.start()
await updateDependencies(payload.dependencies, config)
dependenciesSpinner?.succeed(`Installing dependencies.`)
}
const dependenciesSpinner = ora(`Installing dependencies.`)?.start()
// await updateRegistryDependencies(tree.registryDependencies, config)
await updateDependencies(tree.dependencies, config)
dependenciesSpinner?.succeed()
}

View File

@@ -15,6 +15,7 @@ export function handleError(error: unknown) {
}
if (error instanceof z.ZodError) {
console.log(error.issues)
logger.error("Validation failed:")
for (const [key, value] of Object.entries(error.flatten().fieldErrors)) {
logger.error(`- ${cyan(key)}: ${value}`)

View File

@@ -5,11 +5,14 @@ import { logger } from "@/src/utils/logger"
import {
registryBaseColorSchema,
registryIndexSchema,
registryItemFileSchema,
registryItemSchema,
registryItemWithContentSchema,
registryWithContentSchema,
registryItemTypeSchema,
registryResolvedItemsTreeSchema,
stylesSchema,
} from "@/src/utils/registry/schema"
import { buildTailwindThemeColorsFromCssVars } from "@/src/utils/updaters/update-tailwind-config"
import deepmerge from "deepmerge"
import { HttpsProxyAgent } from "https-proxy-agent"
import { cyan } from "kleur/colors"
import fetch from "node-fetch"
@@ -28,7 +31,8 @@ export async function getRegistryIndex() {
return registryIndexSchema.parse(result)
} catch (error) {
throw new Error(`Failed to fetch components from registry.`)
logger.error("\n")
handleError(error)
}
}
@@ -38,7 +42,9 @@ export async function getRegistryStyles() {
return stylesSchema.parse(result)
} catch (error) {
throw new Error(`Failed to fetch styles from registry.`)
logger.error("\n")
handleError(error)
return []
}
}
@@ -48,6 +54,7 @@ export async function getRegistryItem(style: string, name: string) {
return registryItemSchema.parse(result)
} catch (error) {
logger.error("\n")
handleError(error)
return null
@@ -123,16 +130,15 @@ export async function fetchTree(
try {
const paths = tree.map((item) => `styles/${style}/${item.name}.json`)
const result = await fetchRegistry(paths)
return registryWithContentSchema.parse(result)
return registryIndexSchema.parse(result)
} catch (error) {
throw new Error(`Failed to fetch tree from registry.`)
handleError(error)
}
}
export async function getItemTargetPath(
config: Config,
item: Pick<z.infer<typeof registryItemWithContentSchema>, "type">,
item: z.infer<typeof registryItemSchema>,
override?: string
) {
if (override) {
@@ -183,3 +189,130 @@ async function fetchRegistry(paths: string[]) {
return []
}
}
export async function getRegistryItemFileTargetPath(
file: z.infer<typeof registryItemFileSchema>,
config: Config,
override?: string
) {
if (override) {
return override
}
if (file.type === "registry:ui") {
return config.resolvedPaths.ui
}
if (file.type === "registry:lib") {
return config.resolvedPaths.utils
}
if (file.type === "registry:component") {
return config.resolvedPaths.components
}
return null
}
export async function registryResolveItemsTree(
names: z.infer<typeof registryItemSchema>["name"][],
config: Config
) {
const index = await getRegistryIndex()
if (!index) {
return null
}
let items = (
await Promise.all(
names.map(async (name) => {
const item = await getRegistryItem(config.style, name)
return item
})
)
).filter((item): item is NonNullable<typeof item> => item !== null)
if (!items.length) {
return null
}
const registryDependencies: string[] = items
.map((item) => item.registryDependencies ?? [])
.flat()
const uniqueDependencies = Array.from(new Set(registryDependencies))
const tree = await resolveTree(index, [...names, ...uniqueDependencies])
let payload = await fetchTree(config.style, tree)
if (!payload) {
return null
}
if (names.includes("index")) {
const index = await getInitRegistryItem(config)
if (index) {
payload.unshift(index)
}
}
let tailwind = {}
payload.forEach((item) => {
console.log(item.tailwind?.config?.theme)
tailwind = deepmerge(tailwind, item.tailwind ?? {})
})
let cssVars = {}
payload.forEach((item) => {
cssVars = deepmerge(cssVars, item.cssVars ?? {})
})
return registryResolvedItemsTreeSchema.parse({
dependencies: deepmerge.all(payload.map((item) => item.dependencies ?? [])),
devDependencies: deepmerge.all(
payload.map((item) => item.devDependencies ?? [])
),
files: deepmerge.all(payload.map((item) => item.files ?? [])),
tailwind,
cssVars,
})
}
async function getInitRegistryItem(config: Config) {
const [payload, baseColor] = await Promise.all([
getRegistryItem(config.style, "index"),
getRegistryBaseColor(config.tailwind.baseColor),
])
if (!payload || !baseColor) {
return null
}
// Inline the base color in the tailwind config.
// TODO: Remove this when we have theme handling.
if (config.tailwind.cssVariables && baseColor) {
payload.cssVars = {
light: {
...baseColor.cssVars.light,
...payload.cssVars?.light,
},
dark: {
...baseColor.cssVars.dark,
...payload.cssVars?.dark,
},
}
if (payload.tailwind?.config && baseColor.cssVars?.light) {
payload.tailwind.config = deepmerge(payload.tailwind.config, {
theme: {
extend: {
colors: buildTailwindThemeColorsFromCssVars(
baseColor.cssVars.light
),
},
},
})
}
}
return payload
}

View File

@@ -1,34 +1,59 @@
import { z } from "zod"
// TODO: Extract this to a shared package.
export const legacyRegistryItemSchema = z.object({
name: z.string(),
dependencies: z.array(z.string()).optional(),
devDependencies: z.array(z.string()).optional(),
registryDependencies: z.array(z.string()).optional(),
files: z.array(z.string()),
type: z
.enum(["components:ui", "components:component", "components:example"])
export const registryItemTypeSchema = z.enum([
"components:ui",
"components:component",
"components:example",
"components:block",
"components:chart",
"registry:style",
"registry:lib",
"registry:component",
"registry:ui",
"registry:hook",
])
export const registryItemFileSchema = z.object({
path: z.string(),
content: z.string().optional(),
type: registryItemTypeSchema,
})
export const registryItemTailwindSchema = z.object({
config: z
.object({
content: z.array(z.string()).optional(),
theme: z.record(z.string(), z.any()).optional(),
plugins: z.array(z.string()).optional(),
})
.optional(),
})
export const registryIndexSchema = z.array(legacyRegistryItemSchema)
export const registryItemWithContentSchema = legacyRegistryItemSchema.extend({
files: z.array(
z.object({
name: z.string(),
content: z.string(),
type: z.enum([
"components:ui",
"components:component",
"components:example",
]),
})
),
export const registryItemCssVarsSchema = z.object({
light: z.record(z.string(), z.string()).optional(),
dark: z.record(z.string(), z.string()).optional(),
})
export const registryWithContentSchema = z.array(registryItemWithContentSchema)
export const registryItemSchema = z.object({
name: z.string(),
type: registryItemTypeSchema,
description: z.string().optional(),
dependencies: z.array(z.string()).optional(),
devDependencies: z.array(z.string()).optional(),
registryDependencies: z.array(z.string()).optional(),
files: z.array(registryItemFileSchema).optional(),
tailwind: registryItemTailwindSchema.optional(),
cssVars: registryItemCssVarsSchema.optional(),
})
export type RegistryItem = z.infer<typeof registryItemSchema>
export const registryIndexSchema = z.array(
registryItemSchema.extend({
files: z.array(z.union([z.string(), registryItemFileSchema])).optional(),
})
)
export const stylesSchema = z.array(
z.object({
@@ -50,33 +75,10 @@ export const registryBaseColorSchema = z.object({
cssVarsTemplate: z.string(),
})
export const registryCssVarsSchema = z.object({
light: z.record(z.string(), z.string()).optional(),
dark: z.record(z.string(), z.string()).optional(),
export const registryResolvedItemsTreeSchema = registryItemSchema.pick({
dependencies: true,
devDependencies: true,
files: true,
tailwind: true,
cssVars: true,
})
export const registryItemSchema = z.object({
name: z.string(),
dependencies: z.array(z.string()).optional(),
devDependencies: z.array(z.string()).optional(),
registryDependencies: z.array(z.string()).optional(),
files: z.array(
z.object({
name: z.string(),
content: z.string(),
type: z.enum(["utils", "ui", "component"]),
})
),
tailwind: z
.object({
config: z.object({
content: z.array(z.string()).optional(),
theme: z.record(z.string(), z.any()).optional(),
plugins: z.array(z.string()).optional(),
}),
})
.optional(),
cssVars: registryCssVarsSchema.optional(),
})
export type RegistryItem = z.infer<typeof registryItemSchema>

View File

@@ -1,11 +1,16 @@
import { Config } from "@/src/utils/get-config"
import { getPackageManager } from "@/src/utils/get-package-manager"
import { RegistryItem } from "@/src/utils/registry/schema"
import { execa } from "execa"
export async function updateDependencies(
dependencies: string[],
dependencies: RegistryItem["dependencies"],
config: Config
) {
if (!dependencies) {
return
}
const packageManager = await getPackageManager(config.resolvedPaths.cwd)
await execa(

View File

@@ -1,16 +0,0 @@
import { promises as fs } from "fs"
import { Config } from "@/src/utils/get-config"
import { RegistryItem } from "@/src/utils/registry/schema"
import * as templates from "@/src/utils/templates"
export async function updateFiles(
files: RegistryItem["files"],
config: Config
) {
const extension = config.tsx ? "ts" : "js"
await fs.writeFile(
`${config.resolvedPaths.utils}.${extension}`,
extension === "ts" ? templates.UTILS : templates.UTILS_JS,
"utf8"
)
}

View File

@@ -0,0 +1,38 @@
import { Config } from "@/src/utils/get-config"
import { fetchTree, getRegistryIndex, resolveTree } from "@/src/utils/registry"
import { RegistryItem } from "@/src/utils/registry/schema"
import { z } from "zod"
export async function updateRegistryDependencies(
registryDependencies: RegistryItem["registryDependencies"],
config: Config
) {
if (!registryDependencies?.length) {
return
}
const registryIndex = await getRegistryIndex()
if (!registryIndex) {
return null
}
const tree = await resolveTree(registryIndex, registryDependencies)
const payload = await fetchTree(config.style, tree)
if (!payload?.length) {
return
}
await Promise.all(
payload.map(async (dependency) => {
console.log(dependency)
// const targetPath = await getRegistryItemFileTargetPath(file, config)
// if (!targetPath) {
// return
// }
// console.log(targetPath)
})
)
}

View File

@@ -2,6 +2,10 @@ import { promises as fs } from "fs"
import { tmpdir } from "os"
import path from "path"
import { Config } from "@/src/utils/get-config"
import {
RegistryItem,
registryItemTailwindSchema,
} from "@/src/utils/registry/schema"
import deepmerge from "deepmerge"
import objectToString from "stringify-object"
import { type Config as TailwindConfig } from "tailwindcss"
@@ -14,6 +18,7 @@ import {
SyntaxKind,
VariableStatement,
} from "ts-morph"
import { z } from "zod"
export type UpdaterTailwindConfig = Omit<TailwindConfig, "plugins"> & {
// We only want string plugins for now.
@@ -21,9 +26,14 @@ export type UpdaterTailwindConfig = Omit<TailwindConfig, "plugins"> & {
}
export async function updateTailwindConfig(
tailwindConfig: UpdaterTailwindConfig,
tailwindConfig:
| z.infer<typeof registryItemTailwindSchema>["config"]
| undefined,
config: Config
) {
if (!tailwindConfig) {
return
}
const raw = await fs.readFile(config.resolvedPaths.tailwindConfig, "utf8")
const output = await transformTailwindConfig(raw, tailwindConfig, config)
await fs.writeFile(config.resolvedPaths.tailwindConfig, output, "utf8")

View File

@@ -1,6 +1,6 @@
import { promises as fs } from "fs"
import { Config } from "@/src/utils/get-config"
import { registryCssVarsSchema } from "@/src/utils/registry/schema"
import { registryItemCssVarsSchema } from "@/src/utils/registry/schema"
import postcss from "postcss"
import AtRule from "postcss/lib/at-rule"
import Root from "postcss/lib/root"
@@ -8,9 +8,13 @@ import Rule from "postcss/lib/rule"
import { z } from "zod"
export async function updateTailwindCss(
cssVars: z.infer<typeof registryCssVarsSchema>,
cssVars: z.infer<typeof registryItemCssVarsSchema> | undefined,
config: Config
) {
if (!cssVars) {
return
}
const raw = await fs.readFile(config.resolvedPaths.tailwindCss, "utf8")
let output = await transformTailwindCss(raw, cssVars)
@@ -19,7 +23,7 @@ export async function updateTailwindCss(
export async function transformTailwindCss(
input: string,
cssVars: z.infer<typeof registryCssVarsSchema>
cssVars: z.infer<typeof registryItemCssVarsSchema>
) {
const result = await postcss([
updateCssVarsPlugin(cssVars),
@@ -94,7 +98,9 @@ function updateBaseLayerPlugin() {
}
}
function updateCssVarsPlugin(cssVars: z.infer<typeof registryCssVarsSchema>) {
function updateCssVarsPlugin(
cssVars: z.infer<typeof registryItemCssVarsSchema>
) {
return {
postcssPlugin: "update-css-vars",
Once(root: Root) {
@@ -140,7 +146,7 @@ function addOrUpdateVars(
}
Object.entries(vars).forEach(([key, value]) => {
const prop = `--${key}`
const prop = `--${key.replace(/^--/, "")}`
const newDecl = postcss.decl({
prop,
value,

View File

@@ -0,0 +1,938 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`registryResolveItemTree > should resolve index 1`] = `
{
"cssVars": {
"dark": {
"brand-primary": "#831843",
"brand-secondary": "#ccfbf1",
},
"light": {
"--radius": "0.5rem",
"brand-primary": "#a855f7",
"brand-secondary": "#10b981",
},
},
"dependencies": [
"tailwindcss-animate",
"class-variance-authority",
"lucide-react",
"",
"@radix-ui/react-label",
"clsx",
"tailwind-merge",
"@radix-ui/react-slot",
"cmdk@1.0.0",
"@radix-ui/react-dialog",
],
"devDependencies": [],
"files": [
{
"content": ""use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }
",
"path": "ui/label.tsx",
"type": "components:ui",
},
{
"content": "import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
",
"path": "lib/utils.ts",
"type": "registry:lib",
},
{
"content": ""use client"
import { useMediaQuery } from "@/registry/default/hooks/use-media-query"
import { cn } from "@/registry/default/lib/utils"
import { Button } from "@/registry/default/ui/button"
export default function ExampleBlock() {
const isDesktop = useMediaQuery("(min-width: 768px)")
return (
<div
className={cn(
"shadow-brand p-12",
isDesktop
? "bg-muted text-muted-foreground"
: "bg-primary text-primary-foreground"
)}
>
<Button className="bg-brand-primary border-brand-secondary border-4">
A button
</Button>
</div>
)
}
",
"path": "block/hello-block.tsx",
"type": "components:block",
},
{
"content": "import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
",
"path": "ui/button.tsx",
"type": "components:ui",
},
{
"content": ""use client"
import * as React from "react"
import { type DialogProps } from "@radix-ui/react-dialog"
import { Command as CommandPrimitive } from "cmdk"
import { Search } from "lucide-react"
import { cn } from "@/lib/utils"
import { Dialog, DialogContent } from "@/registry/default/ui/dialog"
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className
)}
{...props}
/>
))
Command.displayName = CommandPrimitive.displayName
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0 shadow-lg">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
</div>
))
CommandInput.displayName = CommandPrimitive.Input.displayName
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
))
CommandList.displayName = CommandPrimitive.List.displayName
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className
)}
{...props}
/>
))
CommandGroup.displayName = CommandPrimitive.Group.displayName
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn("-mx-1 h-px bg-border", className)}
{...props}
/>
))
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
className
)}
{...props}
/>
))
CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
CommandShortcut.displayName = "CommandShortcut"
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}
",
"path": "ui/command.tsx",
"type": "components:ui",
},
{
"content": ""use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 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-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
",
"path": "ui/dialog.tsx",
"type": "components:ui",
},
],
"tailwind": {
"config": {
"plugins": [
"require("tailwindcss-animate")",
],
"theme": {
"extend": {
"borderRadius": {
"lg": "var(--radius)",
"md": "calc(var(--radius) - 2px)",
"sm": "calc(var(--radius) - 4px)",
},
"boxShadow": {
"brand": "0px 0px 10px 0px rgba(255, 255, 255, 0.10)",
},
},
},
},
},
}
`;
exports[`registryResolveItemTree > should resolve items tree 1`] = `
{
"cssVars": {},
"dependencies": [
"@radix-ui/react-slot",
],
"devDependencies": [],
"files": [
{
"content": "import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
",
"path": "ui/button.tsx",
"type": "components:ui",
},
],
"tailwind": {},
}
`;
exports[`registryResolveItemTree > should resolve multiple items tree 1`] = `
{
"cssVars": {},
"dependencies": [
"@radix-ui/react-slot",
"cmdk@1.0.0",
"@radix-ui/react-dialog",
],
"devDependencies": [],
"files": [
{
"content": "import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
",
"path": "ui/button.tsx",
"type": "components:ui",
},
{
"content": "import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }
",
"path": "ui/input.tsx",
"type": "components:ui",
},
{
"content": ""use client"
import * as React from "react"
import { type DialogProps } from "@radix-ui/react-dialog"
import { Command as CommandPrimitive } from "cmdk"
import { Search } from "lucide-react"
import { cn } from "@/lib/utils"
import { Dialog, DialogContent } from "@/registry/default/ui/dialog"
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className
)}
{...props}
/>
))
Command.displayName = CommandPrimitive.displayName
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0 shadow-lg">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
</div>
))
CommandInput.displayName = CommandPrimitive.Input.displayName
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
))
CommandList.displayName = CommandPrimitive.List.displayName
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className
)}
{...props}
/>
))
CommandGroup.displayName = CommandPrimitive.Group.displayName
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn("-mx-1 h-px bg-border", className)}
{...props}
/>
))
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
className
)}
{...props}
/>
))
CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
CommandShortcut.displayName = "CommandShortcut"
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}
",
"path": "ui/command.tsx",
"type": "components:ui",
},
{
"content": ""use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 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-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
",
"path": "ui/dialog.tsx",
"type": "components:ui",
},
],
"tailwind": {},
}
`;

View File

@@ -0,0 +1,38 @@
import { describe, expect, test } from "vitest"
import { registryResolveItemsTree } from "../../../src/utils/registry"
describe("registryResolveItemTree", () => {
test("should resolve items tree", async () => {
expect(
await registryResolveItemsTree(["button"], {
style: "new-york",
tailwind: {
baseColor: "stone",
},
})
).toMatchSnapshot()
})
test("should resolve multiple items tree", async () => {
expect(
await registryResolveItemsTree(["button", "input", "command"], {
style: "default",
tailwind: {
baseColor: "zinc",
},
})
).toMatchSnapshot()
})
test("should resolve index", async () => {
expect(
await registryResolveItemsTree(["index", "label"], {
style: "default",
tailwind: {
baseColor: "zinc",
},
})
).toMatchSnapshot()
})
})