Compare commits

..

23 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
github-actions[bot]
cd9a55b76a chore(release): version packages (#4777)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-07 23:42:29 +04:00
shadcn
49373eed96 fix(shadcn): better error handling (#4776)
* fix(shadcn): better error messages

* chore: changeset
2024-09-07 23:35:37 +04:00
shadcn
078dfe6607 docs(www): Open in v0 docs (#4734)
* feat(www): open in v0

* feat: update copy

* fix: sidebar link

* fix(tests): snapshots
2024-09-04 00:37:57 +04:00
xuxucode
77fc5ec8db Fix use-toast module path (#4728) 2024-09-03 18:37:11 +00:00
github-actions[bot]
cfba3fdf70 chore(release): version packages (#4730)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-03 22:22:26 +04:00
adrianhelvikspond
4e4118f3cf Fix init command for default style - fixes #4722 (#4724)
* Fix init command for default style

The empty dependency string was tripping up package managers.

* fix(www): registry dependencies for default

---------

Co-authored-by: shadcn <m@shadcn.com>
2024-09-03 22:21:51 +04:00
shadcn
faa7a67fb3 fix(shadcn): init with src (#4731)
* fix(shadcn): init with src

* chore: add changesets
2024-09-03 22:16:59 +04:00
shadcn
701e1160ea feat(shadcn): add support for src dir (#4729)
* feat(shadcn): add support for src dir

* chore: add changesets
2024-09-03 21:47:15 +04:00
shadcn
f5931f8d09 docs(www): update installation docs (#4725) 2024-09-03 16:41:48 +04:00
shadcn
5a28937c6e Merge branch 'main' of github.com:shadcn/ui 2024-09-03 16:35:51 +04:00
shadcn
0b74059d38 docs(www): update laravel docs 2024-09-03 16:35:36 +04:00
shadcn
fab9877586 fix(shadcn): handle quote in theme values (#4726)
* fix(shadcn): handle quote in theme values

* chore(shadcn): fix theme values bug
2024-09-03 16:22:45 +04:00
shadcn
0f7591f67c docs(www): updates docs for Astro (#4723) 2024-09-03 15:35:24 +04:00
shadcn
81c7e44863 fix(www): url 2024-08-31 03:15:10 +04:00
shadcn
2fac3e40c2 fix(www): hide sidebar-01 for now 2024-08-31 03:10:14 +04:00
shadcn
5ad11ff851 docs(www): update callout 2024-08-31 03:01:42 +04:00
shadcn
6b92dd8eaf Merge branch 'main' of github.com:shadcn/ui 2024-08-31 02:57:49 +04:00
shadcn
84540f551d fix: blocks 2024-08-31 02:57:37 +04:00
shadcn
99588fff8f fix: cli (#4669) 2024-08-31 02:09:14 +04:00
40 changed files with 777 additions and 264 deletions

View File

@@ -0,0 +1,13 @@
import { LoginForm } from "@/registry/default/block/login-01/components/login-form"
export const iframeHeight = "870px"
export const containerClassName = "w-full h-full"
export default function Page() {
return (
<div className="flex h-screen w-full items-center justify-center px-4">
<LoginForm />
</div>
)
}

View File

@@ -0,0 +1,25 @@
import { AppSidebar } from "@/registry/default/block/sidebar-01/components/app-sidebar"
import {
SidebarLayout,
SidebarTrigger,
} from "@/registry/default/block/sidebar-01/ui/sidebar"
export const iframeHeight = "870px"
export const containerClassName = "w-full h-full"
export default async function Page() {
const { cookies } = await import("next/headers")
return (
<SidebarLayout
defaultOpen={cookies().get("sidebar:state")?.value === "true"}
>
<AppSidebar />
<main className="flex flex-1 flex-col p-2 transition-all duration-300 ease-in-out">
<div className="h-full rounded-md border-2 border-dashed p-2">
<SidebarTrigger />
</div>
</main>
</SidebarLayout>
)
}

View File

@@ -2145,7 +2145,7 @@ export const Index: Record<string, any> = {
registryDependencies: ["avatar","button","collapsible","dropdown-menu","drawer","separator","input","popover","sheet","progress","card","use-mobile"],
files: ["registry/new-york/block/sidebar-01/page.tsx","registry/new-york/block/sidebar-01/components/app-sidebar.tsx","registry/new-york/block/sidebar-01/components/nav-main.tsx","registry/new-york/block/sidebar-01/components/nav-projects.tsx","registry/new-york/block/sidebar-01/components/nav-secondary.tsx","registry/new-york/block/sidebar-01/components/nav-user.tsx","registry/new-york/block/sidebar-01/components/storage-card.tsx","registry/new-york/block/sidebar-01/components/team-switcher.tsx","registry/new-york/block/sidebar-01/hooks/use-sidebar.tsx","registry/new-york/block/sidebar-01/ui/sidebar.tsx"],
component: React.lazy(() => import("@/registry/new-york/block/sidebar-01/page.tsx")),
source: "__registry__/new-york/block/sidebar-01.tsx",
source: "__registry__/new-york/block/sidebar-01/page.tsx",
category: "Application",
subcategory: "Dashboard",
chunks: []
@@ -2156,7 +2156,7 @@ export const Index: Record<string, any> = {
registryDependencies: ["button","card","input","label"],
files: ["registry/new-york/block/login-01/page.tsx","registry/new-york/block/login-01/components/login-form.tsx"],
component: React.lazy(() => import("@/registry/new-york/block/login-01/page.tsx")),
source: "__registry__/new-york/block/login-01.tsx",
source: "__registry__/new-york/block/login-01/page.tsx",
category: "Authentication",
subcategory: "Login",
chunks: []
@@ -5501,7 +5501,7 @@ export const Index: Record<string, any> = {
registryDependencies: ["avatar","button","collapsible","dropdown-menu","drawer","separator","input","popover","sheet","progress","card","use-mobile"],
files: ["registry/default/block/sidebar-01/page.tsx","registry/default/block/sidebar-01/components/app-sidebar.tsx","registry/default/block/sidebar-01/components/nav-main.tsx","registry/default/block/sidebar-01/components/nav-projects.tsx","registry/default/block/sidebar-01/components/nav-secondary.tsx","registry/default/block/sidebar-01/components/nav-user.tsx","registry/default/block/sidebar-01/components/storage-card.tsx","registry/default/block/sidebar-01/components/team-switcher.tsx","registry/default/block/sidebar-01/hooks/use-sidebar.tsx","registry/default/block/sidebar-01/ui/sidebar.tsx"],
component: React.lazy(() => import("@/registry/default/block/sidebar-01/page.tsx")),
source: "__registry__/default/block/sidebar-01.tsx",
source: "__registry__/default/block/sidebar-01/page.tsx",
category: "Application",
subcategory: "Dashboard",
chunks: []
@@ -5512,7 +5512,7 @@ export const Index: Record<string, any> = {
registryDependencies: ["button","card","input","label"],
files: ["registry/default/block/login-01/page.tsx","registry/default/block/login-01/components/login-form.tsx"],
component: React.lazy(() => import("@/registry/default/block/login-01/page.tsx")),
source: "__registry__/default/block/login-01.tsx",
source: "__registry__/default/block/login-01/page.tsx",
category: "Authentication",
subcategory: "Login",
chunks: []

View File

@@ -0,0 +1,13 @@
import { LoginForm } from "@/registry/new-york/block/login-01/components/login-form"
export const iframeHeight = "870px"
export const containerClassName = "w-full h-full"
export default function Page() {
return (
<div className="flex h-screen w-full items-center justify-center px-4">
<LoginForm />
</div>
)
}

View File

@@ -0,0 +1,25 @@
import { AppSidebar } from "@/registry/new-york/block/sidebar-01/components/app-sidebar"
import {
SidebarLayout,
SidebarTrigger,
} from "@/registry/new-york/block/sidebar-01/ui/sidebar"
export const iframeHeight = "870px"
export const containerClassName = "w-full h-full"
export default async function Page() {
const { cookies } = await import("next/headers")
return (
<SidebarLayout
defaultOpen={cookies().get("sidebar:state")?.value === "true"}
>
<AppSidebar />
<main className="flex flex-1 flex-col p-2 transition-all duration-300 ease-in-out">
<div className="h-full rounded-md border-2 border-dashed p-2">
<SidebarTrigger />
</div>
</main>
</SidebarLayout>
)
}

View File

@@ -5,7 +5,10 @@ import { ThemesSwitcher } from "@/components/themes-selector"
export default async function BlocksPage() {
const blocks = (await getAllBlockIds()).filter(
(name) => !name.startsWith("chart-")
(name) =>
!name.startsWith("chart-") &&
!name.startsWith("sidebar-01") &&
!name.startsWith("login-01")
)
// These themes are not compatible with the blocks yet.

View File

@@ -11,6 +11,7 @@ import { siteConfig } from "@/config/site"
import { getTableOfContents } from "@/lib/toc"
import { absoluteUrl, cn } from "@/lib/utils"
import { Mdx } from "@/components/mdx-components"
import { OpenInV0Cta } from "@/components/open-in-v0-cta"
import { DocsPager } from "@/components/pager"
import { DashboardTableOfContents } from "@/components/toc"
import { badgeVariants } from "@/registry/new-york/ui/badge"
@@ -135,17 +136,14 @@ export default async function DocPage({ params }: DocPageProps) {
</div>
<DocsPager doc={doc} />
</div>
{doc.toc && (
<div className="hidden text-sm xl:block">
<div className="sticky top-16 -mt-10 pt-4">
<ScrollArea className="pb-10">
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] py-12">
<DashboardTableOfContents toc={toc} />
</div>
</ScrollArea>
</div>
<div className="hidden text-sm xl:block">
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] pt-4">
<ScrollArea className="h-full pb-10">
{doc.toc && <DashboardTableOfContents toc={toc} />}
<OpenInV0Cta className="mt-6 max-w-[80%]" />
</ScrollArea>
</div>
)}
</div>
</main>
)
}

View File

@@ -7,7 +7,7 @@ import { Separator } from "@/registry/new-york/ui/separator"
export function Announcement() {
return (
<Link
href="/docs/components/chart"
href="/docs/changelog"
className="group inline-flex items-center px-0.5 text-sm font-medium"
>
<PieChart className="h-4 w-4" />{" "}

View File

@@ -0,0 +1,35 @@
import Link from "next/link"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/new-york/ui/button"
export function OpenInV0Cta({ className }: React.ComponentProps<"div">) {
return (
<div
className={cn(
"group relative flex flex-col gap-2 rounded-lg border p-4 text-sm",
className
)}
>
<div className="text-balance text-lg font-semibold leading-tight group-hover:underline">
Bring your app built with shadcn to life on Vercel
</div>
<div>Trusted by OpenAI, Sonos, Chick-fil-A, and more.</div>
<div>
Vercel provides tools and infrastructure to deploy apps and features at
scale.
</div>
<Button size="sm" className="mt-2 w-fit">
Deploy Now
</Button>
<Link
href="https://vercel.com/new?utm_source=shadcn_site&utm_medium=web&utm_campaign=docs_cta_deploy_now_callout"
target="_blank"
rel="noreferrer"
className="absolute inset-0"
>
<span className="sr-only">Deploy to Vercel</span>
</Link>
</div>
)
}

View File

@@ -26,7 +26,7 @@ export function DashboardTableOfContents({ toc }: TocProps) {
const activeHeading = useActiveItem(itemIds)
const mounted = useMounted()
if (!toc?.items || !mounted) {
if (!toc?.items?.length) {
return null
}

View File

@@ -77,6 +77,12 @@ export const docsConfig: DocsConfig = {
href: "/docs/components/typography",
items: [],
},
{
title: "Open in v0",
href: "/docs/v0",
items: [],
label: "New",
},
{
title: "Figma",
href: "/docs/figma",
@@ -89,6 +95,46 @@ export const docsConfig: DocsConfig = {
},
],
},
{
title: "Installation",
items: [
{
title: "Next.js",
href: "/docs/installation/next",
items: [],
},
{
title: "Vite",
href: "/docs/installation/vite",
items: [],
},
{
title: "Remix",
href: "/docs/installation/remix",
items: [],
},
{
title: "Astro",
href: "/docs/installation/astro",
items: [],
},
{
title: "Laravel",
href: "/docs/installation/laravel",
items: [],
},
{
title: "Gatsby",
href: "/docs/installation/gatsby",
items: [],
},
{
title: "Manual",
href: "/docs/installation/manual",
items: [],
},
],
},
{
title: "Components",
items: [

View File

@@ -5,7 +5,7 @@ description: Use the CLI to add components to your project.
<Callout>
**Note:** We just released a new `shadcn` CLI to make it easier to add components to your project. See the [changelog](/docs/changelog) for more information.
**Note:** We just released a new `shadcn` CLI. See the [changelog](/docs/changelog) for more information.
</Callout>

View File

@@ -104,7 +104,7 @@ export default function RootLayout({ children }) {
The `useToast` hook returns a `toast` function that you can use to display a toast.
```tsx
import { useToast } from "@/components/ui/use-toast"
import { useToast } from "@/components/hooks/use-toast"
```
```tsx {2,7-10}

View File

@@ -18,18 +18,12 @@ npm create astro@latest
You will be asked a few questions to configure your project:
```txt showLineNumbers
- Where should we create your new project?
./your-app-name
- How would you like to start your new project?
Choose a starter template (or Empty)
- Install dependencies?
Yes
- Do you plan to write TypeScript?
Yes
- How strict should TypeScript be?
Strict
- Initialize a new git repository? (optional)
Yes/No
- Where should we create your new project? ./your-app-name
- How would you like to start your new project? Choose a template
- Do you plan to write TypeScript? Yes
- How strict should TypeScript be? Strict
- Install dependencies? Yes
- Initialize a new git repository? (optional) Yes/No
```
### Add React to your project
@@ -48,23 +42,47 @@ Answer `Yes` to all the question prompted by the CLI when installing React.
### Add Tailwind CSS to your project
Install Tailwind CSS using the Astro CLI:
```bash
npx astro add tailwind
```
<Callout className="mt-4">
<Step>Create a `styles/globals.css` file in the `src` folder.</Step>
Answer `Yes` to all the question prompted by the CLI when installing Tailwind CSS.
```css title="styles/globals.css" showLineNumbers
@tailwind base;
@tailwind components;
@tailwind utilities;
```
</Callout>
<Step>Import the `globals.css` file</Step>
Import the `styles/globals.css` file in the `src/pages/index.astro` file:
```ts title="src/pages/index.astro" showLineNumbers
---
import '@/styles/globals.css'
---
```
<Step>Update `astro.config.mjs` and set `applyBaseStyles` to `false`</Step>
To prevent serving the Tailwind base styles twice, we need to tell Astro not to apply the base styles, since we already include them in our own `globals.css` file. To do this, set the `applyBaseStyles` config option for the tailwind plugin in `astro.config.mjs` to `false`.
```js title="astro.config.mjs" {3-5} showLineNumbers
export default defineConfig({
integrations: [
tailwind({
applyBaseStyles: false,
}),
],
})
```
### Edit tsconfig.json file
Add the following code to the `tsconfig.json` file to resolve paths:
```ts {4-9} showLineNumbers
```ts title="tsconfig.json" {4-9} showLineNumbers
{
"compilerOptions": {
// ...
@@ -81,62 +99,12 @@ Add the following code to the `tsconfig.json` file to resolve paths:
### 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
```
### Configure components.json
You will be asked a few questions to configure `components.json`:
```txt showLineNumbers
Would you like to use TypeScript (recommended)? no / yes
Which style would you like to use? Default
Which color would you like to use as base color? Slate
Where is your global CSS file? ./src/styles/globals.css
Do you want to use CSS variables for colors? no / yes
Where is your tailwind.config.js located? tailwind.config.mjs
Configure the import alias for components: @/components
Configure the import alias for utils: @/lib/utils
Are you using React Server Components? no
```
### Import the globals.css file
Import the `globals.css` file in the `src/pages/index.astro` file:
```ts {2} showLineNumbers
---
import '@/styles/globals.css'
---
```
### Update astro tailwind config
To prevent serving the Tailwind base styles twice, we need to tell Astro not to apply the base styles, since we already include them in our own `globals.css` file. To do this, set the `applyBaseStyles` config option for the tailwind plugin in `astro.config.mjs` to `false`.
```ts {3-5} showLineNumbers
export default defineConfig({
integrations: [
tailwind({
applyBaseStyles: false,
}),
],
})
```
### Update tailwind.config.mjs
When running `npx shadcn@latest init`, your tailwind config for content will be overwritten. To fix this, change the `module.exports` to `export default` and the `content` section with the code below to your `tailwind.config.mjs` file:
```js {1-4} showLineNumbers
export default {
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
}
```
### That's it
You can now start adding components to your project.

View File

@@ -45,19 +45,6 @@ description: How to install dependencies and structure your app.
</svg>
<p className="font-medium mt-2">Remix</p>
</LinkedCard>
<LinkedCard href="/docs/installation/gatsby">
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
className="w-10 h-10"
fill="currentColor"
>
<title>Gatsby</title>
<path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.6 0 12 0zm0 2.571c3.171 0 5.915 1.543 7.629 3.858l-1.286 1.115C16.886 5.572 14.571 4.286 12 4.286c-3.343 0-6.171 2.143-7.286 5.143l9.857 9.857c2.486-.857 4.373-3 4.973-5.572h-4.115V12h6c0 4.457-3.172 8.228-7.372 9.17L2.83 9.944C3.772 5.743 7.543 2.57 12 2.57zm-9.429 9.6l9.344 9.258c-2.4-.086-4.801-.943-6.601-2.743-1.8-1.8-2.743-4.201-2.743-6.515z" />
</svg>
<p className="font-medium mt-2">Gatsby</p>
</LinkedCard>
<LinkedCard href="/docs/installation/astro">
<svg
role="img"
@@ -86,6 +73,19 @@ description: How to install dependencies and structure your app.
</svg>
<p className="font-medium mt-2">Laravel</p>
</LinkedCard>
<LinkedCard href="/docs/installation/gatsby">
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
className="w-10 h-10"
fill="currentColor"
>
<title>Gatsby</title>
<path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.6 0 12 0zm0 2.571c3.171 0 5.915 1.543 7.629 3.858l-1.286 1.115C16.886 5.572 14.571 4.286 12 4.286c-3.343 0-6.171 2.143-7.286 5.143l9.857 9.857c2.486-.857 4.373-3 4.973-5.572h-4.115V12h6c0 4.457-3.172 8.228-7.372 9.17L2.83 9.944C3.772 5.743 7.543 2.57 12 2.57zm-9.429 9.6l9.344 9.258c-2.4-.086-4.801-.943-6.601-2.743-1.8-1.8-2.743-4.201-2.743-6.515z" />
</svg>
<p className="font-medium mt-2">Gatsby</p>
</LinkedCard>
<LinkedCard href="/docs/installation/manual">
<svg
role="img"

View File

@@ -15,7 +15,7 @@ laravel new my-app --typescript --breeze --stack=react --git --no-interaction
### 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
@@ -26,106 +26,9 @@ npx shadcn@latest init
You will be asked a few questions to configure `components.json`:
```txt showLineNumbers
Would you like to use TypeScript (recommended)? no / yes
Which style would you like to use? Default
Which color would you like to use as base color? Slate
Where is your global CSS file? resources/css/app.css
Do you want to use CSS variables for colors? no / yes
Where is your tailwind.config.js located? tailwind.config.js
Configure the import alias for components: @/Components
Configure the import alias for utils: @/lib/utils
Are you using React Server Components? no / yes
```
### Update tailwind.config.js
The `shadcn-ui` CLI will automatically overwrite your `tailwind.config.js`. Update it to look like this:
```js
import forms from "@tailwindcss/forms"
import defaultTheme from "tailwindcss/defaultTheme"
/** @type {import('tailwindcss').Config} */
export default {
darkMode: "class",
content: [
"./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php",
"./storage/framework/views/*.php",
"./resources/views/**/*.blade.php",
"./resources/js/**/*.tsx",
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: `var(--radius)`,
md: `calc(var(--radius) - 2px)`,
sm: "calc(var(--radius) - 4px)",
},
fontFamily: {
sans: ["Figtree", ...defaultTheme.fontFamily.sans],
},
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",
},
},
},
plugins: [forms, require("tailwindcss-animate")],
}
Which style would you like to use?
Which color would you like to use as base color?
Do you want to use CSS variables for colors? yes
```
### That's it

View File

@@ -13,6 +13,16 @@ Run the `init` command to create a new Next.js project or to setup an existing o
npx shadcn@latest init
```
<Callout className="mt-4">
You can use the `-d` flag for defaults i.e `new-york`, `zinc` and `yes` for the css variables.
```bash
npx shadcn@latest init -d
```
</Callout>
### Configure components.json
You will be asked a few questions to configure `components.json`:
@@ -23,40 +33,6 @@ Which color would you like to use as base color? Zinc
Do you want to use CSS variables for colors? no / yes
```
### App structure
Here's how I structure my Next.js apps. You can use this as a reference:
```txt {6-10,14-15}
.
├── app
│ ├── layout.tsx
│ └── page.tsx
├── components
│ ├── ui
│ │ ├── alert-dialog.tsx
│ │ ├── button.tsx
│ │ ├── dropdown-menu.tsx
│ │ └── ...
│ ├── main-nav.tsx
│ ├── page-header.tsx
│ └── ...
├── lib
│ └── utils.ts
├── styles
│ └── globals.css
├── next.config.js
├── package.json
├── postcss.config.js
├── tailwind.config.ts
└── tsconfig.json
```
- I place the UI components in the `components/ui` folder.
- The rest of the components such as `<PageHeader />` and `<MainNav />` are placed in the `components` folder.
- The `lib` folder contains all the utility functions. I have a `utils.ts` where I define the `cn` helper.
- The `styles` folder contains the global CSS.
### That's it
You can now start adding components to your project.

View File

@@ -0,0 +1,30 @@
---
title: Open in v0
description: Open components in v0 for customization.
---
Every component on ui.shadcn.com is editable on [v0 by Vercel](https://v0.dev). This allows you to easily customize the components in natural language and paste into your app.
<a href="https://vercel.com/signup?utm_source=shad&utm_medium=web&utm_campaign=docs_cta_signup">
<Image
src="/images/open-in-v0.png"
width="716"
height="420"
alt="Open in v0"
className="border dark:hidden shadow-sm rounded-lg overflow-hidden mt-6 w-full"
/>
<Image
src="/images/open-in-v0-dark.png"
width="716"
height="420"
alt="Open in v0"
className="border hidden dark:block shadow-sm rounded-lg overflow-hidden mt-6 w-full"
/>
<span class="sr-only">Open in v0</span>
</a>
To use v0, sign-up for a free [Vercel account here](https://vercel.com/signup?utm_source=shad&utm_medium=web&utm_campaign=docs_cta_signup). In addition to v0, this gives you free access to Vercel's frontend cloud platform by the creators of Next.js, where you can deploy and host your project for free.
Learn more about getting started with [Vercel here](https://vercel.com/docs/getting-started-with-vercel?utm_source=shadcn_site&utm_medium=web&utm_campaign=docs_cta_about_vercel).
Learn more about getting started with [v0 here](https://v0.dev/faq).

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -4,8 +4,7 @@
"dependencies": [
"tailwindcss-animate",
"class-variance-authority",
"lucide-react",
""
"lucide-react"
],
"registryDependencies": [
"utils"

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

@@ -231,6 +231,18 @@ export const Index: Record<string, any> = {
// // Write the source file for blocks only.
sourceFilename = `__registry__/${style.name}/${type}/${item.name}.tsx`
if (item.files) {
const files = item.files.map((file) =>
typeof file === "string"
? { type: "registry:page", path: file }
: file
)
if (files?.length) {
sourceFilename = `__registry__/${style.name}/${files[0].path}`
}
}
const sourcePath = path.join(process.cwd(), sourceFilename)
if (!existsSync(sourcePath)) {
await fs.mkdir(sourcePath, { recursive: true })
@@ -414,16 +426,21 @@ async function buildStylesIndex() {
for (const style of styles) {
const targetPath = path.join(REGISTRY_PATH, "styles", style.name)
const dependencies = [
"tailwindcss-animate",
"class-variance-authority",
"lucide-react",
]
// TODO: Remove this when we migrate to lucide-react.
if (style.name === "new-york") {
dependencies.push("@radix-ui/react-icons")
}
const payload: RegistryEntry = {
name: style.name,
type: "registry:style",
dependencies: [
"tailwindcss-animate",
"class-variance-authority",
"lucide-react",
// TODO: Remove this when we migrate to lucide-react.
style.name === "new-york" ? "@radix-ui/react-icons" : "",
],
dependencies,
registryDependencies: ["utils"],
tailwind: {
config: {

View File

@@ -25,6 +25,7 @@
"shadcn:dev": "turbo --filter=shadcn dev",
"shadcn": "pnpm --filter=shadcn start:dev",
"shadcn:build": "pnpm --filter=shadcn build",
"shadcn:test": "pnpm --filter=shadcn test",
"docs:build": "pnpm --filter=www build:docs",
"www:dev": "pnpm --filter=www dev",
"www:build": "pnpm --filter=www build",

View File

@@ -1,5 +1,31 @@
# @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
- [#4776](https://github.com/shadcn-ui/ui/pull/4776) [`49373ee`](https://github.com/shadcn-ui/ui/commit/49373eed9672d6ecf82219f6e682cab914e7cc41) Thanks [@shadcn](https://github.com/shadcn)! - better error handling
## 2.0.3
### Patch Changes
- [#4729](https://github.com/shadcn-ui/ui/pull/4729) [`701e116`](https://github.com/shadcn-ui/ui/commit/701e1160ea86b2ddd9f43121b447477caaf7aefa) Thanks [@shadcn](https://github.com/shadcn)! - add --src-dir
- [#4731](https://github.com/shadcn-ui/ui/pull/4731) [`faa7a67`](https://github.com/shadcn-ui/ui/commit/faa7a67fb30c741c2f2559fc64b34f103aac9d79) Thanks [@shadcn](https://github.com/shadcn)! - fix routes for src dir
## 2.0.0
### Major Changes

View File

@@ -1,6 +1,6 @@
{
"name": "shadcn",
"version": "2.0.0",
"version": "2.0.6",
"description": "Add components to your apps.",
"publishConfig": {
"access": "public"
@@ -13,7 +13,7 @@
"repository": {
"type": "git",
"url": "https://github.com/shadcn/ui.git",
"directory": "packages/cli"
"directory": "packages/shadcn"
},
"files": [
"dist"

View File

@@ -21,6 +21,7 @@ export const addOptionsSchema = z.object({
all: z.boolean(),
path: z.string().optional(),
silent: z.boolean(),
srcDir: z.boolean().optional(),
})
export const add = new Command()
@@ -40,6 +41,11 @@ export const add = new Command()
.option("-a, --all", "add all available components", false)
.option("-p, --path <path>", "the path to add the component to.")
.option("-s, --silent", "mute output.", false)
.option(
"--src-dir",
"use the src directory when creating a new project.",
false
)
.action(async (components, opts) => {
try {
const options = addOptionsSchema.parse({
@@ -100,6 +106,7 @@ export const add = new Command()
skipPreflight: false,
silent: true,
isNewProject: false,
srcDir: options.srcDir,
})
}
@@ -108,6 +115,7 @@ export const add = new Command()
const { projectPath } = await createProject({
cwd: options.cwd,
force: options.overwrite,
srcDir: options.srcDir,
})
if (!projectPath) {
logger.break()
@@ -123,6 +131,7 @@ export const add = new Command()
skipPreflight: true,
silent: true,
isNewProject: true,
srcDir: options.srcDir,
})
shouldUpdateAppIndex =

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"
@@ -32,6 +33,7 @@ export const initOptionsSchema = z.object({
force: z.boolean(),
silent: z.boolean(),
isNewProject: z.boolean(),
srcDir: z.boolean().optional(),
})
export const init = new Command()
@@ -50,6 +52,11 @@ export const init = new Command()
process.cwd()
)
.option("-s, --silent", "mute output.", false)
.option(
"--src-dir",
"use the src directory when creating a new project.",
false
)
.action(async (components, opts) => {
try {
const options = initOptionsSchema.parse({
@@ -131,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

@@ -10,15 +10,20 @@ import prompts from "prompts"
import { z } from "zod"
export async function createProject(
options: Pick<z.infer<typeof initOptionsSchema>, "cwd" | "force">
options: Pick<z.infer<typeof initOptionsSchema>, "cwd" | "force" | "srcDir">
) {
options = {
srcDir: false,
...options,
}
if (!options.force) {
const { proceed } = await prompts({
type: "confirm",
name: "proceed",
message: `The path ${highlighter.info(
options.cwd
)} is empty. Would you like to start a new ${highlighter.info(
)} is does not contain a package.json file. Would you like to start a new ${highlighter.info(
"Next.js"
)} project?`,
initial: true,
@@ -81,7 +86,7 @@ export async function createProject(
"--eslint",
"--typescript",
"--app",
"--no-src-dir",
options.srcDir ? "--src-dir" : "--no-src-dir",
"--no-import-alias",
`--use-${packageManager}`,
]
@@ -97,7 +102,7 @@ export async function createProject(
} catch (error) {
logger.break()
logger.error(
`Something went wront creating a new Next.js project. Please try again.`
`Something went wrong creating a new Next.js project. Please try again.`
)
process.exit(1)
}

View File

@@ -164,7 +164,6 @@ async function fetchRegistry(paths: string[]) {
const results = await Promise.all(
paths.map(async (path) => {
const url = getRegistryUrl(path)
console.log("👉", url)
const response = await fetch(url, { agent })
if (!response.ok) {
@@ -175,6 +174,31 @@ async function fetchRegistry(paths: string[]) {
404: "Not found",
500: "Internal server error",
}
if (response.status === 401) {
throw new Error(
`You are not authorized to access the component at ${highlighter.info(
url
)}.\nIf this is a remote registry, you may need to authenticate.`
)
}
if (response.status === 404) {
throw new Error(
`The component at ${highlighter.info(
url
)} was not found.\nIt may not exist at the registry. Please make sure it is a valid component.`
)
}
if (response.status === 403) {
throw new Error(
`You do not have access to the component at ${highlighter.info(
url
)}.\nIf this is a remote registry, you may need to authenticate or a token.`
)
}
const result = await response.json()
const message =
result && typeof result === "object" && "error" in result
@@ -297,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 ?? [])
@@ -307,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

@@ -1,6 +1,7 @@
import { existsSync, promises as fs } from "fs"
import path, { basename } from "path"
import { Config } from "@/src/utils/get-config"
import { getProjectInfo } from "@/src/utils/get-project-info"
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import {
@@ -37,7 +38,12 @@ export async function updateFiles(
const filesCreatedSpinner = spinner(`Updating files.`, {
silent: options.silent,
})?.start()
const baseColor = await getRegistryBaseColor(config.tailwind.baseColor)
const [projectInfo, baseColor] = await Promise.all([
getProjectInfo(config.resolvedPaths.cwd),
getRegistryBaseColor(config.tailwind.baseColor),
])
const filesCreated = []
const filesUpdated = []
const filesSkipped = []
@@ -52,7 +58,9 @@ export async function updateFiles(
let filePath = path.join(targetDir, fileName)
if (file.target) {
filePath = path.join(config.resolvedPaths.cwd, file.target)
filePath = projectInfo?.isSrcDir
? path.join(config.resolvedPaths.cwd, "src", file.target)
: path.join(config.resolvedPaths.cwd, file.target)
targetDir = path.dirname(filePath)
}

View File

@@ -196,12 +196,14 @@ async function addTailwindConfigTheme(
const themeObject = await parseObjectLiteral(themeObjectString)
const result = deepmerge(themeObject, theme)
const resultString = objectToString(result)
.replace(/\'\"/g, "'")
.replace(/\"\'/g, "'")
.replace(/\'\[/g, "[")
.replace(/\]\'/g, "]")
.replace(/\\\'/g, "")
.replace(/\\\'/g, "")
.replace(/\'\"/g, "'") // Replace `\" with "
.replace(/\"\'/g, "'") // Replace `\" with "
.replace(/\'\[/g, "[") // Replace `[ with [
.replace(/\]\'/g, "]") // Replace `] with ]
.replace(/\'\\\'/g, "'") // Replace `\' with '
.replace(/\\\'/g, "'") // Replace \' with '
.replace(/\\\'\'/g, "'")
.replace(/\'\'/g, "'")
themeInitializer.replaceWithText(resultString)
}
@@ -247,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"
@@ -266,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

@@ -12,16 +12,15 @@ exports[`registryResolveItemTree > should resolve index 1`] = `
"tailwindcss-animate",
"class-variance-authority",
"lucide-react",
"",
"tailwindcss-animate",
"class-variance-authority",
"lucide-react",
"",
"@radix-ui/react-label",
"clsx",
"tailwind-merge",
],
"devDependencies": [],
"docs": "",
"files": [
{
"content": ""use client"
@@ -95,6 +94,7 @@ exports[`registryResolveItemTree > should resolve items tree 1`] = `
"@radix-ui/react-slot",
],
"devDependencies": [],
"docs": "",
"files": [
{
"content": "import * as React from "react"
@@ -173,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

@@ -369,6 +369,41 @@ export default config
"
`;
exports[`transformTailwindConfig -> theme > should keep quotes in strings 1`] = `
"import type { Config } from 'tailwindcss'
const config: Config = {
darkMode: ["class"],
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
fontFamily: {
sans: ['Figtree', ...defaultTheme.fontFamily.sans]
},
colors: {
...defaultColors,
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
}
}
}
},
}
export default config
"
`;
exports[`transformTailwindConfig -> theme > should keep spread assignments 1`] = `
"import type { Config } from 'tailwindcss'

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

@@ -760,6 +760,55 @@ export default config
expect(output3).toBe(output1)
expect(output3).toBe(output2)
})
test("should keep quotes in strings", async () => {
expect(
await transformTailwindConfig(
`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: {
fontFamily: {
sans: ['Figtree', ...defaultTheme.fontFamily.sans],
},
colors: {
...defaultColors,
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
},
},
},
}
export default config
`,
{
theme: {
extend: {
colors: {
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
},
},
},
{
config: SHARED_CONFIG,
}
)
).toMatchSnapshot()
})
})
describe("nestSpreadProperties", () => {

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