mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-22 12:15:43 +00:00
Compare commits
155 Commits
shadcn@3.4
...
shadcn@3.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d28e02be1b | ||
|
|
6699158a22 | ||
|
|
142cd8ef13 | ||
|
|
bdedce2750 | ||
|
|
4cb283d68e | ||
|
|
480a6cdb37 | ||
|
|
8ba883738e | ||
|
|
b022c24825 | ||
|
|
3587477865 | ||
|
|
05143a80e6 | ||
|
|
728d2003b7 | ||
|
|
12c9e6b0b5 | ||
|
|
56cd757c45 | ||
|
|
9eb784054f | ||
|
|
824577692b | ||
|
|
6be68df08c | ||
|
|
cc48808a0d | ||
|
|
a56b3720d1 | ||
|
|
334db11234 | ||
|
|
8a5027a0cd | ||
|
|
803206305d | ||
|
|
d0fb73ac0e | ||
|
|
62218c1c0c | ||
|
|
dd1563d57d | ||
|
|
0538384860 | ||
|
|
d43b437abc | ||
|
|
8fbfacd243 | ||
|
|
778cee31ee | ||
|
|
73d8b8a817 | ||
|
|
55ab069aca | ||
|
|
c39925a9be | ||
|
|
51179ccd64 | ||
|
|
dcfa05e392 | ||
|
|
541f55df04 | ||
|
|
69010e0230 | ||
|
|
a8025c866e | ||
|
|
6e34ec7280 | ||
|
|
10ccb244a1 | ||
|
|
16fdb07ccc | ||
|
|
49da1fae79 | ||
|
|
a2244d42f7 | ||
|
|
c2075e2a8b | ||
|
|
dd2d8d7ead | ||
|
|
b6a93b7ec6 | ||
|
|
4899d3f0da | ||
|
|
3d04cb099a | ||
|
|
cde343916c | ||
|
|
c877df07b8 | ||
|
|
65e5c1c3cf | ||
|
|
8a7f05f670 | ||
|
|
db004ce4c0 | ||
|
|
e23698a897 | ||
|
|
5813ef20a3 | ||
|
|
515024b69e | ||
|
|
f7284c5cc3 | ||
|
|
c02d00aafc | ||
|
|
df497ad236 | ||
|
|
1e468e33ac | ||
|
|
ff91c31a71 | ||
|
|
25d6a18f6f | ||
|
|
c0309510b6 | ||
|
|
a3a1574668 | ||
|
|
65d581ea5a | ||
|
|
fdf80a1d49 | ||
|
|
86c494c452 | ||
|
|
eb158686b9 | ||
|
|
134cd46edb | ||
|
|
47b0efb20c | ||
|
|
bd4d09d33e | ||
|
|
14d6265580 | ||
|
|
68805d29a1 | ||
|
|
c100d5841a | ||
|
|
7a71da5218 | ||
|
|
e18902039a | ||
|
|
559af6c245 | ||
|
|
8971be484f | ||
|
|
ad6a3c6367 | ||
|
|
befa56b5be | ||
|
|
5d1770e36d | ||
|
|
653521725a | ||
|
|
7c0618bf43 | ||
|
|
854641cea1 | ||
|
|
3a72007f61 | ||
|
|
6b53b238fb | ||
|
|
b398fea304 | ||
|
|
f22174a77f | ||
|
|
c9a39f1007 | ||
|
|
a8ad21f81f | ||
|
|
504503c638 | ||
|
|
f8df5c95cb | ||
|
|
2bfc1c82ba | ||
|
|
84bd724d97 | ||
|
|
39fdf94550 | ||
|
|
08479cc3db | ||
|
|
02d5ce85ec | ||
|
|
c0329c86b9 | ||
|
|
3b1491f908 | ||
|
|
ca4c1c43ec | ||
|
|
1e840eb53c | ||
|
|
96ac92e63f | ||
|
|
e11546e692 | ||
|
|
0b4d62f95c | ||
|
|
dae80dad65 | ||
|
|
abc09809e8 | ||
|
|
8a40fe0ead | ||
|
|
b3ab304a00 | ||
|
|
bb45fd83c3 | ||
|
|
84678ee1c0 | ||
|
|
33ffb0419c | ||
|
|
a2f6c031e2 | ||
|
|
ac098d8cf0 | ||
|
|
8160610410 | ||
|
|
c7901e3a41 | ||
|
|
d73ac361b3 | ||
|
|
ebad2901ce | ||
|
|
4f617d59b8 | ||
|
|
ed0e103bd6 | ||
|
|
9cab0c9b18 | ||
|
|
d80e084814 | ||
|
|
efcf9728c2 | ||
|
|
8835bacc8b | ||
|
|
f2556d2386 | ||
|
|
75a0000075 | ||
|
|
ac306c60f5 | ||
|
|
5e2ef1f8bd | ||
|
|
7d9b8aefff | ||
|
|
58208e3802 | ||
|
|
a16a77446a | ||
|
|
39032bb390 | ||
|
|
d7e0dc3ec8 | ||
|
|
6bddba986d | ||
|
|
b70059b25b | ||
|
|
37bc2eec1f | ||
|
|
bb048fb532 | ||
|
|
9c373dbd27 | ||
|
|
d75b092c61 | ||
|
|
be49662bf5 | ||
|
|
b2b2e3fc98 | ||
|
|
188b746074 | ||
|
|
6f093a0f3f | ||
|
|
f18f1eaff7 | ||
|
|
9ac1b5c0a5 | ||
|
|
f63b70b413 | ||
|
|
54e725d986 | ||
|
|
62dbad36bb | ||
|
|
a707424fa2 | ||
|
|
e2bfa6bd85 | ||
|
|
6292464d90 | ||
|
|
6617167d6f | ||
|
|
ca28857d40 | ||
|
|
343bc941b1 | ||
|
|
c9311f26fa | ||
|
|
4e0871f426 | ||
|
|
cb769b7059 | ||
|
|
93037dca94 |
@@ -7,5 +7,5 @@
|
|||||||
"access": "public",
|
"access": "public",
|
||||||
"baseBranch": "main",
|
"baseBranch": "main",
|
||||||
"updateInternalDependencies": "patch",
|
"updateInternalDependencies": "patch",
|
||||||
"ignore": ["www", "v4", "tests"]
|
"ignore": ["v4", "tests"]
|
||||||
}
|
}
|
||||||
|
|||||||
63
.github/ISSUE_TEMPLATE/registry_directory.yml
vendored
Normal file
63
.github/ISSUE_TEMPLATE/registry_directory.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
name: Add registry to directory
|
||||||
|
description: Add your registry to the directory
|
||||||
|
title: "[Registry Directory]: "
|
||||||
|
labels: ["registry", "directory"]
|
||||||
|
assignees: []
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: name
|
||||||
|
attributes:
|
||||||
|
label: Name
|
||||||
|
description: The name of your registry. This is also the namespace.
|
||||||
|
placeholder: e.g., "@acme"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: url
|
||||||
|
attributes:
|
||||||
|
label: URL
|
||||||
|
description: The URL to your registry index. Use {name} placeholder.
|
||||||
|
placeholder: https://ui.acme.com/r/{name}.json
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: homepage
|
||||||
|
attributes:
|
||||||
|
label: Homepage
|
||||||
|
description: The URL to your registry homepage. This is where users can browse your registry.
|
||||||
|
placeholder: https://ui.acme.com
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: Briefly describe what is your registry and what type of components or code it distributes.
|
||||||
|
placeholder:
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logo
|
||||||
|
attributes:
|
||||||
|
label: Logo
|
||||||
|
description: Add your SVG logo here.
|
||||||
|
placeholder:
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: requirements
|
||||||
|
attributes:
|
||||||
|
label: Checklist
|
||||||
|
description: Verify that your registry meets the following requirements.
|
||||||
|
options:
|
||||||
|
- label: The registry must be open source and publicly accessible.
|
||||||
|
- label: The registry must be a valid JSON file that conforms to the [registry schema](https://ui.shadcn.com/docs/registry/registry-json) specification.
|
||||||
|
- label: The `files` array, if present on your registry items, must NOT include a `content` property.
|
||||||
|
- label: I've attached a square SVG logo to this issue
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
4
.github/workflows/prerelease.yml
vendored
4
.github/workflows/prerelease.yml
vendored
@@ -27,10 +27,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
version: 9.0.6
|
version: 9.0.6
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 20
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install NPM Dependencies
|
- name: Install NPM Dependencies
|
||||||
|
|||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -23,11 +23,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
version: 9.0.6
|
version: 9.0.6
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 20
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
version: 9.0.6
|
version: 9.0.6
|
||||||
node-version: 18
|
node-version: 20
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install NPM Dependencies
|
- name: Install NPM Dependencies
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ node_modules
|
|||||||
.next
|
.next
|
||||||
build
|
build
|
||||||
.contentlayer
|
.contentlayer
|
||||||
apps/www/pages/api/registry.json
|
|
||||||
**/fixtures
|
**/fixtures
|
||||||
|
deprecated
|
||||||
|
|||||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -10,6 +10,6 @@
|
|||||||
"**/fixtures/**"
|
"**/fixtures/**"
|
||||||
],
|
],
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
"apps/www": true
|
"deprecated": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,28 +20,25 @@ This repository is structured as follows:
|
|||||||
|
|
||||||
```
|
```
|
||||||
apps
|
apps
|
||||||
└── www
|
└── v4
|
||||||
├── app
|
├── app
|
||||||
├── components
|
├── components
|
||||||
├── content
|
├── content
|
||||||
└── registry
|
└── registry
|
||||||
├── default
|
└── new-york-v4
|
||||||
│ ├── example
|
|
||||||
│ └── ui
|
|
||||||
└── new-york
|
|
||||||
├── example
|
├── example
|
||||||
└── ui
|
└── ui
|
||||||
packages
|
packages
|
||||||
└── cli
|
└── shadcn
|
||||||
```
|
```
|
||||||
|
|
||||||
| Path | Description |
|
| Path | Description |
|
||||||
| --------------------- | ---------------------------------------- |
|
| -------------------- | ---------------------------------------- |
|
||||||
| `apps/www/app` | The Next.js application for the website. |
|
| `apps/v4/app` | The Next.js application for the website. |
|
||||||
| `apps/www/components` | The React components for the website. |
|
| `apps/v4/components` | The React components for the website. |
|
||||||
| `apps/www/content` | The content for the website. |
|
| `apps/v4/content` | The content for the website. |
|
||||||
| `apps/www/registry` | The registry for the components. |
|
| `apps/v4/registry` | The registry for the components. |
|
||||||
| `packages/cli` | The `shadcn-ui` package. |
|
| `packages/shadcn` | The `shadcn` package. |
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
@@ -82,32 +79,26 @@ You can use the `pnpm --filter=[WORKSPACE]` command to start the development pro
|
|||||||
1. To run the `ui.shadcn.com` website:
|
1. To run the `ui.shadcn.com` website:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm --filter=www dev
|
pnpm --filter=v4 dev
|
||||||
```
|
```
|
||||||
|
|
||||||
2. To run the `shadcn-ui` package:
|
2. To run the `shadcn` package:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm --filter=shadcn-ui dev
|
pnpm --filter=shadcn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running the CLI Locally
|
## Running the CLI Locally
|
||||||
|
|
||||||
To run the CLI locally, you can follow the workflow:
|
To run the CLI locally, you can follow the workflow:
|
||||||
|
|
||||||
1. Start by running the registry (main site) to make sure the components are up to date:
|
1. Start by running the dev server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm v4:dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Run the development script for the CLI:
|
2. In another terminal tab, test the CLI by running:
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm shadcn:dev
|
|
||||||
```
|
|
||||||
|
|
||||||
3. In another terminal tab, test the CLI by running:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm shadcn
|
pnpm shadcn
|
||||||
@@ -119,36 +110,27 @@ To run the CLI locally, you can follow the workflow:
|
|||||||
pnpm shadcn <init | add | ...> -c ~/Desktop/my-app
|
pnpm shadcn <init | add | ...> -c ~/Desktop/my-app
|
||||||
```
|
```
|
||||||
|
|
||||||
4. To run the tests for the CLI:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm --filter=shadcn test
|
|
||||||
```
|
|
||||||
|
|
||||||
This workflow ensures that you are running the most recent version of the registry and testing the CLI properly in your local environment.
|
This workflow ensures that you are running the most recent version of the registry and testing the CLI properly in your local environment.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The documentation for this project is located in the `www` workspace. You can run the documentation locally by running the following command:
|
The documentation for this project is located in the `v4` workspace. You can run the documentation locally by running the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm --filter=www dev
|
pnpm --filter=v4 dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Documentation is written using [MDX](https://mdxjs.com). You can find the documentation files in the `apps/www/content/docs` directory.
|
Documentation is written using [MDX](https://mdxjs.com). You can find the documentation files in the `apps/v4/content/docs` directory.
|
||||||
|
|
||||||
## Components
|
## Components
|
||||||
|
|
||||||
We use a registry system for developing components. You can find the source code for the components under `apps/www/registry`. The components are organized by styles.
|
We use a registry system for developing components. You can find the source code for the components under `apps/v4/registry`. The components are organized by styles.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
apps
|
apps
|
||||||
└── www
|
└── v4
|
||||||
└── registry
|
└── registry
|
||||||
├── default
|
└── new-york-v4
|
||||||
│ ├── example
|
|
||||||
│ └── ui
|
|
||||||
└── new-york
|
|
||||||
├── example
|
├── example
|
||||||
└── ui
|
└── ui
|
||||||
```
|
```
|
||||||
@@ -157,7 +139,7 @@ When adding or modifying components, please ensure that:
|
|||||||
|
|
||||||
1. You make the changes for every style.
|
1. You make the changes for every style.
|
||||||
2. You update the documentation.
|
2. You update the documentation.
|
||||||
3. You run `pnpm build:registry` to update the registry.
|
3. You run `pnpm registry:build` to update the registry.
|
||||||
|
|
||||||
## Commit Convention
|
## Commit Convention
|
||||||
|
|
||||||
@@ -196,9 +178,9 @@ If you have a request for a new component, please open a discussion on GitHub. W
|
|||||||
|
|
||||||
## CLI
|
## CLI
|
||||||
|
|
||||||
The `shadcn-ui` package is a CLI for adding components to your project. You can find the documentation for the CLI [here](https://ui.shadcn.com/docs/cli).
|
The `shadcn` package is a CLI for adding components to your project. You can find the documentation for the CLI [here](https://ui.shadcn.com/docs/cli).
|
||||||
|
|
||||||
Any changes to the CLI should be made in the `packages/cli` directory. If you can, it would be great if you could add tests for your changes.
|
Any changes to the CLI should be made in the `packages/shadcn` directory. If you can, it would be great if you could add tests for your changes.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# shadcn/ui
|
# shadcn/ui
|
||||||
|
|
||||||
Accessible and customizable components that you can copy and paste into your apps. Free. Open Source. **Use this to build your own component library**.
|
A set of beautifully designed components that you can customize, extend, and build on. Start here then make it your own. Open Source. Open Code. **Use this to build your own component library**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
|||||||
@@ -6,4 +6,4 @@ We will investigate all legitimate reports and do our best to quickly fix the pr
|
|||||||
|
|
||||||
Our preference is that you make use of GitHub's private vulnerability reporting feature to disclose potential security vulnerabilities in our Open Source Software.
|
Our preference is that you make use of GitHub's private vulnerability reporting feature to disclose potential security vulnerabilities in our Open Source Software.
|
||||||
|
|
||||||
To do this, please visit the security tab of the repository and click the "Report a vulnerability" button.
|
To do this, please visit the security tab of the repository and click the [Report a vulnerability](https://github.com/shadcn-ui/ui/security/advisories/new) button.
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
import { IconMinus, IconPlus } from "@tabler/icons-react"
|
import { IconMinus, IconPlus } from "@tabler/icons-react"
|
||||||
import { CheckIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { useThemeConfig } from "@/components/active-theme"
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
import { ButtonGroup } from "@/registry/new-york-v4/ui/button-group"
|
import { ButtonGroup } from "@/registry/new-york-v4/ui/button-group"
|
||||||
import {
|
import {
|
||||||
@@ -18,34 +17,31 @@ import {
|
|||||||
FieldTitle,
|
FieldTitle,
|
||||||
} from "@/registry/new-york-v4/ui/field"
|
} from "@/registry/new-york-v4/ui/field"
|
||||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
import { Input } from "@/registry/new-york-v4/ui/input"
|
||||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
|
||||||
import {
|
import {
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
RadioGroupItem,
|
RadioGroupItem,
|
||||||
} from "@/registry/new-york-v4/ui/radio-group"
|
} from "@/registry/new-york-v4/ui/radio-group"
|
||||||
import { Switch } from "@/registry/new-york-v4/ui/switch"
|
import { Switch } from "@/registry/new-york-v4/ui/switch"
|
||||||
|
|
||||||
const accents = [
|
|
||||||
{
|
|
||||||
name: "Blue",
|
|
||||||
value: "blue",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Amber",
|
|
||||||
value: "amber",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Green",
|
|
||||||
value: "green",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Rose",
|
|
||||||
value: "rose",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export function AppearanceSettings() {
|
export function AppearanceSettings() {
|
||||||
const { activeTheme, setActiveTheme } = useThemeConfig()
|
const [gpuCount, setGpuCount] = React.useState(8)
|
||||||
|
|
||||||
|
const handleGpuAdjustment = React.useCallback((adjustment: number) => {
|
||||||
|
setGpuCount((prevCount) =>
|
||||||
|
Math.max(1, Math.min(99, prevCount + adjustment))
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleGpuInputChange = React.useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = parseInt(e.target.value, 10)
|
||||||
|
if (!isNaN(value) && value >= 1 && value <= 99) {
|
||||||
|
setGpuCount(value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldSet>
|
<FieldSet>
|
||||||
<FieldGroup>
|
<FieldGroup>
|
||||||
@@ -90,37 +86,6 @@ export function AppearanceSettings() {
|
|||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
<FieldSeparator />
|
<FieldSeparator />
|
||||||
<Field orientation="horizontal">
|
|
||||||
<FieldContent>
|
|
||||||
<FieldTitle>Accent</FieldTitle>
|
|
||||||
<FieldDescription>Select the accent color.</FieldDescription>
|
|
||||||
</FieldContent>
|
|
||||||
<FieldSet aria-label="Accent">
|
|
||||||
<RadioGroup
|
|
||||||
className="flex flex-wrap gap-2"
|
|
||||||
value={activeTheme}
|
|
||||||
onValueChange={setActiveTheme}
|
|
||||||
>
|
|
||||||
{accents.map((accent) => (
|
|
||||||
<Label
|
|
||||||
htmlFor={accent.value}
|
|
||||||
key={accent.value}
|
|
||||||
data-theme={accent.value}
|
|
||||||
className="flex size-6 items-center justify-center rounded-full data-[theme=amber]:bg-amber-600 data-[theme=blue]:bg-blue-700 data-[theme=green]:bg-green-600 data-[theme=rose]:bg-rose-600"
|
|
||||||
>
|
|
||||||
<RadioGroupItem
|
|
||||||
id={accent.value}
|
|
||||||
value={accent.value}
|
|
||||||
aria-label={accent.name}
|
|
||||||
className="peer sr-only"
|
|
||||||
/>
|
|
||||||
<CheckIcon className="hidden size-4 stroke-white peer-data-[state=checked]:block" />
|
|
||||||
</Label>
|
|
||||||
))}
|
|
||||||
</RadioGroup>
|
|
||||||
</FieldSet>
|
|
||||||
</Field>
|
|
||||||
<FieldSeparator />
|
|
||||||
<Field orientation="horizontal">
|
<Field orientation="horizontal">
|
||||||
<FieldContent>
|
<FieldContent>
|
||||||
<FieldLabel htmlFor="number-of-gpus-f6l">Number of GPUs</FieldLabel>
|
<FieldLabel htmlFor="number-of-gpus-f6l">Number of GPUs</FieldLabel>
|
||||||
@@ -129,7 +94,8 @@ export function AppearanceSettings() {
|
|||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Input
|
<Input
|
||||||
id="number-of-gpus-f6l"
|
id="number-of-gpus-f6l"
|
||||||
placeholder="8"
|
value={gpuCount}
|
||||||
|
onChange={handleGpuInputChange}
|
||||||
size={3}
|
size={3}
|
||||||
className="h-8 !w-14 font-mono"
|
className="h-8 !w-14 font-mono"
|
||||||
maxLength={3}
|
maxLength={3}
|
||||||
@@ -139,6 +105,8 @@ export function AppearanceSettings() {
|
|||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
type="button"
|
type="button"
|
||||||
aria-label="Decrement"
|
aria-label="Decrement"
|
||||||
|
onClick={() => handleGpuAdjustment(-1)}
|
||||||
|
disabled={gpuCount <= 1}
|
||||||
>
|
>
|
||||||
<IconMinus />
|
<IconMinus />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -147,6 +115,8 @@ export function AppearanceSettings() {
|
|||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
type="button"
|
type="button"
|
||||||
aria-label="Increment"
|
aria-label="Increment"
|
||||||
|
onClick={() => handleGpuAdjustment(1)}
|
||||||
|
disabled={gpuCount >= 99}
|
||||||
>
|
>
|
||||||
<IconPlus />
|
<IconPlus />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function RootComponents() {
|
|||||||
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
|
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
|
||||||
<InputGroupButtonExample />
|
<InputGroupButtonExample />
|
||||||
<ItemDemo />
|
<ItemDemo />
|
||||||
<FieldSeparator>Appearance Settings</FieldSeparator>
|
<FieldSeparator className="my-4">Appearance Settings</FieldSeparator>
|
||||||
<AppearanceSettings />
|
<AppearanceSettings />
|
||||||
</div>
|
</div>
|
||||||
<div className="order-first flex flex-col gap-6 lg:hidden xl:order-last xl:flex *:[div]:w-full *:[div]:max-w-full">
|
<div className="order-first flex flex-col gap-6 lg:hidden xl:order-last xl:flex *:[div]:w-full *:[div]:max-w-full">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Spinner } from "@/registry/new-york-v4/ui/spinner"
|
|||||||
|
|
||||||
export function SpinnerBadge() {
|
export function SpinnerBadge() {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 [--radius:1.2rem]">
|
<div className="flex items-center gap-2">
|
||||||
<Badge>
|
<Badge>
|
||||||
<Spinner />
|
<Spinner />
|
||||||
Syncing
|
Syncing
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { getAllBlockIds } from "@/lib/blocks"
|
import { getAllBlockIds } from "@/lib/blocks"
|
||||||
|
import { registryCategories } from "@/lib/categories"
|
||||||
import { BlockDisplay } from "@/components/block-display"
|
import { BlockDisplay } from "@/components/block-display"
|
||||||
import { registryCategories } from "@/registry/registry-categories"
|
import { getActiveStyle } from "@/registry/styles"
|
||||||
|
|
||||||
export const revalidate = false
|
export const revalidate = false
|
||||||
export const dynamic = "force-static"
|
export const dynamic = "force-static"
|
||||||
@@ -17,13 +18,16 @@ export default async function BlocksPage({
|
|||||||
}: {
|
}: {
|
||||||
params: Promise<{ categories?: string[] }>
|
params: Promise<{ categories?: string[] }>
|
||||||
}) {
|
}) {
|
||||||
const { categories = [] } = await params
|
const [{ categories = [] }, activeStyle] = await Promise.all([
|
||||||
|
params,
|
||||||
|
getActiveStyle(),
|
||||||
|
])
|
||||||
const blocks = await getAllBlockIds(["registry:block"], categories)
|
const blocks = await getAllBlockIds(["registry:block"], categories)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-12 md:gap-24">
|
<div className="flex flex-col gap-12 md:gap-24">
|
||||||
{blocks.map((name) => (
|
{blocks.map((name) => (
|
||||||
<BlockDisplay name={name} key={name} />
|
<BlockDisplay name={name} key={name} styleName={activeStyle.name} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Link from "next/link"
|
|||||||
|
|
||||||
import { BlockDisplay } from "@/components/block-display"
|
import { BlockDisplay } from "@/components/block-display"
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
import { getActiveStyle } from "@/registry/styles"
|
||||||
|
|
||||||
export const dynamic = "force-static"
|
export const dynamic = "force-static"
|
||||||
export const revalidate = false
|
export const revalidate = false
|
||||||
@@ -15,10 +16,12 @@ const FEATURED_BLOCKS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export default async function BlocksPage() {
|
export default async function BlocksPage() {
|
||||||
|
const activeStyle = await getActiveStyle()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-12 md:gap-24">
|
<div className="flex flex-col gap-12 md:gap-24">
|
||||||
{FEATURED_BLOCKS.map((name) => (
|
{FEATURED_BLOCKS.map((name) => (
|
||||||
<BlockDisplay name={name} key={name} />
|
<BlockDisplay name={name} key={name} styleName={activeStyle.name} />
|
||||||
))}
|
))}
|
||||||
<div className="container-wrapper">
|
<div className="container-wrapper">
|
||||||
<div className="container flex justify-center py-6">
|
<div className="container flex justify-center py-6">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { notFound } from "next/navigation"
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { ChartDisplay } from "@/components/chart-display"
|
import { ChartDisplay } from "@/components/chart-display"
|
||||||
|
import { getActiveStyle } from "@/registry/styles"
|
||||||
import { charts } from "@/app/(app)/charts/charts"
|
import { charts } from "@/app/(app)/charts/charts"
|
||||||
|
|
||||||
export const revalidate = false
|
export const revalidate = false
|
||||||
@@ -41,6 +42,7 @@ export default async function ChartPage({ params }: ChartPageProps) {
|
|||||||
|
|
||||||
const chartType = type as ChartType
|
const chartType = type as ChartType
|
||||||
const chartList = charts[chartType]
|
const chartList = charts[chartType]
|
||||||
|
const activeStyle = await getActiveStyle()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid flex-1 gap-12 lg:gap-24">
|
<div className="grid flex-1 gap-12 lg:gap-24">
|
||||||
@@ -54,6 +56,7 @@ export default async function ChartPage({ params }: ChartPageProps) {
|
|||||||
<ChartDisplay
|
<ChartDisplay
|
||||||
key={chart.id}
|
key={chart.id}
|
||||||
name={chart.id}
|
name={chart.id}
|
||||||
|
styleName={activeStyle.name}
|
||||||
className={cn(chart.fullWidth && "md:col-span-2 lg:col-span-3")}
|
className={cn(chart.fullWidth && "md:col-span-2 lg:col-span-3")}
|
||||||
>
|
>
|
||||||
<chart.component />
|
<chart.component />
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import {
|
|||||||
IconArrowRight,
|
IconArrowRight,
|
||||||
IconArrowUpRight,
|
IconArrowUpRight,
|
||||||
} from "@tabler/icons-react"
|
} from "@tabler/icons-react"
|
||||||
import { findNeighbour } from "fumadocs-core/server"
|
import fm from "front-matter"
|
||||||
|
import { findNeighbour } from "fumadocs-core/page-tree"
|
||||||
|
import z from "zod"
|
||||||
|
|
||||||
import { source } from "@/lib/source"
|
import { source } from "@/lib/source"
|
||||||
import { absoluteUrl } from "@/lib/utils"
|
import { absoluteUrl } from "@/lib/utils"
|
||||||
@@ -25,7 +27,7 @@ export function generateStaticParams() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata(props: {
|
export async function generateMetadata(props: {
|
||||||
params: Promise<{ slug?: string[] }>
|
params: Promise<{ slug: string[] }>
|
||||||
}) {
|
}) {
|
||||||
const params = await props.params
|
const params = await props.params
|
||||||
const page = source.getPage(params.slug)
|
const page = source.getPage(params.slug)
|
||||||
@@ -73,7 +75,7 @@ export async function generateMetadata(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
params: Promise<{ slug?: string[] }>
|
params: Promise<{ slug: string[] }>
|
||||||
}) {
|
}) {
|
||||||
const params = await props.params
|
const params = await props.params
|
||||||
const page = source.getPage(params.slug)
|
const page = source.getPage(params.slug)
|
||||||
@@ -82,18 +84,24 @@ export default async function Page(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const doc = page.data
|
const doc = page.data
|
||||||
// @ts-expect-error - revisit fumadocs types.
|
|
||||||
const MDX = doc.body
|
const MDX = doc.body
|
||||||
const neighbours = await findNeighbour(source.pageTree, page.url)
|
const neighbours = findNeighbour(source.pageTree, page.url)
|
||||||
|
|
||||||
// @ts-expect-error - revisit fumadocs types.
|
const raw = await page.data.getText("raw")
|
||||||
const links = doc.links
|
const { attributes } = fm(raw)
|
||||||
|
const { links } = z
|
||||||
|
.object({
|
||||||
|
links: z
|
||||||
|
.object({
|
||||||
|
doc: z.string().optional(),
|
||||||
|
api: z.string().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
.parse(attributes)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="flex items-stretch text-[1.05rem] sm:text-[15px] xl:w-full">
|
||||||
data-slot="docs"
|
|
||||||
className="flex items-stretch text-[1.05rem] sm:text-[15px] xl:w-full"
|
|
||||||
>
|
|
||||||
<div className="flex min-w-0 flex-1 flex-col">
|
<div className="flex min-w-0 flex-1 flex-col">
|
||||||
<div className="h-(--top-spacing) shrink-0" />
|
<div className="h-(--top-spacing) shrink-0" />
|
||||||
<div className="mx-auto flex w-full max-w-2xl min-w-0 flex-1 flex-col gap-8 px-4 py-6 text-neutral-800 md:px-0 lg:py-8 dark:text-neutral-300">
|
<div className="mx-auto flex w-full max-w-2xl min-w-0 flex-1 flex-col gap-8 px-4 py-6 text-neutral-800 md:px-0 lg:py-8 dark:text-neutral-300">
|
||||||
@@ -104,11 +112,7 @@ export default async function Page(props: {
|
|||||||
{doc.title}
|
{doc.title}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="docs-nav bg-background/80 border-border/50 fixed inset-x-0 bottom-0 isolate z-50 flex items-center gap-2 border-t px-6 py-4 backdrop-blur-sm sm:static sm:z-0 sm:border-t-0 sm:bg-transparent sm:px-0 sm:pt-1.5 sm:backdrop-blur-none">
|
<div className="docs-nav bg-background/80 border-border/50 fixed inset-x-0 bottom-0 isolate z-50 flex items-center gap-2 border-t px-6 py-4 backdrop-blur-sm sm:static sm:z-0 sm:border-t-0 sm:bg-transparent sm:px-0 sm:pt-1.5 sm:backdrop-blur-none">
|
||||||
<DocsCopyPage
|
<DocsCopyPage page={raw} url={absoluteUrl(page.url)} />
|
||||||
// @ts-expect-error - revisit fumadocs types.
|
|
||||||
page={doc.content}
|
|
||||||
url={absoluteUrl(page.url)}
|
|
||||||
/>
|
|
||||||
{neighbours.previous && (
|
{neighbours.previous && (
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -195,10 +199,8 @@ export default async function Page(props: {
|
|||||||
</div>
|
</div>
|
||||||
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[calc(100svh-var(--footer-height)+2rem)] w-72 flex-col gap-4 overflow-hidden overscroll-none pb-8 xl:flex">
|
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[calc(100svh-var(--footer-height)+2rem)] w-72 flex-col gap-4 overflow-hidden overscroll-none pb-8 xl:flex">
|
||||||
<div className="h-(--top-spacing) shrink-0" />
|
<div className="h-(--top-spacing) shrink-0" />
|
||||||
{/* @ts-expect-error - revisit fumadocs types. */}
|
|
||||||
{doc.toc?.length ? (
|
{doc.toc?.length ? (
|
||||||
<div className="no-scrollbar overflow-y-auto px-8">
|
<div className="no-scrollbar overflow-y-auto px-8">
|
||||||
{/* @ts-expect-error - revisit fumadocs types. */}
|
|
||||||
<DocsTableOfContents toc={doc.toc} />
|
<DocsTableOfContents toc={doc.toc} />
|
||||||
<div className="h-12" />
|
<div className="h-12" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ import { Button } from "@/registry/new-york-v4/ui/button"
|
|||||||
export const dynamic = "force-static"
|
export const dynamic = "force-static"
|
||||||
export const revalidate = false
|
export const revalidate = false
|
||||||
|
|
||||||
const title = "Examples"
|
const title = "The Foundation for your Design System"
|
||||||
const description = "Check out some examples app built using the components."
|
const description =
|
||||||
|
"A set of beautifully designed components that you can customize, extend, and build on. Start here then make it your own. Open Source. Open Code."
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title,
|
title,
|
||||||
@@ -52,24 +53,20 @@ export default function ExamplesLayout({
|
|||||||
<>
|
<>
|
||||||
<PageHeader>
|
<PageHeader>
|
||||||
<Announcement />
|
<Announcement />
|
||||||
<PageHeaderHeading>Build your Component Library</PageHeaderHeading>
|
<PageHeaderHeading className="max-w-4xl">{title}</PageHeaderHeading>
|
||||||
<PageHeaderDescription>
|
<PageHeaderDescription>{description}</PageHeaderDescription>
|
||||||
A set of beautifully-designed, accessible components and a code
|
|
||||||
distribution platform. Works with your favorite frameworks. Open
|
|
||||||
Source. Open Code.
|
|
||||||
</PageHeaderDescription>
|
|
||||||
<PageActions>
|
<PageActions>
|
||||||
<Button asChild size="sm">
|
<Button asChild size="sm">
|
||||||
<Link href="/docs">Get Started</Link>
|
<Link href="/docs/installation">Get Started</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild size="sm" variant="ghost">
|
<Button asChild size="sm" variant="ghost">
|
||||||
<Link href="/blocks">Browse Blocks</Link>
|
<Link href="/docs/components">View Components</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</PageActions>
|
</PageActions>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<PageNav id="examples">
|
<PageNav id="examples" className="hidden md:flex">
|
||||||
<ExamplesNav className="[&>a:first-child]:text-primary flex-1 overflow-hidden" />
|
<ExamplesNav className="[&>a:first-child]:text-primary flex-1 overflow-hidden" />
|
||||||
<ThemeSelector className="mr-4 hidden md:block" />
|
<ThemeSelector className="mr-4 hidden md:flex" />
|
||||||
</PageNav>
|
</PageNav>
|
||||||
<div className="container-wrapper section-soft flex flex-1 flex-col pb-6">
|
<div className="container-wrapper section-soft flex flex-1 flex-col pb-6">
|
||||||
<div className="theme-container container flex flex-1 scroll-mt-20 flex-col">
|
<div className="theme-container container flex flex-1 scroll-mt-20 flex-col">
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import { SiteHeader } from "@/components/site-header"
|
|||||||
|
|
||||||
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-background relative z-10 flex min-h-svh flex-col">
|
<div
|
||||||
|
data-slot="layout"
|
||||||
|
className="bg-background relative z-10 flex min-h-svh flex-col"
|
||||||
|
>
|
||||||
<SiteHeader />
|
<SiteHeader />
|
||||||
<main className="flex flex-1 flex-col">{children}</main>
|
<main className="flex flex-1 flex-col">{children}</main>
|
||||||
<SiteFooter />
|
<SiteFooter />
|
||||||
|
|||||||
@@ -3,22 +3,26 @@ import { NextResponse, type NextRequest } from "next/server"
|
|||||||
|
|
||||||
import { processMdxForLLMs } from "@/lib/llm"
|
import { processMdxForLLMs } from "@/lib/llm"
|
||||||
import { source } from "@/lib/source"
|
import { source } from "@/lib/source"
|
||||||
|
import { getActiveStyle } from "@/registry/styles"
|
||||||
|
|
||||||
export const revalidate = false
|
export const revalidate = false
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
_req: NextRequest,
|
_req: NextRequest,
|
||||||
{ params }: { params: Promise<{ slug: string[] }> }
|
{ params }: { params: Promise<{ slug?: string[] }> }
|
||||||
) {
|
) {
|
||||||
const slug = (await params).slug
|
const [{ slug }, activeStyle] = await Promise.all([params, getActiveStyle()])
|
||||||
|
|
||||||
const page = source.getPage(slug)
|
const page = source.getPage(slug)
|
||||||
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
notFound()
|
notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-expect-error - revisit fumadocs types.
|
const processedContent = processMdxForLLMs(
|
||||||
const processedContent = processMdxForLLMs(page.data.content)
|
await page.data.getText("raw"),
|
||||||
|
activeStyle.name
|
||||||
|
)
|
||||||
|
|
||||||
return new NextResponse(processedContent, {
|
return new NextResponse(processedContent, {
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import { ItemDemo } from "./components/item-demo"
|
|||||||
import { KbdDemo } from "./components/kbd-demo"
|
import { KbdDemo } from "./components/kbd-demo"
|
||||||
import { LabelDemo } from "./components/label-demo"
|
import { LabelDemo } from "./components/label-demo"
|
||||||
import { MenubarDemo } from "./components/menubar-demo"
|
import { MenubarDemo } from "./components/menubar-demo"
|
||||||
|
import { NativeSelectDemo } from "./components/native-select-demo"
|
||||||
import { NavigationMenuDemo } from "./components/navigation-menu-demo"
|
import { NavigationMenuDemo } from "./components/navigation-menu-demo"
|
||||||
import { PaginationDemo } from "./components/pagination-demo"
|
import { PaginationDemo } from "./components/pagination-demo"
|
||||||
import { PopoverDemo } from "./components/popover-demo"
|
import { PopoverDemo } from "./components/popover-demo"
|
||||||
@@ -279,6 +280,13 @@ export const componentRegistry: Record<string, ComponentConfig> = {
|
|||||||
type: "registry:ui",
|
type: "registry:ui",
|
||||||
href: "/sink/navigation-menu",
|
href: "/sink/navigation-menu",
|
||||||
},
|
},
|
||||||
|
"native-select": {
|
||||||
|
name: "Native Select",
|
||||||
|
component: NativeSelectDemo,
|
||||||
|
type: "registry:ui",
|
||||||
|
href: "/sink/native-select",
|
||||||
|
label: "New",
|
||||||
|
},
|
||||||
pagination: {
|
pagination: {
|
||||||
name: "Pagination",
|
name: "Pagination",
|
||||||
component: PaginationDemo,
|
component: PaginationDemo,
|
||||||
|
|||||||
135
apps/v4/app/(internal)/sink/components/native-select-demo.tsx
Normal file
135
apps/v4/app/(internal)/sink/components/native-select-demo.tsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import {
|
||||||
|
NativeSelect,
|
||||||
|
NativeSelectOptGroup,
|
||||||
|
NativeSelectOption,
|
||||||
|
} from "@/registry/new-york-v4/ui/native-select"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/registry/new-york-v4/ui/select"
|
||||||
|
|
||||||
|
export function NativeSelectDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-8">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="text-muted-foreground text-sm font-medium">
|
||||||
|
Basic Select
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<NativeSelect>
|
||||||
|
<NativeSelectOption value="">Select a fruit</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="apple">Apple</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="banana">Banana</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="blueberry">Blueberry</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="grapes" disabled>
|
||||||
|
Grapes
|
||||||
|
</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="pineapple">Pineapple</NativeSelectOption>
|
||||||
|
</NativeSelect>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a fruit" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
<SelectItem value="blueberry">Blueberry</SelectItem>
|
||||||
|
<SelectItem value="grapes" disabled>
|
||||||
|
Grapes
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="pineapple">Pineapple</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="text-muted-foreground text-sm font-medium">
|
||||||
|
With Groups
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<NativeSelect>
|
||||||
|
<NativeSelectOption value="">Select a food</NativeSelectOption>
|
||||||
|
<NativeSelectOptGroup label="Fruits">
|
||||||
|
<NativeSelectOption value="apple">Apple</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="banana">Banana</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="blueberry">
|
||||||
|
Blueberry
|
||||||
|
</NativeSelectOption>
|
||||||
|
</NativeSelectOptGroup>
|
||||||
|
<NativeSelectOptGroup label="Vegetables">
|
||||||
|
<NativeSelectOption value="carrot">Carrot</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="broccoli">Broccoli</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="spinach">Spinach</NativeSelectOption>
|
||||||
|
</NativeSelectOptGroup>
|
||||||
|
</NativeSelect>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a food" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Fruits</SelectLabel>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
<SelectItem value="blueberry">Blueberry</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Vegetables</SelectLabel>
|
||||||
|
<SelectItem value="carrot">Carrot</SelectItem>
|
||||||
|
<SelectItem value="broccoli">Broccoli</SelectItem>
|
||||||
|
<SelectItem value="spinach">Spinach</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="text-muted-foreground text-sm font-medium">
|
||||||
|
Disabled State
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<NativeSelect disabled>
|
||||||
|
<NativeSelectOption value="">Disabled</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="apple">Apple</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="banana">Banana</NativeSelectOption>
|
||||||
|
</NativeSelect>
|
||||||
|
<Select disabled>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Disabled" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="text-muted-foreground text-sm font-medium">
|
||||||
|
Error State
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<NativeSelect aria-invalid="true">
|
||||||
|
<NativeSelectOption value="">Error state</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="apple">Apple</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="banana">Banana</NativeSelectOption>
|
||||||
|
</NativeSelect>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger aria-invalid="true">
|
||||||
|
<SelectValue placeholder="Error state" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,11 @@
|
|||||||
import { BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react"
|
import {
|
||||||
|
BoldIcon,
|
||||||
|
BookmarkIcon,
|
||||||
|
HeartIcon,
|
||||||
|
ItalicIcon,
|
||||||
|
StarIcon,
|
||||||
|
UnderlineIcon,
|
||||||
|
} from "lucide-react"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ToggleGroup,
|
ToggleGroup,
|
||||||
@@ -8,7 +15,7 @@ import {
|
|||||||
export function ToggleGroupDemo() {
|
export function ToggleGroupDemo() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-start gap-4">
|
<div className="flex flex-wrap items-start gap-4">
|
||||||
<ToggleGroup type="multiple">
|
<ToggleGroup type="multiple" spacing={2}>
|
||||||
<ToggleGroupItem value="bold" aria-label="Toggle bold">
|
<ToggleGroupItem value="bold" aria-label="Toggle bold">
|
||||||
<BoldIcon />
|
<BoldIcon />
|
||||||
</ToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
@@ -54,12 +61,7 @@ export function ToggleGroupDemo() {
|
|||||||
</ToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
</ToggleGroup>
|
</ToggleGroup>
|
||||||
|
|
||||||
<ToggleGroup
|
<ToggleGroup type="single" size="sm" defaultValue="last-24-hours">
|
||||||
type="single"
|
|
||||||
size="sm"
|
|
||||||
defaultValue="last-24-hours"
|
|
||||||
className="*:data-[slot=toggle-group-item]:px-3"
|
|
||||||
>
|
|
||||||
<ToggleGroupItem
|
<ToggleGroupItem
|
||||||
value="last-24-hours"
|
value="last-24-hours"
|
||||||
aria-label="Toggle last 24 hours"
|
aria-label="Toggle last 24 hours"
|
||||||
@@ -70,6 +72,68 @@ export function ToggleGroupDemo() {
|
|||||||
Last 7 days
|
Last 7 days
|
||||||
</ToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
</ToggleGroup>
|
</ToggleGroup>
|
||||||
|
<ToggleGroup type="single" size="sm" defaultValue="top" variant="outline">
|
||||||
|
<ToggleGroupItem value="top" aria-label="Toggle top">
|
||||||
|
Top
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="bottom" aria-label="Toggle bottom">
|
||||||
|
Bottom
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="left" aria-label="Toggle left">
|
||||||
|
Left
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="right" aria-label="Toggle right">
|
||||||
|
Right
|
||||||
|
</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
|
||||||
|
<ToggleGroup
|
||||||
|
type="single"
|
||||||
|
size="sm"
|
||||||
|
defaultValue="top"
|
||||||
|
variant="outline"
|
||||||
|
spacing={2}
|
||||||
|
>
|
||||||
|
<ToggleGroupItem value="top" aria-label="Toggle top">
|
||||||
|
Top
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="bottom" aria-label="Toggle bottom">
|
||||||
|
Bottom
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="left" aria-label="Toggle left">
|
||||||
|
Left
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="right" aria-label="Toggle right">
|
||||||
|
Right
|
||||||
|
</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
|
||||||
|
<ToggleGroup type="multiple" variant="outline" spacing={2} size="sm">
|
||||||
|
<ToggleGroupItem
|
||||||
|
value="star"
|
||||||
|
aria-label="Toggle star"
|
||||||
|
className="data-[state=on]:bg-transparent data-[state=on]:*:[svg]:fill-yellow-500 data-[state=on]:*:[svg]:stroke-yellow-500"
|
||||||
|
>
|
||||||
|
<StarIcon />
|
||||||
|
Star
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem
|
||||||
|
value="heart"
|
||||||
|
aria-label="Toggle heart"
|
||||||
|
className="data-[state=on]:bg-transparent data-[state=on]:*:[svg]:fill-red-500 data-[state=on]:*:[svg]:stroke-red-500"
|
||||||
|
>
|
||||||
|
<HeartIcon />
|
||||||
|
Heart
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem
|
||||||
|
value="bookmark"
|
||||||
|
aria-label="Toggle bookmark"
|
||||||
|
className="data-[state=on]:bg-transparent data-[state=on]:*:[svg]:fill-blue-500 data-[state=on]:*:[svg]:stroke-blue-500"
|
||||||
|
>
|
||||||
|
<BookmarkIcon />
|
||||||
|
Bookmark
|
||||||
|
</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
105
apps/v4/app/(sandbox)/sandbox/[style]/page.tsx
Normal file
105
apps/v4/app/(sandbox)/sandbox/[style]/page.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { Metadata } from "next"
|
||||||
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
|
import { siteConfig } from "@/lib/config"
|
||||||
|
import { getRegistryComponent, getRegistryItems } from "@/lib/registry"
|
||||||
|
import { absoluteUrl, cn } from "@/lib/utils"
|
||||||
|
import { getStyle, STYLES } from "@/registry/styles"
|
||||||
|
|
||||||
|
export const revalidate = false
|
||||||
|
export const dynamic = "force-static"
|
||||||
|
export const dynamicParams = false
|
||||||
|
|
||||||
|
const allowedTypes = ["registry:example"]
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{
|
||||||
|
style: string
|
||||||
|
}>
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { style: styleName } = await params
|
||||||
|
const style = getStyle(styleName)
|
||||||
|
|
||||||
|
if (!style) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = style.title
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
openGraph: {
|
||||||
|
title,
|
||||||
|
type: "article",
|
||||||
|
url: absoluteUrl(`/sandbox/${style.name}`),
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: siteConfig.ogImage,
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
alt: siteConfig.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title,
|
||||||
|
images: [siteConfig.ogImage],
|
||||||
|
creator: "@shadcn",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
return STYLES.map((style) => ({
|
||||||
|
style: style.name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function BlockPage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{
|
||||||
|
style: string
|
||||||
|
}>
|
||||||
|
}) {
|
||||||
|
const { style: styleName } = await params
|
||||||
|
const style = getStyle(styleName)
|
||||||
|
|
||||||
|
if (!style) {
|
||||||
|
return notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = await getRegistryItems(style.name, (item) =>
|
||||||
|
allowedTypes.includes(item.type)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
return notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={cn("grid gap-6")}>
|
||||||
|
{items
|
||||||
|
.filter((item) => item !== null)
|
||||||
|
.map((item) => {
|
||||||
|
const Component = getRegistryComponent(item.name, style.name)
|
||||||
|
if (!Component) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.name}
|
||||||
|
className={cn("bg-background", item.meta?.container)}
|
||||||
|
>
|
||||||
|
<Component />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,30 +1,39 @@
|
|||||||
|
/* eslint-disable react-hooks/static-components */
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Metadata } from "next"
|
import { Metadata } from "next"
|
||||||
import { notFound } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
import { registryItemSchema } from "shadcn/schema"
|
|
||||||
import { z } from "zod"
|
|
||||||
|
|
||||||
import { siteConfig } from "@/lib/config"
|
import { siteConfig } from "@/lib/config"
|
||||||
import { getRegistryComponent, getRegistryItem } from "@/lib/registry"
|
import { getRegistryComponent, getRegistryItem } from "@/lib/registry"
|
||||||
import { absoluteUrl, cn } from "@/lib/utils"
|
import { absoluteUrl, cn } from "@/lib/utils"
|
||||||
|
import { getStyle, STYLES, type Style } from "@/registry/styles"
|
||||||
|
|
||||||
export const revalidate = false
|
export const revalidate = false
|
||||||
export const dynamic = "force-static"
|
export const dynamic = "force-static"
|
||||||
export const dynamicParams = false
|
export const dynamicParams = false
|
||||||
|
|
||||||
const getCachedRegistryItem = React.cache(async (name: string) => {
|
const getCachedRegistryItem = React.cache(
|
||||||
return await getRegistryItem(name)
|
async (name: string, styleName: Style["name"]) => {
|
||||||
})
|
return await getRegistryItem(name, styleName)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
params: Promise<{
|
params: Promise<{
|
||||||
|
style: string
|
||||||
name: string
|
name: string
|
||||||
}>
|
}>
|
||||||
}): Promise<Metadata> {
|
}): Promise<Metadata> {
|
||||||
const { name } = await params
|
const { style: styleName, name } = await params
|
||||||
const item = await getCachedRegistryItem(name)
|
const style = getStyle(styleName)
|
||||||
|
|
||||||
|
if (!style) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = await getCachedRegistryItem(name, style.name)
|
||||||
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return {}
|
return {}
|
||||||
@@ -34,13 +43,13 @@ export async function generateMetadata({
|
|||||||
const description = item.description
|
const description = item.description
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: item.description,
|
title: item.name,
|
||||||
description,
|
description,
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
type: "article",
|
type: "article",
|
||||||
url: absoluteUrl(`/view/${item.name}`),
|
url: absoluteUrl(`/view/${style.name}/${item.name}`),
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: siteConfig.ogImage,
|
url: siteConfig.ogImage,
|
||||||
@@ -62,32 +71,52 @@ export async function generateMetadata({
|
|||||||
|
|
||||||
export async function generateStaticParams() {
|
export async function generateStaticParams() {
|
||||||
const { Index } = await import("@/registry/__index__")
|
const { Index } = await import("@/registry/__index__")
|
||||||
const index = z.record(registryItemSchema).parse(Index)
|
const params: Array<{ style: string; name: string }> = []
|
||||||
|
|
||||||
return Object.values(index)
|
for (const style of STYLES) {
|
||||||
.filter((block) =>
|
if (!Index[style.name]) {
|
||||||
[
|
continue
|
||||||
"registry:block",
|
}
|
||||||
"registry:component",
|
|
||||||
"registry:example",
|
const styleIndex = Index[style.name]
|
||||||
"registry:internal",
|
for (const itemName in styleIndex) {
|
||||||
].includes(block.type)
|
const item = styleIndex[itemName]
|
||||||
)
|
if (
|
||||||
.map((block) => ({
|
[
|
||||||
name: block.name,
|
"registry:block",
|
||||||
}))
|
"registry:component",
|
||||||
|
"registry:example",
|
||||||
|
"registry:internal",
|
||||||
|
].includes(item.type)
|
||||||
|
) {
|
||||||
|
params.push({
|
||||||
|
style: style.name,
|
||||||
|
name: item.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function BlockPage({
|
export default async function BlockPage({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
params: Promise<{
|
params: Promise<{
|
||||||
|
style: string
|
||||||
name: string
|
name: string
|
||||||
}>
|
}>
|
||||||
}) {
|
}) {
|
||||||
const { name } = await params
|
const { style: styleName, name } = await params
|
||||||
const item = await getCachedRegistryItem(name)
|
const style = getStyle(styleName)
|
||||||
const Component = getRegistryComponent(name)
|
|
||||||
|
if (!style) {
|
||||||
|
return notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = await getCachedRegistryItem(name, style.name)
|
||||||
|
const Component = getRegistryComponent(name, style.name)
|
||||||
|
|
||||||
if (!item || !Component) {
|
if (!item || !Component) {
|
||||||
return notFound()
|
return notFound()
|
||||||
5
apps/v4/app/api/search/route.ts
Normal file
5
apps/v4/app/api/search/route.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { createFromSource } from "fumadocs-core/search/server"
|
||||||
|
|
||||||
|
import { source } from "@/lib/source"
|
||||||
|
|
||||||
|
export const { GET } = createFromSource(source)
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Metadata } from "next"
|
import type { Metadata } from "next"
|
||||||
|
import { NuqsAdapter } from "nuqs/adapters/next/app"
|
||||||
|
|
||||||
import { META_THEME_COLORS, siteConfig } from "@/lib/config"
|
import { META_THEME_COLORS, siteConfig } from "@/lib/config"
|
||||||
import { fontVariables } from "@/lib/fonts"
|
import { fontVariables } from "@/lib/fonts"
|
||||||
@@ -84,18 +85,20 @@ export default function RootLayout({
|
|||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-foreground group/body theme-blue overscroll-none font-sans antialiased [--footer-height:calc(var(--spacing)*14)] [--header-height:calc(var(--spacing)*14)] xl:[--footer-height:calc(var(--spacing)*24)]",
|
"group/body overscroll-none antialiased [--footer-height:calc(var(--spacing)*14)] [--header-height:calc(var(--spacing)*14)] xl:[--footer-height:calc(var(--spacing)*24)]",
|
||||||
fontVariables
|
fontVariables
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<LayoutProvider>
|
<LayoutProvider>
|
||||||
<ActiveThemeProvider initialTheme="blue">
|
<NuqsAdapter>
|
||||||
{children}
|
<ActiveThemeProvider>
|
||||||
<TailwindIndicator />
|
{children}
|
||||||
<Toaster position="top-center" />
|
<TailwindIndicator />
|
||||||
<Analytics />
|
<Toaster position="top-center" />
|
||||||
</ActiveThemeProvider>
|
<Analytics />
|
||||||
|
</ActiveThemeProvider>
|
||||||
|
</NuqsAdapter>
|
||||||
</LayoutProvider>
|
</LayoutProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from "react"
|
} from "react"
|
||||||
|
|
||||||
const DEFAULT_THEME = "blue"
|
const DEFAULT_THEME = "default"
|
||||||
|
|
||||||
type ThemeContextType = {
|
type ThemeContextType = {
|
||||||
activeTheme: string
|
activeTheme: string
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Badge } from "@/registry/new-york-v4/ui/badge"
|
|||||||
|
|
||||||
export function Announcement() {
|
export function Announcement() {
|
||||||
return (
|
return (
|
||||||
<Badge asChild variant="secondary" className="rounded-full">
|
<Badge asChild variant="secondary" className="bg-transparent">
|
||||||
<Link href="/docs/changelog">
|
<Link href="/docs/changelog">
|
||||||
<span className="flex size-2 rounded-full bg-blue-500" title="New" />
|
<span className="flex size-2 rounded-full bg-blue-500" title="New" />
|
||||||
New Components: Field, Input Group, Item and more <ArrowRightIcon />
|
New Components: Field, Input Group, Item and more <ArrowRightIcon />
|
||||||
|
|||||||
@@ -10,9 +10,16 @@ import {
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { BlockViewer } from "@/components/block-viewer"
|
import { BlockViewer } from "@/components/block-viewer"
|
||||||
import { ComponentPreview } from "@/components/component-preview"
|
import { ComponentPreview } from "@/components/component-preview"
|
||||||
|
import { type Style } from "@/registry/styles"
|
||||||
|
|
||||||
export async function BlockDisplay({ name }: { name: string }) {
|
export async function BlockDisplay({
|
||||||
const item = await getCachedRegistryItem(name)
|
name,
|
||||||
|
styleName,
|
||||||
|
}: {
|
||||||
|
name: string
|
||||||
|
styleName: Style["name"]
|
||||||
|
}) {
|
||||||
|
const item = await getCachedRegistryItem(name, styleName)
|
||||||
|
|
||||||
if (!item?.files) {
|
if (!item?.files) {
|
||||||
return null
|
return null
|
||||||
@@ -24,9 +31,15 @@ export async function BlockDisplay({ name }: { name: string }) {
|
|||||||
])
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BlockViewer item={item} tree={tree} highlightedFiles={highlightedFiles}>
|
<BlockViewer
|
||||||
|
item={item}
|
||||||
|
tree={tree}
|
||||||
|
highlightedFiles={highlightedFiles}
|
||||||
|
styleName={styleName}
|
||||||
|
>
|
||||||
<ComponentPreview
|
<ComponentPreview
|
||||||
name={item.name}
|
name={item.name}
|
||||||
|
styleName={styleName}
|
||||||
hideCode
|
hideCode
|
||||||
className={cn(
|
className={cn(
|
||||||
"my-0 **:[.preview]:h-auto **:[.preview]:p-4 **:[.preview>.p-6]:p-0",
|
"my-0 **:[.preview]:h-auto **:[.preview]:p-4 **:[.preview>.p-6]:p-0",
|
||||||
@@ -37,9 +50,11 @@ export async function BlockDisplay({ name }: { name: string }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCachedRegistryItem = React.cache(async (name: string) => {
|
const getCachedRegistryItem = React.cache(
|
||||||
return await getRegistryItem(name)
|
async (name: string, styleName: Style["name"]) => {
|
||||||
})
|
return await getRegistryItem(name, styleName)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const getCachedFileTree = React.cache(
|
const getCachedFileTree = React.cache(
|
||||||
async (files: Array<{ path: string; target?: string }>) => {
|
async (files: Array<{ path: string; target?: string }>) => {
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import {
|
|||||||
ToggleGroup,
|
ToggleGroup,
|
||||||
ToggleGroupItem,
|
ToggleGroupItem,
|
||||||
} from "@/registry/new-york-v4/ui/toggle-group"
|
} from "@/registry/new-york-v4/ui/toggle-group"
|
||||||
|
import { type Style } from "@/registry/styles"
|
||||||
|
|
||||||
type BlockViewerContext = {
|
type BlockViewerContext = {
|
||||||
item: z.infer<typeof registryItemSchema>
|
item: z.infer<typeof registryItemSchema>
|
||||||
@@ -128,7 +129,15 @@ function BlockViewerProvider({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function BlockViewerToolbar() {
|
type BlockViewerProps = Pick<
|
||||||
|
BlockViewerContext,
|
||||||
|
"item" | "tree" | "highlightedFiles"
|
||||||
|
> & {
|
||||||
|
children: React.ReactNode
|
||||||
|
styleName: Style["name"]
|
||||||
|
}
|
||||||
|
|
||||||
|
function BlockViewerToolbar({ styleName }: { styleName: Style["name"] }) {
|
||||||
const { setView, view, item, resizablePanelRef, setIframeKey } =
|
const { setView, view, item, resizablePanelRef, setIframeKey } =
|
||||||
useBlockViewer()
|
useBlockViewer()
|
||||||
const { copyToClipboard, isCopied } = useCopyToClipboard()
|
const { copyToClipboard, isCopied } = useCopyToClipboard()
|
||||||
@@ -181,7 +190,7 @@ function BlockViewerToolbar() {
|
|||||||
asChild
|
asChild
|
||||||
title="Open in New Tab"
|
title="Open in New Tab"
|
||||||
>
|
>
|
||||||
<Link href={`/view/${item.name}`} target="_blank">
|
<Link href={`/view/${styleName}/${item.name}`} target="_blank">
|
||||||
<span className="sr-only">Open in New Tab</span>
|
<span className="sr-only">Open in New Tab</span>
|
||||||
<Fullscreen />
|
<Fullscreen />
|
||||||
</Link>
|
</Link>
|
||||||
@@ -222,13 +231,19 @@ function BlockViewerToolbar() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function BlockViewerIframe({ className }: { className?: string }) {
|
function BlockViewerIframe({
|
||||||
|
className,
|
||||||
|
styleName,
|
||||||
|
}: {
|
||||||
|
className?: string
|
||||||
|
styleName: Style["name"]
|
||||||
|
}) {
|
||||||
const { item, iframeKey } = useBlockViewer()
|
const { item, iframeKey } = useBlockViewer()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<iframe
|
<iframe
|
||||||
key={iframeKey}
|
key={iframeKey}
|
||||||
src={`/view/${item.name}`}
|
src={`/view/${styleName}/${item.name}`}
|
||||||
height={item.meta?.iframeHeight ?? 930}
|
height={item.meta?.iframeHeight ?? 930}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -239,7 +254,7 @@ function BlockViewerIframe({ className }: { className?: string }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function BlockViewerView() {
|
function BlockViewerView({ styleName }: { styleName: Style["name"] }) {
|
||||||
const { resizablePanelRef } = useBlockViewer()
|
const { resizablePanelRef } = useBlockViewer()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -256,7 +271,7 @@ function BlockViewerView() {
|
|||||||
defaultSize={100}
|
defaultSize={100}
|
||||||
minSize={30}
|
minSize={30}
|
||||||
>
|
>
|
||||||
<BlockViewerIframe />
|
<BlockViewerIframe styleName={styleName} />
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
<ResizableHandle className="after:bg-border relative hidden w-3 bg-transparent p-0 after:absolute after:top-1/2 after:right-0 after:h-8 after:w-[6px] after:translate-x-[-1px] after:-translate-y-1/2 after:rounded-full after:transition-all after:hover:h-10 md:block" />
|
<ResizableHandle className="after:bg-border relative hidden w-3 bg-transparent p-0 after:absolute after:top-1/2 after:right-0 after:h-8 after:w-[6px] after:translate-x-[-1px] after:-translate-y-1/2 after:rounded-full after:transition-all after:hover:h-10 md:block" />
|
||||||
<ResizablePanel defaultSize={0} minSize={0} />
|
<ResizablePanel defaultSize={0} minSize={0} />
|
||||||
@@ -471,10 +486,9 @@ function BlockViewer({
|
|||||||
tree,
|
tree,
|
||||||
highlightedFiles,
|
highlightedFiles,
|
||||||
children,
|
children,
|
||||||
|
styleName,
|
||||||
...props
|
...props
|
||||||
}: Pick<BlockViewerContext, "item" | "tree" | "highlightedFiles"> & {
|
}: BlockViewerProps) {
|
||||||
children: React.ReactNode
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<BlockViewerProvider
|
<BlockViewerProvider
|
||||||
item={item}
|
item={item}
|
||||||
@@ -482,8 +496,8 @@ function BlockViewer({
|
|||||||
highlightedFiles={highlightedFiles}
|
highlightedFiles={highlightedFiles}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<BlockViewerToolbar />
|
<BlockViewerToolbar styleName={styleName} />
|
||||||
<BlockViewerView />
|
<BlockViewerView styleName={styleName} />
|
||||||
<BlockViewerCode />
|
<BlockViewerCode />
|
||||||
<BlockViewerMobile>{children}</BlockViewerMobile>
|
<BlockViewerMobile>{children}</BlockViewerMobile>
|
||||||
</BlockViewerProvider>
|
</BlockViewerProvider>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
|
|
||||||
|
import { registryCategories } from "@/lib/categories"
|
||||||
import { ScrollArea, ScrollBar } from "@/registry/new-york-v4/ui/scroll-area"
|
import { ScrollArea, ScrollBar } from "@/registry/new-york-v4/ui/scroll-area"
|
||||||
import { registryCategories } from "@/registry/registry-categories"
|
|
||||||
|
|
||||||
export function BlocksNav() {
|
export function BlocksNav() {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { CheckIcon, ClipboardIcon } from "lucide-react"
|
import { IconCheck, IconCopy } from "@tabler/icons-react"
|
||||||
|
|
||||||
import { Event, trackEvent } from "@/lib/events"
|
import { Event, trackEvent } from "@/lib/events"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
@@ -54,7 +54,7 @@ export function ChartCopyButton({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Copy</span>
|
<span className="sr-only">Copy</span>
|
||||||
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
|
{hasCopied ? <IconCheck /> : <IconCopy />}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent className="bg-black text-white">Copy code</TooltipContent>
|
<TooltipContent className="bg-black text-white">Copy code</TooltipContent>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { highlightCode } from "@/lib/highlight-code"
|
|||||||
import { getRegistryItem } from "@/lib/registry"
|
import { getRegistryItem } from "@/lib/registry"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { ChartToolbar } from "@/components/chart-toolbar"
|
import { ChartToolbar } from "@/components/chart-toolbar"
|
||||||
|
import { type Style } from "@/registry/styles"
|
||||||
|
|
||||||
export type Chart = z.infer<typeof registryItemSchema> & {
|
export type Chart = z.infer<typeof registryItemSchema> & {
|
||||||
highlightedCode: string
|
highlightedCode: string
|
||||||
@@ -13,10 +14,14 @@ export type Chart = z.infer<typeof registryItemSchema> & {
|
|||||||
|
|
||||||
export async function ChartDisplay({
|
export async function ChartDisplay({
|
||||||
name,
|
name,
|
||||||
|
styleName,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
}: { name: string } & React.ComponentProps<"div">) {
|
}: {
|
||||||
const chart = await getCachedRegistryItem(name)
|
name: string
|
||||||
|
styleName: Style["name"]
|
||||||
|
} & React.ComponentProps<"div">) {
|
||||||
|
const chart = await getCachedRegistryItem(name, styleName)
|
||||||
const highlightedCode = await getChartHighlightedCode(
|
const highlightedCode = await getChartHighlightedCode(
|
||||||
chart?.files?.[0]?.content ?? ""
|
chart?.files?.[0]?.content ?? ""
|
||||||
)
|
)
|
||||||
@@ -45,9 +50,11 @@ export async function ChartDisplay({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCachedRegistryItem = React.cache(async (name: string) => {
|
const getCachedRegistryItem = React.cache(
|
||||||
return await getRegistryItem(name)
|
async (name: string, styleName: Style["name"]) => {
|
||||||
})
|
return await getRegistryItem(name, styleName)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const getChartHighlightedCode = React.cache(async (content: string) => {
|
const getChartHighlightedCode = React.cache(async (content: string) => {
|
||||||
return await highlightCode(content)
|
return await highlightCode(content)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { CheckIcon, ClipboardIcon, TerminalIcon } from "lucide-react"
|
import { IconCheck, IconCopy, IconTerminal } from "@tabler/icons-react"
|
||||||
|
|
||||||
import { useConfig } from "@/hooks/use-config"
|
import { useConfig } from "@/hooks/use-config"
|
||||||
import { copyToClipboardWithMeta } from "@/components/copy-button"
|
import { copyToClipboardWithMeta } from "@/components/copy-button"
|
||||||
@@ -80,7 +80,7 @@ export function CodeBlockCommand({
|
|||||||
>
|
>
|
||||||
<div className="border-border/50 flex items-center gap-2 border-b px-3 py-1">
|
<div className="border-border/50 flex items-center gap-2 border-b px-3 py-1">
|
||||||
<div className="bg-foreground flex size-4 items-center justify-center rounded-[1px] opacity-70">
|
<div className="bg-foreground flex size-4 items-center justify-center rounded-[1px] opacity-70">
|
||||||
<TerminalIcon className="text-code size-3" />
|
<IconTerminal className="text-code size-3" />
|
||||||
</div>
|
</div>
|
||||||
<TabsList className="rounded-none bg-transparent p-0">
|
<TabsList className="rounded-none bg-transparent p-0">
|
||||||
{Object.entries(tabs).map(([key]) => {
|
{Object.entries(tabs).map(([key]) => {
|
||||||
@@ -123,7 +123,7 @@ export function CodeBlockCommand({
|
|||||||
onClick={copyCommand}
|
onClick={copyCommand}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Copy</span>
|
<span className="sr-only">Copy</span>
|
||||||
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
|
{hasCopied ? <IconCheck /> : <IconCopy />}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ import * as React from "react"
|
|||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { type DialogProps } from "@radix-ui/react-dialog"
|
import { type DialogProps } from "@radix-ui/react-dialog"
|
||||||
import { IconArrowRight } from "@tabler/icons-react"
|
import { IconArrowRight } from "@tabler/icons-react"
|
||||||
|
import { useDocsSearch } from "fumadocs-core/search/client"
|
||||||
import { CornerDownLeftIcon, SquareDashedIcon } from "lucide-react"
|
import { CornerDownLeftIcon, SquareDashedIcon } from "lucide-react"
|
||||||
|
|
||||||
import { type Color, type ColorPalette } from "@/lib/colors"
|
import { type Color, type ColorPalette } from "@/lib/colors"
|
||||||
|
import { trackEvent } from "@/lib/events"
|
||||||
import { showMcpDocs } from "@/lib/flags"
|
import { showMcpDocs } from "@/lib/flags"
|
||||||
import { source } from "@/lib/source"
|
import { source } from "@/lib/source"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { useConfig } from "@/hooks/use-config"
|
import { useConfig } from "@/hooks/use-config"
|
||||||
import { useIsMac } from "@/hooks/use-is-mac"
|
|
||||||
import { useMutationObserver } from "@/hooks/use-mutation-observer"
|
import { useMutationObserver } from "@/hooks/use-mutation-observer"
|
||||||
import { copyToClipboardWithMeta } from "@/components/copy-button"
|
import { copyToClipboardWithMeta } from "@/components/copy-button"
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
@@ -33,6 +34,7 @@ import {
|
|||||||
} from "@/registry/new-york-v4/ui/dialog"
|
} from "@/registry/new-york-v4/ui/dialog"
|
||||||
import { Kbd, KbdGroup } from "@/registry/new-york-v4/ui/kbd"
|
import { Kbd, KbdGroup } from "@/registry/new-york-v4/ui/kbd"
|
||||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||||
|
import { Spinner } from "@/registry/new-york-v4/ui/spinner"
|
||||||
|
|
||||||
export function CommandMenu({
|
export function CommandMenu({
|
||||||
tree,
|
tree,
|
||||||
@@ -47,15 +49,63 @@ export function CommandMenu({
|
|||||||
navItems?: { href: string; label: string }[]
|
navItems?: { href: string; label: string }[]
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const isMac = useIsMac()
|
|
||||||
const [config] = useConfig()
|
const [config] = useConfig()
|
||||||
const [open, setOpen] = React.useState(false)
|
const [open, setOpen] = React.useState(false)
|
||||||
const [selectedType, setSelectedType] = React.useState<
|
const [selectedType, setSelectedType] = React.useState<
|
||||||
"color" | "page" | "component" | "block" | null
|
"color" | "page" | "component" | "block" | null
|
||||||
>(null)
|
>(null)
|
||||||
const [copyPayload, setCopyPayload] = React.useState("")
|
const [copyPayload, setCopyPayload] = React.useState("")
|
||||||
|
|
||||||
|
const { search, setSearch, query } = useDocsSearch({
|
||||||
|
type: "fetch",
|
||||||
|
})
|
||||||
const packageManager = config.packageManager || "pnpm"
|
const packageManager = config.packageManager || "pnpm"
|
||||||
|
|
||||||
|
// Track search queries with debouncing to avoid excessive tracking.
|
||||||
|
const searchTimeoutRef = React.useRef<NodeJS.Timeout | undefined>(undefined)
|
||||||
|
const lastTrackedQueryRef = React.useRef<string>("")
|
||||||
|
|
||||||
|
const trackSearchQuery = React.useCallback((query: string) => {
|
||||||
|
const trimmedQuery = query.trim()
|
||||||
|
|
||||||
|
// Only track if the query is different from the last tracked query and has content.
|
||||||
|
if (trimmedQuery && trimmedQuery !== lastTrackedQueryRef.current) {
|
||||||
|
lastTrackedQueryRef.current = trimmedQuery
|
||||||
|
trackEvent({
|
||||||
|
name: "search_query",
|
||||||
|
properties: {
|
||||||
|
query: trimmedQuery,
|
||||||
|
query_length: trimmedQuery.length,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleSearchChange = React.useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
// Clear existing timeout.
|
||||||
|
if (searchTimeoutRef.current) {
|
||||||
|
clearTimeout(searchTimeoutRef.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set new timeout to debounce both search and tracking.
|
||||||
|
searchTimeoutRef.current = setTimeout(() => {
|
||||||
|
setSearch(value)
|
||||||
|
trackSearchQuery(value)
|
||||||
|
}, 500)
|
||||||
|
},
|
||||||
|
[setSearch, trackSearchQuery]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cleanup timeout on unmount.
|
||||||
|
React.useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (searchTimeoutRef.current) {
|
||||||
|
clearTimeout(searchTimeoutRef.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handlePageHighlight = React.useCallback(
|
const handlePageHighlight = React.useCallback(
|
||||||
(isComponent: boolean, item: { url: string; name?: React.ReactNode }) => {
|
(isComponent: boolean, item: { url: string; name?: React.ReactNode }) => {
|
||||||
if (isComponent) {
|
if (isComponent) {
|
||||||
@@ -154,7 +204,7 @@ export function CommandMenu({
|
|||||||
<span className="inline-flex lg:hidden">Search...</span>
|
<span className="inline-flex lg:hidden">Search...</span>
|
||||||
<div className="absolute top-1.5 right-1.5 hidden gap-1 sm:flex">
|
<div className="absolute top-1.5 right-1.5 hidden gap-1 sm:flex">
|
||||||
<KbdGroup>
|
<KbdGroup>
|
||||||
<Kbd className="border">{isMac ? "⌘" : "Ctrl"}</Kbd>
|
<Kbd className="border">⌘</Kbd>
|
||||||
<Kbd className="border">K</Kbd>
|
<Kbd className="border">K</Kbd>
|
||||||
</KbdGroup>
|
</KbdGroup>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,6 +221,7 @@ export function CommandMenu({
|
|||||||
<Command
|
<Command
|
||||||
className="**:data-[slot=command-input-wrapper]:bg-input/50 **:data-[slot=command-input-wrapper]:border-input rounded-none bg-transparent **:data-[slot=command-input]:!h-9 **:data-[slot=command-input]:py-0 **:data-[slot=command-input-wrapper]:mb-0 **:data-[slot=command-input-wrapper]:!h-9 **:data-[slot=command-input-wrapper]:rounded-md **:data-[slot=command-input-wrapper]:border"
|
className="**:data-[slot=command-input-wrapper]:bg-input/50 **:data-[slot=command-input-wrapper]:border-input rounded-none bg-transparent **:data-[slot=command-input]:!h-9 **:data-[slot=command-input]:py-0 **:data-[slot=command-input-wrapper]:mb-0 **:data-[slot=command-input-wrapper]:!h-9 **:data-[slot=command-input-wrapper]:rounded-md **:data-[slot=command-input-wrapper]:border"
|
||||||
filter={(value, search, keywords) => {
|
filter={(value, search, keywords) => {
|
||||||
|
handleSearchChange(search)
|
||||||
const extendValue = value + " " + (keywords?.join(" ") || "")
|
const extendValue = value + " " + (keywords?.join(" ") || "")
|
||||||
if (extendValue.toLowerCase().includes(search.toLowerCase())) {
|
if (extendValue.toLowerCase().includes(search.toLowerCase())) {
|
||||||
return 1
|
return 1
|
||||||
@@ -178,10 +229,17 @@ export function CommandMenu({
|
|||||||
return 0
|
return 0
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CommandInput placeholder="Search documentation..." />
|
<div className="relative">
|
||||||
|
<CommandInput placeholder="Search documentation..." />
|
||||||
|
{query.isLoading && (
|
||||||
|
<div className="pointer-events-none absolute top-1/2 right-3 z-10 flex -translate-y-1/2 items-center justify-center">
|
||||||
|
<Spinner className="text-muted-foreground size-4" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<CommandList className="no-scrollbar min-h-80 scroll-pt-2 scroll-pb-1.5">
|
<CommandList className="no-scrollbar min-h-80 scroll-pt-2 scroll-pb-1.5">
|
||||||
<CommandEmpty className="text-muted-foreground py-12 text-center text-sm">
|
<CommandEmpty className="text-muted-foreground py-12 text-center text-sm">
|
||||||
No results found.
|
{query.isLoading ? "Searching..." : "No results found."}
|
||||||
</CommandEmpty>
|
</CommandEmpty>
|
||||||
{navItems && navItems.length > 0 && (
|
{navItems && navItems.length > 0 && (
|
||||||
<CommandGroup
|
<CommandGroup
|
||||||
@@ -322,6 +380,12 @@ export function CommandMenu({
|
|||||||
))}
|
))}
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
) : null}
|
) : null}
|
||||||
|
<SearchResults
|
||||||
|
open={open}
|
||||||
|
setOpen={setOpen}
|
||||||
|
query={query}
|
||||||
|
search={search}
|
||||||
|
/>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</Command>
|
</Command>
|
||||||
<div className="text-muted-foreground absolute inset-x-0 bottom-0 z-20 flex h-10 items-center gap-2 rounded-b-xl border-t border-t-neutral-100 bg-neutral-50 px-4 text-xs font-medium dark:border-t-neutral-700 dark:bg-neutral-800">
|
<div className="text-muted-foreground absolute inset-x-0 bottom-0 z-20 flex h-10 items-center gap-2 rounded-b-xl border-t border-t-neutral-100 bg-neutral-50 px-4 text-xs font-medium dark:border-t-neutral-700 dark:bg-neutral-800">
|
||||||
@@ -338,7 +402,7 @@ export function CommandMenu({
|
|||||||
<>
|
<>
|
||||||
<Separator orientation="vertical" className="!h-4" />
|
<Separator orientation="vertical" className="!h-4" />
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<CommandMenuKbd>{isMac ? "⌘" : "Ctrl"}</CommandMenuKbd>
|
<CommandMenuKbd>⌘</CommandMenuKbd>
|
||||||
<CommandMenuKbd>C</CommandMenuKbd>
|
<CommandMenuKbd>C</CommandMenuKbd>
|
||||||
{copyPayload}
|
{copyPayload}
|
||||||
</div>
|
</div>
|
||||||
@@ -399,3 +463,66 @@ function CommandMenuKbd({ className, ...props }: React.ComponentProps<"kbd">) {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Query = Awaited<ReturnType<typeof useDocsSearch>>["query"]
|
||||||
|
|
||||||
|
function SearchResults({
|
||||||
|
setOpen,
|
||||||
|
query,
|
||||||
|
search,
|
||||||
|
}: {
|
||||||
|
open: boolean
|
||||||
|
setOpen: (open: boolean) => void
|
||||||
|
query: Query
|
||||||
|
search: string
|
||||||
|
}) {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const uniqueResults =
|
||||||
|
query.data && Array.isArray(query.data)
|
||||||
|
? query.data.filter(
|
||||||
|
(item, index, self) =>
|
||||||
|
!(
|
||||||
|
item.type === "text" &&
|
||||||
|
item.content.trim().split(/\s+/).length <= 1
|
||||||
|
) && index === self.findIndex((t) => t.content === item.content)
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
|
||||||
|
if (!search.trim()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query.data || query.data === "empty") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.data && uniqueResults.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommandGroup
|
||||||
|
className="!px-0 [&_[cmdk-group-heading]]:scroll-mt-16 [&_[cmdk-group-heading]]:!p-3 [&_[cmdk-group-heading]]:!pb-1"
|
||||||
|
heading="Search Results"
|
||||||
|
>
|
||||||
|
{uniqueResults.map((item) => {
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
key={item.id}
|
||||||
|
data-type={item.type}
|
||||||
|
onSelect={() => {
|
||||||
|
router.push(item.url)
|
||||||
|
setOpen(false)
|
||||||
|
}}
|
||||||
|
className="data-[selected=true]:border-input data-[selected=true]:bg-input/50 h-9 rounded-md border border-transparent !px-3 font-normal"
|
||||||
|
keywords={[item.content]}
|
||||||
|
value={`${item.content} ${item.type}`}
|
||||||
|
>
|
||||||
|
<div className="line-clamp-1 text-sm">{item.content}</div>
|
||||||
|
</CommandItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</CommandGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/registry/new-york-v4/ui/tabs"
|
|
||||||
|
|
||||||
export function ComponentPreviewTabs({
|
export function ComponentPreviewTabs({
|
||||||
className,
|
className,
|
||||||
@@ -20,64 +19,32 @@ export function ComponentPreviewTabs({
|
|||||||
component: React.ReactNode
|
component: React.ReactNode
|
||||||
source: React.ReactNode
|
source: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const [tab, setTab] = React.useState("preview")
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("group relative mt-4 mb-12 flex flex-col gap-2", className)}
|
className={cn(
|
||||||
|
"group relative mt-4 mb-12 flex flex-col gap-2 rounded-lg border",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<Tabs
|
<div data-slot="preview">
|
||||||
className="relative mr-auto w-full"
|
<div
|
||||||
value={tab}
|
data-align={align}
|
||||||
onValueChange={setTab}
|
className={cn(
|
||||||
>
|
"preview flex w-full justify-center data-[align=center]:items-center data-[align=end]:items-end data-[align=start]:items-start",
|
||||||
<div className="flex items-center justify-between">
|
chromeLessOnMobile ? "sm:p-10" : "h-[450px] p-10"
|
||||||
{!hideCode && (
|
|
||||||
<TabsList className="justify-start gap-4 rounded-none bg-transparent px-2 md:px-0">
|
|
||||||
<TabsTrigger
|
|
||||||
value="preview"
|
|
||||||
className="text-muted-foreground data-[state=active]:text-foreground px-0 text-base data-[state=active]:shadow-none dark:data-[state=active]:border-transparent dark:data-[state=active]:bg-transparent"
|
|
||||||
>
|
|
||||||
Preview
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger
|
|
||||||
value="code"
|
|
||||||
className="text-muted-foreground data-[state=active]:text-foreground px-0 text-base data-[state=active]:shadow-none dark:data-[state=active]:border-transparent dark:data-[state=active]:bg-transparent"
|
|
||||||
>
|
|
||||||
Code
|
|
||||||
</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</Tabs>
|
|
||||||
<div
|
|
||||||
data-tab={tab}
|
|
||||||
data-chrome-less-on-mobile={chromeLessOnMobile}
|
|
||||||
className="data-[tab=code]:border-code relative rounded-lg border data-[chrome-less-on-mobile=true]:border-0 sm:data-[chrome-less-on-mobile=true]:border md:-mx-1"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-slot="preview"
|
|
||||||
data-active={tab === "preview"}
|
|
||||||
className="invisible data-[active=true]:visible"
|
|
||||||
>
|
>
|
||||||
|
{component}
|
||||||
|
</div>
|
||||||
|
{!hideCode && (
|
||||||
<div
|
<div
|
||||||
data-align={align}
|
data-slot="code"
|
||||||
className={cn(
|
className="overflow-hidden [&_[data-rehype-pretty-code-figure]]:!m-0 [&_[data-rehype-pretty-code-figure]]:rounded-t-none [&_[data-rehype-pretty-code-figure]]:border-t [&_pre]:max-h-[400px]"
|
||||||
"preview flex w-full justify-center data-[align=center]:items-center data-[align=end]:items-end data-[align=start]:items-start",
|
|
||||||
chromeLessOnMobile ? "sm:p-10" : "h-[450px] p-10"
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{component}
|
{source}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
<div
|
|
||||||
data-slot="code"
|
|
||||||
data-active={tab === "code"}
|
|
||||||
className="absolute inset-0 hidden overflow-hidden data-[active=true]:block **:[figure]:!m-0 **:[pre]:h-[450px]"
|
|
||||||
>
|
|
||||||
{source}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import Image from "next/image"
|
|||||||
import { ComponentPreviewTabs } from "@/components/component-preview-tabs"
|
import { ComponentPreviewTabs } from "@/components/component-preview-tabs"
|
||||||
import { ComponentSource } from "@/components/component-source"
|
import { ComponentSource } from "@/components/component-source"
|
||||||
import { Index } from "@/registry/__index__"
|
import { Index } from "@/registry/__index__"
|
||||||
|
import { type Style } from "@/registry/styles"
|
||||||
|
|
||||||
export function ComponentPreview({
|
export function ComponentPreview({
|
||||||
name,
|
name,
|
||||||
|
styleName = "new-york-v4",
|
||||||
type,
|
type,
|
||||||
className,
|
className,
|
||||||
align = "center",
|
align = "center",
|
||||||
@@ -14,13 +16,14 @@ export function ComponentPreview({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
name: string
|
name: string
|
||||||
|
styleName?: Style["name"]
|
||||||
align?: "center" | "start" | "end"
|
align?: "center" | "start" | "end"
|
||||||
description?: string
|
description?: string
|
||||||
hideCode?: boolean
|
hideCode?: boolean
|
||||||
type?: "block" | "component" | "example"
|
type?: "block" | "component" | "example"
|
||||||
chromeLessOnMobile?: boolean
|
chromeLessOnMobile?: boolean
|
||||||
}) {
|
}) {
|
||||||
const Component = Index[name]?.component
|
const Component = Index[styleName]?.[name]?.component
|
||||||
|
|
||||||
if (!Component) {
|
if (!Component) {
|
||||||
return (
|
return (
|
||||||
@@ -52,7 +55,7 @@ export function ComponentPreview({
|
|||||||
className="bg-background absolute top-0 left-0 z-20 hidden w-[970px] max-w-none sm:w-[1280px] md:hidden dark:block md:dark:hidden"
|
className="bg-background absolute top-0 left-0 z-20 hidden w-[970px] max-w-none sm:w-[1280px] md:hidden dark:block md:dark:hidden"
|
||||||
/>
|
/>
|
||||||
<div className="bg-background absolute inset-0 hidden w-[1600px] md:block">
|
<div className="bg-background absolute inset-0 hidden w-[1600px] md:block">
|
||||||
<iframe src={`/view/${name}`} className="size-full" />
|
<iframe src={`/view/${styleName}/${name}`} className="size-full" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -64,7 +67,13 @@ export function ComponentPreview({
|
|||||||
align={align}
|
align={align}
|
||||||
hideCode={hideCode}
|
hideCode={hideCode}
|
||||||
component={<Component />}
|
component={<Component />}
|
||||||
source={<ComponentSource name={name} collapsible={false} />}
|
source={
|
||||||
|
<ComponentSource
|
||||||
|
name={name}
|
||||||
|
collapsible={false}
|
||||||
|
styleName={styleName}
|
||||||
|
/>
|
||||||
|
}
|
||||||
chromeLessOnMobile={chromeLessOnMobile}
|
chromeLessOnMobile={chromeLessOnMobile}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { cn } from "@/lib/utils"
|
|||||||
import { CodeCollapsibleWrapper } from "@/components/code-collapsible-wrapper"
|
import { CodeCollapsibleWrapper } from "@/components/code-collapsible-wrapper"
|
||||||
import { CopyButton } from "@/components/copy-button"
|
import { CopyButton } from "@/components/copy-button"
|
||||||
import { getIconForLanguageExtension } from "@/components/icons"
|
import { getIconForLanguageExtension } from "@/components/icons"
|
||||||
|
import { type Style } from "@/registry/styles"
|
||||||
|
|
||||||
export async function ComponentSource({
|
export async function ComponentSource({
|
||||||
name,
|
name,
|
||||||
@@ -16,12 +17,14 @@ export async function ComponentSource({
|
|||||||
language,
|
language,
|
||||||
collapsible = true,
|
collapsible = true,
|
||||||
className,
|
className,
|
||||||
|
styleName = "new-york-v4",
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
name?: string
|
name?: string
|
||||||
src?: string
|
src?: string
|
||||||
title?: string
|
title?: string
|
||||||
language?: string
|
language?: string
|
||||||
collapsible?: boolean
|
collapsible?: boolean
|
||||||
|
styleName?: Style["name"]
|
||||||
}) {
|
}) {
|
||||||
if (!name && !src) {
|
if (!name && !src) {
|
||||||
return null
|
return null
|
||||||
@@ -30,7 +33,7 @@ export async function ComponentSource({
|
|||||||
let code: string | undefined
|
let code: string | undefined
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
const item = await getRegistryItem(name)
|
const item = await getRegistryItem(name, styleName)
|
||||||
code = item?.files?.[0]?.content
|
code = item?.files?.[0]?.content
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,8 +47,8 @@ export async function ComponentSource({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fix imports.
|
// Fix imports.
|
||||||
// Replace @/registry/new-york-v4/ with @/components/.
|
// Replace @/registry/${style}/ with @/components/.
|
||||||
code = code.replaceAll("@/registry/new-york-v4/", "@/components/")
|
code = code.replaceAll(`@/registry/${styleName}/`, "@/components/")
|
||||||
|
|
||||||
// Replace export default with export.
|
// Replace export default with export.
|
||||||
code = code.replaceAll("export default", "export")
|
code = code.replaceAll("export default", "export")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { CheckIcon, ClipboardIcon } from "lucide-react"
|
import { IconCheck, IconCopy } from "@tabler/icons-react"
|
||||||
|
|
||||||
import { Event, trackEvent } from "@/lib/events"
|
import { Event, trackEvent } from "@/lib/events"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
@@ -24,11 +24,13 @@ export function CopyButton({
|
|||||||
className,
|
className,
|
||||||
variant = "ghost",
|
variant = "ghost",
|
||||||
event,
|
event,
|
||||||
|
tooltip = "Copy to Clipboard",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Button> & {
|
}: React.ComponentProps<typeof Button> & {
|
||||||
value: string
|
value: string
|
||||||
src?: string
|
src?: string
|
||||||
event?: Event["name"]
|
event?: Event["name"]
|
||||||
|
tooltip?: string
|
||||||
}) {
|
}) {
|
||||||
const [hasCopied, setHasCopied] = React.useState(false)
|
const [hasCopied, setHasCopied] = React.useState(false)
|
||||||
|
|
||||||
@@ -43,6 +45,7 @@ export function CopyButton({
|
|||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
data-slot="copy-button"
|
data-slot="copy-button"
|
||||||
|
data-copied={hasCopied}
|
||||||
size="icon"
|
size="icon"
|
||||||
variant={variant}
|
variant={variant}
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -66,12 +69,10 @@ export function CopyButton({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Copy</span>
|
<span className="sr-only">Copy</span>
|
||||||
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
|
{hasCopied ? <IconCheck /> : <IconCopy />}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>{hasCopied ? "Copied" : tooltip}</TooltipContent>
|
||||||
{hasCopied ? "Copied" : "Copy to Clipboard"}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
157
apps/v4/components/directory-add-button.tsx
Normal file
157
apps/v4/components/directory-add-button.tsx
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import Link from "next/link"
|
||||||
|
import { IconCheck } from "@tabler/icons-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
||||||
|
import { useIsMobile } from "@/hooks/use-mobile"
|
||||||
|
import { CopyButton } from "@/components/copy-button"
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/dialog"
|
||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
DrawerClose,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerDescription,
|
||||||
|
DrawerFooter,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerTitle,
|
||||||
|
DrawerTrigger,
|
||||||
|
} from "@/registry/new-york-v4/ui/drawer"
|
||||||
|
|
||||||
|
export function DirectoryAddButton({
|
||||||
|
registry,
|
||||||
|
}: {
|
||||||
|
registry: {
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
const { copyToClipboard, isCopied } = useCopyToClipboard()
|
||||||
|
const isMobile = useIsMobile()
|
||||||
|
const [open, setOpen] = React.useState(false)
|
||||||
|
|
||||||
|
const jsonValue = `{
|
||||||
|
"registries": {
|
||||||
|
"${registry.name}": "${registry.url}"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const Trigger = (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="relative z-10"
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
>
|
||||||
|
{isCopied ? (
|
||||||
|
<IconCheck />
|
||||||
|
) : (
|
||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>Model Context Protocol</title>
|
||||||
|
<path
|
||||||
|
d="M13.85 0a4.16 4.16 0 0 0-2.95 1.217L1.456 10.66a.835.835 0 0 0 0 1.18.835.835 0 0 0 1.18 0l9.442-9.442a2.49 2.49 0 0 1 3.541 0 2.49 2.49 0 0 1 0 3.541L8.59 12.97l-.1.1a.835.835 0 0 0 0 1.18.835.835 0 0 0 1.18 0l.1-.098 7.03-7.034a2.49 2.49 0 0 1 3.542 0l.049.05a2.49 2.49 0 0 1 0 3.54l-8.54 8.54a1.96 1.96 0 0 0 0 2.755l1.753 1.753a.835.835 0 0 0 1.18 0 .835.835 0 0 0 0-1.18l-1.753-1.753a.266.266 0 0 1 0-.394l8.54-8.54a4.185 4.185 0 0 0 0-5.9l-.05-.05a4.16 4.16 0 0 0-2.95-1.218c-.2 0-.401.02-.6.048a4.17 4.17 0 0 0-1.17-3.552A4.16 4.16 0 0 0 13.85 0m0 3.333a.84.84 0 0 0-.59.245L6.275 10.56a4.186 4.186 0 0 0 0 5.902 4.186 4.186 0 0 0 5.902 0L19.16 9.48a.835.835 0 0 0 0-1.18.835.835 0 0 0-1.18 0l-6.985 6.984a2.49 2.49 0 0 1-3.54 0 2.49 2.49 0 0 1 0-3.54l6.983-6.985a.835.835 0 0 0 0-1.18.84.84 0 0 0-.59-.245"
|
||||||
|
className="fill-foreground"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
MCP
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
|
||||||
|
const Content = (
|
||||||
|
<>
|
||||||
|
<figure
|
||||||
|
data-rehype-pretty-code-figure
|
||||||
|
className={cn(
|
||||||
|
"group relative mt-0",
|
||||||
|
!isMobile &&
|
||||||
|
"dark:bg-background dark:[&_[data-line]:not([data-highlighted-line]):before]:bg-background!"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CopyButton
|
||||||
|
value={jsonValue}
|
||||||
|
className="top-3 right-2"
|
||||||
|
tooltip="Copy Code"
|
||||||
|
/>
|
||||||
|
<div data-rehype-pretty-code-title>components.json</div>
|
||||||
|
<pre className="no-scrollbar min-w-0 overflow-x-auto px-4 py-3.5 outline-none has-[[data-highlighted-line]]:px-0 has-[[data-line-numbers]]:px-0 has-[[data-slot=tabs]]:p-0">
|
||||||
|
<code data-line-numbers data-language="json">
|
||||||
|
<span data-line>{"{"}</span>
|
||||||
|
<span data-line>{' "registries": {'}</span>
|
||||||
|
<span
|
||||||
|
data-line
|
||||||
|
data-highlighted-line
|
||||||
|
>{` "${registry.name}": "${registry.url}"`}</span>
|
||||||
|
<span data-line>{" }"}</span>
|
||||||
|
<span data-line>{"}"}</span>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</figure>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<Drawer open={open} onOpenChange={setOpen}>
|
||||||
|
<DrawerTrigger asChild>{Trigger}</DrawerTrigger>
|
||||||
|
<DrawerContent>
|
||||||
|
<DrawerHeader>
|
||||||
|
<DrawerTitle>Configure MCP</DrawerTitle>
|
||||||
|
<DrawerDescription>
|
||||||
|
Copy and paste the following code into your project's
|
||||||
|
components.json.
|
||||||
|
</DrawerDescription>
|
||||||
|
</DrawerHeader>
|
||||||
|
<div className="px-6">{Content}</div>
|
||||||
|
<DrawerFooter>
|
||||||
|
<DrawerClose asChild>
|
||||||
|
<Button size="sm">Close</Button>
|
||||||
|
</DrawerClose>
|
||||||
|
<Button size="sm" asChild variant="outline">
|
||||||
|
<Link href="/docs/mcp">Read the docs</Link>
|
||||||
|
</Button>
|
||||||
|
</DrawerFooter>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>{Trigger}</DialogTrigger>
|
||||||
|
<DialogContent
|
||||||
|
className="rounded-xl border-none bg-clip-padding shadow-2xl ring-4 ring-neutral-200/80 sm:max-w-[600px] dark:bg-neutral-900 dark:ring-neutral-800"
|
||||||
|
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Configure MCP</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Copy and paste the following code into your project's
|
||||||
|
components.json.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
{Content}
|
||||||
|
<DialogFooter className="justify-between!">
|
||||||
|
<Button size="sm" asChild variant="ghost">
|
||||||
|
<Link href="/docs/mcp">Read the docs</Link>
|
||||||
|
</Button>
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button size="sm">Done</Button>
|
||||||
|
</DialogClose>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
90
apps/v4/components/directory-list.tsx
Normal file
90
apps/v4/components/directory-list.tsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { IconArrowUpRight } from "@tabler/icons-react"
|
||||||
|
|
||||||
|
import { useSearchRegistry } from "@/hooks/use-search-registry"
|
||||||
|
import { DirectoryAddButton } from "@/components/directory-add-button"
|
||||||
|
import globalRegistries from "@/registry/directory.json"
|
||||||
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
import {
|
||||||
|
Item,
|
||||||
|
ItemActions,
|
||||||
|
ItemContent,
|
||||||
|
ItemDescription,
|
||||||
|
ItemFooter,
|
||||||
|
ItemGroup,
|
||||||
|
ItemMedia,
|
||||||
|
ItemSeparator,
|
||||||
|
ItemTitle,
|
||||||
|
} from "@/registry/new-york-v4/ui/item"
|
||||||
|
|
||||||
|
import { SearchDirectory } from "./search-directory"
|
||||||
|
|
||||||
|
function getHomepageUrl(homepage: string) {
|
||||||
|
const url = new URL(homepage)
|
||||||
|
url.searchParams.set("utm_source", "ui.shadcn.com")
|
||||||
|
url.searchParams.set("utm_medium", "referral")
|
||||||
|
url.searchParams.set("utm_campaign", "directory")
|
||||||
|
return url.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DirectoryList() {
|
||||||
|
const { registries } = useSearchRegistry()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-6">
|
||||||
|
<SearchDirectory />
|
||||||
|
<ItemGroup className="my-8">
|
||||||
|
{registries.map((registry, index) => (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
<Item className="group/item relative gap-6 px-0">
|
||||||
|
<ItemMedia
|
||||||
|
variant="image"
|
||||||
|
dangerouslySetInnerHTML={{ __html: registry.logo }}
|
||||||
|
className="*:[svg]:fill-foreground grayscale *:[svg]:size-8"
|
||||||
|
/>
|
||||||
|
<ItemContent>
|
||||||
|
<ItemTitle>
|
||||||
|
<a
|
||||||
|
href={getHomepageUrl(registry.homepage)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer external"
|
||||||
|
>
|
||||||
|
{registry.name}
|
||||||
|
</a>
|
||||||
|
</ItemTitle>
|
||||||
|
{registry.description && (
|
||||||
|
<ItemDescription className="text-pretty">
|
||||||
|
{registry.description}
|
||||||
|
</ItemDescription>
|
||||||
|
)}
|
||||||
|
</ItemContent>
|
||||||
|
<ItemActions className="relative z-10 hidden self-start sm:flex">
|
||||||
|
<Button size="sm" variant="outline" asChild>
|
||||||
|
<a
|
||||||
|
href={getHomepageUrl(registry.homepage)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer external"
|
||||||
|
>
|
||||||
|
View <IconArrowUpRight />
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
<DirectoryAddButton registry={registry} />
|
||||||
|
</ItemActions>
|
||||||
|
<ItemFooter className="justify-start pl-16 sm:hidden">
|
||||||
|
<Button size="sm" variant="outline">
|
||||||
|
View <IconArrowUpRight />
|
||||||
|
</Button>
|
||||||
|
<DirectoryAddButton registry={registry} />
|
||||||
|
</ItemFooter>
|
||||||
|
</Item>
|
||||||
|
{index < globalRegistries.length - 1 && (
|
||||||
|
<ItemSeparator className="my-1" />
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</ItemGroup>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import { Fragment } from "react"
|
|||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
import { useBreadcrumb } from "fumadocs-core/breadcrumb"
|
import { useBreadcrumb } from "fumadocs-core/breadcrumb"
|
||||||
import type { PageTree } from "fumadocs-core/server"
|
import type { Root } from "fumadocs-core/page-tree"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
@@ -19,7 +19,7 @@ export function DocsBreadcrumb({
|
|||||||
tree,
|
tree,
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
tree: PageTree.Root
|
tree: Root
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ const TOP_LEVEL_SECTIONS = [
|
|||||||
href: "/docs/components",
|
href: "/docs/components",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Registry",
|
name: "Directory",
|
||||||
href: "/docs/registry",
|
href: "/docs/directory",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "MCP Server",
|
name: "MCP Server",
|
||||||
@@ -51,12 +51,12 @@ export function DocsSidebar({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar
|
<Sidebar
|
||||||
className="sticky top-[calc(var(--header-height)+1px)] z-30 hidden h-[calc(100svh-var(--footer-height)+2rem)] bg-transparent lg:flex"
|
className="sticky top-[calc(var(--header-height)+1px)] z-30 hidden h-[calc(100svh-var(--footer-height)-4rem)] overscroll-none bg-transparent lg:flex"
|
||||||
collapsible="none"
|
collapsible="none"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<SidebarContent className="no-scrollbar overflow-x-hidden px-2 pb-12">
|
<SidebarContent className="no-scrollbar overflow-x-hidden px-2">
|
||||||
<div className="h-(--top-spacing) shrink-0" />
|
<div className="from-background via-background/80 to-background/50 sticky -top-1 z-10 h-8 shrink-0 bg-gradient-to-b blur-xs" />
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupLabel className="text-muted-foreground font-medium">
|
<SidebarGroupLabel className="text-muted-foreground font-medium">
|
||||||
Sections
|
Sections
|
||||||
@@ -141,6 +141,7 @@ export function DocsSidebar({
|
|||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
<div className="from-background via-background/80 to-background/50 sticky -bottom-1 z-10 h-16 shrink-0 bg-gradient-to-t blur-xs" />
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function GitHubLink() {
|
|||||||
<Button asChild size="sm" variant="ghost" className="h-8 shadow-none">
|
<Button asChild size="sm" variant="ghost" className="h-8 shadow-none">
|
||||||
<Link href={siteConfig.links.github} target="_blank" rel="noreferrer">
|
<Link href={siteConfig.links.github} target="_blank" rel="noreferrer">
|
||||||
<Icons.gitHub />
|
<Icons.gitHub />
|
||||||
<React.Suspense fallback={<Skeleton className="h-4 w-8" />}>
|
<React.Suspense fallback={<Skeleton className="h-4 w-[42px]" />}>
|
||||||
<StarsCount />
|
<StarsCount />
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -21,15 +21,20 @@ export function GitHubLink() {
|
|||||||
|
|
||||||
export async function StarsCount() {
|
export async function StarsCount() {
|
||||||
const data = await fetch("https://api.github.com/repos/shadcn-ui/ui", {
|
const data = await fetch("https://api.github.com/repos/shadcn-ui/ui", {
|
||||||
next: { revalidate: 86400 }, // Cache for 1 day (86400 seconds)
|
next: { revalidate: 86400 },
|
||||||
})
|
})
|
||||||
const json = await data.json()
|
const json = await data.json()
|
||||||
|
|
||||||
|
const formattedCount =
|
||||||
|
json.stargazers_count >= 1000
|
||||||
|
? json.stargazers_count % 1000 === 0
|
||||||
|
? `${Math.floor(json.stargazers_count / 1000)}k`
|
||||||
|
: `${(json.stargazers_count / 1000).toFixed(1)}k`
|
||||||
|
: json.stargazers_count.toLocaleString()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="text-muted-foreground w-8 text-xs tabular-nums">
|
<span className="text-muted-foreground w-fit text-xs tabular-nums">
|
||||||
{json.stargazers_count >= 1000
|
{formattedCount.replace(".0k", "k")}
|
||||||
? `${(json.stargazers_count / 1000).toFixed(1)}k`
|
|
||||||
: json.stargazers_count.toLocaleString()}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export function MainNav({
|
|||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className={cn("items-center gap-0.5", className)} {...props}>
|
<nav className={cn("items-center", className)} {...props}>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<Button key={item.href} variant="ghost" asChild size="sm">
|
<Button key={item.href} variant="ghost" asChild size="sm">
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ const TOP_LEVEL_SECTIONS = [
|
|||||||
href: "/docs/components",
|
href: "/docs/components",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Registry",
|
name: "Directory",
|
||||||
href: "/docs/registry",
|
href: "/docs/directory",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "MCP Server",
|
name: "MCP Server",
|
||||||
|
|||||||
49
apps/v4/components/search-directory.tsx
Normal file
49
apps/v4/components/search-directory.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Search, X } from "lucide-react"
|
||||||
|
|
||||||
|
import { useSearchRegistry } from "@/hooks/use-search-registry"
|
||||||
|
import { Field } from "@/registry/new-york-v4/ui/field"
|
||||||
|
import {
|
||||||
|
InputGroup,
|
||||||
|
InputGroupAddon,
|
||||||
|
InputGroupButton,
|
||||||
|
InputGroupInput,
|
||||||
|
} from "@/registry/new-york-v4/ui/input-group"
|
||||||
|
|
||||||
|
export const SearchDirectory = () => {
|
||||||
|
const { query, setQuery } = useSearchRegistry()
|
||||||
|
|
||||||
|
const onQueryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value
|
||||||
|
setQuery(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field>
|
||||||
|
<InputGroup>
|
||||||
|
<InputGroupAddon>
|
||||||
|
<Search />
|
||||||
|
</InputGroupAddon>
|
||||||
|
<InputGroupInput
|
||||||
|
placeholder="Search"
|
||||||
|
value={query}
|
||||||
|
onChange={onQueryChange}
|
||||||
|
/>
|
||||||
|
<InputGroupAddon
|
||||||
|
align="inline-end"
|
||||||
|
data-disabled={!query.length}
|
||||||
|
className="data-[disabled=true]:hidden"
|
||||||
|
>
|
||||||
|
<InputGroupButton
|
||||||
|
aria-label="Clear"
|
||||||
|
title="Clear"
|
||||||
|
size="icon-xs"
|
||||||
|
onClick={() => setQuery(null)}
|
||||||
|
>
|
||||||
|
<X />
|
||||||
|
</InputGroupButton>
|
||||||
|
</InputGroupAddon>
|
||||||
|
</InputGroup>
|
||||||
|
</Field>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ export function SiteHeader() {
|
|||||||
return (
|
return (
|
||||||
<header className="bg-background sticky top-0 z-50 w-full">
|
<header className="bg-background sticky top-0 z-50 w-full">
|
||||||
<div className="container-wrapper 3xl:fixed:px-0 px-6">
|
<div className="container-wrapper 3xl:fixed:px-0 px-6">
|
||||||
<div className="3xl:fixed:container flex h-(--header-height) items-center gap-2 **:data-[slot=separator]:!h-4">
|
<div className="3xl:fixed:container flex h-(--header-height) items-center **:data-[slot=separator]:!h-4">
|
||||||
<MobileNav
|
<MobileNav
|
||||||
tree={pageTree}
|
tree={pageTree}
|
||||||
items={siteConfig.navItems}
|
items={siteConfig.navItems}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import { IconCheck, IconCopy } from "@tabler/icons-react"
|
||||||
import template from "lodash/template"
|
import template from "lodash/template"
|
||||||
import { CheckIcon, ClipboardIcon } from "lucide-react"
|
|
||||||
|
|
||||||
|
import { THEMES } from "@/lib/themes"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { useThemeConfig } from "@/components/active-theme"
|
import { useThemeConfig } from "@/components/active-theme"
|
||||||
import { copyToClipboardWithMeta } from "@/components/copy-button"
|
import { copyToClipboardWithMeta } from "@/components/copy-button"
|
||||||
import { Icons } from "@/components/icons"
|
import { Icons } from "@/components/icons"
|
||||||
|
import { BaseColor, baseColors, baseColorsOKLCH } from "@/registry/base-colors"
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -41,21 +43,12 @@ import {
|
|||||||
TabsList,
|
TabsList,
|
||||||
TabsTrigger,
|
TabsTrigger,
|
||||||
} from "@/registry/new-york-v4/ui/tabs"
|
} from "@/registry/new-york-v4/ui/tabs"
|
||||||
import {
|
|
||||||
BaseColor,
|
|
||||||
baseColors,
|
|
||||||
baseColorsOKLCH,
|
|
||||||
} from "@/registry/registry-base-colors"
|
|
||||||
|
|
||||||
interface BaseColorOKLCH {
|
interface BaseColorOKLCH {
|
||||||
light: Record<string, string>
|
light: Record<string, string>
|
||||||
dark: Record<string, string>
|
dark: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
const THEMES = baseColors.filter(
|
|
||||||
(theme) => !["slate", "stone", "gray", "zinc"].includes(theme.name)
|
|
||||||
)
|
|
||||||
|
|
||||||
export function ThemeCustomizer({ className }: React.ComponentProps<"div">) {
|
export function ThemeCustomizer({ className }: React.ComponentProps<"div">) {
|
||||||
const { activeTheme = "neutral", setActiveTheme } = useThemeConfig()
|
const { activeTheme = "neutral", setActiveTheme } = useThemeConfig()
|
||||||
|
|
||||||
@@ -131,9 +124,7 @@ export function CopyCodeButton({
|
|||||||
</DrawerTrigger>
|
</DrawerTrigger>
|
||||||
<DrawerContent className="h-auto">
|
<DrawerContent className="h-auto">
|
||||||
<DrawerHeader>
|
<DrawerHeader>
|
||||||
<DrawerTitle className="capitalize">
|
<DrawerTitle className="capitalize">{activeThemeName}</DrawerTitle>
|
||||||
{activeThemeName === "neutral" ? "Default" : activeThemeName}
|
|
||||||
</DrawerTitle>
|
|
||||||
<DrawerDescription>
|
<DrawerDescription>
|
||||||
Copy and paste the following code into your CSS file.
|
Copy and paste the following code into your CSS file.
|
||||||
</DrawerDescription>
|
</DrawerDescription>
|
||||||
@@ -143,15 +134,20 @@ export function CopyCodeButton({
|
|||||||
</Drawer>
|
</Drawer>
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button className={cn("hidden sm:flex", className)} {...props}>
|
<Button
|
||||||
Copy Code
|
data-size={props.size}
|
||||||
|
className={cn("group/button hidden sm:flex", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<IconCopy />
|
||||||
|
<span className="group-data-[size=icon-sm]/button:sr-only">
|
||||||
|
Copy Code
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="outline-none md:max-w-3xl">
|
<DialogContent className="rounded-xl border-none bg-clip-padding shadow-2xl ring-4 ring-neutral-200/80 outline-none md:max-w-2xl dark:bg-neutral-800 dark:ring-neutral-900">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="capitalize">
|
<DialogTitle className="capitalize">{activeThemeName}</DialogTitle>
|
||||||
{activeThemeName === "neutral" ? "Default" : activeThemeName}
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Copy and paste the following code into your CSS file.
|
Copy and paste the following code into your CSS file.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
@@ -165,7 +161,7 @@ export function CopyCodeButton({
|
|||||||
|
|
||||||
function CustomizerCode({ themeName }: { themeName: string }) {
|
function CustomizerCode({ themeName }: { themeName: string }) {
|
||||||
const [hasCopied, setHasCopied] = React.useState(false)
|
const [hasCopied, setHasCopied] = React.useState(false)
|
||||||
const [tailwindVersion, setTailwindVersion] = React.useState("v4")
|
const [tailwindVersion, setTailwindVersion] = React.useState("v4-oklch")
|
||||||
const activeTheme = React.useMemo(
|
const activeTheme = React.useMemo(
|
||||||
() => baseColors.find((theme) => theme.name === themeName),
|
() => baseColors.find((theme) => theme.name === themeName),
|
||||||
[themeName]
|
[themeName]
|
||||||
@@ -191,10 +187,11 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
className="min-w-0 px-4 pb-4 md:p-0"
|
className="min-w-0 px-4 pb-4 md:p-0"
|
||||||
>
|
>
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="v4">Tailwind v4</TabsTrigger>
|
<TabsTrigger value="v4-oklch">OKLCH</TabsTrigger>
|
||||||
|
<TabsTrigger value="v4-hsl">HSL</TabsTrigger>
|
||||||
<TabsTrigger value="v3">Tailwind v3</TabsTrigger>
|
<TabsTrigger value="v3">Tailwind v3</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="v4">
|
<TabsContent value="v4-oklch">
|
||||||
<figure
|
<figure
|
||||||
data-rehype-pretty-code-figure
|
data-rehype-pretty-code-figure
|
||||||
className="!mx-0 mt-0 rounded-lg"
|
className="!mx-0 mt-0 rounded-lg"
|
||||||
@@ -216,14 +213,12 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
className="bg-code text-code-foreground absolute top-3 right-2 z-10 size-7 shadow-none hover:opacity-100 focus-visible:opacity-100"
|
className="bg-code text-code-foreground absolute top-3 right-2 z-10 size-7 shadow-none hover:opacity-100 focus-visible:opacity-100"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
copyToClipboardWithMeta(
|
copyToClipboardWithMeta(
|
||||||
tailwindVersion === "v3"
|
getThemeCodeOKLCH(activeThemeOKLCH, 0.65),
|
||||||
? getThemeCode(activeTheme, 0.65)
|
|
||||||
: getThemeCodeOKLCH(activeThemeOKLCH, 0.65),
|
|
||||||
{
|
{
|
||||||
name: "copy_theme_code",
|
name: "copy_theme_code",
|
||||||
properties: {
|
properties: {
|
||||||
theme: themeName,
|
theme: themeName,
|
||||||
radius: 0.5,
|
radius: 0.65,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -231,7 +226,7 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Copy</span>
|
<span className="sr-only">Copy</span>
|
||||||
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
|
{hasCopied ? <IconCheck /> : <IconCopy />}
|
||||||
</Button>
|
</Button>
|
||||||
<code data-line-numbers data-language="css">
|
<code data-line-numbers data-language="css">
|
||||||
<span data-line className="line text-code-foreground">
|
<span data-line className="line text-code-foreground">
|
||||||
@@ -246,7 +241,8 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
className="line text-code-foreground"
|
className="line text-code-foreground"
|
||||||
key={key}
|
key={key}
|
||||||
>
|
>
|
||||||
--{key}: {value};
|
--{key}: <ColorIndicator color={value} />{" "}
|
||||||
|
{value};
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
<span data-line className="line text-code-foreground">
|
<span data-line className="line text-code-foreground">
|
||||||
@@ -264,7 +260,8 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
className="line text-code-foreground"
|
className="line text-code-foreground"
|
||||||
key={key}
|
key={key}
|
||||||
>
|
>
|
||||||
--{key}: {value};
|
--{key}: <ColorIndicator color={value} />{" "}
|
||||||
|
{value};
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
<span data-line className="line text-code-foreground">
|
<span data-line className="line text-code-foreground">
|
||||||
@@ -274,6 +271,90 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
</pre>
|
</pre>
|
||||||
</figure>
|
</figure>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
<TabsContent value="v4-hsl">
|
||||||
|
<figure
|
||||||
|
data-rehype-pretty-code-figure
|
||||||
|
className="!mx-0 mt-0 rounded-lg"
|
||||||
|
>
|
||||||
|
<figcaption
|
||||||
|
className="text-code-foreground [&_svg]:text-code-foreground flex items-center gap-2 [&_svg]:size-4 [&_svg]:opacity-70"
|
||||||
|
data-rehype-pretty-code-title=""
|
||||||
|
data-language="css"
|
||||||
|
data-theme="github-dark github-light-default"
|
||||||
|
>
|
||||||
|
<Icons.css className="fill-foreground" />
|
||||||
|
app/globals.css
|
||||||
|
</figcaption>
|
||||||
|
<pre className="no-scrollbar max-h-[300px] min-w-0 overflow-x-auto px-4 py-3.5 outline-none has-[[data-highlighted-line]]:px-0 has-[[data-line-numbers]]:px-0 has-[[data-slot=tabs]]:p-0 md:max-h-[450px]">
|
||||||
|
<Button
|
||||||
|
data-slot="copy-button"
|
||||||
|
size="icon"
|
||||||
|
variant="ghost"
|
||||||
|
className="bg-code text-code-foreground absolute top-3 right-2 z-10 size-7 shadow-none hover:opacity-100 focus-visible:opacity-100"
|
||||||
|
onClick={() => {
|
||||||
|
copyToClipboardWithMeta(
|
||||||
|
getThemeCodeHSLV4(activeTheme, 0.65),
|
||||||
|
{
|
||||||
|
name: "copy_theme_code",
|
||||||
|
properties: {
|
||||||
|
theme: themeName,
|
||||||
|
radius: 0.65,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
setHasCopied(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Copy</span>
|
||||||
|
{hasCopied ? <IconCheck /> : <IconCopy />}
|
||||||
|
</Button>
|
||||||
|
<code data-line-numbers data-language="css">
|
||||||
|
<span data-line className="line text-code-foreground">
|
||||||
|
:root {
|
||||||
|
</span>
|
||||||
|
<span data-line className="line text-code-foreground">
|
||||||
|
--radius: 0.65rem;
|
||||||
|
</span>
|
||||||
|
{Object.entries(activeTheme?.cssVars.light || {}).map(
|
||||||
|
([key, value]) => (
|
||||||
|
<span
|
||||||
|
data-line
|
||||||
|
className="line text-code-foreground"
|
||||||
|
key={key}
|
||||||
|
>
|
||||||
|
--{key}:{" "}
|
||||||
|
<ColorIndicator color={`hsl(${value})`} /> hsl({value});
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<span data-line className="line text-code-foreground">
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span data-line className="line text-code-foreground">
|
||||||
|
|
||||||
|
</span>
|
||||||
|
<span data-line className="line text-code-foreground">
|
||||||
|
.dark {
|
||||||
|
</span>
|
||||||
|
{Object.entries(activeTheme?.cssVars.dark || {}).map(
|
||||||
|
([key, value]) => (
|
||||||
|
<span
|
||||||
|
data-line
|
||||||
|
className="line text-code-foreground"
|
||||||
|
key={key}
|
||||||
|
>
|
||||||
|
--{key}:{" "}
|
||||||
|
<ColorIndicator color={`hsl(${value})`} /> hsl({value});
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<span data-line className="line text-code-foreground">
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</figure>
|
||||||
|
</TabsContent>
|
||||||
<TabsContent value="v3">
|
<TabsContent value="v3">
|
||||||
<figure
|
<figure
|
||||||
data-rehype-pretty-code-figure
|
data-rehype-pretty-code-figure
|
||||||
@@ -295,23 +376,18 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="bg-code text-code-foreground absolute top-3 right-2 z-10 size-7 shadow-none hover:opacity-100 focus-visible:opacity-100"
|
className="bg-code text-code-foreground absolute top-3 right-2 z-10 size-7 shadow-none hover:opacity-100 focus-visible:opacity-100"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
copyToClipboardWithMeta(
|
copyToClipboardWithMeta(getThemeCode(activeTheme, 0.5), {
|
||||||
tailwindVersion === "v3"
|
name: "copy_theme_code",
|
||||||
? getThemeCode(activeTheme, 0.65)
|
properties: {
|
||||||
: getThemeCodeOKLCH(activeThemeOKLCH, 0.65),
|
theme: themeName,
|
||||||
{
|
radius: 0.5,
|
||||||
name: "copy_theme_code",
|
},
|
||||||
properties: {
|
})
|
||||||
theme: themeName,
|
|
||||||
radius: 0.5,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
setHasCopied(true)
|
setHasCopied(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Copy</span>
|
<span className="sr-only">Copy</span>
|
||||||
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
|
{hasCopied ? <IconCheck /> : <IconCopy />}
|
||||||
</Button>
|
</Button>
|
||||||
<code data-line-numbers data-language="css">
|
<code data-line-numbers data-language="css">
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
@@ -322,10 +398,16 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
</span>
|
</span>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--background:{" "}
|
--background:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${activeTheme?.cssVars.light["background"]})`}
|
||||||
|
/>{" "}
|
||||||
{activeTheme?.cssVars.light["background"]};
|
{activeTheme?.cssVars.light["background"]};
|
||||||
</span>
|
</span>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--foreground:{" "}
|
--foreground:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${activeTheme?.cssVars.light["foreground"]})`}
|
||||||
|
/>{" "}
|
||||||
{activeTheme?.cssVars.light["foreground"]};
|
{activeTheme?.cssVars.light["foreground"]};
|
||||||
</span>
|
</span>
|
||||||
{[
|
{[
|
||||||
@@ -340,6 +422,13 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
<React.Fragment key={prefix}>
|
<React.Fragment key={prefix}>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--{prefix}:{" "}
|
--{prefix}:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${
|
||||||
|
activeTheme?.cssVars.light[
|
||||||
|
prefix as keyof typeof activeTheme.cssVars.light
|
||||||
|
]
|
||||||
|
})`}
|
||||||
|
/>{" "}
|
||||||
{
|
{
|
||||||
activeTheme?.cssVars.light[
|
activeTheme?.cssVars.light[
|
||||||
prefix as keyof typeof activeTheme.cssVars.light
|
prefix as keyof typeof activeTheme.cssVars.light
|
||||||
@@ -349,6 +438,13 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
</span>
|
</span>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--{prefix}-foreground:{" "}
|
--{prefix}-foreground:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${
|
||||||
|
activeTheme?.cssVars.light[
|
||||||
|
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.light
|
||||||
|
]
|
||||||
|
})`}
|
||||||
|
/>{" "}
|
||||||
{
|
{
|
||||||
activeTheme?.cssVars.light[
|
activeTheme?.cssVars.light[
|
||||||
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.light
|
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.light
|
||||||
@@ -360,14 +456,23 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
))}
|
))}
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--border:{" "}
|
--border:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${activeTheme?.cssVars.light["border"]})`}
|
||||||
|
/>{" "}
|
||||||
{activeTheme?.cssVars.light["border"]};
|
{activeTheme?.cssVars.light["border"]};
|
||||||
</span>
|
</span>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--input:{" "}
|
--input:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${activeTheme?.cssVars.light["input"]})`}
|
||||||
|
/>{" "}
|
||||||
{activeTheme?.cssVars.light["input"]};
|
{activeTheme?.cssVars.light["input"]};
|
||||||
</span>
|
</span>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--ring:{" "}
|
--ring:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${activeTheme?.cssVars.light["ring"]})`}
|
||||||
|
/>{" "}
|
||||||
{activeTheme?.cssVars.light["ring"]};
|
{activeTheme?.cssVars.light["ring"]};
|
||||||
</span>
|
</span>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
@@ -378,6 +483,13 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
<React.Fragment key={prefix}>
|
<React.Fragment key={prefix}>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--{prefix}:{" "}
|
--{prefix}:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${
|
||||||
|
activeTheme?.cssVars.light[
|
||||||
|
prefix as keyof typeof activeTheme.cssVars.light
|
||||||
|
]
|
||||||
|
})`}
|
||||||
|
/>{" "}
|
||||||
{
|
{
|
||||||
activeTheme?.cssVars.light[
|
activeTheme?.cssVars.light[
|
||||||
prefix as keyof typeof activeTheme.cssVars.light
|
prefix as keyof typeof activeTheme.cssVars.light
|
||||||
@@ -399,10 +511,16 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
</span>
|
</span>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--background:{" "}
|
--background:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${activeTheme?.cssVars.dark["background"]})`}
|
||||||
|
/>{" "}
|
||||||
{activeTheme?.cssVars.dark["background"]};
|
{activeTheme?.cssVars.dark["background"]};
|
||||||
</span>
|
</span>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--foreground:{" "}
|
--foreground:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${activeTheme?.cssVars.dark["foreground"]})`}
|
||||||
|
/>{" "}
|
||||||
{activeTheme?.cssVars.dark["foreground"]};
|
{activeTheme?.cssVars.dark["foreground"]};
|
||||||
</span>
|
</span>
|
||||||
{[
|
{[
|
||||||
@@ -417,6 +535,13 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
<React.Fragment key={prefix}>
|
<React.Fragment key={prefix}>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--{prefix}:{" "}
|
--{prefix}:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${
|
||||||
|
activeTheme?.cssVars.dark[
|
||||||
|
prefix as keyof typeof activeTheme.cssVars.dark
|
||||||
|
]
|
||||||
|
})`}
|
||||||
|
/>{" "}
|
||||||
{
|
{
|
||||||
activeTheme?.cssVars.dark[
|
activeTheme?.cssVars.dark[
|
||||||
prefix as keyof typeof activeTheme.cssVars.dark
|
prefix as keyof typeof activeTheme.cssVars.dark
|
||||||
@@ -426,6 +551,13 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
</span>
|
</span>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--{prefix}-foreground:{" "}
|
--{prefix}-foreground:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${
|
||||||
|
activeTheme?.cssVars.dark[
|
||||||
|
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.dark
|
||||||
|
]
|
||||||
|
})`}
|
||||||
|
/>{" "}
|
||||||
{
|
{
|
||||||
activeTheme?.cssVars.dark[
|
activeTheme?.cssVars.dark[
|
||||||
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.dark
|
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.dark
|
||||||
@@ -437,14 +569,23 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
))}
|
))}
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--border:{" "}
|
--border:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${activeTheme?.cssVars.dark["border"]})`}
|
||||||
|
/>{" "}
|
||||||
{activeTheme?.cssVars.dark["border"]};
|
{activeTheme?.cssVars.dark["border"]};
|
||||||
</span>
|
</span>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--input:{" "}
|
--input:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${activeTheme?.cssVars.dark["input"]})`}
|
||||||
|
/>{" "}
|
||||||
{activeTheme?.cssVars.dark["input"]};
|
{activeTheme?.cssVars.dark["input"]};
|
||||||
</span>
|
</span>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--ring:{" "}
|
--ring:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${activeTheme?.cssVars.dark["ring"]})`}
|
||||||
|
/>{" "}
|
||||||
{activeTheme?.cssVars.dark["ring"]};
|
{activeTheme?.cssVars.dark["ring"]};
|
||||||
</span>
|
</span>
|
||||||
{["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"].map(
|
{["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"].map(
|
||||||
@@ -452,6 +593,13 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
<React.Fragment key={prefix}>
|
<React.Fragment key={prefix}>
|
||||||
<span data-line className="line">
|
<span data-line className="line">
|
||||||
--{prefix}:{" "}
|
--{prefix}:{" "}
|
||||||
|
<ColorIndicator
|
||||||
|
color={`hsl(${
|
||||||
|
activeTheme?.cssVars.dark[
|
||||||
|
prefix as keyof typeof activeTheme.cssVars.dark
|
||||||
|
]
|
||||||
|
})`}
|
||||||
|
/>{" "}
|
||||||
{
|
{
|
||||||
activeTheme?.cssVars.dark[
|
activeTheme?.cssVars.dark[
|
||||||
prefix as keyof typeof activeTheme.cssVars.dark
|
prefix as keyof typeof activeTheme.cssVars.dark
|
||||||
@@ -477,6 +625,15 @@ function CustomizerCode({ themeName }: { themeName: string }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ColorIndicator({ color }: { color: string }) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="border-border/50 inline-block size-3 border"
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function getThemeCodeOKLCH(theme: BaseColorOKLCH | undefined, radius: number) {
|
function getThemeCodeOKLCH(theme: BaseColorOKLCH | undefined, radius: number) {
|
||||||
if (!theme) {
|
if (!theme) {
|
||||||
return ""
|
return ""
|
||||||
@@ -509,6 +666,27 @@ function getThemeCode(theme: BaseColor | undefined, radius: number) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getThemeCodeHSLV4(theme: BaseColor | undefined, radius: number) {
|
||||||
|
if (!theme) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootSection =
|
||||||
|
":root {\n --radius: " +
|
||||||
|
radius +
|
||||||
|
"rem;\n" +
|
||||||
|
Object.entries(theme.cssVars.light)
|
||||||
|
.map((entry) => " --" + entry[0] + ": hsl(" + entry[1] + ");")
|
||||||
|
.join("\n") +
|
||||||
|
"\n}\n\n.dark {\n" +
|
||||||
|
Object.entries(theme.cssVars.dark)
|
||||||
|
.map((entry) => " --" + entry[0] + ": hsl(" + entry[1] + ");")
|
||||||
|
.join("\n") +
|
||||||
|
"\n}\n"
|
||||||
|
|
||||||
|
return rootSection
|
||||||
|
}
|
||||||
|
|
||||||
const BASE_STYLES_WITH_VARIABLES = `
|
const BASE_STYLES_WITH_VARIABLES = `
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
@@ -1,74 +1,30 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
|
import { THEMES } from "@/lib/themes"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { useThemeConfig } from "@/components/active-theme"
|
import { useThemeConfig } from "@/components/active-theme"
|
||||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
import { Label } from "@/registry/new-york-v4/ui/label"
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectGroup,
|
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectLabel,
|
|
||||||
SelectSeparator,
|
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/registry/new-york-v4/ui/select"
|
} from "@/registry/new-york-v4/ui/select"
|
||||||
|
|
||||||
const DEFAULT_THEMES = [
|
import { CopyCodeButton } from "./theme-customizer"
|
||||||
{
|
|
||||||
name: "Default",
|
|
||||||
value: "default",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Scaled",
|
|
||||||
value: "scaled",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Mono",
|
|
||||||
value: "mono",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const COLOR_THEMES = [
|
|
||||||
{
|
|
||||||
name: "Blue",
|
|
||||||
value: "blue",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Green",
|
|
||||||
value: "green",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Amber",
|
|
||||||
value: "amber",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Rose",
|
|
||||||
value: "rose",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Purple",
|
|
||||||
value: "purple",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Orange",
|
|
||||||
value: "orange",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Teal",
|
|
||||||
value: "teal",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export function ThemeSelector({ className }: React.ComponentProps<"div">) {
|
export function ThemeSelector({ className }: React.ComponentProps<"div">) {
|
||||||
const { activeTheme, setActiveTheme } = useThemeConfig()
|
const { activeTheme, setActiveTheme } = useThemeConfig()
|
||||||
|
|
||||||
|
const value = activeTheme === "default" ? "neutral" : activeTheme
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex items-center gap-2", className)}>
|
<div className={cn("flex items-center gap-2", className)}>
|
||||||
<Label htmlFor="theme-selector" className="sr-only">
|
<Label htmlFor="theme-selector" className="sr-only">
|
||||||
Theme
|
Theme
|
||||||
</Label>
|
</Label>
|
||||||
<Select value={activeTheme} onValueChange={setActiveTheme}>
|
<Select value={value} onValueChange={setActiveTheme}>
|
||||||
<SelectTrigger
|
<SelectTrigger
|
||||||
id="theme-selector"
|
id="theme-selector"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -78,32 +34,18 @@ export function ThemeSelector({ className }: React.ComponentProps<"div">) {
|
|||||||
<SelectValue placeholder="Select a theme" />
|
<SelectValue placeholder="Select a theme" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent align="end">
|
<SelectContent align="end">
|
||||||
<SelectGroup>
|
{THEMES.map((theme) => (
|
||||||
{DEFAULT_THEMES.map((theme) => (
|
<SelectItem
|
||||||
<SelectItem
|
key={theme.name}
|
||||||
key={theme.name}
|
value={theme.name}
|
||||||
value={theme.value}
|
className="data-[state=checked]:opacity-50"
|
||||||
className="data-[state=checked]:opacity-50"
|
>
|
||||||
>
|
{theme.label}
|
||||||
{theme.name}
|
</SelectItem>
|
||||||
</SelectItem>
|
))}
|
||||||
))}
|
|
||||||
</SelectGroup>
|
|
||||||
<SelectSeparator />
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectLabel>Colors</SelectLabel>
|
|
||||||
{COLOR_THEMES.map((theme) => (
|
|
||||||
<SelectItem
|
|
||||||
key={theme.name}
|
|
||||||
value={theme.value}
|
|
||||||
className="data-[state=checked]:opacity-50"
|
|
||||||
>
|
|
||||||
{theme.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
<CopyCodeButton variant="secondary" size="icon-sm" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1014,7 +1014,7 @@ It has support for infinite looping, autoplay, vertical orientation, and more.
|
|||||||
|
|
||||||
### Drawer
|
### Drawer
|
||||||
|
|
||||||
Oh the drawer component 😍. Built on top of [Vaul](https://github.com/emilkowalski/vaul) by [emilkowalski\_](https://twitter.com/emilkowalski_).
|
Oh the drawer component 😍. Built on top of [Vaul](https://github.com/emilkowalski/vaul) by [emilkowalski](https://twitter.com/emilkowalski).
|
||||||
|
|
||||||
Try opening the following drawer on mobile. It looks amazing!
|
Try opening the following drawer on mobile. It looks amazing!
|
||||||
|
|
||||||
@@ -1036,7 +1036,7 @@ Build resizable panel groups and layouts with this `<Resizable />` component.
|
|||||||
|
|
||||||
### Sonner
|
### Sonner
|
||||||
|
|
||||||
Another one by [emilkowalski\_](https://twitter.com/emilkowalski_). The last toast component you'll ever need. Sonner is now availabe in shadcn/ui.
|
Another one by [emilkowalski](https://twitter.com/emilkowalski). The last toast component you'll ever need. Sonner is now availabe in shadcn/ui.
|
||||||
|
|
||||||
<ComponentPreview name="sonner-demo" />
|
<ComponentPreview name="sonner-demo" />
|
||||||
|
|
||||||
|
|||||||
69
apps/v4/content/docs/(root)/directory.mdx
Normal file
69
apps/v4/content/docs/(root)/directory.mdx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
title: Registry Directory
|
||||||
|
description: Discover community registries for shadcn/ui components and blocks.
|
||||||
|
---
|
||||||
|
|
||||||
|
These registries are built into the CLI with no additional configuration required. To add a component, run: `npx shadcn add @<registry>/<component>`.
|
||||||
|
|
||||||
|
<DirectoryList />
|
||||||
|
|
||||||
|
Don't see a registry? Learn how to [add it here](/docs/registry/registry-index).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
You can use the `shadcn` CLI to run your own code registry. Running your own registry allows you to distribute your custom components, hooks, pages, config, rules and other files to any project.
|
||||||
|
|
||||||
|
<div className="mt-6 grid gap-4 sm:grid-cols-2">
|
||||||
|
<LinkedCard href="/docs/registry/getting-started" className="items-start text-sm md:p-6">
|
||||||
|
<div className="font-medium">Getting Started</div>
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
Set up and build your own registry
|
||||||
|
</div>
|
||||||
|
</LinkedCard>
|
||||||
|
|
||||||
|
<LinkedCard
|
||||||
|
href="/docs/registry/authentication"
|
||||||
|
className="items-start text-sm md:p-6"
|
||||||
|
>
|
||||||
|
<div className="font-medium">Authentication</div>
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
Secure your registry with authentication
|
||||||
|
</div>
|
||||||
|
</LinkedCard>
|
||||||
|
<LinkedCard
|
||||||
|
href="/docs/registry/namespace"
|
||||||
|
className="items-start text-sm md:p-6"
|
||||||
|
>
|
||||||
|
<div className="font-medium">Namespaces</div>
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
Configure registries with namespaces
|
||||||
|
</div>
|
||||||
|
</LinkedCard>
|
||||||
|
<LinkedCard
|
||||||
|
href="/docs/registry/registry-index"
|
||||||
|
className="items-start text-sm md:p-6"
|
||||||
|
>
|
||||||
|
<div className="font-medium">Add a Registry</div>
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
Learn how to add a registry to the directory
|
||||||
|
</div>
|
||||||
|
</LinkedCard>
|
||||||
|
<LinkedCard
|
||||||
|
href="/docs/registry/examples"
|
||||||
|
className="items-start text-sm md:p-6"
|
||||||
|
>
|
||||||
|
<div className="font-medium">Examples</div>
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
Registry item examples and configurations
|
||||||
|
</div>
|
||||||
|
</LinkedCard>
|
||||||
|
<LinkedCard
|
||||||
|
href="/docs/registry/registry-json"
|
||||||
|
className="items-start text-sm md:p-6"
|
||||||
|
>
|
||||||
|
<div className="font-medium">Schema</div>
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
Schema specification for registry.json
|
||||||
|
</div>
|
||||||
|
</LinkedCard>
|
||||||
|
</div>
|
||||||
@@ -8,12 +8,13 @@ description: Every component recreated in Figma. With customizable props, typogr
|
|||||||
questions or feedback, please reach out to the Figma file maintainers.
|
questions or feedback, please reach out to the Figma file maintainers.
|
||||||
</Callout>
|
</Callout>
|
||||||
|
|
||||||
|
## Free
|
||||||
|
|
||||||
|
- [Obra shadcn/ui](https://www.figma.com/community/file/1514746685758799870/obra-shadcn-ui) by [Obra Studio](https://obra.studio/) - Carefully crafted kit designed in the philosophy of shadcn, tracks v4, MIT licensed
|
||||||
|
- [shadcn/ui design system](https://www.figma.com/community/file/1203061493325953101) by [Pietro Schirano](https://twitter.com/skirano) - A design companion for shadcn/ui. Each component was painstakingly crafted to perfectly match the code implementation.
|
||||||
|
|
||||||
## Paid
|
## Paid
|
||||||
|
|
||||||
- [shadcn/ui kit](https://shadcndesign.com) by [ Matt Wierzbicki](https://x.com/matsugfx) - A premium, always up-to-date UI kit for Figma - shadcn/ui compatible and optimized for smooth design-to-dev handoff.
|
- [shadcn/ui kit](https://shadcndesign.com) by [ Matt Wierzbicki](https://x.com/matsugfx) - A premium, always up-to-date UI kit for Figma - shadcn/ui compatible and optimized for smooth design-to-dev handoff.
|
||||||
- [Shadcraft UI Kit](https://shadcraft.com) - The most advanced shadcn-compatible kit with instant theming via [tweakcn](https://tweakcn.com), a pro library of components and templates, and complete coverage of shadcn components and blocks.
|
- [Shadcraft UI Kit](https://shadcraft.com) - The most advanced shadcn-compatible kit with instant theming via [tweakcn](https://tweakcn.com), a pro library of components and templates, and complete coverage of shadcn components and blocks.
|
||||||
|
- [shadcn/studio UI Kit](https://shadcnstudio.com/figma) - Accelerate design & development with a shadcn/ui compatible Figma kit with updated components, 550+ blocks, 10+ templates, 20+ themes, and an AI tool that converts designs into shadcn/ui code.
|
||||||
## Free
|
|
||||||
|
|
||||||
- [shadcn/ui design system](https://www.figma.com/community/file/1203061493325953101) by [Pietro Schirano](https://twitter.com/skirano) - A design companion for shadcn/ui. Each component was painstakingly crafted to perfectly match the code implementation.
|
|
||||||
- [Obra shadcn/ui](https://www.figma.com/community/file/1514746685758799870/obra-shadcn-ui) by [Obra Studio](https://obra.studio/) - Carefully crafted kit designed in the philosophy of shadcn, tracks v4, MIT licensed
|
|
||||||
|
|||||||
@@ -9,20 +9,24 @@ However we provide a JavaScript version of the components as well. The JavaScrip
|
|||||||
|
|
||||||
To opt-out of TypeScript, you can use the `tsx` flag in your `components.json` file.
|
To opt-out of TypeScript, you can use the `tsx` flag in your `components.json` file.
|
||||||
|
|
||||||
```json {10} title="components.json" showLineNumbers
|
```json {4} title="components.json" showLineNumbers
|
||||||
{
|
{
|
||||||
"style": "default",
|
"style": "new-york",
|
||||||
|
"rsc": false,
|
||||||
|
"tsx": false,
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "tailwind.config.js",
|
"config": "",
|
||||||
"css": "src/app/globals.css",
|
"css": "src/app/globals.css",
|
||||||
"baseColor": "zinc",
|
"baseColor": "zinc",
|
||||||
"cssVariables": true
|
"cssVariables": true
|
||||||
},
|
},
|
||||||
"rsc": false,
|
"iconLibrary": "lucide",
|
||||||
"tsx": false,
|
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"utils": "~/lib/utils",
|
"components": "@/components",
|
||||||
"components": "~/components"
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ To configure MCP in VS Code with GitHub Copilot, add the shadcn server to your p
|
|||||||
|
|
||||||
```json title=".vscode/mcp.json" showLineNumbers
|
```json title=".vscode/mcp.json" showLineNumbers
|
||||||
{
|
{
|
||||||
"mcpServers": {
|
"servers": {
|
||||||
"shadcn": {
|
"shadcn": {
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["shadcn@latest", "mcp"]
|
"args": ["shadcn@latest", "mcp"]
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ To create a new monorepo project, run the `init` command. You will be prompted
|
|||||||
to select the type of project you are creating.
|
to select the type of project you are creating.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx shadcn@canary init
|
npx shadcn@latest init
|
||||||
```
|
```
|
||||||
|
|
||||||
Select the `Next.js (Monorepo)` option.
|
Select the `Next.js (Monorepo)` option.
|
||||||
@@ -51,15 +51,15 @@ cd apps/web
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx shadcn@canary add [COMPONENT]
|
npx shadcn@latest add [COMPONENT]
|
||||||
```
|
```
|
||||||
|
|
||||||
The CLI will figure out what type of component you are adding and install the
|
The CLI will figure out what type of component you are adding and install the
|
||||||
correct files to the correct path.
|
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`.
|
For example, if you run `npx shadcn@latest 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`.
|
If you run `npx shadcn@latest 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
|
### Importing components
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,9 @@ title: Next.js 15 + React 19
|
|||||||
description: Using shadcn/ui with Next.js 15 and React 19.
|
description: Using shadcn/ui with Next.js 15 and React 19.
|
||||||
---
|
---
|
||||||
|
|
||||||
<Callout className="mb-6 border-blue-600 bg-blue-50 dark:border-blue-900 dark:bg-blue-950 [&_code]:bg-blue-100 dark:[&_code]:bg-blue-900">
|
<Callout className="">
|
||||||
**Update:** We have added full support for React 19 and Tailwind v4 in the
|
**Update:** We have added full support for React 19 and Tailwind v4 in the
|
||||||
`canary` release. See the docs for [Tailwind v4](/docs/tailwind-v4) for more
|
`latest` release. **This guide might be outdated. Proceed with caution.**
|
||||||
information.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
<Callout>
|
|
||||||
**The following guide applies to any framework that supports React 19**. I
|
|
||||||
titled this page "Next.js 15 + React 19" to help people upgrading to Next.js
|
|
||||||
15 find it. We are working with package maintainers to help upgrade to React
|
|
||||||
19.
|
|
||||||
</Callout>
|
</Callout>
|
||||||
|
|
||||||
## TL;DR
|
## TL;DR
|
||||||
@@ -148,7 +140,7 @@ To make it easy for you track the progress of the upgrade, I've created a table
|
|||||||
| [react-day-picker](https://www.npmjs.com/package/react-day-picker) | ✅ | Works with flag for npm. Work to upgrade to v9 in progress. |
|
| [react-day-picker](https://www.npmjs.com/package/react-day-picker) | ✅ | Works with flag for npm. Work to upgrade to v9 in progress. |
|
||||||
| [input-otp](https://www.npmjs.com/package/input-otp) | ✅ | |
|
| [input-otp](https://www.npmjs.com/package/input-otp) | ✅ | |
|
||||||
| [vaul](https://www.npmjs.com/package/vaul) | ✅ | |
|
| [vaul](https://www.npmjs.com/package/vaul) | ✅ | |
|
||||||
| [@radix-ui/react-icons](https://www.npmjs.com/package/@radix-ui/react-icons) | 🚧 | See [PR #194](https://github.com/radix-ui/icons/pull/194) |
|
| [@radix-ui/react-icons](https://www.npmjs.com/package/@radix-ui/react-icons) | ✅ | See [PR #194](https://github.com/radix-ui/icons/pull/194) |
|
||||||
| [cmdk](https://www.npmjs.com/package/cmdk) | ✅ | |
|
| [cmdk](https://www.npmjs.com/package/cmdk) | ✅ | |
|
||||||
|
|
||||||
If you have any questions, please [open an issue](https://github.com/shadcn/ui/issues) on GitHub.
|
If you have any questions, please [open an issue](https://github.com/shadcn/ui/issues) on GitHub.
|
||||||
|
|||||||
@@ -1,208 +0,0 @@
|
|||||||
---
|
|
||||||
title: Styleguide
|
|
||||||
description: A styleguide for writing documentation in mdx.
|
|
||||||
---
|
|
||||||
|
|
||||||
The OpenAI API provides a simple interface to state-of-the-art AI models for text generation, natural language processing, computer vision, and more. This example generates text output from a prompt, as you might using ChatGPT.
|
|
||||||
|
|
||||||
## Analyze image inputs
|
|
||||||
|
|
||||||
You can provide image inputs to the model as well. Scan receipts, analyze screenshots, or find objects in the real world with [computer vision](/docs/installation/computer-vision). This is code in a `pre` tag and `npx` command in a `code` tag.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install foo
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest init
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest add button
|
|
||||||
```
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<Button>Click me</Button>
|
|
||||||
```
|
|
||||||
|
|
||||||
```tsx showLineNumbers
|
|
||||||
// With line numbers
|
|
||||||
export default function Home() {
|
|
||||||
return <div>Hello</div>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```tsx title="Button.tsx"
|
|
||||||
export default function Button({ children }: { children: React.ReactNode }) {
|
|
||||||
return <button>{children}</button>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is a code block with a title.
|
|
||||||
|
|
||||||
## Line Numbers and Line Highlighting
|
|
||||||
|
|
||||||
Draw attention to a particular line of code.
|
|
||||||
|
|
||||||
```tsx {4} showLineNumbers
|
|
||||||
import { useFloating } from "@floating-ui/react"
|
|
||||||
|
|
||||||
function MyComponent() {
|
|
||||||
const { refs, floatingStyles } = useFloating()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div ref={refs.setReference} />
|
|
||||||
<div ref={refs.setFloating} style={floatingStyles} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Word Highlighting
|
|
||||||
|
|
||||||
Draw attention to a particular word or series of characters.
|
|
||||||
|
|
||||||
```tsx /floatingStyles/
|
|
||||||
import { useFloating } from "@floating-ui/react"
|
|
||||||
|
|
||||||
function MyComponent() {
|
|
||||||
const { refs, floatingStyles } = useFloating()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div ref={refs.setReference} />
|
|
||||||
<div ref={refs.setFloating} style={floatingStyles} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
How
|
|
||||||
|
|
||||||
```tsx title="apps/www/registry/registry-blocks.tsx"
|
|
||||||
export const blocks = [
|
|
||||||
// ...
|
|
||||||
{
|
|
||||||
name: "dashboard-01",
|
|
||||||
author: "shadcn (https://ui.shadcn.com)",
|
|
||||||
title: "Dashboard",
|
|
||||||
description: "A simple dashboard with a hello world component.",
|
|
||||||
type: "registry:block",
|
|
||||||
registryDependencies: ["input", "button", "card"],
|
|
||||||
dependencies: ["zod"],
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
path: "blocks/dashboard-01/page.tsx",
|
|
||||||
type: "registry:page",
|
|
||||||
target: "app/dashboard/page.tsx",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "blocks/dashboard-01/components/hello-world.tsx",
|
|
||||||
type: "registry:component",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "blocks/dashboard-01/components/example-card.tsx",
|
|
||||||
type: "registry:component",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "blocks/dashboard-01/hooks/use-hello-world.ts",
|
|
||||||
type: "registry:hook",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "blocks/dashboard-01/lib/format-date.ts",
|
|
||||||
type: "registry:lib",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
categories: ["dashboard"],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
```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
|
|
||||||
```
|
|
||||||
|
|
||||||
```diff showLineNumbers
|
|
||||||
- @plugin 'tailwindcss-animate';
|
|
||||||
+ @import "tw-animate-css";
|
|
||||||
```
|
|
||||||
|
|
||||||
## CSS Variables
|
|
||||||
|
|
||||||
```tsx /bg-background/ /text-foreground/
|
|
||||||
<div className="bg-background text-foreground" />
|
|
||||||
```
|
|
||||||
|
|
||||||
To use CSS variables for theming set `tailwind.cssVariables` to `true` in your `components.json` file.
|
|
||||||
|
|
||||||
```json {8} title="components.json"
|
|
||||||
{
|
|
||||||
"style": "default",
|
|
||||||
"rsc": true,
|
|
||||||
"tailwind": {
|
|
||||||
"config": "",
|
|
||||||
"css": "app/globals.css",
|
|
||||||
"baseColor": "neutral",
|
|
||||||
"cssVariables": true
|
|
||||||
},
|
|
||||||
"aliases": {
|
|
||||||
"components": "@/components",
|
|
||||||
"utils": "@/lib/utils",
|
|
||||||
"ui": "@/components/ui",
|
|
||||||
"lib": "@/lib",
|
|
||||||
"hooks": "@/hooks"
|
|
||||||
},
|
|
||||||
"iconLibrary": "lucide"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Utility classes
|
|
||||||
|
|
||||||
```tsx /bg-zinc-950/ /text-zinc-50/ /dark:bg-white/ /dark:text-zinc-950/
|
|
||||||
<div className="bg-zinc-950 dark:bg-white" />
|
|
||||||
```
|
|
||||||
|
|
||||||
To use utility classes for theming set `tailwind.cssVariables` to `false` in your `components.json` file.
|
|
||||||
|
|
||||||
```json {8} title="components.json"
|
|
||||||
{
|
|
||||||
"style": "default",
|
|
||||||
"rsc": true,
|
|
||||||
"tailwind": {
|
|
||||||
"config": "",
|
|
||||||
"css": "app/globals.css",
|
|
||||||
"baseColor": "slate",
|
|
||||||
"cssVariables": false
|
|
||||||
},
|
|
||||||
"aliases": {
|
|
||||||
"components": "@/components",
|
|
||||||
"utils": "@/lib/utils",
|
|
||||||
"ui": "@/components/ui",
|
|
||||||
"lib": "@/lib",
|
|
||||||
"hooks": "@/hooks"
|
|
||||||
},
|
|
||||||
"iconLibrary": "lucide"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -15,7 +15,7 @@ To use CSS variables for theming set `tailwind.cssVariables` to `true` in your `
|
|||||||
|
|
||||||
```json {8} title="components.json" showLineNumbers
|
```json {8} title="components.json" showLineNumbers
|
||||||
{
|
{
|
||||||
"style": "default",
|
"style": "new-york",
|
||||||
"rsc": true,
|
"rsc": true,
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "",
|
"config": "",
|
||||||
@@ -44,7 +44,7 @@ To use utility classes for theming set `tailwind.cssVariables` to `false` in you
|
|||||||
|
|
||||||
```json {8} title="components.json" showLineNumbers
|
```json {8} title="components.json" showLineNumbers
|
||||||
{
|
{
|
||||||
"style": "default",
|
"style": "new-york",
|
||||||
"rsc": true,
|
"rsc": true,
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "",
|
"config": "",
|
||||||
@@ -52,14 +52,14 @@ To use utility classes for theming set `tailwind.cssVariables` to `false` in you
|
|||||||
"baseColor": "neutral",
|
"baseColor": "neutral",
|
||||||
"cssVariables": false
|
"cssVariables": false
|
||||||
},
|
},
|
||||||
|
"iconLibrary": "lucide",
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"components": "@/components",
|
"components": "@/components",
|
||||||
"utils": "@/lib/utils",
|
"utils": "@/lib/utils",
|
||||||
"ui": "@/components/ui",
|
"ui": "@/components/ui",
|
||||||
"lib": "@/lib",
|
"lib": "@/lib",
|
||||||
"hooks": "@/hooks"
|
"hooks": "@/hooks"
|
||||||
},
|
}
|
||||||
"iconLibrary": "lucide"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ Here's the list of variables available for customization:
|
|||||||
|
|
||||||
## Adding new colors
|
## Adding new colors
|
||||||
|
|
||||||
To add new colors, you need to add them to your CSS file and to your `tailwind.config.js` file.
|
To add new colors, you need to add them to your CSS file under the `:root` and `dark` pseudo-classes. Then, use the `@theme inline` directive to make the colors available as CSS variables.
|
||||||
|
|
||||||
```css title="app/globals.css" showLineNumbers
|
```css title="app/globals.css" showLineNumbers
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
@@ -108,12 +108,40 @@ To use the Persian calendar, edit `components/ui/calendar.tsx` and replace `reac
|
|||||||
description="A Persian calendar."
|
description="A Persian calendar."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
## Selected Date (With TimeZone)
|
||||||
|
|
||||||
|
The Calendar component accepts a `timeZone` prop to ensure dates are displayed and selected in the user's local timezone.
|
||||||
|
|
||||||
|
```tsx showLineNumbers
|
||||||
|
export function CalendarWithTimezone() {
|
||||||
|
const [date, setDate] = React.useState<Date | undefined>(undefined)
|
||||||
|
const [timeZone, setTimeZone] = React.useState<string | undefined>(undefined)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setTimeZone(Intl.DateTimeFormat().resolvedOptions().timeZone)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={date}
|
||||||
|
onSelect={setDate}
|
||||||
|
timeZone={timeZone}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** If you notice a selected date offset (for example, selecting the 20th highlights the 19th), make sure the `timeZone` prop is set to the user's local timezone.
|
||||||
|
|
||||||
|
**Why client-side?** The timezone is detected using `Intl.DateTimeFormat().resolvedOptions().timeZone` inside a `useEffect` to ensure compatibility with server-side rendering. Detecting the timezone during render would cause hydration mismatches, as the server and client may be in different timezones.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Range Calendar
|
### Range Calendar
|
||||||
|
|
||||||
<ComponentPreview
|
<ComponentPreview
|
||||||
name="calendar-02"
|
name="calendar-05"
|
||||||
title="Range Calendar"
|
title="Range Calendar"
|
||||||
description="A calendar showing the current date and range selection."
|
description="A calendar showing the current date and range selection."
|
||||||
className="**:[.preview]:h-auto lg:**:[.preview]:h-[450px]"
|
className="**:[.preview]:h-auto lg:**:[.preview]:h-[450px]"
|
||||||
@@ -153,9 +181,36 @@ This component uses the `chrono-node` library to parse natural language dates.
|
|||||||
description="A calendar with natural language picker."
|
description="A calendar with natural language picker."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
### Form
|
### Custom Cell Size
|
||||||
|
|
||||||
<ComponentPreview name="calendar-form" />
|
<ComponentPreview
|
||||||
|
name="calendar-18"
|
||||||
|
title="Custom Cell Size"
|
||||||
|
description="A calendar with custom cell size that's responsive."
|
||||||
|
className="**:[.preview]:h-[560px]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
You can customize the size of calendar cells using the `--cell-size` CSS variable. You can also make it responsive by using breakpoint-specific values:
|
||||||
|
|
||||||
|
```tsx showLineNumbers
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={date}
|
||||||
|
onSelect={setDate}
|
||||||
|
className="rounded-lg border [--cell-size:--spacing(11)] md:[--cell-size:--spacing(12)]"
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use fixed values:
|
||||||
|
|
||||||
|
```tsx showLineNumbers
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={date}
|
||||||
|
onSelect={setDate}
|
||||||
|
className="rounded-lg border [--cell-size:2.75rem] md:[--cell-size:3rem]"
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
## Upgrade Guide
|
## Upgrade Guide
|
||||||
|
|
||||||
@@ -289,7 +344,10 @@ function Calendar({
|
|||||||
defaultClassNames.week_number
|
defaultClassNames.week_number
|
||||||
),
|
),
|
||||||
day: cn(
|
day: cn(
|
||||||
"group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
|
"relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
|
||||||
|
props.showWeekNumber
|
||||||
|
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
|
||||||
|
: "[&:first-child[data-selected=true]_button]:rounded-l-md",
|
||||||
defaultClassNames.day
|
defaultClassNames.day
|
||||||
),
|
),
|
||||||
range_start: cn(
|
range_start: cn(
|
||||||
@@ -417,3 +475,25 @@ npx shadcn@latest add calendar-02
|
|||||||
```
|
```
|
||||||
|
|
||||||
This will install the latest version of the calendar blocks.
|
This will install the latest version of the calendar blocks.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### 2025-10-26 Fixed day radius with week numbers
|
||||||
|
|
||||||
|
We have fixed an issue where the selected day's left border radius was not applied correctly when week numbers were displayed. The fix ensures that when `showWeekNumber` is enabled, the first day (which is the second child due to the week number column) correctly receives the rounded left border.
|
||||||
|
|
||||||
|
To apply this fix, edit `components/ui/calendar.tsx` and update the `day` class in `classNames`:
|
||||||
|
|
||||||
|
```tsx showLineNumbers title="components/ui/calendar.tsx" {5-7}
|
||||||
|
classNames={{
|
||||||
|
// ... other classNames
|
||||||
|
day: cn(
|
||||||
|
"relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
|
||||||
|
props.showWeekNumber
|
||||||
|
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
|
||||||
|
: "[&:first-child[data-selected=true]_button]:rounded-l-md",
|
||||||
|
defaultClassNames.day
|
||||||
|
),
|
||||||
|
// ... other classNames
|
||||||
|
}}
|
||||||
|
```
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ We designed the `chart` component with composition in mind. **You build your cha
|
|||||||
```tsx showLineNumbers /ChartContainer/ /ChartTooltipContent/
|
```tsx showLineNumbers /ChartContainer/ /ChartTooltipContent/
|
||||||
import { Bar, BarChart } from "recharts"
|
import { Bar, BarChart } from "recharts"
|
||||||
|
|
||||||
import { ChartContainer, ChartTooltipContent } from "@/components/ui/charts"
|
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
|
||||||
|
|
||||||
export function MyChart() {
|
export function MyChart() {
|
||||||
return (
|
return (
|
||||||
@@ -193,7 +193,7 @@ You can now build your chart using Recharts components.
|
|||||||
|
|
||||||
<Callout className="mt-4 bg-amber-50 border-amber-200 dark:bg-amber-950/50 dark:border-amber-950">
|
<Callout className="mt-4 bg-amber-50 border-amber-200 dark:bg-amber-950/50 dark:border-amber-950">
|
||||||
|
|
||||||
**Important:** Remember to set a `min-h-[VALUE]` on the `ChartContainer` component. This is required for the chart be responsive.
|
**Important:** Remember to set a `min-h-[VALUE]` on the `ChartContainer` component. This is required for the chart to be responsive.
|
||||||
|
|
||||||
</Callout>
|
</Callout>
|
||||||
|
|
||||||
@@ -370,7 +370,7 @@ The chart config is where you define the labels, icons and colors for a chart.
|
|||||||
|
|
||||||
It is intentionally decoupled from chart data.
|
It is intentionally decoupled from chart data.
|
||||||
|
|
||||||
This allows you to share config and color tokens between charts. It can also works independently for cases where your data or color tokens live remotely or in a different format.
|
This allows you to share config and color tokens between charts. It can also work independently for cases where your data or color tokens live remotely or in a different format.
|
||||||
|
|
||||||
```tsx showLineNumbers /ChartConfig/
|
```tsx showLineNumbers /ChartConfig/
|
||||||
import { Monitor } from "lucide-react"
|
import { Monitor } from "lucide-react"
|
||||||
@@ -394,7 +394,7 @@ const chartConfig = {
|
|||||||
|
|
||||||
## Theming
|
## Theming
|
||||||
|
|
||||||
Charts has built-in support for theming. You can use css variables (recommended) or color values in any color format, such as hex, hsl or oklch.
|
Charts have built-in support for theming. You can use css variables (recommended) or color values in any color format, such as hex, hsl or oklch.
|
||||||
|
|
||||||
### CSS Variables
|
### CSS Variables
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ links:
|
|||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
Drawer is built on top of [Vaul](https://github.com/emilkowalski/vaul) by [emilkowalski\_](https://twitter.com/emilkowalski_).
|
Drawer is built on top of [Vaul](https://github.com/emilkowalski/vaul) by [emilkowalski](https://twitter.com/emilkowalski).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Empty
|
title: Empty
|
||||||
description: Use the Empty component to display a empty state.
|
description: Use the Empty component to display an empty state.
|
||||||
component: true
|
component: true
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ import {
|
|||||||
|
|
||||||
### Outline
|
### Outline
|
||||||
|
|
||||||
Use the `border` utility class to create a outline empty state.
|
Use the `border` utility class to create an outline empty state.
|
||||||
|
|
||||||
<ComponentPreview
|
<ComponentPreview
|
||||||
name="empty-outline"
|
name="empty-outline"
|
||||||
|
|||||||
@@ -4,3 +4,7 @@ description: Here you can find all the components available in the library. We a
|
|||||||
---
|
---
|
||||||
|
|
||||||
<ComponentsList />
|
<ComponentsList />
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Can't find what you need? Try the [registry directory](/docs/directory) for community-maintained components.
|
||||||
|
|||||||
205
apps/v4/content/docs/components/native-select.mdx
Normal file
205
apps/v4/content/docs/components/native-select.mdx
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
---
|
||||||
|
title: Native Select
|
||||||
|
description: A styled native HTML select element with consistent design system integration.
|
||||||
|
component: true
|
||||||
|
---
|
||||||
|
|
||||||
|
import { InfoIcon } from "lucide-react"
|
||||||
|
|
||||||
|
<Callout variant="info" icon={<InfoIcon className="!translate-y-[3px]" />}>
|
||||||
|
For a styled select component, see the [Select](/docs/components/select)
|
||||||
|
component.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
<ComponentPreview name="native-select-demo" />
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
<CodeTabs>
|
||||||
|
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="cli">CLI</TabsTrigger>
|
||||||
|
<TabsTrigger value="manual">Manual</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="cli">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest add native-select
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="manual">
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
<Step>Copy and paste the following code into your project.</Step>
|
||||||
|
|
||||||
|
<ComponentSource name="native-select" title="components/ui/native-select.tsx" />
|
||||||
|
|
||||||
|
<Step>Update the import paths to match your project setup.</Step>
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
</CodeTabs>
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```tsx showLineNumbers
|
||||||
|
import {
|
||||||
|
NativeSelect,
|
||||||
|
NativeSelectOptGroup,
|
||||||
|
NativeSelectOption,
|
||||||
|
} from "@/components/ui/native-select"
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx showLineNumbers
|
||||||
|
<NativeSelect>
|
||||||
|
<NativeSelectOption value="">Select a fruit</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="apple">Apple</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="banana">Banana</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="blueberry">Blueberry</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="grapes" disabled>
|
||||||
|
Grapes
|
||||||
|
</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="pineapple">Pineapple</NativeSelectOption>
|
||||||
|
</NativeSelect>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### With Groups
|
||||||
|
|
||||||
|
Organize options using `NativeSelectOptGroup` for better categorization.
|
||||||
|
|
||||||
|
<ComponentPreview name="native-select-groups" />
|
||||||
|
|
||||||
|
```tsx showLineNumbers
|
||||||
|
<NativeSelect>
|
||||||
|
<NativeSelectOption value="">Select a food</NativeSelectOption>
|
||||||
|
<NativeSelectOptGroup label="Fruits">
|
||||||
|
<NativeSelectOption value="apple">Apple</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="banana">Banana</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="blueberry">Blueberry</NativeSelectOption>
|
||||||
|
</NativeSelectOptGroup>
|
||||||
|
<NativeSelectOptGroup label="Vegetables">
|
||||||
|
<NativeSelectOption value="carrot">Carrot</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="broccoli">Broccoli</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="spinach">Spinach</NativeSelectOption>
|
||||||
|
</NativeSelectOptGroup>
|
||||||
|
</NativeSelect>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disabled State
|
||||||
|
|
||||||
|
Disable individual options or the entire select component.
|
||||||
|
|
||||||
|
<ComponentPreview name="native-select-disabled" />
|
||||||
|
|
||||||
|
### Invalid State
|
||||||
|
|
||||||
|
Show validation errors with the `aria-invalid` attribute and error styling.
|
||||||
|
|
||||||
|
<ComponentPreview name="native-select-invalid" />
|
||||||
|
|
||||||
|
```tsx showLineNumbers
|
||||||
|
<NativeSelect aria-invalid="true">
|
||||||
|
<NativeSelectOption value="">Select a country</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="us">United States</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="uk">United Kingdom</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="ca">Canada</NativeSelectOption>
|
||||||
|
</NativeSelect>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Form Integration
|
||||||
|
|
||||||
|
Use with form libraries like React Hook Form for controlled components.
|
||||||
|
|
||||||
|
<ComponentPreview name="native-select-form" />
|
||||||
|
|
||||||
|
### Input Group Integration
|
||||||
|
|
||||||
|
Combine with `InputGroup` for complex input layouts.
|
||||||
|
|
||||||
|
<ComponentPreview name="native-select-input-group" />
|
||||||
|
|
||||||
|
## Native Select vs Select
|
||||||
|
|
||||||
|
- Use `NativeSelect` when you need native browser behavior, better performance, or mobile-optimized dropdowns.
|
||||||
|
- Use `Select` when you need custom styling, animations, or complex interactions.
|
||||||
|
|
||||||
|
The `NativeSelect` component provides native HTML select functionality with consistent styling that matches your design system.
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
- The component maintains all native HTML select accessibility features.
|
||||||
|
- Screen readers can navigate through options using arrow keys.
|
||||||
|
- The chevron icon is marked as `aria-hidden="true"` to avoid duplication.
|
||||||
|
- Use `aria-label` or `aria-labelledby` for additional context when needed.
|
||||||
|
|
||||||
|
```tsx showLineNumbers
|
||||||
|
<NativeSelect aria-label="Choose your preferred language">
|
||||||
|
<NativeSelectOption value="en">English</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="es">Spanish</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="fr">French</NativeSelectOption>
|
||||||
|
</NativeSelect>
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### NativeSelect
|
||||||
|
|
||||||
|
The main select component that wraps the native HTML select element.
|
||||||
|
|
||||||
|
| Prop | Type | Default |
|
||||||
|
| ----------- | -------- | ------- |
|
||||||
|
| `className` | `string` | |
|
||||||
|
|
||||||
|
All other props are passed through to the underlying `<select>` element.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<NativeSelect>
|
||||||
|
<NativeSelectOption value="option1">Option 1</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="option2">Option 2</NativeSelectOption>
|
||||||
|
</NativeSelect>
|
||||||
|
```
|
||||||
|
|
||||||
|
### NativeSelectOption
|
||||||
|
|
||||||
|
Represents an individual option within the select.
|
||||||
|
|
||||||
|
| Prop | Type | Default |
|
||||||
|
| ----------- | --------- | ------- |
|
||||||
|
| `value` | `string` | |
|
||||||
|
| `disabled` | `boolean` | `false` |
|
||||||
|
| `className` | `string` | |
|
||||||
|
|
||||||
|
All other props are passed through to the underlying `<option>` element.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<NativeSelectOption value="apple">Apple</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="banana" disabled>
|
||||||
|
Banana
|
||||||
|
</NativeSelectOption>
|
||||||
|
```
|
||||||
|
|
||||||
|
### NativeSelectOptGroup
|
||||||
|
|
||||||
|
Groups related options together for better organization.
|
||||||
|
|
||||||
|
| Prop | Type | Default |
|
||||||
|
| ----------- | --------- | ------- |
|
||||||
|
| `label` | `string` | |
|
||||||
|
| `disabled` | `boolean` | `false` |
|
||||||
|
| `className` | `string` | |
|
||||||
|
|
||||||
|
All other props are passed through to the underlying `<optgroup>` element.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<NativeSelectOptGroup label="Fruits">
|
||||||
|
<NativeSelectOption value="apple">Apple</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="banana">Banana</NativeSelectOption>
|
||||||
|
</NativeSelectOptGroup>
|
||||||
|
```
|
||||||
@@ -7,7 +7,10 @@ links:
|
|||||||
api: https://www.radix-ui.com/docs/primitives/components/navigation-menu#api-reference
|
api: https://www.radix-ui.com/docs/primitives/components/navigation-menu#api-reference
|
||||||
---
|
---
|
||||||
|
|
||||||
<ComponentPreview name="navigation-menu-demo" />
|
<ComponentPreview
|
||||||
|
name="navigation-menu-demo"
|
||||||
|
className="[&_.preview]:!items-start [&_.preview]:p-4 [&_.preview]:pt-8 md:[&_.preview]:pt-16"
|
||||||
|
/>
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ links:
|
|||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
Sonner is built and maintained by [emilkowalski\_](https://twitter.com/emilkowalski_).
|
Sonner is built and maintained by [emilkowalski](https://twitter.com/emilkowalski).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ links:
|
|||||||
api: https://www.radix-ui.com/docs/primitives/components/toggle-group#api-reference
|
api: https://www.radix-ui.com/docs/primitives/components/toggle-group#api-reference
|
||||||
---
|
---
|
||||||
|
|
||||||
<ComponentPreview
|
<ComponentPreview name="toggle-group-spacing" />
|
||||||
name="toggle-group-demo"
|
|
||||||
description="A toggle group with three items."
|
|
||||||
/>
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -66,13 +63,6 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Default
|
|
||||||
|
|
||||||
<ComponentPreview
|
|
||||||
name="toggle-group-demo"
|
|
||||||
description="A toggle group with three items."
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Outline
|
### Outline
|
||||||
|
|
||||||
<ComponentPreview
|
<ComponentPreview
|
||||||
@@ -107,3 +97,42 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
|
|||||||
name="toggle-group-disabled"
|
name="toggle-group-disabled"
|
||||||
description="A disabled toggle group."
|
description="A disabled toggle group."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
### Spacing
|
||||||
|
|
||||||
|
Use `spacing={2}` to add spacing between toggle group items.
|
||||||
|
|
||||||
|
<ComponentPreview
|
||||||
|
name="toggle-group-spacing"
|
||||||
|
description="A toggle group with spacing."
|
||||||
|
/>
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### ToggleGroup
|
||||||
|
|
||||||
|
The main component that wraps toggle group items.
|
||||||
|
|
||||||
|
| Prop | Type | Default |
|
||||||
|
| ----------- | --------------------------- | ----------- |
|
||||||
|
| `type` | `"single" \| "multiple"` | `"single"` |
|
||||||
|
| `variant` | `"default" \| "outline"` | `"default"` |
|
||||||
|
| `size` | `"default" \| "sm" \| "lg"` | `"default"` |
|
||||||
|
| `spacing` | `number` | `0` |
|
||||||
|
| `className` | `string` | |
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<ToggleGroup type="single" variant="outline" size="sm">
|
||||||
|
<ToggleGroupItem value="a">A</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="b">B</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
### ToggleGroupItem
|
||||||
|
|
||||||
|
Individual toggle items within a toggle group. Remember to add an `aria-label` to each item for accessibility.
|
||||||
|
|
||||||
|
| Prop | Type | Default |
|
||||||
|
| ----------- | -------- | -------- |
|
||||||
|
| `value` | `string` | Required |
|
||||||
|
| `className` | `string` | |
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ npx create-tsrouter-app@latest my-app --template file-router --tailwind --add-on
|
|||||||
You can now start adding components to your project.
|
You can now start adding components to your project.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx shadcn@canary add button
|
npx shadcn@latest add button
|
||||||
```
|
```
|
||||||
|
|
||||||
The command above will add the `Button` component to your project. You can then import it like this:
|
The command above will add the `Button` component to your project. You can then import it like this:
|
||||||
|
|||||||
@@ -7,109 +7,18 @@ description: Install and configure shadcn/ui for TanStack Start.
|
|||||||
|
|
||||||
### Create project
|
### Create project
|
||||||
|
|
||||||
Start by creating a new TanStack Start project by following the [Build a Project from Scratch](https://tanstack.com/start/latest/docs/framework/react/build-from-scratch) guide on the TanStack Start website.
|
Run the following command to create a new TanStack Start project with shadcn/ui:
|
||||||
|
|
||||||
**Do not add Tailwind yet. We'll install Tailwind v4 in the next step.**
|
|
||||||
|
|
||||||
### Add Tailwind
|
|
||||||
|
|
||||||
Install `tailwindcss` and its dependencies.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install tailwindcss @tailwindcss/postcss postcss
|
npm create @tanstack/start@latest --tailwind --add-ons shadcn
|
||||||
```
|
```
|
||||||
|
|
||||||
### Create postcss.config.ts
|
### Add Components
|
||||||
|
|
||||||
Create a `postcss.config.ts` file at the root of your project.
|
|
||||||
|
|
||||||
```ts title="postcss.config.ts" showLineNumbers
|
|
||||||
export default {
|
|
||||||
plugins: {
|
|
||||||
"@tailwindcss/postcss": {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create `app/styles/app.css`
|
|
||||||
|
|
||||||
Create an `app.css` file in the `app/styles` directory and import `tailwindcss`
|
|
||||||
|
|
||||||
```css title="app/styles/app.css"
|
|
||||||
@import "tailwindcss" source("../");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Import `app.css`
|
|
||||||
|
|
||||||
```tsx title="app/routes/__root.tsx" showLineNumbers {5,21-26} showLineNumbers
|
|
||||||
import type { ReactNode } from "react"
|
|
||||||
import { createRootRoute, Outlet } from "@tanstack/react-router"
|
|
||||||
import { Meta, Scripts } from "@tanstack/start"
|
|
||||||
|
|
||||||
import appCss from "@/styles/app.css?url"
|
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
|
||||||
head: () => ({
|
|
||||||
meta: [
|
|
||||||
{
|
|
||||||
charSet: "utf-8",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "viewport",
|
|
||||||
content: "width=device-width, initial-scale=1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "TanStack Start Starter",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
rel: "stylesheet",
|
|
||||||
href: appCss,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
component: RootComponent,
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Edit tsconfig.json file
|
|
||||||
|
|
||||||
Add the following code to the `tsconfig.json` file to resolve paths.
|
|
||||||
|
|
||||||
```ts title="tsconfig.json" showLineNumbers {9-12}
|
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"moduleResolution": "Bundler",
|
|
||||||
"module": "ESNext",
|
|
||||||
"target": "ES2022",
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./app/*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run the CLI
|
|
||||||
|
|
||||||
Run the `shadcn` init command to setup your project:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@canary init
|
|
||||||
```
|
|
||||||
|
|
||||||
This will create a `components.json` file in the root of your project and configure CSS variables inside `app/styles/app.css`.
|
|
||||||
|
|
||||||
### That's it
|
|
||||||
|
|
||||||
You can now start adding components to your project.
|
You can now start adding components to your project.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx shadcn@canary add button
|
npx shadcn@latest add button
|
||||||
```
|
```
|
||||||
|
|
||||||
The command above will add the `Button` component to your project. You can then import it like this:
|
The command above will add the `Button` component to your project. You can then import it like this:
|
||||||
@@ -117,10 +26,7 @@ The command above will add the `Button` component to your project. You can then
|
|||||||
```tsx title="app/routes/index.tsx" showLineNumbers {1,6}
|
```tsx title="app/routes/index.tsx" showLineNumbers {1,6}
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
|
||||||
function Home() {
|
function App() {
|
||||||
const router = useRouter()
|
|
||||||
const state = Route.useLoaderData()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button>Click me</Button>
|
<Button>Click me</Button>
|
||||||
@@ -130,3 +36,9 @@ function Home() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
|
If you want to add all `shadcn/ui` components, you can run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest add --all
|
||||||
|
```
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ You can read more about the registry item schema and file types in the [registry
|
|||||||
### Install the shadcn CLI
|
### Install the shadcn CLI
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install shadcn@canary
|
npm install shadcn@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add a build script
|
### Add a build script
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: Index
|
title: Add a Registry
|
||||||
description: Open Source Registry Index
|
description: Open Source Registry Index
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -11,16 +11,9 @@ You can see the full list at [https://ui.shadcn.com/r/registries.json](https://u
|
|||||||
|
|
||||||
## Adding a Registry
|
## Adding a Registry
|
||||||
|
|
||||||
You can submit a PR to add a registry to the index by adding it to the [registries.json](https://github.com/shadcn-ui/ui/blob/main/apps/v4/public/r/registries.json) file.
|
You can open an issue to add a registry to the index by filling out the [registry directory issue template](https://github.com/shadcn-ui/ui/issues/new?template=registry_directory.yml).
|
||||||
|
|
||||||
Here's an example of how to add a registry to the index:
|
Once you have submitted your issue, it will be validated and reviewed by the team.
|
||||||
|
|
||||||
```json title="registries.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"@acme": "https://registry.acme.com/r/{name}.json",
|
|
||||||
"@example": "https://example.com/r/{name}"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
@@ -65,15 +58,3 @@ Here's an example of a valid registry:
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Validation
|
|
||||||
|
|
||||||
At the root of the `shadcn/ui` project, you can run the following command to validate the `registries.json` file.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm validate:registries
|
|
||||||
```
|
|
||||||
|
|
||||||
This will validate the registries.json file and output any errors.
|
|
||||||
|
|
||||||
Once you have submitted your PR, it will be validated and reviewed by the team.
|
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
import { dirname } from "path"
|
import { defineConfig, globalIgnores } from "eslint/config"
|
||||||
import { fileURLToPath } from "url"
|
import nextVitals from "eslint-config-next/core-web-vitals"
|
||||||
import { FlatCompat } from "@eslint/eslintrc"
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
const eslintConfig = defineConfig([
|
||||||
const __dirname = dirname(__filename)
|
...nextVitals,
|
||||||
|
globalIgnores([
|
||||||
const compat = new FlatCompat({
|
"node_modules/**",
|
||||||
baseDirectory: __dirname,
|
".next/**",
|
||||||
})
|
"out/**",
|
||||||
|
"build/**",
|
||||||
const eslintConfig = [
|
"next-env.d.ts",
|
||||||
...compat.config({
|
".source/**",
|
||||||
extends: ["next/core-web-vitals", "next/typescript"],
|
]),
|
||||||
|
{
|
||||||
rules: {
|
rules: {
|
||||||
"@next/next/no-duplicate-head": "off",
|
"react-hooks/incompatible-library": "off",
|
||||||
|
"react-hooks/purity": "off",
|
||||||
|
"@next/next/no-html-link-for-pages": "off",
|
||||||
|
"@next/next/no-img-element": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
]
|
])
|
||||||
|
|
||||||
export default eslintConfig
|
export default eslintConfig
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { useEffect, useState } from "react"
|
|
||||||
|
|
||||||
export function useIsMac() {
|
|
||||||
const [isMac, setIsMac] = useState(true)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsMac(navigator.platform.toUpperCase().includes("MAC"))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return isMac
|
|
||||||
}
|
|
||||||
@@ -61,7 +61,10 @@ const Layout = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const attrs = !value ? ["layout-fixed", "layout-full"] : Object.values(value)
|
const attrs = React.useMemo(
|
||||||
|
() => (!value ? ["layout-fixed", "layout-full"] : Object.values(value)),
|
||||||
|
[value]
|
||||||
|
)
|
||||||
|
|
||||||
const applyLayout = React.useCallback(
|
const applyLayout = React.useCallback(
|
||||||
(layout: Layout) => {
|
(layout: Layout) => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function useIsMobile(mobileBreakpoint = 768) {
|
|||||||
mql.addEventListener("change", onChange)
|
mql.addEventListener("change", onChange)
|
||||||
setIsMobile(window.innerWidth < mobileBreakpoint)
|
setIsMobile(window.innerWidth < mobileBreakpoint)
|
||||||
return () => mql.removeEventListener("change", onChange)
|
return () => mql.removeEventListener("change", onChange)
|
||||||
}, [])
|
}, [mobileBreakpoint])
|
||||||
|
|
||||||
return !!isMobile
|
return !!isMobile
|
||||||
}
|
}
|
||||||
|
|||||||
39
apps/v4/hooks/use-search-registry.ts
Normal file
39
apps/v4/hooks/use-search-registry.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { debounce, useQueryState } from "nuqs"
|
||||||
|
|
||||||
|
import globalRegistries from "@/registry/directory.json"
|
||||||
|
|
||||||
|
const normalizeQuery = (query: string) =>
|
||||||
|
query.toLowerCase().replaceAll(" ", "").replaceAll("@", "")
|
||||||
|
|
||||||
|
function finderFn<T extends (typeof globalRegistries)[0]>(
|
||||||
|
registry: T,
|
||||||
|
query: string
|
||||||
|
) {
|
||||||
|
const normalizedName = normalizeQuery(registry.name)
|
||||||
|
const normalizedDecription = normalizeQuery(registry.description)
|
||||||
|
const normalizedQuery = normalizeQuery(query)
|
||||||
|
|
||||||
|
return (
|
||||||
|
normalizedName.includes(normalizedQuery) ||
|
||||||
|
normalizedDecription.includes(normalizedQuery)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchDirectory = (query: string | null) => {
|
||||||
|
if (!query) return globalRegistries
|
||||||
|
|
||||||
|
return globalRegistries.filter((registry) => finderFn(registry, query))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSearchRegistry = () => {
|
||||||
|
const [query, setQuery] = useQueryState("q", {
|
||||||
|
defaultValue: "",
|
||||||
|
limitUrlUpdates: debounce(250),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
query,
|
||||||
|
registries: searchDirectory(query),
|
||||||
|
setQuery,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,9 +23,31 @@ export async function getAllBlocks(
|
|||||||
categories: string[] = []
|
categories: string[] = []
|
||||||
) {
|
) {
|
||||||
const { Index } = await import("@/registry/__index__")
|
const { Index } = await import("@/registry/__index__")
|
||||||
const index = z.record(registryItemSchema).parse(Index)
|
|
||||||
|
|
||||||
return Object.values(index).filter(
|
// Collect all blocks from all styles.
|
||||||
|
const allBlocks: z.infer<typeof registryItemSchema>[] = []
|
||||||
|
|
||||||
|
for (const style in Index) {
|
||||||
|
const styleIndex = Index[style]
|
||||||
|
if (typeof styleIndex === "object" && styleIndex !== null) {
|
||||||
|
for (const itemName in styleIndex) {
|
||||||
|
const item = styleIndex[itemName]
|
||||||
|
allBlocks.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate each block.
|
||||||
|
const validatedBlocks = allBlocks
|
||||||
|
.map((block) => {
|
||||||
|
const result = registryItemSchema.safeParse(block)
|
||||||
|
return result.success ? result.data : null
|
||||||
|
})
|
||||||
|
.filter(
|
||||||
|
(block): block is z.infer<typeof registryItemSchema> => block !== null
|
||||||
|
)
|
||||||
|
|
||||||
|
return validatedBlocks.filter(
|
||||||
(block) =>
|
(block) =>
|
||||||
types.includes(block.type) &&
|
types.includes(block.type) &&
|
||||||
(categories.length === 0 ||
|
(categories.length === 0 ||
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import { colors } from "@/registry/registry-colors"
|
import { colors } from "@/registry/colors"
|
||||||
|
|
||||||
const colorSchema = z.object({
|
const colorSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ export const siteConfig = {
|
|||||||
href: "/charts/area",
|
href: "/charts/area",
|
||||||
label: "Charts",
|
label: "Charts",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
href: "/docs/directory",
|
||||||
|
label: "Directory",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
href: "/themes",
|
href: "/themes",
|
||||||
label: "Themes",
|
label: "Themes",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const PAGES_NEW = [
|
|||||||
"/docs/components/item",
|
"/docs/components/item",
|
||||||
"/docs/components/kbd",
|
"/docs/components/kbd",
|
||||||
"/docs/components/spinner",
|
"/docs/components/spinner",
|
||||||
|
"/docs/components/native-select",
|
||||||
]
|
]
|
||||||
|
|
||||||
export const PAGES_UPDATED = ["/docs/components/button"]
|
export const PAGES_UPDATED = ["/docs/components/button"]
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const eventSchema = z.object({
|
|||||||
"copy_chart_data",
|
"copy_chart_data",
|
||||||
"copy_color",
|
"copy_color",
|
||||||
"set_layout",
|
"set_layout",
|
||||||
|
"search_query",
|
||||||
]),
|
]),
|
||||||
// declare type AllowedPropertyValues = string | number | boolean | null
|
// declare type AllowedPropertyValues = string | number | boolean | null
|
||||||
properties: z
|
properties: z
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
|
|
||||||
import { Index } from "@/registry/__index__"
|
import { Index } from "@/registry/__index__"
|
||||||
|
import { type Style } from "@/registry/styles"
|
||||||
|
|
||||||
export function processMdxForLLMs(content: string) {
|
export function processMdxForLLMs(content: string, style: Style["name"]) {
|
||||||
const componentPreviewRegex =
|
const componentPreviewRegex =
|
||||||
/<ComponentPreview\s+[^>]*name="([^"]+)"[^>]*\/>/g
|
/<ComponentPreview[\s\S]*?name="([^"]+)"[\s\S]*?\/>/g
|
||||||
|
|
||||||
return content.replace(componentPreviewRegex, (match, name) => {
|
return content.replace(componentPreviewRegex, (match, name) => {
|
||||||
try {
|
try {
|
||||||
const component = Index[name]
|
const component = Index[style]?.[name]
|
||||||
if (!component?.files) {
|
if (!component?.files) {
|
||||||
return match
|
return match
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,36 @@ import { Project, ScriptKind } from "ts-morph"
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import { Index } from "@/registry/__index__"
|
import { Index } from "@/registry/__index__"
|
||||||
|
import { type Style } from "@/registry/styles"
|
||||||
|
|
||||||
export function getRegistryComponent(name: string) {
|
export function getRegistryComponent(name: string, styleName: Style["name"]) {
|
||||||
return Index[name]?.component
|
return Index[styleName]?.[name]?.component
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRegistryItem(name: string) {
|
export async function getRegistryItems(
|
||||||
const item = Index[name]
|
styleName: Style["name"],
|
||||||
|
filter?: (item: z.infer<typeof registryItemSchema>) => boolean
|
||||||
|
) {
|
||||||
|
const styleIndex = Index[styleName]
|
||||||
|
|
||||||
|
if (!styleIndex) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = Object.values(styleIndex)
|
||||||
|
|
||||||
|
const filteredEntries = filter ? entries.filter(filter) : entries
|
||||||
|
|
||||||
|
return await Promise.all(
|
||||||
|
filteredEntries.map(async (entry) => {
|
||||||
|
const item = await getRegistryItem(entry.name, styleName)
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
).then((results) => results.filter(Boolean))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getRegistryItem(name: string, styleName: Style["name"]) {
|
||||||
|
const item = Index[styleName]?.[name]
|
||||||
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { u } from "unist-builder"
|
|||||||
import { visit } from "unist-util-visit"
|
import { visit } from "unist-util-visit"
|
||||||
|
|
||||||
import { Index } from "@/registry/__index__"
|
import { Index } from "@/registry/__index__"
|
||||||
|
import { getActiveStyle } from "@/registry/styles"
|
||||||
|
|
||||||
interface UnistNode {
|
interface UnistNode {
|
||||||
type: string
|
type: string
|
||||||
@@ -26,6 +27,8 @@ export interface UnistTree {
|
|||||||
|
|
||||||
export function rehypeComponent() {
|
export function rehypeComponent() {
|
||||||
return async (tree: UnistTree) => {
|
return async (tree: UnistTree) => {
|
||||||
|
const activeStyle = await getActiveStyle()
|
||||||
|
|
||||||
visit(tree, (node: UnistNode) => {
|
visit(tree, (node: UnistNode) => {
|
||||||
// src prop overrides both name and fileName.
|
// src prop overrides both name and fileName.
|
||||||
const { value: srcPath } =
|
const { value: srcPath } =
|
||||||
@@ -111,7 +114,7 @@ export function rehypeComponent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const component = Index[name]
|
const component = Index[activeStyle.name]?.[name]
|
||||||
const src = component.files[0]?.path
|
const src = component.files[0]?.path
|
||||||
|
|
||||||
// Read the source file.
|
// Read the source file.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { docs } from "@/.source"
|
import { docs } from "@/.source"
|
||||||
import { loader } from "fumadocs-core/source"
|
import { loader } from "fumadocs-core/source"
|
||||||
|
|
||||||
export const source: ReturnType<typeof loader> = loader({
|
export const source = loader({
|
||||||
baseUrl: "/docs",
|
baseUrl: "/docs",
|
||||||
source: docs.toFumadocsSource(),
|
source: docs.toFumadocsSource(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,31 +1,5 @@
|
|||||||
export const THEMES = [
|
import { baseColors } from "@/registry/base-colors"
|
||||||
{
|
|
||||||
name: "Default",
|
export const THEMES = baseColors
|
||||||
value: "default",
|
.filter((theme) => !["slate", "stone", "gray", "zinc"].includes(theme.name))
|
||||||
},
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
{
|
|
||||||
name: "Neutral",
|
|
||||||
value: "neutral",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Stone",
|
|
||||||
value: "stone",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Zinc",
|
|
||||||
value: "zinc",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Gray",
|
|
||||||
value: "gray",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Slate",
|
|
||||||
value: "slate",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Scaled",
|
|
||||||
value: "scaled",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
export type Theme = (typeof THEMES)[number]
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { ComponentPreview } from "@/components/component-preview"
|
|||||||
import { ComponentSource } from "@/components/component-source"
|
import { ComponentSource } from "@/components/component-source"
|
||||||
import { ComponentsList } from "@/components/components-list"
|
import { ComponentsList } from "@/components/components-list"
|
||||||
import { CopyButton } from "@/components/copy-button"
|
import { CopyButton } from "@/components/copy-button"
|
||||||
|
import { DirectoryList } from "@/components/directory-list"
|
||||||
import { getIconForLanguageExtension } from "@/components/icons"
|
import { getIconForLanguageExtension } from "@/components/icons"
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
@@ -127,7 +128,6 @@ export const mdxComponents = {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
img: ({ className, alt, ...props }: React.ComponentProps<"img">) => (
|
img: ({ className, alt, ...props }: React.ComponentProps<"img">) => (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img className={cn("rounded-md", className)} alt={alt} {...props} />
|
<img className={cn("rounded-md", className)} alt={alt} {...props} />
|
||||||
),
|
),
|
||||||
hr: ({ ...props }: React.ComponentProps<"hr">) => (
|
hr: ({ ...props }: React.ComponentProps<"hr">) => (
|
||||||
@@ -280,7 +280,7 @@ export const mdxComponents = {
|
|||||||
}: React.ComponentProps<"img">) => (
|
}: React.ComponentProps<"img">) => (
|
||||||
<Image
|
<Image
|
||||||
className={cn("mt-6 rounded-md border", className)}
|
className={cn("mt-6 rounded-md border", className)}
|
||||||
src={src || ""}
|
src={(src as string) || ""}
|
||||||
width={Number(width)}
|
width={Number(width)}
|
||||||
height={Number(height)}
|
height={Number(height)}
|
||||||
alt={alt || ""}
|
alt={alt || ""}
|
||||||
@@ -344,6 +344,7 @@ export const mdxComponents = {
|
|||||||
ComponentSource,
|
ComponentSource,
|
||||||
CodeCollapsibleWrapper,
|
CodeCollapsibleWrapper,
|
||||||
ComponentsList,
|
ComponentsList,
|
||||||
|
DirectoryList,
|
||||||
Link: ({ className, ...props }: React.ComponentProps<typeof Link>) => (
|
Link: ({ className, ...props }: React.ComponentProps<typeof Link>) => (
|
||||||
<Link
|
<Link
|
||||||
className={cn("font-medium underline underline-offset-4", className)}
|
className={cn("font-medium underline underline-offset-4", className)}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
experimental: {
|
||||||
|
turbopackFileSystemCacheForDev: true,
|
||||||
|
},
|
||||||
redirects() {
|
redirects() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -72,6 +75,11 @@ const nextConfig = {
|
|||||||
destination: "/docs/mcp",
|
destination: "/docs/mcp",
|
||||||
permanent: false,
|
permanent: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: "/directory",
|
||||||
|
destination: "/docs/directory",
|
||||||
|
permanent: false,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
rewrites() {
|
rewrites() {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
"dev": "next dev --turbopack --port 4000",
|
"dev": "next dev --turbopack --port 4000",
|
||||||
"build": "pnpm --filter=shadcn build && next build",
|
"build": "pnpm --filter=shadcn build && next build",
|
||||||
"start": "next start --port 4000",
|
"start": "next start --port 4000",
|
||||||
"lint": "next lint",
|
"lint": "eslint .",
|
||||||
"lint:fix": "next lint --fix",
|
"lint:fix": "eslint --fix .",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"format:write": "prettier --write \"**/*.{ts,tsx,mdx}\" --cache",
|
"format:write": "prettier --write \"**/*.{ts,tsx,mdx}\" --cache",
|
||||||
"format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache",
|
"format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache",
|
||||||
@@ -22,10 +22,10 @@
|
|||||||
"@dnd-kit/modifiers": "^9.0.0",
|
"@dnd-kit/modifiers": "^9.0.0",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@faker-js/faker": "^8.2.0",
|
"@faker-js/faker": "^10.1.0",
|
||||||
"@hookform/resolvers": "^3.10.0",
|
"@hookform/resolvers": "^3.10.0",
|
||||||
"@radix-ui/react-accessible-icon": "^1.1.1",
|
"@radix-ui/react-accessible-icon": "^1.1.1",
|
||||||
"@radix-ui/react-accordion": "^1.2.2",
|
"@radix-ui/react-accordion": "^1.2.12",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.5",
|
"@radix-ui/react-alert-dialog": "^1.1.5",
|
||||||
"@radix-ui/react-aspect-ratio": "^1.1.1",
|
"@radix-ui/react-aspect-ratio": "^1.1.1",
|
||||||
"@radix-ui/react-avatar": "^1.1.2",
|
"@radix-ui/react-avatar": "^1.1.2",
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"@radix-ui/react-icons": "^1.3.2",
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
"@radix-ui/react-label": "^2.1.1",
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
"@radix-ui/react-menubar": "^1.1.5",
|
"@radix-ui/react-menubar": "^1.1.5",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.4",
|
"@radix-ui/react-navigation-menu": "^1.2.14",
|
||||||
"@radix-ui/react-popover": "^1.1.5",
|
"@radix-ui/react-popover": "^1.1.5",
|
||||||
"@radix-ui/react-portal": "^1.1.3",
|
"@radix-ui/react-portal": "^1.1.3",
|
||||||
"@radix-ui/react-progress": "^1.1.1",
|
"@radix-ui/react-progress": "^1.1.1",
|
||||||
@@ -51,9 +51,9 @@
|
|||||||
"@radix-ui/react-switch": "^1.1.2",
|
"@radix-ui/react-switch": "^1.1.2",
|
||||||
"@radix-ui/react-tabs": "^1.1.2",
|
"@radix-ui/react-tabs": "^1.1.2",
|
||||||
"@radix-ui/react-toast": "^1.2.5",
|
"@radix-ui/react-toast": "^1.2.5",
|
||||||
"@radix-ui/react-toggle": "^1.1.1",
|
"@radix-ui/react-toggle": "^1.1.10",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.1",
|
"@radix-ui/react-toggle-group": "^1.1.1",
|
||||||
"@radix-ui/react-tooltip": "^1.1.7",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tabler/icons-react": "^3.31.0",
|
"@tabler/icons-react": "^3.31.0",
|
||||||
"@tailwindcss/postcss": "^4.1.11",
|
"@tailwindcss/postcss": "^4.1.11",
|
||||||
"@tanstack/react-form": "^1.20.0",
|
"@tanstack/react-form": "^1.20.0",
|
||||||
@@ -67,48 +67,49 @@
|
|||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"embla-carousel-autoplay": "8.5.2",
|
"embla-carousel-autoplay": "8.5.2",
|
||||||
"embla-carousel-react": "8.5.2",
|
"embla-carousel-react": "8.5.2",
|
||||||
"fumadocs-core": "15.3.1",
|
"front-matter": "^4.0.2",
|
||||||
|
"fumadocs-core": "16.0.5",
|
||||||
"fumadocs-docgen": "2.0.0",
|
"fumadocs-docgen": "2.0.0",
|
||||||
"fumadocs-mdx": "11.6.3",
|
"fumadocs-mdx": "13.0.2",
|
||||||
"fumadocs-ui": "15.3.1",
|
"fumadocs-ui": "16.0.5",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"jotai": "^2.1.0",
|
"jotai": "^2.15.0",
|
||||||
"little-date": "^1.0.0",
|
"little-date": "^1.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lucide-react": "0.474.0",
|
"lucide-react": "0.474.0",
|
||||||
"motion": "^12.12.1",
|
"motion": "^12.12.1",
|
||||||
"next": "15.3.1",
|
"next": "16.0.7",
|
||||||
"next-themes": "0.4.6",
|
"next-themes": "0.4.6",
|
||||||
|
"nuqs": "^2.7.2",
|
||||||
"postcss": "^8.5.1",
|
"postcss": "^8.5.1",
|
||||||
"react": "19.1.0",
|
"react": "19.2.0",
|
||||||
"react-day-picker": "^9.7.0",
|
"react-day-picker": "^9.7.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.2.0",
|
||||||
"react-hook-form": "^7.62.0",
|
"react-hook-form": "^7.62.0",
|
||||||
"react-resizable-panels": "^2.1.7",
|
"react-resizable-panels": "^2.1.7",
|
||||||
"react-textarea-autosize": "^8.5.9",
|
"react-textarea-autosize": "^8.5.9",
|
||||||
"recharts": "2.15.1",
|
"recharts": "2.15.1",
|
||||||
"rehype-pretty-code": "^0.14.1",
|
"rehype-pretty-code": "^0.14.1",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"shadcn": "3.4.2",
|
"shadcn": "3.5.2",
|
||||||
"shiki": "^1.10.1",
|
"shiki": "^1.10.1",
|
||||||
"sonner": "^2.0.0",
|
"sonner": "^2.0.0",
|
||||||
"tailwind-merge": "^3.0.1",
|
"tailwind-merge": "^3.0.1",
|
||||||
"ts-morph": "18.0.0",
|
"ts-morph": "18.0.0",
|
||||||
"vaul": "1.1.2",
|
"vaul": "1.1.2",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.25.76"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
|
||||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
|
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/lodash": "^4.17.7",
|
"@types/lodash": "^4.17.7",
|
||||||
"@types/mdx": "^2.0.13",
|
"@types/mdx": "^2.0.13",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "19.1.2",
|
"@types/react": "19.2.2",
|
||||||
"@types/react-dom": "19.1.2",
|
"@types/react-dom": "19.2.2",
|
||||||
"@typescript-eslint/parser": "^8.31.0",
|
"@typescript-eslint/parser": "^8.31.0",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.3.1",
|
"eslint-config-next": "16.0.0",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
{
|
{
|
||||||
"@8bitcn": "https://8bitcn.com/r/{name}.json",
|
"@8bitcn": "https://8bitcn.com/r/{name}.json",
|
||||||
"@97cn": "https://97cn.itzik.co/r/{name}.json",
|
"@97cn": "https://97cn.itzik.co/r/{name}.json",
|
||||||
|
"@abstract": "https://build.abs.xyz/r/{name}/json",
|
||||||
|
"@abui": "https://abui.io/r/{name}.json",
|
||||||
"@aceternity": "https://ui.aceternity.com/registry/{name}.json",
|
"@aceternity": "https://ui.aceternity.com/registry/{name}.json",
|
||||||
|
"@aevr": "https://ui.aevr.space/r/{name}.json",
|
||||||
|
"@ai-blocks": "https://webllm.org/r/{name}.json",
|
||||||
"@ai-elements": "https://registry.ai-sdk.dev/{name}.json",
|
"@ai-elements": "https://registry.ai-sdk.dev/{name}.json",
|
||||||
"@alexcarpenter": "https://ui.alexcarpenter.me/r/{name}.json",
|
"@alexcarpenter": "https://ui.alexcarpenter.me/r/{name}.json",
|
||||||
|
"@algolia": "https://sitesearch.algolia.com/r/{name}.json",
|
||||||
"@alpine": "https://alpine-registry.vercel.app/r/{name}.json",
|
"@alpine": "https://alpine-registry.vercel.app/r/{name}.json",
|
||||||
|
"@aliimam": "https://aliimam.in/r/{name}.json",
|
||||||
"@animate-ui": "https://animate-ui.com/r/{name}.json",
|
"@animate-ui": "https://animate-ui.com/r/{name}.json",
|
||||||
"@assistant-ui": "https://r.assistant-ui.com/{name}.json",
|
"@assistant-ui": "https://r.assistant-ui.com/{name}.json",
|
||||||
"@austin-ui": "https://austin-ui.netlify.app/r/{name}.json",
|
"@austin-ui": "https://austin-ui.netlify.app/r/{name}.json",
|
||||||
@@ -14,40 +20,89 @@
|
|||||||
"@blocks": "https://blocks.so/r/{name}.json",
|
"@blocks": "https://blocks.so/r/{name}.json",
|
||||||
"@bucharitesh": "https://bucharitesh.in/r/{name}.json",
|
"@bucharitesh": "https://bucharitesh.in/r/{name}.json",
|
||||||
"@clerk": "https://clerk.com/r/{name}.json",
|
"@clerk": "https://clerk.com/r/{name}.json",
|
||||||
|
"@coss": "https://coss.com/ui/r/{name}.json",
|
||||||
|
"@commercn": "https://commercn.com/r/{name}.json",
|
||||||
|
"@chisom-ui": "https://chisom-ui.netlify.app/r/{name}.json",
|
||||||
|
"@creative-tim": "https://www.creative-tim.com/ui/r/{name}.json",
|
||||||
"@cult-ui": "https://cult-ui.com/r/{name}.json",
|
"@cult-ui": "https://cult-ui.com/r/{name}.json",
|
||||||
"@efferd-ui": "https://ui.efferd.com/r/{name}.json",
|
"@diceui": "https://diceui.com/r/{name}.json",
|
||||||
|
"@doras-ui": "https://ui.doras.to/r/{name}.json",
|
||||||
|
"@efferd": "https://efferd.com/r/{name}.json",
|
||||||
"@eldoraui": "https://eldoraui.site/r/{name}.json",
|
"@eldoraui": "https://eldoraui.site/r/{name}.json",
|
||||||
"@elements": "https://tryelements.dev/r/{name}.json",
|
"@elements": "https://tryelements.dev/r/{name}.json",
|
||||||
"@elevenlabs-ui": "https://ui.elevenlabs.io/r/{name}.json",
|
"@elevenlabs-ui": "https://ui.elevenlabs.io/r/{name}.json",
|
||||||
"@fancy": "https://fancycomponents.dev/r/{name}.json",
|
"@fancy": "https://fancycomponents.dev/r/{name}.json",
|
||||||
"@formcn": "https://formcn.dev/r/{name}.json",
|
"@formcn": "https://formcn.dev/r/{name}.json",
|
||||||
|
"@gaia": "https://ui.heygaia.io/r/{name}.json",
|
||||||
|
"@glass-ui": "https://glass-ui.crenspire.com/r/{name}.json",
|
||||||
"@heseui": "https://www.heseui.com/r/{name}.json",
|
"@heseui": "https://www.heseui.com/r/{name}.json",
|
||||||
|
"@hooks": "https://shadcn-hooks.vercel.app/r/{name}.json",
|
||||||
"@intentui": "https://intentui.com/r/{name}",
|
"@intentui": "https://intentui.com/r/{name}",
|
||||||
"@kibo-ui": "https://www.kibo-ui.com/r/{name}.json",
|
"@kibo-ui": "https://www.kibo-ui.com/r/{name}.json",
|
||||||
|
"@kanpeki": "https://kanpeki.vercel.app/r/{name}.json",
|
||||||
"@kokonutui": "https://kokonutui.com/r/{name}.json",
|
"@kokonutui": "https://kokonutui.com/r/{name}.json",
|
||||||
|
"@lens-blocks": "https://lensblocks.com/r/{name}.json",
|
||||||
"@limeplay": "https://limeplay.winoffrg.dev/r/{name}.json",
|
"@limeplay": "https://limeplay.winoffrg.dev/r/{name}.json",
|
||||||
|
"@lucide-animated": "https://lucide-animated.com/r/{name}.json",
|
||||||
|
"@lytenyte": "https://www.1771technologies.com/r/{name}.json",
|
||||||
"@magicui": "https://magicui.design/r/{name}.json",
|
"@magicui": "https://magicui.design/r/{name}.json",
|
||||||
|
"@magicui-pro": "https://pro.magicui.design/registry/{name}",
|
||||||
"@motion-primitives": "https://motion-primitives.com/c/{name}.json",
|
"@motion-primitives": "https://motion-primitives.com/c/{name}.json",
|
||||||
|
"@mui-treasury": "https://mui-treasury.com/r/{name}.json",
|
||||||
"@nativeui": "https://nativeui.io/registry/{name}.json",
|
"@nativeui": "https://nativeui.io/registry/{name}.json",
|
||||||
|
"@nexus-elements": "https://elements.nexus.availproject.org/r/{name}.json",
|
||||||
"@ncdai": "https://chanhdai.com/r/{name}.json",
|
"@ncdai": "https://chanhdai.com/r/{name}.json",
|
||||||
"@paceui-ui": "https://ui.paceui.com/r/{name}.json",
|
"@nuqs": "https://nuqs.dev/r/{name}.json",
|
||||||
|
"@oui": "https://oui.mw10013.workers.dev/r/{name}.json",
|
||||||
|
"@paceui": "https://ui.paceui.com/r/{name}.json",
|
||||||
|
"@plate": "https://platejs.org/r/{name}.json",
|
||||||
"@prompt-kit": "https://prompt-kit.com/c/{name}.json",
|
"@prompt-kit": "https://prompt-kit.com/c/{name}.json",
|
||||||
|
"@prosekit": "https://prosekit.dev/r/{name}.json",
|
||||||
|
"@phucbm": "https://ui.phucbm.com/r/{name}.json",
|
||||||
"@react-bits": "https://reactbits.dev/r/{name}.json",
|
"@react-bits": "https://reactbits.dev/r/{name}.json",
|
||||||
"@react-market": "https://www.react-market.com/get/{name}.json",
|
"@react-market": "https://www.react-market.com/get/{name}.json",
|
||||||
"@retroui": "https://retroui.dev/r/{name}.json",
|
"@retroui": "https://retroui.dev/r/{name}.json",
|
||||||
"@reui": "https://reui.io/r/{name}.json",
|
"@reui": "https://reui.io/r/{name}.json",
|
||||||
"@rigidui": "https://rigidui.com/r/{name}.json",
|
"@rigidui": "https://rigidui.com/r/{name}.json",
|
||||||
|
"@roiui": "https://roiui.com/r/{name}.json",
|
||||||
|
"@solaceui": "https://www.solaceui.com/r/{name}.json",
|
||||||
"@scrollxui": "https://www.scrollxui.dev/registry/{name}.json",
|
"@scrollxui": "https://www.scrollxui.dev/registry/{name}.json",
|
||||||
|
"@systaliko-ui": "https://systaliko-ui.vercel.app/r/{name}.json",
|
||||||
"@shadcn-editor": "https://shadcn-editor.vercel.app/r/{name}.json",
|
"@shadcn-editor": "https://shadcn-editor.vercel.app/r/{name}.json",
|
||||||
"@shadcn-map": "http://shadcn-map.vercel.app/r/{name}.json",
|
"@shadcn-map": "http://shadcn-map.vercel.app/r/{name}.json",
|
||||||
"@shadcn-studio": "https://shadcnstudio.com/r/{name}.json",
|
"@shadcn-studio": "https://shadcnstudio.com/r/{name}.json",
|
||||||
"@shadcnblocks": "https://shadcnblocks.com/r/{name}.json",
|
"@shadcnblocks": "https://shadcnblocks.com/r/{name}.json",
|
||||||
|
"@shadcnui-blocks": "https://shadcnui-blocks.com/r/{name}.json",
|
||||||
|
"@shadcraft": "https://shadcraft-free.vercel.app/r/{name}.json",
|
||||||
|
"@simple-ai": "https://simple-ai.dev/r/{name}.json",
|
||||||
"@skiper-ui": "https://skiper-ui.com/registry/{name}.json",
|
"@skiper-ui": "https://skiper-ui.com/registry/{name}.json",
|
||||||
"@skyr": "https://ui-play.skyroc.me/r/{name}.json",
|
"@skyr": "https://ui-play.skyroc.me/r/{name}.json",
|
||||||
"@smoothui": "https://smoothui.dev/r/{name}.json",
|
"@smoothui": "https://smoothui.dev/r/{name}.json",
|
||||||
|
"@spectrumui": "https://ui.spectrumhq.in/r/{name}.json",
|
||||||
|
"@supabase": "https://supabase.com/ui/r/{name}.json",
|
||||||
"@svgl": "https://svgl.app/r/{name}.json",
|
"@svgl": "https://svgl.app/r/{name}.json",
|
||||||
"@tailark": "https://tailark.com/r/{name}.json",
|
"@tailark": "https://tailark.com/r/{name}.json",
|
||||||
|
"@tailwind-admin": "https://tailwind-admin.com/r/{name}.json",
|
||||||
|
"@tailwind-builder": "https://tailwindbuilder.ai/r/{name}.json",
|
||||||
"@tweakcn": "https://tweakcn.com/r/themes/{name}.json",
|
"@tweakcn": "https://tweakcn.com/r/themes/{name}.json",
|
||||||
"@wds": "https://wds-shadcn-registry.netlify.app/r/{name}.json",
|
"@wds": "https://wds-shadcn-registry.netlify.app/r/{name}.json",
|
||||||
"@zippystarter": "https://zippystarter.com/r/{name}.json"
|
"@wandry-ui": "https://ui.wandry.com.ua/r/{name}.json",
|
||||||
|
"@wigggle-ui": "https://wigggle-ui.vercel.app/r/{name}.json",
|
||||||
|
"@paykit-sdk": "https://www.usepaykit.dev/r/{name}.json",
|
||||||
|
"@pixelact-ui": "https://www.pixelactui.com/r/{name}.json",
|
||||||
|
"@zippystarter": "https://zippystarter.com/r/{name}.json",
|
||||||
|
"@shadcndesign": "https://shadcndesign-free.vercel.app/r/{name}.json",
|
||||||
|
"@ha-components": "https://hacomponents.keshuac.com/r/{name}.json",
|
||||||
|
"@shadix-ui": "https://shadix-ui.vercel.app/r/{name}.json",
|
||||||
|
"@utilcn": "https://utilcn.dev/r/{name}.json",
|
||||||
|
"@hextaui": "https://hextaui.com/r/{name}.json",
|
||||||
|
"@taki": "https://taki-ui.com/r/{name}.json",
|
||||||
|
"@square-ui": "https://square.lndev.me/registry/{name}.json",
|
||||||
|
"@moleculeui": "https://moleculeui.design/r/{name}.json",
|
||||||
|
"@uicapsule": "https://uicapsule.com/r/{name}.json",
|
||||||
|
"@uitripled": "https://ui.tripled.work/r/{name}.json",
|
||||||
|
"@ui-layouts": "https://ui-layouts.com/r/{name}.json",
|
||||||
|
"@tour": "https://onboarding-tour.vercel.app/r/{name}.json",
|
||||||
|
"@tb-blocks": "https://tailwindbuilder.ai/r/blocks/{name}.json",
|
||||||
|
"@ui-layouts": "https://ui-layouts.com/r/{name}.json"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"path": "registry/new-york-v4/ui/badge.tsx",
|
"path": "registry/new-york-v4/ui/badge.tsx",
|
||||||
"content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst badgeVariants = cva(\n \"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden\",\n {\n variants: {\n variant: {\n default:\n \"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90\",\n secondary:\n \"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90\",\n destructive:\n \"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n outline:\n \"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n\nfunction Badge({\n className,\n variant,\n asChild = false,\n ...props\n}: React.ComponentProps<\"span\"> &\n VariantProps<typeof badgeVariants> & { asChild?: boolean }) {\n const Comp = asChild ? Slot : \"span\"\n\n return (\n <Comp\n data-slot=\"badge\"\n className={cn(badgeVariants({ variant }), className)}\n {...props}\n />\n )\n}\n\nexport { Badge, badgeVariants }\n",
|
"content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst badgeVariants = cva(\n \"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden\",\n {\n variants: {\n variant: {\n default:\n \"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90\",\n secondary:\n \"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90\",\n destructive:\n \"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n outline:\n \"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n\nfunction Badge({\n className,\n variant,\n asChild = false,\n ...props\n}: React.ComponentProps<\"span\"> &\n VariantProps<typeof badgeVariants> & { asChild?: boolean }) {\n const Comp = asChild ? Slot : \"span\"\n\n return (\n <Comp\n data-slot=\"badge\"\n className={cn(badgeVariants({ variant }), className)}\n {...props}\n />\n )\n}\n\nexport { Badge, badgeVariants }\n",
|
||||||
"type": "registry:ui"
|
"type": "registry:ui"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"path": "registry/new-york-v4/blocks/calendar-15.tsx",
|
"path": "registry/new-york-v4/blocks/calendar-15.tsx",
|
||||||
"content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\n\nexport default function Calendar15() {\n const [date, setDate] = React.useState<Date | undefined>(\n new Date(2025, 5, 12)\n )\n\n return (\n <Calendar\n mode=\"single\"\n defaultMonth={date}\n selected={date}\n onSelect={setDate}\n className=\"rounded-lg border shadow-sm\"\n showWeekNumber\n />\n )\n}\n",
|
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { DateRange } from \"react-day-picker\"\n\nimport { Calendar } from \"@/registry/new-york-v4/ui/calendar\"\n\nexport default function Calendar15() {\n const [dateRange, setDateRange] = React.useState<DateRange | undefined>({\n from: new Date(2025, 5, 12),\n to: new Date(2025, 5, 23),\n })\n\n return (\n <Calendar\n mode=\"range\"\n defaultMonth={dateRange?.from}\n selected={dateRange}\n onSelect={setDateRange}\n className=\"rounded-lg border shadow-sm\"\n showWeekNumber\n />\n )\n}\n",
|
||||||
"type": "registry:component"
|
"type": "registry:component"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -8,7 +8,7 @@
|
|||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"path": "registry/new-york-v4/examples/field-input.tsx",
|
"path": "registry/new-york-v4/examples/field-input.tsx",
|
||||||
"content": "import {\n Field,\n FieldDescription,\n FieldGroup,\n FieldLabel,\n FieldSet,\n} from \"@/registry/new-york-v4/ui/field\"\nimport { Input } from \"@/registry/new-york-v4/ui/input\"\n\nexport default function FieldInput() {\n return (\n <div className=\"w-full max-w-md\">\n <FieldSet>\n <FieldGroup>\n <Field>\n <FieldLabel htmlFor=\"username\">Username</FieldLabel>\n <Input id=\"username\" type=\"text\" placeholder=\"Max Leiter\" />\n <FieldDescription>\n Choose a unique username for your account.\n </FieldDescription>\n </Field>\n <Field>\n <FieldLabel htmlFor=\"password\">Password</FieldLabel>\n <FieldDescription>\n Must be at least 8 characters long.\n </FieldDescription>\n <Input id=\"password\" type=\"password\" placeholder=\"********\" />\n </Field>\n </FieldGroup>\n </FieldSet>\n </div>\n )\n}\n",
|
"content": "import {\n Field,\n FieldDescription,\n FieldGroup,\n FieldLabel,\n FieldSet,\n} from \"@/registry/new-york-v4/ui/field\"\nimport { Input } from \"@/registry/new-york-v4/ui/input\"\n\nexport default function FieldInput() {\n return (\n <div className=\"w-full max-w-md\">\n <FieldSet>\n <FieldGroup>\n <Field>\n <FieldLabel htmlFor=\"username\">Username</FieldLabel>\n <Input id=\"username\" type=\"text\" placeholder=\"Max Leiter\" />\n <FieldDescription>\n Choose a unique username for your account.\n </FieldDescription>\n </Field>\n <Field>\n <FieldLabel htmlFor=\"password\">Password</FieldLabel>\n <FieldDescription>\n Must be at least 8 characters long.\n </FieldDescription>\n <Input id=\"password\" type=\"password\" placeholder=\"••••••••\" />\n </Field>\n </FieldGroup>\n </FieldSet>\n </div>\n )\n}\n",
|
||||||
"type": "registry:example"
|
"type": "registry:example"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
15
apps/v4/public/r/styles/new-york-v4/native-select-demo.json
Normal file
15
apps/v4/public/r/styles/new-york-v4/native-select-demo.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||||
|
"name": "native-select-demo",
|
||||||
|
"type": "registry:example",
|
||||||
|
"registryDependencies": [
|
||||||
|
"native-select"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "registry/new-york-v4/examples/native-select-demo.tsx",
|
||||||
|
"content": "import {\n NativeSelect,\n NativeSelectOption,\n} from \"@/registry/new-york-v4/ui/native-select\"\n\nexport default function NativeSelectDemo() {\n return (\n <NativeSelect>\n <NativeSelectOption value=\"\">Select status</NativeSelectOption>\n <NativeSelectOption value=\"todo\">Todo</NativeSelectOption>\n <NativeSelectOption value=\"in-progress\">In Progress</NativeSelectOption>\n <NativeSelectOption value=\"done\">Done</NativeSelectOption>\n <NativeSelectOption value=\"cancelled\">Cancelled</NativeSelectOption>\n </NativeSelect>\n )\n}\n",
|
||||||
|
"type": "registry:example"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user