mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-22 20:25:44 +00:00
Compare commits
16 Commits
shadcn@2.0
...
shadcn@2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
061083006f | ||
|
|
1af66c2d08 | ||
|
|
0993d98cc7 | ||
|
|
52d223393a | ||
|
|
207b69fe8d | ||
|
|
408760a93b | ||
|
|
a9ab7afebf | ||
|
|
b6221ea524 | ||
|
|
9ef7967b0d | ||
|
|
64b2f1a5ad | ||
|
|
f4ca57a79c | ||
|
|
99ff9caf71 | ||
|
|
cd9a55b76a | ||
|
|
49373eed96 | ||
|
|
078dfe6607 | ||
|
|
77fc5ec8db |
9
.github/workflows/issue-stale.yml
vendored
9
.github/workflows/issue-stale.yml
vendored
@@ -16,11 +16,12 @@ jobs:
|
|||||||
name: "Close stale issues with no reproduction"
|
name: "Close stale issues with no reproduction"
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.STALE_TOKEN }}
|
repo-token: ${{ secrets.STALE_TOKEN }}
|
||||||
close-issue-message: "This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you."
|
close-issue-message: "This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please reopen or leave a comment. Thank you.\n(This is an automated message.)"
|
||||||
days-before-issue-close: 7
|
days-before-issue-close: 7
|
||||||
days-before-issue-stale: 15
|
days-before-issue-stale: 30
|
||||||
stale-pr-label: "stale?"
|
stale-pr-label: "stale?"
|
||||||
days-before-pr-close: -1
|
days-before-pr-close: 7
|
||||||
days-before-pr-stale: -1
|
days-before-pr-stale: 15
|
||||||
|
only-pr-labels: "postpone: more info or changes requested,please add a reproduction"
|
||||||
exempt-issue-labels: "roadmap,next,bug"
|
exempt-issue-labels: "roadmap,next,bug"
|
||||||
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
|
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { siteConfig } from "@/config/site"
|
|||||||
import { getTableOfContents } from "@/lib/toc"
|
import { getTableOfContents } from "@/lib/toc"
|
||||||
import { absoluteUrl, cn } from "@/lib/utils"
|
import { absoluteUrl, cn } from "@/lib/utils"
|
||||||
import { Mdx } from "@/components/mdx-components"
|
import { Mdx } from "@/components/mdx-components"
|
||||||
|
import { OpenInV0Cta } from "@/components/open-in-v0-cta"
|
||||||
import { DocsPager } from "@/components/pager"
|
import { DocsPager } from "@/components/pager"
|
||||||
import { DashboardTableOfContents } from "@/components/toc"
|
import { DashboardTableOfContents } from "@/components/toc"
|
||||||
import { badgeVariants } from "@/registry/new-york/ui/badge"
|
import { badgeVariants } from "@/registry/new-york/ui/badge"
|
||||||
@@ -135,17 +136,14 @@ export default async function DocPage({ params }: DocPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
<DocsPager doc={doc} />
|
<DocsPager doc={doc} />
|
||||||
</div>
|
</div>
|
||||||
{doc.toc && (
|
<div className="hidden text-sm xl:block">
|
||||||
<div className="hidden text-sm xl:block">
|
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] pt-4">
|
||||||
<div className="sticky top-16 -mt-10 pt-4">
|
<ScrollArea className="h-full pb-10">
|
||||||
<ScrollArea className="pb-10">
|
{doc.toc && <DashboardTableOfContents toc={toc} />}
|
||||||
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] py-12">
|
<OpenInV0Cta className="mt-6 max-w-[80%]" />
|
||||||
<DashboardTableOfContents toc={toc} />
|
</ScrollArea>
|
||||||
</div>
|
|
||||||
</ScrollArea>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</main>
|
</main>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
35
apps/www/components/open-in-v0-cta.tsx
Normal file
35
apps/www/components/open-in-v0-cta.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ export function DashboardTableOfContents({ toc }: TocProps) {
|
|||||||
const activeHeading = useActiveItem(itemIds)
|
const activeHeading = useActiveItem(itemIds)
|
||||||
const mounted = useMounted()
|
const mounted = useMounted()
|
||||||
|
|
||||||
if (!toc?.items || !mounted) {
|
if (!toc?.items?.length) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ export const docsConfig: DocsConfig = {
|
|||||||
href: "/docs/components/typography",
|
href: "/docs/components/typography",
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Open in v0",
|
||||||
|
href: "/docs/v0",
|
||||||
|
items: [],
|
||||||
|
label: "New",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Figma",
|
title: "Figma",
|
||||||
href: "/docs/figma",
|
href: "/docs/figma",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ links:
|
|||||||
|
|
||||||
Every data table or datagrid I've created has been unique. They all behave differently, have specific sorting and filtering requirements, and work with different data sources.
|
Every data table or datagrid I've created has been unique. They all behave differently, have specific sorting and filtering requirements, and work with different data sources.
|
||||||
|
|
||||||
It doesn't make sense to combine all of these variations into a single component. If we do that, we'll lose the flexibility that [headless UI](https://tanstack.com/table/v8/docs/guide/introduction#what-is-headless-ui) provides.
|
It doesn't make sense to combine all of these variations into a single component. If we do that, we'll lose the flexibility that [headless UI](https://tanstack.com/table/v8/docs/introduction#what-is-headless-ui) provides.
|
||||||
|
|
||||||
So instead of a data-table component, I thought it would be more helpful to provide a guide on how to build your own.
|
So instead of a data-table component, I thought it would be more helpful to provide a guide on how to build your own.
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export default function RootLayout({ children }) {
|
|||||||
The `useToast` hook returns a `toast` function that you can use to display a toast.
|
The `useToast` hook returns a `toast` function that you can use to display a toast.
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useToast } from "@/components/ui/use-toast"
|
import { useToast } from "@/hooks/use-toast"
|
||||||
```
|
```
|
||||||
|
|
||||||
```tsx {2,7-10}
|
```tsx {2,7-10}
|
||||||
|
|||||||
30
apps/www/content/docs/v0.mdx
Normal file
30
apps/www/content/docs/v0.mdx
Normal 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).
|
||||||
BIN
apps/www/public/images/open-in-v0-dark.png
Normal file
BIN
apps/www/public/images/open-in-v0-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
BIN
apps/www/public/images/open-in-v0.png
Normal file
BIN
apps/www/public/images/open-in-v0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
@@ -4,7 +4,7 @@
|
|||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"path": "ui/input.tsx",
|
"path": "ui/input.tsx",
|
||||||
"content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface InputProps\n extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"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\",\n className\n )}\n ref={ref}\n {...props}\n />\n )\n }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n",
|
"content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface InputProps\n extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"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 file:text-foreground 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\",\n className\n )}\n ref={ref}\n {...props}\n />\n )\n }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n",
|
||||||
"type": "registry:ui",
|
"type": "registry:ui",
|
||||||
"target": ""
|
"target": ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"path": "ui/input.tsx",
|
"path": "ui/input.tsx",
|
||||||
"content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface InputProps\n extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50\",\n className\n )}\n ref={ref}\n {...props}\n />\n )\n }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n",
|
"content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface InputProps\n extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50\",\n className\n )}\n ref={ref}\n {...props}\n />\n )\n }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n",
|
||||||
"type": "registry:ui",
|
"type": "registry:ui",
|
||||||
"target": ""
|
"target": ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
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",
|
"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 file:text-foreground 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
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export const registryEntrySchema = z.object({
|
|||||||
category: z.string().optional(),
|
category: z.string().optional(),
|
||||||
subcategory: z.string().optional(),
|
subcategory: z.string().optional(),
|
||||||
chunks: z.array(blockChunkSchema).optional(),
|
chunks: z.array(blockChunkSchema).optional(),
|
||||||
|
docs: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const registrySchema = z.array(registryEntrySchema)
|
export const registrySchema = z.array(registryEntrySchema)
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @shadcn/ui
|
# @shadcn/ui
|
||||||
|
|
||||||
|
## 0.9.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#4797](https://github.com/shadcn-ui/ui/pull/4797) [`207b69f`](https://github.com/shadcn-ui/ui/commit/207b69fe8dd59b10dddc9337d333416976e2a30d) Thanks [@Wiper-R](https://github.com/Wiper-R)! - add scss support
|
||||||
|
|
||||||
## 0.9.0
|
## 0.9.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "shadcn-ui",
|
"name": "shadcn-ui",
|
||||||
"version": "0.9.0",
|
"version": "0.9.1",
|
||||||
"description": "Add components to your apps.",
|
"description": "Add components to your apps.",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export async function getProjectType(cwd: string): Promise<ProjectType | null> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getTailwindCssFile(cwd: string) {
|
export async function getTailwindCssFile(cwd: string) {
|
||||||
const files = await fg.glob("**/*.css", {
|
const files = await fg.glob(["**/*.css", "**/*.scss"], {
|
||||||
cwd,
|
cwd,
|
||||||
deep: 3,
|
deep: 3,
|
||||||
ignore: PROJECT_SHARED_IGNORE,
|
ignore: PROJECT_SHARED_IGNORE,
|
||||||
|
|||||||
@@ -1,5 +1,31 @@
|
|||||||
# @shadcn/ui
|
# @shadcn/ui
|
||||||
|
|
||||||
|
## 2.0.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#4848](https://github.com/shadcn-ui/ui/pull/4848) [`1af66c2`](https://github.com/shadcn-ui/ui/commit/1af66c2d08df7dd7f6a8d4d1544d965e41a1fb0d) Thanks [@jherr](https://github.com/jherr)! - add support for ~ dir in target path
|
||||||
|
|
||||||
|
- [#4815](https://github.com/shadcn-ui/ui/pull/4815) [`408760a`](https://github.com/shadcn-ui/ui/commit/408760a93b398b7d02a0a522a74a7a195ccda7c4) Thanks [@rana-haris-ali](https://github.com/rana-haris-ali)! - fix typo in error message
|
||||||
|
|
||||||
|
## 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
|
## 2.0.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "shadcn",
|
"name": "shadcn",
|
||||||
"version": "2.0.3",
|
"version": "2.0.7",
|
||||||
"description": "Add components to your apps.",
|
"description": "Add components to your apps.",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { highlighter } from "@/src/utils/highlighter"
|
|||||||
import { logger } from "@/src/utils/logger"
|
import { logger } from "@/src/utils/logger"
|
||||||
import { getRegistryBaseColors, getRegistryStyles } from "@/src/utils/registry"
|
import { getRegistryBaseColors, getRegistryStyles } from "@/src/utils/registry"
|
||||||
import { spinner } from "@/src/utils/spinner"
|
import { spinner } from "@/src/utils/spinner"
|
||||||
|
import { updateTailwindContent } from "@/src/utils/updaters/update-tailwind-content"
|
||||||
import { Command } from "commander"
|
import { Command } from "commander"
|
||||||
import prompts from "prompts"
|
import prompts from "prompts"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
@@ -137,6 +138,18 @@ export async function runInit(
|
|||||||
options.isNewProject || projectInfo?.framework.name === "next-app",
|
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
|
return fullConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { type Config } from "@/src/utils/get-config"
|
import { type Config } from "@/src/utils/get-config"
|
||||||
import { handleError } from "@/src/utils/handle-error"
|
import { handleError } from "@/src/utils/handle-error"
|
||||||
|
import { logger } from "@/src/utils/logger"
|
||||||
import { registryResolveItemsTree } from "@/src/utils/registry"
|
import { registryResolveItemsTree } from "@/src/utils/registry"
|
||||||
import { spinner } from "@/src/utils/spinner"
|
import { spinner } from "@/src/utils/spinner"
|
||||||
import { updateCssVars } from "@/src/utils/updaters/update-css-vars"
|
import { updateCssVars } from "@/src/utils/updaters/update-css-vars"
|
||||||
@@ -48,4 +49,8 @@ export async function addComponents(
|
|||||||
overwrite: options.overwrite,
|
overwrite: options.overwrite,
|
||||||
silent: options.silent,
|
silent: options.silent,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (tree.docs) {
|
||||||
|
logger.info(tree.docs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export async function createProject(
|
|||||||
name: "proceed",
|
name: "proceed",
|
||||||
message: `The path ${highlighter.info(
|
message: `The path ${highlighter.info(
|
||||||
options.cwd
|
options.cwd
|
||||||
)} is does not contain a package.json file. Would you like to start a new ${highlighter.info(
|
)} does not contain a package.json file. Would you like to start a new ${highlighter.info(
|
||||||
"Next.js"
|
"Next.js"
|
||||||
)} project?`,
|
)} project?`,
|
||||||
initial: true,
|
initial: true,
|
||||||
|
|||||||
@@ -174,6 +174,31 @@ async function fetchRegistry(paths: string[]) {
|
|||||||
404: "Not found",
|
404: "Not found",
|
||||||
500: "Internal server error",
|
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 result = await response.json()
|
||||||
const message =
|
const message =
|
||||||
result && typeof result === "object" && "error" in result
|
result && typeof result === "object" && "error" in result
|
||||||
@@ -296,6 +321,13 @@ export async function registryResolveItemsTree(
|
|||||||
cssVars = deepmerge(cssVars, item.cssVars ?? {})
|
cssVars = deepmerge(cssVars, item.cssVars ?? {})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let docs = ""
|
||||||
|
payload.forEach((item) => {
|
||||||
|
if (item.docs) {
|
||||||
|
docs += `${item.docs}\n`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return registryResolvedItemsTreeSchema.parse({
|
return registryResolvedItemsTreeSchema.parse({
|
||||||
dependencies: deepmerge.all(
|
dependencies: deepmerge.all(
|
||||||
payload.map((item) => item.dependencies ?? [])
|
payload.map((item) => item.dependencies ?? [])
|
||||||
@@ -306,6 +338,7 @@ export async function registryResolveItemsTree(
|
|||||||
files: deepmerge.all(payload.map((item) => item.files ?? [])),
|
files: deepmerge.all(payload.map((item) => item.files ?? [])),
|
||||||
tailwind,
|
tailwind,
|
||||||
cssVars,
|
cssVars,
|
||||||
|
docs,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error)
|
handleError(error)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export const registryItemSchema = z.object({
|
|||||||
tailwind: registryItemTailwindSchema.optional(),
|
tailwind: registryItemTailwindSchema.optional(),
|
||||||
cssVars: registryItemCssVarsSchema.optional(),
|
cssVars: registryItemCssVarsSchema.optional(),
|
||||||
meta: z.record(z.string(), z.any()).optional(),
|
meta: z.record(z.string(), z.any()).optional(),
|
||||||
|
docs: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type RegistryItem = z.infer<typeof registryItemSchema>
|
export type RegistryItem = z.infer<typeof registryItemSchema>
|
||||||
@@ -82,4 +83,5 @@ export const registryResolvedItemsTreeSchema = registryItemSchema.pick({
|
|||||||
files: true,
|
files: true,
|
||||||
tailwind: true,
|
tailwind: true,
|
||||||
cssVars: true,
|
cssVars: true,
|
||||||
|
docs: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,6 +17,19 @@ import { transformRsc } from "@/src/utils/transformers/transform-rsc"
|
|||||||
import { transformTwPrefixes } from "@/src/utils/transformers/transform-tw-prefix"
|
import { transformTwPrefixes } from "@/src/utils/transformers/transform-tw-prefix"
|
||||||
import prompts from "prompts"
|
import prompts from "prompts"
|
||||||
|
|
||||||
|
export function resolveTargetDir(
|
||||||
|
projectInfo: Awaited<ReturnType<typeof getProjectInfo>>,
|
||||||
|
config: Config,
|
||||||
|
target: string
|
||||||
|
) {
|
||||||
|
if (target.startsWith("~/")) {
|
||||||
|
return path.join(config.resolvedPaths.cwd, target.replace("~/", ""))
|
||||||
|
}
|
||||||
|
return projectInfo?.isSrcDir
|
||||||
|
? path.join(config.resolvedPaths.cwd, "src", target)
|
||||||
|
: path.join(config.resolvedPaths.cwd, target)
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateFiles(
|
export async function updateFiles(
|
||||||
files: RegistryItem["files"],
|
files: RegistryItem["files"],
|
||||||
config: Config,
|
config: Config,
|
||||||
@@ -58,9 +71,7 @@ export async function updateFiles(
|
|||||||
let filePath = path.join(targetDir, fileName)
|
let filePath = path.join(targetDir, fileName)
|
||||||
|
|
||||||
if (file.target) {
|
if (file.target) {
|
||||||
filePath = projectInfo?.isSrcDir
|
filePath = resolveTargetDir(projectInfo, config, file.target)
|
||||||
? path.join(config.resolvedPaths.cwd, "src", file.target)
|
|
||||||
: path.join(config.resolvedPaths.cwd, file.target)
|
|
||||||
targetDir = path.dirname(filePath)
|
targetDir = path.dirname(filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ function addTailwindConfigPlugin(
|
|||||||
return configObject
|
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 dir = await fs.mkdtemp(path.join(tmpdir(), "shadcn-"))
|
||||||
const resolvedPath =
|
const resolvedPath =
|
||||||
config?.resolvedPaths?.tailwindConfig || "tailwind.config.ts"
|
config?.resolvedPaths?.tailwindConfig || "tailwind.config.ts"
|
||||||
@@ -268,7 +268,7 @@ async function _createSourceFile(input: string, config: Config | null) {
|
|||||||
return sourceFile
|
return sourceFile
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getQuoteChar(configObject: ObjectLiteralExpression) {
|
export function _getQuoteChar(configObject: ObjectLiteralExpression) {
|
||||||
return configObject
|
return configObject
|
||||||
.getFirstDescendantByKind(SyntaxKind.StringLiteral)
|
.getFirstDescendantByKind(SyntaxKind.StringLiteral)
|
||||||
?.getQuoteKind() === QuoteKind.Single
|
?.getQuoteKind() === QuoteKind.Single
|
||||||
|
|||||||
121
packages/shadcn/src/utils/updaters/update-tailwind-content.ts
Normal file
121
packages/shadcn/src/utils/updaters/update-tailwind-content.ts
Normal 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
|
||||||
|
}
|
||||||
@@ -12,16 +12,15 @@ exports[`registryResolveItemTree > should resolve index 1`] = `
|
|||||||
"tailwindcss-animate",
|
"tailwindcss-animate",
|
||||||
"class-variance-authority",
|
"class-variance-authority",
|
||||||
"lucide-react",
|
"lucide-react",
|
||||||
"",
|
|
||||||
"tailwindcss-animate",
|
"tailwindcss-animate",
|
||||||
"class-variance-authority",
|
"class-variance-authority",
|
||||||
"lucide-react",
|
"lucide-react",
|
||||||
"",
|
|
||||||
"@radix-ui/react-label",
|
"@radix-ui/react-label",
|
||||||
"clsx",
|
"clsx",
|
||||||
"tailwind-merge",
|
"tailwind-merge",
|
||||||
],
|
],
|
||||||
"devDependencies": [],
|
"devDependencies": [],
|
||||||
|
"docs": "",
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"content": ""use client"
|
"content": ""use client"
|
||||||
@@ -95,6 +94,7 @@ exports[`registryResolveItemTree > should resolve items tree 1`] = `
|
|||||||
"@radix-ui/react-slot",
|
"@radix-ui/react-slot",
|
||||||
],
|
],
|
||||||
"devDependencies": [],
|
"devDependencies": [],
|
||||||
|
"docs": "",
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"content": "import * as React from "react"
|
"content": "import * as React from "react"
|
||||||
@@ -173,6 +173,7 @@ exports[`registryResolveItemTree > should resolve multiple items tree 1`] = `
|
|||||||
"@radix-ui/react-dialog",
|
"@radix-ui/react-dialog",
|
||||||
],
|
],
|
||||||
"devDependencies": [],
|
"devDependencies": [],
|
||||||
|
"docs": "",
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"content": "import * as React from "react"
|
"content": "import * as React from "react"
|
||||||
|
|||||||
@@ -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
|
||||||
|
"
|
||||||
|
`;
|
||||||
65
packages/shadcn/test/utils/updaters/update-files.test.ts
Normal file
65
packages/shadcn/test/utils/updaters/update-files.test.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { describe, expect, test } from "vitest"
|
||||||
|
|
||||||
|
import { resolveTargetDir } from "../../../src/utils/updaters/update-files"
|
||||||
|
|
||||||
|
describe("resolveTargetDir", () => {
|
||||||
|
test("should handle a home target without a src directory", () => {
|
||||||
|
const targetDir = resolveTargetDir(
|
||||||
|
{
|
||||||
|
isSrcDir: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resolvedPaths: {
|
||||||
|
cwd: "/foo/bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"~/.env"
|
||||||
|
)
|
||||||
|
expect(targetDir).toBe("/foo/bar/.env")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle a home target even with a src directory", () => {
|
||||||
|
const targetDir = resolveTargetDir(
|
||||||
|
{
|
||||||
|
isSrcDir: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resolvedPaths: {
|
||||||
|
cwd: "/foo/bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"~/.env"
|
||||||
|
)
|
||||||
|
expect(targetDir).toBe("/foo/bar/.env")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle a simple target", () => {
|
||||||
|
const targetDir = resolveTargetDir(
|
||||||
|
{
|
||||||
|
isSrcDir: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resolvedPaths: {
|
||||||
|
cwd: "/foo/bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"./components/ui/button.tsx"
|
||||||
|
)
|
||||||
|
expect(targetDir).toBe("/foo/bar/components/ui/button.tsx")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle a simple target with src directory", () => {
|
||||||
|
const targetDir = resolveTargetDir(
|
||||||
|
{
|
||||||
|
isSrcDir: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resolvedPaths: {
|
||||||
|
cwd: "/foo/bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"./components/ui/button.tsx"
|
||||||
|
)
|
||||||
|
expect(targetDir).toBe("/foo/bar/src/components/ui/button.tsx")
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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()
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user