mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 09:51:40 +00:00
Compare commits
31 Commits
shadcn@2.0
...
shadcn-ui@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
061083006f | ||
|
|
1af66c2d08 | ||
|
|
0993d98cc7 | ||
|
|
52d223393a | ||
|
|
207b69fe8d | ||
|
|
408760a93b | ||
|
|
a9ab7afebf | ||
|
|
b6221ea524 | ||
|
|
9ef7967b0d | ||
|
|
64b2f1a5ad | ||
|
|
f4ca57a79c | ||
|
|
99ff9caf71 | ||
|
|
cd9a55b76a | ||
|
|
49373eed96 | ||
|
|
078dfe6607 | ||
|
|
77fc5ec8db | ||
|
|
cfba3fdf70 | ||
|
|
4e4118f3cf | ||
|
|
faa7a67fb3 | ||
|
|
701e1160ea | ||
|
|
f5931f8d09 | ||
|
|
5a28937c6e | ||
|
|
0b74059d38 | ||
|
|
fab9877586 | ||
|
|
0f7591f67c | ||
|
|
81c7e44863 | ||
|
|
2fac3e40c2 | ||
|
|
5ad11ff851 | ||
|
|
6b92dd8eaf | ||
|
|
84540f551d | ||
|
|
99588fff8f |
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"
|
||||
with:
|
||||
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-stale: 15
|
||||
days-before-issue-stale: 30
|
||||
stale-pr-label: "stale?"
|
||||
days-before-pr-close: -1
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: 7
|
||||
days-before-pr-stale: 15
|
||||
only-pr-labels: "postpone: more info or changes requested,please add a reproduction"
|
||||
exempt-issue-labels: "roadmap,next,bug"
|
||||
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
|
||||
|
||||
13
apps/www/__registry__/default/block/login-01/page.tsx
Normal file
13
apps/www/__registry__/default/block/login-01/page.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
25
apps/www/__registry__/default/block/sidebar-01/page.tsx
Normal file
25
apps/www/__registry__/default/block/sidebar-01/page.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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: []
|
||||
|
||||
13
apps/www/__registry__/new-york/block/login-01/page.tsx
Normal file
13
apps/www/__registry__/new-york/block/login-01/page.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
25
apps/www/__registry__/new-york/block/sidebar-01/page.tsx
Normal file
25
apps/www/__registry__/new-york/block/sidebar-01/page.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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" />{" "}
|
||||
|
||||
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 mounted = useMounted()
|
||||
|
||||
if (!toc?.items || !mounted) {
|
||||
if (!toc?.items?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -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 "@/hooks/use-toast"
|
||||
```
|
||||
|
||||
```tsx {2,7-10}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
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,8 +4,7 @@
|
||||
"dependencies": [
|
||||
"tailwindcss-animate",
|
||||
"class-variance-authority",
|
||||
"lucide-react",
|
||||
""
|
||||
"lucide-react"
|
||||
],
|
||||
"registryDependencies": [
|
||||
"utils"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"files": [
|
||||
{
|
||||
"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",
|
||||
"target": ""
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"files": [
|
||||
{
|
||||
"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",
|
||||
"target": ""
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"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
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -11,7 +11,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
<input
|
||||
type={type}
|
||||
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
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @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
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "shadcn-ui",
|
||||
"version": "0.9.0",
|
||||
"version": "0.9.1",
|
||||
"description": "Add components to your apps.",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -133,7 +133,7 @@ export async function getProjectType(cwd: string): Promise<ProjectType | null> {
|
||||
}
|
||||
|
||||
export async function getTailwindCssFile(cwd: string) {
|
||||
const files = await fg.glob("**/*.css", {
|
||||
const files = await fg.glob(["**/*.css", "**/*.scss"], {
|
||||
cwd,
|
||||
deep: 3,
|
||||
ignore: PROJECT_SHARED_IGNORE,
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# @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
|
||||
|
||||
### 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "shadcn",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.7",
|
||||
"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"
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
)} 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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
@@ -16,6 +17,19 @@ import { transformRsc } from "@/src/utils/transformers/transform-rsc"
|
||||
import { transformTwPrefixes } from "@/src/utils/transformers/transform-tw-prefix"
|
||||
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(
|
||||
files: RegistryItem["files"],
|
||||
config: Config,
|
||||
@@ -37,7 +51,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 +71,7 @@ export async function updateFiles(
|
||||
let filePath = path.join(targetDir, fileName)
|
||||
|
||||
if (file.target) {
|
||||
filePath = path.join(config.resolvedPaths.cwd, file.target)
|
||||
filePath = resolveTargetDir(projectInfo, config, file.target)
|
||||
targetDir = path.dirname(filePath)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
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",
|
||||
"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"
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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