@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,2211 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
+
import { PanelLeftIcon } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
import { useIsMobile } from "@/hooks/use-mobile";
|
|
8
|
+
import { cn } from "@/lib/utils";
|
|
9
|
+
import { Button } from "@/components/ui/button";
|
|
10
|
+
import { Input } from "@/components/ui/input";
|
|
11
|
+
import { Separator } from "@/components/ui/separator";
|
|
12
|
+
import {
|
|
13
|
+
Sheet,
|
|
14
|
+
SheetContent,
|
|
15
|
+
SheetDescription,
|
|
16
|
+
SheetHeader,
|
|
17
|
+
SheetTitle,
|
|
18
|
+
} from "@/components/ui/sheet";
|
|
19
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
20
|
+
import {
|
|
21
|
+
Tooltip,
|
|
22
|
+
TooltipContent,
|
|
23
|
+
TooltipProvider,
|
|
24
|
+
TooltipTrigger,
|
|
25
|
+
} from "@/components/ui/tooltip";
|
|
26
|
+
|
|
27
|
+
const SIDEBAR_COOKIE_NAME = "sidebar_state";
|
|
28
|
+
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
|
29
|
+
const SIDEBAR_WIDTH = "16rem";
|
|
30
|
+
const SIDEBAR_WIDTH_MOBILE = "18rem";
|
|
31
|
+
const SIDEBAR_WIDTH_ICON = "3rem";
|
|
32
|
+
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
|
33
|
+
|
|
34
|
+
type SidebarContextProps = {
|
|
35
|
+
state: "expanded" | "collapsed";
|
|
36
|
+
open: boolean;
|
|
37
|
+
setOpen: (open: boolean) => void;
|
|
38
|
+
openMobile: boolean;
|
|
39
|
+
setOpenMobile: (open: boolean) => void;
|
|
40
|
+
isMobile: boolean;
|
|
41
|
+
toggleSidebar: () => void;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const SidebarContext = React.createContext<SidebarContextProps | null>(null);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Hook to access sidebar state and control functions
|
|
48
|
+
*
|
|
49
|
+
* Provides access to the sidebar's state management including expanded/collapsed states,
|
|
50
|
+
* mobile-specific behavior, and toggle functions. This hook must be used within a
|
|
51
|
+
* SidebarProvider component tree to function properly.
|
|
52
|
+
*
|
|
53
|
+
* @returns {SidebarContextProps} Object containing sidebar state and control functions:
|
|
54
|
+
* - `state`: "expanded" | "collapsed" - Current sidebar state
|
|
55
|
+
* - `open`: boolean - Whether sidebar is open (desktop)
|
|
56
|
+
* - `setOpen`: (open: boolean) => void - Function to control sidebar open state
|
|
57
|
+
* - `openMobile`: boolean - Whether sidebar is open on mobile
|
|
58
|
+
* - `setOpenMobile`: (open: boolean) => void - Function to control mobile sidebar state
|
|
59
|
+
* - `isMobile`: boolean - Whether current viewport is considered mobile
|
|
60
|
+
* - `toggleSidebar`: () => void - Function to toggle sidebar state
|
|
61
|
+
*
|
|
62
|
+
* @throws {Error} When used outside of SidebarProvider context
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```tsx
|
|
66
|
+
* // Basic usage for toggle button
|
|
67
|
+
* function ToggleButton() {
|
|
68
|
+
* const { state, toggleSidebar } = useSidebar()
|
|
69
|
+
* return (
|
|
70
|
+
* <button onClick={toggleSidebar}>
|
|
71
|
+
* {state === "expanded" ? "Collapse" : "Expand"}
|
|
72
|
+
* </button>
|
|
73
|
+
* )
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```tsx
|
|
79
|
+
* // Conditional rendering based on state
|
|
80
|
+
* function ConditionalContent() {
|
|
81
|
+
* const { state, isMobile } = useSidebar()
|
|
82
|
+
* if (isMobile) return <MobileOnlyContent />
|
|
83
|
+
* return state === "expanded" ? <FullContent /> : <CompactContent />
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* @see {@link SidebarProvider} - Required context provider
|
|
88
|
+
* @since 1.0.0
|
|
89
|
+
*/
|
|
90
|
+
function useSidebar() {
|
|
91
|
+
const context = React.useContext(SidebarContext);
|
|
92
|
+
if (!context) {
|
|
93
|
+
throw new Error("useSidebar must be used within a SidebarProvider.");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return context;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Context provider for comprehensive sidebar state management
|
|
101
|
+
*
|
|
102
|
+
* The SidebarProvider is the foundational component that manages all sidebar behavior
|
|
103
|
+
* including state persistence, responsive design, keyboard shortcuts, and mobile handling.
|
|
104
|
+
* It provides context to all sidebar-related components and should wrap your entire
|
|
105
|
+
* application or the section containing the sidebar.
|
|
106
|
+
*
|
|
107
|
+
* @component
|
|
108
|
+
*
|
|
109
|
+
* @param {boolean} [defaultOpen=true] - Initial open state when uncontrolled
|
|
110
|
+
* @param {boolean} [open] - Controlled open state (makes component controlled)
|
|
111
|
+
* @param {(open: boolean) => void} [onOpenChange] - Callback for open state changes
|
|
112
|
+
* @param {string} [className] - Additional CSS classes for wrapper
|
|
113
|
+
* @param {React.CSSProperties} [style] - Inline styles including CSS variables
|
|
114
|
+
* @param {React.ReactNode} children - Child components
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```tsx
|
|
118
|
+
* // Basic uncontrolled usage
|
|
119
|
+
* <SidebarProvider>
|
|
120
|
+
* <Sidebar />
|
|
121
|
+
* <SidebarInset>
|
|
122
|
+
* <main>Your content</main>
|
|
123
|
+
* </SidebarInset>
|
|
124
|
+
* </SidebarProvider>
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```tsx
|
|
129
|
+
* // Controlled usage with custom state
|
|
130
|
+
* function App() {
|
|
131
|
+
* const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
132
|
+
* return (
|
|
133
|
+
* <SidebarProvider open={sidebarOpen} onOpenChange={setSidebarOpen}>
|
|
134
|
+
* <Sidebar />
|
|
135
|
+
* <SidebarInset>Content</SidebarInset>
|
|
136
|
+
* </SidebarProvider>
|
|
137
|
+
* )
|
|
138
|
+
* }
|
|
139
|
+
* ```
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```tsx
|
|
143
|
+
* // Custom width with CSS variables
|
|
144
|
+
* <SidebarProvider
|
|
145
|
+
* style={{
|
|
146
|
+
* "--sidebar-width": "20rem",
|
|
147
|
+
* "--sidebar-width-mobile": "18rem"
|
|
148
|
+
* }}
|
|
149
|
+
* >
|
|
150
|
+
* <Sidebar />
|
|
151
|
+
* </SidebarProvider>
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* @features
|
|
155
|
+
* - State persistence via cookies
|
|
156
|
+
* - Keyboard shortcut (Cmd/Ctrl+B) support
|
|
157
|
+
* - Responsive mobile behavior
|
|
158
|
+
* - CSS variable-based theming
|
|
159
|
+
* - Tooltip provider integration
|
|
160
|
+
*
|
|
161
|
+
* @accessibility
|
|
162
|
+
* - Keyboard shortcuts for power users
|
|
163
|
+
* - Mobile-optimized touch interactions
|
|
164
|
+
* - Focus management for modal-like mobile behavior
|
|
165
|
+
*
|
|
166
|
+
* @see {@link useSidebar} - Hook to access provided context
|
|
167
|
+
* @see {@link Sidebar} - Main sidebar component
|
|
168
|
+
* @since 1.0.0
|
|
169
|
+
*/
|
|
170
|
+
function SidebarProvider({
|
|
171
|
+
defaultOpen = true,
|
|
172
|
+
open: openProp,
|
|
173
|
+
onOpenChange: setOpenProp,
|
|
174
|
+
className,
|
|
175
|
+
style,
|
|
176
|
+
children,
|
|
177
|
+
...props
|
|
178
|
+
}: React.ComponentProps<"div"> & {
|
|
179
|
+
defaultOpen?: boolean;
|
|
180
|
+
open?: boolean;
|
|
181
|
+
onOpenChange?: (open: boolean) => void;
|
|
182
|
+
}) {
|
|
183
|
+
const isMobile = useIsMobile();
|
|
184
|
+
const [openMobile, setOpenMobile] = React.useState(false);
|
|
185
|
+
|
|
186
|
+
// This is the internal state of the sidebar.
|
|
187
|
+
// We use openProp and setOpenProp for control from outside the component.
|
|
188
|
+
const [_open, _setOpen] = React.useState(defaultOpen);
|
|
189
|
+
const open = openProp ?? _open;
|
|
190
|
+
const setOpen = useCallback(
|
|
191
|
+
(value: boolean | ((value: boolean) => boolean)) => {
|
|
192
|
+
const openState = typeof value === "function" ? value(open) : value;
|
|
193
|
+
if (setOpenProp) {
|
|
194
|
+
setOpenProp(openState);
|
|
195
|
+
} else {
|
|
196
|
+
_setOpen(openState);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// This sets the cookie to keep the sidebar state.
|
|
200
|
+
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
|
|
201
|
+
},
|
|
202
|
+
[setOpenProp, _setOpen, open],
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Helper to toggle the sidebar.
|
|
206
|
+
const toggleSidebar = useCallback(() => {
|
|
207
|
+
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
|
|
208
|
+
}, [isMobile, setOpenMobile, setOpen]);
|
|
209
|
+
|
|
210
|
+
// Adds a keyboard shortcut to toggle the sidebar.
|
|
211
|
+
React.useEffect(() => {
|
|
212
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
213
|
+
if (
|
|
214
|
+
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
|
|
215
|
+
(event.metaKey || event.ctrlKey)
|
|
216
|
+
) {
|
|
217
|
+
event.preventDefault();
|
|
218
|
+
toggleSidebar();
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
223
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
224
|
+
}, [toggleSidebar]);
|
|
225
|
+
|
|
226
|
+
// We add a state so that we can do data-state="expanded" or "collapsed".
|
|
227
|
+
// This makes it easier to style the sidebar with Tailwind classes.
|
|
228
|
+
const state = open ? "expanded" : "collapsed";
|
|
229
|
+
|
|
230
|
+
const contextValue: SidebarContextProps = {
|
|
231
|
+
state,
|
|
232
|
+
open,
|
|
233
|
+
setOpen,
|
|
234
|
+
isMobile,
|
|
235
|
+
openMobile,
|
|
236
|
+
setOpenMobile,
|
|
237
|
+
toggleSidebar,
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<SidebarContext.Provider value={contextValue}>
|
|
242
|
+
<TooltipProvider delayDuration={0}>
|
|
243
|
+
<div
|
|
244
|
+
data-slot="sidebar-wrapper"
|
|
245
|
+
style={
|
|
246
|
+
{
|
|
247
|
+
"--sidebar-width": SIDEBAR_WIDTH,
|
|
248
|
+
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
|
|
249
|
+
...style,
|
|
250
|
+
} as React.CSSProperties
|
|
251
|
+
}
|
|
252
|
+
className={cn(
|
|
253
|
+
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
|
|
254
|
+
className,
|
|
255
|
+
)}
|
|
256
|
+
{...props}
|
|
257
|
+
>
|
|
258
|
+
{children}
|
|
259
|
+
</div>
|
|
260
|
+
</TooltipProvider>
|
|
261
|
+
</SidebarContext.Provider>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Flexible and responsive navigation sidebar component
|
|
267
|
+
*
|
|
268
|
+
* The Sidebar component is the main container for navigation content, supporting multiple
|
|
269
|
+
* visual variants, collapsible behaviors, and responsive design patterns. It automatically
|
|
270
|
+
* adapts between desktop layouts and mobile sheet overlays based on viewport size.
|
|
271
|
+
*
|
|
272
|
+
* @component
|
|
273
|
+
*
|
|
274
|
+
* @param {"left" | "right"} [side="left"] - Which side of the screen the sidebar appears on
|
|
275
|
+
* @param {"sidebar" | "floating" | "inset"} [variant="sidebar"] - Visual variant:
|
|
276
|
+
* - `sidebar`: Standard full-height sidebar
|
|
277
|
+
* - `floating`: Sidebar with padding, rounded corners, and shadow
|
|
278
|
+
* - `inset`: Sidebar with margin that affects main content layout
|
|
279
|
+
* @param {"offcanvas" | "icon" | "none"} [collapsible="offcanvas"] - Collapse behavior:
|
|
280
|
+
* - `offcanvas`: Slides completely off-screen when collapsed
|
|
281
|
+
* - `icon`: Collapses to icon-only width, hiding text labels
|
|
282
|
+
* - `none`: Cannot be collapsed, always visible
|
|
283
|
+
* @param {string} [className] - Additional CSS classes
|
|
284
|
+
* @param {React.ReactNode} children - Sidebar content (Header, Content, Footer)
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```tsx
|
|
288
|
+
* // Standard navigation sidebar
|
|
289
|
+
* <SidebarProvider>
|
|
290
|
+
* <Sidebar>
|
|
291
|
+
* <SidebarHeader>
|
|
292
|
+
* <h2>My Application</h2>
|
|
293
|
+
* </SidebarHeader>
|
|
294
|
+
* <SidebarContent>
|
|
295
|
+
* <SidebarGroup>
|
|
296
|
+
* <SidebarGroupLabel>Main</SidebarGroupLabel>
|
|
297
|
+
* <SidebarGroupContent>
|
|
298
|
+
* <SidebarMenu>
|
|
299
|
+
* <SidebarMenuItem>
|
|
300
|
+
* <SidebarMenuButton asChild>
|
|
301
|
+
* <Link href="/dashboard">
|
|
302
|
+
* <Home />
|
|
303
|
+
* <span>Dashboard</span>
|
|
304
|
+
* </Link>
|
|
305
|
+
* </SidebarMenuButton>
|
|
306
|
+
* </SidebarMenuItem>
|
|
307
|
+
* </SidebarMenu>
|
|
308
|
+
* </SidebarGroupContent>
|
|
309
|
+
* </SidebarGroup>
|
|
310
|
+
* </SidebarContent>
|
|
311
|
+
* <SidebarFooter>
|
|
312
|
+
* <SidebarMenu>
|
|
313
|
+
* <SidebarMenuItem>
|
|
314
|
+
* <SidebarMenuButton>
|
|
315
|
+
* <Settings />
|
|
316
|
+
* <span>Settings</span>
|
|
317
|
+
* </SidebarMenuButton>
|
|
318
|
+
* </SidebarMenuItem>
|
|
319
|
+
* </SidebarMenu>
|
|
320
|
+
* </SidebarFooter>
|
|
321
|
+
* </Sidebar>
|
|
322
|
+
* <SidebarInset>
|
|
323
|
+
* <main>Main content area</main>
|
|
324
|
+
* </SidebarInset>
|
|
325
|
+
* </SidebarProvider>
|
|
326
|
+
* ```
|
|
327
|
+
*
|
|
328
|
+
* @example
|
|
329
|
+
* ```tsx
|
|
330
|
+
* // Icon-collapsible sidebar with tooltips
|
|
331
|
+
* <SidebarProvider>
|
|
332
|
+
* <Sidebar collapsible="icon">
|
|
333
|
+
* <SidebarContent>
|
|
334
|
+
* <SidebarGroup>
|
|
335
|
+
* <SidebarGroupLabel>Navigation</SidebarGroupLabel>
|
|
336
|
+
* <SidebarGroupContent>
|
|
337
|
+
* <SidebarMenu>
|
|
338
|
+
* <SidebarMenuItem>
|
|
339
|
+
* <SidebarMenuButton tooltip="Go to Dashboard">
|
|
340
|
+
* <Home />
|
|
341
|
+
* <span>Dashboard</span>
|
|
342
|
+
* </SidebarMenuButton>
|
|
343
|
+
* </SidebarMenuItem>
|
|
344
|
+
* </SidebarMenu>
|
|
345
|
+
* </SidebarGroupContent>
|
|
346
|
+
* </SidebarGroup>
|
|
347
|
+
* </SidebarContent>
|
|
348
|
+
* </Sidebar>
|
|
349
|
+
* </SidebarProvider>
|
|
350
|
+
* ```
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```tsx
|
|
354
|
+
* // Floating variant with custom styling
|
|
355
|
+
* <SidebarProvider>
|
|
356
|
+
* <Sidebar variant="floating">
|
|
357
|
+
* <SidebarHeader>
|
|
358
|
+
* <SidebarTrigger />
|
|
359
|
+
* <h2>Floating Sidebar</h2>
|
|
360
|
+
* </SidebarHeader>
|
|
361
|
+
* <SidebarContent>
|
|
362
|
+
* // Navigation content
|
|
363
|
+
* </SidebarContent>
|
|
364
|
+
* </Sidebar>
|
|
365
|
+
* </SidebarProvider>
|
|
366
|
+
* ```
|
|
367
|
+
*
|
|
368
|
+
* @accessibility
|
|
369
|
+
* - Keyboard shortcut: Cmd/Ctrl+B to toggle visibility
|
|
370
|
+
* - Full keyboard navigation support within sidebar
|
|
371
|
+
* - ARIA attributes indicate current state (expanded/collapsed)
|
|
372
|
+
* - Screen reader announcements for state changes
|
|
373
|
+
* - Mobile: Sheet overlay with proper focus management
|
|
374
|
+
* - Desktop: Seamless keyboard navigation between sidebar and main content
|
|
375
|
+
*
|
|
376
|
+
* @responsive
|
|
377
|
+
* - **Desktop**: Fixed-position sidebar with smooth width transitions
|
|
378
|
+
* - **Mobile**: Sheet overlay that slides in from the side
|
|
379
|
+
* - **Tablet**: Responsive breakpoints adapt behavior automatically
|
|
380
|
+
*
|
|
381
|
+
* @see {@link SidebarProvider} - Required context provider
|
|
382
|
+
* @see {@link SidebarHeader} - Header section component
|
|
383
|
+
* @see {@link SidebarContent} - Scrollable content area
|
|
384
|
+
* @see {@link SidebarFooter} - Footer section component
|
|
385
|
+
* @see {@link SidebarTrigger} - Toggle button component
|
|
386
|
+
* @see {@link SidebarRail} - Interactive resize handle
|
|
387
|
+
* @see {@link https://ui.shadcn.com/docs/components/sidebar} - shadcn/ui Sidebar documentation
|
|
388
|
+
* @since 1.0.0
|
|
389
|
+
*/
|
|
390
|
+
function Sidebar({
|
|
391
|
+
side = "left",
|
|
392
|
+
variant = "sidebar",
|
|
393
|
+
collapsible = "offcanvas",
|
|
394
|
+
className,
|
|
395
|
+
children,
|
|
396
|
+
...props
|
|
397
|
+
}: React.ComponentProps<"div"> & {
|
|
398
|
+
side?: "left" | "right";
|
|
399
|
+
variant?: "sidebar" | "floating" | "inset";
|
|
400
|
+
collapsible?: "offcanvas" | "icon" | "none";
|
|
401
|
+
}) {
|
|
402
|
+
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
|
|
403
|
+
|
|
404
|
+
if (collapsible === "none") {
|
|
405
|
+
return (
|
|
406
|
+
<div
|
|
407
|
+
data-slot="sidebar"
|
|
408
|
+
className={cn(
|
|
409
|
+
"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
|
|
410
|
+
className,
|
|
411
|
+
)}
|
|
412
|
+
{...props}
|
|
413
|
+
>
|
|
414
|
+
{children}
|
|
415
|
+
</div>
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (isMobile) {
|
|
420
|
+
return (
|
|
421
|
+
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
|
|
422
|
+
<SheetContent
|
|
423
|
+
data-sidebar="sidebar"
|
|
424
|
+
data-slot="sidebar"
|
|
425
|
+
data-mobile="true"
|
|
426
|
+
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
|
|
427
|
+
style={
|
|
428
|
+
{
|
|
429
|
+
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
|
|
430
|
+
} as React.CSSProperties
|
|
431
|
+
}
|
|
432
|
+
side={side}
|
|
433
|
+
>
|
|
434
|
+
<SheetHeader className="sr-only">
|
|
435
|
+
<SheetTitle>Sidebar</SheetTitle>
|
|
436
|
+
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
|
|
437
|
+
</SheetHeader>
|
|
438
|
+
<div className="flex h-full w-full flex-col">{children}</div>
|
|
439
|
+
</SheetContent>
|
|
440
|
+
</Sheet>
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return (
|
|
445
|
+
<div
|
|
446
|
+
className="group peer text-sidebar-foreground hidden md:block"
|
|
447
|
+
data-state={state}
|
|
448
|
+
data-collapsible={state === "collapsed" ? collapsible : ""}
|
|
449
|
+
data-variant={variant}
|
|
450
|
+
data-side={side}
|
|
451
|
+
data-slot="sidebar"
|
|
452
|
+
>
|
|
453
|
+
{/* This is what handles the sidebar gap on desktop */}
|
|
454
|
+
<div
|
|
455
|
+
data-slot="sidebar-gap"
|
|
456
|
+
className={cn(
|
|
457
|
+
"relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
|
|
458
|
+
"group-data-[collapsible=offcanvas]:w-0",
|
|
459
|
+
"group-data-[side=right]:rotate-180",
|
|
460
|
+
variant === "floating" || variant === "inset"
|
|
461
|
+
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
|
|
462
|
+
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
|
|
463
|
+
)}
|
|
464
|
+
/>
|
|
465
|
+
<div
|
|
466
|
+
data-slot="sidebar-container"
|
|
467
|
+
className={cn(
|
|
468
|
+
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
|
|
469
|
+
side === "left"
|
|
470
|
+
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
|
471
|
+
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
|
472
|
+
// Adjust the padding for floating and inset variants.
|
|
473
|
+
variant === "floating" || variant === "inset"
|
|
474
|
+
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
|
475
|
+
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
|
|
476
|
+
className,
|
|
477
|
+
)}
|
|
478
|
+
{...props}
|
|
479
|
+
>
|
|
480
|
+
<div
|
|
481
|
+
data-sidebar="sidebar"
|
|
482
|
+
data-slot="sidebar-inner"
|
|
483
|
+
className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
|
|
484
|
+
>
|
|
485
|
+
{children}
|
|
486
|
+
</div>
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Interactive button component for toggling sidebar visibility
|
|
494
|
+
*
|
|
495
|
+
* A pre-styled button that toggles the sidebar between expanded and collapsed states
|
|
496
|
+
* on desktop, or opens/closes the mobile sheet overlay on smaller screens. Features
|
|
497
|
+
* a panel icon and proper accessibility attributes.
|
|
498
|
+
*
|
|
499
|
+
* @component
|
|
500
|
+
*
|
|
501
|
+
* @param {string} [className] - Additional CSS classes
|
|
502
|
+
* @param {(event: React.MouseEvent) => void} [onClick] - Additional click handler
|
|
503
|
+
* @param {...React.ComponentProps<typeof Button>} props - All Button component props
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* ```tsx
|
|
507
|
+
* // Basic usage in header
|
|
508
|
+
* <header className="flex items-center gap-2 p-4">
|
|
509
|
+
* <SidebarTrigger />
|
|
510
|
+
* <h1>My Application</h1>
|
|
511
|
+
* </header>
|
|
512
|
+
* ```
|
|
513
|
+
*
|
|
514
|
+
* @example
|
|
515
|
+
* ```tsx
|
|
516
|
+
* // With custom click handler
|
|
517
|
+
* <SidebarTrigger
|
|
518
|
+
* onClick={(e) => {
|
|
519
|
+
* console.log('Sidebar toggled')
|
|
520
|
+
* // Additional logic here
|
|
521
|
+
* }}
|
|
522
|
+
* />
|
|
523
|
+
* ```
|
|
524
|
+
*
|
|
525
|
+
* @accessibility
|
|
526
|
+
* - Screen reader label: "Toggle Sidebar"
|
|
527
|
+
* - Keyboard accessible (Enter and Space keys)
|
|
528
|
+
* - Focus visible indicator for keyboard navigation
|
|
529
|
+
* - ARIA attributes for assistive technology
|
|
530
|
+
*
|
|
531
|
+
* @see {@link useSidebar} - Hook for custom toggle implementations
|
|
532
|
+
* @see {@link SidebarProvider} - Required context provider
|
|
533
|
+
* @since 1.0.0
|
|
534
|
+
*/
|
|
535
|
+
function SidebarTrigger({
|
|
536
|
+
className,
|
|
537
|
+
onClick,
|
|
538
|
+
...props
|
|
539
|
+
}: React.ComponentProps<typeof Button>) {
|
|
540
|
+
const { toggleSidebar } = useSidebar();
|
|
541
|
+
|
|
542
|
+
return (
|
|
543
|
+
<Button
|
|
544
|
+
data-sidebar="trigger"
|
|
545
|
+
data-slot="sidebar-trigger"
|
|
546
|
+
variant="ghost"
|
|
547
|
+
size="icon"
|
|
548
|
+
className={cn("size-7", className)}
|
|
549
|
+
onClick={(event) => {
|
|
550
|
+
onClick?.(event);
|
|
551
|
+
toggleSidebar();
|
|
552
|
+
}}
|
|
553
|
+
{...props}
|
|
554
|
+
>
|
|
555
|
+
<PanelLeftIcon />
|
|
556
|
+
<span className="sr-only">Toggle Sidebar</span>
|
|
557
|
+
</Button>
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Interactive rail component for sidebar edge interaction
|
|
563
|
+
*
|
|
564
|
+
* Provides a subtle interactive area along the sidebar edge that users can click
|
|
565
|
+
* to toggle the sidebar state. The rail is positioned at the sidebar boundary and
|
|
566
|
+
* includes hover states for discoverability. Only visible on desktop viewports.
|
|
567
|
+
*
|
|
568
|
+
* @component
|
|
569
|
+
*
|
|
570
|
+
* @param {string} [className] - Additional CSS classes
|
|
571
|
+
* @param {...React.ComponentProps<"button">} props - All button element props
|
|
572
|
+
*
|
|
573
|
+
* @example
|
|
574
|
+
* ```tsx
|
|
575
|
+
* // Basic usage (typically placed at end of Sidebar)
|
|
576
|
+
* <Sidebar>
|
|
577
|
+
* <SidebarHeader>Header content</SidebarHeader>
|
|
578
|
+
* <SidebarContent>Navigation content</SidebarContent>
|
|
579
|
+
* <SidebarFooter>Footer content</SidebarFooter>
|
|
580
|
+
* <SidebarRail />
|
|
581
|
+
* </Sidebar>
|
|
582
|
+
* ```
|
|
583
|
+
*
|
|
584
|
+
* @accessibility
|
|
585
|
+
* - ARIA label: "Toggle Sidebar"
|
|
586
|
+
* - Keyboard accessible but with tabIndex={-1} to avoid tab trap
|
|
587
|
+
* - Hover and focus states for discoverability
|
|
588
|
+
* - Cursor changes based on sidebar state and side
|
|
589
|
+
*
|
|
590
|
+
* @see {@link SidebarTrigger} - Alternative toggle button
|
|
591
|
+
* @since 1.0.0
|
|
592
|
+
*/
|
|
593
|
+
function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
|
|
594
|
+
const { toggleSidebar } = useSidebar();
|
|
595
|
+
|
|
596
|
+
return (
|
|
597
|
+
<button
|
|
598
|
+
data-sidebar="rail"
|
|
599
|
+
data-slot="sidebar-rail"
|
|
600
|
+
aria-label="Toggle Sidebar"
|
|
601
|
+
tabIndex={-1}
|
|
602
|
+
onClick={toggleSidebar}
|
|
603
|
+
title="Toggle Sidebar"
|
|
604
|
+
className={cn(
|
|
605
|
+
"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
|
|
606
|
+
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
|
|
607
|
+
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
|
|
608
|
+
"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
|
|
609
|
+
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
|
|
610
|
+
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
|
|
611
|
+
className,
|
|
612
|
+
)}
|
|
613
|
+
{...props}
|
|
614
|
+
/>
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Main content container that adapts to sidebar layout
|
|
620
|
+
*
|
|
621
|
+
* The SidebarInset component serves as the primary content area that automatically
|
|
622
|
+
* adjusts its layout and spacing based on the sidebar's state and variant. It provides
|
|
623
|
+
* proper margins, responsive behavior, and styling coordination with the sidebar.
|
|
624
|
+
*
|
|
625
|
+
* @component
|
|
626
|
+
*
|
|
627
|
+
* @param {string} [className] - Additional CSS classes
|
|
628
|
+
* @param {...React.ComponentProps<"main">} props - All main element props
|
|
629
|
+
*
|
|
630
|
+
* @example
|
|
631
|
+
* ```tsx
|
|
632
|
+
* // Standard layout with header and content
|
|
633
|
+
* <SidebarProvider>
|
|
634
|
+
* <Sidebar>Sidebar content</Sidebar>
|
|
635
|
+
* <SidebarInset>
|
|
636
|
+
* <header className="flex h-16 items-center gap-2 px-4">
|
|
637
|
+
* <SidebarTrigger />
|
|
638
|
+
* <h1>Page Title</h1>
|
|
639
|
+
* </header>
|
|
640
|
+
* <main className="flex-1 p-4">
|
|
641
|
+
* <p>Your main content goes here</p>
|
|
642
|
+
* </main>
|
|
643
|
+
* </SidebarInset>
|
|
644
|
+
* </SidebarProvider>
|
|
645
|
+
* ```
|
|
646
|
+
*
|
|
647
|
+
* @example
|
|
648
|
+
* ```tsx
|
|
649
|
+
* // With inset variant coordination
|
|
650
|
+
* <SidebarProvider>
|
|
651
|
+
* <Sidebar variant="inset">Sidebar</Sidebar>
|
|
652
|
+
* <SidebarInset>
|
|
653
|
+
* // Content automatically gets proper spacing
|
|
654
|
+
* <div>Content with proper inset margins</div>
|
|
655
|
+
* </SidebarInset>
|
|
656
|
+
* </SidebarProvider>
|
|
657
|
+
* ```
|
|
658
|
+
*
|
|
659
|
+
* @features
|
|
660
|
+
* - Automatic layout adjustment based on sidebar state
|
|
661
|
+
* - Responsive margin and padding coordination
|
|
662
|
+
* - Proper spacing for different sidebar variants
|
|
663
|
+
* - Smooth transitions when sidebar state changes
|
|
664
|
+
*
|
|
665
|
+
* @see {@link Sidebar} - Main sidebar component
|
|
666
|
+
* @see {@link SidebarProvider} - Required context provider
|
|
667
|
+
* @since 1.0.0
|
|
668
|
+
*/
|
|
669
|
+
function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
|
|
670
|
+
return (
|
|
671
|
+
<main
|
|
672
|
+
data-slot="sidebar-inset"
|
|
673
|
+
className={cn(
|
|
674
|
+
"bg-background relative flex w-full flex-1 flex-col",
|
|
675
|
+
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
|
|
676
|
+
className,
|
|
677
|
+
)}
|
|
678
|
+
{...props}
|
|
679
|
+
/>
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Input component optimized for sidebar usage
|
|
685
|
+
*
|
|
686
|
+
* A pre-styled input field designed to integrate seamlessly within sidebar layouts.
|
|
687
|
+
* Commonly used for search functionality, filters, or other input needs within
|
|
688
|
+
* the sidebar context. Features appropriate sizing and styling for sidebar use.
|
|
689
|
+
*
|
|
690
|
+
* @component
|
|
691
|
+
*
|
|
692
|
+
* @param {string} [className] - Additional CSS classes
|
|
693
|
+
* @param {...React.ComponentProps<typeof Input>} props - All Input component props
|
|
694
|
+
*
|
|
695
|
+
* @example
|
|
696
|
+
* ```tsx
|
|
697
|
+
* // Search input in sidebar header
|
|
698
|
+
* <SidebarHeader>
|
|
699
|
+
* <h2>Navigation</h2>
|
|
700
|
+
* <SidebarInput
|
|
701
|
+
* placeholder="Search..."
|
|
702
|
+
* onChange={(e) => handleSearch(e.target.value)}
|
|
703
|
+
* />
|
|
704
|
+
* </SidebarHeader>
|
|
705
|
+
* ```
|
|
706
|
+
*
|
|
707
|
+
* @example
|
|
708
|
+
* ```tsx
|
|
709
|
+
* // Filter input with icon
|
|
710
|
+
* <SidebarGroup>
|
|
711
|
+
* <SidebarGroupLabel>Filter Options</SidebarGroupLabel>
|
|
712
|
+
* <SidebarGroupContent>
|
|
713
|
+
* <SidebarInput
|
|
714
|
+
* type="search"
|
|
715
|
+
* placeholder="Filter items"
|
|
716
|
+
* className="mb-2"
|
|
717
|
+
* />
|
|
718
|
+
* </SidebarGroupContent>
|
|
719
|
+
* </SidebarGroup>
|
|
720
|
+
* ```
|
|
721
|
+
*
|
|
722
|
+
* @see {@link Input} - Base input component
|
|
723
|
+
* @since 1.0.0
|
|
724
|
+
*/
|
|
725
|
+
function SidebarInput({
|
|
726
|
+
className,
|
|
727
|
+
...props
|
|
728
|
+
}: React.ComponentProps<typeof Input>) {
|
|
729
|
+
return (
|
|
730
|
+
<Input
|
|
731
|
+
data-slot="sidebar-input"
|
|
732
|
+
data-sidebar="input"
|
|
733
|
+
className={cn("bg-background h-8 w-full shadow-none", className)}
|
|
734
|
+
{...props}
|
|
735
|
+
/>
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Header section container for sidebar content
|
|
741
|
+
*
|
|
742
|
+
* A flexible container for sidebar header content such as logos, application titles,
|
|
743
|
+
* user information, or action buttons. The header remains visible even when the sidebar
|
|
744
|
+
* is collapsed to icon mode, making it ideal for persistent branding or key actions.
|
|
745
|
+
*
|
|
746
|
+
* @component
|
|
747
|
+
*
|
|
748
|
+
* @param {string} [className] - Additional CSS classes
|
|
749
|
+
* @param {...React.ComponentProps<"div">} props - All div element props
|
|
750
|
+
*
|
|
751
|
+
* @example
|
|
752
|
+
* ```tsx
|
|
753
|
+
* // Simple header with title
|
|
754
|
+
* <SidebarHeader>
|
|
755
|
+
* <h2 className="text-lg font-semibold">My Application</h2>
|
|
756
|
+
* </SidebarHeader>
|
|
757
|
+
* ```
|
|
758
|
+
*
|
|
759
|
+
* @example
|
|
760
|
+
* ```tsx
|
|
761
|
+
* // Header with trigger and search
|
|
762
|
+
* <SidebarHeader>
|
|
763
|
+
* <div className="flex items-center justify-between">
|
|
764
|
+
* <h2>Navigation</h2>
|
|
765
|
+
* <SidebarTrigger />
|
|
766
|
+
* </div>
|
|
767
|
+
* <SidebarInput placeholder="Search..." className="mt-2" />
|
|
768
|
+
* </SidebarHeader>
|
|
769
|
+
* ```
|
|
770
|
+
*
|
|
771
|
+
* @example
|
|
772
|
+
* ```tsx
|
|
773
|
+
* // Header with user menu
|
|
774
|
+
* <SidebarHeader>
|
|
775
|
+
* <SidebarMenu>
|
|
776
|
+
* <SidebarMenuItem>
|
|
777
|
+
* <DropdownMenu>
|
|
778
|
+
* <DropdownMenuTrigger asChild>
|
|
779
|
+
* <SidebarMenuButton>
|
|
780
|
+
* <Avatar />
|
|
781
|
+
* <span>John Doe</span>
|
|
782
|
+
* <ChevronDown className="ml-auto" />
|
|
783
|
+
* </SidebarMenuButton>
|
|
784
|
+
* </DropdownMenuTrigger>
|
|
785
|
+
* <DropdownMenuContent>
|
|
786
|
+
* <DropdownMenuItem>Profile</DropdownMenuItem>
|
|
787
|
+
* <DropdownMenuItem>Settings</DropdownMenuItem>
|
|
788
|
+
* <DropdownMenuItem>Logout</DropdownMenuItem>
|
|
789
|
+
* </DropdownMenuContent>
|
|
790
|
+
* </DropdownMenu>
|
|
791
|
+
* </SidebarMenuItem>
|
|
792
|
+
* </SidebarMenu>
|
|
793
|
+
* </SidebarHeader>
|
|
794
|
+
* ```
|
|
795
|
+
*
|
|
796
|
+
* @see {@link SidebarFooter} - Footer section component
|
|
797
|
+
* @see {@link SidebarContent} - Main scrollable content area
|
|
798
|
+
* @since 1.0.0
|
|
799
|
+
*/
|
|
800
|
+
function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
801
|
+
return (
|
|
802
|
+
<div
|
|
803
|
+
data-slot="sidebar-header"
|
|
804
|
+
data-sidebar="header"
|
|
805
|
+
className={cn("flex flex-col gap-2 p-2", className)}
|
|
806
|
+
{...props}
|
|
807
|
+
/>
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Footer section container for sidebar content
|
|
813
|
+
*
|
|
814
|
+
* A container positioned at the bottom of the sidebar for footer content such as
|
|
815
|
+
* user profiles, settings links, logout buttons, or help resources. The footer
|
|
816
|
+
* remains anchored to the bottom regardless of content height.
|
|
817
|
+
*
|
|
818
|
+
* @component
|
|
819
|
+
*
|
|
820
|
+
* @param {string} [className] - Additional CSS classes
|
|
821
|
+
* @param {...React.ComponentProps<"div">} props - All div element props
|
|
822
|
+
*
|
|
823
|
+
* @example
|
|
824
|
+
* ```tsx
|
|
825
|
+
* // Simple footer with settings link
|
|
826
|
+
* <SidebarFooter>
|
|
827
|
+
* <SidebarMenu>
|
|
828
|
+
* <SidebarMenuItem>
|
|
829
|
+
* <SidebarMenuButton>
|
|
830
|
+
* <Settings />
|
|
831
|
+
* <span>Settings</span>
|
|
832
|
+
* </SidebarMenuButton>
|
|
833
|
+
* </SidebarMenuItem>
|
|
834
|
+
* </SidebarMenu>
|
|
835
|
+
* </SidebarFooter>
|
|
836
|
+
* ```
|
|
837
|
+
*
|
|
838
|
+
* @example
|
|
839
|
+
* ```tsx
|
|
840
|
+
* // Footer with user profile and actions
|
|
841
|
+
* <SidebarFooter>
|
|
842
|
+
* <SidebarMenu>
|
|
843
|
+
* <SidebarMenuItem>
|
|
844
|
+
* <SidebarMenuButton>
|
|
845
|
+
* <Avatar className="h-6 w-6" />
|
|
846
|
+
* <div className="flex flex-col text-left">
|
|
847
|
+
* <span className="text-sm">John Doe</span>
|
|
848
|
+
* <span className="text-xs text-muted-foreground">john@example.com</span>
|
|
849
|
+
* </div>
|
|
850
|
+
* </SidebarMenuButton>
|
|
851
|
+
* <SidebarMenuAction>
|
|
852
|
+
* <LogOut />
|
|
853
|
+
* </SidebarMenuAction>
|
|
854
|
+
* </SidebarMenuItem>
|
|
855
|
+
* </SidebarMenu>
|
|
856
|
+
* </SidebarFooter>
|
|
857
|
+
* ```
|
|
858
|
+
*
|
|
859
|
+
* @see {@link SidebarHeader} - Header section component
|
|
860
|
+
* @see {@link SidebarContent} - Main content area
|
|
861
|
+
* @since 1.0.0
|
|
862
|
+
*/
|
|
863
|
+
function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
864
|
+
return (
|
|
865
|
+
<div
|
|
866
|
+
data-slot="sidebar-footer"
|
|
867
|
+
data-sidebar="footer"
|
|
868
|
+
className={cn("flex flex-col gap-2 p-2", className)}
|
|
869
|
+
{...props}
|
|
870
|
+
/>
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Visual separator for organizing sidebar sections
|
|
876
|
+
*
|
|
877
|
+
* A horizontal divider component used to visually separate different sections
|
|
878
|
+
* within the sidebar content. Helps organize navigation groups and improve
|
|
879
|
+
* visual hierarchy and readability.
|
|
880
|
+
*
|
|
881
|
+
* @component
|
|
882
|
+
*
|
|
883
|
+
* @param {string} [className] - Additional CSS classes
|
|
884
|
+
* @param {...React.ComponentProps<typeof Separator>} props - All Separator component props
|
|
885
|
+
*
|
|
886
|
+
* @example
|
|
887
|
+
* ```tsx
|
|
888
|
+
* // Separating navigation groups
|
|
889
|
+
* <SidebarContent>
|
|
890
|
+
* <SidebarGroup>
|
|
891
|
+
* <SidebarGroupLabel>Main Navigation</SidebarGroupLabel>
|
|
892
|
+
* <SidebarGroupContent>
|
|
893
|
+
* // Main nav items
|
|
894
|
+
* </SidebarGroupContent>
|
|
895
|
+
* </SidebarGroup>
|
|
896
|
+
*
|
|
897
|
+
* <SidebarSeparator />
|
|
898
|
+
*
|
|
899
|
+
* <SidebarGroup>
|
|
900
|
+
* <SidebarGroupLabel>Settings</SidebarGroupLabel>
|
|
901
|
+
* <SidebarGroupContent>
|
|
902
|
+
* // Settings items
|
|
903
|
+
* </SidebarGroupContent>
|
|
904
|
+
* </SidebarGroup>
|
|
905
|
+
* </SidebarContent>
|
|
906
|
+
* ```
|
|
907
|
+
*
|
|
908
|
+
* @example
|
|
909
|
+
* ```tsx
|
|
910
|
+
* // Between header and content
|
|
911
|
+
* <Sidebar>
|
|
912
|
+
* <SidebarHeader>
|
|
913
|
+
* <h2>Application</h2>
|
|
914
|
+
* </SidebarHeader>
|
|
915
|
+
* <SidebarSeparator />
|
|
916
|
+
* <SidebarContent>
|
|
917
|
+
* // Navigation content
|
|
918
|
+
* </SidebarContent>
|
|
919
|
+
* </Sidebar>
|
|
920
|
+
* ```
|
|
921
|
+
*
|
|
922
|
+
* @see {@link Separator} - Base separator component
|
|
923
|
+
* @since 1.0.0
|
|
924
|
+
*/
|
|
925
|
+
function SidebarSeparator({
|
|
926
|
+
className,
|
|
927
|
+
...props
|
|
928
|
+
}: React.ComponentProps<typeof Separator>) {
|
|
929
|
+
return (
|
|
930
|
+
<Separator
|
|
931
|
+
data-slot="sidebar-separator"
|
|
932
|
+
data-sidebar="separator"
|
|
933
|
+
className={cn("bg-sidebar-border mx-2 w-auto", className)}
|
|
934
|
+
{...props}
|
|
935
|
+
/>
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* Scrollable main content area of the sidebar
|
|
941
|
+
*
|
|
942
|
+
* The primary content container that houses navigation groups, menus, and other
|
|
943
|
+
* sidebar content. Provides automatic scrolling when content exceeds available
|
|
944
|
+
* height and handles overflow behavior when the sidebar is collapsed.
|
|
945
|
+
*
|
|
946
|
+
* @component
|
|
947
|
+
*
|
|
948
|
+
* @param {string} [className] - Additional CSS classes
|
|
949
|
+
* @param {...React.ComponentProps<"div">} props - All div element props
|
|
950
|
+
*
|
|
951
|
+
* @example
|
|
952
|
+
* ```tsx
|
|
953
|
+
* // Basic content with navigation groups
|
|
954
|
+
* <SidebarContent>
|
|
955
|
+
* <SidebarGroup>
|
|
956
|
+
* <SidebarGroupLabel>Main</SidebarGroupLabel>
|
|
957
|
+
* <SidebarGroupContent>
|
|
958
|
+
* <SidebarMenu>
|
|
959
|
+
* <SidebarMenuItem>
|
|
960
|
+
* <SidebarMenuButton>
|
|
961
|
+
* <Home />
|
|
962
|
+
* <span>Dashboard</span>
|
|
963
|
+
* </SidebarMenuButton>
|
|
964
|
+
* </SidebarMenuItem>
|
|
965
|
+
* </SidebarMenu>
|
|
966
|
+
* </SidebarGroupContent>
|
|
967
|
+
* </SidebarGroup>
|
|
968
|
+
* </SidebarContent>
|
|
969
|
+
* ```
|
|
970
|
+
*
|
|
971
|
+
* @example
|
|
972
|
+
* ```tsx
|
|
973
|
+
* // Multiple groups with separators
|
|
974
|
+
* <SidebarContent>
|
|
975
|
+
* <SidebarGroup>
|
|
976
|
+
* <SidebarGroupLabel>Navigation</SidebarGroupLabel>
|
|
977
|
+
* <SidebarGroupContent>
|
|
978
|
+
* // Navigation items
|
|
979
|
+
* </SidebarGroupContent>
|
|
980
|
+
* </SidebarGroup>
|
|
981
|
+
*
|
|
982
|
+
* <SidebarSeparator />
|
|
983
|
+
*
|
|
984
|
+
* <SidebarGroup>
|
|
985
|
+
* <SidebarGroupLabel>Projects</SidebarGroupLabel>
|
|
986
|
+
* <SidebarGroupContent>
|
|
987
|
+
* // Project items
|
|
988
|
+
* </SidebarGroupContent>
|
|
989
|
+
* </SidebarGroup>
|
|
990
|
+
* </SidebarContent>
|
|
991
|
+
* ```
|
|
992
|
+
*
|
|
993
|
+
* @features
|
|
994
|
+
* - Automatic scrolling for overflow content
|
|
995
|
+
* - Responsive to sidebar collapse states
|
|
996
|
+
* - Flexible gap spacing between child elements
|
|
997
|
+
* - Overflow hidden when collapsed to icon mode
|
|
998
|
+
*
|
|
999
|
+
* @see {@link SidebarGroup} - Content organization component
|
|
1000
|
+
* @see {@link SidebarMenu} - Menu container component
|
|
1001
|
+
* @since 1.0.0
|
|
1002
|
+
*/
|
|
1003
|
+
function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
1004
|
+
return (
|
|
1005
|
+
<div
|
|
1006
|
+
data-slot="sidebar-content"
|
|
1007
|
+
data-sidebar="content"
|
|
1008
|
+
className={cn(
|
|
1009
|
+
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
|
|
1010
|
+
className,
|
|
1011
|
+
)}
|
|
1012
|
+
{...props}
|
|
1013
|
+
/>
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Container for organizing related sidebar navigation items
|
|
1019
|
+
*
|
|
1020
|
+
* Groups related navigation items together with optional labels and actions,
|
|
1021
|
+
* providing visual organization and hierarchy for complex navigation structures.
|
|
1022
|
+
* Groups can include labels, action buttons, and any combination of menu items.
|
|
1023
|
+
*
|
|
1024
|
+
* @component
|
|
1025
|
+
*
|
|
1026
|
+
* @param {string} [className] - Additional CSS classes
|
|
1027
|
+
* @param {...React.ComponentProps<"div">} props - All div element props
|
|
1028
|
+
*
|
|
1029
|
+
* @example
|
|
1030
|
+
* ```tsx
|
|
1031
|
+
* // Basic group with label and menu
|
|
1032
|
+
* <SidebarGroup>
|
|
1033
|
+
* <SidebarGroupLabel>Navigation</SidebarGroupLabel>
|
|
1034
|
+
* <SidebarGroupContent>
|
|
1035
|
+
* <SidebarMenu>
|
|
1036
|
+
* <SidebarMenuItem>
|
|
1037
|
+
* <SidebarMenuButton>
|
|
1038
|
+
* <Home />
|
|
1039
|
+
* <span>Dashboard</span>
|
|
1040
|
+
* </SidebarMenuButton>
|
|
1041
|
+
* </SidebarMenuItem>
|
|
1042
|
+
* </SidebarMenu>
|
|
1043
|
+
* </SidebarGroupContent>
|
|
1044
|
+
* </SidebarGroup>
|
|
1045
|
+
* ```
|
|
1046
|
+
*
|
|
1047
|
+
* @example
|
|
1048
|
+
* ```tsx
|
|
1049
|
+
* // Group with action button
|
|
1050
|
+
* <SidebarGroup>
|
|
1051
|
+
* <SidebarGroupLabel>Projects</SidebarGroupLabel>
|
|
1052
|
+
* <SidebarGroupAction>
|
|
1053
|
+
* <Plus className="h-4 w-4" />
|
|
1054
|
+
* <span className="sr-only">Add Project</span>
|
|
1055
|
+
* </SidebarGroupAction>
|
|
1056
|
+
* <SidebarGroupContent>
|
|
1057
|
+
* // Project items
|
|
1058
|
+
* </SidebarGroupContent>
|
|
1059
|
+
* </SidebarGroup>
|
|
1060
|
+
* ```
|
|
1061
|
+
*
|
|
1062
|
+
* @see {@link SidebarGroupLabel} - Group label component
|
|
1063
|
+
* @see {@link SidebarGroupAction} - Group action button
|
|
1064
|
+
* @see {@link SidebarGroupContent} - Group content container
|
|
1065
|
+
* @since 1.0.0
|
|
1066
|
+
*/
|
|
1067
|
+
function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
1068
|
+
return (
|
|
1069
|
+
<div
|
|
1070
|
+
data-slot="sidebar-group"
|
|
1071
|
+
data-sidebar="group"
|
|
1072
|
+
className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
|
|
1073
|
+
{...props}
|
|
1074
|
+
/>
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Label component for sidebar groups
|
|
1080
|
+
*
|
|
1081
|
+
* Displays a descriptive label for a group of sidebar items, providing clear
|
|
1082
|
+
* categorization and hierarchy. The label automatically hides when the sidebar
|
|
1083
|
+
* is collapsed to icon mode to maintain clean visual presentation.
|
|
1084
|
+
*
|
|
1085
|
+
* @component
|
|
1086
|
+
*
|
|
1087
|
+
* @param {string} [className] - Additional CSS classes
|
|
1088
|
+
* @param {boolean} [asChild=false] - Render as child element (using Slot)
|
|
1089
|
+
* @param {...React.ComponentProps<"div">} props - All div element props
|
|
1090
|
+
*
|
|
1091
|
+
* @example
|
|
1092
|
+
* ```tsx
|
|
1093
|
+
* // Standard text label
|
|
1094
|
+
* <SidebarGroup>
|
|
1095
|
+
* <SidebarGroupLabel>Main Navigation</SidebarGroupLabel>
|
|
1096
|
+
* <SidebarGroupContent>
|
|
1097
|
+
* // Menu items
|
|
1098
|
+
* </SidebarGroupContent>
|
|
1099
|
+
* </SidebarGroup>
|
|
1100
|
+
* ```
|
|
1101
|
+
*
|
|
1102
|
+
* @example
|
|
1103
|
+
* ```tsx
|
|
1104
|
+
* // Custom element using asChild
|
|
1105
|
+
* <SidebarGroup>
|
|
1106
|
+
* <SidebarGroupLabel asChild>
|
|
1107
|
+
* <h3 className="custom-heading">Projects</h3>
|
|
1108
|
+
* </SidebarGroupLabel>
|
|
1109
|
+
* <SidebarGroupContent>
|
|
1110
|
+
* // Project items
|
|
1111
|
+
* </SidebarGroupContent>
|
|
1112
|
+
* </SidebarGroup>
|
|
1113
|
+
* ```
|
|
1114
|
+
*
|
|
1115
|
+
* @features
|
|
1116
|
+
* - Automatic hide/show based on sidebar collapse state
|
|
1117
|
+
* - Smooth opacity and margin transitions
|
|
1118
|
+
* - Focus management for keyboard navigation
|
|
1119
|
+
* - Support for custom elements via asChild prop
|
|
1120
|
+
*
|
|
1121
|
+
* @see {@link SidebarGroup} - Parent group container
|
|
1122
|
+
* @see {@link SidebarGroupAction} - Associated action button
|
|
1123
|
+
* @since 1.0.0
|
|
1124
|
+
*/
|
|
1125
|
+
function SidebarGroupLabel({
|
|
1126
|
+
className,
|
|
1127
|
+
asChild = false,
|
|
1128
|
+
...props
|
|
1129
|
+
}: React.ComponentProps<"div"> & { asChild?: boolean }) {
|
|
1130
|
+
const Comp = asChild ? Slot : "div";
|
|
1131
|
+
|
|
1132
|
+
return (
|
|
1133
|
+
<Comp
|
|
1134
|
+
data-slot="sidebar-group-label"
|
|
1135
|
+
data-sidebar="group-label"
|
|
1136
|
+
className={cn(
|
|
1137
|
+
"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
1138
|
+
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
|
|
1139
|
+
className,
|
|
1140
|
+
)}
|
|
1141
|
+
{...props}
|
|
1142
|
+
/>
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Action button positioned within sidebar groups
|
|
1148
|
+
*
|
|
1149
|
+
* A compact action button positioned at the top-right of a sidebar group,
|
|
1150
|
+
* typically used for group-specific actions such as adding new items, editing
|
|
1151
|
+
* group settings, or accessing group options. Automatically hidden when collapsed.
|
|
1152
|
+
*
|
|
1153
|
+
* @component
|
|
1154
|
+
*
|
|
1155
|
+
* @param {string} [className] - Additional CSS classes
|
|
1156
|
+
* @param {boolean} [asChild=false] - Render as child element (using Slot)
|
|
1157
|
+
* @param {...React.ComponentProps<"button">} props - All button element props
|
|
1158
|
+
*
|
|
1159
|
+
* @example
|
|
1160
|
+
* ```tsx
|
|
1161
|
+
* // Add button for projects group
|
|
1162
|
+
* <SidebarGroup>
|
|
1163
|
+
* <SidebarGroupLabel>Projects</SidebarGroupLabel>
|
|
1164
|
+
* <SidebarGroupAction>
|
|
1165
|
+
* <Plus className="h-4 w-4" />
|
|
1166
|
+
* <span className="sr-only">Add Project</span>
|
|
1167
|
+
* </SidebarGroupAction>
|
|
1168
|
+
* <SidebarGroupContent>
|
|
1169
|
+
* // Project items
|
|
1170
|
+
* </SidebarGroupContent>
|
|
1171
|
+
* </SidebarGroup>
|
|
1172
|
+
* ```
|
|
1173
|
+
*
|
|
1174
|
+
* @example
|
|
1175
|
+
* ```tsx
|
|
1176
|
+
* // Custom action with asChild
|
|
1177
|
+
* <SidebarGroup>
|
|
1178
|
+
* <SidebarGroupLabel>Settings</SidebarGroupLabel>
|
|
1179
|
+
* <SidebarGroupAction asChild>
|
|
1180
|
+
* <Button variant="ghost" size="sm">
|
|
1181
|
+
* <MoreHorizontal className="h-4 w-4" />
|
|
1182
|
+
* </Button>
|
|
1183
|
+
* </SidebarGroupAction>
|
|
1184
|
+
* <SidebarGroupContent>
|
|
1185
|
+
* // Settings items
|
|
1186
|
+
* </SidebarGroupContent>
|
|
1187
|
+
* </SidebarGroup>
|
|
1188
|
+
* ```
|
|
1189
|
+
*
|
|
1190
|
+
* @accessibility
|
|
1191
|
+
* - Increased touch target on mobile devices
|
|
1192
|
+
* - Keyboard accessible with focus management
|
|
1193
|
+
* - Screen reader support with descriptive labels
|
|
1194
|
+
*
|
|
1195
|
+
* @see {@link SidebarGroup} - Parent group container
|
|
1196
|
+
* @see {@link SidebarGroupLabel} - Associated group label
|
|
1197
|
+
* @since 1.0.0
|
|
1198
|
+
*/
|
|
1199
|
+
function SidebarGroupAction({
|
|
1200
|
+
className,
|
|
1201
|
+
asChild = false,
|
|
1202
|
+
...props
|
|
1203
|
+
}: React.ComponentProps<"button"> & { asChild?: boolean }) {
|
|
1204
|
+
const Comp = asChild ? Slot : "button";
|
|
1205
|
+
|
|
1206
|
+
return (
|
|
1207
|
+
<Comp
|
|
1208
|
+
data-slot="sidebar-group-action"
|
|
1209
|
+
data-sidebar="group-action"
|
|
1210
|
+
className={cn(
|
|
1211
|
+
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
1212
|
+
// Increases the hit area of the button on mobile.
|
|
1213
|
+
"after:absolute after:-inset-2 md:after:hidden",
|
|
1214
|
+
"group-data-[collapsible=icon]:hidden",
|
|
1215
|
+
className,
|
|
1216
|
+
)}
|
|
1217
|
+
{...props}
|
|
1218
|
+
/>
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* Content container for sidebar group items
|
|
1224
|
+
*
|
|
1225
|
+
* Houses the actual navigation items, menus, and content within a sidebar group.
|
|
1226
|
+
* Provides consistent spacing and organization for group content while maintaining
|
|
1227
|
+
* proper typography and layout coordination.
|
|
1228
|
+
*
|
|
1229
|
+
* @component
|
|
1230
|
+
*
|
|
1231
|
+
* @param {string} [className] - Additional CSS classes
|
|
1232
|
+
* @param {...React.ComponentProps<"div">} props - All div element props
|
|
1233
|
+
*
|
|
1234
|
+
* @example
|
|
1235
|
+
* ```tsx
|
|
1236
|
+
* // Basic content with menu items
|
|
1237
|
+
* <SidebarGroup>
|
|
1238
|
+
* <SidebarGroupLabel>Navigation</SidebarGroupLabel>
|
|
1239
|
+
* <SidebarGroupContent>
|
|
1240
|
+
* <SidebarMenu>
|
|
1241
|
+
* <SidebarMenuItem>
|
|
1242
|
+
* <SidebarMenuButton>
|
|
1243
|
+
* <Home />
|
|
1244
|
+
* <span>Dashboard</span>
|
|
1245
|
+
* </SidebarMenuButton>
|
|
1246
|
+
* </SidebarMenuItem>
|
|
1247
|
+
* <SidebarMenuItem>
|
|
1248
|
+
* <SidebarMenuButton>
|
|
1249
|
+
* <Settings />
|
|
1250
|
+
* <span>Settings</span>
|
|
1251
|
+
* </SidebarMenuButton>
|
|
1252
|
+
* </SidebarMenuItem>
|
|
1253
|
+
* </SidebarMenu>
|
|
1254
|
+
* </SidebarGroupContent>
|
|
1255
|
+
* </SidebarGroup>
|
|
1256
|
+
* ```
|
|
1257
|
+
*
|
|
1258
|
+
* @example
|
|
1259
|
+
* ```tsx
|
|
1260
|
+
* // Multiple menus in group content
|
|
1261
|
+
* <SidebarGroup>
|
|
1262
|
+
* <SidebarGroupLabel>Projects</SidebarGroupLabel>
|
|
1263
|
+
* <SidebarGroupContent>
|
|
1264
|
+
* <SidebarMenu>
|
|
1265
|
+
* // Recent projects
|
|
1266
|
+
* </SidebarMenu>
|
|
1267
|
+
* <SidebarSeparator />
|
|
1268
|
+
* <SidebarMenu>
|
|
1269
|
+
* // Archived projects
|
|
1270
|
+
* </SidebarMenu>
|
|
1271
|
+
* </SidebarGroupContent>
|
|
1272
|
+
* </SidebarGroup>
|
|
1273
|
+
* ```
|
|
1274
|
+
*
|
|
1275
|
+
* @see {@link SidebarGroup} - Parent group container
|
|
1276
|
+
* @see {@link SidebarMenu} - Menu container component
|
|
1277
|
+
* @since 1.0.0
|
|
1278
|
+
*/
|
|
1279
|
+
function SidebarGroupContent({
|
|
1280
|
+
className,
|
|
1281
|
+
...props
|
|
1282
|
+
}: React.ComponentProps<"div">) {
|
|
1283
|
+
return (
|
|
1284
|
+
<div
|
|
1285
|
+
data-slot="sidebar-group-content"
|
|
1286
|
+
data-sidebar="group-content"
|
|
1287
|
+
className={cn("w-full text-sm", className)}
|
|
1288
|
+
{...props}
|
|
1289
|
+
/>
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
/**
|
|
1294
|
+
* Navigation menu container for organizing sidebar items
|
|
1295
|
+
*
|
|
1296
|
+
* An unordered list container that holds and organizes SidebarMenuItem components.
|
|
1297
|
+
* Provides consistent spacing, layout, and semantic structure for navigation items
|
|
1298
|
+
* within sidebar groups or sections.
|
|
1299
|
+
*
|
|
1300
|
+
* @component
|
|
1301
|
+
*
|
|
1302
|
+
* @param {string} [className] - Additional CSS classes
|
|
1303
|
+
* @param {...React.ComponentProps<"ul">} props - All ul element props
|
|
1304
|
+
*
|
|
1305
|
+
* @example
|
|
1306
|
+
* ```tsx
|
|
1307
|
+
* // Basic navigation menu
|
|
1308
|
+
* <SidebarMenu>
|
|
1309
|
+
* <SidebarMenuItem>
|
|
1310
|
+
* <SidebarMenuButton>
|
|
1311
|
+
* <Home />
|
|
1312
|
+
* <span>Dashboard</span>
|
|
1313
|
+
* </SidebarMenuButton>
|
|
1314
|
+
* </SidebarMenuItem>
|
|
1315
|
+
* <SidebarMenuItem>
|
|
1316
|
+
* <SidebarMenuButton>
|
|
1317
|
+
* <Settings />
|
|
1318
|
+
* <span>Settings</span>
|
|
1319
|
+
* </SidebarMenuButton>
|
|
1320
|
+
* </SidebarMenuItem>
|
|
1321
|
+
* </SidebarMenu>
|
|
1322
|
+
* ```
|
|
1323
|
+
*
|
|
1324
|
+
* @example
|
|
1325
|
+
* ```tsx
|
|
1326
|
+
* // Menu with badges and actions
|
|
1327
|
+
* <SidebarMenu>
|
|
1328
|
+
* <SidebarMenuItem>
|
|
1329
|
+
* <SidebarMenuButton>
|
|
1330
|
+
* <Inbox />
|
|
1331
|
+
* <span>Messages</span>
|
|
1332
|
+
* </SidebarMenuButton>
|
|
1333
|
+
* <SidebarMenuBadge>3</SidebarMenuBadge>
|
|
1334
|
+
* </SidebarMenuItem>
|
|
1335
|
+
* <SidebarMenuItem>
|
|
1336
|
+
* <SidebarMenuButton>
|
|
1337
|
+
* <Archive />
|
|
1338
|
+
* <span>Archive</span>
|
|
1339
|
+
* </SidebarMenuButton>
|
|
1340
|
+
* <SidebarMenuAction>
|
|
1341
|
+
* <MoreHorizontal />
|
|
1342
|
+
* </SidebarMenuAction>
|
|
1343
|
+
* </SidebarMenuItem>
|
|
1344
|
+
* </SidebarMenu>
|
|
1345
|
+
* ```
|
|
1346
|
+
*
|
|
1347
|
+
* @accessibility
|
|
1348
|
+
* - Semantic ul/li structure for screen readers
|
|
1349
|
+
* - Proper navigation landmarks
|
|
1350
|
+
* - Keyboard navigation support
|
|
1351
|
+
*
|
|
1352
|
+
* @see {@link SidebarMenuItem} - Individual menu item component
|
|
1353
|
+
* @see {@link SidebarMenuButton} - Interactive menu button
|
|
1354
|
+
* @since 1.0.0
|
|
1355
|
+
*/
|
|
1356
|
+
function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
|
|
1357
|
+
return (
|
|
1358
|
+
<ul
|
|
1359
|
+
data-slot="sidebar-menu"
|
|
1360
|
+
data-sidebar="menu"
|
|
1361
|
+
className={cn("flex w-full min-w-0 flex-col gap-1", className)}
|
|
1362
|
+
{...props}
|
|
1363
|
+
/>
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
* Individual menu item container for sidebar navigation
|
|
1369
|
+
*
|
|
1370
|
+
* A list item container that wraps a single navigation item, typically containing
|
|
1371
|
+
* a SidebarMenuButton along with optional components like SidebarMenuAction,
|
|
1372
|
+
* SidebarMenuBadge, or SidebarMenuSub for hierarchical navigation.
|
|
1373
|
+
*
|
|
1374
|
+
* @component
|
|
1375
|
+
*
|
|
1376
|
+
* @param {string} [className] - Additional CSS classes
|
|
1377
|
+
* @param {...React.ComponentProps<"li">} props - All li element props
|
|
1378
|
+
*
|
|
1379
|
+
* @example
|
|
1380
|
+
* ```tsx
|
|
1381
|
+
* // Simple menu item with button
|
|
1382
|
+
* <SidebarMenuItem>
|
|
1383
|
+
* <SidebarMenuButton>
|
|
1384
|
+
* <Home />
|
|
1385
|
+
* <span>Dashboard</span>
|
|
1386
|
+
* </SidebarMenuButton>
|
|
1387
|
+
* </SidebarMenuItem>
|
|
1388
|
+
* ```
|
|
1389
|
+
*
|
|
1390
|
+
* @example
|
|
1391
|
+
* ```tsx
|
|
1392
|
+
* // Menu item with badge
|
|
1393
|
+
* <SidebarMenuItem>
|
|
1394
|
+
* <SidebarMenuButton>
|
|
1395
|
+
* <Inbox />
|
|
1396
|
+
* <span>Messages</span>
|
|
1397
|
+
* </SidebarMenuButton>
|
|
1398
|
+
* <SidebarMenuBadge>5</SidebarMenuBadge>
|
|
1399
|
+
* </SidebarMenuItem>
|
|
1400
|
+
* ```
|
|
1401
|
+
*
|
|
1402
|
+
* @example
|
|
1403
|
+
* ```tsx
|
|
1404
|
+
* // Menu item with action button
|
|
1405
|
+
* <SidebarMenuItem>
|
|
1406
|
+
* <SidebarMenuButton>
|
|
1407
|
+
* <Folder />
|
|
1408
|
+
* <span>Projects</span>
|
|
1409
|
+
* </SidebarMenuButton>
|
|
1410
|
+
* <SidebarMenuAction showOnHover>
|
|
1411
|
+
* <Plus />
|
|
1412
|
+
* </SidebarMenuAction>
|
|
1413
|
+
* </SidebarMenuItem>
|
|
1414
|
+
* ```
|
|
1415
|
+
*
|
|
1416
|
+
* @example
|
|
1417
|
+
* ```tsx
|
|
1418
|
+
* // Menu item with submenu
|
|
1419
|
+
* <SidebarMenuItem>
|
|
1420
|
+
* <SidebarMenuButton>
|
|
1421
|
+
* <Folder />
|
|
1422
|
+
* <span>Projects</span>
|
|
1423
|
+
* <ChevronRight className="ml-auto" />
|
|
1424
|
+
* </SidebarMenuButton>
|
|
1425
|
+
* <SidebarMenuSub>
|
|
1426
|
+
* <SidebarMenuSubItem>
|
|
1427
|
+
* <SidebarMenuSubButton>Web App</SidebarMenuSubButton>
|
|
1428
|
+
* </SidebarMenuSubItem>
|
|
1429
|
+
* </SidebarMenuSub>
|
|
1430
|
+
* </SidebarMenuItem>
|
|
1431
|
+
* ```
|
|
1432
|
+
*
|
|
1433
|
+
* @see {@link SidebarMenu} - Parent menu container
|
|
1434
|
+
* @see {@link SidebarMenuButton} - Interactive button component
|
|
1435
|
+
* @see {@link SidebarMenuAction} - Action button component
|
|
1436
|
+
* @see {@link SidebarMenuBadge} - Badge indicator component
|
|
1437
|
+
* @since 1.0.0
|
|
1438
|
+
*/
|
|
1439
|
+
function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
|
|
1440
|
+
return (
|
|
1441
|
+
<li
|
|
1442
|
+
data-slot="sidebar-menu-item"
|
|
1443
|
+
data-sidebar="menu-item"
|
|
1444
|
+
className={cn("group/menu-item relative", className)}
|
|
1445
|
+
{...props}
|
|
1446
|
+
/>
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
const sidebarMenuButtonVariants = cva(
|
|
1451
|
+
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
|
|
1452
|
+
{
|
|
1453
|
+
variants: {
|
|
1454
|
+
variant: {
|
|
1455
|
+
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
1456
|
+
outline:
|
|
1457
|
+
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
|
|
1458
|
+
},
|
|
1459
|
+
size: {
|
|
1460
|
+
default: "h-8 text-sm",
|
|
1461
|
+
sm: "h-7 text-xs",
|
|
1462
|
+
lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
|
|
1463
|
+
},
|
|
1464
|
+
},
|
|
1465
|
+
defaultVariants: {
|
|
1466
|
+
variant: "default",
|
|
1467
|
+
size: "default",
|
|
1468
|
+
},
|
|
1469
|
+
},
|
|
1470
|
+
);
|
|
1471
|
+
|
|
1472
|
+
/**
|
|
1473
|
+
* Interactive navigation button for sidebar menu items
|
|
1474
|
+
*
|
|
1475
|
+
* The primary interactive element for sidebar navigation, offering a flexible button
|
|
1476
|
+
* component that supports active states, multiple visual variants, tooltip integration
|
|
1477
|
+
* for collapsed modes, and composition patterns through the asChild prop. Automatically
|
|
1478
|
+
* adapts its appearance based on sidebar state and provides smooth hover/focus transitions.
|
|
1479
|
+
*
|
|
1480
|
+
* @component
|
|
1481
|
+
*
|
|
1482
|
+
* @param {boolean} [asChild=false] - Render as child element using Slot composition
|
|
1483
|
+
* @param {boolean} [isActive=false] - Whether this menu item is currently active
|
|
1484
|
+
* @param {"default" | "outline"} [variant="default"] - Visual variant:
|
|
1485
|
+
* - `default`: Standard button appearance with hover states
|
|
1486
|
+
* - `outline`: Button with border and background on hover
|
|
1487
|
+
* @param {"default" | "sm" | "lg"} [size="default"] - Button size variant:
|
|
1488
|
+
* - `default`: Standard height (h-8)
|
|
1489
|
+
* - `sm`: Small height (h-7)
|
|
1490
|
+
* - `lg`: Large height (h-12)
|
|
1491
|
+
* @param {string | React.ComponentProps<typeof TooltipContent>} [tooltip] - Tooltip content shown when sidebar is collapsed
|
|
1492
|
+
* @param {string} [className] - Additional CSS classes
|
|
1493
|
+
* @param {...React.ComponentProps<"button">} props - All button element props
|
|
1494
|
+
*
|
|
1495
|
+
* @example
|
|
1496
|
+
* ```tsx
|
|
1497
|
+
* // Basic navigation button
|
|
1498
|
+
* <SidebarMenuButton>
|
|
1499
|
+
* <Home className="h-4 w-4" />
|
|
1500
|
+
* <span>Dashboard</span>
|
|
1501
|
+
* </SidebarMenuButton>
|
|
1502
|
+
* ```
|
|
1503
|
+
*
|
|
1504
|
+
* @example
|
|
1505
|
+
* ```tsx
|
|
1506
|
+
* // Active state with Link composition
|
|
1507
|
+
* <SidebarMenuButton asChild isActive={pathname === '/dashboard'}>
|
|
1508
|
+
* <Link href="/dashboard">
|
|
1509
|
+
* <Home className="h-4 w-4" />
|
|
1510
|
+
* <span>Dashboard</span>
|
|
1511
|
+
* </Link>
|
|
1512
|
+
* </SidebarMenuButton>
|
|
1513
|
+
* ```
|
|
1514
|
+
*
|
|
1515
|
+
* @example
|
|
1516
|
+
* ```tsx
|
|
1517
|
+
* // With tooltip for collapsed mode
|
|
1518
|
+
* <SidebarMenuButton tooltip="Go to Dashboard">
|
|
1519
|
+
* <Home className="h-4 w-4" />
|
|
1520
|
+
* <span>Dashboard</span>
|
|
1521
|
+
* </SidebarMenuButton>
|
|
1522
|
+
* ```
|
|
1523
|
+
*
|
|
1524
|
+
* @example
|
|
1525
|
+
* ```tsx
|
|
1526
|
+
* // Different variants and sizes
|
|
1527
|
+
* <SidebarMenuButton variant="outline" size="lg">
|
|
1528
|
+
* <Settings className="h-4 w-4" />
|
|
1529
|
+
* <span>Settings</span>
|
|
1530
|
+
* </SidebarMenuButton>
|
|
1531
|
+
* ```
|
|
1532
|
+
*
|
|
1533
|
+
* @example
|
|
1534
|
+
* ```tsx
|
|
1535
|
+
* // Custom tooltip with props
|
|
1536
|
+
* <SidebarMenuButton
|
|
1537
|
+
* tooltip={{
|
|
1538
|
+
* children: "Dashboard Overview",
|
|
1539
|
+
* side: "right",
|
|
1540
|
+
* sideOffset: 10
|
|
1541
|
+
* }}
|
|
1542
|
+
* >
|
|
1543
|
+
* <Home className="h-4 w-4" />
|
|
1544
|
+
* <span>Dashboard</span>
|
|
1545
|
+
* </SidebarMenuButton>
|
|
1546
|
+
* ```
|
|
1547
|
+
*
|
|
1548
|
+
* @accessibility
|
|
1549
|
+
* - Full keyboard navigation support (Enter, Space)
|
|
1550
|
+
* - Focus visible ring for keyboard users
|
|
1551
|
+
* - Active state indication for screen readers
|
|
1552
|
+
* - Tooltip automatically shown/hidden based on sidebar state
|
|
1553
|
+
* - Proper contrast ratios for all states
|
|
1554
|
+
* - Support for screen reader announcements
|
|
1555
|
+
*
|
|
1556
|
+
* @responsive
|
|
1557
|
+
* - Automatically adapts to collapsed sidebar (icon-only mode)
|
|
1558
|
+
* - Tooltip integration for collapsed state
|
|
1559
|
+
* - Touch-friendly sizing on mobile devices
|
|
1560
|
+
* - Smooth transitions between states
|
|
1561
|
+
*
|
|
1562
|
+
* @see {@link SidebarMenuItem} - Parent menu item container
|
|
1563
|
+
* @see {@link SidebarMenuAction} - Secondary action button
|
|
1564
|
+
* @see {@link SidebarMenuBadge} - Badge component for notifications
|
|
1565
|
+
* @see {@link useSidebar} - Hook for sidebar state
|
|
1566
|
+
* @since 1.0.0
|
|
1567
|
+
*/
|
|
1568
|
+
function SidebarMenuButton({
|
|
1569
|
+
asChild = false,
|
|
1570
|
+
isActive = false,
|
|
1571
|
+
variant = "default",
|
|
1572
|
+
size = "default",
|
|
1573
|
+
tooltip,
|
|
1574
|
+
className,
|
|
1575
|
+
...props
|
|
1576
|
+
}: React.ComponentProps<"button"> & {
|
|
1577
|
+
asChild?: boolean;
|
|
1578
|
+
isActive?: boolean;
|
|
1579
|
+
tooltip?: string | React.ComponentProps<typeof TooltipContent>;
|
|
1580
|
+
} & VariantProps<typeof sidebarMenuButtonVariants>) {
|
|
1581
|
+
const Comp = asChild ? Slot : "button";
|
|
1582
|
+
const { isMobile, state } = useSidebar();
|
|
1583
|
+
|
|
1584
|
+
const button = (
|
|
1585
|
+
<Comp
|
|
1586
|
+
data-slot="sidebar-menu-button"
|
|
1587
|
+
data-sidebar="menu-button"
|
|
1588
|
+
data-size={size}
|
|
1589
|
+
data-active={isActive}
|
|
1590
|
+
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
|
|
1591
|
+
{...props}
|
|
1592
|
+
/>
|
|
1593
|
+
);
|
|
1594
|
+
|
|
1595
|
+
if (!tooltip) {
|
|
1596
|
+
return button;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
if (typeof tooltip === "string") {
|
|
1600
|
+
tooltip = {
|
|
1601
|
+
children: tooltip,
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
return (
|
|
1606
|
+
<Tooltip>
|
|
1607
|
+
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
|
1608
|
+
<TooltipContent
|
|
1609
|
+
side="right"
|
|
1610
|
+
align="center"
|
|
1611
|
+
hidden={state !== "collapsed" || isMobile}
|
|
1612
|
+
{...tooltip}
|
|
1613
|
+
/>
|
|
1614
|
+
</Tooltip>
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* Secondary action button for sidebar menu items
|
|
1620
|
+
*
|
|
1621
|
+
* A compact action button positioned on the right side of menu items, providing
|
|
1622
|
+
* secondary actions like edit, delete, or more options. Can be configured to show
|
|
1623
|
+
* only on hover for cleaner interfaces or remain always visible for important actions.
|
|
1624
|
+
*
|
|
1625
|
+
* @component
|
|
1626
|
+
*
|
|
1627
|
+
* @param {string} [className] - Additional CSS classes
|
|
1628
|
+
* @param {boolean} [asChild=false] - Render as child element using Slot composition
|
|
1629
|
+
* @param {boolean} [showOnHover=false] - Only show button on menu item hover
|
|
1630
|
+
* @param {...React.ComponentProps<"button">} props - All button element props
|
|
1631
|
+
*
|
|
1632
|
+
* @example
|
|
1633
|
+
* ```tsx
|
|
1634
|
+
* // Always visible action button
|
|
1635
|
+
* <SidebarMenuItem>
|
|
1636
|
+
* <SidebarMenuButton>
|
|
1637
|
+
* <Folder />
|
|
1638
|
+
* <span>Projects</span>
|
|
1639
|
+
* </SidebarMenuButton>
|
|
1640
|
+
* <SidebarMenuAction>
|
|
1641
|
+
* <MoreHorizontal className="h-4 w-4" />
|
|
1642
|
+
* </SidebarMenuAction>
|
|
1643
|
+
* </SidebarMenuItem>
|
|
1644
|
+
* ```
|
|
1645
|
+
*
|
|
1646
|
+
* @example
|
|
1647
|
+
* ```tsx
|
|
1648
|
+
* // Show only on hover
|
|
1649
|
+
* <SidebarMenuItem>
|
|
1650
|
+
* <SidebarMenuButton>
|
|
1651
|
+
* <File />
|
|
1652
|
+
* <span>Document</span>
|
|
1653
|
+
* </SidebarMenuButton>
|
|
1654
|
+
* <SidebarMenuAction showOnHover>
|
|
1655
|
+
* <Trash2 className="h-4 w-4" />
|
|
1656
|
+
* </SidebarMenuAction>
|
|
1657
|
+
* </SidebarMenuItem>
|
|
1658
|
+
* ```
|
|
1659
|
+
*
|
|
1660
|
+
* @example
|
|
1661
|
+
* ```tsx
|
|
1662
|
+
* // Custom action with asChild
|
|
1663
|
+
* <SidebarMenuItem>
|
|
1664
|
+
* <SidebarMenuButton>
|
|
1665
|
+
* <Star />
|
|
1666
|
+
* <span>Favorites</span>
|
|
1667
|
+
* </SidebarMenuButton>
|
|
1668
|
+
* <SidebarMenuAction asChild>
|
|
1669
|
+
* <DropdownMenu>
|
|
1670
|
+
* <DropdownMenuTrigger>
|
|
1671
|
+
* <MoreVertical className="h-4 w-4" />
|
|
1672
|
+
* </DropdownMenuTrigger>
|
|
1673
|
+
* <DropdownMenuContent>
|
|
1674
|
+
* <DropdownMenuItem>Edit</DropdownMenuItem>
|
|
1675
|
+
* <DropdownMenuItem>Delete</DropdownMenuItem>
|
|
1676
|
+
* </DropdownMenuContent>
|
|
1677
|
+
* </DropdownMenu>
|
|
1678
|
+
* </SidebarMenuAction>
|
|
1679
|
+
* </SidebarMenuItem>
|
|
1680
|
+
* ```
|
|
1681
|
+
*
|
|
1682
|
+
* @accessibility
|
|
1683
|
+
* - Increased touch target area on mobile devices
|
|
1684
|
+
* - Keyboard accessible with proper focus management
|
|
1685
|
+
* - Hidden from screen readers when showOnHover and not focused
|
|
1686
|
+
* - Proper contrast ratios for visibility
|
|
1687
|
+
*
|
|
1688
|
+
* @see {@link SidebarMenuItem} - Parent menu item
|
|
1689
|
+
* @see {@link SidebarMenuButton} - Primary menu button
|
|
1690
|
+
* @since 1.0.0
|
|
1691
|
+
*/
|
|
1692
|
+
function SidebarMenuAction({
|
|
1693
|
+
className,
|
|
1694
|
+
asChild = false,
|
|
1695
|
+
showOnHover = false,
|
|
1696
|
+
...props
|
|
1697
|
+
}: React.ComponentProps<"button"> & {
|
|
1698
|
+
asChild?: boolean;
|
|
1699
|
+
showOnHover?: boolean;
|
|
1700
|
+
}) {
|
|
1701
|
+
const Comp = asChild ? Slot : "button";
|
|
1702
|
+
|
|
1703
|
+
return (
|
|
1704
|
+
<Comp
|
|
1705
|
+
data-slot="sidebar-menu-action"
|
|
1706
|
+
data-sidebar="menu-action"
|
|
1707
|
+
className={cn(
|
|
1708
|
+
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
1709
|
+
// Increases the hit area of the button on mobile.
|
|
1710
|
+
"after:absolute after:-inset-2 md:after:hidden",
|
|
1711
|
+
"peer-data-[size=sm]/menu-button:top-1",
|
|
1712
|
+
"peer-data-[size=default]/menu-button:top-1.5",
|
|
1713
|
+
"peer-data-[size=lg]/menu-button:top-2.5",
|
|
1714
|
+
"group-data-[collapsible=icon]:hidden",
|
|
1715
|
+
showOnHover &&
|
|
1716
|
+
"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
|
|
1717
|
+
className,
|
|
1718
|
+
)}
|
|
1719
|
+
{...props}
|
|
1720
|
+
/>
|
|
1721
|
+
);
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
/**
|
|
1725
|
+
* Badge indicator component for menu items
|
|
1726
|
+
*
|
|
1727
|
+
* Displays count indicators, status badges, or notification markers on menu items.
|
|
1728
|
+
* Positioned on the right side of menu items and automatically coordinates with
|
|
1729
|
+
* menu button states and sidebar collapse behavior.
|
|
1730
|
+
*
|
|
1731
|
+
* @component
|
|
1732
|
+
*
|
|
1733
|
+
* @param {string} [className] - Additional CSS classes
|
|
1734
|
+
* @param {...React.ComponentProps<"div">} props - All div element props
|
|
1735
|
+
*
|
|
1736
|
+
* @example
|
|
1737
|
+
* ```tsx
|
|
1738
|
+
* // Unread count badge
|
|
1739
|
+
* <SidebarMenuItem>
|
|
1740
|
+
* <SidebarMenuButton>
|
|
1741
|
+
* <Inbox />
|
|
1742
|
+
* <span>Messages</span>
|
|
1743
|
+
* </SidebarMenuButton>
|
|
1744
|
+
* <SidebarMenuBadge>12</SidebarMenuBadge>
|
|
1745
|
+
* </SidebarMenuItem>
|
|
1746
|
+
* ```
|
|
1747
|
+
*
|
|
1748
|
+
* @example
|
|
1749
|
+
* ```tsx
|
|
1750
|
+
* // Status indicator
|
|
1751
|
+
* <SidebarMenuItem>
|
|
1752
|
+
* <SidebarMenuButton>
|
|
1753
|
+
* <Activity />
|
|
1754
|
+
* <span>System Status</span>
|
|
1755
|
+
* </SidebarMenuButton>
|
|
1756
|
+
* <SidebarMenuBadge className="bg-green-500">●</SidebarMenuBadge>
|
|
1757
|
+
* </SidebarMenuItem>
|
|
1758
|
+
* ```
|
|
1759
|
+
*
|
|
1760
|
+
* @example
|
|
1761
|
+
* ```tsx
|
|
1762
|
+
* // Dynamic badge with state
|
|
1763
|
+
* <SidebarMenuItem>
|
|
1764
|
+
* <SidebarMenuButton>
|
|
1765
|
+
* <Bell />
|
|
1766
|
+
* <span>Notifications</span>
|
|
1767
|
+
* </SidebarMenuButton>
|
|
1768
|
+
* {notificationCount > 0 && (
|
|
1769
|
+
* <SidebarMenuBadge>{notificationCount}</SidebarMenuBadge>
|
|
1770
|
+
* )}
|
|
1771
|
+
* </SidebarMenuItem>
|
|
1772
|
+
* ```
|
|
1773
|
+
*
|
|
1774
|
+
* @features
|
|
1775
|
+
* - Automatic positioning relative to menu button size
|
|
1776
|
+
* - Hidden when sidebar is collapsed to icon mode
|
|
1777
|
+
* - Tabular numbers for consistent numeric display
|
|
1778
|
+
* - Pointer events disabled to prevent interference
|
|
1779
|
+
*
|
|
1780
|
+
* @accessibility
|
|
1781
|
+
* - Non-interactive (pointer-events-none)
|
|
1782
|
+
* - Proper color contrast for readability
|
|
1783
|
+
* - Screen reader friendly with appropriate text
|
|
1784
|
+
*
|
|
1785
|
+
* @see {@link SidebarMenuItem} - Parent menu item
|
|
1786
|
+
* @see {@link SidebarMenuButton} - Associated menu button
|
|
1787
|
+
* @since 1.0.0
|
|
1788
|
+
*/
|
|
1789
|
+
function SidebarMenuBadge({
|
|
1790
|
+
className,
|
|
1791
|
+
...props
|
|
1792
|
+
}: React.ComponentProps<"div">) {
|
|
1793
|
+
return (
|
|
1794
|
+
<div
|
|
1795
|
+
data-slot="sidebar-menu-badge"
|
|
1796
|
+
data-sidebar="menu-badge"
|
|
1797
|
+
className={cn(
|
|
1798
|
+
"text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none",
|
|
1799
|
+
"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
|
|
1800
|
+
"peer-data-[size=sm]/menu-button:top-1",
|
|
1801
|
+
"peer-data-[size=default]/menu-button:top-1.5",
|
|
1802
|
+
"peer-data-[size=lg]/menu-button:top-2.5",
|
|
1803
|
+
"group-data-[collapsible=icon]:hidden",
|
|
1804
|
+
className,
|
|
1805
|
+
)}
|
|
1806
|
+
{...props}
|
|
1807
|
+
/>
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
/**
|
|
1812
|
+
* Loading skeleton placeholder for menu items
|
|
1813
|
+
*
|
|
1814
|
+
* Displays an animated loading placeholder while menu items are being fetched
|
|
1815
|
+
* or loaded. Features random width variation to simulate realistic content
|
|
1816
|
+
* and optional icon placeholder for more accurate representation.
|
|
1817
|
+
*
|
|
1818
|
+
* @component
|
|
1819
|
+
*
|
|
1820
|
+
* @param {string} [className] - Additional CSS classes
|
|
1821
|
+
* @param {boolean} [showIcon=false] - Whether to show icon placeholder
|
|
1822
|
+
* @param {...React.ComponentProps<"div">} props - All div element props
|
|
1823
|
+
*
|
|
1824
|
+
* @example
|
|
1825
|
+
* ```tsx
|
|
1826
|
+
* // Basic skeleton without icon
|
|
1827
|
+
* <SidebarMenu>
|
|
1828
|
+
* <SidebarMenuItem>
|
|
1829
|
+
* <SidebarMenuSkeleton />
|
|
1830
|
+
* </SidebarMenuItem>
|
|
1831
|
+
* <SidebarMenuItem>
|
|
1832
|
+
* <SidebarMenuSkeleton />
|
|
1833
|
+
* </SidebarMenuItem>
|
|
1834
|
+
* </SidebarMenu>
|
|
1835
|
+
* ```
|
|
1836
|
+
*
|
|
1837
|
+
* @example
|
|
1838
|
+
* ```tsx
|
|
1839
|
+
* // Skeleton with icon placeholder
|
|
1840
|
+
* <SidebarMenu>
|
|
1841
|
+
* {Array.from({ length: 5 }).map((_, index) => (
|
|
1842
|
+
* <SidebarMenuItem key={index}>
|
|
1843
|
+
* <SidebarMenuSkeleton showIcon />
|
|
1844
|
+
* </SidebarMenuItem>
|
|
1845
|
+
* ))}
|
|
1846
|
+
* </SidebarMenu>
|
|
1847
|
+
* ```
|
|
1848
|
+
*
|
|
1849
|
+
* @example
|
|
1850
|
+
* ```tsx
|
|
1851
|
+
* // Loading state with React Suspense
|
|
1852
|
+
* <SidebarGroup>
|
|
1853
|
+
* <SidebarGroupLabel>Projects</SidebarGroupLabel>
|
|
1854
|
+
* <SidebarGroupContent>
|
|
1855
|
+
* <SidebarMenu>
|
|
1856
|
+
* <React.Suspense
|
|
1857
|
+
* fallback={
|
|
1858
|
+
* <>
|
|
1859
|
+
* <SidebarMenuItem><SidebarMenuSkeleton showIcon /></SidebarMenuItem>
|
|
1860
|
+
* <SidebarMenuItem><SidebarMenuSkeleton showIcon /></SidebarMenuItem>
|
|
1861
|
+
* <SidebarMenuItem><SidebarMenuSkeleton showIcon /></SidebarMenuItem>
|
|
1862
|
+
* </>
|
|
1863
|
+
* }
|
|
1864
|
+
* >
|
|
1865
|
+
* <ProjectsList />
|
|
1866
|
+
* </React.Suspense>
|
|
1867
|
+
* </SidebarMenu>
|
|
1868
|
+
* </SidebarGroupContent>
|
|
1869
|
+
* </SidebarGroup>
|
|
1870
|
+
* ```
|
|
1871
|
+
*
|
|
1872
|
+
* @features
|
|
1873
|
+
* - Random width variation (50-90%) for realistic appearance
|
|
1874
|
+
* - Optional icon skeleton placeholder
|
|
1875
|
+
* - Smooth loading animation
|
|
1876
|
+
* - Proper sizing to match actual menu items
|
|
1877
|
+
*
|
|
1878
|
+
* @see {@link SidebarMenuItem} - Container for skeleton
|
|
1879
|
+
* @see {@link SidebarMenu} - Parent menu container
|
|
1880
|
+
* @see {@link Skeleton} - Base skeleton component
|
|
1881
|
+
* @since 1.0.0
|
|
1882
|
+
*/
|
|
1883
|
+
function SidebarMenuSkeleton({
|
|
1884
|
+
className,
|
|
1885
|
+
showIcon = false,
|
|
1886
|
+
...props
|
|
1887
|
+
}: React.ComponentProps<"div"> & {
|
|
1888
|
+
showIcon?: boolean;
|
|
1889
|
+
}) {
|
|
1890
|
+
// Random width between 50 to 90%.
|
|
1891
|
+
const width = `${Math.floor(Math.random() * 40) + 50}%`;
|
|
1892
|
+
|
|
1893
|
+
return (
|
|
1894
|
+
<div
|
|
1895
|
+
data-slot="sidebar-menu-skeleton"
|
|
1896
|
+
data-sidebar="menu-skeleton"
|
|
1897
|
+
className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
|
|
1898
|
+
{...props}
|
|
1899
|
+
>
|
|
1900
|
+
{showIcon && (
|
|
1901
|
+
<Skeleton
|
|
1902
|
+
className="size-4 rounded-md"
|
|
1903
|
+
data-sidebar="menu-skeleton-icon"
|
|
1904
|
+
/>
|
|
1905
|
+
)}
|
|
1906
|
+
<Skeleton
|
|
1907
|
+
className="h-4 max-w-(--skeleton-width) flex-1"
|
|
1908
|
+
data-sidebar="menu-skeleton-text"
|
|
1909
|
+
style={
|
|
1910
|
+
{
|
|
1911
|
+
"--skeleton-width": width,
|
|
1912
|
+
} as React.CSSProperties
|
|
1913
|
+
}
|
|
1914
|
+
/>
|
|
1915
|
+
</div>
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
/**
|
|
1920
|
+
* Submenu container for hierarchical navigation
|
|
1921
|
+
*
|
|
1922
|
+
* A specialized container for nested navigation items that provides visual hierarchy
|
|
1923
|
+
* through indentation and connecting border lines. Perfect for organizing complex
|
|
1924
|
+
* navigation structures with parent-child relationships.
|
|
1925
|
+
*
|
|
1926
|
+
* @component
|
|
1927
|
+
*
|
|
1928
|
+
* @param {string} [className] - Additional CSS classes
|
|
1929
|
+
* @param {...React.ComponentProps<"ul">} props - All ul element props
|
|
1930
|
+
*
|
|
1931
|
+
* @example
|
|
1932
|
+
* ```tsx
|
|
1933
|
+
* // Collapsible submenu with parent item
|
|
1934
|
+
* <SidebarMenuItem>
|
|
1935
|
+
* <Collapsible>
|
|
1936
|
+
* <CollapsibleTrigger asChild>
|
|
1937
|
+
* <SidebarMenuButton>
|
|
1938
|
+
* <Folder />
|
|
1939
|
+
* <span>Projects</span>
|
|
1940
|
+
* <ChevronRight className="ml-auto transition-transform group-data-[state=open]:rotate-90" />
|
|
1941
|
+
* </SidebarMenuButton>
|
|
1942
|
+
* </CollapsibleTrigger>
|
|
1943
|
+
* <CollapsibleContent>
|
|
1944
|
+
* <SidebarMenuSub>
|
|
1945
|
+
* <SidebarMenuSubItem>
|
|
1946
|
+
* <SidebarMenuSubButton>Web Application</SidebarMenuSubButton>
|
|
1947
|
+
* </SidebarMenuSubItem>
|
|
1948
|
+
* <SidebarMenuSubItem>
|
|
1949
|
+
* <SidebarMenuSubButton>Mobile App</SidebarMenuSubButton>
|
|
1950
|
+
* </SidebarMenuSubItem>
|
|
1951
|
+
* <SidebarMenuSubItem>
|
|
1952
|
+
* <SidebarMenuSubButton>Documentation</SidebarMenuSubButton>
|
|
1953
|
+
* </SidebarMenuSubItem>
|
|
1954
|
+
* </SidebarMenuSub>
|
|
1955
|
+
* </CollapsibleContent>
|
|
1956
|
+
* </Collapsible>
|
|
1957
|
+
* </SidebarMenuItem>
|
|
1958
|
+
* ```
|
|
1959
|
+
*
|
|
1960
|
+
* @example
|
|
1961
|
+
* ```tsx
|
|
1962
|
+
* // Always expanded submenu
|
|
1963
|
+
* <SidebarMenuItem>
|
|
1964
|
+
* <SidebarMenuButton>
|
|
1965
|
+
* <Settings />
|
|
1966
|
+
* <span>Settings</span>
|
|
1967
|
+
* </SidebarMenuButton>
|
|
1968
|
+
* <SidebarMenuSub>
|
|
1969
|
+
* <SidebarMenuSubItem>
|
|
1970
|
+
* <SidebarMenuSubButton>General</SidebarMenuSubButton>
|
|
1971
|
+
* </SidebarMenuSubItem>
|
|
1972
|
+
* <SidebarMenuSubItem>
|
|
1973
|
+
* <SidebarMenuSubButton>Privacy</SidebarMenuSubButton>
|
|
1974
|
+
* </SidebarMenuSubItem>
|
|
1975
|
+
* <SidebarMenuSubItem>
|
|
1976
|
+
* <SidebarMenuSubButton>Security</SidebarMenuSubButton>
|
|
1977
|
+
* </SidebarMenuSubItem>
|
|
1978
|
+
* </SidebarMenuSub>
|
|
1979
|
+
* </SidebarMenuItem>
|
|
1980
|
+
* ```
|
|
1981
|
+
*
|
|
1982
|
+
* @features
|
|
1983
|
+
* - Visual hierarchy with left border and indentation
|
|
1984
|
+
* - Automatic hiding when sidebar is collapsed
|
|
1985
|
+
* - Proper spacing and alignment with parent items
|
|
1986
|
+
* - Seamless integration with collapsible components
|
|
1987
|
+
*
|
|
1988
|
+
* @accessibility
|
|
1989
|
+
* - Semantic ul/li structure for screen readers
|
|
1990
|
+
* - Proper nesting hierarchy for navigation
|
|
1991
|
+
* - Keyboard navigation support
|
|
1992
|
+
*
|
|
1993
|
+
* @see {@link SidebarMenuSubItem} - Individual submenu item
|
|
1994
|
+
* @see {@link SidebarMenuSubButton} - Submenu button component
|
|
1995
|
+
* @see {@link SidebarMenuItem} - Parent menu item
|
|
1996
|
+
* @since 1.0.0
|
|
1997
|
+
*/
|
|
1998
|
+
function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
|
|
1999
|
+
return (
|
|
2000
|
+
<ul
|
|
2001
|
+
data-slot="sidebar-menu-sub"
|
|
2002
|
+
data-sidebar="menu-sub"
|
|
2003
|
+
className={cn(
|
|
2004
|
+
"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5",
|
|
2005
|
+
"group-data-[collapsible=icon]:hidden",
|
|
2006
|
+
className,
|
|
2007
|
+
)}
|
|
2008
|
+
{...props}
|
|
2009
|
+
/>
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
/**
|
|
2014
|
+
* Individual item container within submenu navigation
|
|
2015
|
+
*
|
|
2016
|
+
* A list item container for single nested navigation items within a submenu,
|
|
2017
|
+
* providing the structural foundation for hierarchical navigation elements.
|
|
2018
|
+
*
|
|
2019
|
+
* @component
|
|
2020
|
+
*
|
|
2021
|
+
* @param {string} [className] - Additional CSS classes
|
|
2022
|
+
* @param {...React.ComponentProps<"li">} props - All li element props
|
|
2023
|
+
*
|
|
2024
|
+
* @example
|
|
2025
|
+
* ```tsx
|
|
2026
|
+
* // Basic submenu item
|
|
2027
|
+
* <SidebarMenuSub>
|
|
2028
|
+
* <SidebarMenuSubItem>
|
|
2029
|
+
* <SidebarMenuSubButton asChild>
|
|
2030
|
+
* <Link href="/projects/web-app">
|
|
2031
|
+
* <span>Web Application</span>
|
|
2032
|
+
* </Link>
|
|
2033
|
+
* </SidebarMenuSubButton>
|
|
2034
|
+
* </SidebarMenuSubItem>
|
|
2035
|
+
* </SidebarMenuSub>
|
|
2036
|
+
* ```
|
|
2037
|
+
*
|
|
2038
|
+
* @example
|
|
2039
|
+
* ```tsx
|
|
2040
|
+
* // Submenu item with active state
|
|
2041
|
+
* <SidebarMenuSub>
|
|
2042
|
+
* <SidebarMenuSubItem>
|
|
2043
|
+
* <SidebarMenuSubButton
|
|
2044
|
+
* isActive={pathname === '/settings/general'}
|
|
2045
|
+
* asChild
|
|
2046
|
+
* >
|
|
2047
|
+
* <Link href="/settings/general">
|
|
2048
|
+
* <span>General Settings</span>
|
|
2049
|
+
* </Link>
|
|
2050
|
+
* </SidebarMenuSubButton>
|
|
2051
|
+
* </SidebarMenuSubItem>
|
|
2052
|
+
* </SidebarMenuSub>
|
|
2053
|
+
* ```
|
|
2054
|
+
*
|
|
2055
|
+
* @see {@link SidebarMenuSub} - Parent submenu container
|
|
2056
|
+
* @see {@link SidebarMenuSubButton} - Interactive button component
|
|
2057
|
+
* @since 1.0.0
|
|
2058
|
+
*/
|
|
2059
|
+
function SidebarMenuSubItem({
|
|
2060
|
+
className,
|
|
2061
|
+
...props
|
|
2062
|
+
}: React.ComponentProps<"li">) {
|
|
2063
|
+
return (
|
|
2064
|
+
<li
|
|
2065
|
+
data-slot="sidebar-menu-sub-item"
|
|
2066
|
+
data-sidebar="menu-sub-item"
|
|
2067
|
+
className={cn("group/menu-sub-item relative", className)}
|
|
2068
|
+
{...props}
|
|
2069
|
+
/>
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
/**
|
|
2074
|
+
* Interactive button component for submenu navigation
|
|
2075
|
+
*
|
|
2076
|
+
* The primary interactive element for nested navigation items within submenus,
|
|
2077
|
+
* featuring support for active states, different sizes, and composition patterns.
|
|
2078
|
+
* Designed specifically for hierarchical navigation with appropriate styling and spacing.
|
|
2079
|
+
*
|
|
2080
|
+
* @component
|
|
2081
|
+
*
|
|
2082
|
+
* @param {boolean} [asChild=false] - Render as child element using Slot composition
|
|
2083
|
+
* @param {"sm" | "md"} [size="md"] - Button size variant:
|
|
2084
|
+
* - `sm`: Small size with text-xs
|
|
2085
|
+
* - `md`: Medium size with text-sm
|
|
2086
|
+
* @param {boolean} [isActive=false] - Whether this submenu item is currently active
|
|
2087
|
+
* @param {string} [className] - Additional CSS classes
|
|
2088
|
+
* @param {...React.ComponentProps<"a">} props - All anchor element props
|
|
2089
|
+
*
|
|
2090
|
+
* @example
|
|
2091
|
+
* ```tsx
|
|
2092
|
+
* // Basic submenu button
|
|
2093
|
+
* <SidebarMenuSubItem>
|
|
2094
|
+
* <SidebarMenuSubButton>
|
|
2095
|
+
* <span>Documentation</span>
|
|
2096
|
+
* </SidebarMenuSubButton>
|
|
2097
|
+
* </SidebarMenuSubItem>
|
|
2098
|
+
* ```
|
|
2099
|
+
*
|
|
2100
|
+
* @example
|
|
2101
|
+
* ```tsx
|
|
2102
|
+
* // Link composition with active state
|
|
2103
|
+
* <SidebarMenuSubItem>
|
|
2104
|
+
* <SidebarMenuSubButton
|
|
2105
|
+
* asChild
|
|
2106
|
+
* isActive={pathname === '/projects/mobile'}
|
|
2107
|
+
* >
|
|
2108
|
+
* <Link href="/projects/mobile">
|
|
2109
|
+
* <span>Mobile Application</span>
|
|
2110
|
+
* </Link>
|
|
2111
|
+
* </SidebarMenuSubButton>
|
|
2112
|
+
* </SidebarMenuSubItem>
|
|
2113
|
+
* ```
|
|
2114
|
+
*
|
|
2115
|
+
* @example
|
|
2116
|
+
* ```tsx
|
|
2117
|
+
* // Small size variant
|
|
2118
|
+
* <SidebarMenuSubItem>
|
|
2119
|
+
* <SidebarMenuSubButton size="sm">
|
|
2120
|
+
* <span>API Reference</span>
|
|
2121
|
+
* </SidebarMenuSubButton>
|
|
2122
|
+
* </SidebarMenuSubItem>
|
|
2123
|
+
* ```
|
|
2124
|
+
*
|
|
2125
|
+
* @example
|
|
2126
|
+
* ```tsx
|
|
2127
|
+
* // With icon and custom styling
|
|
2128
|
+
* <SidebarMenuSubItem>
|
|
2129
|
+
* <SidebarMenuSubButton className="gap-3">
|
|
2130
|
+
* <File className="h-3 w-3" />
|
|
2131
|
+
* <span>README.md</span>
|
|
2132
|
+
* </SidebarMenuSubButton>
|
|
2133
|
+
* </SidebarMenuSubItem>
|
|
2134
|
+
* ```
|
|
2135
|
+
*
|
|
2136
|
+
* @accessibility
|
|
2137
|
+
* - Full keyboard navigation support
|
|
2138
|
+
* - Focus visible indicators
|
|
2139
|
+
* - Active state indication for screen readers
|
|
2140
|
+
* - Proper contrast ratios for all states
|
|
2141
|
+
* - Support for assistive technology navigation
|
|
2142
|
+
*
|
|
2143
|
+
* @features
|
|
2144
|
+
* - Automatic hiding when parent sidebar is collapsed
|
|
2145
|
+
* - Smooth hover and focus transitions
|
|
2146
|
+
* - Consistent spacing with parent menu items
|
|
2147
|
+
* - Overflow text truncation for long labels
|
|
2148
|
+
*
|
|
2149
|
+
* @see {@link SidebarMenuSubItem} - Parent submenu item container
|
|
2150
|
+
* @see {@link SidebarMenuSub} - Submenu container
|
|
2151
|
+
* @see {@link SidebarMenuButton} - Parent menu button
|
|
2152
|
+
* @since 1.0.0
|
|
2153
|
+
*/
|
|
2154
|
+
function SidebarMenuSubButton({
|
|
2155
|
+
asChild = false,
|
|
2156
|
+
size = "md",
|
|
2157
|
+
isActive = false,
|
|
2158
|
+
className,
|
|
2159
|
+
...props
|
|
2160
|
+
}: React.ComponentProps<"a"> & {
|
|
2161
|
+
asChild?: boolean;
|
|
2162
|
+
size?: "sm" | "md";
|
|
2163
|
+
isActive?: boolean;
|
|
2164
|
+
}) {
|
|
2165
|
+
const Comp = asChild ? Slot : "a";
|
|
2166
|
+
|
|
2167
|
+
return (
|
|
2168
|
+
<Comp
|
|
2169
|
+
data-slot="sidebar-menu-sub-button"
|
|
2170
|
+
data-sidebar="menu-sub-button"
|
|
2171
|
+
data-size={size}
|
|
2172
|
+
data-active={isActive}
|
|
2173
|
+
className={cn(
|
|
2174
|
+
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
|
|
2175
|
+
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
|
|
2176
|
+
size === "sm" && "text-xs",
|
|
2177
|
+
size === "md" && "text-sm",
|
|
2178
|
+
"group-data-[collapsible=icon]:hidden",
|
|
2179
|
+
className,
|
|
2180
|
+
)}
|
|
2181
|
+
{...props}
|
|
2182
|
+
/>
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
export {
|
|
2187
|
+
Sidebar,
|
|
2188
|
+
SidebarContent,
|
|
2189
|
+
SidebarFooter,
|
|
2190
|
+
SidebarGroup,
|
|
2191
|
+
SidebarGroupAction,
|
|
2192
|
+
SidebarGroupContent,
|
|
2193
|
+
SidebarGroupLabel,
|
|
2194
|
+
SidebarHeader,
|
|
2195
|
+
SidebarInput,
|
|
2196
|
+
SidebarInset,
|
|
2197
|
+
SidebarMenu,
|
|
2198
|
+
SidebarMenuAction,
|
|
2199
|
+
SidebarMenuBadge,
|
|
2200
|
+
SidebarMenuButton,
|
|
2201
|
+
SidebarMenuItem,
|
|
2202
|
+
SidebarMenuSkeleton,
|
|
2203
|
+
SidebarMenuSub,
|
|
2204
|
+
SidebarMenuSubButton,
|
|
2205
|
+
SidebarMenuSubItem,
|
|
2206
|
+
SidebarProvider,
|
|
2207
|
+
SidebarRail,
|
|
2208
|
+
SidebarSeparator,
|
|
2209
|
+
SidebarTrigger,
|
|
2210
|
+
useSidebar,
|
|
2211
|
+
};
|