Compare commits

..

4 Commits

Author SHA1 Message Date
github-actions[bot]
9ef7967b0d chore(release): version packages (#4821)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-12 17:54:41 +04:00
shadcn
64b2f1a5ad feat(shadcn): add experimental docs (#4820)
* feat(shadcn): add cli docs

* chore: add changeset

* fix: tests
2024-09-12 17:51:59 +04:00
github-actions[bot]
f4ca57a79c chore(release): version packages (#4788)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-09 12:34:19 +04:00
shadcn
99ff9caf71 fix(shadcn): add src to content in tailwind config (#4787)
* feat(shadcn): handle src dir

* chore: changeset
2024-09-09 12:32:12 +04:00
12 changed files with 314 additions and 3 deletions

View File

@@ -62,6 +62,7 @@ export const registryEntrySchema = z.object({
category: z.string().optional(),
subcategory: z.string().optional(),
chunks: z.array(blockChunkSchema).optional(),
docs: z.string().optional(),
})
export const registrySchema = z.array(registryEntrySchema)

View File

@@ -1,5 +1,17 @@
# @shadcn/ui
## 2.0.6
### Patch Changes
- [#4820](https://github.com/shadcn-ui/ui/pull/4820) [`64b2f1a`](https://github.com/shadcn-ui/ui/commit/64b2f1a5ad865c831045c954fec85e0fec2289e7) Thanks [@shadcn](https://github.com/shadcn)! - add docs support
## 2.0.5
### Patch Changes
- [#4787](https://github.com/shadcn-ui/ui/pull/4787) [`99ff9ca`](https://github.com/shadcn-ui/ui/commit/99ff9caf7180c8c19df130969bdef3d0fb78b218) Thanks [@shadcn](https://github.com/shadcn)! - add src to content for tailwind
## 2.0.4
### Patch Changes

View File

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

View File

@@ -20,6 +20,7 @@ import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import { getRegistryBaseColors, getRegistryStyles } from "@/src/utils/registry"
import { spinner } from "@/src/utils/spinner"
import { updateTailwindContent } from "@/src/utils/updaters/update-tailwind-content"
import { Command } from "commander"
import prompts from "prompts"
import { z } from "zod"
@@ -137,6 +138,18 @@ export async function runInit(
options.isNewProject || projectInfo?.framework.name === "next-app",
})
// If a new project is using src dir, let's update the tailwind content config.
// TODO: Handle this per framework.
if (options.isNewProject && options.srcDir) {
await updateTailwindContent(
["./src/**/*.{js,ts,jsx,tsx,mdx}"],
fullConfig,
{
silent: options.silent,
}
)
}
return fullConfig
}

View File

@@ -1,5 +1,6 @@
import { type Config } from "@/src/utils/get-config"
import { handleError } from "@/src/utils/handle-error"
import { logger } from "@/src/utils/logger"
import { registryResolveItemsTree } from "@/src/utils/registry"
import { spinner } from "@/src/utils/spinner"
import { updateCssVars } from "@/src/utils/updaters/update-css-vars"
@@ -48,4 +49,8 @@ export async function addComponents(
overwrite: options.overwrite,
silent: options.silent,
})
if (tree.docs) {
logger.info(tree.docs)
}
}

View File

@@ -321,6 +321,13 @@ export async function registryResolveItemsTree(
cssVars = deepmerge(cssVars, item.cssVars ?? {})
})
let docs = ""
payload.forEach((item) => {
if (item.docs) {
docs += `${item.docs}\n`
}
})
return registryResolvedItemsTreeSchema.parse({
dependencies: deepmerge.all(
payload.map((item) => item.dependencies ?? [])
@@ -331,6 +338,7 @@ export async function registryResolveItemsTree(
files: deepmerge.all(payload.map((item) => item.files ?? [])),
tailwind,
cssVars,
docs,
})
} catch (error) {
handleError(error)

View File

@@ -46,6 +46,7 @@ export const registryItemSchema = z.object({
tailwind: registryItemTailwindSchema.optional(),
cssVars: registryItemCssVarsSchema.optional(),
meta: z.record(z.string(), z.any()).optional(),
docs: z.string().optional(),
})
export type RegistryItem = z.infer<typeof registryItemSchema>
@@ -82,4 +83,5 @@ export const registryResolvedItemsTreeSchema = registryItemSchema.pick({
files: true,
tailwind: true,
cssVars: true,
docs: true,
})

View File

@@ -249,7 +249,7 @@ function addTailwindConfigPlugin(
return configObject
}
async function _createSourceFile(input: string, config: Config | null) {
export async function _createSourceFile(input: string, config: Config | null) {
const dir = await fs.mkdtemp(path.join(tmpdir(), "shadcn-"))
const resolvedPath =
config?.resolvedPaths?.tailwindConfig || "tailwind.config.ts"
@@ -268,7 +268,7 @@ async function _createSourceFile(input: string, config: Config | null) {
return sourceFile
}
function _getQuoteChar(configObject: ObjectLiteralExpression) {
export function _getQuoteChar(configObject: ObjectLiteralExpression) {
return configObject
.getFirstDescendantByKind(SyntaxKind.StringLiteral)
?.getQuoteKind() === QuoteKind.Single

View File

@@ -0,0 +1,121 @@
import { promises as fs } from "fs"
import path from "path"
import { Config } from "@/src/utils/get-config"
import { highlighter } from "@/src/utils/highlighter"
import { spinner } from "@/src/utils/spinner"
import {
_createSourceFile,
_getQuoteChar,
} from "@/src/utils/updaters/update-tailwind-config"
import { ObjectLiteralExpression, SyntaxKind } from "ts-morph"
export async function updateTailwindContent(
content: string[],
config: Config,
options: {
silent?: boolean
}
) {
if (!content) {
return
}
options = {
silent: false,
...options,
}
const tailwindFileRelativePath = path.relative(
config.resolvedPaths.cwd,
config.resolvedPaths.tailwindConfig
)
const tailwindSpinner = spinner(
`Updating ${highlighter.info(tailwindFileRelativePath)}`,
{
silent: options.silent,
}
).start()
const raw = await fs.readFile(config.resolvedPaths.tailwindConfig, "utf8")
const output = await transformTailwindContent(raw, content, config)
await fs.writeFile(config.resolvedPaths.tailwindConfig, output, "utf8")
tailwindSpinner?.succeed()
}
export async function transformTailwindContent(
input: string,
content: string[],
config: Config
) {
const sourceFile = await _createSourceFile(input, config)
// Find the object with content property.
// This is faster than traversing the default export.
// TODO: maybe we do need to traverse the default export?
const configObject = sourceFile
.getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression)
.find((node) =>
node
.getProperties()
.some(
(property) =>
property.isKind(SyntaxKind.PropertyAssignment) &&
property.getName() === "content"
)
)
// We couldn't find the config object, so we return the input as is.
if (!configObject) {
return input
}
addTailwindConfigContent(configObject, content)
return sourceFile.getFullText()
}
async function addTailwindConfigContent(
configObject: ObjectLiteralExpression,
content: string[]
) {
const quoteChar = _getQuoteChar(configObject)
const existingProperty = configObject.getProperty("content")
if (!existingProperty) {
const newProperty = {
name: "content",
initializer: `[${quoteChar}${content.join(
`${quoteChar}, ${quoteChar}`
)}${quoteChar}]`,
}
configObject.addPropertyAssignment(newProperty)
return configObject
}
if (existingProperty.isKind(SyntaxKind.PropertyAssignment)) {
const initializer = existingProperty.getInitializer()
// If property is an array, append.
if (initializer?.isKind(SyntaxKind.ArrayLiteralExpression)) {
for (const contentItem of content) {
const newValue = `${quoteChar}${contentItem}${quoteChar}`
// Check if the array already contains the value.
if (
initializer
.getElements()
.map((element) => element.getText())
.includes(newValue)
) {
continue
}
initializer.addElement(newValue)
}
}
return configObject
}
return configObject
}

View File

@@ -20,6 +20,7 @@ exports[`registryResolveItemTree > should resolve index 1`] = `
"tailwind-merge",
],
"devDependencies": [],
"docs": "",
"files": [
{
"content": ""use client"
@@ -93,6 +94,7 @@ exports[`registryResolveItemTree > should resolve items tree 1`] = `
"@radix-ui/react-slot",
],
"devDependencies": [],
"docs": "",
"files": [
{
"content": "import * as React from "react"
@@ -171,6 +173,7 @@ exports[`registryResolveItemTree > should resolve multiple items tree 1`] = `
"@radix-ui/react-dialog",
],
"devDependencies": [],
"docs": "",
"files": [
{
"content": "import * as React from "react"

View File

@@ -0,0 +1,52 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`transformTailwindContent -> content property > should NOT add content property if already in config 1`] = `
"import type { Config } from 'tailwindcss'
const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./bar/**/*.{js,ts,jsx,tsx,mdx}"
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
}
export default config
"
`;
exports[`transformTailwindContent -> content property > should add content property if not in config 1`] = `
"import type { Config } from 'tailwindcss'
const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./foo/**/*.{js,ts,jsx,tsx,mdx}",
"./bar/**/*.{js,ts,jsx,tsx,mdx}"
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
}
export default config
"
`;

View File

@@ -0,0 +1,94 @@
import { describe, expect, test } from "vitest"
import { transformTailwindContent } from "../../../src/utils/updaters/update-tailwind-content"
const SHARED_CONFIG = {
$schema: "https://ui.shadcn.com/schema.json",
style: "new-york",
rsc: true,
tsx: true,
tailwind: {
config: "tailwind.config.ts",
css: "app/globals.css",
baseColor: "slate",
cssVariables: true,
},
aliases: {
components: "@/components",
utils: "@/lib/utils",
},
resolvedPaths: {
cwd: ".",
tailwindConfig: "tailwind.config.ts",
tailwindCss: "app/globals.css",
components: "./components",
utils: "./lib/utils",
ui: "./components/ui",
},
}
describe("transformTailwindContent -> content property", () => {
test("should add content property if not in config", async () => {
expect(
await transformTailwindContent(
`import type { Config } from 'tailwindcss'
const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
}
export default config
`,
["./foo/**/*.{js,ts,jsx,tsx,mdx}", "./bar/**/*.{js,ts,jsx,tsx,mdx}"],
{
config: SHARED_CONFIG,
}
)
).toMatchSnapshot()
})
test("should NOT add content property if already in config", async () => {
expect(
await transformTailwindContent(
`import type { Config } from 'tailwindcss'
const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
}
export default config
`,
["./app/**/*.{js,ts,jsx,tsx,mdx}", "./bar/**/*.{js,ts,jsx,tsx,mdx}"],
{
config: SHARED_CONFIG,
}
)
).toMatchSnapshot()
})
})