Compare commits

..

67 Commits

Author SHA1 Message Date
shadcn
f5a0945caf chore: add changelog 2026-01-20 19:09:05 +04:00
shadcn
eecffac153 fix 2026-01-20 14:01:55 +04:00
shadcn
93b1b012f1 fix 2026-01-20 13:55:54 +04:00
shadcn
113be481d5 fix: create page 2026-01-20 13:32:33 +04:00
shadcn
90952b1b0c docs: update 2026-01-20 13:08:50 +04:00
shadcn
c470caca45 Merge branch 'main' into shadcn/base-docs 2026-01-20 12:58:40 +04:00
shadcn
63ad095a34 Merge branch 'main' into shadcn/base-docs 2026-01-20 12:53:09 +04:00
shadcn
d3b3ecde53 fi 2026-01-20 12:52:55 +04:00
shadcn
f337ccef13 fix 2026-01-20 12:42:04 +04:00
shadcn
818c060efc fix 2026-01-20 11:46:12 +04:00
shadcn
528b27a63f feat: add docs for sidebar 2026-01-20 11:35:07 +04:00
shadcn
a7bd5d2d22 fix: tabs 2026-01-20 10:22:08 +04:00
shadcn
62b1b89bcc fix: tabs 2026-01-20 09:09:41 +04:00
shadcn
97371b578e feat: add more components 2026-01-19 22:31:25 +04:00
shadcn
d6878556ba feat: add spinner and switch 2026-01-19 21:34:56 +04:00
shadcn
e7d802fc07 feat: add skeleton, slider and sonner 2026-01-19 21:18:56 +04:00
shadcn
572e5d4532 feat: more components 2026-01-19 18:04:17 +04:00
shadcn
90f8057a24 feat: more components 2026-01-19 17:23:10 +04:00
shadcn
151ae29653 feat: add more components 2026-01-19 17:05:41 +04:00
shadcn
40e5bf5eff feat: add native-select 2026-01-19 16:41:52 +04:00
shadcn
1fd52c41f9 feat: add menubar 2026-01-19 16:23:09 +04:00
shadcn
63c28a1496 feat: add kbd and label 2026-01-19 16:12:19 +04:00
shadcn
6a9d68cc2c feat: add item 2026-01-19 15:56:32 +04:00
shadcn
587c76f46f feat: add input-otp 2026-01-19 15:34:34 +04:00
shadcn
52a4b1d466 feat: add input-group 2026-01-19 13:56:42 +04:00
shadcn
7ecba59894 feat: add input 2026-01-19 12:49:14 +04:00
shadcn
4073811f64 Merge branch 'main' into shadcn/base-docs 2026-01-19 11:41:24 +04:00
shadcn
5f966a282a Merge branch 'main' into shadcn/base-docs
# Conflicts:
#	apps/v4/lib/llm.ts
#	apps/v4/lib/registry.ts
#	apps/v4/public/r/styles/base-lyra/accordion-example.json
#	apps/v4/public/r/styles/base-lyra/accordion.json
#	apps/v4/public/r/styles/base-lyra/alert-dialog-example.json
#	apps/v4/public/r/styles/base-lyra/alert-example.json
#	apps/v4/public/r/styles/base-lyra/avatar-example.json
#	apps/v4/public/r/styles/base-lyra/badge-example.json
#	apps/v4/public/r/styles/base-lyra/breadcrumb.json
#	apps/v4/public/r/styles/base-lyra/button-example.json
#	apps/v4/public/r/styles/base-lyra/button-group-example.json
#	apps/v4/public/r/styles/base-lyra/calendar-example.json
#	apps/v4/public/r/styles/base-lyra/calendar.json
#	apps/v4/public/r/styles/base-lyra/card-example.json
#	apps/v4/public/r/styles/base-lyra/carousel.json
#	apps/v4/public/r/styles/base-lyra/chart-example.json
#	apps/v4/public/r/styles/base-lyra/chatgpt.json
#	apps/v4/public/r/styles/base-lyra/checkbox.json
#	apps/v4/public/r/styles/base-lyra/collapsible-example.json
#	apps/v4/public/r/styles/base-lyra/combobox-example.json
#	apps/v4/public/r/styles/base-lyra/combobox.json
#	apps/v4/public/r/styles/base-lyra/command-example.json
#	apps/v4/public/r/styles/base-lyra/command.json
#	apps/v4/public/r/styles/base-lyra/component-example.json
#	apps/v4/public/r/styles/base-lyra/context-menu-example.json
#	apps/v4/public/r/styles/base-lyra/context-menu.json
#	apps/v4/public/r/styles/base-lyra/dialog-example.json
#	apps/v4/public/r/styles/base-lyra/dialog.json
#	apps/v4/public/r/styles/base-lyra/dropdown-menu-example.json
#	apps/v4/public/r/styles/base-lyra/dropdown-menu.json
#	apps/v4/public/r/styles/base-lyra/empty-example.json
#	apps/v4/public/r/styles/base-lyra/github.json
#	apps/v4/public/r/styles/base-lyra/input-group-example.json
#	apps/v4/public/r/styles/base-lyra/input-otp-example.json
#	apps/v4/public/r/styles/base-lyra/input-otp.json
#	apps/v4/public/r/styles/base-lyra/item-example.json
#	apps/v4/public/r/styles/base-lyra/kbd-example.json
#	apps/v4/public/r/styles/base-lyra/menubar-example.json
#	apps/v4/public/r/styles/base-lyra/menubar.json
#	apps/v4/public/r/styles/base-lyra/native-select.json
#	apps/v4/public/r/styles/base-lyra/navigation-menu-example.json
#	apps/v4/public/r/styles/base-lyra/navigation-menu.json
#	apps/v4/public/r/styles/base-lyra/pagination.json
#	apps/v4/public/r/styles/base-lyra/preview.json
#	apps/v4/public/r/styles/base-lyra/progress-example.json
#	apps/v4/public/r/styles/base-lyra/radio-group.json
#	apps/v4/public/r/styles/base-lyra/select-example.json
#	apps/v4/public/r/styles/base-lyra/select.json
#	apps/v4/public/r/styles/base-lyra/sheet.json
#	apps/v4/public/r/styles/base-lyra/sidebar-example.json
#	apps/v4/public/r/styles/base-lyra/sidebar-floating-example.json
#	apps/v4/public/r/styles/base-lyra/sidebar-icon-example.json
#	apps/v4/public/r/styles/base-lyra/sidebar-inset-example.json
#	apps/v4/public/r/styles/base-lyra/sidebar.json
#	apps/v4/public/r/styles/base-lyra/spinner-example.json
#	apps/v4/public/r/styles/base-lyra/spinner.json
#	apps/v4/public/r/styles/base-lyra/table-example.json
#	apps/v4/public/r/styles/base-lyra/tabs-example.json
#	apps/v4/public/r/styles/base-lyra/toggle-example.json
#	apps/v4/public/r/styles/base-lyra/toggle-group-example.json
#	apps/v4/public/r/styles/base-lyra/tooltip-example.json
#	apps/v4/public/r/styles/base-lyra/vercel.json
#	apps/v4/public/r/styles/base-maia/accordion-example.json
#	apps/v4/public/r/styles/base-maia/accordion.json
#	apps/v4/public/r/styles/base-maia/alert-dialog-example.json
#	apps/v4/public/r/styles/base-maia/alert-example.json
#	apps/v4/public/r/styles/base-maia/avatar-example.json
#	apps/v4/public/r/styles/base-maia/badge-example.json
#	apps/v4/public/r/styles/base-maia/breadcrumb.json
#	apps/v4/public/r/styles/base-maia/button-example.json
#	apps/v4/public/r/styles/base-maia/button-group-example.json
#	apps/v4/public/r/styles/base-maia/calendar-example.json
#	apps/v4/public/r/styles/base-maia/calendar.json
#	apps/v4/public/r/styles/base-maia/card-example.json
#	apps/v4/public/r/styles/base-maia/carousel.json
#	apps/v4/public/r/styles/base-maia/chart-example.json
#	apps/v4/public/r/styles/base-maia/chatgpt.json
#	apps/v4/public/r/styles/base-maia/checkbox.json
#	apps/v4/public/r/styles/base-maia/collapsible-example.json
#	apps/v4/public/r/styles/base-maia/combobox-example.json
#	apps/v4/public/r/styles/base-maia/combobox.json
#	apps/v4/public/r/styles/base-maia/command-example.json
#	apps/v4/public/r/styles/base-maia/command.json
#	apps/v4/public/r/styles/base-maia/component-example.json
#	apps/v4/public/r/styles/base-maia/context-menu-example.json
#	apps/v4/public/r/styles/base-maia/context-menu.json
#	apps/v4/public/r/styles/base-maia/dialog-example.json
#	apps/v4/public/r/styles/base-maia/dialog.json
#	apps/v4/public/r/styles/base-maia/dropdown-menu-example.json
#	apps/v4/public/r/styles/base-maia/dropdown-menu.json
#	apps/v4/public/r/styles/base-maia/empty-example.json
#	apps/v4/public/r/styles/base-maia/github.json
#	apps/v4/public/r/styles/base-maia/input-group-example.json
#	apps/v4/public/r/styles/base-maia/input-otp-example.json
#	apps/v4/public/r/styles/base-maia/input-otp.json
#	apps/v4/public/r/styles/base-maia/item-example.json
#	apps/v4/public/r/styles/base-maia/kbd-example.json
#	apps/v4/public/r/styles/base-maia/menubar-example.json
#	apps/v4/public/r/styles/base-maia/menubar.json
#	apps/v4/public/r/styles/base-maia/native-select.json
#	apps/v4/public/r/styles/base-maia/navigation-menu-example.json
#	apps/v4/public/r/styles/base-maia/navigation-menu.json
#	apps/v4/public/r/styles/base-maia/pagination.json
#	apps/v4/public/r/styles/base-maia/preview.json
#	apps/v4/public/r/styles/base-maia/progress-example.json
#	apps/v4/public/r/styles/base-maia/radio-group.json
#	apps/v4/public/r/styles/base-maia/select-example.json
#	apps/v4/public/r/styles/base-maia/select.json
#	apps/v4/public/r/styles/base-maia/sheet.json
#	apps/v4/public/r/styles/base-maia/sidebar-example.json
#	apps/v4/public/r/styles/base-maia/sidebar-floating-example.json
#	apps/v4/public/r/styles/base-maia/sidebar-icon-example.json
#	apps/v4/public/r/styles/base-maia/sidebar-inset-example.json
#	apps/v4/public/r/styles/base-maia/sidebar.json
#	apps/v4/public/r/styles/base-maia/spinner-example.json
#	apps/v4/public/r/styles/base-maia/spinner.json
#	apps/v4/public/r/styles/base-maia/table-example.json
#	apps/v4/public/r/styles/base-maia/tabs-example.json
#	apps/v4/public/r/styles/base-maia/toggle-example.json
#	apps/v4/public/r/styles/base-maia/toggle-group-example.json
#	apps/v4/public/r/styles/base-maia/tooltip-example.json
#	apps/v4/public/r/styles/base-maia/vercel.json
#	apps/v4/public/r/styles/base-mira/accordion-example.json
#	apps/v4/public/r/styles/base-mira/accordion.json
#	apps/v4/public/r/styles/base-mira/alert-dialog-example.json
#	apps/v4/public/r/styles/base-mira/alert-example.json
#	apps/v4/public/r/styles/base-mira/avatar-example.json
#	apps/v4/public/r/styles/base-mira/badge-example.json
#	apps/v4/public/r/styles/base-mira/breadcrumb.json
#	apps/v4/public/r/styles/base-mira/button-example.json
#	apps/v4/public/r/styles/base-mira/button-group-example.json
#	apps/v4/public/r/styles/base-mira/calendar-example.json
#	apps/v4/public/r/styles/base-mira/calendar.json
#	apps/v4/public/r/styles/base-mira/card-example.json
#	apps/v4/public/r/styles/base-mira/carousel.json
#	apps/v4/public/r/styles/base-mira/chart-example.json
#	apps/v4/public/r/styles/base-mira/chatgpt.json
#	apps/v4/public/r/styles/base-mira/checkbox.json
#	apps/v4/public/r/styles/base-mira/collapsible-example.json
#	apps/v4/public/r/styles/base-mira/combobox-example.json
#	apps/v4/public/r/styles/base-mira/combobox.json
#	apps/v4/public/r/styles/base-mira/command-example.json
#	apps/v4/public/r/styles/base-mira/command.json
#	apps/v4/public/r/styles/base-mira/component-example.json
#	apps/v4/public/r/styles/base-mira/context-menu-example.json
#	apps/v4/public/r/styles/base-mira/context-menu.json
#	apps/v4/public/r/styles/base-mira/dialog-example.json
#	apps/v4/public/r/styles/base-mira/dialog.json
#	apps/v4/public/r/styles/base-mira/dropdown-menu-example.json
#	apps/v4/public/r/styles/base-mira/dropdown-menu.json
#	apps/v4/public/r/styles/base-mira/empty-example.json
#	apps/v4/public/r/styles/base-mira/github.json
#	apps/v4/public/r/styles/base-mira/input-group-example.json
#	apps/v4/public/r/styles/base-mira/input-otp-example.json
#	apps/v4/public/r/styles/base-mira/input-otp.json
#	apps/v4/public/r/styles/base-mira/item-example.json
#	apps/v4/public/r/styles/base-mira/kbd-example.json
#	apps/v4/public/r/styles/base-mira/menubar-example.json
#	apps/v4/public/r/styles/base-mira/menubar.json
#	apps/v4/public/r/styles/base-mira/native-select.json
#	apps/v4/public/r/styles/base-mira/navigation-menu-example.json
#	apps/v4/public/r/styles/base-mira/navigation-menu.json
#	apps/v4/public/r/styles/base-mira/pagination.json
#	apps/v4/public/r/styles/base-mira/preview.json
#	apps/v4/public/r/styles/base-mira/progress-example.json
#	apps/v4/public/r/styles/base-mira/radio-group.json
#	apps/v4/public/r/styles/base-mira/select-example.json
#	apps/v4/public/r/styles/base-mira/select.json
#	apps/v4/public/r/styles/base-mira/sheet.json
#	apps/v4/public/r/styles/base-mira/sidebar-example.json
#	apps/v4/public/r/styles/base-mira/sidebar-floating-example.json
#	apps/v4/public/r/styles/base-mira/sidebar-icon-example.json
#	apps/v4/public/r/styles/base-mira/sidebar-inset-example.json
#	apps/v4/public/r/styles/base-mira/sidebar.json
#	apps/v4/public/r/styles/base-mira/spinner-example.json
#	apps/v4/public/r/styles/base-mira/spinner.json
#	apps/v4/public/r/styles/base-mira/table-example.json
#	apps/v4/public/r/styles/base-mira/tabs-example.json
#	apps/v4/public/r/styles/base-mira/toggle-example.json
#	apps/v4/public/r/styles/base-mira/toggle-group-example.json
#	apps/v4/public/r/styles/base-mira/tooltip-example.json
#	apps/v4/public/r/styles/base-mira/vercel.json
#	apps/v4/public/r/styles/base-nova/accordion-example.json
#	apps/v4/public/r/styles/base-nova/accordion.json
#	apps/v4/public/r/styles/base-nova/alert-dialog-example.json
#	apps/v4/public/r/styles/base-nova/alert-example.json
#	apps/v4/public/r/styles/base-nova/avatar-example.json
#	apps/v4/public/r/styles/base-nova/badge-example.json
#	apps/v4/public/r/styles/base-nova/breadcrumb.json
#	apps/v4/public/r/styles/base-nova/button-example.json
#	apps/v4/public/r/styles/base-nova/button-group-example.json
#	apps/v4/public/r/styles/base-nova/calendar-example.json
#	apps/v4/public/r/styles/base-nova/calendar.json
#	apps/v4/public/r/styles/base-nova/card-example.json
#	apps/v4/public/r/styles/base-nova/carousel.json
#	apps/v4/public/r/styles/base-nova/chart-example.json
#	apps/v4/public/r/styles/base-nova/chatgpt.json
#	apps/v4/public/r/styles/base-nova/checkbox.json
#	apps/v4/public/r/styles/base-nova/collapsible-example.json
#	apps/v4/public/r/styles/base-nova/combobox-example.json
#	apps/v4/public/r/styles/base-nova/combobox.json
#	apps/v4/public/r/styles/base-nova/command-example.json
#	apps/v4/public/r/styles/base-nova/command.json
#	apps/v4/public/r/styles/base-nova/component-example.json
#	apps/v4/public/r/styles/base-nova/context-menu-example.json
#	apps/v4/public/r/styles/base-nova/context-menu.json
#	apps/v4/public/r/styles/base-nova/dialog-example.json
#	apps/v4/public/r/styles/base-nova/dialog.json
#	apps/v4/public/r/styles/base-nova/dropdown-menu-example.json
#	apps/v4/public/r/styles/base-nova/dropdown-menu.json
#	apps/v4/public/r/styles/base-nova/empty-example.json
#	apps/v4/public/r/styles/base-nova/github.json
#	apps/v4/public/r/styles/base-nova/input-group-example.json
#	apps/v4/public/r/styles/base-nova/input-otp-example.json
#	apps/v4/public/r/styles/base-nova/input-otp.json
#	apps/v4/public/r/styles/base-nova/item-example.json
#	apps/v4/public/r/styles/base-nova/kbd-example.json
#	apps/v4/public/r/styles/base-nova/menubar-example.json
#	apps/v4/public/r/styles/base-nova/menubar.json
#	apps/v4/public/r/styles/base-nova/native-select.json
#	apps/v4/public/r/styles/base-nova/navigation-menu-example.json
#	apps/v4/public/r/styles/base-nova/navigation-menu.json
#	apps/v4/public/r/styles/base-nova/pagination.json
#	apps/v4/public/r/styles/base-nova/preview.json
#	apps/v4/public/r/styles/base-nova/progress-example.json
#	apps/v4/public/r/styles/base-nova/radio-group.json
#	apps/v4/public/r/styles/base-nova/select-example.json
#	apps/v4/public/r/styles/base-nova/select.json
#	apps/v4/public/r/styles/base-nova/sheet.json
#	apps/v4/public/r/styles/base-nova/sidebar-example.json
#	apps/v4/public/r/styles/base-nova/sidebar-floating-example.json
#	apps/v4/public/r/styles/base-nova/sidebar-icon-example.json
#	apps/v4/public/r/styles/base-nova/sidebar-inset-example.json
#	apps/v4/public/r/styles/base-nova/sidebar.json
#	apps/v4/public/r/styles/base-nova/spinner-example.json
#	apps/v4/public/r/styles/base-nova/spinner.json
#	apps/v4/public/r/styles/base-nova/table-example.json
#	apps/v4/public/r/styles/base-nova/tabs-example.json
#	apps/v4/public/r/styles/base-nova/toggle-example.json
#	apps/v4/public/r/styles/base-nova/toggle-group-example.json
#	apps/v4/public/r/styles/base-nova/tooltip-example.json
#	apps/v4/public/r/styles/base-nova/vercel.json
#	apps/v4/public/r/styles/base-vega/accordion-example.json
#	apps/v4/public/r/styles/base-vega/accordion.json
#	apps/v4/public/r/styles/base-vega/alert-dialog-example.json
#	apps/v4/public/r/styles/base-vega/alert-example.json
#	apps/v4/public/r/styles/base-vega/avatar-example.json
#	apps/v4/public/r/styles/base-vega/badge-example.json
#	apps/v4/public/r/styles/base-vega/breadcrumb.json
#	apps/v4/public/r/styles/base-vega/button-example.json
#	apps/v4/public/r/styles/base-vega/button-group-example.json
#	apps/v4/public/r/styles/base-vega/calendar-example.json
#	apps/v4/public/r/styles/base-vega/calendar.json
#	apps/v4/public/r/styles/base-vega/card-example.json
#	apps/v4/public/r/styles/base-vega/carousel.json
#	apps/v4/public/r/styles/base-vega/chart-example.json
#	apps/v4/public/r/styles/base-vega/chatgpt.json
#	apps/v4/public/r/styles/base-vega/checkbox.json
#	apps/v4/public/r/styles/base-vega/collapsible-example.json
#	apps/v4/public/r/styles/base-vega/combobox-example.json
#	apps/v4/public/r/styles/base-vega/combobox.json
#	apps/v4/public/r/styles/base-vega/command-example.json
#	apps/v4/public/r/styles/base-vega/command.json
#	apps/v4/public/r/styles/base-vega/component-example.json
#	apps/v4/public/r/styles/base-vega/context-menu-example.json
#	apps/v4/public/r/styles/base-vega/context-menu.json
#	apps/v4/public/r/styles/base-vega/dialog-example.json
#	apps/v4/public/r/styles/base-vega/dialog.json
#	apps/v4/public/r/styles/base-vega/dropdown-menu-example.json
#	apps/v4/public/r/styles/base-vega/dropdown-menu.json
#	apps/v4/public/r/styles/base-vega/empty-example.json
#	apps/v4/public/r/styles/base-vega/github.json
#	apps/v4/public/r/styles/base-vega/input-group-example.json
#	apps/v4/public/r/styles/base-vega/input-otp-example.json
#	apps/v4/public/r/styles/base-vega/input-otp.json
#	apps/v4/public/r/styles/base-vega/item-example.json
#	apps/v4/public/r/styles/base-vega/kbd-example.json
#	apps/v4/public/r/styles/base-vega/menubar-example.json
#	apps/v4/public/r/styles/base-vega/menubar.json
#	apps/v4/public/r/styles/base-vega/native-select.json
#	apps/v4/public/r/styles/base-vega/navigation-menu-example.json
#	apps/v4/public/r/styles/base-vega/navigation-menu.json
#	apps/v4/public/r/styles/base-vega/pagination.json
#	apps/v4/public/r/styles/base-vega/preview.json
#	apps/v4/public/r/styles/base-vega/progress-example.json
#	apps/v4/public/r/styles/base-vega/radio-group.json
#	apps/v4/public/r/styles/base-vega/select-example.json
#	apps/v4/public/r/styles/base-vega/select.json
#	apps/v4/public/r/styles/base-vega/sheet.json
#	apps/v4/public/r/styles/base-vega/sidebar-example.json
#	apps/v4/public/r/styles/base-vega/sidebar-floating-example.json
#	apps/v4/public/r/styles/base-vega/sidebar-icon-example.json
#	apps/v4/public/r/styles/base-vega/sidebar-inset-example.json
#	apps/v4/public/r/styles/base-vega/sidebar.json
#	apps/v4/public/r/styles/base-vega/spinner-example.json
#	apps/v4/public/r/styles/base-vega/spinner.json
#	apps/v4/public/r/styles/base-vega/table-example.json
#	apps/v4/public/r/styles/base-vega/tabs-example.json
#	apps/v4/public/r/styles/base-vega/toggle-example.json
#	apps/v4/public/r/styles/base-vega/toggle-group-example.json
#	apps/v4/public/r/styles/base-vega/tooltip-example.json
#	apps/v4/public/r/styles/base-vega/vercel.json
#	apps/v4/public/r/styles/radix-lyra/accordion-example.json
#	apps/v4/public/r/styles/radix-lyra/accordion.json
#	apps/v4/public/r/styles/radix-lyra/alert-dialog-example.json
#	apps/v4/public/r/styles/radix-lyra/alert-example.json
#	apps/v4/public/r/styles/radix-lyra/avatar-example.json
#	apps/v4/public/r/styles/radix-lyra/badge-example.json
#	apps/v4/public/r/styles/radix-lyra/breadcrumb.json
#	apps/v4/public/r/styles/radix-lyra/button-example.json
#	apps/v4/public/r/styles/radix-lyra/button-group-example.json
#	apps/v4/public/r/styles/radix-lyra/calendar-example.json
#	apps/v4/public/r/styles/radix-lyra/calendar.json
#	apps/v4/public/r/styles/radix-lyra/card-example.json
#	apps/v4/public/r/styles/radix-lyra/carousel.json
#	apps/v4/public/r/styles/radix-lyra/chart-example.json
#	apps/v4/public/r/styles/radix-lyra/chatgpt.json
#	apps/v4/public/r/styles/radix-lyra/checkbox.json
#	apps/v4/public/r/styles/radix-lyra/collapsible-example.json
#	apps/v4/public/r/styles/radix-lyra/combobox-example.json
#	apps/v4/public/r/styles/radix-lyra/combobox.json
#	apps/v4/public/r/styles/radix-lyra/command-example.json
#	apps/v4/public/r/styles/radix-lyra/command.json
#	apps/v4/public/r/styles/radix-lyra/component-example.json
#	apps/v4/public/r/styles/radix-lyra/context-menu-example.json
#	apps/v4/public/r/styles/radix-lyra/context-menu.json
#	apps/v4/public/r/styles/radix-lyra/dialog-example.json
#	apps/v4/public/r/styles/radix-lyra/dialog.json
#	apps/v4/public/r/styles/radix-lyra/dropdown-menu-example.json
#	apps/v4/public/r/styles/radix-lyra/dropdown-menu.json
#	apps/v4/public/r/styles/radix-lyra/empty-example.json
#	apps/v4/public/r/styles/radix-lyra/github.json
#	apps/v4/public/r/styles/radix-lyra/input-group-example.json
#	apps/v4/public/r/styles/radix-lyra/input-otp-example.json
#	apps/v4/public/r/styles/radix-lyra/input-otp.json
#	apps/v4/public/r/styles/radix-lyra/item-example.json
#	apps/v4/public/r/styles/radix-lyra/kbd-example.json
#	apps/v4/public/r/styles/radix-lyra/menubar-example.json
#	apps/v4/public/r/styles/radix-lyra/menubar.json
#	apps/v4/public/r/styles/radix-lyra/native-select.json
#	apps/v4/public/r/styles/radix-lyra/navigation-menu-example.json
#	apps/v4/public/r/styles/radix-lyra/navigation-menu.json
#	apps/v4/public/r/styles/radix-lyra/pagination.json
#	apps/v4/public/r/styles/radix-lyra/preview.json
#	apps/v4/public/r/styles/radix-lyra/progress-example.json
#	apps/v4/public/r/styles/radix-lyra/radio-group.json
#	apps/v4/public/r/styles/radix-lyra/select-example.json
#	apps/v4/public/r/styles/radix-lyra/select.json
#	apps/v4/public/r/styles/radix-lyra/sheet.json
#	apps/v4/public/r/styles/radix-lyra/sidebar-example.json
#	apps/v4/public/r/styles/radix-lyra/sidebar-floating-example.json
#	apps/v4/public/r/styles/radix-lyra/sidebar-icon-example.json
#	apps/v4/public/r/styles/radix-lyra/sidebar-inset-example.json
#	apps/v4/public/r/styles/radix-lyra/sidebar.json
#	apps/v4/public/r/styles/radix-lyra/spinner-example.json
#	apps/v4/public/r/styles/radix-lyra/spinner.json
#	apps/v4/public/r/styles/radix-lyra/table-example.json
#	apps/v4/public/r/styles/radix-lyra/tabs-example.json
#	apps/v4/public/r/styles/radix-lyra/toggle-example.json
#	apps/v4/public/r/styles/radix-lyra/toggle-group-example.json
#	apps/v4/public/r/styles/radix-lyra/tooltip-example.json
#	apps/v4/public/r/styles/radix-lyra/vercel.json
#	apps/v4/public/r/styles/radix-maia/accordion-example.json
#	apps/v4/public/r/styles/radix-maia/accordion.json
#	apps/v4/public/r/styles/radix-maia/alert-dialog-example.json
#	apps/v4/public/r/styles/radix-maia/alert-example.json
#	apps/v4/public/r/styles/radix-maia/avatar-example.json
#	apps/v4/public/r/styles/radix-maia/badge-example.json
#	apps/v4/public/r/styles/radix-maia/breadcrumb.json
#	apps/v4/public/r/styles/radix-maia/button-example.json
#	apps/v4/public/r/styles/radix-maia/button-group-example.json
#	apps/v4/public/r/styles/radix-maia/calendar-example.json
#	apps/v4/public/r/styles/radix-maia/calendar.json
#	apps/v4/public/r/styles/radix-maia/card-example.json
#	apps/v4/public/r/styles/radix-maia/carousel.json
#	apps/v4/public/r/styles/radix-maia/chart-example.json
#	apps/v4/public/r/styles/radix-maia/chatgpt.json
#	apps/v4/public/r/styles/radix-maia/checkbox.json
#	apps/v4/public/r/styles/radix-maia/collapsible-example.json
#	apps/v4/public/r/styles/radix-maia/combobox-example.json
#	apps/v4/public/r/styles/radix-maia/combobox.json
#	apps/v4/public/r/styles/radix-maia/command-example.json
#	apps/v4/public/r/styles/radix-maia/command.json
#	apps/v4/public/r/styles/radix-maia/component-example.json
#	apps/v4/public/r/styles/radix-maia/context-menu-example.json
#	apps/v4/public/r/styles/radix-maia/context-menu.json
#	apps/v4/public/r/styles/radix-maia/dialog-example.json
#	apps/v4/public/r/styles/radix-maia/dialog.json
#	apps/v4/public/r/styles/radix-maia/dropdown-menu-example.json
#	apps/v4/public/r/styles/radix-maia/dropdown-menu.json
#	apps/v4/public/r/styles/radix-maia/empty-example.json
#	apps/v4/public/r/styles/radix-maia/github.json
#	apps/v4/public/r/styles/radix-maia/input-group-example.json
#	apps/v4/public/r/styles/radix-maia/input-otp-example.json
#	apps/v4/public/r/styles/radix-maia/input-otp.json
#	apps/v4/public/r/styles/radix-maia/item-example.json
#	apps/v4/public/r/styles/radix-maia/kbd-example.json
#	apps/v4/public/r/styles/radix-maia/menubar-example.json
#	apps/v4/public/r/styles/radix-maia/menubar.json
#	apps/v4/public/r/styles/radix-maia/native-select.json
#	apps/v4/public/r/styles/radix-maia/navigation-menu-example.json
#	apps/v4/public/r/styles/radix-maia/navigation-menu.json
#	apps/v4/public/r/styles/radix-maia/pagination.json
#	apps/v4/public/r/styles/radix-maia/preview.json
#	apps/v4/public/r/styles/radix-maia/progress-example.json
#	apps/v4/public/r/styles/radix-maia/radio-group.json
#	apps/v4/public/r/styles/radix-maia/select-example.json
#	apps/v4/public/r/styles/radix-maia/select.json
#	apps/v4/public/r/styles/radix-maia/sheet.json
#	apps/v4/public/r/styles/radix-maia/sidebar-example.json
#	apps/v4/public/r/styles/radix-maia/sidebar-floating-example.json
#	apps/v4/public/r/styles/radix-maia/sidebar-icon-example.json
#	apps/v4/public/r/styles/radix-maia/sidebar-inset-example.json
#	apps/v4/public/r/styles/radix-maia/sidebar.json
#	apps/v4/public/r/styles/radix-maia/spinner-example.json
#	apps/v4/public/r/styles/radix-maia/spinner.json
#	apps/v4/public/r/styles/radix-maia/table-example.json
#	apps/v4/public/r/styles/radix-maia/tabs-example.json
#	apps/v4/public/r/styles/radix-maia/toggle-example.json
#	apps/v4/public/r/styles/radix-maia/toggle-group-example.json
#	apps/v4/public/r/styles/radix-maia/tooltip-example.json
#	apps/v4/public/r/styles/radix-maia/vercel.json
#	apps/v4/public/r/styles/radix-mira/accordion-example.json
#	apps/v4/public/r/styles/radix-mira/accordion.json
#	apps/v4/public/r/styles/radix-mira/alert-dialog-example.json
#	apps/v4/public/r/styles/radix-mira/alert-example.json
#	apps/v4/public/r/styles/radix-mira/avatar-example.json
#	apps/v4/public/r/styles/radix-mira/badge-example.json
#	apps/v4/public/r/styles/radix-mira/breadcrumb.json
#	apps/v4/public/r/styles/radix-mira/button-example.json
#	apps/v4/public/r/styles/radix-mira/button-group-example.json
#	apps/v4/public/r/styles/radix-mira/calendar-example.json
#	apps/v4/public/r/styles/radix-mira/calendar.json
#	apps/v4/public/r/styles/radix-mira/card-example.json
#	apps/v4/public/r/styles/radix-mira/carousel.json
#	apps/v4/public/r/styles/radix-mira/chart-example.json
#	apps/v4/public/r/styles/radix-mira/chatgpt.json
#	apps/v4/public/r/styles/radix-mira/checkbox.json
#	apps/v4/public/r/styles/radix-mira/collapsible-example.json
#	apps/v4/public/r/styles/radix-mira/combobox-example.json
#	apps/v4/public/r/styles/radix-mira/combobox.json
#	apps/v4/public/r/styles/radix-mira/command-example.json
#	apps/v4/public/r/styles/radix-mira/command.json
#	apps/v4/public/r/styles/radix-mira/component-example.json
#	apps/v4/public/r/styles/radix-mira/context-menu-example.json
#	apps/v4/public/r/styles/radix-mira/context-menu.json
#	apps/v4/public/r/styles/radix-mira/dialog-example.json
#	apps/v4/public/r/styles/radix-mira/dialog.json
#	apps/v4/public/r/styles/radix-mira/dropdown-menu-example.json
#	apps/v4/public/r/styles/radix-mira/dropdown-menu.json
#	apps/v4/public/r/styles/radix-mira/empty-example.json
#	apps/v4/public/r/styles/radix-mira/github.json
#	apps/v4/public/r/styles/radix-mira/input-group-example.json
#	apps/v4/public/r/styles/radix-mira/input-otp-example.json
#	apps/v4/public/r/styles/radix-mira/input-otp.json
#	apps/v4/public/r/styles/radix-mira/item-example.json
#	apps/v4/public/r/styles/radix-mira/kbd-example.json
#	apps/v4/public/r/styles/radix-mira/menubar-example.json
#	apps/v4/public/r/styles/radix-mira/menubar.json
#	apps/v4/public/r/styles/radix-mira/native-select.json
#	apps/v4/public/r/styles/radix-mira/navigation-menu-example.json
#	apps/v4/public/r/styles/radix-mira/navigation-menu.json
#	apps/v4/public/r/styles/radix-mira/pagination.json
#	apps/v4/public/r/styles/radix-mira/preview.json
#	apps/v4/public/r/styles/radix-mira/progress-example.json
#	apps/v4/public/r/styles/radix-mira/radio-group.json
#	apps/v4/public/r/styles/radix-mira/select-example.json
#	apps/v4/public/r/styles/radix-mira/select.json
#	apps/v4/public/r/styles/radix-mira/sheet.json
#	apps/v4/public/r/styles/radix-mira/sidebar-example.json
#	apps/v4/public/r/styles/radix-mira/sidebar-floating-example.json
#	apps/v4/public/r/styles/radix-mira/sidebar-icon-example.json
#	apps/v4/public/r/styles/radix-mira/sidebar-inset-example.json
#	apps/v4/public/r/styles/radix-mira/sidebar.json
#	apps/v4/public/r/styles/radix-mira/spinner-example.json
#	apps/v4/public/r/styles/radix-mira/spinner.json
#	apps/v4/public/r/styles/radix-mira/table-example.json
#	apps/v4/public/r/styles/radix-mira/tabs-example.json
#	apps/v4/public/r/styles/radix-mira/toggle-example.json
#	apps/v4/public/r/styles/radix-mira/toggle-group-example.json
#	apps/v4/public/r/styles/radix-mira/tooltip-example.json
#	apps/v4/public/r/styles/radix-mira/vercel.json
#	apps/v4/public/r/styles/radix-nova/accordion-example.json
#	apps/v4/public/r/styles/radix-nova/accordion.json
#	apps/v4/public/r/styles/radix-nova/alert-dialog-example.json
#	apps/v4/public/r/styles/radix-nova/alert-example.json
#	apps/v4/public/r/styles/radix-nova/avatar-example.json
#	apps/v4/public/r/styles/radix-nova/badge-example.json
#	apps/v4/public/r/styles/radix-nova/breadcrumb.json
#	apps/v4/public/r/styles/radix-nova/button-example.json
#	apps/v4/public/r/styles/radix-nova/button-group-example.json
#	apps/v4/public/r/styles/radix-nova/calendar-example.json
#	apps/v4/public/r/styles/radix-nova/calendar.json
#	apps/v4/public/r/styles/radix-nova/card-example.json
#	apps/v4/public/r/styles/radix-nova/carousel.json
#	apps/v4/public/r/styles/radix-nova/chart-example.json
#	apps/v4/public/r/styles/radix-nova/chatgpt.json
#	apps/v4/public/r/styles/radix-nova/checkbox.json
#	apps/v4/public/r/styles/radix-nova/collapsible-example.json
#	apps/v4/public/r/styles/radix-nova/combobox-example.json
#	apps/v4/public/r/styles/radix-nova/combobox.json
#	apps/v4/public/r/styles/radix-nova/command-example.json
#	apps/v4/public/r/styles/radix-nova/command.json
#	apps/v4/public/r/styles/radix-nova/component-example.json
#	apps/v4/public/r/styles/radix-nova/context-menu-example.json
#	apps/v4/public/r/styles/radix-nova/context-menu.json
#	apps/v4/public/r/styles/radix-nova/dialog-example.json
#	apps/v4/public/r/styles/radix-nova/dialog.json
#	apps/v4/public/r/styles/radix-nova/dropdown-menu-example.json
#	apps/v4/public/r/styles/radix-nova/dropdown-menu.json
#	apps/v4/public/r/styles/radix-nova/empty-example.json
#	apps/v4/public/r/styles/radix-nova/github.json
#	apps/v4/public/r/styles/radix-nova/input-group-example.json
#	apps/v4/public/r/styles/radix-nova/input-otp-example.json
#	apps/v4/public/r/styles/radix-nova/input-otp.json
#	apps/v4/public/r/styles/radix-nova/item-example.json
#	apps/v4/public/r/styles/radix-nova/kbd-example.json
#	apps/v4/public/r/styles/radix-nova/menubar-example.json
#	apps/v4/public/r/styles/radix-nova/menubar.json
#	apps/v4/public/r/styles/radix-nova/native-select.json
#	apps/v4/public/r/styles/radix-nova/navigation-menu-example.json
#	apps/v4/public/r/styles/radix-nova/navigation-menu.json
#	apps/v4/public/r/styles/radix-nova/pagination.json
#	apps/v4/public/r/styles/radix-nova/preview.json
#	apps/v4/public/r/styles/radix-nova/progress-example.json
#	apps/v4/public/r/styles/radix-nova/radio-group.json
#	apps/v4/public/r/styles/radix-nova/select-example.json
#	apps/v4/public/r/styles/radix-nova/select.json
#	apps/v4/public/r/styles/radix-nova/sheet.json
#	apps/v4/public/r/styles/radix-nova/sidebar-example.json
#	apps/v4/public/r/styles/radix-nova/sidebar-floating-example.json
#	apps/v4/public/r/styles/radix-nova/sidebar-icon-example.json
#	apps/v4/public/r/styles/radix-nova/sidebar-inset-example.json
#	apps/v4/public/r/styles/radix-nova/sidebar.json
#	apps/v4/public/r/styles/radix-nova/spinner-example.json
#	apps/v4/public/r/styles/radix-nova/spinner.json
#	apps/v4/public/r/styles/radix-nova/table-example.json
#	apps/v4/public/r/styles/radix-nova/tabs-example.json
#	apps/v4/public/r/styles/radix-nova/toggle-example.json
#	apps/v4/public/r/styles/radix-nova/toggle-group-example.json
#	apps/v4/public/r/styles/radix-nova/tooltip-example.json
#	apps/v4/public/r/styles/radix-nova/vercel.json
#	apps/v4/public/r/styles/radix-vega/accordion-example.json
#	apps/v4/public/r/styles/radix-vega/accordion.json
#	apps/v4/public/r/styles/radix-vega/alert-dialog-example.json
#	apps/v4/public/r/styles/radix-vega/alert-example.json
#	apps/v4/public/r/styles/radix-vega/avatar-example.json
#	apps/v4/public/r/styles/radix-vega/badge-example.json
#	apps/v4/public/r/styles/radix-vega/breadcrumb.json
#	apps/v4/public/r/styles/radix-vega/button-example.json
#	apps/v4/public/r/styles/radix-vega/button-group-example.json
#	apps/v4/public/r/styles/radix-vega/calendar-example.json
#	apps/v4/public/r/styles/radix-vega/calendar.json
#	apps/v4/public/r/styles/radix-vega/card-example.json
#	apps/v4/public/r/styles/radix-vega/carousel.json
#	apps/v4/public/r/styles/radix-vega/chart-example.json
#	apps/v4/public/r/styles/radix-vega/chatgpt.json
#	apps/v4/public/r/styles/radix-vega/checkbox.json
#	apps/v4/public/r/styles/radix-vega/collapsible-example.json
#	apps/v4/public/r/styles/radix-vega/combobox-example.json
#	apps/v4/public/r/styles/radix-vega/combobox.json
#	apps/v4/public/r/styles/radix-vega/command-example.json
#	apps/v4/public/r/styles/radix-vega/command.json
#	apps/v4/public/r/styles/radix-vega/component-example.json
#	apps/v4/public/r/styles/radix-vega/context-menu-example.json
#	apps/v4/public/r/styles/radix-vega/context-menu.json
#	apps/v4/public/r/styles/radix-vega/dialog-example.json
#	apps/v4/public/r/styles/radix-vega/dialog.json
#	apps/v4/public/r/styles/radix-vega/dropdown-menu-example.json
#	apps/v4/public/r/styles/radix-vega/dropdown-menu.json
#	apps/v4/public/r/styles/radix-vega/empty-example.json
#	apps/v4/public/r/styles/radix-vega/github.json
#	apps/v4/public/r/styles/radix-vega/input-group-example.json
#	apps/v4/public/r/styles/radix-vega/input-otp-example.json
#	apps/v4/public/r/styles/radix-vega/input-otp.json
#	apps/v4/public/r/styles/radix-vega/item-example.json
#	apps/v4/public/r/styles/radix-vega/kbd-example.json
#	apps/v4/public/r/styles/radix-vega/menubar-example.json
#	apps/v4/public/r/styles/radix-vega/menubar.json
#	apps/v4/public/r/styles/radix-vega/native-select.json
#	apps/v4/public/r/styles/radix-vega/navigation-menu-example.json
#	apps/v4/public/r/styles/radix-vega/navigation-menu.json
#	apps/v4/public/r/styles/radix-vega/pagination.json
#	apps/v4/public/r/styles/radix-vega/preview.json
#	apps/v4/public/r/styles/radix-vega/progress-example.json
#	apps/v4/public/r/styles/radix-vega/radio-group.json
#	apps/v4/public/r/styles/radix-vega/select-example.json
#	apps/v4/public/r/styles/radix-vega/select.json
#	apps/v4/public/r/styles/radix-vega/sheet.json
#	apps/v4/public/r/styles/radix-vega/sidebar-example.json
#	apps/v4/public/r/styles/radix-vega/sidebar-floating-example.json
#	apps/v4/public/r/styles/radix-vega/sidebar-icon-example.json
#	apps/v4/public/r/styles/radix-vega/sidebar-inset-example.json
#	apps/v4/public/r/styles/radix-vega/sidebar.json
#	apps/v4/public/r/styles/radix-vega/spinner-example.json
#	apps/v4/public/r/styles/radix-vega/spinner.json
#	apps/v4/public/r/styles/radix-vega/table-example.json
#	apps/v4/public/r/styles/radix-vega/tabs-example.json
#	apps/v4/public/r/styles/radix-vega/toggle-example.json
#	apps/v4/public/r/styles/radix-vega/toggle-group-example.json
#	apps/v4/public/r/styles/radix-vega/tooltip-example.json
#	apps/v4/public/r/styles/radix-vega/vercel.json
#	apps/v4/scripts/build-registry.mts
2026-01-19 11:39:57 +04:00
shadcn
ff3c1e1d95 fix: input 2026-01-15 19:48:20 +04:00
shadcn
068f7c22aa feat: add field and hover-card 2026-01-15 17:32:16 +04:00
shadcn
b1b25fe15d feat: add empty 2026-01-15 16:51:57 +04:00
shadcn
689b4c6b41 feat: add date-picker 2026-01-15 16:32:35 +04:00
shadcn
66637058fc feat: dropdown-menu 2026-01-15 15:50:35 +04:00
shadcn
da07cf6ffe feat: add data-table dialog and drawer 2026-01-15 13:17:45 +04:00
shadcn
1a5b9ce036 feat: add context menu 2026-01-15 12:57:18 +04:00
shadcn
1ce874edd2 feat: add command 2026-01-15 12:34:24 +04:00
shadcn
45480505d8 feat: add combobox 2026-01-15 12:11:27 +04:00
shadcn
bd4ef8e08c feat: add collapsible 2026-01-15 11:25:35 +04:00
shadcn
5a897b7765 feat: add checkbox 2026-01-15 10:59:58 +04:00
shadcn
3f62e7dee0 fix: chart 2026-01-15 10:26:46 +04:00
shadcn
f2d4395233 feat: add carousel 2026-01-15 09:36:08 +04:00
shadcn
3ab7d04824 feat: add calendar and card 2026-01-15 08:54:40 +04:00
shadcn
58f73f62a0 fix 2026-01-14 22:05:15 +04:00
shadcn
d9061d64aa fix 2026-01-14 22:02:57 +04:00
shadcn
4784f264c5 fix 2026-01-14 21:37:16 +04:00
shadcn
7031141cf3 feat: add button 2026-01-14 21:30:23 +04:00
shadcn
d0fe494491 fix 2026-01-14 20:55:40 +04:00
shadcn
be0b798e21 feat: add breadcrumb 2026-01-14 20:46:23 +04:00
shadcn
953107e7f9 feat: add badge 2026-01-14 20:01:05 +04:00
shadcn
c880796bf2 feat: add avatar 2026-01-14 19:26:01 +04:00
shadcn
48b069c453 feat: add aspect-ratio 2026-01-14 13:19:37 +04:00
shadcn
b5c7a014c8 fix 2026-01-14 12:59:48 +04:00
shadcn
aee10914fe feat: add more examples 2026-01-14 09:25:14 +04:00
shadcn
aadba2f859 Merge branch 'main' into shadcn/base-docs 2026-01-13 17:03:38 +04:00
shadcn
dced7f6045 fix 2026-01-08 22:25:29 +04:00
shadcn
cbe672151a chore: run format:write 2026-01-08 21:35:21 +04:00
shadcn
62aef1117f fix 2026-01-08 21:27:27 +04:00
shadcn
cbc9ed8688 fix 2026-01-08 15:26:02 +04:00
shadcn
b309b1a060 fix 2026-01-08 13:13:10 +04:00
shadcn
e003cf74d2 chore: remove claude files 2026-01-08 12:59:38 +04:00
shadcn
34f1061c6b fix 2026-01-08 12:54:08 +04:00
shadcn
ed28a348c7 fix 2026-01-08 12:05:23 +04:00
shadcn
38bcb3c2eb fix 2026-01-07 22:43:45 +04:00
shadcn
e9acf86c24 fix 2026-01-07 22:05:19 +04:00
shadcn
2f829db41d fix 2026-01-07 14:37:42 +04:00
shadcn
413dc4c01f feat: transform code for display 2026-01-06 16:58:56 +04:00
shadcn
eb098f87d2 feat: add base and radix docs 2026-01-06 16:43:01 +04:00
1925 changed files with 15625 additions and 96176 deletions

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
Fix: skip all transforms for universal registry items

View File

@@ -1,12 +1,12 @@
// ORIGINALLY FROM CLOUDFLARE WRANGLER:
// https://github.com/cloudflare/wrangler2/blob/main/.github/changeset-version.js
import { execSync } from "child_process"
import { exec } from "child_process"
// This script is used by the `release.yml` workflow to update the version of the packages being released.
// The standard step is only to run `changeset version` but this does not update the pnpm-lock.yaml file.
// So we also run `pnpm install`, which does this update.
// The standard step is only to run `changeset version` but this does not update the package-lock.json file.
// So we also run `npm install`, which does this update.
// This is a workaround until this is handled automatically by `changeset version`.
// See https://github.com/changesets/changesets/issues/421.
execSync("npx changeset version", { stdio: "inherit" })
execSync("pnpm install --lockfile-only", { stdio: "inherit" })
exec("npx changeset version")
exec("npm install")

View File

@@ -93,7 +93,7 @@ export function AppearanceSettings() {
value={gpuCount}
onChange={handleGpuInputChange}
size={3}
className="h-7 !w-14 font-mono"
className="h-8 !w-14 font-mono"
maxLength={3}
/>
<Button

View File

@@ -56,7 +56,7 @@ export function ButtonGroupDemo() {
<MoreHorizontalIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuContent align="end" className="w-48 [--radius:1rem]">
<DropdownMenuGroup>
<DropdownMenuItem>
<MailCheckIcon />

View File

@@ -21,7 +21,7 @@ export function ButtonGroupPopover() {
<ChevronDownIcon />
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="gap-0 rounded-xl p-0 text-sm">
<PopoverContent align="end" className="rounded-xl p-0 text-sm">
<div className="px-4 py-3">
<div className="text-sm font-medium">Agent Tasks</div>
</div>

View File

@@ -1,9 +1,4 @@
import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarImage,
} from "@/examples/radix/ui/avatar"
import { Avatar, AvatarFallback, AvatarImage } from "@/examples/radix/ui/avatar"
import { Button } from "@/examples/radix/ui/button"
import {
Empty,
@@ -17,10 +12,10 @@ import { PlusIcon } from "lucide-react"
export function EmptyAvatarGroup() {
return (
<Empty className="flex-none border py-10">
<Empty className="flex-none border">
<EmptyHeader>
<EmptyMedia>
<AvatarGroup className="grayscale">
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
@@ -39,7 +34,7 @@ export function EmptyAvatarGroup() {
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
</div>
</EmptyMedia>
<EmptyTitle>No Team Members</EmptyTitle>
<EmptyDescription>

View File

@@ -13,7 +13,6 @@ import { Input } from "@/examples/radix/ui/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
@@ -22,7 +21,7 @@ import { Textarea } from "@/examples/radix/ui/textarea"
export function FieldDemo() {
return (
<div className="w-full max-w-md rounded-xl border p-6">
<div className="w-full max-w-md rounded-lg border p-6">
<form>
<FieldGroup>
<FieldSet>
@@ -70,20 +69,18 @@ export function FieldDemo() {
<SelectValue placeholder="MM" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="01">01</SelectItem>
<SelectItem value="02">02</SelectItem>
<SelectItem value="03">03</SelectItem>
<SelectItem value="04">04</SelectItem>
<SelectItem value="05">05</SelectItem>
<SelectItem value="06">06</SelectItem>
<SelectItem value="07">07</SelectItem>
<SelectItem value="08">08</SelectItem>
<SelectItem value="09">09</SelectItem>
<SelectItem value="10">10</SelectItem>
<SelectItem value="11">11</SelectItem>
<SelectItem value="12">12</SelectItem>
</SelectGroup>
<SelectItem value="01">01</SelectItem>
<SelectItem value="02">02</SelectItem>
<SelectItem value="03">03</SelectItem>
<SelectItem value="04">04</SelectItem>
<SelectItem value="05">05</SelectItem>
<SelectItem value="06">06</SelectItem>
<SelectItem value="07">07</SelectItem>
<SelectItem value="08">08</SelectItem>
<SelectItem value="09">09</SelectItem>
<SelectItem value="10">10</SelectItem>
<SelectItem value="11">11</SelectItem>
<SelectItem value="12">12</SelectItem>
</SelectContent>
</Select>
</Field>
@@ -96,14 +93,12 @@ export function FieldDemo() {
<SelectValue placeholder="YYYY" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="2024">2024</SelectItem>
<SelectItem value="2025">2025</SelectItem>
<SelectItem value="2026">2026</SelectItem>
<SelectItem value="2027">2027</SelectItem>
<SelectItem value="2028">2028</SelectItem>
<SelectItem value="2029">2029</SelectItem>
</SelectGroup>
<SelectItem value="2024">2024</SelectItem>
<SelectItem value="2025">2025</SelectItem>
<SelectItem value="2026">2026</SelectItem>
<SelectItem value="2027">2027</SelectItem>
<SelectItem value="2028">2028</SelectItem>
<SelectItem value="2029">2029</SelectItem>
</SelectContent>
</Select>
</Field>

View File

@@ -50,7 +50,7 @@ export function FieldHear() {
>
<Field
orientation="horizontal"
className="gap-1.5 overflow-hidden px-3! py-1.5! transition-all duration-100 ease-linear group-has-data-[state=checked]/field-label:px-2!"
className="gap-1.5 overflow-hidden !px-3 !py-1.5 transition-all duration-100 ease-linear group-has-data-[state=checked]/field-label:!px-2"
>
<Checkbox
value={option.value}

View File

@@ -66,7 +66,11 @@ export function InputGroupDemo() {
<DropdownMenuTrigger asChild>
<InputGroupButton variant="ghost">Auto</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent side="top" align="start">
<DropdownMenuContent
side="top"
align="start"
className="[--radius:0.95rem]"
>
<DropdownMenuItem>Auto</DropdownMenuItem>
<DropdownMenuItem>Agent</DropdownMenuItem>
<DropdownMenuItem>Manual</DropdownMenuItem>

View File

@@ -185,17 +185,17 @@ export function NotionPromptForm() {
const hasMentions = mentions.length > 0
return (
<form>
<form className="[--radius:1.2rem]">
<Field>
<FieldLabel htmlFor="notion-prompt" className="sr-only">
Prompt
</FieldLabel>
<InputGroup className="rounded-xl">
<InputGroup>
<InputGroupTextarea
id="notion-prompt"
placeholder="Ask, search, or make anything..."
/>
<InputGroupAddon align="block-start" className="pt-3">
<InputGroupAddon align="block-start">
<Popover
open={mentionPopoverOpen}
onOpenChange={setMentionPopoverOpen}
@@ -209,7 +209,7 @@ export function NotionPromptForm() {
<InputGroupButton
variant="outline"
size={!hasMentions ? "sm" : "icon-sm"}
className="transition-transform"
className="rounded-full transition-transform"
>
<IconAt /> {!hasMentions && "Add context"}
</InputGroupButton>
@@ -217,7 +217,7 @@ export function NotionPromptForm() {
</TooltipTrigger>
<TooltipContent>Mention a person, page, or date</TooltipContent>
</Tooltip>
<PopoverContent className="p-0" align="start">
<PopoverContent className="p-0 [--radius:1.2rem]" align="start">
<Command>
<CommandInput placeholder="Search pages..." />
<CommandList>
@@ -235,7 +235,6 @@ export function NotionPromptForm() {
setMentions((prev) => [...prev, currentValue])
setMentionPopoverOpen(false)
}}
className="rounded-lg"
>
<MentionableIcon item={item} />
{item.title}
@@ -302,8 +301,12 @@ export function NotionPromptForm() {
</TooltipTrigger>
<TooltipContent>Select AI model</TooltipContent>
</Tooltip>
<DropdownMenuContent side="top" align="start" className="w-48">
<DropdownMenuGroup className="w-48">
<DropdownMenuContent
side="top"
align="start"
className="[--radius:1rem]"
>
<DropdownMenuGroup className="w-42">
<DropdownMenuLabel className="text-muted-foreground text-xs">
Select Agent Mode
</DropdownMenuLabel>
@@ -338,7 +341,11 @@ export function NotionPromptForm() {
<IconWorld /> All Sources
</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent side="top" align="end" className="w-72">
<DropdownMenuContent
side="top"
align="end"
className="[--radius:1rem]"
>
<DropdownMenuGroup>
<DropdownMenuItem
asChild

View File

@@ -79,10 +79,7 @@ export default async function Page(props: {
const doc = page.data
const MDX = doc.body
const isChangelog = params.slug?.[0] === "changelog"
const neighbours = isChangelog
? { previous: null, next: null }
: findNeighbour(source.pageTree, page.url)
const neighbours = findNeighbour(source.pageTree, page.url)
const raw = await page.data.getText("raw")
return (
@@ -95,14 +92,12 @@ export default async function Page(props: {
<div className="mx-auto flex w-full max-w-[40rem] min-w-0 flex-1 flex-col gap-6 px-4 py-6 text-neutral-800 md:px-0 lg:py-8 dark:text-neutral-300">
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between md:items-start">
<h1 className="scroll-m-24 text-3xl font-semibold tracking-tight sm:text-3xl">
<div className="flex items-start justify-between">
<h1 className="scroll-m-24 text-4xl font-semibold tracking-tight sm:text-3xl">
{doc.title}
</h1>
<div className="docs-nav flex items-center gap-2">
<div className="hidden sm:block">
<DocsCopyPage page={raw} url={absoluteUrl(page.url)} />
</div>
<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:py-1.5 sm:backdrop-blur-none">
<DocsCopyPage page={raw} url={absoluteUrl(page.url)} />
<div className="ml-auto flex gap-2">
{neighbours.previous && (
<Button
@@ -181,7 +176,7 @@ export default async function Page(props: {
</div>
</div>
</div>
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[90svh] w-(--sidebar-width) flex-col gap-4 overflow-hidden overscroll-none pb-8 xl:flex">
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[90svh] w-72 flex-col gap-4 overflow-hidden overscroll-none pb-8 lg:flex">
<div className="h-(--top-spacing) shrink-0"></div>
{doc.toc?.length ? (
<div className="no-scrollbar flex flex-col gap-8 overflow-y-auto px-8">

View File

@@ -1,144 +0,0 @@
import Link from "next/link"
import { Button } from "@/examples/radix/ui/button"
import { mdxComponents } from "@/mdx-components"
import { IconRss } from "@tabler/icons-react"
import { getChangelogPages, type ChangelogPageData } from "@/lib/changelog"
import { absoluteUrl } from "@/lib/utils"
import { OpenInV0Cta } from "@/components/open-in-v0-cta"
export const revalidate = false
export const dynamic = "force-static"
export function generateMetadata() {
return {
title: "Changelog",
description: "Latest updates and announcements.",
openGraph: {
title: "Changelog",
description: "Latest updates and announcements.",
type: "article",
url: absoluteUrl("/docs/changelog"),
images: [
{
url: `/og?title=${encodeURIComponent(
"Changelog"
)}&description=${encodeURIComponent(
"Latest updates and announcements."
)}`,
},
],
},
}
}
export default function ChangelogPage() {
const pages = getChangelogPages()
const latestPages = pages.slice(0, 5)
const olderPages = pages.slice(5)
return (
<div
data-slot="docs"
className="flex scroll-mt-24 items-stretch pb-8 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-[40rem] min-w-0 flex-1 flex-col gap-6 px-4 py-6 text-neutral-800 md:px-0 lg:py-8 dark:text-neutral-300">
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<h1 className="scroll-m-24 text-4xl font-semibold tracking-tight sm:text-3xl">
Changelog
</h1>
<Button variant="secondary" size="sm" asChild>
<a href="/rss.xml" target="_blank" rel="noopener noreferrer">
<IconRss />
RSS
</a>
</Button>
</div>
<p className="text-muted-foreground text-[1.05rem] sm:text-base sm:text-balance md:max-w-[80%]">
Latest updates and announcements.
</p>
</div>
<div className="w-full flex-1 pb-16 sm:pb-0">
{latestPages.map((page) => {
const data = page.data as ChangelogPageData
const MDX = page.data.body
return (
<article key={page.url} className="mb-12 border-b pb-12">
<h2 className="font-heading text-xl font-semibold tracking-tight">
{data.title}
</h2>
<div className="prose-changelog mt-6 *:first:mt-0">
<MDX components={mdxComponents} />
</div>
</article>
)
})}
{olderPages.length > 0 && (
<div id="more-updates" className="mb-24 scroll-mt-24">
<h2 className="font-heading mb-6 text-xl font-semibold tracking-tight">
More Updates
</h2>
<div className="grid auto-rows-fr gap-3 sm:grid-cols-2">
{olderPages.map((page) => {
const data = page.data as ChangelogPageData
const [date, ...titleParts] = data.title.split(" - ")
const title = titleParts.join(" - ")
return (
<Link
key={page.url}
href={page.url}
className="bg-surface text-surface-foreground hover:bg-surface/80 flex w-full flex-col rounded-xl px-4 py-3 transition-colors"
>
<span className="text-muted-foreground text-xs">
{date}
</span>
<span className="text-sm font-medium">{title}</span>
</Link>
)
})}
</div>
</div>
)}
</div>
</div>
</div>
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[90svh] w-72 flex-col gap-4 overflow-hidden overscroll-none pb-8 lg:flex">
<div className="h-(--top-spacing) shrink-0"></div>
<div className="no-scrollbar flex flex-col gap-8 overflow-y-auto px-8">
<div className="flex flex-col gap-2 p-4 pt-0 text-sm">
<p className="text-muted-foreground bg-background sticky top-0 h-6 text-xs font-medium">
On This Page
</p>
{latestPages.map((page) => {
const data = page.data as ChangelogPageData
return (
<Link
key={page.url}
href={page.url}
className="text-muted-foreground hover:text-foreground text-[0.8rem] no-underline transition-colors"
>
{data.title}
</Link>
)
})}
{olderPages.length > 0 && (
<a
href="#more-updates"
className="text-muted-foreground hover:text-foreground text-[0.8rem] no-underline transition-colors"
>
More Updates
</a>
)}
</div>
</div>
<div className="hidden flex-1 flex-col gap-6 px-6 xl:flex">
<OpenInV0Cta />
</div>
</div>
</div>
)
}

View File

@@ -11,11 +11,7 @@ export default function DocsLayout({
<div className="container-wrapper flex flex-1 flex-col px-2">
<SidebarProvider
className="3xl:fixed:container 3xl:fixed:px-3 min-h-min flex-1 items-start px-0 [--top-spacing:0] lg:grid lg:grid-cols-[var(--sidebar-width)_minmax(0,1fr)] lg:[--top-spacing:calc(var(--spacing)*4)]"
style={
{
"--sidebar-width": "calc(var(--spacing) * 72)",
} as React.CSSProperties
}
style={{ "--sidebar-width": "220px" } as React.CSSProperties}
>
<DocsSidebar tree={source.pageTree} />
<div className="h-full w-full">{children}</div>

View File

@@ -70,7 +70,7 @@ export default function ExamplesLayout({
</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">
<div className="bg-background flex flex-col overflow-hidden rounded-lg border bg-clip-padding has-[[data-slot=rtl-components]]:overflow-visible has-[[data-slot=rtl-components]]:border-0 has-[[data-slot=rtl-components]]:bg-transparent md:flex-1 xl:rounded-xl">
<div className="bg-background flex flex-col overflow-hidden rounded-lg border bg-clip-padding md:flex-1 xl:rounded-xl">
{children}
</div>
</div>

View File

@@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import type { Slider as SliderPrimitive } from "radix-ui"
import { type SliderProps } from "@radix-ui/react-slider"
import {
HoverCard,
@@ -12,9 +12,7 @@ import { Label } from "@/registry/new-york-v4/ui/label"
import { Slider } from "@/registry/new-york-v4/ui/slider"
interface MaxLengthSelectorProps {
defaultValue: React.ComponentProps<
typeof SliderPrimitive.Root
>["defaultValue"]
defaultValue: SliderProps["defaultValue"]
}
export function MaxLengthSelector({ defaultValue }: MaxLengthSelectorProps) {

View File

@@ -1,8 +1,8 @@
"use client"
import * as React from "react"
import { type PopoverProps } from "@radix-ui/react-popover"
import { Check, ChevronsUpDown } from "lucide-react"
import type { Popover as PopoverPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { useMutationObserver } from "@/hooks/use-mutation-observer"
@@ -29,8 +29,7 @@ import {
import { type Model, type ModelType } from "../data/models"
interface ModelSelectorProps
extends React.ComponentProps<typeof PopoverPrimitive.Root> {
interface ModelSelectorProps extends PopoverProps {
types: readonly ModelType[]
models: Model[]
}

View File

@@ -1,6 +1,7 @@
"use client"
import * as React from "react"
import { Dialog } from "@radix-ui/react-dialog"
import { MoreHorizontal } from "lucide-react"
import { toast } from "sonner"
@@ -15,7 +16,6 @@ import {
} from "@/registry/new-york-v4/ui/alert-dialog"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,

View File

@@ -1,8 +1,8 @@
"use client"
import * as React from "react"
import { type PopoverProps } from "@radix-ui/react-popover"
import { Check, ChevronsUpDown } from "lucide-react"
import type { Popover as PopoverPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/new-york-v4/ui/button"
@@ -23,8 +23,7 @@ import {
import { type Preset } from "../data/presets"
interface PresetSelectorProps
extends React.ComponentProps<typeof PopoverPrimitive.Root> {
interface PresetSelectorProps extends PopoverProps {
presets: Preset[]
}

View File

@@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import type { Slider as SliderPrimitive } from "radix-ui"
import { type SliderProps } from "@radix-ui/react-slider"
import {
HoverCard,
@@ -12,9 +12,7 @@ import { Label } from "@/registry/new-york-v4/ui/label"
import { Slider } from "@/registry/new-york-v4/ui/slider"
interface TemperatureSelectorProps {
defaultValue: React.ComponentProps<
typeof SliderPrimitive.Root
>["defaultValue"]
defaultValue: SliderProps["defaultValue"]
}
export function TemperatureSelector({

View File

@@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import type { Slider as SliderPrimitive } from "radix-ui"
import { type SliderProps } from "@radix-ui/react-slider"
import {
HoverCard,
@@ -12,9 +12,7 @@ import { Label } from "@/registry/new-york-v4/ui/label"
import { Slider } from "@/registry/new-york-v4/ui/slider"
interface TopPSelectorProps {
defaultValue: React.ComponentProps<
typeof SliderPrimitive.Root
>["defaultValue"]
defaultValue: SliderProps["defaultValue"]
}
export function TopPSelector({ defaultValue }: TopPSelectorProps) {

View File

@@ -1,170 +0,0 @@
"use client"
import * as React from "react"
import { Button } from "@/examples/base/ui-rtl/button"
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
FieldTitle,
} from "@/examples/base/ui-rtl/field"
import { Input } from "@/examples/base/ui-rtl/input"
import { RadioGroup, RadioGroupItem } from "@/examples/base/ui-rtl/radio-group"
import { Switch } from "@/examples/base/ui-rtl/switch"
import { IconMinus, IconPlus } from "@tabler/icons-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
computeEnvironment: "بيئة الحوسبة",
computeDescription: "اختر بيئة الحوسبة لمجموعتك.",
kubernetes: "كوبرنيتس",
kubernetesDescription:
"تشغيل أحمال عمل GPU على مجموعة مُهيأة بـ K8s. هذا هو الافتراضي.",
virtualMachine: "جهاز افتراضي",
vmDescription: "الوصول إلى مجموعة VM مُهيأة لتشغيل أحمال العمل. (قريبًا)",
numberOfGpus: "عدد وحدات GPU",
gpuDescription: "يمكنك إضافة المزيد لاحقًا.",
decrement: "إنقاص",
increment: "زيادة",
wallpaperTinting: "تلوين الخلفية",
wallpaperDescription: "السماح بتلوين الخلفية.",
},
he: {
dir: "rtl" as const,
computeEnvironment: "סביבת מחשוב",
computeDescription: "בחר את סביבת המחשוב לאשכול שלך.",
kubernetes: "קוברנטיס",
kubernetesDescription:
"הפעל עומסי עבודה של GPU באשכול מוגדר K8s. זו ברירת המחדל.",
virtualMachine: "מכונה וירטואלית",
vmDescription: "גש לאשכול VM מוגדר להפעלת עומסי עבודה. (בקרוב)",
numberOfGpus: "מספר GPUs",
gpuDescription: "תוכל להוסיף עוד מאוחר יותר.",
decrement: "הפחת",
increment: "הגדל",
wallpaperTinting: "צביעת טפט",
wallpaperDescription: "אפשר לטפט להיצבע.",
},
}
export function AppearanceSettings() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
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 (
<div dir={t.dir}>
<FieldSet>
<FieldGroup>
<FieldSet>
<FieldLegend>{t.computeEnvironment}</FieldLegend>
<FieldDescription>{t.computeDescription}</FieldDescription>
<RadioGroup defaultValue="kubernetes">
<FieldLabel htmlFor="rtl-kubernetes">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>{t.kubernetes}</FieldTitle>
<FieldDescription>
{t.kubernetesDescription}
</FieldDescription>
</FieldContent>
<RadioGroupItem
value="kubernetes"
id="rtl-kubernetes"
aria-label={t.kubernetes}
/>
</Field>
</FieldLabel>
<FieldLabel htmlFor="rtl-vm">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>{t.virtualMachine}</FieldTitle>
<FieldDescription>{t.vmDescription}</FieldDescription>
</FieldContent>
<RadioGroupItem
value="vm"
id="rtl-vm"
aria-label={t.virtualMachine}
/>
</Field>
</FieldLabel>
</RadioGroup>
</FieldSet>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="rtl-gpu-count">{t.numberOfGpus}</FieldLabel>
<FieldDescription>{t.gpuDescription}</FieldDescription>
</FieldContent>
<ButtonGroup>
<Input
id="rtl-gpu-count"
value={gpuCount}
onChange={handleGpuInputChange}
size={3}
className="h-7 !w-14 font-mono"
maxLength={3}
/>
<Button
variant="outline"
size="icon-sm"
type="button"
aria-label={t.decrement}
onClick={() => handleGpuAdjustment(-1)}
disabled={gpuCount <= 1}
>
<IconMinus />
</Button>
<Button
variant="outline"
size="icon-sm"
type="button"
aria-label={t.increment}
onClick={() => handleGpuAdjustment(1)}
disabled={gpuCount >= 99}
>
<IconPlus />
</Button>
</ButtonGroup>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="rtl-tinting">
{t.wallpaperTinting}
</FieldLabel>
<FieldDescription>{t.wallpaperDescription}</FieldDescription>
</FieldContent>
<Switch id="rtl-tinting" defaultChecked />
</Field>
</FieldGroup>
</FieldSet>
</div>
)
}

View File

@@ -1,179 +0,0 @@
"use client"
import * as React from "react"
import { Button } from "@/examples/base/ui-rtl/button"
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/examples/base/ui-rtl/dropdown-menu"
import {
ArchiveIcon,
ArrowLeftIcon,
CalendarPlusIcon,
ClockIcon,
ListFilterIcon,
MailCheckIcon,
MoreHorizontalIcon,
TagIcon,
Trash2Icon,
} from "lucide-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
goBack: "رجوع",
archive: "أرشفة",
report: "إبلاغ",
snooze: "تأجيل",
moreOptions: "خيارات أخرى",
markAsRead: "تحديد كمقروء",
addToCalendar: "إضافة إلى التقويم",
addToList: "إضافة إلى القائمة",
labelAs: "تصنيف كـ...",
personal: "شخصي",
work: "عمل",
other: "أخرى",
trash: "حذف",
},
he: {
dir: "rtl" as const,
goBack: "חזור",
archive: "ארכיון",
report: "דווח",
snooze: "נודניק",
moreOptions: "אפשרויות נוספות",
markAsRead: "סמן כנקרא",
addToCalendar: "הוסף ליומן",
addToList: "הוסף לרשימה",
labelAs: "תייג כ...",
personal: "אישי",
work: "עבודה",
other: "אחר",
trash: "מחק",
},
}
export function ButtonGroupDemo() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const [label, setLabel] = React.useState("personal")
return (
<div dir={t.dir}>
<ButtonGroup>
<ButtonGroup className="hidden sm:flex">
<Button variant="outline" size="icon-sm" aria-label={t.goBack}>
<ArrowLeftIcon className="rtl:rotate-180" />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="sm">
{t.archive}
</Button>
<Button variant="outline" size="sm">
{t.report}
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="sm">
{t.snooze}
</Button>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
variant="outline"
size="icon-sm"
aria-label={t.moreOptions}
/>
}
>
<MoreHorizontalIcon />
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
dir={t.dir}
data-lang={lang}
className="w-44"
>
<DropdownMenuGroup>
<DropdownMenuItem>
<MailCheckIcon />
{t.markAsRead}
</DropdownMenuItem>
<DropdownMenuItem>
<ArchiveIcon />
{t.archive}
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<ClockIcon />
{t.snooze}
</DropdownMenuItem>
<DropdownMenuItem>
<CalendarPlusIcon />
{t.addToCalendar}
</DropdownMenuItem>
<DropdownMenuItem>
<ListFilterIcon />
{t.addToList}
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<TagIcon />
{t.labelAs}
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent
side="left"
dir={t.dir}
data-lang={lang}
>
<DropdownMenuRadioGroup
value={label}
onValueChange={setLabel}
>
<DropdownMenuRadioItem value="personal">
{t.personal}
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="work">
{t.work}
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="other">
{t.other}
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<Trash2Icon />
{t.trash}
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</ButtonGroup>
</div>
)
}

View File

@@ -1,82 +0,0 @@
"use client"
import * as React from "react"
import { Button } from "@/examples/base/ui-rtl/button"
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/examples/base/ui-rtl/input-group"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/examples/base/ui-rtl/tooltip"
import { AudioLinesIcon, PlusIcon } from "lucide-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
add: "إضافة",
voicePlaceholder: "سجل وأرسل صوتًا...",
messagePlaceholder: "أرسل رسالة...",
voiceMode: "الوضع الصوتي",
},
he: {
dir: "rtl" as const,
add: "הוסף",
voicePlaceholder: "הקלט ושלח אודיו...",
messagePlaceholder: "שלח הודעה...",
voiceMode: "מצב קולי",
},
}
export function ButtonGroupInputGroup() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const [voiceEnabled, setVoiceEnabled] = React.useState(false)
return (
<ButtonGroup dir={t.dir}>
<ButtonGroup>
<Button variant="outline" size="icon" aria-label={t.add}>
<PlusIcon />
</Button>
</ButtonGroup>
<ButtonGroup className="flex-1">
<InputGroup>
<InputGroupInput
placeholder={
voiceEnabled ? t.voicePlaceholder : t.messagePlaceholder
}
disabled={voiceEnabled}
/>
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger
render={
<InputGroupButton
onClick={() => setVoiceEnabled(!voiceEnabled)}
data-active={voiceEnabled}
className="data-[active=true]:bg-primary data-[active=true]:text-primary-foreground"
aria-pressed={voiceEnabled}
size="icon-xs"
aria-label={t.voiceMode}
/>
}
>
<AudioLinesIcon />
</TooltipTrigger>
<TooltipContent>{t.voiceMode}</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
</ButtonGroup>
</ButtonGroup>
)
}

View File

@@ -1,56 +0,0 @@
"use client"
import { Button } from "@/examples/base/ui-rtl/button"
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
locale: "ar-SA",
previous: "السابق",
next: "التالي",
},
he: {
dir: "rtl" as const,
locale: "he-IL",
previous: "הקודם",
next: "הבא",
},
}
function formatNumber(value: number, locale: string) {
return new Intl.NumberFormat(locale).format(value)
}
export function ButtonGroupNested() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
return (
<ButtonGroup dir={t.dir}>
<ButtonGroup>
<Button variant="outline" size="sm">
{formatNumber(1, t.locale)}
</Button>
<Button variant="outline" size="sm">
{formatNumber(2, t.locale)}
</Button>
<Button variant="outline" size="sm">
{formatNumber(3, t.locale)}
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="icon-sm" aria-label={t.previous}>
<ArrowLeftIcon className="rtl:rotate-180" />
</Button>
<Button variant="outline" size="icon-sm" aria-label={t.next}>
<ArrowRightIcon className="rtl:rotate-180" />
</Button>
</ButtonGroup>
</ButtonGroup>
)
}

View File

@@ -1,83 +0,0 @@
"use client"
import { Button } from "@/examples/base/ui-rtl/button"
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/examples/base/ui-rtl/popover"
import { Separator } from "@/examples/base/ui-rtl/separator"
import { Textarea } from "@/examples/base/ui-rtl/textarea"
import { BotIcon, ChevronDownIcon } from "lucide-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
copilot: "المساعد",
openPopover: "فتح القائمة",
agentTasks: "مهام الوكيل",
placeholder: "صف مهمتك بلغة طبيعية.",
startTask: "ابدأ مهمة جديدة مع المساعد",
description:
"صف مهمتك بلغة طبيعية. سيعمل المساعد في الخلفية ويفتح طلب سحب لمراجعتك.",
},
he: {
dir: "rtl" as const,
copilot: "עוזר",
openPopover: "פתח תפריט",
agentTasks: "משימות סוכן",
placeholder: "תאר את המשימה שלך בשפה טבעית.",
startTask: "התחל משימה חדשה עם העוזר",
description:
"תאר את המשימה שלך בשפה טבעית. העוזר יעבוד ברקע ויפתח בקשת משיכה לבדיקתך.",
},
}
export function ButtonGroupPopover() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
return (
<ButtonGroup dir={t.dir}>
<Button variant="outline" size="sm">
<BotIcon /> {t.copilot}
</Button>
<Popover>
<PopoverTrigger
render={
<Button
variant="outline"
size="icon-sm"
aria-label={t.openPopover}
/>
}
>
<ChevronDownIcon />
</PopoverTrigger>
<PopoverContent
align="start"
dir={t.dir}
data-lang={lang}
className="p-0"
>
<div className="px-4 py-3">
<div className="text-sm font-medium">{t.agentTasks}</div>
</div>
<Separator />
<div className="p-4 text-sm *:[p:not(:last-child)]:mb-2">
<Textarea
placeholder={t.placeholder}
className="mb-4 resize-none"
/>
<p className="font-medium">{t.startTask}</p>
<p className="text-muted-foreground">{t.description}</p>
</div>
</PopoverContent>
</Popover>
</ButtonGroup>
)
}

View File

@@ -1,78 +0,0 @@
"use client"
import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarImage,
} from "@/examples/base/ui-rtl/avatar"
import { Button } from "@/examples/base/ui-rtl/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/examples/base/ui-rtl/empty"
import { PlusIcon } from "lucide-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
title: "لا يوجد أعضاء في الفريق",
description: "قم بدعوة فريقك للتعاون في هذا المشروع.",
invite: "دعوة أعضاء",
},
he: {
dir: "rtl" as const,
title: "אין חברי צוות",
description: "הזמן את הצוות שלך לשתף פעולה בפרויקט זה.",
invite: "הזמן חברים",
},
}
export function EmptyAvatarGroup() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
return (
<Empty className="flex-none border py-10" dir={t.dir}>
<EmptyHeader>
<EmptyMedia>
<AvatarGroup className="grayscale">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
</EmptyMedia>
<EmptyTitle>{t.title}</EmptyTitle>
<EmptyDescription>{t.description}</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm">
<PlusIcon />
{t.invite}
</Button>
</EmptyContent>
</Empty>
)
}

View File

@@ -1,36 +0,0 @@
"use client"
import { Checkbox } from "@/examples/base/ui-rtl/checkbox"
import { Field, FieldLabel } from "@/examples/base/ui-rtl/field"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
terms: "أوافق على الشروط والأحكام",
},
he: {
dir: "rtl" as const,
terms: "אני מסכים לתנאים וההגבלות",
},
}
export function FieldCheckbox() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const { dir, terms } = translations[lang]
return (
<div dir={dir}>
<FieldLabel htmlFor="checkbox-demo-rtl">
<Field orientation="horizontal">
<Checkbox id="checkbox-demo-rtl" defaultChecked />
<FieldLabel htmlFor="checkbox-demo-rtl" className="line-clamp-1">
{terms}
</FieldLabel>
</Field>
</FieldLabel>
</div>
)
}

View File

@@ -1,217 +0,0 @@
"use client"
import { Button } from "@/examples/base/ui-rtl/button"
import { Checkbox } from "@/examples/base/ui-rtl/checkbox"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
} from "@/examples/base/ui-rtl/field"
import { Input } from "@/examples/base/ui-rtl/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/examples/base/ui-rtl/select"
import { Textarea } from "@/examples/base/ui-rtl/textarea"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
locale: "ar-SA",
paymentMethod: "طريقة الدفع",
secureEncrypted: "جميع المعاملات آمنة ومشفرة",
nameOnCard: "الاسم على البطاقة",
namePlaceholder: "أحمد محمد",
cardNumber: "رقم البطاقة",
cardDescription: "أدخل رقمك المكون من 16 رقمًا.",
cvv: "رمز الأمان",
month: "الشهر",
year: "السنة",
billingAddress: "عنوان الفواتير",
billingDescription: "عنوان الفواتير المرتبط بطريقة الدفع الخاصة بك",
sameAsShipping: "نفس عنوان الشحن",
comments: "تعليقات",
commentsPlaceholder: "أضف أي تعليقات إضافية",
submit: "إرسال",
cancel: "إلغاء",
},
he: {
dir: "rtl" as const,
locale: "he-IL",
paymentMethod: "אמצעי תשלום",
secureEncrypted: "כל העסקאות מאובטחות ומוצפנות",
nameOnCard: "שם על הכרטיס",
namePlaceholder: "ישראל ישראלי",
cardNumber: "מספר כרטיס",
cardDescription: "הזן את המספר בן 16 הספרות שלך.",
cvv: "קוד אבטחה",
month: "חודש",
year: "שנה",
billingAddress: "כתובת לחיוב",
billingDescription: "כתובת החיוב המשויכת לאמצעי התשלום שלך",
sameAsShipping: "זהה לכתובת המשלוח",
comments: "הערות",
commentsPlaceholder: "הוסף הערות נוספות",
submit: "שלח",
cancel: "ביטול",
},
}
function formatCardNumber(locale: string) {
const formatter = new Intl.NumberFormat(locale, { useGrouping: false })
return `${formatter.format(1234)} ${formatter.format(5678)} ${formatter.format(9012)} ${formatter.format(3456)}`
}
function formatCvv(locale: string) {
return new Intl.NumberFormat(locale, { useGrouping: false }).format(123)
}
function getMonths(locale: string) {
const formatter = new Intl.NumberFormat(locale, {
minimumIntegerDigits: 2,
useGrouping: false,
})
return Array.from({ length: 12 }, (_, i) => {
const value = String(i + 1).padStart(2, "0")
return { label: formatter.format(i + 1), value }
})
}
function getYears(locale: string) {
const formatter = new Intl.NumberFormat(locale, { useGrouping: false })
return Array.from({ length: 6 }, (_, i) => {
const year = 2024 + i
return { label: formatter.format(year), value: String(year) }
})
}
export function FieldDemo() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const months = getMonths(t.locale)
const years = getYears(t.locale)
const cardPlaceholder = formatCardNumber(t.locale)
const cvvPlaceholder = formatCvv(t.locale)
return (
<div dir={t.dir} className="w-full max-w-md rounded-lg border p-6">
<form>
<FieldGroup>
<FieldSet>
<FieldLegend>{t.paymentMethod}</FieldLegend>
<FieldDescription>{t.secureEncrypted}</FieldDescription>
<FieldGroup>
<Field>
<FieldLabel htmlFor="rtl-card-name">{t.nameOnCard}</FieldLabel>
<Input
id="rtl-card-name"
placeholder={t.namePlaceholder}
required
/>
</Field>
<div className="grid grid-cols-3 gap-4">
<Field className="col-span-2">
<FieldLabel htmlFor="rtl-card-number">
{t.cardNumber}
</FieldLabel>
<Input
id="rtl-card-number"
placeholder={cardPlaceholder}
required
/>
<FieldDescription>{t.cardDescription}</FieldDescription>
</Field>
<Field className="col-span-1">
<FieldLabel htmlFor="rtl-cvv">{t.cvv}</FieldLabel>
<Input id="rtl-cvv" placeholder={cvvPlaceholder} required />
</Field>
</div>
<div className="grid grid-cols-2 gap-4">
<Field>
<FieldLabel htmlFor="rtl-exp-month">{t.month}</FieldLabel>
<Select defaultValue="" items={months}>
<SelectTrigger id="rtl-exp-month">
<SelectValue placeholder="MM" />
</SelectTrigger>
<SelectContent data-lang={lang} dir={t.dir}>
<SelectGroup>
{months.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="rtl-exp-year">{t.year}</FieldLabel>
<Select defaultValue="" items={years}>
<SelectTrigger id="rtl-exp-year">
<SelectValue placeholder="YYYY" />
</SelectTrigger>
<SelectContent data-lang={lang} dir={t.dir}>
<SelectGroup>
{years.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
</div>
</FieldGroup>
</FieldSet>
<FieldSeparator />
<FieldSet>
<FieldLegend>{t.billingAddress}</FieldLegend>
<FieldDescription>{t.billingDescription}</FieldDescription>
<FieldGroup>
<Field orientation="horizontal">
<Checkbox id="rtl-same-as-shipping" defaultChecked />
<FieldLabel
htmlFor="rtl-same-as-shipping"
className="font-normal"
>
{t.sameAsShipping}
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
<FieldSeparator />
<FieldSet>
<FieldGroup>
<Field>
<FieldLabel htmlFor="rtl-comments">{t.comments}</FieldLabel>
<Textarea
id="rtl-comments"
placeholder={t.commentsPlaceholder}
className="resize-none"
/>
</Field>
</FieldGroup>
</FieldSet>
<Field orientation="horizontal">
<Button type="submit">{t.submit}</Button>
<Button variant="outline" type="button">
{t.cancel}
</Button>
</Field>
</FieldGroup>
</form>
</div>
)
}

View File

@@ -1,90 +0,0 @@
"use client"
import { Card, CardContent } from "@/examples/base/ui-rtl/card"
import { Checkbox } from "@/examples/base/ui-rtl/checkbox"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
FieldTitle,
} from "@/examples/base/ui-rtl/field"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
legend: "كيف سمعت عنا؟",
description: "اختر الخيار الذي يصف أفضل طريقة سمعت عنا من خلالها.",
socialMedia: "التواصل الاجتماعي",
searchEngine: "البحث",
referral: "إحالة",
other: "أخرى",
},
he: {
dir: "rtl" as const,
legend: "איך שמעת עלינו?",
description: "בחר את האפשרות שמתארת בצורה הטובה ביותר כיצד שמעת עלינו.",
socialMedia: "חברתיות",
searchEngine: "חיפוש",
referral: "הפניה",
other: "אחר",
},
}
export function FieldHear() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const options = [
{ label: t.socialMedia, value: "social-media" },
{ label: t.searchEngine, value: "search-engine" },
{ label: t.referral, value: "referral" },
{ label: t.other, value: "other" },
]
return (
<div dir={t.dir}>
<Card className="border-0 py-4 shadow-none">
<CardContent className="px-4">
<form>
<FieldGroup>
<FieldSet className="gap-4">
<FieldLegend>{t.legend}</FieldLegend>
<FieldDescription className="line-clamp-1">
{t.description}
</FieldDescription>
<FieldGroup className="flex flex-row flex-wrap gap-2 [--radius:9999rem]">
{options.map((option) => (
<FieldLabel
htmlFor={`rtl-${option.value}`}
key={option.value}
className="!w-fit"
>
<Field
orientation="horizontal"
className="gap-1.5 overflow-hidden px-3! py-1.5! transition-all duration-100 ease-linear group-has-data-[state=checked]/field-label:px-2!"
>
<Checkbox
value={option.value}
id={`rtl-${option.value}`}
defaultChecked={option.value === "social-media"}
className="-ms-6 translate-x-1 rounded-full transition-all duration-100 ease-linear data-checked:ms-0 data-checked:translate-x-0"
/>
<FieldTitle>{option.label}</FieldTitle>
</Field>
</FieldLabel>
))}
</FieldGroup>
</FieldSet>
</FieldGroup>
</form>
</CardContent>
</Card>
</div>
)
}

View File

@@ -1,67 +0,0 @@
"use client"
import { useState } from "react"
import {
Field,
FieldDescription,
FieldTitle,
} from "@/examples/base/ui-rtl/field"
import { Slider } from "@/examples/base/ui-rtl/slider"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
locale: "ar-SA",
title: "نطاق السعر",
description: "حدد نطاق ميزانيتك",
ariaLabel: "نطاق السعر",
currency: "﷼",
},
he: {
dir: "rtl" as const,
locale: "he-IL",
title: "טווח מחירים",
description: "הגדר את טווח התקציב שלך",
ariaLabel: "טווח מחירים",
currency: "₪",
},
}
function formatNumber(value: number, locale: string) {
return new Intl.NumberFormat(locale).format(value)
}
export function FieldSlider() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const [value, setValue] = useState([200, 800])
return (
<Field dir={t.dir}>
<FieldTitle>{t.title}</FieldTitle>
<FieldDescription>
{t.description} ({t.currency}
<span className="font-medium tabular-nums">
{formatNumber(value[0], t.locale)}
</span>{" "}
-{" "}
<span className="font-medium tabular-nums">
{formatNumber(value[1], t.locale)}
</span>
).
</FieldDescription>
<Slider
value={value}
onValueChange={(value) => setValue(value as [number, number])}
max={1000}
min={0}
step={10}
className="mt-2 w-full"
aria-label={t.ariaLabel}
/>
</Field>
)
}

View File

@@ -1,92 +0,0 @@
"use client"
import { DirectionProvider } from "@/examples/base/ui-rtl/direction"
import { FieldSeparator } from "@/examples/base/ui-rtl/field"
import {
LanguageProvider,
LanguageSelector,
useLanguageContext,
} from "@/components/language-selector"
import { AppearanceSettings } from "./appearance-settings"
import { ButtonGroupDemo } from "./button-group-demo"
import { ButtonGroupInputGroup } from "./button-group-input-group"
import { ButtonGroupNested } from "./button-group-nested"
import { ButtonGroupPopover } from "./button-group-popover"
import { EmptyAvatarGroup } from "./empty-avatar-group"
import { FieldCheckbox } from "./field-checkbox"
import { FieldDemo } from "./field-demo"
import { FieldHear } from "./field-hear"
import { FieldSlider } from "./field-slider"
import { InputGroupButtonExample } from "./input-group-button"
import { InputGroupDemo } from "./input-group-demo"
import { ItemDemo } from "./item-demo"
import { NotionPromptForm } from "./notion-prompt-form"
import { SpinnerBadge } from "./spinner-badge"
import { SpinnerEmpty } from "./spinner-empty"
function RtlComponentsContent() {
const context = useLanguageContext()
if (!context) {
return null
}
const { language } = context
return (
<div
className="relative grid gap-8 p-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-6 2xl:gap-8"
dir="rtl"
data-lang={language}
data-slot="rtl-components"
>
<LanguageSelector
value={language}
onValueChange={context.setLanguage}
className="absolute -top-12 right-52 hidden h-8! data-[size=sm]:rounded-lg lg:flex"
languages={["ar", "he"]}
/>
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
<FieldDemo />
</div>
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
<EmptyAvatarGroup />
<SpinnerBadge />
<ButtonGroupInputGroup />
<FieldSlider />
<InputGroupDemo />
</div>
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
<InputGroupButtonExample />
<ItemDemo />
<FieldSeparator className="my-4">
{language === "he" ? "הגדרות מראה" : "إعدادات المظهر"}
</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">
<NotionPromptForm />
<ButtonGroupDemo />
<FieldCheckbox />
<div className="flex justify-between gap-4">
<ButtonGroupNested />
<ButtonGroupPopover />
</div>
<FieldHear />
<SpinnerEmpty />
</div>
</div>
)
}
export function RtlComponents() {
return (
<LanguageProvider defaultLanguage="ar">
<DirectionProvider direction="rtl">
<RtlComponentsContent />
</DirectionProvider>
</LanguageProvider>
)
}

View File

@@ -1,97 +0,0 @@
"use client"
import * as React from "react"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/examples/base/ui-rtl/input-group"
import { Label } from "@/examples/base/ui-rtl/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/examples/base/ui-rtl/popover"
import { IconInfoCircle, IconStar } from "@tabler/icons-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
inputLabel: "السعر",
info: "معلومات",
priceInfo: "أدخل السعر بالريال السعودي.",
priceDescription: "سيتم تحويل السعر تلقائياً.",
favorite: "مفضل",
currency: "ر.س",
},
he: {
dir: "rtl" as const,
inputLabel: "מחיר",
info: "מידע",
priceInfo: "הזן את המחיר בשקלים.",
priceDescription: "המחיר יומר אוטומטית.",
favorite: "מועדף",
currency: "₪",
},
}
export function InputGroupButtonExample() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const [isFavorite, setIsFavorite] = React.useState(false)
return (
<div dir={t.dir} className="grid w-full max-w-sm gap-6">
<Label htmlFor="input-secure-rtl" className="sr-only">
{t.inputLabel}
</Label>
<InputGroup className="[--radius:9999px]">
<InputGroupInput id="input-secure-rtl" className="!pr-0.5" />
<InputGroupAddon>
<Popover>
<PopoverTrigger
render={
<InputGroupButton
variant="secondary"
size="icon-xs"
aria-label={t.info}
/>
}
>
<IconInfoCircle />
</PopoverTrigger>
<PopoverContent
align="end"
alignOffset={10}
className="flex flex-col gap-1 rounded-xl text-sm"
data-lang={lang}
dir={t.dir}
>
<p className="font-medium">{t.priceInfo}</p>
<p>{t.priceDescription}</p>
</PopoverContent>
</Popover>
</InputGroupAddon>
<InputGroupAddon className="text-muted-foreground">
{t.currency}
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupButton
onClick={() => setIsFavorite(!isFavorite)}
size="icon-xs"
aria-label={t.favorite}
>
<IconStar
data-favorite={isFavorite}
className="data-[favorite=true]:fill-primary data-[favorite=true]:stroke-primary"
/>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
)
}

View File

@@ -1,140 +0,0 @@
"use client"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/examples/base/ui-rtl/dropdown-menu"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/examples/base/ui-rtl/input-group"
import { Separator } from "@/examples/base/ui-rtl/separator"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/examples/base/ui-rtl/tooltip"
import {
IconCheck,
IconChevronDown,
IconInfoCircle,
IconPlus,
} from "@tabler/icons-react"
import { ArrowUpIcon, Search } from "lucide-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
search: "بحث...",
results: "12 نتيجة",
example: "example.com",
tooltipContent: "هذا محتوى في تلميح.",
askSearchChat: "اسأل، ابحث أو تحدث...",
add: "إضافة",
auto: "تلقائي",
agent: "وكيل",
manual: "يدوي",
used: "52% مستخدم",
send: "إرسال",
},
he: {
dir: "rtl" as const,
search: "חיפוש...",
results: "12 תוצאות",
example: "example.com",
tooltipContent: "זה תוכן בטולטיפ.",
askSearchChat: "שאל, חפש או שוחח...",
add: "הוסף",
auto: "אוטומטי",
agent: "סוכן",
manual: "ידני",
used: "52% בשימוש",
send: "שלח",
},
}
export function InputGroupDemo() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
return (
<div dir={t.dir} className="grid w-full max-w-sm gap-6">
<InputGroup>
<InputGroupInput placeholder={t.search} />
<InputGroupAddon>
<Search />
</InputGroupAddon>
<InputGroupAddon align="inline-end">{t.results}</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder={t.example} />
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger
render={
<InputGroupButton
className="rounded-full"
size="icon-xs"
aria-label={t.add}
/>
}
>
<IconInfoCircle />
</TooltipTrigger>
<TooltipContent>{t.tooltipContent}</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupTextarea placeholder={t.askSearchChat} />
<InputGroupAddon align="block-end">
<InputGroupButton
variant="outline"
className="rounded-full"
size="icon-xs"
aria-label={t.add}
>
<IconPlus />
</InputGroupButton>
<DropdownMenu>
<DropdownMenuTrigger render={<InputGroupButton variant="ghost" />}>
<IconChevronDown />
</DropdownMenuTrigger>
<DropdownMenuContent side="top" align="start">
<DropdownMenuItem>{t.auto}</DropdownMenuItem>
<DropdownMenuItem>{t.agent}</DropdownMenuItem>
<DropdownMenuItem>{t.manual}</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<InputGroupText className="ms-auto">{t.used}</InputGroupText>
<Separator orientation="vertical" className="!h-4" />
<InputGroupButton
variant="default"
className="rounded-full"
size="icon-xs"
>
<ArrowUpIcon />
<span className="sr-only">{t.send}</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="shadcn" />
<InputGroupAddon align="inline-end">
<div className="bg-primary text-foreground flex size-4 items-center justify-center rounded-full">
<IconCheck className="size-3 text-white" />
</div>
</InputGroupAddon>
</InputGroup>
</div>
)
}

View File

@@ -1,64 +0,0 @@
"use client"
import { Button } from "@/examples/base/ui-rtl/button"
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/examples/base/ui-rtl/item"
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
twoFactor: "المصادقة الثنائية",
twoFactorDescription: "التحقق عبر البريد الإلكتروني أو رقم الهاتف.",
enable: "تفعيل",
verified: "تم التحقق من ملفك الشخصي.",
},
he: {
dir: "rtl" as const,
twoFactor: "אימות דו-שלבי",
twoFactorDescription: "אמת באמצעות אימייל או מספר טלפון.",
enable: "הפעל",
verified: "הפרופיל שלך אומת.",
},
}
export function ItemDemo() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
return (
<div dir={t.dir} className="flex w-full max-w-md flex-col gap-6">
<Item variant="outline">
<ItemContent>
<ItemTitle>{t.twoFactor}</ItemTitle>
<ItemDescription className="text-pretty xl:hidden 2xl:block">
{t.twoFactorDescription}
</ItemDescription>
</ItemContent>
<ItemActions>
<Button size="sm">{t.enable}</Button>
</ItemActions>
</Item>
<Item variant="outline" size="sm">
<ItemMedia>
<BadgeCheckIcon className="size-5" />
</ItemMedia>
<ItemContent>
<ItemTitle>{t.verified}</ItemTitle>
</ItemContent>
<ItemActions>
<ChevronRightIcon className="size-4 rtl:rotate-180" />
</ItemActions>
</Item>
</div>
)
}

View File

@@ -1,516 +0,0 @@
"use client"
import { useMemo, useState } from "react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/examples/base/ui-rtl/avatar"
import { Badge } from "@/examples/base/ui-rtl/badge"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/examples/base/ui-rtl/command"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/examples/base/ui-rtl/dropdown-menu"
import { Field, FieldLabel } from "@/examples/base/ui-rtl/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupTextarea,
} from "@/examples/base/ui-rtl/input-group"
import { Popover, PopoverContent } from "@/examples/base/ui-rtl/popover"
import { Switch } from "@/examples/base/ui-rtl/switch"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/examples/base/ui-rtl/tooltip"
import {
IconApps,
IconArrowUp,
IconAt,
IconBook,
IconCircleDashedPlus,
IconPaperclip,
IconPlus,
IconWorld,
IconX,
} from "@tabler/icons-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
prompt: "الأمر",
placeholder: "اسأل، ابحث، أو أنشئ أي شيء...",
addContext: "أضف سياق",
mentionTooltip: "اذكر شخصًا أو صفحة أو تاريخًا",
searchPages: "البحث في الصفحات...",
noPagesFound: "لم يتم العثور على صفحات",
pages: "الصفحات",
users: "المستخدمون",
attachFile: "إرفاق ملف",
selectModel: "اختر نموذج الذكاء الاصطناعي",
selectAgentMode: "اختر وضع الوكيل",
webSearch: "البحث على الويب",
appsIntegrations: "التطبيقات والتكاملات",
allSourcesAccess: "جميع المصادر التي يمكنني الوصول إليها",
findKnowledge: "ابحث أو استخدم المعرفة في...",
noKnowledgeFound: "لم يتم العثور على معرفة",
helpCenter: "مركز المساعدة",
connectApps: "ربط التطبيقات",
searchSourcesNote: "سنبحث فقط في المصادر المحددة هنا.",
send: "إرسال",
allSources: "جميع المصادر",
auto: "تلقائي",
agentMode: "وضع الوكيل",
planMode: "وضع التخطيط",
beta: "تجريبي",
workspace: "مساحة العمل",
meetingNotes: "ملاحظات الاجتماع",
projectDashboard: "لوحة المشروع",
ideasBrainstorming: "أفكار وعصف ذهني",
calendarEvents: "التقويم والأحداث",
documentation: "التوثيق",
goalsObjectives: "الأهداف والغايات",
budgetPlanning: "تخطيط الميزانية",
teamDirectory: "دليل الفريق",
technicalSpecs: "المواصفات التقنية",
analyticsReport: "تقرير التحليلات",
},
he: {
dir: "rtl" as const,
prompt: "פקודה",
placeholder: "שאל, חפש, או צור משהו...",
addContext: "הוסף הקשר",
mentionTooltip: "הזכר אדם, עמוד או תאריך",
searchPages: "חפש עמודים...",
noPagesFound: "לא נמצאו עמודים",
pages: "עמודים",
users: "משתמשים",
attachFile: "צרף קובץ",
selectModel: "בחר מודל AI",
selectAgentMode: "בחר מצב סוכן",
webSearch: "חיפוש באינטרנט",
appsIntegrations: "אפליקציות ואינטגרציות",
allSourcesAccess: "כל המקורות שיש לי גישה אליהם",
findKnowledge: "מצא או השתמש בידע ב...",
noKnowledgeFound: "לא נמצא ידע",
helpCenter: "מרכז עזרה",
connectApps: "חבר אפליקציות",
searchSourcesNote: "נחפש רק במקורות שנבחרו כאן.",
send: "שלח",
allSources: "כל המקורות",
auto: "אוטומטי",
agentMode: "מצב סוכן",
planMode: "מצב תכנון",
beta: "בטא",
workspace: "סביבת עבודה",
meetingNotes: "הערות פגישה",
projectDashboard: "לוח מחוונים לפרויקט",
ideasBrainstorming: "רעיונות וסיעור מוחות",
calendarEvents: "יומן ואירועים",
documentation: "תיעוד",
goalsObjectives: "מטרות ויעדים",
budgetPlanning: "תכנון תקציב",
teamDirectory: "ספריית צוות",
technicalSpecs: "מפרט טכני",
analyticsReport: "דוח אנליטיקה",
},
}
function MentionableIcon({
item,
}: {
item: { type: string; title: string; image: string }
}) {
return item.type === "page" ? (
<span className="flex size-4 items-center justify-center">
{item.image}
</span>
) : (
<Avatar className="size-4">
<AvatarImage src={item.image} />
<AvatarFallback>{item.title[0]}</AvatarFallback>
</Avatar>
)
}
export function NotionPromptForm() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const SAMPLE_DATA = useMemo(
() => ({
mentionable: [
{ type: "page", title: t.meetingNotes, image: "📝" },
{ type: "page", title: t.projectDashboard, image: "📊" },
{ type: "page", title: t.ideasBrainstorming, image: "💡" },
{ type: "page", title: t.calendarEvents, image: "📅" },
{ type: "page", title: t.documentation, image: "📚" },
{ type: "page", title: t.goalsObjectives, image: "🎯" },
{ type: "page", title: t.budgetPlanning, image: "💰" },
{ type: "page", title: t.teamDirectory, image: "👥" },
{ type: "page", title: t.technicalSpecs, image: "🔧" },
{ type: "page", title: t.analyticsReport, image: "📈" },
{
type: "user",
title: "shadcn",
image: "https://github.com/shadcn.png",
workspace: t.workspace,
},
{
type: "user",
title: "maxleiter",
image: "https://github.com/maxleiter.png",
workspace: t.workspace,
},
{
type: "user",
title: "evilrabbit",
image: "https://github.com/evilrabbit.png",
workspace: t.workspace,
},
],
models: [
{ name: t.auto },
{ name: t.agentMode, badge: t.beta },
{ name: t.planMode },
],
}),
[t]
)
const [mentions, setMentions] = useState<string[]>([])
const [mentionPopoverOpen, setMentionPopoverOpen] = useState(false)
const [modelPopoverOpen, setModelPopoverOpen] = useState(false)
const [selectedModel, setSelectedModel] = useState<
(typeof SAMPLE_DATA.models)[0]
>(SAMPLE_DATA.models[0])
const [scopeMenuOpen, setScopeMenuOpen] = useState(false)
const grouped = useMemo(() => {
return SAMPLE_DATA.mentionable.reduce(
(acc, item) => {
const isAvailable = !mentions.includes(item.title)
if (isAvailable) {
if (!acc[item.type]) {
acc[item.type] = []
}
acc[item.type].push(item)
}
return acc
},
{} as Record<string, typeof SAMPLE_DATA.mentionable>
)
}, [mentions, SAMPLE_DATA])
const hasMentions = mentions.length > 0
return (
<div dir={t.dir}>
<form>
<Field>
<FieldLabel htmlFor="rtl-notion-prompt" className="sr-only">
{t.prompt}
</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="rtl-notion-prompt"
placeholder={t.placeholder}
/>
<InputGroupAddon align="block-start">
<Popover
open={mentionPopoverOpen}
onOpenChange={setMentionPopoverOpen}
>
<Tooltip>
<TooltipTrigger
render={
<InputGroupButton
variant="outline"
size={!hasMentions ? "sm" : "icon-sm"}
className="rounded-full transition-transform"
/>
}
onFocusCapture={(e) => e.stopPropagation()}
>
<IconAt /> {!hasMentions && t.addContext}
</TooltipTrigger>
<TooltipContent>{t.mentionTooltip}</TooltipContent>
</Tooltip>
<PopoverContent className="p-0" align="start" dir={t.dir}>
<Command>
<CommandInput placeholder={t.searchPages} />
<CommandList>
<CommandEmpty>{t.noPagesFound}</CommandEmpty>
{Object.entries(grouped).map(([type, items]) => (
<CommandGroup
key={type}
heading={type === "page" ? t.pages : t.users}
>
{items.map((item) => (
<CommandItem
key={item.title}
value={item.title}
onSelect={(currentValue) => {
setMentions((prev) => [...prev, currentValue])
setMentionPopoverOpen(false)
}}
>
<MentionableIcon item={item} />
{item.title}
</CommandItem>
))}
</CommandGroup>
))}
</CommandList>
</Command>
</PopoverContent>
</Popover>
<div className="no-scrollbar -m-1.5 flex gap-1 overflow-y-auto p-1.5">
{mentions.map((mention) => {
const item = SAMPLE_DATA.mentionable.find(
(item) => item.title === mention
)
if (!item) {
return null
}
return (
<InputGroupButton
key={mention}
size="sm"
variant="secondary"
className="rounded-full !pr-2"
onClick={() => {
setMentions((prev) => prev.filter((m) => m !== mention))
}}
>
<MentionableIcon item={item} />
{item.title}
<IconX />
</InputGroupButton>
)
})}
</div>
</InputGroupAddon>
<InputGroupAddon align="block-end" className="gap-1">
<Tooltip>
<TooltipTrigger
render={
<InputGroupButton
size="icon-sm"
className="rounded-full"
aria-label={t.attachFile}
/>
}
>
<IconPaperclip />
</TooltipTrigger>
<TooltipContent>{t.attachFile}</TooltipContent>
</Tooltip>
<DropdownMenu
open={modelPopoverOpen}
onOpenChange={setModelPopoverOpen}
>
<Tooltip>
<TooltipTrigger
render={
<InputGroupButton size="sm" className="rounded-full" />
}
>
{selectedModel.name}
</TooltipTrigger>
<TooltipContent>{t.selectModel}</TooltipContent>
</Tooltip>
<DropdownMenuContent
side="top"
align="start"
className="w-48"
dir={t.dir}
>
<DropdownMenuGroup className="w-48">
<DropdownMenuLabel className="text-muted-foreground text-xs">
{t.selectAgentMode}
</DropdownMenuLabel>
{SAMPLE_DATA.models.map((model) => (
<DropdownMenuCheckboxItem
key={model.name}
checked={model.name === selectedModel.name}
onCheckedChange={(checked) => {
if (checked) {
setSelectedModel(model)
}
}}
className="pr-2 *:[span:first-child]:right-auto *:[span:first-child]:left-2"
>
{model.name}
{model.badge && (
<Badge
variant="secondary"
className="h-5 rounded-sm bg-blue-100 px-1 text-xs text-blue-800 dark:bg-blue-900 dark:text-blue-100"
>
{model.badge}
</Badge>
)}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu
open={scopeMenuOpen}
onOpenChange={setScopeMenuOpen}
>
<DropdownMenuTrigger
render={
<InputGroupButton size="sm" className="rounded-full" />
}
>
<IconWorld /> {t.allSources}
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="end"
className="w-72"
dir={t.dir}
>
<DropdownMenuGroup>
<DropdownMenuItem
render={
<label htmlFor="rtl-web-search">
<IconWorld /> {t.webSearch}{" "}
<Switch
id="rtl-web-search"
className="ms-auto"
defaultChecked
size="sm"
/>
</label>
}
onSelect={(e) => e.preventDefault()}
></DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem
render={
<label htmlFor="rtl-apps">
<IconApps /> {t.appsIntegrations}
<Switch
id="rtl-apps"
className="ms-auto"
defaultChecked
size="sm"
/>
</label>
}
onSelect={(e) => e.preventDefault()}
></DropdownMenuItem>
<DropdownMenuItem>
<IconCircleDashedPlus /> {t.allSourcesAccess}
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Avatar className="size-4">
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
shadcn
</DropdownMenuSubTrigger>
<DropdownMenuSubContent
className="w-72 rounded-lg p-0"
dir={t.dir}
side="left"
>
<Command>
<CommandInput
placeholder={t.findKnowledge}
autoFocus
/>
<CommandList>
<CommandEmpty>{t.noKnowledgeFound}</CommandEmpty>
<CommandGroup>
{SAMPLE_DATA.mentionable
.filter((item) => item.type === "user")
.map((user) => (
<CommandItem
key={user.title}
value={user.title}
onSelect={() => {
console.log("Selected user:", user.title)
}}
>
<Avatar className="size-4">
<AvatarImage src={user.image} />
<AvatarFallback>
{user.title[0]}
</AvatarFallback>
</Avatar>
{user.title}{" "}
<span className="text-muted-foreground">
-{" "}
{
(user as { workspace?: string })
.workspace
}
</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuItem>
<IconBook /> {t.helpCenter}
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlus /> {t.connectApps}
</DropdownMenuItem>
<DropdownMenuLabel className="text-muted-foreground text-xs">
{t.searchSourcesNote}
</DropdownMenuLabel>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<InputGroupButton
aria-label={t.send}
className="ms-auto rounded-full"
variant="default"
size="icon-sm"
>
<IconArrowUp />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
</form>
</div>
)
}

View File

@@ -1,44 +0,0 @@
"use client"
import { Badge } from "@/examples/base/ui-rtl/badge"
import { Spinner } from "@/examples/base/ui-rtl/spinner"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
syncing: "جارٍ المزامنة",
updating: "جارٍ التحديث",
loading: "جارٍ التحميل",
},
he: {
dir: "rtl" as const,
syncing: "מסנכרן",
updating: "מעדכן",
loading: "טוען",
},
}
export function SpinnerBadge() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
return (
<div dir={t.dir} className="flex items-center gap-2">
<Badge>
<Spinner />
{t.syncing}
</Badge>
<Badge variant="secondary">
<Spinner />
{t.updating}
</Badge>
<Badge variant="outline">
<Spinner />
{t.loading}
</Badge>
</div>
)
}

View File

@@ -1,52 +0,0 @@
"use client"
import { Button } from "@/examples/base/ui-rtl/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/examples/base/ui-rtl/empty"
import { Spinner } from "@/examples/base/ui-rtl/spinner"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
title: "جارٍ معالجة طلبك",
description: "يرجى الانتظار بينما نعالج طلبك. لا تقم بتحديث الصفحة.",
cancel: "إلغاء",
},
he: {
dir: "rtl" as const,
title: "מעבד את הבקשה שלך",
description: "אנא המתן בזמן שאנו מעבדים את בקשתך. אל תרענן את הדף.",
cancel: "ביטול",
},
}
export function SpinnerEmpty() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
return (
<Empty className="w-full border md:p-6" dir={t.dir}>
<EmptyHeader>
<EmptyMedia variant="icon">
<Spinner />
</EmptyMedia>
<EmptyTitle>{t.title}</EmptyTitle>
<EmptyDescription>{t.description}</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="outline" size="sm">
{t.cancel}
</Button>
</EmptyContent>
</Empty>
)
}

View File

@@ -1,14 +0,0 @@
import { type Metadata } from "next"
import { RtlComponents } from "./components"
export const metadata: Metadata = {
title: "RTL",
description: "RTL example page with right-to-left language support.",
}
export function RtlPage() {
return <RtlComponents />
}
export default RtlPage

View File

@@ -1,5 +1,6 @@
"use client"
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"
import { type Table } from "@tanstack/react-table"
import { Settings2 } from "lucide-react"
@@ -10,7 +11,6 @@ import {
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
export function DataTableViewOptions<TData>({

View File

@@ -59,7 +59,7 @@ export function FontPicker({
anchor={isMobile ? anchorRef : undefined}
side={isMobile ? "top" : "right"}
align={isMobile ? "center" : "start"}
className="max-h-96 md:w-72"
className="max-h-80 md:w-72"
>
<PickerRadioGroup
value={currentFont?.value}

View File

@@ -79,7 +79,7 @@ export function ItemExplorer({
)}
<SidebarMenuButton
onClick={() => setParams({ item: item.name })}
className="data-[active=true]:bg-accent data-[active=true]:border-accent 3xl:fixed:w-full 3xl:fixed:max-w-48 relative h-[26px] w-fit cursor-pointer overflow-visible border border-transparent text-[0.8rem] font-normal after:absolute after:inset-x-0 after:-inset-y-1 after:z-0 after:rounded-md"
className="data-[active=true]:bg-accent data-[active=true]:border-accent 3xl:fixed:w-full 3xl:fixed:max-w-48 relative h-[26px] w-fit cursor-pointer overflow-visible border border-transparent text-[0.8rem] font-normal after:absolute after:inset-x-0 after:-inset-y-1 after:-z-0 after:rounded-md"
data-active={item.name === currentItem?.name}
isActive={item.name === currentItem?.name}
>

View File

@@ -80,7 +80,7 @@ function PickerLabel({
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn(
"text-muted-foreground px-2 py-1.5 text-xs font-medium data-inset:pl-8",
"text-muted-foreground px-2 py-1.5 text-xs font-medium data-[inset]:pl-8",
className
)}
{...props}
@@ -103,7 +103,7 @@ function PickerItem({
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 pointer-coarse:py-2.5 pointer-coarse:pl-3 pointer-coarse:text-base [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 pointer-coarse:py-2.5 pointer-coarse:pl-3 pointer-coarse:text-base [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
@@ -128,7 +128,7 @@ function PickerSubTrigger({
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
@@ -180,7 +180,7 @@ function PickerCheckboxItem({
<MenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
checked={checked}
@@ -220,7 +220,7 @@ function PickerRadioItem({
<MenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-lg py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 pointer-coarse:gap-3 pointer-coarse:py-2.5 pointer-coarse:pl-3 pointer-coarse:text-base [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-lg py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 pointer-coarse:gap-3 pointer-coarse:py-2.5 pointer-coarse:pl-3 pointer-coarse:text-base [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}

View File

@@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import { type PanelImperativeHandle } from "react-resizable-panels"
import { type ImperativePanelHandle } from "react-resizable-panels"
import { DARK_MODE_FORWARD_TYPE } from "@/components/mode-switcher"
import { Badge } from "@/registry/new-york-v4/ui/badge"
@@ -16,7 +16,7 @@ import {
export function Preview() {
const [params] = useDesignSystemSearchParams()
const iframeRef = React.useRef<HTMLIFrameElement>(null)
const resizablePanelRef = React.useRef<PanelImperativeHandle>(null)
const resizablePanelRef = React.useRef<ImperativePanelHandle>(null)
// Sync resizable panel with URL param changes.
React.useEffect(() => {

View File

@@ -76,7 +76,7 @@ export function ThemePicker({
anchor={isMobile ? anchorRef : undefined}
side={isMobile ? "top" : "right"}
align={isMobile ? "center" : "start"}
className="max-h-[23rem]"
className="max-h-96"
>
<PickerRadioGroup
value={currentTheme?.name}

View File

@@ -1,7 +1,6 @@
"use client"
import * as React from "react"
import Link from "next/link"
import {
ComputerTerminal01Icon,
Copy01Icon,
@@ -24,8 +23,6 @@ import {
} from "@/registry/new-york-v4/ui/dialog"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldTitle,
@@ -34,7 +31,6 @@ import {
RadioGroup,
RadioGroupItem,
} from "@/registry/new-york-v4/ui/radio-group"
import { Switch } from "@/registry/new-york-v4/ui/switch"
import {
Tabs,
TabsContent,
@@ -75,15 +71,14 @@ export function ToolbarControls() {
const packageManager = config.packageManager || "pnpm"
const commands = React.useMemo(() => {
const origin = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:4000"
const url = `${origin}/init?base=${params.base}&style=${params.style}&baseColor=${params.baseColor}&theme=${params.theme}&iconLibrary=${params.iconLibrary}&font=${params.font}&menuAccent=${params.menuAccent}&menuColor=${params.menuColor}&radius=${params.radius}&template=${params.template}&rtl=${params.rtl}`
const origin = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"
const url = `${origin}/init?base=${params.base}&style=${params.style}&baseColor=${params.baseColor}&theme=${params.theme}&iconLibrary=${params.iconLibrary}&font=${params.font}&menuAccent=${params.menuAccent}&menuColor=${params.menuColor}&radius=${params.radius}&template=${params.template}`
const templateFlag = params.template ? ` --template ${params.template}` : ""
const rtlFlag = params.rtl ? " --rtl" : ""
return {
pnpm: `pnpm dlx shadcn@latest create${rtlFlag} --preset "${url}"${templateFlag}`,
npm: `npx shadcn@latest create${rtlFlag} --preset "${url}"${templateFlag}`,
yarn: `yarn dlx shadcn@latest create${rtlFlag} --preset "${url}"${templateFlag}`,
bun: `bunx --bun shadcn@latest create${rtlFlag} --preset "${url}"${templateFlag}`,
pnpm: `pnpm dlx shadcn@latest create --preset "${url}"${templateFlag}`,
npm: `npx shadcn@latest create --preset "${url}"${templateFlag}`,
yarn: `yarn dlx shadcn@latest create --preset "${url}"${templateFlag}`,
bun: `bunx --bun shadcn@latest create --preset "${url}"${templateFlag}`,
}
}, [
params.base,
@@ -96,7 +91,6 @@ export function ToolbarControls() {
params.menuColor,
params.radius,
params.template,
params.rtl,
])
const command = commands[packageManager]
@@ -170,7 +164,7 @@ export function ToolbarControls() {
{selectedTemplate?.title} + shadcn/ui project.
</DialogDescription>
</DialogHeader>
<FieldGroup className="gap-3">
<FieldGroup>
<Field>
<FieldLabel htmlFor="template" className="sr-only">
Template
@@ -189,7 +183,7 @@ export function ToolbarControls() {
<FieldLabel
key={template.value}
htmlFor={template.value}
className="has-data-[state=checked]:border-primary/10 rounded-lg!"
className="rounded-lg!"
>
<Field className="flex min-w-0 flex-col items-center justify-center gap-2 p-3! text-center *:w-auto!">
<RadioGroupItem
@@ -211,81 +205,59 @@ export function ToolbarControls() {
))}
</RadioGroup>
</Field>
<FieldLabel className="has-data-[state=checked]:border-primary/10 rounded-lg!">
<Field orientation="horizontal">
<FieldContent className="gap-1">
<FieldTitle>Enable RTL</FieldTitle>
<FieldDescription>
<a
href={`/docs/rtl/${params.template}`}
className="text-foreground underline"
target="_blank"
rel="noopener noreferrer"
>
View the RTL setup guide for {selectedTemplate?.title}.
</a>
</FieldDescription>
</FieldContent>
<Switch
checked={params.rtl}
onCheckedChange={(rtl) => setParams({ rtl })}
className="shadow-none"
/>
</Field>
</FieldLabel>
<Tabs
value={packageManager}
onValueChange={(value) => {
setConfig({
...config,
packageManager: value as "pnpm" | "npm" | "yarn" | "bun",
})
}}
className="bg-surface min-w-0 gap-0 overflow-hidden rounded-lg border"
>
<div className="flex items-center gap-2 p-2">
<TabsList className="*:data-[slot=tabs-trigger]:data-[state=active]:border-input h-auto rounded-none bg-transparent p-0 font-mono group-data-[orientation=horizontal]/tabs:h-8 *:data-[slot=tabs-trigger]:h-7 *:data-[slot=tabs-trigger]:border *:data-[slot=tabs-trigger]:border-transparent *:data-[slot=tabs-trigger]:pt-0.5 *:data-[slot=tabs-trigger]:shadow-none!">
<TabsTrigger value="pnpm">pnpm</TabsTrigger>
<TabsTrigger value="npm">npm</TabsTrigger>
<TabsTrigger value="yarn">yarn</TabsTrigger>
<TabsTrigger value="bun">bun</TabsTrigger>
</TabsList>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon-sm"
variant="ghost"
className="ml-auto size-7 rounded-lg"
onClick={handleCopyFromTabs}
>
{hasCopied ? (
<HugeiconsIcon icon={Tick02Icon} className="size-4" />
) : (
<HugeiconsIcon icon={Copy01Icon} className="size-4" />
)}
<span className="sr-only">Copy command</span>
</Button>
</TooltipTrigger>
<TooltipContent>
{hasCopied ? "Copied!" : "Copy command"}
</TooltipContent>
</Tooltip>
</div>
{Object.entries(commands).map(([key, cmd]) => {
return (
<TabsContent key={key} value={key}>
<div className="bg-surface border-border/50 text-surface-foreground relative overflow-hidden border-t px-3 py-3">
<div className="no-scrollbar overflow-x-auto">
<code className="font-mono text-sm whitespace-nowrap">
{cmd}
</code>
</div>
</div>
</TabsContent>
)
})}
</Tabs>
</FieldGroup>
<Tabs
value={packageManager}
onValueChange={(value) => {
setConfig({
...config,
packageManager: value as "pnpm" | "npm" | "yarn" | "bun",
})
}}
className="bg-surface min-w-0 gap-0 overflow-hidden rounded-lg border"
>
<div className="flex items-center gap-2 p-2">
<TabsList className="*:data-[slot=tabs-trigger]:data-[state=active]:border-input h-auto rounded-none bg-transparent p-0 font-mono *:data-[slot=tabs-trigger]:border *:data-[slot=tabs-trigger]:border-transparent *:data-[slot=tabs-trigger]:pt-0.5 *:data-[slot=tabs-trigger]:shadow-none!">
<TabsTrigger value="pnpm">pnpm</TabsTrigger>
<TabsTrigger value="npm">npm</TabsTrigger>
<TabsTrigger value="yarn">yarn</TabsTrigger>
<TabsTrigger value="bun">bun</TabsTrigger>
</TabsList>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon-sm"
variant="ghost"
className="ml-auto size-7 rounded-lg"
onClick={handleCopyFromTabs}
>
{hasCopied ? (
<HugeiconsIcon icon={Tick02Icon} className="size-4" />
) : (
<HugeiconsIcon icon={Copy01Icon} className="size-4" />
)}
<span className="sr-only">Copy command</span>
</Button>
</TooltipTrigger>
<TooltipContent>
{hasCopied ? "Copied!" : "Copy command"}
</TooltipContent>
</Tooltip>
</div>
{Object.entries(commands).map(([key, cmd]) => {
return (
<TabsContent key={key} value={key}>
<div className="bg-surface border-border/50 text-surface-foreground relative overflow-hidden border-t px-3 py-3">
<div className="no-scrollbar overflow-x-auto">
<code className="font-mono text-sm whitespace-nowrap">
{cmd}
</code>
</div>
</div>
</TabsContent>
)
})}
</Tabs>
<DialogFooter className="bg-muted/50 -mx-6 mt-2 -mb-6 flex flex-col gap-2 border-t p-6 sm:flex-col">
<Button
size="sm"

View File

@@ -78,7 +78,6 @@ export default async function CreatePage({
title: item.title,
type: item.type,
}))
.filter((item) => !/\d+$/.test(item.name))
return (
<div
@@ -87,8 +86,8 @@ export default async function CreatePage({
>
<header className="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 **:data-[slot=separator]:h-4!">
<div className="3xl:fixed:container flex h-(--header-height) items-center **:data-[slot=separator]:h-4!">
<div className="3xl:fixed:container flex h-(--header-height) items-center **: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

@@ -45,6 +45,18 @@ export async function GET(request: NextRequest) {
}
const designSystemConfig = parseResult.data
const registryBase = buildRegistryBase(designSystemConfig)
const validateResult = registryItemSchema.safeParse(registryBase)
if (!validateResult.success) {
return NextResponse.json(
{
error: "Invalid registry base item",
details: validateResult.error.format(),
},
{ status: 500 }
)
}
track("create_open_in_v0", designSystemConfig)
@@ -63,23 +75,28 @@ export async function GET(request: NextRequest) {
}
async function buildV0Payload(designSystemConfig: DesignSystemConfig) {
const registryBase = buildRegistryBase(designSystemConfig)
const files: z.infer<typeof registryItemFileSchema>[] = []
// Build all files in parallel.
const [globalsCss, layoutFile, componentFiles] = await Promise.all([
buildGlobalsCss(registryBase),
buildLayoutFile(designSystemConfig),
buildComponentFiles(designSystemConfig),
])
// Build globals.css file.
files.push(buildGlobalsCss(designSystemConfig))
// Build layout.tsx file.
files.push(buildLayoutFile(designSystemConfig))
// Build component files.
const componentFiles = await buildComponentFiles(designSystemConfig)
files.push(...componentFiles)
return registryItemSchema.parse({
name: designSystemConfig.item ?? "Item",
type: "registry:item",
files: [globalsCss, layoutFile, ...componentFiles],
files,
})
}
function buildGlobalsCss(registryBase: RegistryItem) {
function buildGlobalsCss(designSystemConfig: DesignSystemConfig) {
const registryBase = buildRegistryBase(designSystemConfig)
const lightVars = Object.entries(registryBase.cssVars?.light ?? {})
.map(([key, value]) => ` --${key}: ${value};`)
.join("\n")

View File

@@ -19,7 +19,6 @@ export async function GET(request: NextRequest) {
menuColor: searchParams.get("menuColor"),
radius: searchParams.get("radius"),
template: searchParams.get("template"),
rtl: searchParams.get("rtl") === "true",
})
if (!result.success) {

View File

@@ -38,15 +38,15 @@ const jetbrainsMono = JetBrains_Mono({
variable: "--font-jetbrains-mono",
})
const geistSans = Geist({
subsets: ["latin"],
variable: "--font-geist-sans",
})
// const geistSans = Geist({
// subsets: ["latin"],
// variable: "--font-geist-sans",
// })
const geistMono = Geist_Mono({
subsets: ["latin"],
variable: "--font-geist-mono",
})
// const geistMono = Geist_Mono({
// subsets: ["latin"],
// variable: "--font-geist-mono",
// })
const roboto = Roboto({
subsets: ["latin"],
@@ -74,12 +74,12 @@ const outfit = Outfit({
})
export const FONTS = [
{
name: "Geist",
value: "geist",
font: geistSans,
type: "sans",
},
// {
// name: "Geist Sans",
// value: "geist",
// font: geistSans,
// type: "sans",
// },
{
name: "Inter",
value: "inter",
@@ -134,18 +134,18 @@ export const FONTS = [
font: outfit,
type: "sans",
},
{
name: "Geist Mono",
value: "geist-mono",
font: geistMono,
type: "mono",
},
{
name: "JetBrains Mono",
value: "jetbrains-mono",
font: jetbrainsMono,
type: "mono",
},
// {
// name: "Geist Mono",
// value: "geist-mono",
// font: geistMono,
// type: "mono",
// },
] as const
export type Font = (typeof FONTS)[number]

View File

@@ -66,7 +66,6 @@ const designSystemSearchParams = {
"start",
"vite",
] as const).withDefault("next"),
rtl: parseAsBoolean.withDefault(false),
size: parseAsInteger.withDefault(100),
custom: parseAsBoolean.withDefault(false),
}

View File

@@ -1,5 +1,3 @@
import { TrashIcon } from "lucide-react"
import {
AlertDialog,
AlertDialogAction,
@@ -8,7 +6,6 @@ import {
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/registry/new-york-v4/ui/alert-dialog"
@@ -16,66 +13,23 @@ import { Button } from "@/registry/new-york-v4/ui/button"
export function AlertDialogDemo() {
return (
<div className="flex flex-wrap gap-4">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">Default</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">With Media</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogMedia>
<TrashIcon className="size-8" />
</AlertDialogMedia>
<AlertDialogTitle>Delete this item?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the
item from your account.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive">Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">Small Size</Button>
</AlertDialogTrigger>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogMedia>
<TrashIcon className="size-8" />
</AlertDialogMedia>
<AlertDialogTitle>Delete this item?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive">Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">Show Dialog</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}

View File

@@ -1,173 +1,89 @@
import { PlusIcon } from "lucide-react"
import {
Avatar,
AvatarBadge,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
export function AvatarDemo() {
return (
<div className="flex flex-col gap-6">
{/* Sizes. */}
<div className="flex flex-row flex-wrap items-center gap-4">
<Avatar size="sm">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<div className="flex flex-row flex-wrap items-center gap-4">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar className="size-12">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar className="rounded-lg">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</div>
{/* Fallback. */}
<div className="flex flex-row flex-wrap items-center gap-4">
<Avatar size="sm">
<AvatarFallback>CN</AvatarFallback>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarFallback>CN</AvatarFallback>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</div>
{/* With badge. */}
<div className="flex flex-row flex-wrap items-center gap-4">
<Avatar size="sm">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
<AvatarBadge />
</Avatar>
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
<AvatarBadge />
</Avatar>
<Avatar size="lg">
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</div>
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 hover:space-x-1 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale *:data-[slot=avatar]:transition-all *:data-[slot=avatar]:duration-300 *:data-[slot=avatar]:ease-in-out">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
<AvatarBadge>
<PlusIcon />
</AvatarBadge>
</Avatar>
</div>
{/* Avatar group. */}
<div className="flex flex-row flex-wrap items-center gap-4">
<AvatarGroup>
<Avatar size="sm">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="sm">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>ML</AvatarFallback>
</Avatar>
<Avatar size="sm">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>ML</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
<AvatarGroup>
<Avatar size="lg">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>ML</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
</div>
{/* Avatar group with count. */}
<div className="flex flex-row flex-wrap items-center gap-4">
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>ML</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<AvatarGroupCount>+3</AvatarGroupCount>
</AvatarGroup>
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>ML</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<AvatarGroupCount>
<PlusIcon />
</AvatarGroupCount>
</AvatarGroup>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</div>
</div>
)

View File

@@ -10,10 +10,6 @@ export function BadgeDemo() {
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>
<Badge variant="ghost">Ghost</Badge>
<Badge variant="link">Link</Badge>
</div>
<div className="flex w-full flex-wrap gap-2">
<Badge variant="outline">
<CheckIcon />
Badge
@@ -59,16 +55,6 @@ export function BadgeDemo() {
Link <ArrowRightIcon />
</a>
</Badge>
<Badge asChild variant="ghost">
<a href="#">
Link <ArrowRightIcon />
</a>
</Badge>
<Badge asChild variant="link">
<a href="#">
Link <ArrowRightIcon />
</a>
</Badge>
</div>
</div>
)

View File

@@ -1,4 +1,4 @@
import { ArrowRightIcon, Loader2Icon, PlusIcon, SendIcon } from "lucide-react"
import { ArrowRightIcon, Loader2Icon, SendIcon } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
@@ -6,25 +6,22 @@ export function ButtonDemo() {
return (
<div className="flex flex-col gap-6">
<div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button size="xs">Extra Small</Button>
<Button variant="outline" size="xs">
Outline
</Button>
<Button variant="ghost" size="xs">
Ghost
</Button>
<Button variant="destructive" size="xs">
Destructive
</Button>
<Button variant="secondary" size="xs">
Secondary
</Button>
<Button variant="link" size="xs">
Link
</Button>
<Button variant="outline" size="xs">
<Button>Button</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="link">Link</Button>
<Button variant="outline">
<SendIcon /> Send
</Button>
<Button variant="outline">
Learn More <ArrowRightIcon />
</Button>
<Button disabled variant="outline">
<Loader2Icon className="animate-spin" />
Please wait
</Button>
</div>
<div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button size="sm">Small</Button>
@@ -46,21 +43,10 @@ export function ButtonDemo() {
<Button variant="outline" size="sm">
<SendIcon /> Send
</Button>
</div>
<div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button>Button</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="link">Link</Button>
<Button variant="outline">
<SendIcon /> Send
</Button>
<Button variant="outline">
<Button variant="outline" size="sm">
Learn More <ArrowRightIcon />
</Button>
<Button disabled variant="outline">
<Button disabled size="sm" variant="outline">
<Loader2Icon className="animate-spin" />
Please wait
</Button>
@@ -85,19 +71,12 @@ export function ButtonDemo() {
<Button variant="outline" size="lg">
<SendIcon /> Send
</Button>
</div>
<div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button size="icon-xs" variant="outline">
<PlusIcon />
<Button variant="outline" size="lg">
Learn More <ArrowRightIcon />
</Button>
<Button size="icon-sm" variant="outline">
<PlusIcon />
</Button>
<Button size="icon" variant="outline">
<PlusIcon />
</Button>
<Button size="icon-lg" variant="outline">
<PlusIcon />
<Button disabled size="lg" variant="outline">
<Loader2Icon className="animate-spin" />
Please wait
</Button>
</div>
</div>

View File

@@ -1,183 +1,405 @@
"use client"
import * as React from "react"
import {
CheckIcon,
ChevronDownIcon,
ChevronsUpDown,
PlusCircleIcon,
} from "lucide-react"
import { cn } from "@/lib/utils"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Combobox,
ComboboxChip,
ComboboxChips,
ComboboxChipsInput,
ComboboxCollection,
ComboboxContent,
ComboboxEmpty,
ComboboxGroup,
ComboboxInput,
ComboboxItem,
ComboboxLabel,
ComboboxList,
ComboboxTrigger,
ComboboxValue,
useComboboxAnchor,
} from "@/registry/new-york-v4/ui/combobox"
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/registry/new-york-v4/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
const frameworks = [
"Next.js",
"SvelteKit",
"Nuxt.js",
"Remix",
"Astro",
{
value: "next.js",
label: "Next.js",
},
{
value: "sveltekit",
label: "SvelteKit",
},
{
value: "nuxt.js",
label: "Nuxt.js",
},
{
value: "remix",
label: "Remix",
},
{
value: "astro",
label: "Astro",
},
]
type Framework = (typeof frameworks)[number]
const users = [
{
id: "1",
username: "shadcn",
},
{
id: "2",
username: "maxleiter",
},
{
id: "3",
username: "evilrabbit",
},
] as const
type User = (typeof users)[number]
const timezones = [
{
value: "Americas",
items: ["(GMT-5) New York", "(GMT-8) Los Angeles", "(GMT-6) Chicago"],
label: "Americas",
timezones: [
{ value: "America/New_York", label: "(GMT-5) New York" },
{ value: "America/Los_Angeles", label: "(GMT-8) Los Angeles" },
{ value: "America/Chicago", label: "(GMT-6) Chicago" },
{ value: "America/Toronto", label: "(GMT-5) Toronto" },
{ value: "America/Vancouver", label: "(GMT-8) Vancouver" },
{ value: "America/Sao_Paulo", label: "(GMT-3) São Paulo" },
],
},
{
value: "Europe",
items: ["(GMT+0) London", "(GMT+1) Paris", "(GMT+1) Berlin"],
label: "Europe",
timezones: [
{ value: "Europe/London", label: "(GMT+0) London" },
{ value: "Europe/Paris", label: "(GMT+1) Paris" },
{ value: "Europe/Berlin", label: "(GMT+1) Berlin" },
{ value: "Europe/Rome", label: "(GMT+1) Rome" },
{ value: "Europe/Madrid", label: "(GMT+1) Madrid" },
{ value: "Europe/Amsterdam", label: "(GMT+1) Amsterdam" },
],
},
{
value: "Asia/Pacific",
items: ["(GMT+9) Tokyo", "(GMT+8) Shanghai", "(GMT+8) Singapore"],
label: "Asia/Pacific",
timezones: [
{ value: "Asia/Tokyo", label: "(GMT+9) Tokyo" },
{ value: "Asia/Shanghai", label: "(GMT+8) Shanghai" },
{ value: "Asia/Singapore", label: "(GMT+8) Singapore" },
{ value: "Asia/Dubai", label: "(GMT+4) Dubai" },
{ value: "Australia/Sydney", label: "(GMT+11) Sydney" },
{ value: "Asia/Seoul", label: "(GMT+9) Seoul" },
],
},
] as const
const countries = [
{ code: "", value: "", label: "Select country" },
{ code: "us", value: "united-states", label: "United States" },
{ code: "ca", value: "canada", label: "Canada" },
{ code: "gb", value: "united-kingdom", label: "United Kingdom" },
{ code: "de", value: "germany", label: "Germany" },
{ code: "fr", value: "france", label: "France" },
{ code: "jp", value: "japan", label: "Japan" },
]
type Timezone = (typeof timezones)[number]
export function ComboboxDemo() {
return (
<div className="flex w-full flex-col gap-6">
{/* Basic combobox. */}
<div className="flex flex-wrap items-start gap-4">
<Combobox items={frameworks}>
<ComboboxInput placeholder="Select a framework" />
<ComboboxContent>
<ComboboxEmpty>No items found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
{/* With clear button. */}
<div className="flex flex-wrap items-start gap-4">
<Combobox items={frameworks} defaultValue={frameworks[0]}>
<ComboboxInput placeholder="Select a framework" showClear />
<ComboboxContent>
<ComboboxEmpty>No items found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
{/* With groups. */}
<div className="flex flex-wrap items-start gap-4">
<Combobox items={timezones}>
<ComboboxInput placeholder="Select a timezone" />
<ComboboxContent>
<ComboboxEmpty>No timezones found.</ComboboxEmpty>
<ComboboxList>
{(group) => (
<ComboboxGroup key={group.value} items={group.items}>
<ComboboxLabel>{group.value}</ComboboxLabel>
<ComboboxCollection>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxCollection>
</ComboboxGroup>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
{/* With trigger button. */}
<div className="flex flex-wrap items-start gap-4">
<Combobox items={countries} defaultValue={countries[0]}>
<ComboboxTrigger
render={
<Button
variant="outline"
className="w-64 justify-between font-normal"
/>
}
>
<ComboboxValue />
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput showTrigger={false} placeholder="Search" />
<ComboboxEmpty>No items found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item.code} value={item}>
{item.label}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
{/* Multiple selection with chips. */}
<ComboboxMultiple />
<div className="flex w-full flex-wrap items-start gap-4">
<FrameworkCombobox frameworks={[...frameworks]} />
<UserCombobox users={[...users]} selectedUserId={users[0].id} />
<TimezoneCombobox
timezones={[...timezones]}
selectedTimezone={timezones[0].timezones[0]}
/>
<ComboboxWithCheckbox frameworks={[...frameworks]} />
</div>
)
}
function ComboboxMultiple() {
const anchor = useComboboxAnchor()
function FrameworkCombobox({ frameworks }: { frameworks: Framework[] }) {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState("")
return (
<div className="flex flex-wrap items-start gap-4">
<Combobox
multiple
autoHighlight
items={frameworks}
defaultValue={[frameworks[0]]}
>
<ComboboxChips ref={anchor}>
<ComboboxValue>
{(values) => (
<React.Fragment>
{values.map((value: string) => (
<ComboboxChip key={value}>{value}</ComboboxChip>
))}
<ComboboxChipsInput placeholder="Add framework..." />
</React.Fragment>
)}
</ComboboxValue>
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxEmpty>No items found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between md:max-w-[200px]"
>
{value
? frameworks.find((framework) => framework.value === value)?.label
: "Select framework..."}
<ChevronsUpDown className="text-muted-foreground" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-(--radix-popover-trigger-width) p-0">
<Command>
<CommandInput placeholder="Search framework..." />
<CommandList>
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{frameworks.map((framework) => (
<CommandItem
key={framework.value}
value={framework.value}
onSelect={(currentValue) => {
setValue(currentValue === value ? "" : currentValue)
setOpen(false)
}}
>
{framework.label}
<CheckIcon
className={cn(
"ml-auto",
value === framework.value ? "opacity-100" : "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}
function UserCombobox({
users,
selectedUserId,
}: {
users: User[]
selectedUserId: string
}) {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState(selectedUserId)
const selectedUser = React.useMemo(
() => users.find((user) => user.id === value),
[value, users]
)
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between px-2 md:max-w-[200px]"
>
{selectedUser ? (
<div className="flex items-center gap-2">
<Avatar className="size-5">
<AvatarImage
src={`https://github.com/${selectedUser.username}.png`}
/>
<AvatarFallback>{selectedUser.username[0]}</AvatarFallback>
</Avatar>
{selectedUser.username}
</div>
) : (
"Select user..."
)}
<ChevronsUpDown className="text-muted-foreground" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-(--radix-popover-trigger-width) p-0">
<Command>
<CommandInput placeholder="Search user..." />
<CommandList>
<CommandEmpty>No user found.</CommandEmpty>
<CommandGroup>
{users.map((user) => (
<CommandItem
key={user.id}
value={user.id}
onSelect={(currentValue) => {
setValue(currentValue === value ? "" : currentValue)
setOpen(false)
}}
>
<Avatar className="size-5">
<AvatarImage
src={`https://github.com/${user.username}.png`}
/>
<AvatarFallback>{user.username[0]}</AvatarFallback>
</Avatar>
{user.username}
<CheckIcon
className={cn(
"ml-auto",
value === user.id ? "opacity-100" : "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
<CommandSeparator />
<CommandGroup>
<CommandItem>
<PlusCircleIcon />
Create user
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}
function TimezoneCombobox({
timezones,
selectedTimezone,
}: {
timezones: Timezone[]
selectedTimezone: Timezone["timezones"][number]
}) {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState(selectedTimezone.value)
const selectedGroup = React.useMemo(
() =>
timezones.find((group) =>
group.timezones.find((tz) => tz.value === value)
),
[value, timezones]
)
const selectedTimezoneLabel = React.useMemo(
() => selectedGroup?.timezones.find((tz) => tz.value === value)?.label,
[value, selectedGroup]
)
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className="h-12 w-full justify-between px-2.5 md:max-w-[200px]"
>
{selectedTimezone ? (
<div className="flex flex-col items-start gap-0.5">
<span className="text-muted-foreground text-xs font-normal">
{selectedGroup?.label}
</span>
<span>{selectedTimezoneLabel}</span>
</div>
) : (
"Select timezone"
)}
<ChevronDownIcon className="text-muted-foreground" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
<Command>
<CommandInput placeholder="Search timezone..." />
<CommandList className="scroll-pb-12">
<CommandEmpty>No timezone found.</CommandEmpty>
{timezones.map((region) => (
<CommandGroup key={region.label} heading={region.label}>
{region.timezones.map((timezone) => (
<CommandItem
key={timezone.value}
value={timezone.value}
onSelect={(currentValue) => {
setValue(
currentValue as Timezone["timezones"][number]["value"]
)
setOpen(false)
}}
>
{timezone.label}
<CheckIcon
className="ml-auto opacity-0 data-[selected=true]:opacity-100"
data-selected={value === timezone.value}
/>
</CommandItem>
))}
</CommandGroup>
))}
<CommandSeparator className="sticky bottom-10" />
<CommandGroup className="bg-popover sticky bottom-0">
<CommandItem>
<PlusCircleIcon />
Create timezone
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}
function ComboboxWithCheckbox({ frameworks }: { frameworks: Framework[] }) {
const [open, setOpen] = React.useState(false)
const [selectedFrameworks, setSelectedFrameworks] = React.useState<
Framework[]
>([])
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-fit min-w-[280px] justify-between"
>
{selectedFrameworks.length > 0
? selectedFrameworks.map((framework) => framework.label).join(", ")
: "Select frameworks (multi-select)..."}
<ChevronsUpDown className="text-muted-foreground" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0" align="start">
<Command>
<CommandInput placeholder="Search framework..." />
<CommandList>
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{frameworks.map((framework) => (
<CommandItem
key={framework.value}
value={framework.value}
onSelect={(currentValue) => {
setSelectedFrameworks(
selectedFrameworks.some((f) => f.value === currentValue)
? selectedFrameworks.filter(
(f) => f.value !== currentValue
)
: [...selectedFrameworks, framework]
)
}}
>
<div
className="border-input data-[selected=true]:border-primary data-[selected=true]:bg-primary data-[selected=true]:text-primary-foreground pointer-events-none size-4 shrink-0 rounded-[4px] border transition-all select-none *:[svg]:opacity-0 data-[selected=true]:*:[svg]:opacity-100"
data-selected={selectedFrameworks.some(
(f) => f.value === framework.value
)}
>
<CheckIcon className="size-3.5 text-current" />
</div>
{framework.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}

View File

@@ -4,64 +4,59 @@ import { Label } from "@/registry/new-york-v4/ui/label"
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover"
export function PopoverDemo() {
return (
<div className="flex gap-4">
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">Open popover</Button>
</PopoverTrigger>
<PopoverContent className="w-80" align="start">
<div className="grid gap-4">
<PopoverHeader>
<PopoverTitle>Dimensions</PopoverTitle>
<PopoverDescription>
Set the dimensions for the layer.
</PopoverDescription>
</PopoverHeader>
<div className="grid gap-2">
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="width">Width</Label>
<Input
id="width"
defaultValue="100%"
className="col-span-2 h-8"
/>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="maxWidth">Max. width</Label>
<Input
id="maxWidth"
defaultValue="300px"
className="col-span-2 h-8"
/>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="height">Height</Label>
<Input
id="height"
defaultValue="25px"
className="col-span-2 h-8"
/>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="maxHeight">Max. height</Label>
<Input
id="maxHeight"
defaultValue="none"
className="col-span-2 h-8"
/>
</div>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">Open popover</Button>
</PopoverTrigger>
<PopoverContent className="w-80" align="start">
<div className="grid gap-4">
<div className="grid gap-1.5">
<h4 className="leading-none font-medium">Dimensions</h4>
<p className="text-muted-foreground text-sm">
Set the dimensions for the layer.
</p>
</div>
<div className="grid gap-2">
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="width">Width</Label>
<Input
id="width"
defaultValue="100%"
className="col-span-2 h-8"
/>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="maxWidth">Max. width</Label>
<Input
id="maxWidth"
defaultValue="300px"
className="col-span-2 h-8"
/>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="height">Height</Label>
<Input
id="height"
defaultValue="25px"
className="col-span-2 h-8"
/>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="maxHeight">Max. height</Label>
<Input
id="maxHeight"
defaultValue="none"
className="col-span-2 h-8"
/>
</div>
</div>
</PopoverContent>
</Popover>
</div>
</div>
</PopoverContent>
</Popover>
)
}

View File

@@ -8,24 +8,24 @@ export function ResizableDemo() {
return (
<div className="flex w-full flex-col gap-6">
<ResizablePanelGroup
orientation="horizontal"
direction="horizontal"
className="max-w-md rounded-lg border md:min-w-[450px]"
>
<ResizablePanel defaultSize="50%">
<ResizablePanel defaultSize={50}>
<div className="flex h-[200px] items-center justify-center p-6">
<span className="font-semibold">One</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="50%">
<ResizablePanelGroup orientation="vertical">
<ResizablePanel defaultSize="25%">
<ResizablePanel defaultSize={50}>
<ResizablePanelGroup direction="vertical">
<ResizablePanel defaultSize={25}>
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Two</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="75%">
<ResizablePanel defaultSize={75}>
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Three</span>
</div>
@@ -34,32 +34,32 @@ export function ResizableDemo() {
</ResizablePanel>
</ResizablePanelGroup>
<ResizablePanelGroup
orientation="horizontal"
direction="horizontal"
className="min-h-[200px] max-w-md rounded-lg border md:min-w-[450px]"
>
<ResizablePanel defaultSize="25%">
<ResizablePanel defaultSize={25}>
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Sidebar</span>
</div>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize="75%">
<ResizablePanel defaultSize={75}>
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Content</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
<ResizablePanelGroup
orientation="vertical"
direction="vertical"
className="min-h-[200px] max-w-md rounded-lg border md:min-w-[450px]"
>
<ResizablePanel defaultSize="25%">
<ResizablePanel defaultSize={25}>
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Header</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="75%">
<ResizablePanel defaultSize={75}>
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Content</span>
</div>

View File

@@ -47,27 +47,6 @@ export function SheetDemo() {
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">No Close Button</Button>
</SheetTrigger>
<SheetContent showCloseButton={false}>
<SheetHeader>
<SheetTitle>Custom Close</SheetTitle>
<SheetDescription>
This sheet has no default close button. Use the footer buttons
instead.
</SheetDescription>
</SheetHeader>
<div className="flex-1 px-4" />
<SheetFooter>
<SheetClose asChild>
<Button variant="outline">Cancel</Button>
</SheetClose>
<Button type="submit">Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<div className="flex gap-2">
{SHEET_SIDES.map((side) => (
<Sheet key={side}>

View File

@@ -4,17 +4,6 @@ import { Switch } from "@/registry/new-york-v4/ui/switch"
export function SwitchDemo() {
return (
<div className="flex flex-col gap-6">
{/* Sizes. */}
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<Switch id="switch-demo-sm" size="sm" />
<Label htmlFor="switch-demo-sm">Small</Label>
</div>
<div className="flex items-center gap-2">
<Switch id="switch-demo-default" />
<Label htmlFor="switch-demo-default">Default</Label>
</div>
</div>
<div className="flex items-center gap-2">
<Switch id="switch-demo-airplane-mode" />
<Label htmlFor="switch-demo-airplane-mode">Airplane Mode</Label>

View File

@@ -101,45 +101,6 @@ export function TabsDemo() {
</TabsTrigger>
</TabsList>
</Tabs>
{/* Line variant. */}
<Tabs defaultValue="preview">
<TabsList variant="line">
<TabsTrigger value="preview">
<AppWindowIcon />
Preview
</TabsTrigger>
<TabsTrigger value="code">
<CodeIcon />
Code
</TabsTrigger>
</TabsList>
</Tabs>
{/* Vertical orientation. */}
<Tabs defaultValue="preview" orientation="vertical">
<TabsList>
<TabsTrigger value="preview">
<AppWindowIcon />
Preview
</TabsTrigger>
<TabsTrigger value="code">
<CodeIcon />
Code
</TabsTrigger>
</TabsList>
</Tabs>
{/* Vertical orientation with line variant. */}
<Tabs defaultValue="preview" orientation="vertical">
<TabsList variant="line">
<TabsTrigger value="preview">
<AppWindowIcon />
Preview
</TabsTrigger>
<TabsTrigger value="code">
<CodeIcon />
Code
</TabsTrigger>
</TabsList>
</Tabs>
</div>
)
}

View File

@@ -9,9 +9,7 @@ import { ActiveThemeProvider } from "@/components/active-theme"
import { Analytics } from "@/components/analytics"
import { TailwindIndicator } from "@/components/tailwind-indicator"
import { ThemeProvider } from "@/components/theme-provider"
import { TooltipProvider as BaseTooltipProvider } from "@/registry/bases/base/ui/tooltip"
import { Toaster } from "@/registry/bases/radix/ui/sonner"
import { TooltipProvider as RadixTooltipProvider } from "@/registry/bases/radix/ui/tooltip"
import "@/styles/globals.css"
@@ -59,11 +57,6 @@ export const metadata: Metadata = {
apple: "/apple-touch-icon.png",
},
manifest: `${siteConfig.url}/site.webmanifest`,
alternates: {
types: {
"application/rss+xml": `${siteConfig.url}/rss.xml`,
},
},
}
export default function RootLayout({
@@ -99,12 +92,8 @@ export default function RootLayout({
<LayoutProvider>
<ActiveThemeProvider>
<NuqsAdapter>
<BaseTooltipProvider delay={0}>
<RadixTooltipProvider delayDuration={0}>
{children}
<Toaster position="top-center" />
</RadixTooltipProvider>
</BaseTooltipProvider>
{children}
<Toaster position="top-center" />
</NuqsAdapter>
<TailwindIndicator />
<Analytics />

View File

@@ -1,44 +0,0 @@
import { NextResponse } from "next/server"
import { getChangelogPages, type ChangelogPageData } from "@/lib/changelog"
import { siteConfig } from "@/lib/config"
export const revalidate = false
export async function GET() {
const pages = getChangelogPages()
const items = pages
.map((page) => {
const data = page.data as ChangelogPageData
const date = page.date?.toUTCString() ?? new Date().toUTCString()
const link = `${siteConfig.url}/docs/${page.slugs.join("/")}`
return ` <item>
<title><![CDATA[${data.title}]]></title>
<link>${link}</link>
<guid>${link}</guid>
<description><![CDATA[${data.description || ""}]]></description>
<pubDate>${date}</pubDate>
</item>`
})
.join("\n")
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>${siteConfig.name} Changelog</title>
<link>${siteConfig.url}</link>
<description>${siteConfig.description}</description>
<language>en-us</language>
<atom:link href="${siteConfig.url}/rss.xml" rel="self" type="application/rss+xml"/>
${items}
</channel>
</rss>`
return new NextResponse(xml, {
headers: {
"Content-Type": "application/rss+xml; charset=utf-8",
},
})
}

View File

@@ -20,9 +20,10 @@ function BaseUILogo() {
export function Announcement() {
return (
<Badge asChild variant="secondary" className="bg-muted">
<Link href="/docs/changelog/2026-01-rtl">
RTL Support <ArrowRightIcon />
<Badge asChild variant="secondary" className="bg-transparent">
<Link href="/docs/changelog">
<BaseUILogo />
Base UI Documentation <ArrowRightIcon />
</Link>
</Badge>
)

View File

@@ -16,7 +16,7 @@ import {
Tablet,
Terminal,
} from "lucide-react"
import { type PanelImperativeHandle } from "react-resizable-panels"
import { type ImperativePanelHandle } from "react-resizable-panels"
import {
type registryItemFileSchema,
type registryItemSchema,
@@ -68,7 +68,7 @@ type BlockViewerContext = {
setView: (view: "code" | "preview") => void
activeFile: string | null
setActiveFile: (file: string) => void
resizablePanelRef: React.RefObject<PanelImperativeHandle | null> | null
resizablePanelRef: React.RefObject<ImperativePanelHandle | null> | null
tree: ReturnType<typeof createFileTreeForRegistryItemFiles> | null
highlightedFiles:
| (z.infer<typeof registryItemFileSchema> & {
@@ -101,7 +101,7 @@ function BlockViewerProvider({
const [activeFile, setActiveFile] = React.useState<
BlockViewerContext["activeFile"]
>(highlightedFiles?.[0].target ?? null)
const resizablePanelRef = React.useRef<PanelImperativeHandle>(null)
const resizablePanelRef = React.useRef<ImperativePanelHandle>(null)
const [iframeKey, setIframeKey] = React.useState(0)
return (
@@ -268,19 +268,19 @@ function BlockViewerView({ styleName }: { styleName: Style["name"] }) {
<div className="relative grid w-full gap-4">
<div className="absolute inset-0 right-4 [background-image:radial-gradient(#d4d4d4_1px,transparent_1px)] [background-size:20px_20px] dark:[background-image:radial-gradient(#404040_1px,transparent_1px)]"></div>
<ResizablePanelGroup
orientation="horizontal"
direction="horizontal"
className="after:bg-surface/50 relative z-10 after:absolute after:inset-0 after:right-3 after:z-0 after:rounded-xl"
>
<ResizablePanel
panelRef={resizablePanelRef}
ref={resizablePanelRef}
className="bg-background relative aspect-[4/2.5] overflow-hidden rounded-lg border md:aspect-auto md:rounded-xl"
defaultSize="100%"
minSize="30%"
defaultSize={100}
minSize={30}
>
<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%" />
<ResizablePanel defaultSize={0} minSize={0} />
</ResizablePanelGroup>
</div>
</div>

View File

@@ -20,7 +20,7 @@ export function Callout({
<Alert
data-variant={variant}
className={cn(
"bg-surface text-surface-foreground border-surface mt-6 w-auto rounded-xl md:-mx-1 **:[code]:border",
"bg-background text-foreground mt-6 w-auto border md:-mx-1",
className
)}
{...props}

View File

@@ -88,7 +88,7 @@ export function CodeBlockCommand({
<TabsTrigger
key={key}
value={key}
className="data-[state=active]:bg-background! data-[state=active]:border-input h-7 border border-transparent pt-0.5 shadow-none!"
className="data-[state=active]:bg-accent data-[state=active]:border-input h-7 border border-transparent pt-0.5 data-[state=active]:shadow-none"
>
{key}
</TabsTrigger>
@@ -113,16 +113,23 @@ export function CodeBlockCommand({
})}
</div>
</Tabs>
<Button
data-slot="copy-button"
size="icon"
variant="ghost"
className="absolute top-2 right-2 z-10 size-7 opacity-70 hover:opacity-100 focus-visible:opacity-100"
onClick={copyCommand}
>
<span className="sr-only">Copy</span>
{hasCopied ? <IconCheck /> : <IconCopy />}
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button
data-slot="copy-button"
size="icon"
variant="ghost"
className="absolute top-2 right-2 z-10 size-7 opacity-70 hover:opacity-100 focus-visible:opacity-100"
onClick={copyCommand}
>
<span className="sr-only">Copy</span>
{hasCopied ? <IconCheck /> : <IconCopy />}
</Button>
</TooltipTrigger>
<TooltipContent>
{hasCopied ? "Copied" : "Copy to Clipboard"}
</TooltipContent>
</Tooltip>
</div>
)
}

View File

@@ -39,7 +39,7 @@ export function CodeCollapsibleWrapper({
</CollapsibleTrigger>
<CollapsibleContent
forceMount
className="relative mt-6 overflow-hidden data-[state=closed]:max-h-64 data-[state=closed]:[content-visibility:auto] [&>figure]:mt-0 [&>figure]:md:!mx-0"
className="relative mt-6 overflow-hidden data-[state=closed]:max-h-64 [&>figure]:mt-0 [&>figure]:md:!mx-0"
>
{children}
</CollapsibleContent>

View File

@@ -2,10 +2,11 @@
import * as React from "react"
import { usePathname, useRouter } from "next/navigation"
import { type DialogProps } from "@radix-ui/react-dialog"
import * as DialogPrimitive 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 { Dialog as DialogPrimitive } from "radix-ui"
import { CornerDownLeftIcon, SquareDashedIcon, XIcon } from "lucide-react"
import { type Color, type ColorPalette } from "@/lib/colors"
import { trackEvent } from "@/lib/events"
@@ -29,11 +30,11 @@ import {
Dialog,
DialogDescription,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
} from "@/registry/new-york-v4/ui/dialog"
import { Kbd } 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"
@@ -43,7 +44,7 @@ export function CommandMenu({
blocks,
navItems,
...props
}: React.ComponentProps<typeof Dialog> & {
}: DialogProps & {
tree: typeof source.pageTree
colors: ColorPalette[]
blocks?: { name: string; description: string; categories: string[] }[]
@@ -54,7 +55,6 @@ export function CommandMenu({
const [config] = useConfig()
const currentBase = getCurrentBase(pathname)
const [open, setOpen] = React.useState(false)
const [renderDelayedGroups, setRenderDelayedGroups] = React.useState(false)
const [selectedType, setSelectedType] = React.useState<
"color" | "page" | "component" | "block" | null
>(null)
@@ -94,30 +94,14 @@ export function CommandMenu({
// Set new timeout to debounce both search and tracking.
searchTimeoutRef.current = setTimeout(() => {
React.startTransition(() => {
setSearch(value)
trackSearchQuery(value)
})
setSearch(value)
trackSearchQuery(value)
}, 500)
},
[setSearch, trackSearchQuery]
)
// Cleanup timeout on unmount.
React.useEffect(() => {
if (open) {
const frame = requestAnimationFrame(() => {
setRenderDelayedGroups(true)
})
return () => {
cancelAnimationFrame(frame)
}
}
setRenderDelayedGroups(false)
}, [open])
React.useEffect(() => {
return () => {
if (searchTimeoutRef.current) {
@@ -126,17 +110,6 @@ export function CommandMenu({
}
}, [])
const commandFilter = React.useCallback(
(value: string, searchValue: string, keywords?: string[]) => {
const extendValue = value + " " + (keywords?.join(" ") || "")
if (extendValue.toLowerCase().includes(searchValue.toLowerCase())) {
return 1
}
return 0
},
[]
)
const handlePageHighlight = React.useCallback(
(isComponent: boolean, item: { url: string; name?: React.ReactNode }) => {
if (isComponent) {
@@ -177,168 +150,6 @@ export function CommandMenu({
[setOpen]
)
const navItemsSection = React.useMemo(() => {
if (!navItems || navItems.length === 0) {
return null
}
return (
<CommandGroup
heading="Pages"
className="!p-0 [&_[cmdk-group-heading]]:scroll-mt-16 [&_[cmdk-group-heading]]:!p-3 [&_[cmdk-group-heading]]:!pb-1"
>
{navItems.map((item) => (
<CommandMenuItem
key={item.href}
value={`Navigation ${item.label}`}
keywords={["nav", "navigation", item.label.toLowerCase()]}
onHighlight={() => {
setSelectedType("page")
setCopyPayload("")
}}
onSelect={() => {
runCommand(() => router.push(item.href))
}}
>
<IconArrowRight />
{item.label}
</CommandMenuItem>
))}
</CommandGroup>
)
}, [navItems, runCommand, router])
const pageGroupsSection = React.useMemo(() => {
return tree.children.map((group) => {
if (group.type !== "folder") {
return null
}
const pages = getPagesFromFolder(group, currentBase).filter((item) => {
if (!showMcpDocs && item.url.includes("/mcp")) {
return false
}
return true
})
if (pages.length === 0) {
return null
}
return (
<CommandGroup
key={group.$id}
heading={group.name}
className="!p-0 [&_[cmdk-group-heading]]:scroll-mt-16 [&_[cmdk-group-heading]]:!p-3 [&_[cmdk-group-heading]]:!pb-1"
>
{pages.map((item) => {
const isComponent = item.url.includes("/components/")
return (
<CommandMenuItem
key={item.url}
value={
item.name?.toString() ? `${group.name} ${item.name}` : ""
}
keywords={isComponent ? ["component"] : undefined}
onHighlight={() => handlePageHighlight(isComponent, item)}
onSelect={() => {
runCommand(() => router.push(item.url))
}}
>
{isComponent ? (
<div className="border-muted-foreground aspect-square size-4 rounded-full border border-dashed" />
) : (
<IconArrowRight />
)}
{item.name}
</CommandMenuItem>
)
})}
</CommandGroup>
)
})
}, [tree.children, currentBase, handlePageHighlight, runCommand, router])
const colorGroupsSection = React.useMemo(() => {
return colors.map((colorPalette) => (
<CommandGroup
key={colorPalette.name}
heading={
colorPalette.name.charAt(0).toUpperCase() + colorPalette.name.slice(1)
}
className="!p-0 [&_[cmdk-group-heading]]:!p-3"
>
{colorPalette.colors.map((color) => (
<CommandMenuItem
key={color.hex}
value={color.className}
keywords={["color", color.name, color.className]}
onHighlight={() => handleColorHighlight(color)}
onSelect={() => {
runCommand(() =>
copyToClipboardWithMeta(color.oklch, {
name: "copy_color",
properties: { color: color.oklch },
})
)
}}
>
<div
className="border-ghost aspect-square size-4 rounded-sm bg-(--color) after:rounded-sm"
style={{ "--color": color.oklch } as React.CSSProperties}
/>
{color.className}
<span className="text-muted-foreground ml-auto font-mono text-xs font-normal tabular-nums">
{color.oklch}
</span>
</CommandMenuItem>
))}
</CommandGroup>
))
}, [colors, handleColorHighlight, runCommand])
const blocksSection = React.useMemo(() => {
if (!blocks || blocks.length === 0) {
return null
}
return (
<CommandGroup
heading="Blocks"
className="!p-0 [&_[cmdk-group-heading]]:!p-3"
>
{blocks.map((block) => (
<CommandMenuItem
key={block.name}
value={block.name}
onHighlight={() => {
handleBlockHighlight(block)
}}
keywords={[
"block",
block.name,
block.description,
...block.categories,
]}
onSelect={() => {
runCommand(() =>
router.push(`/blocks/${block.categories[0]}#${block.name}`)
)
}}
>
<SquareDashedIcon />
{block.description}
<span className="text-muted-foreground ml-auto font-mono text-xs font-normal tabular-nums">
{block.name}
</span>
</CommandMenuItem>
))}
</CommandGroup>
)
}, [blocks, handleBlockHighlight, runCommand, router])
React.useEffect(() => {
const down = (e: KeyboardEvent) => {
if ((e.key === "k" && (e.metaKey || e.ctrlKey)) || e.key === "/") {
@@ -391,13 +202,16 @@ export function CommandMenu({
<Button
variant="outline"
className={cn(
"text-foreground dark:bg-card hover:bg-muted/50 relative h-8 w-full justify-start rounded-lg pl-3 font-normal shadow-none sm:pr-12 md:w-48 lg:w-56 xl:w-64"
"text-foreground dark:bg-card hover:bg-muted/50 relative h-8 w-full justify-start pl-3 font-normal shadow-none sm:pr-12 md:w-48 lg:w-56 xl:w-64"
)}
onClick={() => setOpen(true)}
{...props}
>
<span className="hidden lg:inline-flex">Search documentation...</span>
<span className="inline-flex lg:hidden">Search...</span>
<div className="absolute top-1.5 right-1.5 hidden gap-1 group-has-[[data-slot=designer]]/body:hidden sm:flex">
<Kbd>K</Kbd>
</div>
</Button>
</DialogTrigger>
<DialogContent className="rounded-xl border-none bg-clip-padding p-2 pb-11 shadow-2xl ring-4 ring-neutral-200/80 dark:bg-neutral-900 dark:ring-neutral-800">
@@ -407,13 +221,17 @@ export function CommandMenu({
</DialogHeader>
<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={commandFilter}
filter={(value, search, keywords) => {
handleSearchChange(search)
const extendValue = value + " " + (keywords?.join(" ") || "")
if (extendValue.toLowerCase().includes(search.toLowerCase())) {
return 1
}
return 0
}}
>
<div className="relative">
<CommandInput
placeholder="Search documentation..."
onValueChange={handleSearchChange}
/>
<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" />
@@ -424,19 +242,148 @@ export function CommandMenu({
<CommandEmpty className="text-muted-foreground py-12 text-center text-sm">
{query.isLoading ? "Searching..." : "No results found."}
</CommandEmpty>
{navItemsSection}
{renderDelayedGroups ? (
<>
{pageGroupsSection}
{colorGroupsSection}
{blocksSection}
<SearchResults
setOpen={setOpen}
query={query}
search={search}
/>
</>
{navItems && navItems.length > 0 && (
<CommandGroup
heading="Pages"
className="!p-0 [&_[cmdk-group-heading]]:scroll-mt-16 [&_[cmdk-group-heading]]:!p-3 [&_[cmdk-group-heading]]:!pb-1"
>
{navItems.map((item) => (
<CommandMenuItem
key={item.href}
value={`Navigation ${item.label}`}
keywords={["nav", "navigation", item.label.toLowerCase()]}
onHighlight={() => {
setSelectedType("page")
setCopyPayload("")
}}
onSelect={() => {
runCommand(() => router.push(item.href))
}}
>
<IconArrowRight />
{item.label}
</CommandMenuItem>
))}
</CommandGroup>
)}
{tree.children.map((group) => (
<CommandGroup
key={group.$id}
heading={group.name}
className="!p-0 [&_[cmdk-group-heading]]:scroll-mt-16 [&_[cmdk-group-heading]]:!p-3 [&_[cmdk-group-heading]]:!pb-1"
>
{group.type === "folder" &&
getPagesFromFolder(group, currentBase).map((item) => {
const isComponent = item.url.includes("/components/")
if (!showMcpDocs && item.url.includes("/mcp")) {
return null
}
return (
<CommandMenuItem
key={item.url}
value={
item.name?.toString()
? `${group.name} ${item.name}`
: ""
}
keywords={isComponent ? ["component"] : undefined}
onHighlight={() =>
handlePageHighlight(isComponent, item)
}
onSelect={() => {
runCommand(() => router.push(item.url))
}}
>
{isComponent ? (
<div className="border-muted-foreground aspect-square size-4 rounded-full border border-dashed" />
) : (
<IconArrowRight />
)}
{item.name}
</CommandMenuItem>
)
})}
</CommandGroup>
))}
{colors.map((colorPalette) => (
<CommandGroup
key={colorPalette.name}
heading={
colorPalette.name.charAt(0).toUpperCase() +
colorPalette.name.slice(1)
}
className="!p-0 [&_[cmdk-group-heading]]:!p-3"
>
{colorPalette.colors.map((color) => (
<CommandMenuItem
key={color.hex}
value={color.className}
keywords={["color", color.name, color.className]}
onHighlight={() => handleColorHighlight(color)}
onSelect={() => {
runCommand(() =>
copyToClipboardWithMeta(color.oklch, {
name: "copy_color",
properties: { color: color.oklch },
})
)
}}
>
<div
className="border-ghost aspect-square size-4 rounded-sm bg-(--color) after:rounded-sm"
style={{ "--color": color.oklch } as React.CSSProperties}
/>
{color.className}
<span className="text-muted-foreground ml-auto font-mono text-xs font-normal tabular-nums">
{color.oklch}
</span>
</CommandMenuItem>
))}
</CommandGroup>
))}
{blocks?.length ? (
<CommandGroup
heading="Blocks"
className="!p-0 [&_[cmdk-group-heading]]:!p-3"
>
{blocks.map((block) => (
<CommandMenuItem
key={block.name}
value={block.name}
onHighlight={() => {
handleBlockHighlight(block)
}}
keywords={[
"block",
block.name,
block.description,
...block.categories,
]}
onSelect={() => {
runCommand(() =>
router.push(
`/blocks/${block.categories[0]}#${block.name}`
)
)
}}
>
<SquareDashedIcon />
{block.description}
<span className="text-muted-foreground ml-auto font-mono text-xs font-normal tabular-nums">
{block.name}
</span>
</CommandMenuItem>
))}
</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">
@@ -522,24 +469,23 @@ function SearchResults({
query,
search,
}: {
open: boolean
setOpen: (open: boolean) => void
query: Query
search: string
}) {
const router = useRouter()
const uniqueResults = React.useMemo(() => {
if (!query.data || !Array.isArray(query.data)) {
return []
}
return query.data.filter(
(item, index, self) =>
!(
item.type === "text" && item.content.trim().split(/\s+/).length <= 1
) && index === self.findIndex((t) => t.content === item.content)
)
}, [query.data])
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
@@ -588,11 +534,14 @@ function DialogContent({
}) {
return (
<DialogPortal data-slot="dialog-portal">
{/* <DialogOverlay /> */}
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className="fixed inset-0 z-50 bg-black/50"
/>
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-background fixed top-1/3 left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
"bg-background fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
className
)}
{...props}

View File

@@ -1,26 +1,9 @@
"use client"
import * as React from "react"
import Link from "next/link"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/examples/base/ui/popover"
import { IconAlertCircle } from "@tabler/icons-react"
import { cn } from "@/lib/utils"
import {
LanguageProvider,
LanguageSelector,
useLanguageContext,
useTranslation,
type Translations,
} from "@/components/language-selector"
import { DirectionProvider as BaseDirectionProvider } from "@/registry/bases/base/ui/direction"
import { DirectionProvider as RadixDirectionProvider } from "@/registry/bases/radix/ui/direction"
import { Button } from "@/registry/new-york-v4/ui/button"
import { Separator } from "@/registry/new-york-v4/ui/separator"
export function ComponentPreviewTabs({
className,
@@ -30,9 +13,6 @@ export function ComponentPreviewTabs({
chromeLessOnMobile = false,
component,
source,
sourcePreview,
direction = "ltr",
styleName,
...props
}: React.ComponentProps<"div"> & {
previewClassName?: string
@@ -41,114 +21,37 @@ export function ComponentPreviewTabs({
chromeLessOnMobile?: boolean
component: React.ReactNode
source: React.ReactNode
sourcePreview?: React.ReactNode
direction?: "ltr" | "rtl"
styleName?: string
}) {
const [isMobileCodeVisible, setIsMobileCodeVisible] = React.useState(false)
const base = styleName?.split("-")[0]
return (
<div
data-slot="component-preview"
className={cn(
"group relative mt-4 mb-12 flex flex-col overflow-hidden rounded-xl border",
"group relative mt-4 mb-12 flex flex-col gap-2 overflow-hidden rounded-xl border",
className
)}
{...props}
>
{direction === "rtl" ? (
<LanguageProvider defaultLanguage="ar">
<div className="flex h-16 items-center border-b px-4">
<RtlLanguageSelector />
<Popover>
<PopoverTrigger
render={
<Button
variant="ghost"
size="icon-sm"
className="ml-auto size-7"
>
<IconAlertCircle />
<span className="sr-only">Toggle</span>
</Button>
}
></PopoverTrigger>
<PopoverContent
side="bottom"
align="end"
className="w-56 text-xs"
>
<div>
I used AI to translate the text for demonstration purposes.
It&apos;s not perfect and may contain errors.
</div>
<Separator className="-mx-2.5 w-auto!" />
<div data-lang="ar">
لقد استخدمت الذكاء الاصطناعي لترجمة النص للأغراض التجريبية
فقط. قد لا تكون الترجمة دقيقة وقد تحتوي على أخطاء.
</div>
<Separator className="-mx-2.5 w-auto!" />
<div data-lang="he">
השתמשתי בבינה מלאכותית כדי לתרגם את הטקסט למטרות הדגמה. זה לא
מושלם ויכול להכיל שגיאות.
</div>
</PopoverContent>
</Popover>
</div>
<PreviewWrapper
align={align}
chromeLessOnMobile={chromeLessOnMobile}
previewClassName={previewClassName}
>
<DirectionProviderWrapper base={base}>
{component}
</DirectionProviderWrapper>
</PreviewWrapper>
</LanguageProvider>
) : (
<DirectionProviderWrapper base={base} dir="ltr">
<PreviewWrapper
align={align}
chromeLessOnMobile={chromeLessOnMobile}
previewClassName={previewClassName}
dir="ltr"
>
{component}
</PreviewWrapper>
</DirectionProviderWrapper>
)}
{!hideCode && (
<div data-slot="preview">
<div
data-slot="code"
data-mobile-code-visible={isMobileCodeVisible}
className="relative overflow-hidden **:data-[slot=copy-button]:right-4 **:data-[slot=copy-button]:hidden data-[mobile-code-visible=true]:**:data-[slot=copy-button]:flex [&_[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-72"
data-align={align}
data-chromeless={chromeLessOnMobile}
className={cn(
"preview flex h-72 w-full justify-center p-10 data-[align=center]:items-center data-[align=end]:items-end data-[align=start]:items-start data-[chromeless=true]:h-auto data-[chromeless=true]:p-0",
previewClassName
)}
>
{isMobileCodeVisible ? (
<>
{direction === "rtl" && (
<div className="bg-code text-muted-foreground no-scrollbar relative z-10 overflow-x-auto border-t p-6 font-mono text-sm">
<pre>{`// You will notice this example uses dir and data-lang attributes.
// This is because this site is not RTL by default.
// In your application, you won't need these.`}</pre>
<span>
{"// See the "}
<Link
href="/docs/rtl"
className="underline underline-offset-4"
>
RTL guide
</Link>
{" for more information."}
</span>
</div>
)}
{source}
</>
) : (
<div className="relative">
{sourcePreview}
<div className="absolute inset-0 flex items-center justify-center pb-4">
{component}
</div>
{!hideCode && (
<div
data-slot="code"
data-mobile-code-visible={isMobileCodeVisible}
className="relative overflow-hidden data-[mobile-code-visible=false]:max-h-24 md:max-h-none data-[mobile-code-visible=false]:md:max-h-none [&_[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-72"
>
{source}
{!isMobileCodeVisible && (
<div className="absolute inset-0 flex items-center justify-center md:hidden">
<div
className="absolute inset-0"
style={{
@@ -160,7 +63,7 @@ export function ComponentPreviewTabs({
type="button"
size="sm"
variant="outline"
className="bg-background text-foreground dark:bg-background dark:text-foreground hover:bg-muted dark:hover:bg-muted relative z-10 rounded-lg shadow-none"
className="bg-background text-foreground dark:bg-background dark:text-foreground relative z-10"
onClick={() => {
setIsMobileCodeVisible(true)
}}
@@ -168,100 +71,10 @@ export function ComponentPreviewTabs({
View Code
</Button>
</div>
</div>
)}
</div>
)}
</div>
)
}
const directionTranslations: Translations<Record<string, never>> = {
en: {
dir: "ltr",
values: {},
},
ar: {
dir: "rtl",
values: {},
},
he: {
dir: "rtl",
values: {},
},
}
function RtlLanguageSelector({ className }: { className?: string }) {
const context = useLanguageContext()
if (!context) {
return null
}
return (
<LanguageSelector
value={context.language}
onValueChange={context.setLanguage}
className={className}
/>
)
}
function PreviewWrapper({
align,
chromeLessOnMobile,
previewClassName,
dir: explicitDir,
children,
}: {
align: "center" | "start" | "end"
chromeLessOnMobile: boolean
previewClassName?: string
dir?: "ltr" | "rtl"
children: React.ReactNode
}) {
// useTranslation handles the case when there's no LanguageProvider context.
// It will fall back to local state with defaultLanguage.
const translation = useTranslation(directionTranslations, "ar")
const dir = explicitDir ?? translation.dir
return (
<div
data-slot="preview"
dir={dir}
data-lang={dir === "rtl" ? translation.language : undefined}
>
<div
data-align={align}
data-chromeless={chromeLessOnMobile}
className={cn(
"preview relative flex h-72 w-full justify-center p-10 data-[align=center]:items-center data-[align=end]:items-end data-[align=start]:items-start data-[chromeless=true]:h-auto data-[chromeless=true]:p-0",
previewClassName
)}
</div>
)}
>
{children}
</div>
</div>
)
}
function DirectionProviderWrapper({
base,
dir: explicitDir,
children,
}: {
base?: string
dir?: "ltr" | "rtl"
children: React.ReactNode
}) {
// useTranslation handles the case when there's no LanguageProvider context.
// It will fall back to local state with defaultLanguage.
const translation = useTranslation(directionTranslations, "ar")
const dir = explicitDir ?? translation.dir
if (base === "base") {
return (
<BaseDirectionProvider direction={dir}>{children}</BaseDirectionProvider>
)
}
return <RadixDirectionProvider dir={dir}>{children}</RadixDirectionProvider>
}

View File

@@ -14,8 +14,6 @@ export function ComponentPreview({
hideCode = false,
chromeLessOnMobile = false,
styleName = "new-york-v4",
direction = "ltr",
caption,
...props
}: React.ComponentProps<"div"> & {
name: string
@@ -26,12 +24,10 @@ export function ComponentPreview({
type?: "block" | "component" | "example"
chromeLessOnMobile?: boolean
previewClassName?: string
direction?: "ltr" | "rtl"
caption?: string
}) {
if (type === "block") {
const content = (
<div className="relative mt-6 aspect-[4/2.5] w-full overflow-hidden rounded-xl border md:-mx-1">
return (
<div className="relative aspect-[4/2.5] w-full overflow-hidden rounded-xl border md:-mx-1">
<Image
src={`/r/styles/new-york-v4/${name}-light.png`}
alt={name}
@@ -51,19 +47,6 @@ export function ComponentPreview({
</div>
</div>
)
if (caption) {
return (
<figure className="flex flex-col gap-4">
{content}
<figcaption className="text-muted-foreground text-center text-sm">
{caption}
</figcaption>
</figure>
)
}
return content
}
const Component = getRegistryComponent(name, styleName)
@@ -80,13 +63,13 @@ export function ComponentPreview({
)
}
const content = (
return (
<ComponentPreviewTabs
className={className}
previewClassName={previewClassName}
align={align}
hideCode={hideCode}
component={React.createElement(Component)}
component={<DynamicComponent name={name} styleName={styleName} />}
source={
<ComponentSource
name={name}
@@ -94,34 +77,27 @@ export function ComponentPreview({
styleName={styleName}
/>
}
sourcePreview={
<ComponentSource
name={name}
collapsible={false}
styleName={styleName}
maxLines={3}
/>
}
chromeLessOnMobile={chromeLessOnMobile}
direction={direction}
styleName={styleName}
{...props}
/>
)
}
if (caption) {
return (
<figure
data-hide-code={hideCode}
className="flex flex-col data-[hide-code=true]:gap-4"
>
{content}
<figcaption className="text-muted-foreground -mt-8 text-center text-sm data-[hide-code=true]:mt-0">
{caption}
</figcaption>
</figure>
)
function DynamicComponent({
name,
styleName,
}: {
name: string
styleName: string
}) {
const Component = React.useMemo(
() => getRegistryComponent(name, styleName),
[name, styleName]
)
if (!Component) {
return null
}
return content
return React.createElement(Component)
}

View File

@@ -18,7 +18,6 @@ export async function ComponentSource({
collapsible = true,
className,
styleName = "new-york-v4",
maxLines,
}: React.ComponentProps<"div"> & {
name?: string
src?: string
@@ -26,7 +25,6 @@ export async function ComponentSource({
language?: string
collapsible?: boolean
styleName?: string
maxLines?: number
}) {
if (!name && !src) {
return null
@@ -53,11 +51,6 @@ export async function ComponentSource({
code = await formatCode(code, styleName)
code = code.replaceAll("/* eslint-disable react/no-children-prop */\n", "")
// Truncate code if maxLines is set.
if (maxLines) {
code = code.split("\n").slice(0, maxLines).join("\n")
}
const lang = language ?? title?.split(".").pop() ?? "tsx"
const highlightedCode = await highlightCode(code, lang)

View File

@@ -13,7 +13,7 @@ export function ComponentsList({
const list = getPagesFromFolder(componentsFolder, currentBase)
return (
<div className="grid grid-cols-2 gap-4 md:grid-cols-3 md:gap-x-8 lg:gap-x-16 lg:gap-y-6 xl:gap-x-20">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 md:gap-x-8 lg:gap-x-16 lg:gap-y-6 xl:gap-x-20">
{list.map((component) => (
<Link
key={component.$id}

View File

@@ -6,6 +6,11 @@ import { IconCheck, IconCopy } from "@tabler/icons-react"
import { trackEvent, type Event } from "@/lib/events"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york-v4/ui/tooltip"
export function copyToClipboardWithMeta(value: string, event?: Event) {
navigator.clipboard.writeText(value)
@@ -19,6 +24,7 @@ export function CopyButton({
className,
variant = "ghost",
event,
tooltip = "Copy to Clipboard",
...props
}: React.ComponentProps<typeof Button> & {
value: string
@@ -29,40 +35,44 @@ export function CopyButton({
const [hasCopied, setHasCopied] = React.useState(false)
React.useEffect(() => {
if (hasCopied) {
const timer = setTimeout(() => setHasCopied(false), 2000)
return () => clearTimeout(timer)
}
}, [hasCopied])
setTimeout(() => {
setHasCopied(false)
}, 2000)
}, [])
return (
<Button
data-slot="copy-button"
data-copied={hasCopied}
size="icon"
variant={variant}
className={cn(
"bg-code absolute top-3 right-2 z-10 size-7 hover:opacity-100 focus-visible:opacity-100",
className
)}
onClick={() => {
copyToClipboardWithMeta(
value,
event
? {
name: event,
properties: {
code: value,
},
}
: undefined
)
setHasCopied(true)
}}
{...props}
>
<span className="sr-only">Copy</span>
{hasCopied ? <IconCheck /> : <IconCopy />}
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button
data-slot="copy-button"
data-copied={hasCopied}
size="icon"
variant={variant}
className={cn(
"bg-code absolute top-3 right-2 z-10 size-7 hover:opacity-100 focus-visible:opacity-100",
className
)}
onClick={() => {
copyToClipboardWithMeta(
value,
event
? {
name: event,
properties: {
code: value,
},
}
: undefined
)
setHasCopied(true)
}}
{...props}
>
<span className="sr-only">Copy</span>
{hasCopied ? <IconCheck /> : <IconCopy />}
</Button>
</TooltipTrigger>
<TooltipContent>{hasCopied ? "Copied" : tooltip}</TooltipContent>
</Tooltip>
)
}

View File

@@ -32,10 +32,6 @@ const TOP_LEVEL_SECTIONS = [
name: "Directory",
href: "/docs/directory",
},
{
name: "RTL",
href: "/docs/rtl",
},
{
name: "MCP Server",
href: "/docs/mcp",
@@ -53,8 +49,8 @@ const TOP_LEVEL_SECTIONS = [
href: "/docs/changelog",
},
]
const EXCLUDED_SECTIONS = ["installation", "dark-mode", "changelog", "rtl"]
const EXCLUDED_PAGES = ["/docs", "/docs/changelog", "/docs/rtl"]
const EXCLUDED_SECTIONS = ["installation", "dark-mode"]
const EXCLUDED_PAGES = ["/docs", "/docs/changelog"]
export function DocsSidebar({
tree,
@@ -65,15 +61,13 @@ export function DocsSidebar({
return (
<Sidebar
className="sticky top-[calc(var(--header-height)+0.6rem)] z-30 hidden h-[calc(100svh-10rem)] overscroll-none bg-transparent [--sidebar-menu-width:--spacing(56)] lg:flex"
className="sticky top-[calc(var(--header-height)+1px)] z-30 hidden h-[calc(100svh-6rem)] overscroll-none bg-transparent lg:flex"
collapsible="none"
{...props}
>
<div className="h-9" />
<div className="from-background via-background/80 to-background/50 absolute top-8 z-10 h-8 w-(--sidebar-menu-width) shrink-0 bg-gradient-to-b blur-xs" />
<div className="via-border absolute top-12 right-2 bottom-0 hidden h-full w-px bg-gradient-to-b from-transparent to-transparent lg:flex" />
<SidebarContent className="no-scrollbar mx-auto w-(--sidebar-menu-width) overflow-x-hidden px-2">
<SidebarGroup className="pt-6">
<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
</SidebarGroupLabel>
@@ -95,14 +89,8 @@ export function DocsSidebar({
className="data-[active=true]:bg-accent data-[active=true]:border-accent 3xl:fixed:w-full 3xl:fixed:max-w-48 relative h-[30px] w-fit overflow-visible border border-transparent text-[0.8rem] font-medium after:absolute after:inset-x-0 after:-inset-y-1 after:z-0 after:rounded-md"
>
<Link href={href}>
<span className="absolute inset-0 flex w-(--sidebar-menu-width) bg-transparent" />
<span className="absolute inset-0 flex w-(--sidebar-width) bg-transparent" />
{name}
{PAGES_NEW.includes(href) && (
<span
className="flex size-2 rounded-full bg-blue-500"
title="New"
/>
)}
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
@@ -141,7 +129,7 @@ export function DocsSidebar({
className="data-[active=true]:bg-accent data-[active=true]:border-accent 3xl:fixed:w-full 3xl:fixed:max-w-48 relative h-[30px] w-fit overflow-visible border border-transparent text-[0.8rem] font-medium after:absolute after:inset-x-0 after:-inset-y-1 after:z-0 after:rounded-md"
>
<Link href={page.url}>
<span className="absolute inset-0 flex w-(--sidebar-menu-width) bg-transparent" />
<span className="absolute inset-0 flex w-(--sidebar-width) bg-transparent" />
{page.name}
{PAGES_NEW.includes(page.url) && (
<span

View File

@@ -114,7 +114,7 @@ export function DocsTableOfContents({
<a
key={item.url}
href={item.url}
className="text-muted-foreground hover:text-foreground data-[active=true]:text-foreground text-[0.8rem] no-underline transition-colors data-[active=true]:font-medium data-[depth=3]:pl-4 data-[depth=4]:pl-6"
className="text-muted-foreground hover:text-foreground data-[active=true]:text-foreground text-[0.8rem] no-underline transition-colors data-[depth=3]:pl-4 data-[depth=4]:pl-6"
data-active={item.url === `#${activeHeading}`}
data-depth={item.depth}
>

View File

@@ -31,12 +31,6 @@ const examples = [
code: "https://github.com/shadcn/ui/tree/main/apps/v4/app/(app)/examples/authentication",
hidden: false,
},
{
name: "RTL",
href: "/examples/rtl",
code: "https://github.com/shadcn/ui/tree/main/apps/v4/app/(app)/examples/rtl",
hidden: false,
},
]
export function ExamplesNav({
@@ -82,13 +76,10 @@ function ExampleLink({
<Link
href={example.href}
key={example.href}
className="text-muted-foreground hover:text-primary data-[active=true]:text-primary flex h-7 items-center justify-center gap-2 px-4 text-center text-base font-medium transition-colors"
className="text-muted-foreground hover:text-primary data-[active=true]:text-primary flex h-7 items-center justify-center px-4 text-center text-base font-medium transition-colors"
data-active={isActive}
>
{example.name}
{example.name === "RTL" && (
<span className="flex size-2 rounded-full bg-blue-500" title="New" />
)}
</Link>
)
}

View File

@@ -1,125 +0,0 @@
"use client"
import * as React from "react"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/examples/base/ui/select"
import { cn } from "@/lib/utils"
export type Language = "en" | "ar" | "he"
export type Direction = "ltr" | "rtl"
export type Translations<
T extends Record<string, string> = Record<string, string>,
> = Record<
Language,
{
dir: Direction
locale?: string
values: T
}
>
export const languageOptions = [
{ value: "en", label: "English" },
{ value: "ar", label: "Arabic (العربية)" },
{ value: "he", label: "Hebrew (עברית)" },
] as const
type LanguageContextType = {
language: Language
setLanguage: (language: Language) => void
}
const LanguageContext = React.createContext<LanguageContextType | undefined>(
undefined
)
export function LanguageProvider({
children,
defaultLanguage = "ar",
}: {
children: React.ReactNode
defaultLanguage?: Language
}) {
const [language, setLanguage] = React.useState<Language>(defaultLanguage)
return (
<LanguageContext.Provider value={{ language, setLanguage }}>
{children}
</LanguageContext.Provider>
)
}
export function useLanguageContext() {
const context = React.useContext(LanguageContext)
return context
}
export function useTranslation<T extends Record<string, string>>(
translations: Translations<T>,
defaultLanguage: Language = "ar"
) {
const context = useLanguageContext()
const [localLanguage, setLocalLanguage] =
React.useState<Language>(defaultLanguage)
const language = context?.language ?? localLanguage
const setLanguage = context?.setLanguage ?? setLocalLanguage
const { dir, locale, values: t } = translations[language]
return { language, setLanguage, dir, locale, t }
}
export interface LanguageSelectorProps {
value: Language
onValueChange: (value: Language) => void
}
export function LanguageSelector({
value,
onValueChange,
className,
languages = ["en", "ar", "he"],
}: LanguageSelectorProps & {
className?: string
languages?: Language[]
}) {
return (
<Select
items={languageOptions}
value={value}
onValueChange={(value) => onValueChange(value as Language)}
>
<SelectTrigger
size="sm"
className={cn("w-36", className)}
dir="ltr"
data-name="language-selector"
>
<SelectValue />
</SelectTrigger>
<SelectContent
dir="ltr"
className="data-closed:animate-none data-open:animate-none"
>
<SelectGroup>
{languageOptions
.filter((option) => languages.includes(option.value as Language))
.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
)
}

View File

@@ -30,10 +30,6 @@ const TOP_LEVEL_SECTIONS = [
name: "Directory",
href: "/docs/directory",
},
{
name: "RTL",
href: "/docs/rtl",
},
{
name: "MCP Server",
href: "/docs/mcp",
@@ -98,7 +94,7 @@ export function MobileNav({
</Button>
</PopoverTrigger>
<PopoverContent
className="bg-background/90 no-scrollbar h-(--radix-popper-available-height) w-(--radix-popper-available-width) overflow-y-auto rounded-none border-none p-0 shadow-none backdrop-blur duration-100 data-open:animate-none!"
className="bg-background/90 no-scrollbar h-(--radix-popper-available-height) w-(--radix-popper-available-width) overflow-y-auto rounded-none border-none p-0 shadow-none backdrop-blur duration-100"
align="start"
side="bottom"
alignOffset={-16}
@@ -132,12 +128,6 @@ export function MobileNav({
return (
<MobileLink key={name} href={href} onOpenChange={setOpen}>
{name}
{PAGES_NEW.includes(href) && (
<span
className="flex size-2 rounded-full bg-blue-500"
title="New"
/>
)}
</MobileLink>
)
})}
@@ -202,7 +192,7 @@ function MobileLink({
router.push(href.toString())
onOpenChange?.(false)
}}
className={cn("flex items-center gap-2 text-2xl font-medium", className)}
className={cn("text-2xl font-medium", className)}
{...props}
>
{children}

View File

@@ -8,7 +8,7 @@ function PageHeader({
return (
<section className={cn("border-grid", className)} {...props}>
<div className="container-wrapper">
<div className="container flex flex-col items-center gap-2 px-6 py-8 text-center md:py-16 lg:py-20 xl:gap-4">
<div className="container flex flex-col items-center gap-2 py-8 text-center md:py-16 lg:py-20 xl:gap-4">
{children}
</div>
</div>
@@ -23,7 +23,7 @@ function PageHeaderHeading({
return (
<h1
className={cn(
"text-primary leading-tighter max-w-3xl text-3xl font-semibold tracking-tight text-balance lg:leading-[1.1] lg:font-semibold xl:text-5xl xl:tracking-tighter",
"text-primary leading-tighter max-w-2xl text-4xl font-semibold tracking-tight text-balance lg:leading-[1.1] lg:font-semibold xl:text-5xl xl:tracking-tighter",
className
)}
{...props}
@@ -38,7 +38,7 @@ function PageHeaderDescription({
return (
<p
className={cn(
"text-foreground max-w-4xl text-base text-balance sm:text-lg",
"text-foreground max-w-3xl text-base text-balance sm:text-lg",
className
)}
{...props}

View File

@@ -2,7 +2,7 @@ import { siteConfig } from "@/lib/config"
export function SiteFooter() {
return (
<footer className="group-has-[.section-soft]/body:bg-surface/40 3xl:fixed:bg-transparent dark:group-has-[.section-soft]/body:bg-surface/40 group-has-[.docs-nav]/body:pb-20 group-has-[[data-slot=designer]]/body:hidden group-has-[[data-slot=docs]]/body:hidden group-has-[.docs-nav]/body:sm:pb-0 dark:bg-transparent">
<footer className="group-has-[.section-soft]/body:bg-surface/40 3xl:fixed:bg-transparent group-has-[.docs-nav]/body:pb-20 group-has-[[data-slot=designer]]/body:hidden group-has-[[data-slot=docs]]/body:hidden group-has-[.docs-nav]/body:sm:pb-0 dark:bg-transparent dark:group-has-[.section-soft]/body:bg-surface/40">
<div className="container-wrapper px-4 xl:px-6">
<div className="flex h-(--footer-height) items-center justify-between">
<div className="text-muted-foreground w-full px-1 text-center text-xs leading-loose sm:text-sm">

View File

@@ -1,19 +1,16 @@
"use client"
import { Label } from "@/examples/base/ui/label"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/examples/base/ui/select"
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,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/new-york-v4/ui/select"
import { CopyCodeButton } from "./theme-customizer"
@@ -22,44 +19,33 @@ export function ThemeSelector({ className }: React.ComponentProps<"div">) {
const value = activeTheme === "default" ? "neutral" : activeTheme
const items = THEMES.map((theme) => ({
label: theme.label,
value: theme.name,
}))
return (
<div className={cn("flex items-center gap-2", className)}>
<Label htmlFor="theme-selector" className="sr-only">
Theme
</Label>
<Select
items={items}
value={value}
onValueChange={(value) => value && setActiveTheme(value)}
>
<SelectTrigger id="theme-selector" className="w-36">
<Select value={value} onValueChange={setActiveTheme}>
<SelectTrigger
id="theme-selector"
size="sm"
className="bg-secondary text-secondary-foreground border-secondary justify-start shadow-none *:data-[slot=select-value]:w-12"
>
<span className="font-medium">Theme:</span>
<SelectValue placeholder="Select a theme" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Theme</SelectLabel>
{items.map((item) => (
<SelectItem
key={item.value}
value={item.value}
className="data-[state=checked]:opacity-50"
>
{item.label}
</SelectItem>
))}
</SelectGroup>
<SelectContent align="end">
{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"
className="rounded-lg border bg-transparent"
/>
<CopyCodeButton variant="secondary" size="icon-sm" />
</div>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -223,113 +223,3 @@ To customize the output directory, use the `--output` option.
```bash
npx shadcn@latest build --output ./public/registry
```
---
## migrate
Use the `migrate` command to run migrations on your project.
```bash
npx shadcn@latest migrate [migration]
```
**Available Migrations**
| Migration | Description |
| --------- | ------------------------------------------------------- |
| `icons` | Migrate your UI components to a different icon library. |
| `radix` | Migrate to radix-ui. |
| `rtl` | Migrate your components to support RTL (right-to-left). |
**Options**
```bash
Usage: shadcn migrate [options] [migration] [path]
run a migration.
Arguments:
migration the migration to run.
path optional path or glob pattern to migrate.
Options:
-c, --cwd <cwd> the working directory. defaults to the current directory.
-l, --list list all migrations. (default: false)
-y, --yes skip confirmation prompt. (default: false)
-h, --help display help for command
```
---
### migrate rtl
The `rtl` migration transforms your components to support RTL (right-to-left) languages.
```bash
npx shadcn@latest migrate rtl
```
This will:
1. Update `components.json` to set `rtl: true`
2. Transform physical CSS properties to logical equivalents (e.g., `ml-4` → `ms-4`, `text-left` → `text-start`)
3. Add `rtl:` variants where needed (e.g., `space-x-4` → `space-x-4 rtl:space-x-reverse`)
**Migrate specific files**
You can migrate specific files or use glob patterns:
```bash
# Migrate a specific file
npx shadcn@latest migrate rtl src/components/ui/button.tsx
# Migrate files matching a glob pattern
npx shadcn@latest migrate rtl "src/components/ui/**"
```
If no path is provided, the migration will transform all files in your `ui` directory (from `components.json`).
---
### migrate radix
The `radix` migration updates your imports from individual `@radix-ui/react-*` packages to the unified `radix-ui` package.
```bash
npx shadcn@latest migrate radix
```
This will:
1. Transform imports from `@radix-ui/react-*` to `radix-ui`
2. Add the `radix-ui` package to your `package.json`
**Before**
```tsx
import * as DialogPrimitive from "@radix-ui/react-dialog"
import * as SelectPrimitive from "@radix-ui/react-select"
```
**After**
```tsx
import { Dialog as DialogPrimitive, Select as SelectPrimitive } from "radix-ui"
```
**Migrate specific files**
You can migrate specific files or use glob patterns:
```bash
# Migrate a specific file.
npx shadcn@latest migrate radix src/components/ui/dialog.tsx
# Migrate files matching a glob pattern.
npx shadcn@latest migrate radix "src/components/ui/**"
```
If no path is provided, the migration will transform all files in your `ui` directory (from `components.json`).
Once complete, you can remove any unused `@radix-ui/react-*` packages from your `package.json`.

View File

@@ -10,8 +10,7 @@ description: Every component recreated in Figma. With customizable props, typogr
## Free
- [Obra shadcn/ui](https://www.figma.com/community/file/1514746685758799870/obra-shadcn-ui) by [Obra Studio](https://obra.studio/) - Carefully crafted shadcn/ui kit, MIT licensed, maintained by team of designers, with free design to code plugin
- [shadcn/ui components](https://www.figma.com/community/file/1342715840824755935) by [Sitsiilia Bergmann](https://x.com/sitsiilia) - A well-structured component library aligned with the shadcn component system, regularly maintained.
- [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

View File

@@ -6,7 +6,6 @@
"components-json",
"theming",
"[Dark Mode](/docs/dark-mode)",
"[RTL](/docs/rtl)",
"[CLI](/docs/cli)",
"monorepo",
"typography",
@@ -14,7 +13,7 @@
"javascript",
"blocks",
"figma",
"[Changelog](/docs/changelog)",
"changelog",
"[llms.txt](/llms.txt)",
"legacy"
]

View File

@@ -1,478 +0,0 @@
---
title: June 2023 - New CLI, Styles and more
description: Complete CLI rewrite with new styles, theming options, and more.
date: 2023-06-22
---
I have a lot of updates to share with you today:
- [**New CLI**](#new-cli) - Rewrote the CLI from scratch. You can now add components, dependencies and configure import paths.
- [**Theming**](#theming-with-css-variables-or-tailwind-colors) - Choose between using CSS variables or Tailwind CSS utility classes for theming.
- [**Base color**](#base-color) - Configure the base color for your project. This will be used to generate the default color palette for your components.
- [**React Server Components**](#react-server-components) - Opt out of using React Server Components. The CLI will automatically append or remove the `use client` directive.
- [**Styles**](#styles) - Introducing a new concept called _Style_. A style comes with its own set of components, animations, icons and more.
- [**Exit animations**](#exit-animations) - Added exit animations to all components.
- [**Other updates**](#other-updates) - New `icon` button size, updated `sheet` component and more.
- [**Updating your project**](#updating-your-project) - How to update your project to get the latest changes.
---
### New CLI
I've been working on a new CLI for the past few weeks. It's a complete rewrite. It comes with a lot of new features and improvements.
### `init`
```bash
npx shadcn@latest init
```
When you run the `init` command, you will be asked a few questions to configure `components.json`:
```txt showLineNumbers
Which style would you like to use? Default
Which color would you like to use as base color? Slate
Where is your global CSS file? app/globals.css
Do you want to use CSS variables for colors? no / yes
Where is your tailwind.config.js located? tailwind.config.js
Configure the import alias for components: @/components
Configure the import alias for utils: @/lib/utils
Are you using React Server Components? no / yes
```
This file contains all the information about your components: where to install them, the import paths, how they are styled...etc.
You can use this file to change the import path of a component, set a baseColor or change the styling method.
```json title="components.json" showLineNumbers
{
"style": "default",
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"baseColor": "zinc",
"cssVariables": true
},
"rsc": false,
"aliases": {
"utils": "~/lib/utils",
"components": "~/components"
}
}
```
This means you can now use the CLI with any directory structure including `src` and `app` directories.
### `add`
```bash
npx shadcn@latest add
```
The `add` command is now much more capable. You can now add UI components but also import more complex components (coming soon).
The CLI will automatically resolve all components and dependencies, format them based on your custom config and add them to your project.
### `diff` (experimental)
```bash
npx shadcn diff
```
We're also introducing a new `diff` command to help you keep track of upstream updates.
You can use this command to see what has changed in the upstream repository and update your project accordingly.
Run the `diff` command to get a list of components that have updates available:
```bash
npx shadcn diff
```
```txt
The following components have updates available:
- button
- /path/to/my-app/components/ui/button.tsx
- toast
- /path/to/my-app/components/ui/use-toast.ts
- /path/to/my-app/components/ui/toaster.tsx
```
Then run `diff [component]` to see the changes:
```bash
npx shadcn diff alert
```
```diff /pl-12/
const alertVariants = cva(
- "relative w-full rounded-lg border",
+ "relative w-full pl-12 rounded-lg border"
)
```
---
### Theming with CSS Variables or Tailwind Colors
You can choose between using CSS variables or Tailwind CSS utility classes for theming.
When you add new components, the CLI will automatically use the correct theming methods based on your `components.json` configuration.
#### 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 {6} title="components.json" showLineNumbers
{
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": false
}
}
```
#### CSS Variables
```tsx /bg-background/ /text-foreground/
<div className="bg-background text-foreground" />
```
To use CSS variables classes for theming set `tailwind.cssVariables` to `true` in your `components.json` file.
```json {6} title="components.json" showLineNumbers
{
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true
}
}
```
---
### Base color
You can now configure the base color for your project. This will be used to generate the default color palette for your components.
```json {5} title="components.json" showLineNumbers
{
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "zinc",
"cssVariables": false
}
}
```
Choose between `gray`, `neutral`, `slate`, `stone` or `zinc`.
If you have `cssVariables` set to `true`, we will set the base colors as CSS variables in your `globals.css` file. If you have `cssVariables` set to `false`, we will inline the Tailwind CSS utility classes in your components.
---
### React Server Components
If you're using a framework that does not support React Server Components, you can now opt out by setting `rsc` to `false`. We will automatically append or remove the `use client` directive when adding components.
```json title="components.json" showLineNumbers
{
"rsc": false
}
```
---
### Styles
We are introducing a new concept called _Style_.
_You can think of style as the visual foundation: shapes, icons, animations & typography._ A style comes with its own set of components, animations, icons and more.
We are shipping two styles: `default` and `new-york` (with more coming soon).
<Image
src="/images/style.jpg"
width="716"
height="402"
alt="Default vs New York style"
className="mt-6 overflow-hidden rounded-lg border"
/>
The `default` style is the one you are used to. It's the one we've been using since the beginning of this project. It uses `lucide-react` for icons and `tailwindcss-animate` for animations.
The `new-york` style is a new style. It ships with smaller buttons, cards with shadows and a new set of icons from [Radix Icons](https://icons.radix-ui.com).
When you run the `init` command, you will be asked which style you would like to use. This is saved in your `components.json` file.
```json title="components.json" showLineNumbers
{
"style": "new-york"
}
```
### Theming
Start with a style as the base then theme using CSS variables or Tailwind CSS utility classes to completely change the look of your components.
<Image
src="/images/style-with-theming.jpg"
width="716"
height="402"
alt="Style with theming"
className="mt-6 overflow-hidden rounded-lg border"
/>
---
### Exit animations
I added exit animations to all components. Click on the combobox below to see the subtle exit animation.
<ComponentPreview name="combobox-demo" className="[&_.preview]:items-start" />
The animations can be customized using utility classes.
---
### Other updates
### Button
- Added a new button size `icon`:
<ComponentPreview name="button-icon" />
### Sheet
- Renamed `position` to `side` to match the other elements.
<ComponentPreview name="sheet-side" />
- Removed the `size` props. Use `className="w-[200px] md:w-[450px]"` for responsive sizing.
---
### Updating your project
Since we follow a copy and paste approach, you will need to manually update your project to get the latest changes.
<Callout className="mt-4">
Note: we are working on a [`diff`](#diff-experimental) command to help you
keep track of upstream updates.
</Callout>
<Steps>
### Add `components.json`
Creating a `components.json` file at the root:
```json title="components.json" showLineNumbers
{
"style": "default",
"rsc": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
```
Update the values for `tailwind.css` and `aliases` to match your project structure.
### Button
Add the `icon` size to the `buttonVariants`:
```tsx {7} title="components/ui/button.tsx" showLineNumbers
const buttonVariants = cva({
variants: {
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
})
```
### Sheet
1. Replace the content of `sheet.tsx` with the following:
```tsx title="components/ui/sheet.tsx" showLineNumbers
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Sheet = SheetPrimitive.Root
const SheetTrigger = SheetPrimitive.Trigger
const SheetClose = SheetPrimitive.Close
const SheetPortal = ({
className,
...props
}: SheetPrimitive.DialogPortalProps) => (
<SheetPrimitive.Portal className={cn(className)} {...props} />
)
SheetPortal.displayName = SheetPrimitive.Portal.displayName
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"bg-background/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 backdrop-blur-sm",
className
)}
{...props}
ref={ref}
/>
))
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
}
)
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
))
SheetContent.displayName = SheetPrimitive.Content.displayName
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
SheetHeader.displayName = "SheetHeader"
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
SheetFooter.displayName = "SheetFooter"
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-foreground text-lg font-semibold", className)}
{...props}
/>
))
SheetTitle.displayName = SheetPrimitive.Title.displayName
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
))
SheetDescription.displayName = SheetPrimitive.Description.displayName
export {
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}
```
2. Rename `position` to `side`
```diff /position/ /side/
- <Sheet position="right" />
+ <Sheet side="right" />
```
</Steps>
### Thank you
I'd like to thank everyone who has been using this project, providing feedback and contributing to it. I really appreciate it. Thank you.

View File

@@ -1,45 +0,0 @@
---
title: July 2023 - JavaScript
description: JavaScript version of components available via the CLI.
date: 2023-07-04
---
This project and the components are written in TypeScript. **We recommend using TypeScript for your project as well**.
However we provide a JavaScript version of the components, available via the [cli](/docs/cli).
```txt
Would you like to use TypeScript (recommended)? no
```
To opt-out of TypeScript, you can use the `tsx` flag in your `components.json` file.
```json {10} title="components.json" showLineNumbers
{
"style": "default",
"tailwind": {
"config": "tailwind.config.js",
"css": "src/app/globals.css",
"baseColor": "zinc",
"cssVariables": true
},
"rsc": false,
"tsx": false,
"aliases": {
"utils": "~/lib/utils",
"components": "~/components"
}
}
```
To configure import aliases, you can use the following `jsconfig.json`:
```json {4} title="jsconfig.json" showLineNumbers
{
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}
```

View File

@@ -1,68 +0,0 @@
---
title: December 2023 - New Components
description: Carousel, Drawer, Pagination, Resizable, Sonner, and CLI updates.
date: 2023-12-22
---
We've added new components to shadcn/ui and made a lot of improvements to the CLI.
Here's a quick overview of what's new:
- [**Carousel**](#carousel) - A carousel component with motion, swipe gestures and keyboard support.
- [**Drawer**](#drawer) - A drawer component that looks amazing on mobile.
- [**Pagination**](#pagination) - A pagination component with page navigation, previous and next buttons.
- [**Resizable**](#resizable) - A resizable component for building resizable panel groups and layouts.
- [**Sonner**](#sonner) - The last toast component you'll ever need.
- [**CLI updates**](#cli-updates) - Support for custom **Tailwind prefix** and `tailwind.config.ts`.
### Carousel
We've added a fully featured carousel component with motion, swipe gestures and keyboard support. Built on top of [Embla Carousel](https://www.embla-carousel.com).
It has support for infinite looping, autoplay, vertical orientation, and more.
<ComponentPreview name="carousel-demo" />
### Drawer
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!
<ComponentPreview name="drawer-demo" />
### Pagination
We've added a pagination component with page navigation, previous and next buttons. Simple, flexible and works with your framework's `<Link />` component.
<ComponentPreview name="pagination-demo" />
### Resizable
Build resizable panel groups and layouts with this `<Resizable />` component.
<ComponentPreview name="resizable-demo-with-handle" />
`<Resizable />` is built using [react-resizable-panels](https://github.com/bvaughn/react-resizable-panels) by [bvaughn](https://github.com/bvaughn). It has support for mouse, touch and keyboard.
### Sonner
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" />
### CLI updates
This has been one of the most requested features. You can now configure a custom Tailwind prefix and the cli will automatically prefix your utility classes when adding components.
This means you can now easily add shadcn/ui components to existing projects like Docusaurus, Nextra...etc. A drop-in for your existing design system with no conflict.
```tsx /tw-/
<AlertDialog className="tw-grid tw-gap-4 tw-border tw-bg-background tw-shadow-lg" />
```
It works with `cn`, `cva` and CSS variables.
The cli can now also detect `tailwind.config.ts` and add the TypeScript version of the config for you.
That's it. Happy Holidays.

View File

@@ -1,99 +0,0 @@
---
title: March 2024 - Introducing Blocks
description: Ready-made components for dashboards and authentication pages.
date: 2024-03-22
---
One of the most requested features since launch has been layouts: admin dashboards with sidebar, marketing page sections, cards and more.
**Today, we're launching [**Blocks**](/blocks)**.
<a href="/blocks">
<Image
src="/images/dashboard-1.jpg"
width="716"
height="430"
alt="Admin dashboard"
className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
/>
<Image
src="/images/dashboard-1-dark.jpg"
width="716"
height="430"
alt="Admin dashboard"
className="mt-6 hidden w-full overflow-hidden rounded-lg border shadow-sm dark:block"
/>
<span className="sr-only">View the blocks library</span>
</a>
Blocks are ready-made components that you can use to build your apps. They are fully responsive, accessible, and composable, meaning they are built using the same principles as the rest of the components in shadcn/ui.
We're starting with dashboard layouts and authentication pages, with plans to add more blocks in the coming weeks.
### Open Source
Blocks are open source. You can find the source on GitHub. Use them in your projects, customize them and contribute back.
<a href="/blocks">
<Image
src="/images/dashboard-2.jpg"
width="716"
height="420"
alt="AI Playground"
className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
/>
<Image
src="/images/dashboard-2-dark.jpg"
width="716"
height="420"
alt="AI Playground"
className="mt-6 hidden w-full overflow-hidden rounded-lg border shadow-sm dark:block"
/>
<span className="sr-only">View the blocks library</span>
</a>
### Request a Block
We're also introducing a "Request a Block" feature. If there's a specific block you'd like to see, simply create a request on GitHub and the community can upvote and build it.
<a href="/blocks">
<Image
src="/images/dashboard-3.jpg"
width="716"
height="420"
alt="Settings Page"
className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
/>
<Image
src="/images/dashboard-3-dark.jpg"
width="716"
height="420"
alt="Settings Page"
className="mt-6 hidden w-full overflow-hidden rounded-lg border shadow-sm dark:block"
/>
<span className="sr-only">View the blocks library</span>
</a>
### v0
If you have a [v0](https://v0.dev) account, you can use the **Edit in v0** feature to open the code on v0 for prompting and further generation.
<div className="bg-background mt-6 flex aspect-video w-full items-center justify-center overflow-hidden rounded-lg border shadow-sm">
<svg
viewBox="0 0 40 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="text-foreground h-40 w-40"
>
<path
d="M23.3919 0H32.9188C36.7819 0 39.9136 3.13165 39.9136 6.99475V16.0805H36.0006V6.99475C36.0006 6.90167 35.9969 6.80925 35.9898 6.71766L26.4628 16.079C26.4949 16.08 26.5272 16.0805 26.5595 16.0805H36.0006V19.7762H26.5595C22.6964 19.7762 19.4788 16.6139 19.4788 12.7508V3.68923H23.3919V12.7508C23.3919 12.9253 23.4054 13.0977 23.4316 13.2668L33.1682 3.6995C33.0861 3.6927 33.003 3.68923 32.9188 3.68923H23.3919V0Z"
fill="currentColor"
></path>
<path
d="M13.7688 19.0956L0 3.68759H5.53933L13.6231 12.7337V3.68759H17.7535V17.5746C17.7535 19.6705 15.1654 20.6584 13.7688 19.0956Z"
fill="currentColor"
></path>
</svg>
</div>
That's it. _Looking forward to seeing what you build with Blocks_.

View File

@@ -1,25 +0,0 @@
---
title: March 2024 - Breadcrumb and Input OTP
description: New Breadcrumb and Input OTP components.
date: 2024-03-08
---
We've added a new Breadcrumb component and an Input OTP component.
### Breadcrumb
An accessible and flexible breadcrumb component. It has support for collapsed items, custom separators, bring-your-own routing `<Link />` and composable with other shadcn/ui components.
<ComponentPreview name="breadcrumb-demo" />
[See more examples](/docs/components/breadcrumb)
### Input OTP
A fully featured input OTP component. It has support for numeric and alphanumeric codes, custom length, copy-paste and accessible. Input OTP is built on top of [input-otp](https://github.com/guilhermerodz/input-otp) by [@guilherme_rodz](https://twitter.com/guilherme_rodz).
<ComponentPreview name="input-otp-demo" />
[Read the docs](/docs/components/input-otp)
If you have a [v0](https://v0.dev), the new components are available for generation.

View File

@@ -1,31 +0,0 @@
---
title: April 2024 - Lift Mode
description: A new mode for Blocks to lift smaller components for copy and paste.
date: 2024-04-05
---
We're introducing a new mode for [Blocks](/blocks) called **Lift Mode**.
Enable Lift Mode to automatically "lift" smaller components from a block template for copy and paste.
<a href="/blocks">
<Image
src="/images/lift-mode-light.png"
width="1432"
height="1050"
alt="Lift Mode"
className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
/>
<Image
src="/images/lift-mode-dark.png"
width="1432"
height="1069"
alt="Lift Mode"
className="mt-6 hidden w-full overflow-hidden rounded-lg border shadow-sm dark:block"
/>
<span className="sr-only">View the blocks library</span>
</a>
With Lift Mode, you'll be able to copy the smaller components that make up a block template, like cards, buttons, and forms, and paste them directly into your project.
Visit the [Blocks](/blocks) page to try it out.

View File

@@ -1,45 +0,0 @@
---
title: August 2024 - npx shadcn init
description: Complete CLI rewrite with support for all major React frameworks.
date: 2024-08-31
---
The new CLI is now available. It's a complete rewrite with a lot of new features and improvements. You can now install components, themes, hooks, utils and more using `npx shadcn add`.
This is a major step towards distributing code that you and your LLMs can access and use.
1. First up, the cli now has support for all major React framework out of the box. Next.js, Remix, Vite and Laravel. And when you init into a new app, we update your existing Tailwind files instead of overriding.
2. A component now ship its own dependencies. Take the accordion for example, it can define its Tailwind keyframes. When you add it to your project, we'll update your tailwind.config.ts file accordingly.
3. You can also install remote components using url. `npx shadcn add https://acme.com/registry/navbar.json`.
4. We have also improve the init command. It does framework detection and can even init a brand new Next.js app in one command. `npx shadcn init`.
5. We have created a new schema that you can use to ship your own component registry. And since it has support for urls, you can even use it to distribute private components.
6. And a few more updates like better error handling and monorepo support.
You can try the new cli today.
```bash
npx shadcn init sidebar-01 login-01
```
### Update Your Project
To update an existing project to use the new CLI, update your `components.json` file to include import aliases for your **components**, **utils**, **ui**, **lib** and **hooks**.
```json showLineNumbers {7-13} title="components.json"
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"tailwind": {
// ...
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
```
If you're using a different import alias prefix eg `~`, replace `@` with your prefix.

View File

@@ -1,11 +0,0 @@
---
title: October 2024 - React 19
description: shadcn/ui is now compatible with React 19 and Next.js 15.
date: 2024-10-29
---
shadcn/ui is now compatible with React 19 and Next.js 15.
We published a guide to help you upgrade your project to React 19.
Read more [here](/docs/react-19).

View File

@@ -1,13 +0,0 @@
---
title: October 2024 - Sidebar
description: 25 components to help you build all kinds of sidebars.
date: 2024-10-18
---
Introducing sidebar.tsx: 25 components to help you build all kinds of sidebars.
I don't like building sidebars. So I built 30+ of them. All types. Then simplified the core into sidebar.tsx: a strong foundation to build on top of.
It works with Next.js, Remix, Vite & Laravel.
See the announcement [here](https://x.com/shadcn/status/1847359896557408461).

View File

@@ -1,13 +0,0 @@
---
title: November 2024 - Icons
description: The new-york style now uses Lucide as the default icon set.
date: 2024-11-06
---
An update on icons. The new-york style now uses Lucide as the default icon set.
- New projects will use Lucide by default
- No breaking changes for existing projects
- Use the CLI to (optionally) migrate primitives to Lucide
For more info on why we're doing this, see the [thread](https://x.com/shadcn/status/1853902179041702169).

View File

@@ -1,18 +0,0 @@
---
title: December 2024 - Monorepo Support
description: New monorepo support in the CLI.
date: 2024-12-20
---
Until now, using shadcn/ui in a monorepo was a bit of a pain. You could add
components using the CLI, but you had to manage where the components
were installed and manually fix import paths.
With the new monorepo support in the CLI, we've made it a lot easier to use
shadcn/ui in a monorepo.
The CLI now understands the monorepo structure and will install the components,
dependencies and registry dependencies to the correct paths and handle imports
for you.
Read more in the [docs](/docs/monorepo).

View File

@@ -1,11 +0,0 @@
---
title: January 2025 - Blocks Community
description: Inviting the community to contribute to the blocks library.
date: 2025-01-14
---
We are inviting the community to contribute to the blocks library. Share your components and blocks with other developers and help build a library of high-quality, reusable components.
We'd love to see all types of blocks: applications, marketing, products, and more.
See the [docs](/docs/blocks) page to get started.

View File

@@ -1,13 +0,0 @@
---
title: February 2025 - Updated Registry Schema
description: Updated registry schema to support more features.
date: 2025-02-06
---
We're updating the registry schema to support more features.
Define code as a flat JSON file and distribute it via the CLI.
- Custom styles: bring your own design system, components & tokens
- Extend, override, mix & match components from third-party registries and LLMs
- Install themes, CSS vars, hooks, animations, and Tailwind layers & utilities

View File

@@ -1,22 +0,0 @@
---
title: February 2025 - Tailwind v4
description: First preview of Tailwind v4 and React 19 support.
date: 2025-02-19
---
We shipped the first preview of Tailwind v4 and React 19. Ready for you to try out. You can start using it today.
What's New:
- The CLI can now initialize projects with Tailwind v4.
- Full support for the new @theme directive and @theme inline option.
- All components are updated for Tailwind v4 and React 19.
- We've removed the forwardRefs and adjusted the types.
- Every primitive now has a data-slot attribute for styling.
- We've fixed and cleaned up the style of the components.
- We're deprecating the toast component in favor of sonner.
- Buttons now use the default cursor.
- We're deprecating the default style. New projects will use new-york.
- HSL colors are now converted to OKLCH.
Read more in the [docs](/docs/tailwind-v4).

View File

@@ -1,9 +0,0 @@
---
title: March 2025 - Cross-framework Route Support
description: The shadcn CLI can now auto-detect your framework and adapts routes for you.
date: 2025-04-09
---
The shadcn CLI can now auto-detect your framework and adapts routes for you.
Works with all frameworks including Laravel, Vite and React Router.

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