mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 01:41:37 +00:00
feat(shadcn): monorepo support (#6104)
This commit is contained in:
8
.eslintignore
Normal file
8
.eslintignore
Normal file
@@ -0,0 +1,8 @@
|
||||
node_modules/
|
||||
target/
|
||||
.next/
|
||||
build/
|
||||
dist/
|
||||
|
||||
/templates/
|
||||
/fixtures/
|
||||
2
.github/workflows/prerelease-comment.yml
vendored
2
.github/workflows/prerelease-comment.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
A new prerelease is available for testing:
|
||||
|
||||
```sh
|
||||
npx shadcn@${{ env.BETA_PACKAGE_VERSION }}
|
||||
pnpm dlx shadcn@${{ env.BETA_PACKAGE_VERSION }}
|
||||
```
|
||||
|
||||
- name: "Remove the autorelease label once published"
|
||||
|
||||
@@ -4,11 +4,11 @@ import { ArrowRight } from "lucide-react"
|
||||
export function Announcement() {
|
||||
return (
|
||||
<Link
|
||||
href="/docs/components/sidebar"
|
||||
href="/docs/monorepo"
|
||||
className="group mb-2 inline-flex items-center px-0.5 text-sm font-medium"
|
||||
>
|
||||
<span className="underline-offset-4 group-hover:underline">
|
||||
New sidebar component
|
||||
Monorepo support
|
||||
</span>
|
||||
<ArrowRight className="ml-1 h-4 w-4" />
|
||||
</Link>
|
||||
|
||||
@@ -71,6 +71,12 @@ export const docsConfig: DocsConfig = {
|
||||
href: "/docs/cli",
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "Monorepo",
|
||||
href: "/docs/monorepo",
|
||||
items: [],
|
||||
label: "New",
|
||||
},
|
||||
{
|
||||
title: "Next.js 15 + React 19",
|
||||
href: "/docs/react-19",
|
||||
@@ -141,12 +147,6 @@ export const docsConfig: DocsConfig = {
|
||||
{
|
||||
title: "Components",
|
||||
items: [
|
||||
{
|
||||
title: "Sidebar",
|
||||
href: "/docs/components/sidebar",
|
||||
items: [],
|
||||
label: "New",
|
||||
},
|
||||
{
|
||||
title: "Accordion",
|
||||
href: "/docs/components/accordion",
|
||||
@@ -337,6 +337,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: "/docs/components/sheet",
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "Sidebar",
|
||||
href: "/docs/components/sidebar",
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "Skeleton",
|
||||
href: "/docs/components/skeleton",
|
||||
|
||||
175
apps/www/content/docs/monorepo.mdx
Normal file
175
apps/www/content/docs/monorepo.mdx
Normal file
@@ -0,0 +1,175 @@
|
||||
---
|
||||
title: Monorepo
|
||||
description: Using shadcn/ui components and CLI in a monorepo.
|
||||
---
|
||||
|
||||
<Callout>
|
||||
**Note:** We're releasing monorepo support in the CLI as __experimental__.
|
||||
Help us improve it by testing it out and sending feedback. If you have any
|
||||
questions, please [reach out to
|
||||
us](https://github.com/shadcn-ui/ui/discussions).
|
||||
</Callout>
|
||||
|
||||
Until now, using shadcn/ui in a monorepo was a bit of a pain. You could add
|
||||
components using the CLI, but you had to manage where the components
|
||||
were installed and manually fix import paths.
|
||||
|
||||
With the new monorepo support in the CLI, we've made it a lot easier to use
|
||||
shadcn/ui in a monorepo.
|
||||
|
||||
The CLI now understands the monorepo structure and will install the components,
|
||||
dependencies and registry dependencies to the correct paths and handle imports
|
||||
for you.
|
||||
|
||||
## Getting started
|
||||
|
||||
<Steps>
|
||||
|
||||
### Create a new monorepo project
|
||||
|
||||
To create a new monorepo project, run the `init` command. You will be prompted
|
||||
to select the type of project you are creating.
|
||||
|
||||
```bash
|
||||
npx shadcn@canary init
|
||||
```
|
||||
|
||||
Select the `Next.js (Monorepo)` option.
|
||||
|
||||
```bash
|
||||
? Would you like to start a new project?
|
||||
Next.js
|
||||
❯ Next.js (Monorepo)
|
||||
```
|
||||
|
||||
This will create a new monorepo project with two workspaces: `web` and `ui`,
|
||||
and [Turborepo](https://turbo.build/repo/docs) as the build system.
|
||||
|
||||
Everything is set up for you, so you can start adding components to your project.
|
||||
|
||||
### Add components to your project
|
||||
|
||||
To add components to your project, run the `add` command **in the path of your app**.
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
```
|
||||
|
||||
```bash
|
||||
npx shadcn@canary add [COMPONENT]
|
||||
```
|
||||
|
||||
The CLI will figure out what type of component you are adding and install the
|
||||
correct files to the correct path.
|
||||
|
||||
For example, if you run `npx shadcn@canary add button`, the CLI will install the button component under `packages/ui` and update the import path for components in `apps/web`.
|
||||
|
||||
If you run `npx shadcn@canary add login-01`, the CLI will install the `button`, `label`, `input` and `card` components under `packages/ui` and the `login-form` component under `apps/web/components`.
|
||||
|
||||
### Importing components
|
||||
|
||||
You can import components from the `@workspace/ui` package as follows:
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
```
|
||||
|
||||
You can also import hooks and utilities from the `@workspace/ui` package.
|
||||
|
||||
```tsx
|
||||
import { useTheme } from "@workspace/ui/hooks/use-theme"
|
||||
import { cn } from "@workspace/ui/lib/utils"
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
## File Structure
|
||||
|
||||
When you create a new monorepo project, the CLI will create the following file structure:
|
||||
|
||||
```txt
|
||||
apps
|
||||
└── web # Your app goes here.
|
||||
├── app
|
||||
│ └── page.tsx
|
||||
├── components
|
||||
│ └── login-form.tsx
|
||||
├── components.json
|
||||
└── package.json
|
||||
packages
|
||||
└── ui # Your components and dependencies are installed here.
|
||||
├── src
|
||||
│ ├── components
|
||||
│ │ └── button.tsx
|
||||
│ ├── hooks
|
||||
│ ├── lib
|
||||
│ │ └── utils.ts
|
||||
│ └── styles
|
||||
│ └── globals.css
|
||||
├── components.json
|
||||
└── package.json
|
||||
package.json
|
||||
turbo.json
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
1. Every workspace must have a `components.json` file. A `package.json` file tells npm how to install the dependencies. A `components.json` file tells the CLI how and where to install components.
|
||||
|
||||
2. The `components.json` file must properly define aliases for the workspace. This tells the CLI how to import components, hooks, utilities, etc.
|
||||
|
||||
```json title="apps/web/components.json"
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "../../packages/ui/tailwind.config.ts",
|
||||
"css": "../../packages/ui/src/styles/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"hooks": "@/hooks",
|
||||
"lib": "@/lib",
|
||||
"utils": "@workspace/ui/lib/utils",
|
||||
"ui": "@workspace/ui/components"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json title="packages/ui/components.json"
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/styles/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@workspace/ui/components",
|
||||
"utils": "@workspace/ui/lib/utils",
|
||||
"hooks": "@workspace/ui/hooks",
|
||||
"lib": "@workspace/ui/lib",
|
||||
"ui": "@workspace/ui/components"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Ensure you have the same `style`, `iconLibrary` and `baseColor` in both `components.json` files.
|
||||
|
||||
By following these requirements, the CLI will be able to install ui components, blocks, libs and hooks to the correct paths and handle imports for you.
|
||||
|
||||
## Help us improve monorepo support
|
||||
|
||||
We're releasing monorepo support in the CLI as **experimental**. Help us improve it by testing it out and sending feedback.
|
||||
|
||||
If you have any questions, please reach out to us on [GitHub Discussions](https://github.com/shadcn-ui/ui/discussions).
|
||||
@@ -10,8 +10,7 @@
|
||||
},
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*",
|
||||
"templates/*"
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { preFlightAdd } from "@/src/preflights/preflight-add"
|
||||
import { addComponents } from "@/src/utils/add-components"
|
||||
import { createProject } from "@/src/utils/create-project"
|
||||
import * as ERRORS from "@/src/utils/errors"
|
||||
import { getConfig } from "@/src/utils/get-config"
|
||||
import { handleError } from "@/src/utils/handle-error"
|
||||
import { highlighter } from "@/src/utils/highlighter"
|
||||
import { logger } from "@/src/utils/logger"
|
||||
@@ -112,7 +113,7 @@ export const add = new Command()
|
||||
|
||||
let shouldUpdateAppIndex = false
|
||||
if (errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT]) {
|
||||
const { projectPath } = await createProject({
|
||||
const { projectPath, projectType } = await createProject({
|
||||
cwd: options.cwd,
|
||||
force: options.overwrite,
|
||||
srcDir: options.srcDir,
|
||||
@@ -124,20 +125,25 @@ export const add = new Command()
|
||||
}
|
||||
options.cwd = projectPath
|
||||
|
||||
config = await runInit({
|
||||
cwd: options.cwd,
|
||||
yes: true,
|
||||
force: true,
|
||||
defaults: false,
|
||||
skipPreflight: true,
|
||||
silent: true,
|
||||
isNewProject: true,
|
||||
srcDir: options.srcDir,
|
||||
})
|
||||
if (projectType === "monorepo") {
|
||||
options.cwd = path.resolve(options.cwd, "apps/web")
|
||||
config = await getConfig(options.cwd)
|
||||
} else {
|
||||
config = await runInit({
|
||||
cwd: options.cwd,
|
||||
yes: true,
|
||||
force: true,
|
||||
defaults: false,
|
||||
skipPreflight: true,
|
||||
silent: true,
|
||||
isNewProject: true,
|
||||
srcDir: options.srcDir,
|
||||
})
|
||||
|
||||
shouldUpdateAppIndex =
|
||||
options.components?.length === 1 &&
|
||||
!!options.components[0].match(/\/chat\/b\//)
|
||||
shouldUpdateAppIndex =
|
||||
options.components?.length === 1 &&
|
||||
!!options.components[0].match(/\/chat\/b\//)
|
||||
}
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
|
||||
@@ -86,21 +86,28 @@ export async function runInit(
|
||||
}
|
||||
) {
|
||||
let projectInfo
|
||||
let newProjectType
|
||||
if (!options.skipPreflight) {
|
||||
const preflight = await preFlightInit(options)
|
||||
if (preflight.errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT]) {
|
||||
const { projectPath } = await createProject(options)
|
||||
const { projectPath, projectType } = await createProject(options)
|
||||
if (!projectPath) {
|
||||
process.exit(1)
|
||||
}
|
||||
options.cwd = projectPath
|
||||
options.isNewProject = true
|
||||
newProjectType = projectType
|
||||
}
|
||||
projectInfo = preflight.projectInfo
|
||||
} else {
|
||||
projectInfo = await getProjectInfo(options.cwd)
|
||||
}
|
||||
|
||||
if (newProjectType === "monorepo") {
|
||||
options.cwd = path.resolve(options.cwd, "apps/web")
|
||||
return await getConfig(options.cwd)
|
||||
}
|
||||
|
||||
const projectConfig = await getProjectConfig(options.cwd, projectInfo)
|
||||
const config = projectConfig
|
||||
? await promptForMinimalConfig(projectConfig, options)
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
import { type Config } from "@/src/utils/get-config"
|
||||
import path from "path"
|
||||
import {
|
||||
configSchema,
|
||||
findCommonRoot,
|
||||
findPackageRoot,
|
||||
getWorkspaceConfig,
|
||||
workspaceConfigSchema,
|
||||
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 {
|
||||
fetchRegistry,
|
||||
getRegistryParentMap,
|
||||
getRegistryTypeAliasMap,
|
||||
registryResolveItemsTree,
|
||||
resolveRegistryItems,
|
||||
} from "@/src/utils/registry"
|
||||
import { registryItemSchema } from "@/src/utils/registry/schema"
|
||||
import { spinner } from "@/src/utils/spinner"
|
||||
import { updateCssVars } from "@/src/utils/updaters/update-css-vars"
|
||||
import { updateDependencies } from "@/src/utils/updaters/update-dependencies"
|
||||
import { updateFiles } from "@/src/utils/updaters/update-files"
|
||||
import { updateTailwindConfig } from "@/src/utils/updaters/update-tailwind-config"
|
||||
import { z } from "zod"
|
||||
|
||||
export async function addComponents(
|
||||
components: string[],
|
||||
@@ -24,6 +40,30 @@ export async function addComponents(
|
||||
...options,
|
||||
}
|
||||
|
||||
const workspaceConfig = await getWorkspaceConfig(config)
|
||||
if (
|
||||
workspaceConfig &&
|
||||
workspaceConfig?.ui.resolvedPaths.cwd !== config.resolvedPaths.cwd
|
||||
) {
|
||||
return await addWorkspaceComponents(components, config, workspaceConfig, {
|
||||
...options,
|
||||
isRemote:
|
||||
components?.length === 1 && !!components[0].match(/\/chat\/b\//),
|
||||
})
|
||||
}
|
||||
|
||||
return await addProjectComponents(components, config, options)
|
||||
}
|
||||
|
||||
async function addProjectComponents(
|
||||
components: string[],
|
||||
config: z.infer<typeof configSchema>,
|
||||
options: {
|
||||
overwrite?: boolean
|
||||
silent?: boolean
|
||||
isNewProject?: boolean
|
||||
}
|
||||
) {
|
||||
const registrySpinner = spinner(`Checking registry.`, {
|
||||
silent: options.silent,
|
||||
})?.start()
|
||||
@@ -54,3 +94,166 @@ export async function addComponents(
|
||||
logger.info(tree.docs)
|
||||
}
|
||||
}
|
||||
|
||||
async function addWorkspaceComponents(
|
||||
components: string[],
|
||||
config: z.infer<typeof configSchema>,
|
||||
workspaceConfig: z.infer<typeof workspaceConfigSchema>,
|
||||
options: {
|
||||
overwrite?: boolean
|
||||
silent?: boolean
|
||||
isNewProject?: boolean
|
||||
isRemote?: boolean
|
||||
}
|
||||
) {
|
||||
const registrySpinner = spinner(`Checking registry.`, {
|
||||
silent: options.silent,
|
||||
})?.start()
|
||||
let registryItems = await resolveRegistryItems(components, config)
|
||||
let result = await fetchRegistry(registryItems)
|
||||
const payload = z.array(registryItemSchema).parse(result)
|
||||
if (!payload) {
|
||||
registrySpinner?.fail()
|
||||
return handleError(new Error("Failed to fetch components from registry."))
|
||||
}
|
||||
registrySpinner?.succeed()
|
||||
|
||||
const registryParentMap = getRegistryParentMap(payload)
|
||||
const registryTypeAliasMap = getRegistryTypeAliasMap()
|
||||
|
||||
const filesCreated: string[] = []
|
||||
const filesUpdated: string[] = []
|
||||
const filesSkipped: string[] = []
|
||||
|
||||
const rootSpinner = spinner(`Installing components.`)?.start()
|
||||
|
||||
for (const component of payload) {
|
||||
const alias = registryTypeAliasMap.get(component.type)
|
||||
const registryParent = registryParentMap.get(component.name)
|
||||
|
||||
// We don't support this type of component.
|
||||
if (!alias) {
|
||||
continue
|
||||
}
|
||||
|
||||
// A good start is ui for now.
|
||||
// TODO: Add support for other types.
|
||||
let targetConfig =
|
||||
component.type === "registry:ui" || registryParent?.type === "registry:ui"
|
||||
? workspaceConfig.ui
|
||||
: config
|
||||
|
||||
const workspaceRoot = findCommonRoot(
|
||||
config.resolvedPaths.cwd,
|
||||
targetConfig.resolvedPaths.ui
|
||||
)
|
||||
const packageRoot =
|
||||
(await findPackageRoot(workspaceRoot, targetConfig.resolvedPaths.cwd)) ??
|
||||
targetConfig.resolvedPaths.cwd
|
||||
|
||||
// 1. Update tailwind config.
|
||||
if (component.tailwind?.config) {
|
||||
await updateTailwindConfig(component.tailwind?.config, targetConfig, {
|
||||
silent: true,
|
||||
})
|
||||
filesUpdated.push(
|
||||
path.relative(workspaceRoot, targetConfig.resolvedPaths.tailwindConfig)
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Update css vars.
|
||||
if (component.cssVars) {
|
||||
await updateCssVars(component.cssVars, targetConfig, {
|
||||
silent: true,
|
||||
})
|
||||
filesUpdated.push(
|
||||
path.relative(workspaceRoot, targetConfig.resolvedPaths.tailwindCss)
|
||||
)
|
||||
}
|
||||
|
||||
// 3. Update dependencies.
|
||||
await updateDependencies(component.dependencies, targetConfig, {
|
||||
silent: true,
|
||||
})
|
||||
|
||||
// 4. Update files.
|
||||
const files = await updateFiles(component.files, targetConfig, {
|
||||
overwrite: options.overwrite,
|
||||
silent: true,
|
||||
rootSpinner,
|
||||
isRemote: options.isRemote,
|
||||
})
|
||||
|
||||
filesCreated.push(
|
||||
...files.filesCreated.map((file) =>
|
||||
path.relative(workspaceRoot, path.join(packageRoot, file))
|
||||
)
|
||||
)
|
||||
filesUpdated.push(
|
||||
...files.filesUpdated.map((file) =>
|
||||
path.relative(workspaceRoot, path.join(packageRoot, file))
|
||||
)
|
||||
)
|
||||
filesSkipped.push(
|
||||
...files.filesSkipped.map((file) =>
|
||||
path.relative(workspaceRoot, path.join(packageRoot, file))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
rootSpinner?.succeed()
|
||||
|
||||
// Sort files.
|
||||
filesCreated.sort()
|
||||
filesUpdated.sort()
|
||||
filesSkipped.sort()
|
||||
|
||||
const hasUpdatedFiles = filesCreated.length || filesUpdated.length
|
||||
if (!hasUpdatedFiles && !filesSkipped.length) {
|
||||
spinner(`No files updated.`, {
|
||||
silent: options.silent,
|
||||
})?.info()
|
||||
}
|
||||
|
||||
if (filesCreated.length) {
|
||||
spinner(
|
||||
`Created ${filesCreated.length} ${
|
||||
filesCreated.length === 1 ? "file" : "files"
|
||||
}:`,
|
||||
{
|
||||
silent: options.silent,
|
||||
}
|
||||
)?.succeed()
|
||||
for (const file of filesCreated) {
|
||||
logger.log(` - ${file}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (filesUpdated.length) {
|
||||
spinner(
|
||||
`Updated ${filesUpdated.length} ${
|
||||
filesUpdated.length === 1 ? "file" : "files"
|
||||
}:`,
|
||||
{
|
||||
silent: options.silent,
|
||||
}
|
||||
)?.info()
|
||||
for (const file of filesUpdated) {
|
||||
logger.log(` - ${file}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (filesSkipped.length) {
|
||||
spinner(
|
||||
`Skipped ${filesSkipped.length} ${
|
||||
filesUpdated.length === 1 ? "file" : "files"
|
||||
}: (use --overwrite to overwrite)`,
|
||||
{
|
||||
silent: options.silent,
|
||||
}
|
||||
)?.info()
|
||||
for (const file of filesSkipped) {
|
||||
logger.log(` - ${file}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import { initOptionsSchema } from "@/src/commands/init"
|
||||
import { getPackageManager } from "@/src/utils/get-package-manager"
|
||||
@@ -11,6 +12,9 @@ import fs from "fs-extra"
|
||||
import prompts from "prompts"
|
||||
import { z } from "zod"
|
||||
|
||||
const MONOREPO_TEMPLATE_URL =
|
||||
"https://codeload.github.com/shadcn-ui/ui/tar.gz/main"
|
||||
|
||||
export async function createProject(
|
||||
options: Pick<
|
||||
z.infer<typeof initOptionsSchema>,
|
||||
@@ -22,11 +26,14 @@ export async function createProject(
|
||||
...options,
|
||||
}
|
||||
|
||||
let nextVersion = "14.2.16"
|
||||
let projectType: "next" | "monorepo" = "next"
|
||||
let projectName: string = "my-app"
|
||||
let nextVersion = "15.1.0"
|
||||
|
||||
const isRemoteComponent =
|
||||
options.components?.length === 1 &&
|
||||
!!options.components[0].match(/\/chat\/b\//)
|
||||
|
||||
if (options.components && isRemoteComponent) {
|
||||
try {
|
||||
const [result] = await fetchRegistry(options.components)
|
||||
@@ -45,40 +52,41 @@ export async function createProject(
|
||||
}
|
||||
|
||||
if (!options.force) {
|
||||
const { proceed } = await prompts({
|
||||
type: "confirm",
|
||||
name: "proceed",
|
||||
message: `The path ${highlighter.info(
|
||||
options.cwd
|
||||
)} does not contain a package.json file. Would you like to start a new ${highlighter.info(
|
||||
"Next.js"
|
||||
)} project?`,
|
||||
initial: true,
|
||||
})
|
||||
const { type, name } = await prompts([
|
||||
{
|
||||
type: "select",
|
||||
name: "type",
|
||||
message: `The path ${highlighter.info(
|
||||
options.cwd
|
||||
)} does not contain a package.json file.\n Would you like to start a new project?`,
|
||||
choices: [
|
||||
{ title: "Next.js", value: "next" },
|
||||
{ title: "Next.js (Monorepo)", value: "monorepo" },
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "name",
|
||||
message: "What is your project named?",
|
||||
initial: projectName,
|
||||
format: (value: string) => value.trim(),
|
||||
validate: (value: string) =>
|
||||
value.length > 128
|
||||
? `Name should be less than 128 characters.`
|
||||
: true,
|
||||
},
|
||||
])
|
||||
|
||||
if (!proceed) {
|
||||
return {
|
||||
projectPath: null,
|
||||
projectName: null,
|
||||
}
|
||||
}
|
||||
projectType = type
|
||||
projectName = name
|
||||
}
|
||||
|
||||
const packageManager = await getPackageManager(options.cwd, {
|
||||
withFallback: true,
|
||||
})
|
||||
|
||||
const { name } = await prompts({
|
||||
type: "text",
|
||||
name: "name",
|
||||
message: `What is your project named?`,
|
||||
initial: "my-app",
|
||||
format: (value: string) => value.trim(),
|
||||
validate: (value: string) =>
|
||||
value.length > 128 ? `Name should be less than 128 characters.` : true,
|
||||
})
|
||||
|
||||
const projectPath = `${options.cwd}/${name}`
|
||||
const projectPath = `${options.cwd}/${projectName}`
|
||||
|
||||
// Check if path is writable.
|
||||
try {
|
||||
@@ -95,16 +103,47 @@ export async function createProject(
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (fs.existsSync(path.resolve(options.cwd, name, "package.json"))) {
|
||||
if (fs.existsSync(path.resolve(options.cwd, projectName, "package.json"))) {
|
||||
logger.break()
|
||||
logger.error(
|
||||
`A project with the name ${highlighter.info(name)} already exists.`
|
||||
`A project with the name ${highlighter.info(projectName)} already exists.`
|
||||
)
|
||||
logger.error(`Please choose a different name and try again.`)
|
||||
logger.break()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (projectType === "next") {
|
||||
await createNextProject(projectPath, {
|
||||
version: nextVersion,
|
||||
cwd: options.cwd,
|
||||
packageManager,
|
||||
srcDir: !!options.srcDir,
|
||||
})
|
||||
}
|
||||
|
||||
if (projectType === "monorepo") {
|
||||
await createMonorepoProject(projectPath, {
|
||||
packageManager,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
projectPath,
|
||||
projectName,
|
||||
projectType,
|
||||
}
|
||||
}
|
||||
|
||||
async function createNextProject(
|
||||
projectPath: string,
|
||||
options: {
|
||||
version: string
|
||||
cwd: string
|
||||
packageManager: string
|
||||
srcDir: boolean
|
||||
}
|
||||
) {
|
||||
const createSpinner = spinner(
|
||||
`Creating a new Next.js project. This may take a few minutes.`
|
||||
).start()
|
||||
@@ -117,17 +156,17 @@ export async function createProject(
|
||||
"--app",
|
||||
options.srcDir ? "--src-dir" : "--no-src-dir",
|
||||
"--no-import-alias",
|
||||
`--use-${packageManager}`,
|
||||
`--use-${options.packageManager}`,
|
||||
]
|
||||
|
||||
if (nextVersion.startsWith("15")) {
|
||||
if (options.version.startsWith("15")) {
|
||||
args.push("--turbopack")
|
||||
}
|
||||
|
||||
try {
|
||||
await execa(
|
||||
"npx",
|
||||
[`create-next-app@${nextVersion}`, projectPath, "--silent", ...args],
|
||||
[`create-next-app@${options.version}`, projectPath, "--silent", ...args],
|
||||
{
|
||||
cwd: options.cwd,
|
||||
}
|
||||
@@ -141,9 +180,60 @@ export async function createProject(
|
||||
}
|
||||
|
||||
createSpinner?.succeed("Creating a new Next.js project.")
|
||||
}
|
||||
|
||||
return {
|
||||
projectPath,
|
||||
projectName: name,
|
||||
async function createMonorepoProject(
|
||||
projectPath: string,
|
||||
options: {
|
||||
packageManager: string
|
||||
}
|
||||
) {
|
||||
const createSpinner = spinner(
|
||||
`Creating a new Next.js monorepo. This may take a few minutes.`
|
||||
).start()
|
||||
|
||||
try {
|
||||
// Get the template.
|
||||
const templatePath = path.join(os.tmpdir(), `shadcn-template-${Date.now()}`)
|
||||
await fs.ensureDir(templatePath)
|
||||
const response = await fetch(MONOREPO_TEMPLATE_URL)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download template: ${response.statusText}`)
|
||||
}
|
||||
|
||||
// Write the tar file
|
||||
const tarPath = path.resolve(templatePath, "template.tar.gz")
|
||||
await fs.writeFile(tarPath, Buffer.from(await response.arrayBuffer()))
|
||||
await execa("tar", [
|
||||
"-xzf",
|
||||
tarPath,
|
||||
"-C",
|
||||
templatePath,
|
||||
"--strip-components=2",
|
||||
"ui-shadcn-cli-monorepo/templates/monorepo-next",
|
||||
])
|
||||
const extractedPath = path.resolve(templatePath, "monorepo-next")
|
||||
await fs.move(extractedPath, projectPath)
|
||||
await fs.remove(templatePath)
|
||||
|
||||
// Run install.
|
||||
await execa(options.packageManager, ["install"], {
|
||||
cwd: projectPath,
|
||||
})
|
||||
|
||||
// Try git init.
|
||||
const cwd = process.cwd()
|
||||
await execa("git", ["--version"], { cwd: projectPath })
|
||||
await execa("git", ["init"], { cwd: projectPath })
|
||||
await execa("git", ["add", "-A"], { cwd: projectPath })
|
||||
await execa("git", ["commit", "-m", "Initial commit"], {
|
||||
cwd: projectPath,
|
||||
})
|
||||
await execa("cd", [cwd])
|
||||
|
||||
createSpinner?.succeed("Creating a new Next.js monorepo.")
|
||||
} catch (error) {
|
||||
createSpinner?.fail("Something went wrong creating a new Next.js monorepo.")
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import path from "path"
|
||||
import { highlighter } from "@/src/utils/highlighter"
|
||||
import { resolveImport } from "@/src/utils/resolve-import"
|
||||
import { cosmiconfig } from "cosmiconfig"
|
||||
import fg from "fast-glob"
|
||||
import fs from "fs-extra"
|
||||
import { loadConfig } from "tsconfig-paths"
|
||||
import { z } from "zod"
|
||||
|
||||
@@ -59,6 +61,10 @@ export const configSchema = rawConfigSchema.extend({
|
||||
|
||||
export type Config = z.infer<typeof configSchema>
|
||||
|
||||
// TODO: type the key.
|
||||
// Okay for now since I don't want a breaking change.
|
||||
export const workspaceConfigSchema = z.record(configSchema)
|
||||
|
||||
export async function getConfig(cwd: string) {
|
||||
const config = await getRawConfig(cwd)
|
||||
|
||||
@@ -137,3 +143,77 @@ export async function getRawConfig(cwd: string): Promise<RawConfig | null> {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Note: we can check for -workspace.yaml or "workspace" in package.json.
|
||||
// Since cwd is not necessarily the root of the project.
|
||||
// We'll instead check if ui aliases resolve to a different root.
|
||||
export async function getWorkspaceConfig(config: Config) {
|
||||
let resolvedAliases: any = {}
|
||||
|
||||
for (const key of Object.keys(config.aliases)) {
|
||||
if (!isAliasKey(key, config)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const resolvedPath = config.resolvedPaths[key]
|
||||
const packageRoot = await findPackageRoot(
|
||||
config.resolvedPaths.cwd,
|
||||
resolvedPath
|
||||
)
|
||||
|
||||
if (!packageRoot) {
|
||||
resolvedAliases[key] = config
|
||||
continue
|
||||
}
|
||||
|
||||
resolvedAliases[key] = await getConfig(packageRoot)
|
||||
}
|
||||
|
||||
const result = workspaceConfigSchema.safeParse(resolvedAliases)
|
||||
if (!result.success) {
|
||||
return null
|
||||
}
|
||||
|
||||
return result.data
|
||||
}
|
||||
|
||||
export async function findPackageRoot(cwd: string, resolvedPath: string) {
|
||||
const commonRoot = findCommonRoot(cwd, resolvedPath)
|
||||
const relativePath = path.relative(commonRoot, resolvedPath)
|
||||
|
||||
const packageRoots = await fg.glob("**/package.json", {
|
||||
cwd: commonRoot,
|
||||
deep: 3,
|
||||
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/public/**"],
|
||||
})
|
||||
|
||||
const matchingPackageRoot = packageRoots
|
||||
.map((pkgPath) => path.dirname(pkgPath))
|
||||
.find((pkgDir) => relativePath.startsWith(pkgDir))
|
||||
|
||||
return matchingPackageRoot ? path.join(commonRoot, matchingPackageRoot) : null
|
||||
}
|
||||
|
||||
function isAliasKey(
|
||||
key: string,
|
||||
config: Config
|
||||
): key is keyof Config["aliases"] {
|
||||
return Object.keys(config.resolvedPaths)
|
||||
.filter((key) => key !== "utils")
|
||||
.includes(key)
|
||||
}
|
||||
|
||||
export function findCommonRoot(cwd: string, resolvedPath: string) {
|
||||
const parts1 = cwd.split(path.sep)
|
||||
const parts2 = resolvedPath.split(path.sep)
|
||||
const commonParts = []
|
||||
|
||||
for (let i = 0; i < Math.min(parts1.length, parts2.length); i++) {
|
||||
if (parts1[i] !== parts2[i]) {
|
||||
break
|
||||
}
|
||||
commonParts.push(parts1[i])
|
||||
}
|
||||
|
||||
return commonParts.join(path.sep)
|
||||
}
|
||||
|
||||
@@ -281,17 +281,8 @@ export async function registryResolveItemsTree(
|
||||
names.unshift("index")
|
||||
}
|
||||
|
||||
let registryDependencies: string[] = []
|
||||
for (const name of names) {
|
||||
const itemRegistryDependencies = await resolveRegistryDependencies(
|
||||
name,
|
||||
config
|
||||
)
|
||||
registryDependencies.push(...itemRegistryDependencies)
|
||||
}
|
||||
|
||||
const uniqueRegistryDependencies = Array.from(new Set(registryDependencies))
|
||||
let result = await fetchRegistry(uniqueRegistryDependencies)
|
||||
let registryItems = await resolveRegistryItems(names, config)
|
||||
let result = await fetchRegistry(registryItems)
|
||||
const payload = z.array(registryItemSchema).parse(result)
|
||||
|
||||
if (!payload) {
|
||||
@@ -461,3 +452,44 @@ function isUrl(path: string) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: We're double-fetching here. Use a cache.
|
||||
export async function resolveRegistryItems(names: string[], config: Config) {
|
||||
let registryDependencies: string[] = []
|
||||
for (const name of names) {
|
||||
const itemRegistryDependencies = await resolveRegistryDependencies(
|
||||
name,
|
||||
config
|
||||
)
|
||||
registryDependencies.push(...itemRegistryDependencies)
|
||||
}
|
||||
|
||||
return Array.from(new Set(registryDependencies))
|
||||
}
|
||||
|
||||
export function getRegistryTypeAliasMap() {
|
||||
return new Map<string, string>([
|
||||
["registry:ui", "ui"],
|
||||
["registry:lib", "lib"],
|
||||
["registry:hook", "hooks"],
|
||||
["registry:block", "components"],
|
||||
["registry:component", "components"],
|
||||
])
|
||||
}
|
||||
|
||||
// Track a dependency and its parent.
|
||||
export function getRegistryParentMap(
|
||||
registryItems: z.infer<typeof registryItemSchema>[]
|
||||
) {
|
||||
const map = new Map<string, z.infer<typeof registryItemSchema>>()
|
||||
registryItems.forEach((item) => {
|
||||
if (!item.registryDependencies) {
|
||||
return
|
||||
}
|
||||
|
||||
item.registryDependencies.forEach((dependency) => {
|
||||
map.set(dependency, item)
|
||||
})
|
||||
})
|
||||
return map
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ export type TransformOpts = {
|
||||
config: Config
|
||||
baseColor?: z.infer<typeof registryBaseColorSchema>
|
||||
transformJsx?: boolean
|
||||
isRemote?: boolean
|
||||
}
|
||||
|
||||
export type Transformer<Output = SourceFile> = (
|
||||
|
||||
@@ -1,24 +1,39 @@
|
||||
import { Config } from "@/src/utils/get-config"
|
||||
import { Transformer } from "@/src/utils/transformers"
|
||||
|
||||
export const transformImport: Transformer = async ({ sourceFile, config }) => {
|
||||
const COMMON_CN_IMPORTS = {
|
||||
"@/lib/utils": /^@\/lib\/utils/,
|
||||
"@workspace/lib/utils": /^@workspace\/lib\/utils/,
|
||||
}
|
||||
|
||||
export const transformImport: Transformer = async ({
|
||||
sourceFile,
|
||||
config,
|
||||
isRemote,
|
||||
}) => {
|
||||
const importDeclarations = sourceFile.getImportDeclarations()
|
||||
|
||||
for (const importDeclaration of importDeclarations) {
|
||||
const moduleSpecifier = updateImportAliases(
|
||||
importDeclaration.getModuleSpecifierValue(),
|
||||
config
|
||||
config,
|
||||
isRemote
|
||||
)
|
||||
|
||||
importDeclaration.setModuleSpecifier(moduleSpecifier)
|
||||
|
||||
// Replace `import { cn } from "@/lib/utils"`
|
||||
if (moduleSpecifier == "@/lib/utils") {
|
||||
if (COMMON_CN_IMPORTS[moduleSpecifier as keyof typeof COMMON_CN_IMPORTS]) {
|
||||
const namedImports = importDeclaration.getNamedImports()
|
||||
const cnImport = namedImports.find((i) => i.getName() === "cn")
|
||||
if (cnImport) {
|
||||
importDeclaration.setModuleSpecifier(
|
||||
moduleSpecifier.replace(/^@\/lib\/utils/, config.aliases.utils)
|
||||
moduleSpecifier.replace(
|
||||
COMMON_CN_IMPORTS[
|
||||
moduleSpecifier as keyof typeof COMMON_CN_IMPORTS
|
||||
],
|
||||
config.aliases.utils
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -27,12 +42,21 @@ export const transformImport: Transformer = async ({ sourceFile, config }) => {
|
||||
return sourceFile
|
||||
}
|
||||
|
||||
function updateImportAliases(moduleSpecifier: string, config: Config) {
|
||||
function updateImportAliases(
|
||||
moduleSpecifier: string,
|
||||
config: Config,
|
||||
isRemote: boolean = false
|
||||
) {
|
||||
// Not a local import.
|
||||
if (!moduleSpecifier.startsWith("@/")) {
|
||||
if (!moduleSpecifier.startsWith("@/") && !isRemote) {
|
||||
return moduleSpecifier
|
||||
}
|
||||
|
||||
// This treats the remote as coming from a faux registry.
|
||||
if (isRemote && moduleSpecifier.startsWith("@/")) {
|
||||
moduleSpecifier = moduleSpecifier.replace(/^@\//, `@/registry/new-york/`)
|
||||
}
|
||||
|
||||
// Not a registry import.
|
||||
if (!moduleSpecifier.startsWith("@/registry/")) {
|
||||
// We fix the alias and return.
|
||||
|
||||
@@ -66,6 +66,7 @@ export async function updateDependencies(
|
||||
cwd: config.resolvedPaths.cwd,
|
||||
}
|
||||
)
|
||||
|
||||
dependenciesSpinner?.succeed()
|
||||
}
|
||||
|
||||
|
||||
@@ -38,15 +38,22 @@ export async function updateFiles(
|
||||
overwrite?: boolean
|
||||
force?: boolean
|
||||
silent?: boolean
|
||||
rootSpinner?: ReturnType<typeof spinner>
|
||||
isRemote?: boolean
|
||||
}
|
||||
) {
|
||||
if (!files?.length) {
|
||||
return
|
||||
return {
|
||||
filesCreated: [],
|
||||
filesUpdated: [],
|
||||
filesSkipped: [],
|
||||
}
|
||||
}
|
||||
options = {
|
||||
overwrite: false,
|
||||
force: false,
|
||||
silent: false,
|
||||
isRemote: false,
|
||||
...options,
|
||||
}
|
||||
const filesCreatedSpinner = spinner(`Updating files.`, {
|
||||
@@ -83,8 +90,12 @@ export async function updateFiles(
|
||||
}
|
||||
|
||||
const existingFile = existsSync(filePath)
|
||||
|
||||
if (existingFile && !options.overwrite) {
|
||||
filesCreatedSpinner.stop()
|
||||
if (options.rootSpinner) {
|
||||
options.rootSpinner.stop()
|
||||
}
|
||||
const { overwrite } = await prompts({
|
||||
type: "confirm",
|
||||
name: "overwrite",
|
||||
@@ -96,9 +107,15 @@ export async function updateFiles(
|
||||
|
||||
if (!overwrite) {
|
||||
filesSkipped.push(path.relative(config.resolvedPaths.cwd, filePath))
|
||||
if (options.rootSpinner) {
|
||||
options.rootSpinner.start()
|
||||
}
|
||||
continue
|
||||
}
|
||||
filesCreatedSpinner?.start()
|
||||
if (options.rootSpinner) {
|
||||
options.rootSpinner.start()
|
||||
}
|
||||
}
|
||||
|
||||
// Create the target directory if it doesn't exist.
|
||||
@@ -114,6 +131,7 @@ export async function updateFiles(
|
||||
config,
|
||||
baseColor,
|
||||
transformJsx: !config.tsx,
|
||||
isRemote: options.isRemote,
|
||||
},
|
||||
[
|
||||
transformImport,
|
||||
@@ -170,7 +188,7 @@ export async function updateFiles(
|
||||
spinner(
|
||||
`Skipped ${filesSkipped.length} ${
|
||||
filesUpdated.length === 1 ? "file" : "files"
|
||||
}:`,
|
||||
}: (use --overwrite to overwrite)`,
|
||||
{
|
||||
silent: options.silent,
|
||||
}
|
||||
@@ -185,4 +203,10 @@ export async function updateFiles(
|
||||
if (!options.silent) {
|
||||
logger.break()
|
||||
}
|
||||
|
||||
return {
|
||||
filesCreated,
|
||||
filesUpdated,
|
||||
filesSkipped,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
packages:
|
||||
- "apps/*"
|
||||
- "packages/*"
|
||||
- "templates/*"
|
||||
- "!**/test/**"
|
||||
|
||||
@@ -10,6 +10,8 @@ module.exports = {
|
||||
"^(next/(.*)$)|^(next$)",
|
||||
"<THIRD_PARTY_MODULES>",
|
||||
"",
|
||||
"^@workspace/(.*)$",
|
||||
"",
|
||||
"^types$",
|
||||
"^@/types/(.*)$",
|
||||
"^@/config/(.*)$",
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
> [!NOTE]
|
||||
> The `next-template` has been deprecated. Use `npx shadcn@latest init` to create a new project.
|
||||
10
templates/monorepo-next/.eslintrc.js
Normal file
10
templates/monorepo-next/.eslintrc.js
Normal file
@@ -0,0 +1,10 @@
|
||||
// This configuration only applies to the package manager root.
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
module.exports = {
|
||||
ignorePatterns: ["apps/**", "packages/**"],
|
||||
extends: ["@workspace/eslint-config/library.js"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
project: true,
|
||||
},
|
||||
}
|
||||
36
templates/monorepo-next/.gitignore
vendored
Normal file
36
templates/monorepo-next/.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# Dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
|
||||
# Turbo
|
||||
.turbo
|
||||
|
||||
# Vercel
|
||||
.vercel
|
||||
|
||||
# Build Outputs
|
||||
.next/
|
||||
out/
|
||||
build
|
||||
dist
|
||||
|
||||
|
||||
# Debug
|
||||
npm-debug.log*
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
0
templates/monorepo-next/.npmrc
Normal file
0
templates/monorepo-next/.npmrc
Normal file
31
templates/monorepo-next/README.md
Normal file
31
templates/monorepo-next/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# shadcn/ui monorepo template
|
||||
|
||||
This template is for creating a monorepo with shadcn/ui.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
pnpm dlx shadcn@latest init
|
||||
```
|
||||
|
||||
## Adding components
|
||||
|
||||
To add components to your app, run the following command at the root of your `web` app:
|
||||
|
||||
```bash
|
||||
pnpm dlx shadcn@latest add button -c apps/web
|
||||
```
|
||||
|
||||
This will place the ui components in the `packages/ui/src/components` directory.
|
||||
|
||||
## Tailwind
|
||||
|
||||
Your `tailwind.config.ts` and `globals.css` are already set up to use the components from the `ui` package.
|
||||
|
||||
## Using components
|
||||
|
||||
To use the components in your app, import them from the `ui` package.
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/ui/button"
|
||||
```
|
||||
BIN
templates/monorepo-next/apps/web/app/favicon.ico
Normal file
BIN
templates/monorepo-next/apps/web/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
30
templates/monorepo-next/apps/web/app/layout.tsx
Normal file
30
templates/monorepo-next/apps/web/app/layout.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Geist, Geist_Mono } from "next/font/google"
|
||||
|
||||
import "@workspace/ui/globals.css"
|
||||
import { Providers } from "@/components/providers"
|
||||
|
||||
const fontSans = Geist({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-sans",
|
||||
})
|
||||
|
||||
const fontMono = Geist_Mono({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-mono",
|
||||
})
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body
|
||||
className={`${fontSans.variable} ${fontMono.variable} font-sans antialiased `}
|
||||
>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
12
templates/monorepo-next/apps/web/app/page.tsx
Normal file
12
templates/monorepo-next/apps/web/app/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-svh">
|
||||
<div className="flex flex-col items-center justify-center gap-4">
|
||||
<h1 className="text-2xl font-bold">Hello World</h1>
|
||||
<Button size="sm">Button</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
20
templates/monorepo-next/apps/web/components.json
Normal file
20
templates/monorepo-next/apps/web/components.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "../../packages/ui/tailwind.config.ts",
|
||||
"css": "../../packages/ui/src/styles/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"hooks": "@/hooks",
|
||||
"lib": "@/lib",
|
||||
"utils": "@workspace/ui/lib/utils",
|
||||
"ui": "@workspace/ui/components"
|
||||
}
|
||||
}
|
||||
18
templates/monorepo-next/apps/web/components/providers.tsx
Normal file
18
templates/monorepo-next/apps/web/components/providers.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<NextThemesProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
enableColorScheme
|
||||
>
|
||||
{children}
|
||||
</NextThemesProvider>
|
||||
)
|
||||
}
|
||||
4
templates/monorepo-next/apps/web/eslint.config.js
Normal file
4
templates/monorepo-next/apps/web/eslint.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import { nextJsConfig } from "@workspace/eslint-config/next-js"
|
||||
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
export default nextJsConfig
|
||||
0
templates/monorepo-next/apps/web/hooks/.gitkeep
Normal file
0
templates/monorepo-next/apps/web/hooks/.gitkeep
Normal file
0
templates/monorepo-next/apps/web/lib/.gitkeep
Normal file
0
templates/monorepo-next/apps/web/lib/.gitkeep
Normal file
5
templates/monorepo-next/apps/web/next-env.d.ts
vendored
Normal file
5
templates/monorepo-next/apps/web/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
6
templates/monorepo-next/apps/web/next.config.mjs
Normal file
6
templates/monorepo-next/apps/web/next.config.mjs
Normal file
@@ -0,0 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
transpilePackages: ["@workspace/ui"],
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
30
templates/monorepo-next/apps/web/package.json
Normal file
30
templates/monorepo-next/apps/web/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@workspace/ui": "workspace:*",
|
||||
"lucide-react": "0.456.0",
|
||||
"next-themes": "^0.4.3",
|
||||
"next": "^15.1.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "18.3.0",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"@workspace/eslint-config": "workspace:^",
|
||||
"@workspace/typescript-config": "workspace:*",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
1
templates/monorepo-next/apps/web/postcss.config.mjs
Normal file
1
templates/monorepo-next/apps/web/postcss.config.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "@workspace/ui/postcss.config";
|
||||
1
templates/monorepo-next/apps/web/tailwind.config.ts
Normal file
1
templates/monorepo-next/apps/web/tailwind.config.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "@workspace/ui/tailwind.config";
|
||||
23
templates/monorepo-next/apps/web/tsconfig.json
Normal file
23
templates/monorepo-next/apps/web/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "@workspace/typescript-config/nextjs.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./*"],
|
||||
"@workspace/ui/*": ["../../packages/ui/src/*"]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"next.config.mjs",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
22
templates/monorepo-next/package.json
Normal file
22
templates/monorepo-next/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "shadcn-ui-monorepo",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "turbo build",
|
||||
"dev": "turbo dev",
|
||||
"lint": "turbo lint",
|
||||
"format": "prettier --write \"**/*.{ts,tsx,md}\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@workspace/eslint-config": "workspace:*",
|
||||
"@workspace/typescript-config": "workspace:*",
|
||||
"prettier": "^3.2.5",
|
||||
"turbo": "^2.3.0",
|
||||
"typescript": "5.5.4"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.3",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
}
|
||||
3
templates/monorepo-next/packages/eslint-config/README.md
Normal file
3
templates/monorepo-next/packages/eslint-config/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# `@workspace/eslint-config`
|
||||
|
||||
Shared eslint configuration for the workspace.
|
||||
32
templates/monorepo-next/packages/eslint-config/base.js
Normal file
32
templates/monorepo-next/packages/eslint-config/base.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import js from "@eslint/js"
|
||||
import eslintConfigPrettier from "eslint-config-prettier"
|
||||
import onlyWarn from "eslint-plugin-only-warn"
|
||||
import turboPlugin from "eslint-plugin-turbo"
|
||||
import tseslint from "typescript-eslint"
|
||||
|
||||
/**
|
||||
* A shared ESLint configuration for the repository.
|
||||
*
|
||||
* @type {import("eslint").Linter.Config}
|
||||
* */
|
||||
export const config = [
|
||||
js.configs.recommended,
|
||||
eslintConfigPrettier,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
plugins: {
|
||||
turbo: turboPlugin,
|
||||
},
|
||||
rules: {
|
||||
"turbo/no-undeclared-env-vars": "warn",
|
||||
},
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
onlyWarn,
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ["dist/**"],
|
||||
},
|
||||
]
|
||||
51
templates/monorepo-next/packages/eslint-config/next.js
Normal file
51
templates/monorepo-next/packages/eslint-config/next.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import js from "@eslint/js"
|
||||
import pluginNext from "@next/eslint-plugin-next"
|
||||
import eslintConfigPrettier from "eslint-config-prettier"
|
||||
import pluginReact from "eslint-plugin-react"
|
||||
import pluginReactHooks from "eslint-plugin-react-hooks"
|
||||
import globals from "globals"
|
||||
import tseslint from "typescript-eslint"
|
||||
|
||||
import { config as baseConfig } from "./base.js"
|
||||
|
||||
/**
|
||||
* A custom ESLint configuration for libraries that use Next.js.
|
||||
*
|
||||
* @type {import("eslint").Linter.Config}
|
||||
* */
|
||||
export const nextJsConfig = [
|
||||
...baseConfig,
|
||||
js.configs.recommended,
|
||||
eslintConfigPrettier,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
...pluginReact.configs.flat.recommended,
|
||||
languageOptions: {
|
||||
...pluginReact.configs.flat.recommended.languageOptions,
|
||||
globals: {
|
||||
...globals.serviceworker,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
"@next/next": pluginNext,
|
||||
},
|
||||
rules: {
|
||||
...pluginNext.configs.recommended.rules,
|
||||
...pluginNext.configs["core-web-vitals"].rules,
|
||||
},
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
"react-hooks": pluginReactHooks,
|
||||
},
|
||||
settings: { react: { version: "detect" } },
|
||||
rules: {
|
||||
...pluginReactHooks.configs.recommended.rules,
|
||||
// React scope no longer necessary with new JSX transform.
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/prop-types": "off",
|
||||
},
|
||||
},
|
||||
]
|
||||
25
templates/monorepo-next/packages/eslint-config/package.json
Normal file
25
templates/monorepo-next/packages/eslint-config/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@workspace/eslint-config",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"exports": {
|
||||
"./base": "./base.js",
|
||||
"./next-js": "./next.js",
|
||||
"./react-internal": "./react-internal.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/eslint-plugin-next": "^15.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
||||
"@typescript-eslint/parser": "^8.15.0",
|
||||
"eslint": "^9.15.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-only-warn": "^1.1.0",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-turbo": "^2.3.0",
|
||||
"globals": "^15.12.0",
|
||||
"typescript": "^5.3.3",
|
||||
"typescript-eslint": "^8.15.0"
|
||||
}
|
||||
}
|
||||
41
templates/monorepo-next/packages/eslint-config/react-internal.js
vendored
Normal file
41
templates/monorepo-next/packages/eslint-config/react-internal.js
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
import js from "@eslint/js"
|
||||
import eslintConfigPrettier from "eslint-config-prettier"
|
||||
import pluginReact from "eslint-plugin-react"
|
||||
import pluginReactHooks from "eslint-plugin-react-hooks"
|
||||
import globals from "globals"
|
||||
import tseslint from "typescript-eslint"
|
||||
|
||||
import { config as baseConfig } from "./base.js"
|
||||
|
||||
/**
|
||||
* A custom ESLint configuration for libraries that use React.
|
||||
*
|
||||
* @type {import("eslint").Linter.Config} */
|
||||
export const config = [
|
||||
...baseConfig,
|
||||
js.configs.recommended,
|
||||
eslintConfigPrettier,
|
||||
...tseslint.configs.recommended,
|
||||
pluginReact.configs.flat.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
...pluginReact.configs.flat.recommended.languageOptions,
|
||||
globals: {
|
||||
...globals.serviceworker,
|
||||
...globals.browser,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
"react-hooks": pluginReactHooks,
|
||||
},
|
||||
settings: { react: { version: "detect" } },
|
||||
rules: {
|
||||
...pluginReactHooks.configs.recommended.rules,
|
||||
// React scope no longer necessary with new JSX transform.
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/prop-types": "off",
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
# `@workspace/typescript-config`
|
||||
|
||||
Shared typescript configuration for the workspace.
|
||||
20
templates/monorepo-next/packages/typescript-config/base.json
Normal file
20
templates/monorepo-next/packages/typescript-config/base.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "Default",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"esModuleInterop": true,
|
||||
"incremental": false,
|
||||
"isolatedModules": true,
|
||||
"lib": ["es2022", "DOM", "DOM.Iterable"],
|
||||
"module": "NodeNext",
|
||||
"moduleDetection": "force",
|
||||
"moduleResolution": "NodeNext",
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "ES2022"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "Next.js",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"plugins": [{ "name": "next" }],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"allowJs": true,
|
||||
"jsx": "preserve",
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@workspace/typescript-config",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"license": "PROPRIETARY",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "React Library",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
}
|
||||
}
|
||||
20
templates/monorepo-next/packages/ui/components.json
Normal file
20
templates/monorepo-next/packages/ui/components.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/styles/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@workspace/ui/components",
|
||||
"utils": "@workspace/ui/lib/utils",
|
||||
"hooks": "@workspace/ui/hooks",
|
||||
"lib": "@workspace/ui/lib",
|
||||
"ui": "@workspace/ui/components"
|
||||
}
|
||||
}
|
||||
4
templates/monorepo-next/packages/ui/eslint.config.js
Normal file
4
templates/monorepo-next/packages/ui/eslint.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import { config } from "@workspace/eslint-config/react-internal"
|
||||
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
export default config
|
||||
42
templates/monorepo-next/packages/ui/package.json
Normal file
42
templates/monorepo-next/packages/ui/package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "@workspace/ui",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "eslint . --max-warnings 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "0.456.0",
|
||||
"next-themes": "^0.4.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tailwind-merge": "^2.5.4",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@turbo/gen": "^2.2.3",
|
||||
"@types/node": "^22.9.0",
|
||||
"@types/react": "18.3.0",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"@workspace/eslint-config": "workspace:*",
|
||||
"@workspace/typescript-config": "workspace:*",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.47",
|
||||
"react": "^18.3.1",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"exports": {
|
||||
"./globals.css": "./src/styles/globals.css",
|
||||
"./postcss.config": "./postcss.config.mjs",
|
||||
"./tailwind.config": "./tailwind.config.ts",
|
||||
"./lib/*": "./src/lib/*.ts",
|
||||
"./components/*": "./src/components/*.tsx",
|
||||
"./hooks/*": "./src/hooks/*.ts"
|
||||
}
|
||||
}
|
||||
9
templates/monorepo-next/packages/ui/postcss.config.mjs
Normal file
9
templates/monorepo-next/packages/ui/postcss.config.mjs
Normal file
@@ -0,0 +1,9 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,56 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@workspace/ui/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
6
templates/monorepo-next/packages/ui/src/lib/utils.ts
Normal file
6
templates/monorepo-next/packages/ui/src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
68
templates/monorepo-next/packages/ui/src/styles/globals.css
Normal file
68
templates/monorepo-next/packages/ui/src/styles/globals.css
Normal file
@@ -0,0 +1,68 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 10% 3.9%;
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
63
templates/monorepo-next/packages/ui/tailwind.config.ts
Normal file
63
templates/monorepo-next/packages/ui/tailwind.config.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type { Config } from "tailwindcss"
|
||||
import tailwindcssAnimate from "tailwindcss-animate"
|
||||
import { fontFamily } from "tailwindcss/defaultTheme"
|
||||
|
||||
const config = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
"app/**/*.{ts,tsx}",
|
||||
"components/**/*.{ts,tsx}",
|
||||
"../../packages/ui/src/components/**/*.{ts,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ["var(--font-sans)", ...fontFamily.sans],
|
||||
mono: ["var(--font-mono)", ...fontFamily.mono],
|
||||
},
|
||||
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)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [tailwindcssAnimate],
|
||||
} satisfies Config
|
||||
|
||||
export default config
|
||||
12
templates/monorepo-next/packages/ui/tsconfig.json
Normal file
12
templates/monorepo-next/packages/ui/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@workspace/typescript-config/react-library.json",
|
||||
"compilerOptions": {
|
||||
//"outDir": "dist"
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@workspace/ui/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
8
templates/monorepo-next/packages/ui/tsconfig.lint.json
Normal file
8
templates/monorepo-next/packages/ui/tsconfig.lint.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@workspace/typescript-config/react-library.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src", "turbo"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
5153
templates/monorepo-next/pnpm-lock.yaml
generated
Normal file
5153
templates/monorepo-next/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
templates/monorepo-next/pnpm-workspace.yaml
Normal file
3
templates/monorepo-next/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
packages:
|
||||
- "apps/*"
|
||||
- "packages/*"
|
||||
4
templates/monorepo-next/tsconfig.json
Normal file
4
templates/monorepo-next/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@workspace/typescript-config/base.json"
|
||||
}
|
||||
|
||||
21
templates/monorepo-next/turbo.json
Normal file
21
templates/monorepo-next/turbo.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"ui": "tui",
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"inputs": ["$TURBO_DEFAULT$", ".env*"],
|
||||
"outputs": [".next/**", "!.next/cache/**"]
|
||||
},
|
||||
"lint": {
|
||||
"dependsOn": ["^lint"]
|
||||
},
|
||||
"check-types": {
|
||||
"dependsOn": ["^check-types"]
|
||||
},
|
||||
"dev": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user