Compare commits

...

159 Commits

Author SHA1 Message Date
github-actions[bot]
d28e02be1b chore(release): version packages (#8998)
* chore(release): version packages

* chore(release): version packages

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-12-09 21:25:59 +04:00
shadcn
6699158a22 fix: handling of base style for add command (#8997)
* fix: handling of base style for add command

* chore: changeset

* fix: shadow config
2025-12-09 20:55:58 +04:00
Pasquale Vitiello
142cd8ef13 Prevent duplicate keyframes when adding components (#8993)
* fix: prevent duplicate keyframes when adding components

- Check for existing keyframes in @theme inline before adding
- Replace existing keyframes instead of creating duplicates
- Add test to verify keyframe replacement behavior

* chore: changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-12-09 13:01:34 +04:00
shadcn
bdedce2750 feat: add start-app (#8992) 2025-12-09 01:54:22 +04:00
shadcn
4cb283d68e fix 2025-12-09 00:43:17 +04:00
shadcn
480a6cdb37 fix: layout 2025-12-09 00:40:19 +04:00
shadcn
8ba883738e chore: update monorepo-next 2025-12-09 00:31:33 +04:00
shadcn
b022c24825 chore: remove pnpm lock file 2025-12-09 00:27:17 +04:00
shadcn
3587477865 feat: add vite-app template (#8989) 2025-12-08 23:03:13 +04:00
Tommy Lundy
05143a80e6 Add @doras-ui registry (#8966)
* Add @doras-ui registry URL to registries.json

* Add @doras-ui component block details to directory
2025-12-08 23:01:20 +04:00
Jarrod Watts
728d2003b7 Add new component registry entries for @abstract (#8962) 2025-12-08 23:00:58 +04:00
Jarrod Watts
12c9e6b0b5 Add @abstract registry URL to registries.json (#8536)
Added new registry entry for @abstract.

Co-authored-by: shadcn <m@shadcn.com>
2025-12-05 15:39:49 +04:00
I Plan Websites
56cd757c45 @ai-blocks registry (#8956)
* Add AI Blocks registry URL to registries.json

* Add AI components for web to directory.json
2025-12-05 15:37:53 +04:00
Ella
9eb784054f feat: add @lucide-animated to directory.json and registries.json (#8937) 2025-12-05 15:37:41 +04:00
Hin
824577692b feat: add tour to registries and directory (#8917)
Co-authored-by: shadcn <m@shadcn.com>
2025-12-05 15:37:21 +04:00
Antonio Brandao
6be68df08c Add @abui registry to directory and index (#8908) 2025-12-05 15:35:50 +04:00
Admin Mart
cc48808a0d feat: add my custom registry (#8879)
Co-authored-by: wrappixelTeam <wrappixelteam.2016@gmail.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-12-05 15:35:26 +04:00
Admin Mart
a56b3720d1 feat/added my custom registry (#8904)
Co-authored-by: wrappixelTeam <wrappixelteam.2016@gmail.com>
2025-12-05 15:34:33 +04:00
dependabot[bot]
334db11234 chore(deps): bump next from 16.0.0 to 16.0.7 (#8954)
Bumps [next](https://github.com/vercel/next.js) from 16.0.0 to 16.0.7.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v16.0.0...v16.0.7)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 16.0.7
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-04 16:24:46 +04:00
Luis Llanes
8a5027a0cd feat: add @shadcraft to directory.json and registries.json (#8913) 2025-12-01 00:44:28 +04:00
github-actions[bot]
803206305d chore(release): version packages (#8665)
* chore(release): version packages

* chore(release): version packages

* chore: lock

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-11-27 21:16:23 +04:00
shadcn
d0fb73ac0e fix: do not install baseStyle when adding registry:theme (#8900) 2025-11-27 21:13:56 +04:00
shadcn
62218c1c0c feat: update color value detection for cssVars (#8901) 2025-11-27 21:12:31 +04:00
Aditya Mathur
dd1563d57d fix: update author links in documentation for Drawer and Sonner components (#8881) 2025-11-27 13:38:26 +04:00
Amartya Singh
0538384860 Add @nexus-elements registry URL to registries.json and directory.json (#8797)
* Add @nexus-elements registry URL to registries.json

* feat: updated directory.json with nexus-elements

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-11-25 11:08:47 +04:00
shadcn
d43b437abc fix 2025-11-25 10:59:48 +04:00
Burhanuddin S. Tinwala
8fbfacd243 docs: fix typo 'mcpServers' to 'servers' in mcp server setup documentation for vs code (#8864) 2025-11-24 12:51:06 +04:00
Neha Prasad
778cee31ee feat: add @ui-layouts registry to directory (#8878) 2025-11-24 12:49:24 +04:00
Wolfr
73d8b8a817 docs - Move free kits to the top (#8639)
* docs - Move free kits to the top

* fix

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-11-24 12:48:21 +04:00
Phuc Bui
55ab069aca feat: add @phucbm in trusted registries (#8830)
* feat: add @phucbm in trusted registries

* Add @phucbm to registry directory

* fix: change svg quotes from double to single
2025-11-19 16:58:43 +04:00
Ajay Patel
c39925a9be Added shadcn/studio UI Kit to figma docs (#8852)
docs: Added shadcn/studio UI Kit to Figma paid section
2025-11-19 16:48:09 +04:00
shadcn
51179ccd64 fix: directory 2025-11-19 12:26:52 +04:00
Hin
dcfa05e392 feat: add shadcn-map to directory (#8832) 2025-11-19 12:20:20 +04:00
Aryan
541f55df04 feat: add @gaia registry entry to directory and registries.json (#8836)
Co-authored-by: shadcn <m@shadcn.com>
2025-11-19 12:19:53 +04:00
Edu Calvo
69010e0230 feat: add new registry entry for @smoothui with logo and description (#8837) 2025-11-19 12:18:35 +04:00
Rushil
a8025c866e feat: add moleculeui to registries and directories (#8838)
Co-authored-by: shadcn <m@shadcn.com>
2025-11-19 12:18:14 +04:00
Kaiyu Hsu
6e34ec7280 feat: add @uicapsule to registry (#8848)
Co-authored-by: shadcn <m@shadcn.com>
2025-11-19 12:16:42 +04:00
Arif Hossain
10ccb244a1 feat: add @commercn in registry directory (#8842) 2025-11-19 12:15:53 +04:00
Akshay Joshi
16fdb07ccc feat: added @crenspire/glass-ui in trusted registries and directory (#8831)
* feat: added @glass-ui in trusted registries and directory

* fix: svg logo not displayed properly
2025-11-19 12:14:41 +04:00
Moumen Soliman
49da1fae79 Add @uitripled registry (#8834)
* Add @uitripled registry

* Update registries.json

* fix

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-11-19 12:13:09 +04:00
Sepehr Soheili
a2244d42f7 docs: fix typo in documentation (#8793) 2025-11-17 20:06:32 +04:00
Brendan Dash
c2075e2a8b fix: typo (#8800) 2025-11-17 20:06:04 +04:00
Anthony Shew
dd2d8d7ead fix: dependencies for Bun in monorepo-next (#8791) 2025-11-13 09:42:31 +04:00
Akash Moradiya
b6a93b7ec6 feat: add @shadcnui-blocks to registry directory (#8763) 2025-11-12 09:39:21 +04:00
shadcn
4899d3f0da fix: minor directory updates 2025-11-10 15:35:21 +04:00
Pawan Kumar
3d04cb099a Add new registry entry for @taki (#8758)
* add taki ui in registry

* fix

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-11-10 15:27:56 +04:00
François Best
cde343916c ref: refactor nuqs usage in directory search (#8756)
* chore: fix typo

* ref: query is non-nullable (as it has a default value)

* ref: leverage nuqs' clearOnDefault behaviour
2025-11-10 15:27:06 +04:00
LN
c877df07b8 feat: add @square-ui in registries (#8761)
* feat: add new registry entry for @square-ui

* feat: add new registry entry for @square-ui with logo and description
2025-11-10 15:17:01 +04:00
DimaDevelopment
65e5c1c3cf Add search input for Directory list (#8673)
* feat(directory): Added directory search input

* feat(directory): Added nuqs for a search state management. Refactor searchFn - includes the description in the search criteria

* fear(directory): Added default query value. Added useQueryState limitUrlUpdates 250ms

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-11-10 12:24:15 +04:00
Anvarys
8a7f05f670 fix: correct typos in docs/components/chart (#8750) 2025-11-10 11:34:53 +04:00
Miracle Onyenma
db004ce4c0 Add new registry entry for @aevr (#8726)
* Add new registry entry for @aevr

Would like to add Aevr UI, a small collection of focused, production‑ready components and primitives for React/Next.js projects—built on shadcn/ui and complementary libraries—to the shadcn registry. It emphasizes practical, purpose‑specific building blocks with consistent design tokens, accessibility, and copy‑paste examples tailored for real product use.

Website: https://ui.aevr.space 

Repo: https://github.com/aevrhq/ui

* Add new component library entry for @aevr in registry directory

* Update logo SVG format in directory.json

* fix: logo color

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-11-10 11:30:16 +04:00
shadcn
e23698a897 feat: add tracking for directories 2025-11-10 11:27:10 +04:00
preet
5813ef20a3 Add new registry entry for @hextaui (#8715)
* Add new registry entry for @hextaui

* feat: add new registry entry for @HextaUI

* Update directory.json

Co-authored-by: shadcn <m@shadcn.com>

* fix: update directory

* fix: logo

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-11-10 11:20:23 +04:00
Arif Hossain
515024b69e feat: add @commercn in trusted registries (#8751) 2025-11-10 11:12:35 +04:00
Shaban
f7284c5cc3 feat(directory): update the @efferd logo (#8733) 2025-11-10 10:43:54 +04:00
Harshul
c02d00aafc fix(shadcn): Restore two-finger navigation on macOS by adjusting overscroll behavior (#8714)
* fix(shadcn): Tow finger navigation on macOS

* Add smooth scrolling to html element
2025-11-06 10:04:15 +04:00
Bassim Shahidy
df497ad236 feat: add assistant-ui to directory.json (#8720)
* feat: add assistant-ui to directory.json

* update description

* desc
2025-11-06 10:01:33 +04:00
shadcn
1e468e33ac feat: add more registries 2025-11-05 10:15:32 +04:00
dependabot[bot]
ff91c31a71 chore(deps): bump @radix-ui/react-toggle from 1.1.9 to 1.1.10 (#8702)
Bumps [@radix-ui/react-toggle](https://github.com/radix-ui/primitives) from 1.1.9 to 1.1.10.
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-toggle"
  dependency-version: 1.1.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-05 09:51:45 +04:00
dependabot[bot]
25d6a18f6f chore(deps): bump @radix-ui/react-tooltip from 1.2.7 to 1.2.8 (#8701)
Bumps [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives) from 1.2.7 to 1.2.8.
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-tooltip"
  dependency-version: 1.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-05 09:51:11 +04:00
dependabot[bot]
c0309510b6 chore(deps): bump @radix-ui/react-accordion from 1.2.11 to 1.2.12 (#8700)
Bumps [@radix-ui/react-accordion](https://github.com/radix-ui/primitives) from 1.2.11 to 1.2.12.
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-accordion"
  dependency-version: 1.2.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-05 09:50:52 +04:00
dependabot[bot]
a3a1574668 chore(deps): bump @commitlint/cli from 17.8.1 to 20.1.0 (#8698)
Bumps [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) from 17.8.1 to 20.1.0.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v20.1.0/@commitlint/cli)

---
updated-dependencies:
- dependency-name: "@commitlint/cli"
  dependency-version: 20.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-05 09:50:29 +04:00
shadcn
65d581ea5a chore: remove console 2025-11-04 15:19:45 +04:00
shadcn
fdf80a1d49 fix: gh link 2025-11-04 09:21:43 +04:00
Ajay Patel
86c494c452 feat: add new entry for @shadcn-studio in directory.json (#8692)
* feat: add new entry for @shadcn-studio in directory.json

* [fix]: for @shadcn-studio in directory.json, Updated logo SVG to use CSS variables

Updated logo SVG to use CSS variables for colors.Refactor logo SVG to use CSS variables
2025-11-03 12:48:36 +04:00
Paul Burke
eb158686b9 feat: adds lens-blocks to the registry index (#8687) 2025-11-03 11:45:57 +04:00
shadcn
134cd46edb feat: add hsl and color indicators to theme (#8691) 2025-11-03 11:25:06 +04:00
shadcn
47b0efb20c docs: update tanstack start 2025-11-03 10:46:35 +04:00
shadcn
bd4d09d33e fix 2025-11-03 10:30:18 +04:00
Dylan
14d6265580 docs: fix typos in Empty (#8675) 2025-11-03 08:57:19 +04:00
Ali Imam
68805d29a1 Update directory.json (#8682) 2025-11-03 08:56:03 +04:00
ocavue
c100d5841a feat: add prosekit to directory (#8677) 2025-11-02 15:00:40 +04:00
Ali Imam
7a71da5218 Update directory.json (#8671)
* Update directory.json

* Update registries.json
2025-11-02 14:51:58 +04:00
Mudunuri bhaskara karthikeya varma
e18902039a adding @eldoraui to directory.json (#8670) 2025-10-31 21:54:20 +04:00
Emmanuel Odii
559af6c245 feat(registry): add paykit to directory (#8654)
Co-authored-by: shadcn <m@shadcn.com>
2025-10-31 12:24:15 +04:00
Alex Paduraru
8971be484f feat: add @creative-tim to registries.json, directory.json (#8651)
* feat: add @creative-tim to registries.json, directory.json

* fix: logo color

---------

Co-authored-by: Alexandru Paduraru <axelut@yahoo.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-10-31 12:19:31 +04:00
Tommy D. Rossi
ad6a3c6367 Fix utils import transform when workspace alias does not start with @ (#7557)
* Fix nested src folder for utils import

* remove .only

* Update packages/shadcn/src/utils/transformers/transform-import.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* check for empty utils

* chore: changeset

---------

Co-authored-by: shadcn <m@shadcn.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-31 12:07:02 +04:00
dependabot[bot]
befa56b5be chore(deps): bump @faker-js/faker from 8.4.1 to 10.1.0 (#8520)
Bumps [@faker-js/faker](https://github.com/faker-js/faker) from 8.4.1 to 10.1.0.
- [Release notes](https://github.com/faker-js/faker/releases)
- [Changelog](https://github.com/faker-js/faker/blob/next/CHANGELOG.md)
- [Commits](https://github.com/faker-js/faker/compare/v8.4.1...v10.1.0)

---
updated-dependencies:
- dependency-name: "@faker-js/faker"
  dependency-version: 10.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-31 11:15:26 +04:00
dependabot[bot]
5d1770e36d chore(deps): bump @radix-ui/react-navigation-menu from 1.2.13 to 1.2.14 (#8519)
Bumps [@radix-ui/react-navigation-menu](https://github.com/radix-ui/primitives) from 1.2.13 to 1.2.14.
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-navigation-menu"
  dependency-version: 1.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-31 11:14:27 +04:00
dependabot[bot]
653521725a chore(deps): bump jotai from 2.13.1 to 2.15.0 (#8521)
Bumps [jotai](https://github.com/pmndrs/jotai) from 2.13.1 to 2.15.0.
- [Release notes](https://github.com/pmndrs/jotai/releases)
- [Commits](https://github.com/pmndrs/jotai/compare/v2.13.1...v2.15.0)

---
updated-dependencies:
- dependency-name: jotai
  dependency-version: 2.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-31 11:14:12 +04:00
shadcn
7c0618bf43 feat: add new registries (#8661) 2025-10-31 10:39:29 +04:00
shadcn
854641cea1 fix 2025-10-30 12:51:15 +04:00
shadcn
3a72007f61 fix 2025-10-30 12:04:55 +04:00
shadcn
6b53b238fb fix: spacing 2025-10-30 11:59:50 +04:00
shadcn
b398fea304 feat: add dir action (#8647)
* feat: add mcp config

* feat

* fix
2025-10-30 11:53:24 +04:00
shadcn
f22174a77f feat: add mcp config (#8641) 2025-10-29 22:56:24 +04:00
shadcn
c9a39f1007 docs: update README 2025-10-29 21:38:18 +04:00
shadcn
a8ad21f81f fix: svgs 2025-10-29 21:37:15 +04:00
shadcn
504503c638 feat: add new registries 2025-10-29 21:20:04 +04:00
Pasquale Vitiello
f8df5c95cb docs: update coss logo (#8631) 2025-10-29 20:57:42 +04:00
shadcn
2bfc1c82ba chore: deprecate www (#8629)
* chore: deprecate www

* chore: updates

* fix
2025-10-29 20:50:55 +04:00
shadcn
84bd724d97 feat: refactor registry (#8598)
* feat: refactor registry

* fix: remove components

* refactor: getActiveStyle

* fix: prettier in build-registry

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* fix

* Update apps/v4/scripts/build-registry.mts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix

* Update apps/v4/scripts/build-registry.mts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update apps/v4/components/block-viewer.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-29 15:07:56 +04:00
shadcn
39fdf94550 feat: add svgl 2025-10-29 14:58:25 +04:00
shadcn
08479cc3db feat: add wigggle-ui 2025-10-29 13:55:50 +04:00
shadcn
02d5ce85ec feat: upgrade to Next.js 16 (#8615)
* feat: upgrade to Next.js 16

* chore: deps

* fix

* fix

* fix

* fix: workaround zod 4 for now

* fix

* fix: copy button

* fix: update apps/v4/hooks/use-is-mac.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix

* fix: remove

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-29 13:37:41 +04:00
shadcn
c0329c86b9 feat: add retroui and reui 2025-10-29 09:53:25 +04:00
shadcn
3b1491f908 fix: typo 2025-10-29 09:30:49 +04:00
shadcn
ca4c1c43ec feat: update registry directory 2025-10-29 00:15:20 +04:00
shadcn
1e840eb53c feat: update directory 2025-10-29 00:05:03 +04:00
shadcn
96ac92e63f fix 2025-10-29 00:00:20 +04:00
shadcn
e11546e692 fix: examples page 2025-10-28 23:56:15 +04:00
shadcn
0b4d62f95c chore: rebuild registry 2025-10-28 22:59:12 +04:00
shadcn
dae80dad65 fix: password field 2025-10-28 22:58:17 +04:00
shadcn
abc09809e8 feat: add blocks to directory 2025-10-28 22:56:29 +04:00
shadcn
8a40fe0ead feat: update registries 2025-10-28 22:45:06 +04:00
shadcn
b3ab304a00 fix: minor styles updates 2025-10-28 21:34:31 +04:00
Elliot Sutton
bb45fd83c3 fix(v4): correction of the Animate UI logo (#8608)
Co-authored-by: shadcn <m@shadcn.com>
2025-10-28 21:03:19 +04:00
Chisom Uma
84678ee1c0 Add new registry entry for @chisom-ui (#8601) 2025-10-28 21:01:08 +04:00
shadcn
33ffb0419c Merge branch 'main' of github.com:shadcn-ui/ui
# Conflicts:
#	apps/v4/registry/directory.json
2025-10-28 20:52:28 +04:00
shadcn
a2f6c031e2 feat: add new registries 2025-10-28 20:49:49 +04:00
shadcn
ac098d8cf0 feat: add new registries (#8600) 2025-10-28 18:28:06 +04:00
shadcn
8160610410 Merge branch 'main' of github.com:shadcn-ui/ui 2025-10-28 11:26:17 +04:00
shadcn
c7901e3a41 chore: update validate-registries script 2025-10-28 11:26:04 +04:00
shadcn
d73ac361b3 feat: add registry directory (#8574)
* feat: add registry directory

* fix: lint

* feat: add more registries

* feat: add nuqs to directory

* feat: add shadcndesign

* feat: add more registries

* feat: add new registries

* chore: remove hooks
2025-10-28 11:25:25 +04:00
François Best
ebad2901ce feat: add nuqs to registries.json (#8579) 2025-10-27 11:16:18 +04:00
shadcn
4f617d59b8 Fix description formatting in registry_directory.yml 2025-10-26 16:22:48 +04:00
shadcn
ed0e103bd6 Refactor checklist validations in registry_directory.yml
Removed individual required validations from checklist items and added a single required validation for the entire checklist.
2025-10-26 16:22:17 +04:00
shadcn
9cab0c9b18 chore: add registry directory issue template 2025-10-26 16:18:46 +04:00
DimaDevelopment
d80e084814 feat: add @wandry-ui to registries.json (#8566) 2025-10-26 15:28:42 +04:00
shadcn
efcf9728c2 feat: add docs for cell size 2025-10-26 14:53:50 +04:00
Lakshya Thakur
8835bacc8b fix: use calendar-05 blocks instead of calendar-02 for range (#8029) 2025-10-26 14:47:06 +04:00
Ian Thorslund
f2556d2386 fix(components): Fix left radius of days when weeks are shown in range calendar (#8570)
* fix(components): Fix left radius of days when weeks are shown in range calendar

* feat: update block to show range

* chore: rebuild registry

* docs: add changelog

---------

Co-authored-by: shadcn <m@shadcn.com>
2025-10-26 14:44:24 +04:00
albertasaftei
75a0000075 Add Pixelact UI registry to registries.json (#8301) 2025-10-25 09:40:38 +04:00
Dillion Verma
ac306c60f5 feat: add magic ui pro registry (#8302)
Co-authored-by: shadcn <m@shadcn.com>
2025-10-24 11:19:25 +04:00
shadcn
5e2ef1f8bd docs: updates 2025-10-24 10:56:23 +04:00
shadcn
7d9b8aefff Merge branch 'main' of github.com:shadcn-ui/ui 2025-10-24 10:44:39 +04:00
shadcn
58208e3802 fix: minor updates 2025-10-24 10:44:21 +04:00
Shaban
a16a77446a feat: rename @efferd-ui -> @efferd (#8557) 2025-10-24 10:37:47 +04:00
github-actions[bot]
39032bb390 chore(release): version packages (#8551)
* chore(release): version packages

* chore(release): version packages

* chore: deps

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-10-23 22:12:05 +04:00
shadcn
d7e0dc3ec8 feat(shadcn): middleware to proxy (#8555)
* feat: implement getFrameworkVersion

* feat(shadcn): add transformNext transformer

* feat(shadcn): rename

* chore: update

* chore: changeset

* fix

* fix: small refactor
2025-10-23 22:00:55 +04:00
shadcn
6bddba986d feat(shadcn): add next 16 to init (#8550)
* feat: add next 16 init

* chore: changeset

* fix: tests

* fix
2025-10-23 17:20:45 +04:00
shadcn
b70059b25b docs: add react-19 docs back 2025-10-23 17:14:09 +04:00
shadcn
37bc2eec1f feat: update code block for component preview (#8549) 2025-10-23 16:24:44 +04:00
mw10013
bb048fb532 feat: add @oui to registries.json (#8541) 2025-10-22 21:35:55 +04:00
Alejandro Wurts
9c373dbd27 feat: add @simple-ai to trusted registries (#8539) 2025-10-22 21:35:03 +04:00
shadcn
d75b092c61 docs: remove legacy docs 2025-10-22 07:02:23 +04:00
Gihan Rangana
be49662bf5 shadix-ui registry added (#8505) 2025-10-21 19:16:23 +04:00
FadilRumasoreng
b2b2e3fc98 docs (security) : add clickable link to security reporting section (#8525)
* docs (security) : add clickable link to security reporting section

* fix(docs): use markdown link format for security section
2025-10-21 17:41:45 +04:00
Neeraj Dalal
188b746074 refactor(field): no duplicate errors (#8514)
* chore: tweaks > field.tsx

apps/v4/registry/new-york-v4/ui/field.tsx

* chore: tweaks > field.tsx

apps/v4/registry/new-york-v4/ui/field.tsx

* chore: tweaks > field.json field.json

apps/www/public/r/styles/new-york-v4/field.json
apps/v4/public/r/styles/new-york-v4/field.json
2025-10-21 16:02:52 +04:00
Joshua Chung
6f093a0f3f Added ha-components registry to registries.json (#8531) 2025-10-21 15:40:03 +04:00
Matt Wierzbicki
f18f1eaff7 Added shadcndesign.com registry to registries.json (#8516)
* Added shadcndesign.com registry to registries.json

* Update registries.json with open source shadcndesign registry
2025-10-21 14:33:40 +04:00
shadcn
9ac1b5c0a5 feat: add native select (#8528)
* feat: add native select

* fix: width
2025-10-21 11:48:25 +04:00
shadcn
f63b70b413 feat: implement search via fumadocs (#8523)
* feat: implement search

* fix: update message when searching
2025-10-21 10:26:29 +04:00
shadcn
54e725d986 feat(toggle-group): add spacing props 2025-10-20 21:28:40 +04:00
shadcn
62dbad36bb fix: theme-customizer 2025-10-20 20:42:55 +04:00
shadcn
a707424fa2 fix: front page blocks 2025-10-20 20:39:01 +04:00
shadcn
e2bfa6bd85 feat(badge): make rounded-full (#8518) 2025-10-20 20:24:45 +04:00
preet
6292464d90 docs(calendar): add timezone fix for date selection offset issue (#8515)
Added a new section to the Calendar documentation explaining how to fix the issue where selecting a date highlights the previous day. The section includes an example showing how to detect the user’s local timezone and pass it as a timeZone prop to the Calendar component to resolve the date offset problem.
2025-10-20 13:17:58 +00:00
shadcn
6617167d6f Merge branch 'main' of github.com:shadcn-ui/ui 2025-10-20 16:05:21 +04:00
shadcn
ca28857d40 fix: themes 2025-10-20 16:05:14 +04:00
Rakesh
343bc941b1 Fix/theme code mismatch (#8368)
* fix/replaced stale theme colors

* fix:missing chart colors added
2025-10-20 15:56:57 +04:00
Pasquale Vitiello
c9311f26fa feat: add @coss to registries.json (#8490) 2025-10-17 15:01:04 +04:00
Ivan Vasilov
4e0871f426 Add supabase registry to trusted registries. (#8161) 2025-10-17 15:00:51 +04:00
shadcn
cb769b7059 fix: navigation menu demo on mobile (#8488) 2025-10-16 14:44:27 +04:00
Dylan Tientcheu
93037dca94 feat: add @algolia in trusted registries (#8485) 2025-10-16 13:05:51 +04:00
github-actions[bot]
ed9d5939e6 chore(release): version packages (#8479)
* chore(release): version packages

* fix

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shadcn <m@shadcn.com>
2025-10-15 22:55:13 +04:00
shadcn
b52ec12f1e fix(shadcn): universal items handling (#8478)
* fix(shadcn): universal items handling

* chore: add changeset
2025-10-15 22:27:12 +04:00
Igor S. Zizinio
2ab9bff4bb Fix formatting of Badge variant prop in documentation (#8477) 2025-10-15 22:09:06 +04:00
Shaban Haider
2f6b51fa0a feat: add @efferd-ui in trusted registries (#8474) 2025-10-15 22:03:39 +04:00
3502 changed files with 18702 additions and 16487 deletions

View File

@@ -7,5 +7,5 @@
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["www", "v4", "tests"]
"ignore": ["v4", "tests"]
}

View 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

View File

@@ -27,10 +27,10 @@ jobs:
with:
version: 9.0.6
- name: Use Node.js 18
- name: Use Node.js 20
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
cache: "pnpm"
- name: Install NPM Dependencies

View File

@@ -23,11 +23,11 @@ jobs:
with:
version: 9.0.6
- name: Use Node.js 18
- name: Use Node.js 20
uses: actions/setup-node@v3
with:
version: 9.0.6
node-version: 18
node-version: 20
cache: "pnpm"
- name: Install NPM Dependencies

View File

@@ -19,7 +19,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
- uses: pnpm/action-setup@v4
name: Install pnpm

View File

@@ -3,5 +3,5 @@ node_modules
.next
build
.contentlayer
apps/www/pages/api/registry.json
**/fixtures
deprecated

View File

@@ -10,6 +10,6 @@
"**/fixtures/**"
],
"files.exclude": {
"apps/www": true
"deprecated": true
}
}

View File

@@ -20,28 +20,25 @@ This repository is structured as follows:
```
apps
└── www
└── v4
├── app
├── components
├── content
└── registry
── default
│ ├── example
│ └── ui
└── new-york
── new-york-v4
├── example
└── ui
packages
└── cli
└── shadcn
```
| Path | Description |
| --------------------- | ---------------------------------------- |
| `apps/www/app` | The Next.js application for the website. |
| `apps/www/components` | The React components for the website. |
| `apps/www/content` | The content for the website. |
| `apps/www/registry` | The registry for the components. |
| `packages/cli` | The `shadcn-ui` package. |
| Path | Description |
| -------------------- | ---------------------------------------- |
| `apps/v4/app` | The Next.js application for the website. |
| `apps/v4/components` | The React components for the website. |
| `apps/v4/content` | The content for the website. |
| `apps/v4/registry` | The registry for the components. |
| `packages/shadcn` | The `shadcn` package. |
## 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:
```bash
pnpm --filter=www dev
pnpm --filter=v4 dev
```
2. To run the `shadcn-ui` package:
2. To run the `shadcn` package:
```bash
pnpm --filter=shadcn-ui dev
pnpm --filter=shadcn dev
```
## Running the CLI Locally
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
pnpm v4:dev
pnpm dev
```
2. Run the development script for the CLI:
```bash
pnpm shadcn:dev
```
3. In another terminal tab, test the CLI by running:
2. In another terminal tab, test the CLI by running:
```bash
pnpm shadcn
@@ -119,36 +110,27 @@ To run the CLI locally, you can follow the workflow:
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.
## 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
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
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
apps
└── www
└── v4
└── registry
── default
│ ├── example
│ └── ui
└── new-york
── new-york-v4
├── example
└── ui
```
@@ -157,7 +139,7 @@ When adding or modifying components, please ensure that:
1. You make the changes for every style.
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
@@ -196,9 +178,9 @@ If you have a request for a new component, please open a discussion on GitHub. W
## 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

View File

@@ -1,8 +1,8 @@
# 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**.
![hero](apps/www/public/og.jpg)
![hero](apps/v4/public/opengraph-image.png)
## Documentation

View File

@@ -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.
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.

View File

@@ -1,9 +1,8 @@
"use client"
import * as React from "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 { ButtonGroup } from "@/registry/new-york-v4/ui/button-group"
import {
@@ -18,34 +17,31 @@ import {
FieldTitle,
} from "@/registry/new-york-v4/ui/field"
import { Input } from "@/registry/new-york-v4/ui/input"
import { Label } from "@/registry/new-york-v4/ui/label"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/new-york-v4/ui/radio-group"
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() {
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 (
<FieldSet>
<FieldGroup>
@@ -90,37 +86,6 @@ export function AppearanceSettings() {
</RadioGroup>
</FieldSet>
<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">
<FieldContent>
<FieldLabel htmlFor="number-of-gpus-f6l">Number of GPUs</FieldLabel>
@@ -129,7 +94,8 @@ export function AppearanceSettings() {
<ButtonGroup>
<Input
id="number-of-gpus-f6l"
placeholder="8"
value={gpuCount}
onChange={handleGpuInputChange}
size={3}
className="h-8 !w-14 font-mono"
maxLength={3}
@@ -139,6 +105,8 @@ export function AppearanceSettings() {
size="icon-sm"
type="button"
aria-label="Decrement"
onClick={() => handleGpuAdjustment(-1)}
disabled={gpuCount <= 1}
>
<IconMinus />
</Button>
@@ -147,6 +115,8 @@ export function AppearanceSettings() {
size="icon-sm"
type="button"
aria-label="Increment"
onClick={() => handleGpuAdjustment(1)}
disabled={gpuCount >= 99}
>
<IconPlus />
</Button>

View File

@@ -33,7 +33,7 @@ export function RootComponents() {
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
<InputGroupButtonExample />
<ItemDemo />
<FieldSeparator>Appearance Settings</FieldSeparator>
<FieldSeparator className="my-4">Appearance Settings</FieldSeparator>
<AppearanceSettings />
</div>
<div className="order-first flex flex-col gap-6 lg:hidden xl:order-last xl:flex *:[div]:w-full *:[div]:max-w-full">

View File

@@ -3,7 +3,7 @@ import { Spinner } from "@/registry/new-york-v4/ui/spinner"
export function SpinnerBadge() {
return (
<div className="flex items-center gap-2 [--radius:1.2rem]">
<div className="flex items-center gap-2">
<Badge>
<Spinner />
Syncing

View File

@@ -1,6 +1,7 @@
import { getAllBlockIds } from "@/lib/blocks"
import { registryCategories } from "@/lib/categories"
import { BlockDisplay } from "@/components/block-display"
import { registryCategories } from "@/registry/registry-categories"
import { getActiveStyle } from "@/registry/styles"
export const revalidate = false
export const dynamic = "force-static"
@@ -17,13 +18,16 @@ export default async function BlocksPage({
}: {
params: Promise<{ categories?: string[] }>
}) {
const { categories = [] } = await params
const [{ categories = [] }, activeStyle] = await Promise.all([
params,
getActiveStyle(),
])
const blocks = await getAllBlockIds(["registry:block"], categories)
return (
<div className="flex flex-col gap-12 md:gap-24">
{blocks.map((name) => (
<BlockDisplay name={name} key={name} />
<BlockDisplay name={name} key={name} styleName={activeStyle.name} />
))}
</div>
)

View File

@@ -2,6 +2,7 @@ import Link from "next/link"
import { BlockDisplay } from "@/components/block-display"
import { Button } from "@/registry/new-york-v4/ui/button"
import { getActiveStyle } from "@/registry/styles"
export const dynamic = "force-static"
export const revalidate = false
@@ -15,10 +16,12 @@ const FEATURED_BLOCKS = [
]
export default async function BlocksPage() {
const activeStyle = await getActiveStyle()
return (
<div className="flex flex-col gap-12 md:gap-24">
{FEATURED_BLOCKS.map((name) => (
<BlockDisplay name={name} key={name} />
<BlockDisplay name={name} key={name} styleName={activeStyle.name} />
))}
<div className="container-wrapper">
<div className="container flex justify-center py-6">

View File

@@ -3,6 +3,7 @@ import { notFound } from "next/navigation"
import { cn } from "@/lib/utils"
import { ChartDisplay } from "@/components/chart-display"
import { getActiveStyle } from "@/registry/styles"
import { charts } from "@/app/(app)/charts/charts"
export const revalidate = false
@@ -41,6 +42,7 @@ export default async function ChartPage({ params }: ChartPageProps) {
const chartType = type as ChartType
const chartList = charts[chartType]
const activeStyle = await getActiveStyle()
return (
<div className="grid flex-1 gap-12 lg:gap-24">
@@ -54,6 +56,7 @@ export default async function ChartPage({ params }: ChartPageProps) {
<ChartDisplay
key={chart.id}
name={chart.id}
styleName={activeStyle.name}
className={cn(chart.fullWidth && "md:col-span-2 lg:col-span-3")}
>
<chart.component />

View File

@@ -6,7 +6,9 @@ import {
IconArrowRight,
IconArrowUpRight,
} 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 { absoluteUrl } from "@/lib/utils"
@@ -25,7 +27,7 @@ export function generateStaticParams() {
}
export async function generateMetadata(props: {
params: Promise<{ slug?: string[] }>
params: Promise<{ slug: string[] }>
}) {
const params = await props.params
const page = source.getPage(params.slug)
@@ -73,7 +75,7 @@ export async function generateMetadata(props: {
}
export default async function Page(props: {
params: Promise<{ slug?: string[] }>
params: Promise<{ slug: string[] }>
}) {
const params = await props.params
const page = source.getPage(params.slug)
@@ -82,18 +84,24 @@ export default async function Page(props: {
}
const doc = page.data
// @ts-expect-error - revisit fumadocs types.
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 links = doc.links
const raw = await page.data.getText("raw")
const { attributes } = fm(raw)
const { links } = z
.object({
links: z
.object({
doc: z.string().optional(),
api: z.string().optional(),
})
.optional(),
})
.parse(attributes)
return (
<div
data-slot="docs"
className="flex items-stretch text-[1.05rem] sm:text-[15px] xl:w-full"
>
<div 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="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">
@@ -104,11 +112,7 @@ export default async function Page(props: {
{doc.title}
</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">
<DocsCopyPage
// @ts-expect-error - revisit fumadocs types.
page={doc.content}
url={absoluteUrl(page.url)}
/>
<DocsCopyPage page={raw} url={absoluteUrl(page.url)} />
{neighbours.previous && (
<Button
variant="secondary"
@@ -195,10 +199,8 @@ export default async function Page(props: {
</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="h-(--top-spacing) shrink-0" />
{/* @ts-expect-error - revisit fumadocs types. */}
{doc.toc?.length ? (
<div className="no-scrollbar overflow-y-auto px-8">
{/* @ts-expect-error - revisit fumadocs types. */}
<DocsTableOfContents toc={doc.toc} />
<div className="h-12" />
</div>

View File

@@ -16,8 +16,9 @@ import { Button } from "@/registry/new-york-v4/ui/button"
export const dynamic = "force-static"
export const revalidate = false
const title = "Examples"
const description = "Check out some examples app built using the components."
const title = "The Foundation for your Design System"
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 = {
title,
@@ -52,24 +53,20 @@ export default function ExamplesLayout({
<>
<PageHeader>
<Announcement />
<PageHeaderHeading>Build your Component Library</PageHeaderHeading>
<PageHeaderDescription>
A set of beautifully-designed, accessible components and a code
distribution platform. Works with your favorite frameworks. Open
Source. Open Code.
</PageHeaderDescription>
<PageHeaderHeading className="max-w-4xl">{title}</PageHeaderHeading>
<PageHeaderDescription>{description}</PageHeaderDescription>
<PageActions>
<Button asChild size="sm">
<Link href="/docs">Get Started</Link>
<Link href="/docs/installation">Get Started</Link>
</Button>
<Button asChild size="sm" variant="ghost">
<Link href="/blocks">Browse Blocks</Link>
<Link href="/docs/components">View Components</Link>
</Button>
</PageActions>
</PageHeader>
<PageNav id="examples">
<PageNav id="examples" className="hidden md:flex">
<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>
<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">

View File

@@ -3,7 +3,10 @@ import { SiteHeader } from "@/components/site-header"
export default function AppLayout({ children }: { children: React.ReactNode }) {
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 />
<main className="flex flex-1 flex-col">{children}</main>
<SiteFooter />

View File

@@ -3,22 +3,26 @@ import { NextResponse, type NextRequest } from "next/server"
import { processMdxForLLMs } from "@/lib/llm"
import { source } from "@/lib/source"
import { getActiveStyle } from "@/registry/styles"
export const revalidate = false
export async function GET(
_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)
if (!page) {
notFound()
}
// @ts-expect-error - revisit fumadocs types.
const processedContent = processMdxForLLMs(page.data.content)
const processedContent = processMdxForLLMs(
await page.data.getText("raw"),
activeStyle.name
)
return new NextResponse(processedContent, {
headers: {

View File

@@ -36,6 +36,7 @@ import { ItemDemo } from "./components/item-demo"
import { KbdDemo } from "./components/kbd-demo"
import { LabelDemo } from "./components/label-demo"
import { MenubarDemo } from "./components/menubar-demo"
import { NativeSelectDemo } from "./components/native-select-demo"
import { NavigationMenuDemo } from "./components/navigation-menu-demo"
import { PaginationDemo } from "./components/pagination-demo"
import { PopoverDemo } from "./components/popover-demo"
@@ -279,6 +280,13 @@ export const componentRegistry: Record<string, ComponentConfig> = {
type: "registry:ui",
href: "/sink/navigation-menu",
},
"native-select": {
name: "Native Select",
component: NativeSelectDemo,
type: "registry:ui",
href: "/sink/native-select",
label: "New",
},
pagination: {
name: "Pagination",
component: PaginationDemo,

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

View File

@@ -1,4 +1,11 @@
import { BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react"
import {
BoldIcon,
BookmarkIcon,
HeartIcon,
ItalicIcon,
StarIcon,
UnderlineIcon,
} from "lucide-react"
import {
ToggleGroup,
@@ -8,7 +15,7 @@ import {
export function ToggleGroupDemo() {
return (
<div className="flex flex-wrap items-start gap-4">
<ToggleGroup type="multiple">
<ToggleGroup type="multiple" spacing={2}>
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<BoldIcon />
</ToggleGroupItem>
@@ -54,12 +61,7 @@ export function ToggleGroupDemo() {
</ToggleGroupItem>
</ToggleGroup>
<ToggleGroup
type="single"
size="sm"
defaultValue="last-24-hours"
className="*:data-[slot=toggle-group-item]:px-3"
>
<ToggleGroup type="single" size="sm" defaultValue="last-24-hours">
<ToggleGroupItem
value="last-24-hours"
aria-label="Toggle last 24 hours"
@@ -70,6 +72,68 @@ export function ToggleGroupDemo() {
Last 7 days
</ToggleGroupItem>
</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>
)
}

View 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>
</>
)
}

View File

@@ -1,30 +1,39 @@
/* eslint-disable react-hooks/static-components */
import * as React from "react"
import { Metadata } from "next"
import { notFound } from "next/navigation"
import { registryItemSchema } from "shadcn/schema"
import { z } from "zod"
import { siteConfig } from "@/lib/config"
import { getRegistryComponent, getRegistryItem } from "@/lib/registry"
import { absoluteUrl, cn } from "@/lib/utils"
import { getStyle, STYLES, type Style } from "@/registry/styles"
export const revalidate = false
export const dynamic = "force-static"
export const dynamicParams = false
const getCachedRegistryItem = React.cache(async (name: string) => {
return await getRegistryItem(name)
})
const getCachedRegistryItem = React.cache(
async (name: string, styleName: Style["name"]) => {
return await getRegistryItem(name, styleName)
}
)
export async function generateMetadata({
params,
}: {
params: Promise<{
style: string
name: string
}>
}): Promise<Metadata> {
const { name } = await params
const item = await getCachedRegistryItem(name)
const { style: styleName, name } = await params
const style = getStyle(styleName)
if (!style) {
return {}
}
const item = await getCachedRegistryItem(name, style.name)
if (!item) {
return {}
@@ -34,13 +43,13 @@ export async function generateMetadata({
const description = item.description
return {
title: item.description,
title: item.name,
description,
openGraph: {
title,
description,
type: "article",
url: absoluteUrl(`/view/${item.name}`),
url: absoluteUrl(`/view/${style.name}/${item.name}`),
images: [
{
url: siteConfig.ogImage,
@@ -62,32 +71,52 @@ export async function generateMetadata({
export async function generateStaticParams() {
const { Index } = await import("@/registry/__index__")
const index = z.record(registryItemSchema).parse(Index)
const params: Array<{ style: string; name: string }> = []
return Object.values(index)
.filter((block) =>
[
"registry:block",
"registry:component",
"registry:example",
"registry:internal",
].includes(block.type)
)
.map((block) => ({
name: block.name,
}))
for (const style of STYLES) {
if (!Index[style.name]) {
continue
}
const styleIndex = Index[style.name]
for (const itemName in styleIndex) {
const item = styleIndex[itemName]
if (
[
"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({
params,
}: {
params: Promise<{
style: string
name: string
}>
}) {
const { name } = await params
const item = await getCachedRegistryItem(name)
const Component = getRegistryComponent(name)
const { style: styleName, name } = await params
const style = getStyle(styleName)
if (!style) {
return notFound()
}
const item = await getCachedRegistryItem(name, style.name)
const Component = getRegistryComponent(name, style.name)
if (!item || !Component) {
return notFound()

View File

@@ -0,0 +1,5 @@
import { createFromSource } from "fumadocs-core/search/server"
import { source } from "@/lib/source"
export const { GET } = createFromSource(source)

View File

@@ -1,4 +1,5 @@
import type { Metadata } from "next"
import { NuqsAdapter } from "nuqs/adapters/next/app"
import { META_THEME_COLORS, siteConfig } from "@/lib/config"
import { fontVariables } from "@/lib/fonts"
@@ -84,18 +85,20 @@ export default function RootLayout({
</head>
<body
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
)}
>
<ThemeProvider>
<LayoutProvider>
<ActiveThemeProvider initialTheme="blue">
{children}
<TailwindIndicator />
<Toaster position="top-center" />
<Analytics />
</ActiveThemeProvider>
<NuqsAdapter>
<ActiveThemeProvider>
{children}
<TailwindIndicator />
<Toaster position="top-center" />
<Analytics />
</ActiveThemeProvider>
</NuqsAdapter>
</LayoutProvider>
</ThemeProvider>
</body>

View File

@@ -8,7 +8,7 @@ import {
useState,
} from "react"
const DEFAULT_THEME = "blue"
const DEFAULT_THEME = "default"
type ThemeContextType = {
activeTheme: string

View File

@@ -5,7 +5,7 @@ import { Badge } from "@/registry/new-york-v4/ui/badge"
export function Announcement() {
return (
<Badge asChild variant="secondary" className="rounded-full">
<Badge asChild variant="secondary" className="bg-transparent">
<Link href="/docs/changelog">
<span className="flex size-2 rounded-full bg-blue-500" title="New" />
New Components: Field, Input Group, Item and more <ArrowRightIcon />

View File

@@ -10,9 +10,16 @@ import {
import { cn } from "@/lib/utils"
import { BlockViewer } from "@/components/block-viewer"
import { ComponentPreview } from "@/components/component-preview"
import { type Style } from "@/registry/styles"
export async function BlockDisplay({ name }: { name: string }) {
const item = await getCachedRegistryItem(name)
export async function BlockDisplay({
name,
styleName,
}: {
name: string
styleName: Style["name"]
}) {
const item = await getCachedRegistryItem(name, styleName)
if (!item?.files) {
return null
@@ -24,9 +31,15 @@ export async function BlockDisplay({ name }: { name: string }) {
])
return (
<BlockViewer item={item} tree={tree} highlightedFiles={highlightedFiles}>
<BlockViewer
item={item}
tree={tree}
highlightedFiles={highlightedFiles}
styleName={styleName}
>
<ComponentPreview
name={item.name}
styleName={styleName}
hideCode
className={cn(
"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) => {
return await getRegistryItem(name)
})
const getCachedRegistryItem = React.cache(
async (name: string, styleName: Style["name"]) => {
return await getRegistryItem(name, styleName)
}
)
const getCachedFileTree = React.cache(
async (files: Array<{ path: string; target?: string }>) => {

View File

@@ -54,6 +54,7 @@ import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/new-york-v4/ui/toggle-group"
import { type Style } from "@/registry/styles"
type BlockViewerContext = {
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 } =
useBlockViewer()
const { copyToClipboard, isCopied } = useCopyToClipboard()
@@ -181,7 +190,7 @@ function BlockViewerToolbar() {
asChild
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>
<Fullscreen />
</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()
return (
<iframe
key={iframeKey}
src={`/view/${item.name}`}
src={`/view/${styleName}/${item.name}`}
height={item.meta?.iframeHeight ?? 930}
loading="lazy"
className={cn(
@@ -239,7 +254,7 @@ function BlockViewerIframe({ className }: { className?: string }) {
)
}
function BlockViewerView() {
function BlockViewerView({ styleName }: { styleName: Style["name"] }) {
const { resizablePanelRef } = useBlockViewer()
return (
@@ -256,7 +271,7 @@ function BlockViewerView() {
defaultSize={100}
minSize={30}
>
<BlockViewerIframe />
<BlockViewerIframe styleName={styleName} />
</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" />
<ResizablePanel defaultSize={0} minSize={0} />
@@ -471,10 +486,9 @@ function BlockViewer({
tree,
highlightedFiles,
children,
styleName,
...props
}: Pick<BlockViewerContext, "item" | "tree" | "highlightedFiles"> & {
children: React.ReactNode
}) {
}: BlockViewerProps) {
return (
<BlockViewerProvider
item={item}
@@ -482,8 +496,8 @@ function BlockViewer({
highlightedFiles={highlightedFiles}
{...props}
>
<BlockViewerToolbar />
<BlockViewerView />
<BlockViewerToolbar styleName={styleName} />
<BlockViewerView styleName={styleName} />
<BlockViewerCode />
<BlockViewerMobile>{children}</BlockViewerMobile>
</BlockViewerProvider>

View File

@@ -3,8 +3,8 @@
import Link from "next/link"
import { usePathname } from "next/navigation"
import { registryCategories } from "@/lib/categories"
import { ScrollArea, ScrollBar } from "@/registry/new-york-v4/ui/scroll-area"
import { registryCategories } from "@/registry/registry-categories"
export function BlocksNav() {
const pathname = usePathname()

View File

@@ -1,7 +1,7 @@
"use client"
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 { cn } from "@/lib/utils"
@@ -54,7 +54,7 @@ export function ChartCopyButton({
{...props}
>
<span className="sr-only">Copy</span>
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
{hasCopied ? <IconCheck /> : <IconCopy />}
</Button>
</TooltipTrigger>
<TooltipContent className="bg-black text-white">Copy code</TooltipContent>

View File

@@ -6,6 +6,7 @@ import { highlightCode } from "@/lib/highlight-code"
import { getRegistryItem } from "@/lib/registry"
import { cn } from "@/lib/utils"
import { ChartToolbar } from "@/components/chart-toolbar"
import { type Style } from "@/registry/styles"
export type Chart = z.infer<typeof registryItemSchema> & {
highlightedCode: string
@@ -13,10 +14,14 @@ export type Chart = z.infer<typeof registryItemSchema> & {
export async function ChartDisplay({
name,
styleName,
children,
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(
chart?.files?.[0]?.content ?? ""
)
@@ -45,9 +50,11 @@ export async function ChartDisplay({
)
}
const getCachedRegistryItem = React.cache(async (name: string) => {
return await getRegistryItem(name)
})
const getCachedRegistryItem = React.cache(
async (name: string, styleName: Style["name"]) => {
return await getRegistryItem(name, styleName)
}
)
const getChartHighlightedCode = React.cache(async (content: string) => {
return await highlightCode(content)

View File

@@ -1,7 +1,7 @@
"use client"
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 { 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="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>
<TabsList className="rounded-none bg-transparent p-0">
{Object.entries(tabs).map(([key]) => {
@@ -123,7 +123,7 @@ export function CodeBlockCommand({
onClick={copyCommand}
>
<span className="sr-only">Copy</span>
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
{hasCopied ? <IconCheck /> : <IconCopy />}
</Button>
</TooltipTrigger>
<TooltipContent>

View File

@@ -4,14 +4,15 @@ import * as React from "react"
import { useRouter } from "next/navigation"
import { type DialogProps } from "@radix-ui/react-dialog"
import { IconArrowRight } from "@tabler/icons-react"
import { useDocsSearch } from "fumadocs-core/search/client"
import { CornerDownLeftIcon, SquareDashedIcon } from "lucide-react"
import { type Color, type ColorPalette } from "@/lib/colors"
import { trackEvent } from "@/lib/events"
import { showMcpDocs } from "@/lib/flags"
import { source } from "@/lib/source"
import { cn } from "@/lib/utils"
import { useConfig } from "@/hooks/use-config"
import { useIsMac } from "@/hooks/use-is-mac"
import { useMutationObserver } from "@/hooks/use-mutation-observer"
import { copyToClipboardWithMeta } from "@/components/copy-button"
import { Button } from "@/registry/new-york-v4/ui/button"
@@ -33,6 +34,7 @@ import {
} from "@/registry/new-york-v4/ui/dialog"
import { Kbd, KbdGroup } from "@/registry/new-york-v4/ui/kbd"
import { Separator } from "@/registry/new-york-v4/ui/separator"
import { Spinner } from "@/registry/new-york-v4/ui/spinner"
export function CommandMenu({
tree,
@@ -47,15 +49,63 @@ export function CommandMenu({
navItems?: { href: string; label: string }[]
}) {
const router = useRouter()
const isMac = useIsMac()
const [config] = useConfig()
const [open, setOpen] = React.useState(false)
const [selectedType, setSelectedType] = React.useState<
"color" | "page" | "component" | "block" | null
>(null)
const [copyPayload, setCopyPayload] = React.useState("")
const { search, setSearch, query } = useDocsSearch({
type: "fetch",
})
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(
(isComponent: boolean, item: { url: string; name?: React.ReactNode }) => {
if (isComponent) {
@@ -154,7 +204,7 @@ export function CommandMenu({
<span className="inline-flex lg:hidden">Search...</span>
<div className="absolute top-1.5 right-1.5 hidden gap-1 sm:flex">
<KbdGroup>
<Kbd className="border">{isMac ? "⌘" : "Ctrl"}</Kbd>
<Kbd className="border"></Kbd>
<Kbd className="border">K</Kbd>
</KbdGroup>
</div>
@@ -171,6 +221,7 @@ export function CommandMenu({
<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"
filter={(value, search, keywords) => {
handleSearchChange(search)
const extendValue = value + " " + (keywords?.join(" ") || "")
if (extendValue.toLowerCase().includes(search.toLowerCase())) {
return 1
@@ -178,10 +229,17 @@ export function CommandMenu({
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">
<CommandEmpty className="text-muted-foreground py-12 text-center text-sm">
No results found.
{query.isLoading ? "Searching..." : "No results found."}
</CommandEmpty>
{navItems && navItems.length > 0 && (
<CommandGroup
@@ -322,6 +380,12 @@ export function CommandMenu({
))}
</CommandGroup>
) : null}
<SearchResults
open={open}
setOpen={setOpen}
query={query}
search={search}
/>
</CommandList>
</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">
@@ -338,7 +402,7 @@ export function CommandMenu({
<>
<Separator orientation="vertical" className="!h-4" />
<div className="flex items-center gap-1">
<CommandMenuKbd>{isMac ? "⌘" : "Ctrl"}</CommandMenuKbd>
<CommandMenuKbd></CommandMenuKbd>
<CommandMenuKbd>C</CommandMenuKbd>
{copyPayload}
</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>
)
}

View File

@@ -3,7 +3,6 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { Tabs, TabsList, TabsTrigger } from "@/registry/new-york-v4/ui/tabs"
export function ComponentPreviewTabs({
className,
@@ -20,64 +19,32 @@ export function ComponentPreviewTabs({
component: React.ReactNode
source: React.ReactNode
}) {
const [tab, setTab] = React.useState("preview")
return (
<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}
>
<Tabs
className="relative mr-auto w-full"
value={tab}
onValueChange={setTab}
>
<div className="flex items-center justify-between">
{!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 data-slot="preview">
<div
data-align={align}
className={cn(
"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"
)}
</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
data-align={align}
className={cn(
"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"
)}
data-slot="code"
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]"
>
{component}
{source}
</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>
)

View File

@@ -3,9 +3,11 @@ import Image from "next/image"
import { ComponentPreviewTabs } from "@/components/component-preview-tabs"
import { ComponentSource } from "@/components/component-source"
import { Index } from "@/registry/__index__"
import { type Style } from "@/registry/styles"
export function ComponentPreview({
name,
styleName = "new-york-v4",
type,
className,
align = "center",
@@ -14,13 +16,14 @@ export function ComponentPreview({
...props
}: React.ComponentProps<"div"> & {
name: string
styleName?: Style["name"]
align?: "center" | "start" | "end"
description?: string
hideCode?: boolean
type?: "block" | "component" | "example"
chromeLessOnMobile?: boolean
}) {
const Component = Index[name]?.component
const Component = Index[styleName]?.[name]?.component
if (!Component) {
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"
/>
<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>
)
@@ -64,7 +67,13 @@ export function ComponentPreview({
align={align}
hideCode={hideCode}
component={<Component />}
source={<ComponentSource name={name} collapsible={false} />}
source={
<ComponentSource
name={name}
collapsible={false}
styleName={styleName}
/>
}
chromeLessOnMobile={chromeLessOnMobile}
{...props}
/>

View File

@@ -8,6 +8,7 @@ import { cn } from "@/lib/utils"
import { CodeCollapsibleWrapper } from "@/components/code-collapsible-wrapper"
import { CopyButton } from "@/components/copy-button"
import { getIconForLanguageExtension } from "@/components/icons"
import { type Style } from "@/registry/styles"
export async function ComponentSource({
name,
@@ -16,12 +17,14 @@ export async function ComponentSource({
language,
collapsible = true,
className,
styleName = "new-york-v4",
}: React.ComponentProps<"div"> & {
name?: string
src?: string
title?: string
language?: string
collapsible?: boolean
styleName?: Style["name"]
}) {
if (!name && !src) {
return null
@@ -30,7 +33,7 @@ export async function ComponentSource({
let code: string | undefined
if (name) {
const item = await getRegistryItem(name)
const item = await getRegistryItem(name, styleName)
code = item?.files?.[0]?.content
}
@@ -44,8 +47,8 @@ export async function ComponentSource({
}
// Fix imports.
// Replace @/registry/new-york-v4/ with @/components/.
code = code.replaceAll("@/registry/new-york-v4/", "@/components/")
// Replace @/registry/${style}/ with @/components/.
code = code.replaceAll(`@/registry/${styleName}/`, "@/components/")
// Replace export default with export.
code = code.replaceAll("export default", "export")

View File

@@ -1,7 +1,7 @@
"use client"
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 { cn } from "@/lib/utils"
@@ -24,11 +24,13 @@ export function CopyButton({
className,
variant = "ghost",
event,
tooltip = "Copy to Clipboard",
...props
}: React.ComponentProps<typeof Button> & {
value: string
src?: string
event?: Event["name"]
tooltip?: string
}) {
const [hasCopied, setHasCopied] = React.useState(false)
@@ -43,6 +45,7 @@ export function CopyButton({
<TooltipTrigger asChild>
<Button
data-slot="copy-button"
data-copied={hasCopied}
size="icon"
variant={variant}
className={cn(
@@ -66,12 +69,10 @@ export function CopyButton({
{...props}
>
<span className="sr-only">Copy</span>
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
{hasCopied ? <IconCheck /> : <IconCopy />}
</Button>
</TooltipTrigger>
<TooltipContent>
{hasCopied ? "Copied" : "Copy to Clipboard"}
</TooltipContent>
<TooltipContent>{hasCopied ? "Copied" : tooltip}</TooltipContent>
</Tooltip>
)
}

View 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&apos;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&apos;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>
)
}

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

View File

@@ -4,7 +4,7 @@ import { Fragment } from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { useBreadcrumb } from "fumadocs-core/breadcrumb"
import type { PageTree } from "fumadocs-core/server"
import type { Root } from "fumadocs-core/page-tree"
import {
Breadcrumb,
@@ -19,7 +19,7 @@ export function DocsBreadcrumb({
tree,
className,
}: {
tree: PageTree.Root
tree: Root
className?: string
}) {
const pathname = usePathname()

View File

@@ -24,8 +24,8 @@ const TOP_LEVEL_SECTIONS = [
href: "/docs/components",
},
{
name: "Registry",
href: "/docs/registry",
name: "Directory",
href: "/docs/directory",
},
{
name: "MCP Server",
@@ -51,12 +51,12 @@ export function DocsSidebar({
return (
<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"
{...props}
>
<SidebarContent className="no-scrollbar overflow-x-hidden px-2 pb-12">
<div className="h-(--top-spacing) shrink-0" />
<SidebarContent className="no-scrollbar overflow-x-hidden px-2">
<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>
<SidebarGroupLabel className="text-muted-foreground font-medium">
Sections
@@ -141,6 +141,7 @@ export function DocsSidebar({
</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>
</Sidebar>
)

View File

@@ -11,7 +11,7 @@ export function GitHubLink() {
<Button asChild size="sm" variant="ghost" className="h-8 shadow-none">
<Link href={siteConfig.links.github} target="_blank" rel="noreferrer">
<Icons.gitHub />
<React.Suspense fallback={<Skeleton className="h-4 w-8" />}>
<React.Suspense fallback={<Skeleton className="h-4 w-[42px]" />}>
<StarsCount />
</React.Suspense>
</Link>
@@ -21,15 +21,20 @@ export function GitHubLink() {
export async function StarsCount() {
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 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 (
<span className="text-muted-foreground w-8 text-xs tabular-nums">
{json.stargazers_count >= 1000
? `${(json.stargazers_count / 1000).toFixed(1)}k`
: json.stargazers_count.toLocaleString()}
<span className="text-muted-foreground w-fit text-xs tabular-nums">
{formattedCount.replace(".0k", "k")}
</span>
)
}

View File

@@ -16,7 +16,7 @@ export function MainNav({
const pathname = usePathname()
return (
<nav className={cn("items-center gap-0.5", className)} {...props}>
<nav className={cn("items-center", className)} {...props}>
{items.map((item) => (
<Button key={item.href} variant="ghost" asChild size="sm">
<Link

View File

@@ -22,8 +22,8 @@ const TOP_LEVEL_SECTIONS = [
href: "/docs/components",
},
{
name: "Registry",
href: "/docs/registry",
name: "Directory",
href: "/docs/directory",
},
{
name: "MCP Server",

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

View File

@@ -21,7 +21,7 @@ export function SiteHeader() {
return (
<header className="bg-background sticky top-0 z-50 w-full">
<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
tree={pageTree}
items={siteConfig.navItems}

View File

@@ -1,13 +1,15 @@
"use client"
import * as React from "react"
import { IconCheck, IconCopy } from "@tabler/icons-react"
import template from "lodash/template"
import { CheckIcon, ClipboardIcon } from "lucide-react"
import { THEMES } from "@/lib/themes"
import { cn } from "@/lib/utils"
import { useThemeConfig } from "@/components/active-theme"
import { copyToClipboardWithMeta } from "@/components/copy-button"
import { Icons } from "@/components/icons"
import { BaseColor, baseColors, baseColorsOKLCH } from "@/registry/base-colors"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Dialog,
@@ -41,21 +43,12 @@ import {
TabsList,
TabsTrigger,
} from "@/registry/new-york-v4/ui/tabs"
import {
BaseColor,
baseColors,
baseColorsOKLCH,
} from "@/registry/registry-base-colors"
interface BaseColorOKLCH {
light: 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">) {
const { activeTheme = "neutral", setActiveTheme } = useThemeConfig()
@@ -131,9 +124,7 @@ export function CopyCodeButton({
</DrawerTrigger>
<DrawerContent className="h-auto">
<DrawerHeader>
<DrawerTitle className="capitalize">
{activeThemeName === "neutral" ? "Default" : activeThemeName}
</DrawerTitle>
<DrawerTitle className="capitalize">{activeThemeName}</DrawerTitle>
<DrawerDescription>
Copy and paste the following code into your CSS file.
</DrawerDescription>
@@ -143,15 +134,20 @@ export function CopyCodeButton({
</Drawer>
<Dialog>
<DialogTrigger asChild>
<Button className={cn("hidden sm:flex", className)} {...props}>
Copy Code
<Button
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>
</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>
<DialogTitle className="capitalize">
{activeThemeName === "neutral" ? "Default" : activeThemeName}
</DialogTitle>
<DialogTitle className="capitalize">{activeThemeName}</DialogTitle>
<DialogDescription>
Copy and paste the following code into your CSS file.
</DialogDescription>
@@ -165,7 +161,7 @@ export function CopyCodeButton({
function CustomizerCode({ themeName }: { themeName: string }) {
const [hasCopied, setHasCopied] = React.useState(false)
const [tailwindVersion, setTailwindVersion] = React.useState("v4")
const [tailwindVersion, setTailwindVersion] = React.useState("v4-oklch")
const activeTheme = React.useMemo(
() => baseColors.find((theme) => theme.name === themeName),
[themeName]
@@ -191,10 +187,11 @@ function CustomizerCode({ themeName }: { themeName: string }) {
className="min-w-0 px-4 pb-4 md:p-0"
>
<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>
</TabsList>
<TabsContent value="v4">
<TabsContent value="v4-oklch">
<figure
data-rehype-pretty-code-figure
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"
onClick={() => {
copyToClipboardWithMeta(
tailwindVersion === "v3"
? getThemeCode(activeTheme, 0.65)
: getThemeCodeOKLCH(activeThemeOKLCH, 0.65),
getThemeCodeOKLCH(activeThemeOKLCH, 0.65),
{
name: "copy_theme_code",
properties: {
theme: themeName,
radius: 0.5,
radius: 0.65,
},
}
)
@@ -231,7 +226,7 @@ function CustomizerCode({ themeName }: { themeName: string }) {
}}
>
<span className="sr-only">Copy</span>
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
{hasCopied ? <IconCheck /> : <IconCopy />}
</Button>
<code data-line-numbers data-language="css">
<span data-line className="line text-code-foreground">
@@ -246,7 +241,8 @@ function CustomizerCode({ themeName }: { themeName: string }) {
className="line text-code-foreground"
key={key}
>
&nbsp;&nbsp;&nbsp;--{key}: {value};
&nbsp;&nbsp;&nbsp;--{key}: <ColorIndicator color={value} />{" "}
{value};
</span>
))}
<span data-line className="line text-code-foreground">
@@ -264,7 +260,8 @@ function CustomizerCode({ themeName }: { themeName: string }) {
className="line text-code-foreground"
key={key}
>
&nbsp;&nbsp;&nbsp;--{key}: {value};
&nbsp;&nbsp;&nbsp;--{key}: <ColorIndicator color={value} />{" "}
{value};
</span>
))}
<span data-line className="line text-code-foreground">
@@ -274,6 +271,90 @@ function CustomizerCode({ themeName }: { themeName: string }) {
</pre>
</figure>
</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">
&nbsp;:root &#123;
</span>
<span data-line className="line text-code-foreground">
&nbsp;&nbsp;&nbsp;--radius: 0.65rem;
</span>
{Object.entries(activeTheme?.cssVars.light || {}).map(
([key, value]) => (
<span
data-line
className="line text-code-foreground"
key={key}
>
&nbsp;&nbsp;&nbsp;--{key}:{" "}
<ColorIndicator color={`hsl(${value})`} /> hsl({value});
</span>
)
)}
<span data-line className="line text-code-foreground">
&nbsp;&#125;
</span>
<span data-line className="line text-code-foreground">
&nbsp;
</span>
<span data-line className="line text-code-foreground">
&nbsp;.dark &#123;
</span>
{Object.entries(activeTheme?.cssVars.dark || {}).map(
([key, value]) => (
<span
data-line
className="line text-code-foreground"
key={key}
>
&nbsp;&nbsp;&nbsp;--{key}:{" "}
<ColorIndicator color={`hsl(${value})`} /> hsl({value});
</span>
)
)}
<span data-line className="line text-code-foreground">
&nbsp;&#125;
</span>
</code>
</pre>
</figure>
</TabsContent>
<TabsContent value="v3">
<figure
data-rehype-pretty-code-figure
@@ -295,23 +376,18 @@ function CustomizerCode({ themeName }: { themeName: string }) {
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(
tailwindVersion === "v3"
? getThemeCode(activeTheme, 0.65)
: getThemeCodeOKLCH(activeThemeOKLCH, 0.65),
{
name: "copy_theme_code",
properties: {
theme: themeName,
radius: 0.5,
},
}
)
copyToClipboardWithMeta(getThemeCode(activeTheme, 0.5), {
name: "copy_theme_code",
properties: {
theme: themeName,
radius: 0.5,
},
})
setHasCopied(true)
}}
>
<span className="sr-only">Copy</span>
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
{hasCopied ? <IconCheck /> : <IconCopy />}
</Button>
<code data-line-numbers data-language="css">
<span data-line className="line">
@@ -322,10 +398,16 @@ function CustomizerCode({ themeName }: { themeName: string }) {
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--background:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.light["background"]})`}
/>{" "}
{activeTheme?.cssVars.light["background"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--foreground:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.light["foreground"]})`}
/>{" "}
{activeTheme?.cssVars.light["foreground"]};
</span>
{[
@@ -340,6 +422,13 @@ function CustomizerCode({ themeName }: { themeName: string }) {
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
<ColorIndicator
color={`hsl(${
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
]
})`}
/>{" "}
{
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
@@ -349,6 +438,13 @@ function CustomizerCode({ themeName }: { themeName: string }) {
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}-foreground:{" "}
<ColorIndicator
color={`hsl(${
activeTheme?.cssVars.light[
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.light
]
})`}
/>{" "}
{
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">
&nbsp;&nbsp;&nbsp;&nbsp;--border:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.light["border"]})`}
/>{" "}
{activeTheme?.cssVars.light["border"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--input:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.light["input"]})`}
/>{" "}
{activeTheme?.cssVars.light["input"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--ring:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.light["ring"]})`}
/>{" "}
{activeTheme?.cssVars.light["ring"]};
</span>
<span data-line className="line">
@@ -378,6 +483,13 @@ function CustomizerCode({ themeName }: { themeName: string }) {
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
<ColorIndicator
color={`hsl(${
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
]
})`}
/>{" "}
{
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
@@ -399,10 +511,16 @@ function CustomizerCode({ themeName }: { themeName: string }) {
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--background:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.dark["background"]})`}
/>{" "}
{activeTheme?.cssVars.dark["background"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--foreground:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.dark["foreground"]})`}
/>{" "}
{activeTheme?.cssVars.dark["foreground"]};
</span>
{[
@@ -417,6 +535,13 @@ function CustomizerCode({ themeName }: { themeName: string }) {
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
<ColorIndicator
color={`hsl(${
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
]
})`}
/>{" "}
{
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
@@ -426,6 +551,13 @@ function CustomizerCode({ themeName }: { themeName: string }) {
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}-foreground:{" "}
<ColorIndicator
color={`hsl(${
activeTheme?.cssVars.dark[
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.dark
]
})`}
/>{" "}
{
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">
&nbsp;&nbsp;&nbsp;&nbsp;--border:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.dark["border"]})`}
/>{" "}
{activeTheme?.cssVars.dark["border"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--input:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.dark["input"]})`}
/>{" "}
{activeTheme?.cssVars.dark["input"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--ring:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.dark["ring"]})`}
/>{" "}
{activeTheme?.cssVars.dark["ring"]};
</span>
{["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"].map(
@@ -452,6 +593,13 @@ function CustomizerCode({ themeName }: { themeName: string }) {
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
<ColorIndicator
color={`hsl(${
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
]
})`}
/>{" "}
{
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) {
if (!theme) {
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 = `
@layer base {
:root {

View File

@@ -1,74 +1,30 @@
"use client"
import { THEMES } from "@/lib/themes"
import { cn } from "@/lib/utils"
import { useThemeConfig } from "@/components/active-theme"
import { Label } from "@/registry/new-york-v4/ui/label"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
const DEFAULT_THEMES = [
{
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",
},
]
import { CopyCodeButton } from "./theme-customizer"
export function ThemeSelector({ className }: React.ComponentProps<"div">) {
const { activeTheme, setActiveTheme } = useThemeConfig()
const value = activeTheme === "default" ? "neutral" : activeTheme
return (
<div className={cn("flex items-center gap-2", className)}>
<Label htmlFor="theme-selector" className="sr-only">
Theme
</Label>
<Select value={activeTheme} onValueChange={setActiveTheme}>
<Select value={value} onValueChange={setActiveTheme}>
<SelectTrigger
id="theme-selector"
size="sm"
@@ -78,32 +34,18 @@ export function ThemeSelector({ className }: React.ComponentProps<"div">) {
<SelectValue placeholder="Select a theme" />
</SelectTrigger>
<SelectContent align="end">
<SelectGroup>
{DEFAULT_THEMES.map((theme) => (
<SelectItem
key={theme.name}
value={theme.value}
className="data-[state=checked]:opacity-50"
>
{theme.name}
</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>
{THEMES.map((theme) => (
<SelectItem
key={theme.name}
value={theme.name}
className="data-[state=checked]:opacity-50"
>
{theme.label}
</SelectItem>
))}
</SelectContent>
</Select>
<CopyCodeButton variant="secondary" size="icon-sm" />
</div>
)
}

View File

@@ -1014,7 +1014,7 @@ It has support for infinite looping, autoplay, vertical orientation, and more.
### 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!
@@ -1036,7 +1036,7 @@ Build resizable panel groups and layouts with this `<Resizable />` component.
### 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" />

View 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>

View File

@@ -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.
</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
- [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.
## 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
- [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.

View File

@@ -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.
```json {10} title="components.json" showLineNumbers
```json {4} title="components.json" showLineNumbers
{
"style": "default",
"style": "new-york",
"rsc": false,
"tsx": false,
"tailwind": {
"config": "tailwind.config.js",
"config": "",
"css": "src/app/globals.css",
"baseColor": "zinc",
"cssVariables": true
},
"rsc": false,
"tsx": false,
"iconLibrary": "lucide",
"aliases": {
"utils": "~/lib/utils",
"components": "~/components"
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
```

View File

@@ -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
{
"mcpServers": {
"servers": {
"shadcn": {
"command": "npx",
"args": ["shadcn@latest", "mcp"]

View File

@@ -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.
```bash
npx shadcn@canary init
npx shadcn@latest init
```
Select the `Next.js (Monorepo)` option.
@@ -51,15 +51,15 @@ cd apps/web
```
```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
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

View File

@@ -3,17 +3,9 @@ title: Next.js 15 + 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
`canary` release. See the docs for [Tailwind v4](/docs/tailwind-v4) for more
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.
`latest` release. **This guide might be outdated. Proceed with caution.**
</Callout>
## 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. |
| [input-otp](https://www.npmjs.com/package/input-otp) | ✅ | |
| [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) | ✅ | |
If you have any questions, please [open an issue](https://github.com/shadcn/ui/issues) on GitHub.

View File

@@ -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"
}
```

View File

@@ -15,7 +15,7 @@ To use CSS variables for theming set `tailwind.cssVariables` to `true` in your `
```json {8} title="components.json" showLineNumbers
{
"style": "default",
"style": "new-york",
"rsc": true,
"tailwind": {
"config": "",
@@ -44,7 +44,7 @@ To use utility classes for theming set `tailwind.cssVariables` to `false` in you
```json {8} title="components.json" showLineNumbers
{
"style": "default",
"style": "new-york",
"rsc": true,
"tailwind": {
"config": "",
@@ -52,14 +52,14 @@ To use utility classes for theming set `tailwind.cssVariables` to `false` in you
"baseColor": "neutral",
"cssVariables": false
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
}
```
@@ -163,7 +163,7 @@ Here's the list of variables available for customization:
## 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
:root {

View File

@@ -49,7 +49,7 @@ import { Badge } from "@/components/ui/badge"
```
```tsx
<Badge variant="default |outline | secondary | destructive">Badge</Badge>
<Badge variant="default | outline | secondary | destructive">Badge</Badge>
```
### Link

View File

@@ -108,12 +108,40 @@ To use the Persian calendar, edit `components/ui/calendar.tsx` and replace `reac
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
### Range Calendar
<ComponentPreview
name="calendar-02"
name="calendar-05"
title="Range Calendar"
description="A calendar showing the current date and range selection."
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."
/>
### 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
@@ -289,7 +344,10 @@ function Calendar({
defaultClassNames.week_number
),
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
),
range_start: cn(
@@ -417,3 +475,25 @@ npx shadcn@latest add calendar-02
```
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
}}
```

View File

@@ -31,7 +31,7 @@ We designed the `chart` component with composition in mind. **You build your cha
```tsx showLineNumbers /ChartContainer/ /ChartTooltipContent/
import { Bar, BarChart } from "recharts"
import { ChartContainer, ChartTooltipContent } from "@/components/ui/charts"
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
export function MyChart() {
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">
**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>
@@ -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.
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/
import { Monitor } from "lucide-react"
@@ -394,7 +394,7 @@ const chartConfig = {
## 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

View File

@@ -10,7 +10,7 @@ links:
## 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

View File

@@ -1,6 +1,6 @@
---
title: Empty
description: Use the Empty component to display a empty state.
description: Use the Empty component to display an empty state.
component: true
---
@@ -70,7 +70,7 @@ import {
### Outline
Use the `border` utility class to create a outline empty state.
Use the `border` utility class to create an outline empty state.
<ComponentPreview
name="empty-outline"

View File

@@ -4,3 +4,7 @@ description: Here you can find all the components available in the library. We a
---
<ComponentsList />
---
Can't find what you need? Try the [registry directory](/docs/directory) for community-maintained components.

View 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>
```

View File

@@ -7,7 +7,10 @@ links:
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

View File

@@ -10,7 +10,7 @@ links:
## About
Sonner is built and maintained by [emilkowalski\_](https://twitter.com/emilkowalski_).
Sonner is built and maintained by [emilkowalski](https://twitter.com/emilkowalski).
## Installation

View File

@@ -7,10 +7,7 @@ links:
api: https://www.radix-ui.com/docs/primitives/components/toggle-group#api-reference
---
<ComponentPreview
name="toggle-group-demo"
description="A toggle group with three items."
/>
<ComponentPreview name="toggle-group-spacing" />
## Installation
@@ -66,13 +63,6 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
## Examples
### Default
<ComponentPreview
name="toggle-group-demo"
description="A toggle group with three items."
/>
### Outline
<ComponentPreview
@@ -107,3 +97,42 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
name="toggle-group-disabled"
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` | |

View File

@@ -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.
```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:

View File

@@ -7,109 +7,18 @@ description: Install and configure shadcn/ui for TanStack Start.
### 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.
**Do not add Tailwind yet. We'll install Tailwind v4 in the next step.**
### Add Tailwind
Install `tailwindcss` and its dependencies.
Run the following command to create a new TanStack Start project with shadcn/ui:
```bash
npm install tailwindcss @tailwindcss/postcss postcss
npm create @tanstack/start@latest --tailwind --add-ons shadcn
```
### Create postcss.config.ts
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
### Add Components
You can now start adding components to your project.
```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:
@@ -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}
import { Button } from "@/components/ui/button"
function Home() {
const router = useRouter()
const state = Route.useLoaderData()
function App() {
return (
<div>
<Button>Click me</Button>
@@ -130,3 +36,9 @@ function Home() {
```
</Steps>
If you want to add all `shadcn/ui` components, you can run the following command:
```bash
npx shadcn@latest add --all
```

View File

@@ -103,7 +103,7 @@ You can read more about the registry item schema and file types in the [registry
### Install the shadcn CLI
```bash
npm install shadcn@canary
npm install shadcn@latest
```
### Add a build script

View File

@@ -1,5 +1,5 @@
---
title: Index
title: Add a Registry
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
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:
```json title="registries.json" showLineNumbers
{
"@acme": "https://registry.acme.com/r/{name}.json",
"@example": "https://example.com/r/{name}"
}
```
Once you have submitted your issue, it will be validated and reviewed by the team.
### 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.

View File

@@ -1,21 +1,25 @@
import { dirname } from "path"
import { fileURLToPath } from "url"
import { FlatCompat } from "@eslint/eslintrc"
import { defineConfig, globalIgnores } from "eslint/config"
import nextVitals from "eslint-config-next/core-web-vitals"
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const compat = new FlatCompat({
baseDirectory: __dirname,
})
const eslintConfig = [
...compat.config({
extends: ["next/core-web-vitals", "next/typescript"],
const eslintConfig = defineConfig([
...nextVitals,
globalIgnores([
"node_modules/**",
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
".source/**",
]),
{
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

View File

@@ -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
}

View File

@@ -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(
(layout: Layout) => {

View File

@@ -11,7 +11,7 @@ export function useIsMobile(mobileBreakpoint = 768) {
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < mobileBreakpoint)
return () => mql.removeEventListener("change", onChange)
}, [])
}, [mobileBreakpoint])
return !!isMobile
}

View 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,
}
}

View File

@@ -23,9 +23,31 @@ export async function getAllBlocks(
categories: string[] = []
) {
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) =>
types.includes(block.type) &&
(categories.length === 0 ||

View File

@@ -1,6 +1,6 @@
import { z } from "zod"
import { colors } from "@/registry/registry-colors"
import { colors } from "@/registry/colors"
const colorSchema = z.object({
name: z.string(),

View File

@@ -25,6 +25,10 @@ export const siteConfig = {
href: "/charts/area",
label: "Charts",
},
{
href: "/docs/directory",
label: "Directory",
},
{
href: "/themes",
label: "Themes",

View File

@@ -6,6 +6,7 @@ export const PAGES_NEW = [
"/docs/components/item",
"/docs/components/kbd",
"/docs/components/spinner",
"/docs/components/native-select",
]
export const PAGES_UPDATED = ["/docs/components/button"]

View File

@@ -16,6 +16,7 @@ const eventSchema = z.object({
"copy_chart_data",
"copy_color",
"set_layout",
"search_query",
]),
// declare type AllowedPropertyValues = string | number | boolean | null
properties: z

View File

@@ -1,14 +1,15 @@
import fs from "fs"
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 =
/<ComponentPreview\s+[^>]*name="([^"]+)"[^>]*\/>/g
/<ComponentPreview[\s\S]*?name="([^"]+)"[\s\S]*?\/>/g
return content.replace(componentPreviewRegex, (match, name) => {
try {
const component = Index[name]
const component = Index[style]?.[name]
if (!component?.files) {
return match
}

View File

@@ -6,13 +6,36 @@ import { Project, ScriptKind } from "ts-morph"
import { z } from "zod"
import { Index } from "@/registry/__index__"
import { type Style } from "@/registry/styles"
export function getRegistryComponent(name: string) {
return Index[name]?.component
export function getRegistryComponent(name: string, styleName: Style["name"]) {
return Index[styleName]?.[name]?.component
}
export async function getRegistryItem(name: string) {
const item = Index[name]
export async function getRegistryItems(
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) {
return null

View File

@@ -4,6 +4,7 @@ import { u } from "unist-builder"
import { visit } from "unist-util-visit"
import { Index } from "@/registry/__index__"
import { getActiveStyle } from "@/registry/styles"
interface UnistNode {
type: string
@@ -26,6 +27,8 @@ export interface UnistTree {
export function rehypeComponent() {
return async (tree: UnistTree) => {
const activeStyle = await getActiveStyle()
visit(tree, (node: UnistNode) => {
// src prop overrides both name and fileName.
const { value: srcPath } =
@@ -111,7 +114,7 @@ export function rehypeComponent() {
}
try {
const component = Index[name]
const component = Index[activeStyle.name]?.[name]
const src = component.files[0]?.path
// Read the source file.

View File

@@ -1,7 +1,7 @@
import { docs } from "@/.source"
import { loader } from "fumadocs-core/source"
export const source: ReturnType<typeof loader> = loader({
export const source = loader({
baseUrl: "/docs",
source: docs.toFumadocsSource(),
})

View File

@@ -1,31 +1,5 @@
export const THEMES = [
{
name: "Default",
value: "default",
},
{
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]
import { baseColors } from "@/registry/base-colors"
export const THEMES = baseColors
.filter((theme) => !["slate", "stone", "gray", "zinc"].includes(theme.name))
.sort((a, b) => a.name.localeCompare(b.name))

View File

@@ -11,6 +11,7 @@ import { ComponentPreview } from "@/components/component-preview"
import { ComponentSource } from "@/components/component-source"
import { ComponentsList } from "@/components/components-list"
import { CopyButton } from "@/components/copy-button"
import { DirectoryList } from "@/components/directory-list"
import { getIconForLanguageExtension } from "@/components/icons"
import {
Accordion,
@@ -127,7 +128,6 @@ export const mdxComponents = {
/>
),
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} />
),
hr: ({ ...props }: React.ComponentProps<"hr">) => (
@@ -280,7 +280,7 @@ export const mdxComponents = {
}: React.ComponentProps<"img">) => (
<Image
className={cn("mt-6 rounded-md border", className)}
src={src || ""}
src={(src as string) || ""}
width={Number(width)}
height={Number(height)}
alt={alt || ""}
@@ -344,6 +344,7 @@ export const mdxComponents = {
ComponentSource,
CodeCollapsibleWrapper,
ComponentsList,
DirectoryList,
Link: ({ className, ...props }: React.ComponentProps<typeof Link>) => (
<Link
className={cn("font-medium underline underline-offset-4", className)}

View File

@@ -25,6 +25,9 @@ const nextConfig = {
},
],
},
experimental: {
turbopackFileSystemCacheForDev: true,
},
redirects() {
return [
{
@@ -72,6 +75,11 @@ const nextConfig = {
destination: "/docs/mcp",
permanent: false,
},
{
source: "/directory",
destination: "/docs/directory",
permanent: false,
},
]
},
rewrites() {

View File

@@ -7,8 +7,8 @@
"dev": "next dev --turbopack --port 4000",
"build": "pnpm --filter=shadcn build && next build",
"start": "next start --port 4000",
"lint": "next lint",
"lint:fix": "next lint --fix",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"typecheck": "tsc --noEmit",
"format:write": "prettier --write \"**/*.{ts,tsx,mdx}\" --cache",
"format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache",
@@ -22,10 +22,10 @@
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@faker-js/faker": "^8.2.0",
"@faker-js/faker": "^10.1.0",
"@hookform/resolvers": "^3.10.0",
"@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-aspect-ratio": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.2",
@@ -38,7 +38,7 @@
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-label": "^2.1.1",
"@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-portal": "^1.1.3",
"@radix-ui/react-progress": "^1.1.1",
@@ -51,9 +51,9 @@
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@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-tooltip": "^1.1.7",
"@radix-ui/react-tooltip": "^1.2.8",
"@tabler/icons-react": "^3.31.0",
"@tailwindcss/postcss": "^4.1.11",
"@tanstack/react-form": "^1.20.0",
@@ -67,48 +67,49 @@
"date-fns": "^4.1.0",
"embla-carousel-autoplay": "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-mdx": "11.6.3",
"fumadocs-ui": "15.3.1",
"fumadocs-mdx": "13.0.2",
"fumadocs-ui": "16.0.5",
"input-otp": "^1.4.2",
"jotai": "^2.1.0",
"jotai": "^2.15.0",
"little-date": "^1.0.0",
"lodash": "^4.17.21",
"lucide-react": "0.474.0",
"motion": "^12.12.1",
"next": "15.3.1",
"next": "16.0.7",
"next-themes": "0.4.6",
"nuqs": "^2.7.2",
"postcss": "^8.5.1",
"react": "19.1.0",
"react": "19.2.0",
"react-day-picker": "^9.7.0",
"react-dom": "19.1.0",
"react-dom": "19.2.0",
"react-hook-form": "^7.62.0",
"react-resizable-panels": "^2.1.7",
"react-textarea-autosize": "^8.5.9",
"recharts": "2.15.1",
"rehype-pretty-code": "^0.14.1",
"rimraf": "^6.0.1",
"shadcn": "3.4.1",
"shadcn": "3.5.2",
"shiki": "^1.10.1",
"sonner": "^2.0.0",
"tailwind-merge": "^3.0.1",
"ts-morph": "18.0.0",
"vaul": "1.1.2",
"zod": "^3.24.1"
"zod": "^3.25.76"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@tailwindcss/postcss": "^4",
"@types/lodash": "^4.17.7",
"@types/mdx": "^2.0.13",
"@types/node": "^20",
"@types/react": "19.1.2",
"@types/react-dom": "19.1.2",
"@types/react": "19.2.2",
"@types/react-dom": "19.2.2",
"@typescript-eslint/parser": "^8.31.0",
"eslint": "^9",
"eslint-config-next": "15.3.1",
"eslint-config-next": "16.0.0",
"prettier": "^3.4.2",
"prettier-plugin-tailwindcss": "^0.6.11",
"tailwindcss": "^4.1.11",

View File

@@ -1,10 +1,16 @@
{
"@8bitcn": "https://8bitcn.com/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",
"@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",
"@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",
"@aliimam": "https://aliimam.in/r/{name}.json",
"@animate-ui": "https://animate-ui.com/r/{name}.json",
"@assistant-ui": "https://r.assistant-ui.com/{name}.json",
"@austin-ui": "https://austin-ui.netlify.app/r/{name}.json",
@@ -14,39 +20,89 @@
"@blocks": "https://blocks.so/r/{name}.json",
"@bucharitesh": "https://bucharitesh.in/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",
"@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",
"@elements": "https://tryelements.dev/r/{name}.json",
"@elevenlabs-ui": "https://ui.elevenlabs.io/r/{name}.json",
"@fancy": "https://fancycomponents.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",
"@hooks": "https://shadcn-hooks.vercel.app/r/{name}.json",
"@intentui": "https://intentui.com/r/{name}",
"@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",
"@lens-blocks": "https://lensblocks.com/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-pro": "https://pro.magicui.design/registry/{name}",
"@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",
"@nexus-elements": "https://elements.nexus.availproject.org/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",
"@prosekit": "https://prosekit.dev/r/{name}.json",
"@phucbm": "https://ui.phucbm.com/r/{name}.json",
"@react-bits": "https://reactbits.dev/r/{name}.json",
"@react-market": "https://www.react-market.com/get/{name}.json",
"@retroui": "https://retroui.dev/r/{name}.json",
"@reui": "https://reui.io/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",
"@systaliko-ui": "https://systaliko-ui.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-studio": "https://shadcnstudio.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",
"@skyr": "https://ui-play.skyroc.me/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",
"@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",
"@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"
}

View File

@@ -8,7 +8,7 @@
"files": [
{
"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"
}
]

View File

@@ -9,7 +9,7 @@
"files": [
{
"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"
}
],

File diff suppressed because one or more lines are too long

View File

@@ -8,7 +8,7 @@
"files": [
{
"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"
}
]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More