@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,624 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
import useEmblaCarousel, {
|
|
4
|
+
type UseEmblaCarouselType,
|
|
5
|
+
} from "embla-carousel-react";
|
|
6
|
+
import { ArrowLeft, ArrowRight } from "lucide-react";
|
|
7
|
+
|
|
8
|
+
import { cn } from "@/lib/utils";
|
|
9
|
+
import { Button } from "@/components/ui/button";
|
|
10
|
+
|
|
11
|
+
type CarouselApi = UseEmblaCarouselType[1];
|
|
12
|
+
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
|
|
13
|
+
type CarouselOptions = UseCarouselParameters[0];
|
|
14
|
+
type CarouselPlugin = UseCarouselParameters[1];
|
|
15
|
+
|
|
16
|
+
type CarouselProps = {
|
|
17
|
+
opts?: CarouselOptions;
|
|
18
|
+
plugins?: CarouselPlugin;
|
|
19
|
+
orientation?: "horizontal" | "vertical";
|
|
20
|
+
setApi?: (api: CarouselApi) => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type CarouselContextProps = {
|
|
24
|
+
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
|
|
25
|
+
api: ReturnType<typeof useEmblaCarousel>[1];
|
|
26
|
+
scrollPrev: () => void;
|
|
27
|
+
scrollNext: () => void;
|
|
28
|
+
canScrollPrev: boolean;
|
|
29
|
+
canScrollNext: boolean;
|
|
30
|
+
} & CarouselProps;
|
|
31
|
+
|
|
32
|
+
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Hook to access carousel context and controls
|
|
36
|
+
*
|
|
37
|
+
* Must be used within a Carousel component. Provides access to the carousel
|
|
38
|
+
* API and navigation methods for building custom carousel interactions.
|
|
39
|
+
*
|
|
40
|
+
* @returns Carousel context containing API, navigation controls, and state
|
|
41
|
+
* @throws Error when used outside of Carousel component
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* function CarouselDots() {
|
|
46
|
+
* const { api, scrollNext, canScrollNext } = useCarousel();
|
|
47
|
+
* const [current, setCurrent] = useState(0);
|
|
48
|
+
*
|
|
49
|
+
* useEffect(() => {
|
|
50
|
+
* if (!api) return;
|
|
51
|
+
* setCurrent(api.selectedScrollSnap());
|
|
52
|
+
* api.on('select', () => setCurrent(api.selectedScrollSnap()));
|
|
53
|
+
* }, [api]);
|
|
54
|
+
*
|
|
55
|
+
* return (
|
|
56
|
+
* <div className="flex gap-2">
|
|
57
|
+
* {Array.from({ length: 5 }).map((_, i) => (
|
|
58
|
+
* <button
|
|
59
|
+
* key={i}
|
|
60
|
+
* className={current === i ? "active" : ""}
|
|
61
|
+
* onClick={() => api?.scrollTo(i)}
|
|
62
|
+
* />
|
|
63
|
+
* ))}
|
|
64
|
+
* </div>
|
|
65
|
+
* );
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @see {@link Carousel} - Main carousel component
|
|
70
|
+
* @since 1.0.0
|
|
71
|
+
*/
|
|
72
|
+
function useCarousel() {
|
|
73
|
+
const context = React.useContext(CarouselContext);
|
|
74
|
+
|
|
75
|
+
if (!context) {
|
|
76
|
+
throw new Error("useCarousel must be used within a <Carousel />");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return context;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A responsive carousel/slider component for content presentation
|
|
84
|
+
*
|
|
85
|
+
* The Carousel component provides a touch-friendly, keyboard-accessible way to
|
|
86
|
+
* browse through multiple items. Built on Embla Carousel, it supports various
|
|
87
|
+
* configurations including autoplay, loop, drag scrolling, and both horizontal
|
|
88
|
+
* and vertical orientations.
|
|
89
|
+
*
|
|
90
|
+
* @param opts - Embla Carousel configuration options (align, loop, etc.)
|
|
91
|
+
* @param plugins - Array of Embla Carousel plugins (autoplay, etc.)
|
|
92
|
+
* @param orientation - Layout direction: "horizontal" or "vertical" (default: "horizontal")
|
|
93
|
+
* @param setApi - Callback to receive the carousel API instance
|
|
94
|
+
* @param className - Additional CSS classes
|
|
95
|
+
* @param children - Carousel content (typically CarouselContent and navigation buttons)
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```tsx
|
|
99
|
+
* // Basic image carousel
|
|
100
|
+
* <Carousel className="w-full max-w-xs">
|
|
101
|
+
* <CarouselContent>
|
|
102
|
+
* {images.map((src, index) => (
|
|
103
|
+
* <CarouselItem key={index}>
|
|
104
|
+
* <img src={src} alt={`Slide ${index + 1}`} className="w-full" />
|
|
105
|
+
* </CarouselItem>
|
|
106
|
+
* ))}
|
|
107
|
+
* </CarouselContent>
|
|
108
|
+
* <CarouselPrevious />
|
|
109
|
+
* <CarouselNext />
|
|
110
|
+
* </Carousel>
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```tsx
|
|
115
|
+
* // Multi-item responsive carousel
|
|
116
|
+
* <Carousel
|
|
117
|
+
* opts={{ align: "start", loop: true }}
|
|
118
|
+
* className="w-full"
|
|
119
|
+
* >
|
|
120
|
+
* <CarouselContent className="-ml-2 md:-ml-4">
|
|
121
|
+
* {products.map((product, index) => (
|
|
122
|
+
* <CarouselItem key={index} className="pl-2 md:pl-4 md:basis-1/2 lg:basis-1/3">
|
|
123
|
+
* <Card>
|
|
124
|
+
* <CardContent className="p-4">
|
|
125
|
+
* <h3>{product.name}</h3>
|
|
126
|
+
* <p>{product.price}</p>
|
|
127
|
+
* </CardContent>
|
|
128
|
+
* </Card>
|
|
129
|
+
* </CarouselItem>
|
|
130
|
+
* ))}
|
|
131
|
+
* </CarouselContent>
|
|
132
|
+
* <CarouselPrevious />
|
|
133
|
+
* <CarouselNext />
|
|
134
|
+
* </Carousel>
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```tsx
|
|
139
|
+
* // Vertical carousel with autoplay
|
|
140
|
+
* import Autoplay from "embla-carousel-autoplay";
|
|
141
|
+
*
|
|
142
|
+
* <Carousel
|
|
143
|
+
* orientation="vertical"
|
|
144
|
+
* plugins={[Autoplay({ delay: 3000, stopOnInteraction: true })]}
|
|
145
|
+
* className="h-[400px]"
|
|
146
|
+
* >
|
|
147
|
+
* <CarouselContent className="h-full">
|
|
148
|
+
* {testimonials.map((testimonial, index) => (
|
|
149
|
+
* <CarouselItem key={index} className="basis-1/2">
|
|
150
|
+
* <div className="p-6">
|
|
151
|
+
* <blockquote>{testimonial.quote}</blockquote>
|
|
152
|
+
* <cite>{testimonial.author}</cite>
|
|
153
|
+
* </div>
|
|
154
|
+
* </CarouselItem>
|
|
155
|
+
* ))}
|
|
156
|
+
* </CarouselContent>
|
|
157
|
+
* </Carousel>
|
|
158
|
+
* ```
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```tsx
|
|
162
|
+
* // Controlled carousel with custom indicators
|
|
163
|
+
* function ControlledCarousel() {
|
|
164
|
+
* const [api, setApi] = useState<CarouselApi>();
|
|
165
|
+
* const [current, setCurrent] = useState(0);
|
|
166
|
+
*
|
|
167
|
+
* useEffect(() => {
|
|
168
|
+
* if (!api) return;
|
|
169
|
+
* setCurrent(api.selectedScrollSnap());
|
|
170
|
+
* api.on('select', () => setCurrent(api.selectedScrollSnap()));
|
|
171
|
+
* }, [api]);
|
|
172
|
+
*
|
|
173
|
+
* return (
|
|
174
|
+
* <div>
|
|
175
|
+
* <Carousel setApi={setApi}>
|
|
176
|
+
* <CarouselContent>...</CarouselContent>
|
|
177
|
+
* </Carousel>
|
|
178
|
+
* <div className="flex justify-center gap-2 mt-4">
|
|
179
|
+
* {Array.from({ length: 5 }).map((_, i) => (
|
|
180
|
+
* <button
|
|
181
|
+
* key={i}
|
|
182
|
+
* className={current === i ? "bg-primary" : "bg-muted"}
|
|
183
|
+
* onClick={() => api?.scrollTo(i)}
|
|
184
|
+
* />
|
|
185
|
+
* ))}
|
|
186
|
+
* </div>
|
|
187
|
+
* </div>
|
|
188
|
+
* );
|
|
189
|
+
* }
|
|
190
|
+
* ```
|
|
191
|
+
*
|
|
192
|
+
* @accessibility
|
|
193
|
+
* - Keyboard navigation with arrow keys and tab focus
|
|
194
|
+
* - Touch/swipe gestures and mouse drag support
|
|
195
|
+
* - Screen reader announcements for slide changes
|
|
196
|
+
* - ARIA roles and labels for navigation elements
|
|
197
|
+
* - Focus management and visible focus indicators
|
|
198
|
+
* - Respects reduced motion preferences
|
|
199
|
+
* - RTL layout support
|
|
200
|
+
*
|
|
201
|
+
* @see {@link https://ui.shadcn.com/docs/components/carousel} shadcn/ui carousel documentation
|
|
202
|
+
* @see {@link https://www.embla-carousel.com/} Embla Carousel documentation
|
|
203
|
+
* @see {@link useCarousel} Hook for accessing carousel controls
|
|
204
|
+
* @see {@link CarouselContent} Container for carousel items
|
|
205
|
+
* @see {@link CarouselItem} Individual carousel slide
|
|
206
|
+
* @see {@link CarouselPrevious} Previous navigation button
|
|
207
|
+
* @see {@link CarouselNext} Next navigation button
|
|
208
|
+
* @since 1.0.0
|
|
209
|
+
*/
|
|
210
|
+
function Carousel({
|
|
211
|
+
orientation = "horizontal",
|
|
212
|
+
opts,
|
|
213
|
+
setApi,
|
|
214
|
+
plugins,
|
|
215
|
+
className,
|
|
216
|
+
children,
|
|
217
|
+
...props
|
|
218
|
+
}: React.ComponentProps<"div"> & CarouselProps) {
|
|
219
|
+
const [carouselRef, api] = useEmblaCarousel(
|
|
220
|
+
{
|
|
221
|
+
...opts,
|
|
222
|
+
axis: orientation === "horizontal" ? "x" : "y",
|
|
223
|
+
},
|
|
224
|
+
plugins,
|
|
225
|
+
);
|
|
226
|
+
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
|
|
227
|
+
const [canScrollNext, setCanScrollNext] = React.useState(false);
|
|
228
|
+
|
|
229
|
+
const onSelect = useCallback((api: CarouselApi) => {
|
|
230
|
+
if (!api) return;
|
|
231
|
+
setCanScrollPrev(api.canScrollPrev());
|
|
232
|
+
setCanScrollNext(api.canScrollNext());
|
|
233
|
+
}, []);
|
|
234
|
+
|
|
235
|
+
const scrollPrev = () => {
|
|
236
|
+
api?.scrollPrev();
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const scrollNext = () => {
|
|
240
|
+
api?.scrollNext();
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
244
|
+
if (event.key === "ArrowLeft") {
|
|
245
|
+
event.preventDefault();
|
|
246
|
+
scrollPrev();
|
|
247
|
+
} else if (event.key === "ArrowRight") {
|
|
248
|
+
event.preventDefault();
|
|
249
|
+
scrollNext();
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
React.useEffect(() => {
|
|
254
|
+
if (!api || !setApi) return;
|
|
255
|
+
setApi(api);
|
|
256
|
+
}, [api, setApi]);
|
|
257
|
+
|
|
258
|
+
React.useEffect(() => {
|
|
259
|
+
if (!api) return;
|
|
260
|
+
onSelect(api);
|
|
261
|
+
api.on("reInit", onSelect);
|
|
262
|
+
api.on("select", onSelect);
|
|
263
|
+
|
|
264
|
+
return () => {
|
|
265
|
+
api?.off("select", onSelect);
|
|
266
|
+
};
|
|
267
|
+
}, [api, onSelect]);
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<CarouselContext.Provider
|
|
271
|
+
value={{
|
|
272
|
+
carouselRef,
|
|
273
|
+
api: api,
|
|
274
|
+
opts,
|
|
275
|
+
orientation:
|
|
276
|
+
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
|
277
|
+
scrollPrev,
|
|
278
|
+
scrollNext,
|
|
279
|
+
canScrollPrev,
|
|
280
|
+
canScrollNext,
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
<div
|
|
284
|
+
onKeyDownCapture={handleKeyDown}
|
|
285
|
+
className={cn("relative", className)}
|
|
286
|
+
role="region"
|
|
287
|
+
aria-roledescription="carousel"
|
|
288
|
+
data-slot="carousel"
|
|
289
|
+
{...props}
|
|
290
|
+
>
|
|
291
|
+
{children}
|
|
292
|
+
</div>
|
|
293
|
+
</CarouselContext.Provider>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Container component for carousel items that handles the scrolling viewport
|
|
299
|
+
*
|
|
300
|
+
* Wraps all carousel items and manages the scrollable area. This component
|
|
301
|
+
* must be a direct child of Carousel and handles orientation-specific layouts
|
|
302
|
+
* and overflow behavior.
|
|
303
|
+
*
|
|
304
|
+
* @param className - Additional CSS classes for custom spacing and styling
|
|
305
|
+
* @param children - CarouselItem components
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```tsx
|
|
309
|
+
* // Basic usage
|
|
310
|
+
* <CarouselContent>
|
|
311
|
+
* <CarouselItem>Slide 1</CarouselItem>
|
|
312
|
+
* <CarouselItem>Slide 2</CarouselItem>
|
|
313
|
+
* <CarouselItem>Slide 3</CarouselItem>
|
|
314
|
+
* </CarouselContent>
|
|
315
|
+
* ```
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```tsx
|
|
319
|
+
* // With custom spacing (horizontal)
|
|
320
|
+
* <CarouselContent className="-ml-2 md:-ml-4">
|
|
321
|
+
* <CarouselItem className="pl-2 md:pl-4">Slide 1</CarouselItem>
|
|
322
|
+
* <CarouselItem className="pl-2 md:pl-4">Slide 2</CarouselItem>
|
|
323
|
+
* </CarouselContent>
|
|
324
|
+
* ```
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```tsx
|
|
328
|
+
* // Vertical orientation spacing
|
|
329
|
+
* <CarouselContent className="-mt-4 h-[300px]">
|
|
330
|
+
* <CarouselItem className="pt-4">Slide 1</CarouselItem>
|
|
331
|
+
* <CarouselItem className="pt-4">Slide 2</CarouselItem>
|
|
332
|
+
* </CarouselContent>
|
|
333
|
+
* ```
|
|
334
|
+
*
|
|
335
|
+
* @accessibility
|
|
336
|
+
* - Provides scrollable container with proper overflow handling
|
|
337
|
+
* - Maintains responsive layout for horizontal/vertical orientations
|
|
338
|
+
* - Works with screen readers for content navigation
|
|
339
|
+
*
|
|
340
|
+
* @see {@link Carousel} Main carousel component
|
|
341
|
+
* @see {@link CarouselItem} Individual carousel slides
|
|
342
|
+
* @since 1.0.0
|
|
343
|
+
*/
|
|
344
|
+
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
345
|
+
const { carouselRef, orientation } = useCarousel();
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<div
|
|
349
|
+
ref={carouselRef}
|
|
350
|
+
className="overflow-hidden"
|
|
351
|
+
data-slot="carousel-content"
|
|
352
|
+
>
|
|
353
|
+
<div
|
|
354
|
+
className={cn(
|
|
355
|
+
"flex",
|
|
356
|
+
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
|
357
|
+
className,
|
|
358
|
+
)}
|
|
359
|
+
{...props}
|
|
360
|
+
/>
|
|
361
|
+
</div>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Individual slide/item component within the carousel
|
|
367
|
+
*
|
|
368
|
+
* Represents a single slide in the carousel that can contain any content.
|
|
369
|
+
* Automatically handles sizing, spacing, and orientation-specific layouts
|
|
370
|
+
* based on the parent carousel configuration.
|
|
371
|
+
*
|
|
372
|
+
* @param className - Additional CSS classes for sizing (basis-*) and spacing
|
|
373
|
+
* @param children - Content to display within the slide
|
|
374
|
+
*
|
|
375
|
+
* @example
|
|
376
|
+
* ```tsx
|
|
377
|
+
* // Basic slide with content
|
|
378
|
+
* <CarouselItem>
|
|
379
|
+
* <div className="p-6">
|
|
380
|
+
* <h3 className="text-lg font-semibold">Slide Title</h3>
|
|
381
|
+
* <p className="text-muted-foreground">Slide description</p>
|
|
382
|
+
* </div>
|
|
383
|
+
* </CarouselItem>
|
|
384
|
+
* ```
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```tsx
|
|
388
|
+
* // Card-based slide
|
|
389
|
+
* <CarouselItem>
|
|
390
|
+
* <Card>
|
|
391
|
+
* <CardHeader>
|
|
392
|
+
* <CardTitle>Product Name</CardTitle>
|
|
393
|
+
* </CardHeader>
|
|
394
|
+
* <CardContent>
|
|
395
|
+
* <img src="product.jpg" alt="Product" className="w-full" />
|
|
396
|
+
* <p className="mt-2">$99.99</p>
|
|
397
|
+
* </CardContent>
|
|
398
|
+
* </Card>
|
|
399
|
+
* </CarouselItem>
|
|
400
|
+
* ```
|
|
401
|
+
*
|
|
402
|
+
* @example
|
|
403
|
+
* ```tsx
|
|
404
|
+
* // Responsive sizing - multiple items visible
|
|
405
|
+
* <CarouselItem className="md:basis-1/2 lg:basis-1/3">
|
|
406
|
+
* <div className="aspect-square bg-muted rounded-lg" />
|
|
407
|
+
* </CarouselItem>
|
|
408
|
+
* ```
|
|
409
|
+
*
|
|
410
|
+
* @example
|
|
411
|
+
* ```tsx
|
|
412
|
+
* // Custom sizing with fixed width
|
|
413
|
+
* <CarouselItem className="basis-4/5">
|
|
414
|
+
* <div className="w-full h-48 bg-gradient-to-r from-blue-500 to-purple-600" />
|
|
415
|
+
* </CarouselItem>
|
|
416
|
+
* ```
|
|
417
|
+
*
|
|
418
|
+
* @accessibility
|
|
419
|
+
* - Labeled as a slide with proper ARIA role for screen readers
|
|
420
|
+
* - Maintains focus behavior and keyboard navigation
|
|
421
|
+
* - Preserves content structure for assistive technologies
|
|
422
|
+
*
|
|
423
|
+
* @see {@link Carousel} Main carousel component
|
|
424
|
+
* @see {@link CarouselContent} Container for carousel items
|
|
425
|
+
* @since 1.0.0
|
|
426
|
+
*/
|
|
427
|
+
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
|
|
428
|
+
const { orientation } = useCarousel();
|
|
429
|
+
|
|
430
|
+
return (
|
|
431
|
+
<div
|
|
432
|
+
role="group"
|
|
433
|
+
aria-roledescription="slide"
|
|
434
|
+
data-slot="carousel-item"
|
|
435
|
+
className={cn(
|
|
436
|
+
"min-w-0 shrink-0 grow-0 basis-full",
|
|
437
|
+
orientation === "horizontal" ? "pl-4" : "pt-4",
|
|
438
|
+
className,
|
|
439
|
+
)}
|
|
440
|
+
{...props}
|
|
441
|
+
/>
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Navigation button to go to the previous carousel slide
|
|
447
|
+
*
|
|
448
|
+
* Renders a previous navigation button that automatically handles disabled
|
|
449
|
+
* states when at the beginning of the carousel. Supports both horizontal
|
|
450
|
+
* and vertical orientations with appropriate positioning.
|
|
451
|
+
*
|
|
452
|
+
* @param className - Additional CSS classes for custom positioning and styling
|
|
453
|
+
* @param variant - Button variant (default: "outline")
|
|
454
|
+
* @param size - Button size (default: "icon")
|
|
455
|
+
*
|
|
456
|
+
* @example
|
|
457
|
+
* ```tsx
|
|
458
|
+
* // Basic usage
|
|
459
|
+
* <Carousel>
|
|
460
|
+
* <CarouselContent>
|
|
461
|
+
* <CarouselItem>Slide 1</CarouselItem>
|
|
462
|
+
* <CarouselItem>Slide 2</CarouselItem>
|
|
463
|
+
* </CarouselContent>
|
|
464
|
+
* <CarouselPrevious />
|
|
465
|
+
* <CarouselNext />
|
|
466
|
+
* </Carousel>
|
|
467
|
+
* ```
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* ```tsx
|
|
471
|
+
* // Custom styling
|
|
472
|
+
* <CarouselPrevious
|
|
473
|
+
* className="-left-8 bg-background shadow-md"
|
|
474
|
+
* variant="ghost"
|
|
475
|
+
* size="sm"
|
|
476
|
+
* />
|
|
477
|
+
* ```
|
|
478
|
+
*
|
|
479
|
+
* @example
|
|
480
|
+
* ```tsx
|
|
481
|
+
* // Vertical carousel positioning
|
|
482
|
+
* <Carousel orientation="vertical">
|
|
483
|
+
* <CarouselContent>...</CarouselContent>
|
|
484
|
+
* <CarouselPrevious />
|
|
485
|
+
* <CarouselNext />
|
|
486
|
+
* </Carousel>
|
|
487
|
+
* ```
|
|
488
|
+
*
|
|
489
|
+
* @accessibility
|
|
490
|
+
* - Keyboard accessible with proper focus management
|
|
491
|
+
* - Screen reader label "Previous slide" for context
|
|
492
|
+
* - Automatically disabled when cannot scroll further
|
|
493
|
+
* - High contrast focus indicators
|
|
494
|
+
* - Supports reduced motion preferences
|
|
495
|
+
*
|
|
496
|
+
* @see {@link Carousel} Main carousel component
|
|
497
|
+
* @see {@link CarouselNext} Next navigation button
|
|
498
|
+
* @see {@link Button} Underlying button component
|
|
499
|
+
* @since 1.0.0
|
|
500
|
+
*/
|
|
501
|
+
function CarouselPrevious({
|
|
502
|
+
className,
|
|
503
|
+
variant = "outline",
|
|
504
|
+
size = "icon",
|
|
505
|
+
...props
|
|
506
|
+
}: React.ComponentProps<typeof Button>) {
|
|
507
|
+
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
|
|
508
|
+
|
|
509
|
+
return (
|
|
510
|
+
<Button
|
|
511
|
+
data-slot="carousel-previous"
|
|
512
|
+
variant={variant}
|
|
513
|
+
size={size}
|
|
514
|
+
className={cn(
|
|
515
|
+
"absolute size-8 rounded-full",
|
|
516
|
+
orientation === "horizontal"
|
|
517
|
+
? "top-1/2 -left-12 -translate-y-1/2"
|
|
518
|
+
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
|
519
|
+
className,
|
|
520
|
+
)}
|
|
521
|
+
disabled={!canScrollPrev}
|
|
522
|
+
onClick={scrollPrev}
|
|
523
|
+
{...props}
|
|
524
|
+
>
|
|
525
|
+
<ArrowLeft />
|
|
526
|
+
<span className="sr-only">Previous slide</span>
|
|
527
|
+
</Button>
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Navigation button to go to the next carousel slide
|
|
533
|
+
*
|
|
534
|
+
* Renders a next navigation button that automatically handles disabled
|
|
535
|
+
* states when at the end of the carousel. Supports both horizontal
|
|
536
|
+
* and vertical orientations with appropriate positioning.
|
|
537
|
+
*
|
|
538
|
+
* @param className - Additional CSS classes for custom positioning and styling
|
|
539
|
+
* @param variant - Button variant (default: "outline")
|
|
540
|
+
* @param size - Button size (default: "icon")
|
|
541
|
+
*
|
|
542
|
+
* @example
|
|
543
|
+
* ```tsx
|
|
544
|
+
* // Basic usage
|
|
545
|
+
* <Carousel>
|
|
546
|
+
* <CarouselContent>
|
|
547
|
+
* <CarouselItem>Slide 1</CarouselItem>
|
|
548
|
+
* <CarouselItem>Slide 2</CarouselItem>
|
|
549
|
+
* </CarouselContent>
|
|
550
|
+
* <CarouselPrevious />
|
|
551
|
+
* <CarouselNext />
|
|
552
|
+
* </Carousel>
|
|
553
|
+
* ```
|
|
554
|
+
*
|
|
555
|
+
* @example
|
|
556
|
+
* ```tsx
|
|
557
|
+
* // Custom styling
|
|
558
|
+
* <CarouselNext
|
|
559
|
+
* className="-right-8 bg-background shadow-md"
|
|
560
|
+
* variant="ghost"
|
|
561
|
+
* size="sm"
|
|
562
|
+
* />
|
|
563
|
+
* ```
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* ```tsx
|
|
567
|
+
* // Vertical carousel positioning
|
|
568
|
+
* <Carousel orientation="vertical">
|
|
569
|
+
* <CarouselContent>...</CarouselContent>
|
|
570
|
+
* <CarouselPrevious />
|
|
571
|
+
* <CarouselNext />
|
|
572
|
+
* </Carousel>
|
|
573
|
+
* ```
|
|
574
|
+
*
|
|
575
|
+
* @accessibility
|
|
576
|
+
* - Keyboard accessible with proper focus management
|
|
577
|
+
* - Screen reader label "Next slide" for context
|
|
578
|
+
* - Automatically disabled when cannot scroll further
|
|
579
|
+
* - High contrast focus indicators
|
|
580
|
+
* - Supports reduced motion preferences
|
|
581
|
+
*
|
|
582
|
+
* @see {@link Carousel} Main carousel component
|
|
583
|
+
* @see {@link CarouselPrevious} Previous navigation button
|
|
584
|
+
* @see {@link Button} Underlying button component
|
|
585
|
+
* @since 1.0.0
|
|
586
|
+
*/
|
|
587
|
+
function CarouselNext({
|
|
588
|
+
className,
|
|
589
|
+
variant = "outline",
|
|
590
|
+
size = "icon",
|
|
591
|
+
...props
|
|
592
|
+
}: React.ComponentProps<typeof Button>) {
|
|
593
|
+
const { orientation, scrollNext, canScrollNext } = useCarousel();
|
|
594
|
+
|
|
595
|
+
return (
|
|
596
|
+
<Button
|
|
597
|
+
data-slot="carousel-next"
|
|
598
|
+
variant={variant}
|
|
599
|
+
size={size}
|
|
600
|
+
className={cn(
|
|
601
|
+
"absolute size-8 rounded-full",
|
|
602
|
+
orientation === "horizontal"
|
|
603
|
+
? "top-1/2 -right-12 -translate-y-1/2"
|
|
604
|
+
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
|
605
|
+
className,
|
|
606
|
+
)}
|
|
607
|
+
disabled={!canScrollNext}
|
|
608
|
+
onClick={scrollNext}
|
|
609
|
+
{...props}
|
|
610
|
+
>
|
|
611
|
+
<ArrowRight />
|
|
612
|
+
<span className="sr-only">Next slide</span>
|
|
613
|
+
</Button>
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export {
|
|
618
|
+
type CarouselApi,
|
|
619
|
+
Carousel,
|
|
620
|
+
CarouselContent,
|
|
621
|
+
CarouselItem,
|
|
622
|
+
CarouselPrevious,
|
|
623
|
+
CarouselNext,
|
|
624
|
+
};
|