@neynar/ui 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/dist/components/ui/accordion.d.ts +229 -0
- package/dist/components/ui/accordion.d.ts.map +1 -0
- package/dist/components/ui/alert-dialog.d.ts +247 -0
- package/dist/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/components/ui/alert.d.ts +187 -0
- package/dist/components/ui/alert.d.ts.map +1 -0
- package/dist/components/ui/aspect-ratio.d.ts +94 -0
- package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
- package/dist/components/ui/avatar.d.ts +244 -0
- package/dist/components/ui/avatar.d.ts.map +1 -0
- package/dist/components/ui/badge.d.ts +163 -0
- package/dist/components/ui/badge.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +281 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/button.d.ts +129 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/calendar.d.ts +169 -0
- package/dist/components/ui/calendar.d.ts.map +1 -0
- package/dist/components/ui/card.d.ts +365 -0
- package/dist/components/ui/card.d.ts.map +1 -0
- package/dist/components/ui/carousel.d.ts +369 -0
- package/dist/components/ui/carousel.d.ts.map +1 -0
- package/dist/components/ui/chart.d.ts +442 -0
- package/dist/components/ui/chart.d.ts.map +1 -0
- package/dist/components/ui/checkbox.d.ts +88 -0
- package/dist/components/ui/checkbox.d.ts.map +1 -0
- package/dist/components/ui/collapsible.d.ts +182 -0
- package/dist/components/ui/collapsible.d.ts.map +1 -0
- package/dist/components/ui/combobox.d.ts +270 -0
- package/dist/components/ui/combobox.d.ts.map +1 -0
- package/dist/components/ui/command.d.ts +355 -0
- package/dist/components/ui/command.d.ts.map +1 -0
- package/dist/components/ui/container.d.ts +102 -0
- package/dist/components/ui/container.d.ts.map +1 -0
- package/dist/components/ui/context-menu.d.ts +339 -0
- package/dist/components/ui/context-menu.d.ts.map +1 -0
- package/dist/components/ui/date-picker.d.ts +145 -0
- package/dist/components/ui/date-picker.d.ts.map +1 -0
- package/dist/components/ui/dialog.d.ts +322 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/drawer.d.ts +154 -0
- package/dist/components/ui/drawer.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +349 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/components/ui/empty-state.d.ts +133 -0
- package/dist/components/ui/empty-state.d.ts.map +1 -0
- package/dist/components/ui/hover-card.d.ts +109 -0
- package/dist/components/ui/hover-card.d.ts.map +1 -0
- package/dist/components/ui/input.d.ts +89 -0
- package/dist/components/ui/input.d.ts.map +1 -0
- package/dist/components/ui/label.d.ts +93 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/menubar.d.ts +306 -0
- package/dist/components/ui/menubar.d.ts.map +1 -0
- package/dist/components/ui/navigation-menu.d.ts +318 -0
- package/dist/components/ui/navigation-menu.d.ts.map +1 -0
- package/dist/components/ui/pagination.d.ts +343 -0
- package/dist/components/ui/pagination.d.ts.map +1 -0
- package/dist/components/ui/popover.d.ts +178 -0
- package/dist/components/ui/popover.d.ts.map +1 -0
- package/dist/components/ui/progress.d.ts +64 -0
- package/dist/components/ui/progress.d.ts.map +1 -0
- package/dist/components/ui/radio-group.d.ts +144 -0
- package/dist/components/ui/radio-group.d.ts.map +1 -0
- package/dist/components/ui/resizable.d.ts +164 -0
- package/dist/components/ui/resizable.d.ts.map +1 -0
- package/dist/components/ui/scroll-area.d.ts +82 -0
- package/dist/components/ui/scroll-area.d.ts.map +1 -0
- package/dist/components/ui/select.d.ts +316 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/separator.d.ts +80 -0
- package/dist/components/ui/separator.d.ts.map +1 -0
- package/dist/components/ui/sheet.d.ts +346 -0
- package/dist/components/ui/sheet.d.ts.map +1 -0
- package/dist/components/ui/sidebar.d.ts +1561 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/skeleton.d.ts +66 -0
- package/dist/components/ui/skeleton.d.ts.map +1 -0
- package/dist/components/ui/slider.d.ts +95 -0
- package/dist/components/ui/slider.d.ts.map +1 -0
- package/dist/components/ui/sonner.d.ts +101 -0
- package/dist/components/ui/sonner.d.ts.map +1 -0
- package/dist/components/ui/stack.d.ts +192 -0
- package/dist/components/ui/stack.d.ts.map +1 -0
- package/dist/components/ui/stories/accordion.stories.d.ts +71 -0
- package/dist/components/ui/stories/accordion.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/alert-dialog.stories.d.ts +39 -0
- package/dist/components/ui/stories/alert-dialog.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/alert.stories.d.ts +48 -0
- package/dist/components/ui/stories/alert.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/aspect-ratio.stories.d.ts +53 -0
- package/dist/components/ui/stories/aspect-ratio.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/avatar.stories.d.ts +49 -0
- package/dist/components/ui/stories/avatar.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/badge.stories.d.ts +64 -0
- package/dist/components/ui/stories/badge.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/breadcrumb.stories.d.ts +27 -0
- package/dist/components/ui/stories/breadcrumb.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/button.stories.d.ts +92 -0
- package/dist/components/ui/stories/button.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/calendar.stories.d.ts +94 -0
- package/dist/components/ui/stories/calendar.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/card.stories.d.ts +29 -0
- package/dist/components/ui/stories/card.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/carousel.stories.d.ts +42 -0
- package/dist/components/ui/stories/carousel.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/chart.stories.d.ts +51 -0
- package/dist/components/ui/stories/chart.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/checkbox.stories.d.ts +72 -0
- package/dist/components/ui/stories/checkbox.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/cn.stories.d.ts +19 -0
- package/dist/components/ui/stories/cn.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/collapsible.stories.d.ts +51 -0
- package/dist/components/ui/stories/collapsible.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/colors.stories.d.ts +31 -0
- package/dist/components/ui/stories/colors.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/combobox.stories.d.ts +89 -0
- package/dist/components/ui/stories/combobox.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/command.stories.d.ts +69 -0
- package/dist/components/ui/stories/command.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/container.stories.d.ts +42 -0
- package/dist/components/ui/stories/container.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/context-menu.stories.d.ts +32 -0
- package/dist/components/ui/stories/context-menu.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/date-picker.stories.d.ts +67 -0
- package/dist/components/ui/stories/date-picker.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/dialog.stories.d.ts +48 -0
- package/dist/components/ui/stories/dialog.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/drawer.stories.d.ts +33 -0
- package/dist/components/ui/stories/drawer.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/dropdown-menu.stories.d.ts +31 -0
- package/dist/components/ui/stories/dropdown-menu.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/empty-state.stories.d.ts +74 -0
- package/dist/components/ui/stories/empty-state.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/hover-card.stories.d.ts +35 -0
- package/dist/components/ui/stories/hover-card.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/input.stories.d.ts +69 -0
- package/dist/components/ui/stories/input.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/label.stories.d.ts +47 -0
- package/dist/components/ui/stories/label.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/menubar.stories.d.ts +39 -0
- package/dist/components/ui/stories/menubar.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/navigation-menu.stories.d.ts +44 -0
- package/dist/components/ui/stories/navigation-menu.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/pagination.stories.d.ts +33 -0
- package/dist/components/ui/stories/pagination.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/popover.stories.d.ts +36 -0
- package/dist/components/ui/stories/popover.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/progress.stories.d.ts +38 -0
- package/dist/components/ui/stories/progress.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/radio-group.stories.d.ts +76 -0
- package/dist/components/ui/stories/radio-group.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/resizable.stories.d.ts +49 -0
- package/dist/components/ui/stories/resizable.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/scroll-area.stories.d.ts +35 -0
- package/dist/components/ui/stories/scroll-area.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/select.stories.d.ts +51 -0
- package/dist/components/ui/stories/select.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/separator.stories.d.ts +58 -0
- package/dist/components/ui/stories/separator.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/sheet.stories.d.ts +43 -0
- package/dist/components/ui/stories/sheet.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/sidebar.stories.d.ts +60 -0
- package/dist/components/ui/stories/sidebar.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/skeleton.stories.d.ts +42 -0
- package/dist/components/ui/stories/skeleton.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/slider.stories.d.ts +99 -0
- package/dist/components/ui/stories/slider.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/sonner.stories.d.ts +9 -0
- package/dist/components/ui/stories/sonner.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/stack.stories.d.ts +39 -0
- package/dist/components/ui/stories/stack.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/switch.stories.d.ts +71 -0
- package/dist/components/ui/stories/switch.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/table.stories.d.ts +40 -0
- package/dist/components/ui/stories/table.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/tabs.stories.d.ts +62 -0
- package/dist/components/ui/stories/tabs.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/text-field.stories.d.ts +78 -0
- package/dist/components/ui/stories/text-field.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/textarea.stories.d.ts +57 -0
- package/dist/components/ui/stories/textarea.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/theme-toggle.stories.d.ts +71 -0
- package/dist/components/ui/stories/theme-toggle.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/theme.stories.d.ts +51 -0
- package/dist/components/ui/stories/theme.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/toggle-group.stories.d.ts +71 -0
- package/dist/components/ui/stories/toggle-group.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/toggle.stories.d.ts +78 -0
- package/dist/components/ui/stories/toggle.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/tooltip.stories.d.ts +37 -0
- package/dist/components/ui/stories/tooltip.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/typography.stories.d.ts +137 -0
- package/dist/components/ui/stories/typography.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/use-mobile.stories.d.ts +20 -0
- package/dist/components/ui/stories/use-mobile.stories.d.ts.map +1 -0
- package/dist/components/ui/stories/use-theme.stories.d.ts +23 -0
- package/dist/components/ui/stories/use-theme.stories.d.ts.map +1 -0
- package/dist/components/ui/switch.d.ts +84 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/components/ui/table.d.ts +321 -0
- package/dist/components/ui/table.d.ts.map +1 -0
- package/dist/components/ui/tabs.d.ts +260 -0
- package/dist/components/ui/tabs.d.ts.map +1 -0
- package/dist/components/ui/text-field.d.ts +157 -0
- package/dist/components/ui/text-field.d.ts.map +1 -0
- package/dist/components/ui/textarea.d.ts +84 -0
- package/dist/components/ui/textarea.d.ts.map +1 -0
- package/dist/components/ui/theme-toggle.d.ts +105 -0
- package/dist/components/ui/theme-toggle.d.ts.map +1 -0
- package/dist/components/ui/theme.d.ts +110 -0
- package/dist/components/ui/theme.d.ts.map +1 -0
- package/dist/components/ui/toggle-group.d.ts +133 -0
- package/dist/components/ui/toggle-group.d.ts.map +1 -0
- package/dist/components/ui/toggle.d.ts +84 -0
- package/dist/components/ui/toggle.d.ts.map +1 -0
- package/dist/components/ui/tooltip.d.ts +202 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/components/ui/typography.d.ts +287 -0
- package/dist/components/ui/typography.d.ts.map +1 -0
- package/dist/hooks/use-mobile.d.ts +74 -0
- package/dist/hooks/use-mobile.d.ts.map +1 -0
- package/dist/hooks/use-theme.d.ts +142 -0
- package/dist/hooks/use-theme.d.ts.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27498 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/utils.d.ts +43 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/llm/colors.md +273 -0
- package/docs/llm/components/buttons.md +68 -0
- package/docs/llm/components/cards.md +53 -0
- package/docs/llm/components/display.md +134 -0
- package/docs/llm/components/feedback.md +96 -0
- package/docs/llm/components/forms.md +90 -0
- package/docs/llm/components/layout.md +59 -0
- package/docs/llm/components/menus.md +70 -0
- package/docs/llm/components/navigation.md +80 -0
- package/docs/llm/components/overlays.md +83 -0
- package/docs/llm/components/tables.md +73 -0
- package/docs/llm/components/typography.md +199 -0
- package/docs/llm/components/utilities.md +114 -0
- package/docs/llm/guide.md +165 -0
- package/llms.txt +122 -0
- package/package.json +104 -0
- package/src/components/ui/accordion.tsx +285 -0
- package/src/components/ui/alert-dialog.tsx +387 -0
- package/src/components/ui/alert.tsx +243 -0
- package/src/components/ui/aspect-ratio.tsx +99 -0
- package/src/components/ui/avatar.tsx +288 -0
- package/src/components/ui/badge.tsx +205 -0
- package/src/components/ui/breadcrumb.tsx +378 -0
- package/src/components/ui/button.tsx +195 -0
- package/src/components/ui/calendar.tsx +371 -0
- package/src/components/ui/card.tsx +447 -0
- package/src/components/ui/carousel.tsx +624 -0
- package/src/components/ui/chart.tsx +802 -0
- package/src/components/ui/checkbox.tsx +113 -0
- package/src/components/ui/collapsible.tsx +207 -0
- package/src/components/ui/combobox.tsx +373 -0
- package/src/components/ui/command.tsx +518 -0
- package/src/components/ui/container.tsx +114 -0
- package/src/components/ui/context-menu.tsx +563 -0
- package/src/components/ui/date-picker.tsx +213 -0
- package/src/components/ui/dialog.tsx +447 -0
- package/src/components/ui/drawer.tsx +273 -0
- package/src/components/ui/dropdown-menu.tsx +578 -0
- package/src/components/ui/empty-state.tsx +145 -0
- package/src/components/ui/hover-card.tsx +144 -0
- package/src/components/ui/input.tsx +106 -0
- package/src/components/ui/label.tsx +110 -0
- package/src/components/ui/menubar.tsx +553 -0
- package/src/components/ui/navigation-menu.tsx +471 -0
- package/src/components/ui/pagination.tsx +456 -0
- package/src/components/ui/popover.tsx +216 -0
- package/src/components/ui/progress.tsx +88 -0
- package/src/components/ui/radio-group.tsx +183 -0
- package/src/components/ui/resizable.tsx +209 -0
- package/src/components/ui/scroll-area.tsx +132 -0
- package/src/components/ui/select.tsx +485 -0
- package/src/components/ui/separator.tsx +101 -0
- package/src/components/ui/sheet.tsx +495 -0
- package/src/components/ui/sidebar.tsx +2211 -0
- package/src/components/ui/skeleton.tsx +76 -0
- package/src/components/ui/slider.tsx +147 -0
- package/src/components/ui/sonner.tsx +120 -0
- package/src/components/ui/stack.tsx +180 -0
- package/src/components/ui/stories/accordion.stories.tsx +429 -0
- package/src/components/ui/stories/alert-dialog.stories.tsx +519 -0
- package/src/components/ui/stories/alert.stories.tsx +228 -0
- package/src/components/ui/stories/aspect-ratio.stories.tsx +200 -0
- package/src/components/ui/stories/avatar.stories.tsx +317 -0
- package/src/components/ui/stories/badge.stories.tsx +260 -0
- package/src/components/ui/stories/breadcrumb.stories.tsx +482 -0
- package/src/components/ui/stories/button.stories.tsx +266 -0
- package/src/components/ui/stories/calendar.stories.tsx +375 -0
- package/src/components/ui/stories/card.stories.tsx +308 -0
- package/src/components/ui/stories/carousel.stories.tsx +328 -0
- package/src/components/ui/stories/chart.stories.tsx +430 -0
- package/src/components/ui/stories/checkbox.stories.tsx +297 -0
- package/src/components/ui/stories/cn.stories.tsx +433 -0
- package/src/components/ui/stories/collapsible.stories.tsx +256 -0
- package/src/components/ui/stories/colors.stories.tsx +502 -0
- package/src/components/ui/stories/combobox.stories.tsx +301 -0
- package/src/components/ui/stories/command.stories.tsx +632 -0
- package/src/components/ui/stories/container.stories.tsx +250 -0
- package/src/components/ui/stories/context-menu.stories.tsx +446 -0
- package/src/components/ui/stories/date-picker.stories.tsx +378 -0
- package/src/components/ui/stories/dialog.stories.tsx +535 -0
- package/src/components/ui/stories/drawer.stories.tsx +364 -0
- package/src/components/ui/stories/dropdown-menu.stories.tsx +374 -0
- package/src/components/ui/stories/empty-state.stories.tsx +244 -0
- package/src/components/ui/stories/hover-card.stories.tsx +355 -0
- package/src/components/ui/stories/input.stories.tsx +289 -0
- package/src/components/ui/stories/label.stories.tsx +294 -0
- package/src/components/ui/stories/menubar.stories.tsx +764 -0
- package/src/components/ui/stories/navigation-menu.stories.tsx +539 -0
- package/src/components/ui/stories/pagination.stories.tsx +604 -0
- package/src/components/ui/stories/popover.stories.tsx +392 -0
- package/src/components/ui/stories/progress.stories.tsx +218 -0
- package/src/components/ui/stories/radio-group.stories.tsx +400 -0
- package/src/components/ui/stories/resizable.stories.tsx +417 -0
- package/src/components/ui/stories/scroll-area.stories.tsx +180 -0
- package/src/components/ui/stories/select.stories.tsx +389 -0
- package/src/components/ui/stories/separator.stories.tsx +192 -0
- package/src/components/ui/stories/sheet.stories.tsx +468 -0
- package/src/components/ui/stories/sidebar.stories.tsx +731 -0
- package/src/components/ui/stories/skeleton.stories.tsx +216 -0
- package/src/components/ui/stories/slider.stories.tsx +321 -0
- package/src/components/ui/stories/sonner.stories.tsx +373 -0
- package/src/components/ui/stories/stack.stories.tsx +222 -0
- package/src/components/ui/stories/switch.stories.tsx +202 -0
- package/src/components/ui/stories/table.stories.tsx +541 -0
- package/src/components/ui/stories/tabs.stories.tsx +544 -0
- package/src/components/ui/stories/text-field.stories.tsx +280 -0
- package/src/components/ui/stories/textarea.stories.tsx +245 -0
- package/src/components/ui/stories/theme-toggle.stories.tsx +275 -0
- package/src/components/ui/stories/theme.stories.tsx +412 -0
- package/src/components/ui/stories/toggle-group.stories.tsx +337 -0
- package/src/components/ui/stories/toggle.stories.tsx +325 -0
- package/src/components/ui/stories/tooltip.stories.tsx +444 -0
- package/src/components/ui/stories/typography.stories.tsx +1586 -0
- package/src/components/ui/stories/use-mobile.stories.tsx +420 -0
- package/src/components/ui/stories/use-theme.stories.tsx +531 -0
- package/src/components/ui/switch.tsx +106 -0
- package/src/components/ui/table.tsx +424 -0
- package/src/components/ui/tabs.tsx +316 -0
- package/src/components/ui/text-field.tsx +206 -0
- package/src/components/ui/textarea.tsx +98 -0
- package/src/components/ui/theme-toggle.tsx +185 -0
- package/src/components/ui/theme.tsx +148 -0
- package/src/components/ui/toggle-group.tsx +196 -0
- package/src/components/ui/toggle.tsx +115 -0
- package/src/components/ui/tooltip.tsx +253 -0
- package/src/components/ui/typography.tsx +468 -0
- package/src/hooks/use-mobile.ts +91 -0
- package/src/hooks/use-theme.ts +319 -0
- package/src/index.ts +77 -0
- package/src/lib/utils.ts +57 -0
- package/src/styles/globals.css +160 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
3
|
+
import { CheckIcon } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A versatile checkbox component for binary and multi-selection interfaces
|
|
9
|
+
*
|
|
10
|
+
* Built on Radix UI Checkbox primitive, this component provides a fully accessible
|
|
11
|
+
* checkbox with support for controlled/uncontrolled states, indeterminate state,
|
|
12
|
+
* and comprehensive form integration. Features consistent styling with the design
|
|
13
|
+
* system and proper keyboard navigation.
|
|
14
|
+
*
|
|
15
|
+
* @example Basic usage
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <div className="flex items-center space-x-2">
|
|
18
|
+
* <Checkbox id="terms" />
|
|
19
|
+
* <Label htmlFor="terms">Accept terms and conditions</Label>
|
|
20
|
+
* </div>
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example Controlled checkbox with state management
|
|
24
|
+
* ```tsx
|
|
25
|
+
* const [checked, setChecked] = useState(false);
|
|
26
|
+
*
|
|
27
|
+
* <div className="flex items-center space-x-2">
|
|
28
|
+
* <Checkbox
|
|
29
|
+
* id="notifications"
|
|
30
|
+
* checked={checked}
|
|
31
|
+
* onCheckedChange={setChecked}
|
|
32
|
+
* />
|
|
33
|
+
* <Label htmlFor="notifications">Send me notifications</Label>
|
|
34
|
+
* </div>
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @example Indeterminate state for parent-child relationships
|
|
38
|
+
* ```tsx
|
|
39
|
+
* const [parentState, setParentState] = useState("indeterminate");
|
|
40
|
+
*
|
|
41
|
+
* <Checkbox
|
|
42
|
+
* checked={parentState}
|
|
43
|
+
* onCheckedChange={(checked) => {
|
|
44
|
+
* setParentState(checked);
|
|
45
|
+
* // Update child checkboxes accordingly
|
|
46
|
+
* }}
|
|
47
|
+
* />
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @example Form integration with multiple selections
|
|
51
|
+
* ```tsx
|
|
52
|
+
* const [preferences, setPreferences] = useState([]);
|
|
53
|
+
*
|
|
54
|
+
* {options.map((option) => (
|
|
55
|
+
* <div key={option.id} className="flex items-center space-x-2">
|
|
56
|
+
* <Checkbox
|
|
57
|
+
* id={option.id}
|
|
58
|
+
* name="preferences"
|
|
59
|
+
* value={option.value}
|
|
60
|
+
* checked={preferences.includes(option.id)}
|
|
61
|
+
* onCheckedChange={(checked) => {
|
|
62
|
+
* setPreferences(prev =>
|
|
63
|
+
* checked
|
|
64
|
+
* ? [...prev, option.id]
|
|
65
|
+
* : prev.filter(id => id !== option.id)
|
|
66
|
+
* );
|
|
67
|
+
* }}
|
|
68
|
+
* />
|
|
69
|
+
* <Label htmlFor={option.id}>{option.label}</Label>
|
|
70
|
+
* </div>
|
|
71
|
+
* ))}
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* @see {@link https://ui.shadcn.com/docs/components/checkbox} for design patterns
|
|
75
|
+
* @since 1.0.0
|
|
76
|
+
* @see {@link Label} for accessible labeling
|
|
77
|
+
* @see {@link Switch} for boolean toggles
|
|
78
|
+
* @see {@link RadioGroup} for single selection scenarios
|
|
79
|
+
*
|
|
80
|
+
* @param className - Additional CSS classes to apply to the checkbox
|
|
81
|
+
* @param checked - Controlled checked state (boolean | "indeterminate")
|
|
82
|
+
* @param defaultChecked - Default checked state for uncontrolled usage
|
|
83
|
+
* @param onCheckedChange - Callback when the checked state changes
|
|
84
|
+
* @param disabled - Whether the checkbox is disabled
|
|
85
|
+
* @param required - Whether the checkbox is required for form validation
|
|
86
|
+
* @param name - Form field name for form submission
|
|
87
|
+
* @param value - Form field value for form submission
|
|
88
|
+
* @param id - Unique identifier, used with Label htmlFor attribute
|
|
89
|
+
*/
|
|
90
|
+
function Checkbox({
|
|
91
|
+
className,
|
|
92
|
+
...props
|
|
93
|
+
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
|
94
|
+
return (
|
|
95
|
+
<CheckboxPrimitive.Root
|
|
96
|
+
data-slot="checkbox"
|
|
97
|
+
className={cn(
|
|
98
|
+
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
99
|
+
className,
|
|
100
|
+
)}
|
|
101
|
+
{...props}
|
|
102
|
+
>
|
|
103
|
+
<CheckboxPrimitive.Indicator
|
|
104
|
+
data-slot="checkbox-indicator"
|
|
105
|
+
className="flex items-center justify-center text-current transition-none"
|
|
106
|
+
>
|
|
107
|
+
<CheckIcon className="size-3.5" />
|
|
108
|
+
</CheckboxPrimitive.Indicator>
|
|
109
|
+
</CheckboxPrimitive.Root>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export { Checkbox };
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* An interactive component which expands/collapses a panel
|
|
6
|
+
*
|
|
7
|
+
* A flexible container component built on Radix UI primitives that allows users to
|
|
8
|
+
* toggle the visibility of content sections. Unlike Accordion, Collapsible provides
|
|
9
|
+
* more flexibility without enforcing specific visual structures. Perfect for FAQ sections,
|
|
10
|
+
* expandable details, settings panels, and any content that should be initially hidden.
|
|
11
|
+
*
|
|
12
|
+
* @component
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* // Basic controlled collapsible
|
|
16
|
+
* const [isOpen, setIsOpen] = React.useState(false)
|
|
17
|
+
*
|
|
18
|
+
* <Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
|
19
|
+
* <CollapsibleTrigger asChild>
|
|
20
|
+
* <Button variant="outline">
|
|
21
|
+
* Can I use this in my project?
|
|
22
|
+
* <ChevronDown className="h-4 w-4" />
|
|
23
|
+
* </Button>
|
|
24
|
+
* </CollapsibleTrigger>
|
|
25
|
+
* <CollapsibleContent>
|
|
26
|
+
* <div className="p-4 border rounded-md bg-muted/50">
|
|
27
|
+
* Yes. Free to use for personal and commercial projects.
|
|
28
|
+
* No attribution required.
|
|
29
|
+
* </div>
|
|
30
|
+
* </CollapsibleContent>
|
|
31
|
+
* </Collapsible>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* // Uncontrolled collapsible with custom styling
|
|
37
|
+
* <Collapsible className="w-full space-y-2">
|
|
38
|
+
* <div className="flex items-center justify-between">
|
|
39
|
+
* <h4 className="text-sm font-semibold">@radix-ui/primitives</h4>
|
|
40
|
+
* <CollapsibleTrigger asChild>
|
|
41
|
+
* <Button variant="ghost" size="sm">
|
|
42
|
+
* <ChevronDown className="h-4 w-4" />
|
|
43
|
+
* <span className="sr-only">Toggle</span>
|
|
44
|
+
* </Button>
|
|
45
|
+
* </CollapsibleTrigger>
|
|
46
|
+
* </div>
|
|
47
|
+
* <CollapsibleContent className="space-y-2">
|
|
48
|
+
* <div className="rounded-md border px-4 py-2 text-sm shadow-sm">
|
|
49
|
+
* @radix-ui/react-collapsible
|
|
50
|
+
* </div>
|
|
51
|
+
* </CollapsibleContent>
|
|
52
|
+
* </Collapsible>
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* // FAQ-style implementation
|
|
58
|
+
* <Collapsible>
|
|
59
|
+
* <CollapsibleTrigger className="flex w-full items-center justify-between py-4 text-sm font-medium transition-all hover:underline">
|
|
60
|
+
* How do I get started?
|
|
61
|
+
* <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
|
62
|
+
* </CollapsibleTrigger>
|
|
63
|
+
* <CollapsibleContent className="overflow-hidden text-sm transition-all">
|
|
64
|
+
* <div className="pb-4 pt-0">
|
|
65
|
+
* Getting started is easy! Simply follow our quick setup guide...
|
|
66
|
+
* </div>
|
|
67
|
+
* </CollapsibleContent>
|
|
68
|
+
* </Collapsible>
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @accessibility
|
|
72
|
+
* - Full keyboard navigation support (Space/Enter to toggle)
|
|
73
|
+
* - ARIA expanded state automatically managed
|
|
74
|
+
* - Content properly hidden from screen readers when collapsed
|
|
75
|
+
* - Focus management and screen reader announcements
|
|
76
|
+
* - Built on WAI-ARIA design patterns for disclosure widgets
|
|
77
|
+
*
|
|
78
|
+
* @see {@link https://ui.shadcn.com/docs/components/collapsible} for design patterns
|
|
79
|
+
* @see {@link CollapsibleTrigger} - Interactive element to toggle visibility
|
|
80
|
+
* @see {@link CollapsibleContent} - Content container that can be hidden/shown
|
|
81
|
+
* @since 1.0.0
|
|
82
|
+
*/
|
|
83
|
+
function Collapsible({
|
|
84
|
+
...props
|
|
85
|
+
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
|
86
|
+
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Interactive trigger element that toggles collapsible content visibility
|
|
91
|
+
*
|
|
92
|
+
* The clickable element that controls the expanded/collapsed state of the associated
|
|
93
|
+
* CollapsibleContent. Supports the asChild prop for composition with custom elements
|
|
94
|
+
* like buttons or other interactive components.
|
|
95
|
+
*
|
|
96
|
+
* @component
|
|
97
|
+
* @example
|
|
98
|
+
* ```tsx
|
|
99
|
+
* // Simple text trigger
|
|
100
|
+
* <CollapsibleTrigger>
|
|
101
|
+
* Can I use this in my project?
|
|
102
|
+
* </CollapsibleTrigger>
|
|
103
|
+
* ```
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```tsx
|
|
107
|
+
* // Composed with Button component
|
|
108
|
+
* <CollapsibleTrigger asChild>
|
|
109
|
+
* <Button variant="outline" className="w-full justify-between">
|
|
110
|
+
* Advanced Settings
|
|
111
|
+
* <ChevronDown className="h-4 w-4" />
|
|
112
|
+
* </Button>
|
|
113
|
+
* </CollapsibleTrigger>
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```tsx
|
|
118
|
+
* // Custom styled trigger
|
|
119
|
+
* <CollapsibleTrigger className="flex w-full items-center justify-between py-4 text-left text-sm font-medium transition-all hover:underline">
|
|
120
|
+
* How does it work?
|
|
121
|
+
* <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
|
122
|
+
* </CollapsibleTrigger>
|
|
123
|
+
* ```
|
|
124
|
+
*
|
|
125
|
+
* @accessibility
|
|
126
|
+
* - Keyboard activation with Space or Enter keys
|
|
127
|
+
* - aria-expanded attribute reflects current expanded state
|
|
128
|
+
* - aria-controls connects trigger to its content panel
|
|
129
|
+
* - Role of button for screen reader compatibility
|
|
130
|
+
* - Focus visible indicator for keyboard navigation
|
|
131
|
+
*
|
|
132
|
+
* @since 1.0.0
|
|
133
|
+
*/
|
|
134
|
+
function CollapsibleTrigger({
|
|
135
|
+
...props
|
|
136
|
+
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
|
|
137
|
+
return (
|
|
138
|
+
<CollapsiblePrimitive.CollapsibleTrigger
|
|
139
|
+
data-slot="collapsible-trigger"
|
|
140
|
+
{...props}
|
|
141
|
+
/>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Content container that can be expanded or collapsed
|
|
147
|
+
*
|
|
148
|
+
* The panel containing content that is shown or hidden based on the collapsible state.
|
|
149
|
+
* Automatically handles smooth animations, height transitions, and accessibility attributes
|
|
150
|
+
* when toggling between expanded and collapsed states.
|
|
151
|
+
*
|
|
152
|
+
* @component
|
|
153
|
+
* @example
|
|
154
|
+
* ```tsx
|
|
155
|
+
* // Basic content panel
|
|
156
|
+
* <CollapsibleContent>
|
|
157
|
+
* <div className="rounded-md border px-4 py-2 text-sm">
|
|
158
|
+
* Yes. Free to use for personal and commercial projects.
|
|
159
|
+
* No attribution required.
|
|
160
|
+
* </div>
|
|
161
|
+
* </CollapsibleContent>
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```tsx
|
|
166
|
+
* // Content with custom styling and multiple elements
|
|
167
|
+
* <CollapsibleContent className="space-y-2">
|
|
168
|
+
* <div className="rounded-md border px-4 py-3 font-mono text-sm">
|
|
169
|
+
* @radix-ui/react-collapsible
|
|
170
|
+
* </div>
|
|
171
|
+
* <div className="rounded-md border px-4 py-3 font-mono text-sm">
|
|
172
|
+
* @radix-ui/react-dialog
|
|
173
|
+
* </div>
|
|
174
|
+
* </CollapsibleContent>
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```tsx
|
|
179
|
+
* // Content with overflow handling
|
|
180
|
+
* <CollapsibleContent className="overflow-hidden text-sm transition-all data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
|
|
181
|
+
* <div className="pb-4 pt-0">
|
|
182
|
+
* Content with custom animations and overflow handling.
|
|
183
|
+
* </div>
|
|
184
|
+
* </CollapsibleContent>
|
|
185
|
+
* ```
|
|
186
|
+
*
|
|
187
|
+
* @accessibility
|
|
188
|
+
* - Automatically hidden from accessibility tree when collapsed
|
|
189
|
+
* - Smooth height animations with data-state attributes
|
|
190
|
+
* - Properly associated with trigger via aria-controls
|
|
191
|
+
* - Supports custom animation classes and transitions
|
|
192
|
+
* - Content is announced by screen readers when expanded
|
|
193
|
+
*
|
|
194
|
+
* @since 1.0.0
|
|
195
|
+
*/
|
|
196
|
+
function CollapsibleContent({
|
|
197
|
+
...props
|
|
198
|
+
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
|
|
199
|
+
return (
|
|
200
|
+
<CollapsiblePrimitive.CollapsibleContent
|
|
201
|
+
data-slot="collapsible-content"
|
|
202
|
+
{...props}
|
|
203
|
+
/>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Check, ChevronsUpDown } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
import { Button } from "@/components/ui/button";
|
|
8
|
+
import {
|
|
9
|
+
Command,
|
|
10
|
+
CommandEmpty,
|
|
11
|
+
CommandGroup,
|
|
12
|
+
CommandInput,
|
|
13
|
+
CommandItem,
|
|
14
|
+
CommandList,
|
|
15
|
+
} from "@/components/ui/command";
|
|
16
|
+
import {
|
|
17
|
+
Popover,
|
|
18
|
+
PopoverContent,
|
|
19
|
+
PopoverTrigger,
|
|
20
|
+
} from "@/components/ui/popover";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Option structure for combobox items
|
|
24
|
+
*
|
|
25
|
+
* Defines the shape of each option that can be displayed and selected
|
|
26
|
+
* in the combobox dropdown list.
|
|
27
|
+
*
|
|
28
|
+
* @since 1.0.0
|
|
29
|
+
*/
|
|
30
|
+
export type ComboboxOption = {
|
|
31
|
+
/**
|
|
32
|
+
* Unique identifier for this option
|
|
33
|
+
*
|
|
34
|
+
* Used for selection tracking and should be unique within the options array.
|
|
35
|
+
*/
|
|
36
|
+
value: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Display text shown to users in the dropdown
|
|
40
|
+
*
|
|
41
|
+
* Should be descriptive and help users understand what this option represents.
|
|
42
|
+
*/
|
|
43
|
+
label: string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Whether this option is disabled and cannot be selected
|
|
47
|
+
*
|
|
48
|
+
* Disabled options remain visible but are not selectable and appear dimmed.
|
|
49
|
+
*
|
|
50
|
+
* @default false
|
|
51
|
+
*/
|
|
52
|
+
disabled?: boolean;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Properties for the Combobox component
|
|
57
|
+
*
|
|
58
|
+
* Comprehensive configuration options for customizing combobox behavior,
|
|
59
|
+
* appearance, and interaction patterns.
|
|
60
|
+
*
|
|
61
|
+
* @since 1.0.0
|
|
62
|
+
*/
|
|
63
|
+
export type ComboboxProps = {
|
|
64
|
+
/**
|
|
65
|
+
* Array of options to display in the combobox dropdown
|
|
66
|
+
*
|
|
67
|
+
* Each option should have a unique value and descriptive label.
|
|
68
|
+
* Options can be disabled to prevent selection while remaining visible.
|
|
69
|
+
*/
|
|
70
|
+
options: ComboboxOption[];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Currently selected value from the options array
|
|
74
|
+
*
|
|
75
|
+
* Should match the value property of one of the provided options.
|
|
76
|
+
* Use with onValueChange for controlled component behavior.
|
|
77
|
+
*/
|
|
78
|
+
value?: string;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Callback fired when the selected value changes
|
|
82
|
+
*
|
|
83
|
+
* Receives the new selected value as a string. If the same option
|
|
84
|
+
* is clicked again, an empty string is passed to allow deselection.
|
|
85
|
+
*
|
|
86
|
+
* @param value - The newly selected option value or empty string for deselection
|
|
87
|
+
*/
|
|
88
|
+
onValueChange?: (value: string) => void;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Placeholder text displayed when no option is selected
|
|
92
|
+
*
|
|
93
|
+
* Should be descriptive and help users understand what they're selecting.
|
|
94
|
+
*
|
|
95
|
+
* @default "Select option..."
|
|
96
|
+
*/
|
|
97
|
+
placeholder?: string;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Text displayed when search query returns no matching options
|
|
101
|
+
*
|
|
102
|
+
* Customize this message to match your application's tone and context.
|
|
103
|
+
*
|
|
104
|
+
* @default "No option found."
|
|
105
|
+
*/
|
|
106
|
+
emptyText?: string;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Placeholder text for the search input field
|
|
110
|
+
*
|
|
111
|
+
* Should guide users on how to search effectively within your option set.
|
|
112
|
+
*
|
|
113
|
+
* @default "Search options..."
|
|
114
|
+
*/
|
|
115
|
+
searchPlaceholder?: string;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Additional CSS classes for the root container element
|
|
119
|
+
*
|
|
120
|
+
* Use for custom spacing, positioning, or layout adjustments.
|
|
121
|
+
*/
|
|
122
|
+
className?: string;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Whether the entire combobox is disabled
|
|
126
|
+
*
|
|
127
|
+
* When disabled, the trigger button cannot be clicked and the
|
|
128
|
+
* dropdown cannot be opened. Use individual option.disabled for
|
|
129
|
+
* granular control.
|
|
130
|
+
*
|
|
131
|
+
* @default false
|
|
132
|
+
*/
|
|
133
|
+
disabled?: boolean;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Additional CSS classes for the trigger button
|
|
137
|
+
*
|
|
138
|
+
* Commonly used to control width (e.g., "w-[300px]"), styling variants,
|
|
139
|
+
* or responsive behavior. For best UX, consider matching popoverClassName width.
|
|
140
|
+
*
|
|
141
|
+
* @example "w-full sm:w-[280px] border-primary/50"
|
|
142
|
+
*/
|
|
143
|
+
buttonClassName?: string;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Additional CSS classes for the popover dropdown content
|
|
147
|
+
*
|
|
148
|
+
* Should typically match buttonClassName width for consistent alignment.
|
|
149
|
+
* Use to control positioning, width, and styling of the dropdown.
|
|
150
|
+
*
|
|
151
|
+
* @example "w-full sm:w-[280px] border-primary/50"
|
|
152
|
+
*/
|
|
153
|
+
popoverClassName?: string;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Searchable dropdown selection component with typeahead functionality
|
|
158
|
+
*
|
|
159
|
+
* A versatile combobox that combines a button trigger with a searchable dropdown list.
|
|
160
|
+
* Ideal for selecting from moderate to large lists of options where search functionality
|
|
161
|
+
* improves user experience. Built on top of shadcn/ui Command and Popover primitives.
|
|
162
|
+
*
|
|
163
|
+
* Use this component when:
|
|
164
|
+
* - You have 10+ options that would benefit from filtering
|
|
165
|
+
* - Users need to quickly find specific options
|
|
166
|
+
* - You want to provide a better UX than a basic select dropdown
|
|
167
|
+
* - You need real-time search/filtering capabilities
|
|
168
|
+
*
|
|
169
|
+
* For fewer options or simpler selection, consider using the Select component instead.
|
|
170
|
+
*
|
|
171
|
+
* @component
|
|
172
|
+
* @example
|
|
173
|
+
* Basic controlled combobox with framework selection
|
|
174
|
+
* ```tsx
|
|
175
|
+
* const [framework, setFramework] = useState("")
|
|
176
|
+
*
|
|
177
|
+
* const frameworks = [
|
|
178
|
+
* { value: "next", label: "Next.js" },
|
|
179
|
+
* { value: "remix", label: "Remix" },
|
|
180
|
+
* { value: "svelte", label: "SvelteKit" },
|
|
181
|
+
* { value: "nuxt", label: "Nuxt.js" },
|
|
182
|
+
* ]
|
|
183
|
+
*
|
|
184
|
+
* <Combobox
|
|
185
|
+
* options={frameworks}
|
|
186
|
+
* value={framework}
|
|
187
|
+
* onValueChange={setFramework}
|
|
188
|
+
* placeholder="Select framework..."
|
|
189
|
+
* searchPlaceholder="Search frameworks..."
|
|
190
|
+
* buttonClassName="w-[300px]"
|
|
191
|
+
* />
|
|
192
|
+
* ```
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* Combobox with disabled options and custom styling
|
|
196
|
+
* ```tsx
|
|
197
|
+
* const statusOptions = [
|
|
198
|
+
* { value: "active", label: "Active" },
|
|
199
|
+
* { value: "pending", label: "Pending" },
|
|
200
|
+
* { value: "archived", label: "Archived (Read Only)", disabled: true },
|
|
201
|
+
* { value: "deleted", label: "Deleted (Unavailable)", disabled: true },
|
|
202
|
+
* ]
|
|
203
|
+
*
|
|
204
|
+
* <Combobox
|
|
205
|
+
* options={statusOptions}
|
|
206
|
+
* value={status}
|
|
207
|
+
* onValueChange={setStatus}
|
|
208
|
+
* placeholder="Select status..."
|
|
209
|
+
* emptyText="No status found."
|
|
210
|
+
* buttonClassName="w-[250px] border-primary/50"
|
|
211
|
+
* popoverClassName="w-[250px]"
|
|
212
|
+
* />
|
|
213
|
+
* ```
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* Form integration with validation and error states
|
|
217
|
+
* ```tsx
|
|
218
|
+
* import { Label } from "@/components/ui/label"
|
|
219
|
+
*
|
|
220
|
+
* <form onSubmit={handleSubmit}>
|
|
221
|
+
* <div className="space-y-2">
|
|
222
|
+
* <Label htmlFor="country">Country</Label>
|
|
223
|
+
* <Combobox
|
|
224
|
+
* options={countries}
|
|
225
|
+
* value={formData.country}
|
|
226
|
+
* onValueChange={(value) =>
|
|
227
|
+
* setFormData(prev => ({ ...prev, country: value }))
|
|
228
|
+
* }
|
|
229
|
+
* placeholder="Choose a country..."
|
|
230
|
+
* searchPlaceholder="Type to search countries..."
|
|
231
|
+
* emptyText="Country not found."
|
|
232
|
+
* buttonClassName="w-full"
|
|
233
|
+
* disabled={isSubmitting}
|
|
234
|
+
* />
|
|
235
|
+
* {errors.country && (
|
|
236
|
+
* <p className="text-sm text-destructive">{errors.country}</p>
|
|
237
|
+
* )}
|
|
238
|
+
* </div>
|
|
239
|
+
* </form>
|
|
240
|
+
* ```
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* Responsive width with mobile optimization
|
|
244
|
+
* ```tsx
|
|
245
|
+
* <Combobox
|
|
246
|
+
* options={userOptions}
|
|
247
|
+
* value={selectedUser}
|
|
248
|
+
* onValueChange={setSelectedUser}
|
|
249
|
+
* placeholder="Select team member..."
|
|
250
|
+
* searchPlaceholder="Search users..."
|
|
251
|
+
* buttonClassName="w-full sm:w-[280px]"
|
|
252
|
+
* popoverClassName="w-full sm:w-[280px]"
|
|
253
|
+
* />
|
|
254
|
+
* ```
|
|
255
|
+
*
|
|
256
|
+
* @accessibility
|
|
257
|
+
*
|
|
258
|
+
* **ARIA Implementation:**
|
|
259
|
+
* - Uses `role="combobox"` on trigger button with proper `aria-expanded` state
|
|
260
|
+
* - Implements `aria-controls` to reference the popup when visible
|
|
261
|
+
* - Uses `aria-activedescendant` for focus management within the dropdown
|
|
262
|
+
* - Provides `aria-haspopup="listbox"` to indicate popup type
|
|
263
|
+
*
|
|
264
|
+
* **Keyboard Navigation (W3C ARIA 1.2 compliant):**
|
|
265
|
+
* - **Tab**: Moves focus to/from combobox in page tab order
|
|
266
|
+
* - **Down Arrow**: Opens dropdown and moves to first option, or navigates to next option
|
|
267
|
+
* - **Up Arrow**: Moves to previous option (when dropdown is open)
|
|
268
|
+
* - **Enter**: Selects the focused option and closes dropdown
|
|
269
|
+
* - **Escape**: Closes dropdown and returns focus to trigger
|
|
270
|
+
* - **Home/End**: Moves to first/last option in list
|
|
271
|
+
* - **Type-ahead**: Real-time filtering as user types in search input
|
|
272
|
+
*
|
|
273
|
+
* **Screen Reader Support:**
|
|
274
|
+
* - Announces current selection state and option count
|
|
275
|
+
* - Provides clear labels for empty states and loading states
|
|
276
|
+
* - Announces selection changes and dropdown open/close states
|
|
277
|
+
* - Disabled options are properly announced and skipped during navigation
|
|
278
|
+
*
|
|
279
|
+
* **Focus Management:**
|
|
280
|
+
* - DOM focus remains on combobox trigger for screen reader compatibility
|
|
281
|
+
* - Visual focus moves through options using `aria-activedescendant`
|
|
282
|
+
* - Search input automatically receives focus when dropdown opens
|
|
283
|
+
* - Focus returns to trigger when dropdown closes via Escape or selection
|
|
284
|
+
*
|
|
285
|
+
* **Visual Accessibility:**
|
|
286
|
+
* - Maintains sufficient color contrast for all states
|
|
287
|
+
* - Provides hover and focus indicators for all interactive elements
|
|
288
|
+
* - Disabled options are visually distinct and non-interactive
|
|
289
|
+
* - Clear visual feedback for selected state with check icon
|
|
290
|
+
*
|
|
291
|
+
* @performance
|
|
292
|
+
* - Efficient option lookup with single find() operation
|
|
293
|
+
* - Minimal re-renders through proper state management
|
|
294
|
+
* - Supports large option lists with built-in virtualization via cmdk
|
|
295
|
+
*
|
|
296
|
+
* @see {@link https://ui.shadcn.com/docs/components/combobox} shadcn/ui Combobox documentation
|
|
297
|
+
* @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/combobox/} W3C ARIA Combobox Pattern
|
|
298
|
+
* @see {@link Select} For simpler dropdowns without search functionality
|
|
299
|
+
* @see {@link Command} The underlying command menu component
|
|
300
|
+
* @see {@link Popover} The popover container component
|
|
301
|
+
* @since 1.0.0
|
|
302
|
+
*/
|
|
303
|
+
export function Combobox({
|
|
304
|
+
options,
|
|
305
|
+
value,
|
|
306
|
+
onValueChange,
|
|
307
|
+
placeholder = "Select option...",
|
|
308
|
+
emptyText = "No option found.",
|
|
309
|
+
searchPlaceholder = "Search options...",
|
|
310
|
+
className,
|
|
311
|
+
disabled = false,
|
|
312
|
+
buttonClassName,
|
|
313
|
+
popoverClassName,
|
|
314
|
+
}: ComboboxProps) {
|
|
315
|
+
const [open, setOpen] = React.useState(false);
|
|
316
|
+
|
|
317
|
+
const selectedOption = options.find((option) => option.value === value);
|
|
318
|
+
|
|
319
|
+
const handleSelect = (selectedValue: string) => {
|
|
320
|
+
const newValue = selectedValue === value ? "" : selectedValue;
|
|
321
|
+
onValueChange?.(newValue);
|
|
322
|
+
setOpen(false);
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<div className={className}>
|
|
327
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
328
|
+
<PopoverTrigger asChild>
|
|
329
|
+
<Button
|
|
330
|
+
variant="outline"
|
|
331
|
+
role="combobox"
|
|
332
|
+
aria-expanded={open}
|
|
333
|
+
disabled={disabled}
|
|
334
|
+
className={cn(
|
|
335
|
+
"w-full justify-between",
|
|
336
|
+
!selectedOption && "text-muted-foreground",
|
|
337
|
+
buttonClassName,
|
|
338
|
+
)}
|
|
339
|
+
>
|
|
340
|
+
{selectedOption ? selectedOption.label : placeholder}
|
|
341
|
+
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
342
|
+
</Button>
|
|
343
|
+
</PopoverTrigger>
|
|
344
|
+
<PopoverContent className={cn("w-full p-0", popoverClassName)}>
|
|
345
|
+
<Command>
|
|
346
|
+
<CommandInput placeholder={searchPlaceholder} className="h-9" />
|
|
347
|
+
<CommandList>
|
|
348
|
+
<CommandEmpty>{emptyText}</CommandEmpty>
|
|
349
|
+
<CommandGroup>
|
|
350
|
+
{options.map((option) => (
|
|
351
|
+
<CommandItem
|
|
352
|
+
key={option.value}
|
|
353
|
+
value={option.value}
|
|
354
|
+
disabled={option.disabled}
|
|
355
|
+
onSelect={handleSelect}
|
|
356
|
+
>
|
|
357
|
+
<Check
|
|
358
|
+
className={cn(
|
|
359
|
+
"mr-2 h-4 w-4",
|
|
360
|
+
value === option.value ? "opacity-100" : "opacity-0",
|
|
361
|
+
)}
|
|
362
|
+
/>
|
|
363
|
+
{option.label}
|
|
364
|
+
</CommandItem>
|
|
365
|
+
))}
|
|
366
|
+
</CommandGroup>
|
|
367
|
+
</CommandList>
|
|
368
|
+
</Command>
|
|
369
|
+
</PopoverContent>
|
|
370
|
+
</Popover>
|
|
371
|
+
</div>
|
|
372
|
+
);
|
|
373
|
+
}
|