@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,91 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
const MOBILE_BREAKPOINT = 768
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hook for detecting mobile viewport width based on Tailwind CSS breakpoints
|
|
7
|
+
*
|
|
8
|
+
* A React hook that monitors viewport width and returns true when the width
|
|
9
|
+
* is less than 768px (Tailwind's `md` breakpoint). This hook provides a
|
|
10
|
+
* consistent way to conditionally render mobile and desktop layouts, following
|
|
11
|
+
* the same responsive patterns used throughout the shadcn/ui ecosystem.
|
|
12
|
+
*
|
|
13
|
+
* The hook uses `window.matchMedia` for efficient viewport monitoring and
|
|
14
|
+
* properly handles server-side rendering by returning `false` initially,
|
|
15
|
+
* then updating on the client side.
|
|
16
|
+
*
|
|
17
|
+
* @returns `true` if viewport width is less than 768px, `false` otherwise
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* Basic responsive layout switching
|
|
21
|
+
* ```tsx
|
|
22
|
+
* function ResponsiveComponent() {
|
|
23
|
+
* const isMobile = useIsMobile();
|
|
24
|
+
*
|
|
25
|
+
* return (
|
|
26
|
+
* <div>
|
|
27
|
+
* {isMobile ? (
|
|
28
|
+
* <MobileLayout />
|
|
29
|
+
* ) : (
|
|
30
|
+
* <DesktopLayout />
|
|
31
|
+
* )}
|
|
32
|
+
* </div>
|
|
33
|
+
* );
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* Conditional navigation rendering
|
|
39
|
+
* ```tsx
|
|
40
|
+
* function Navigation() {
|
|
41
|
+
* const isMobile = useIsMobile();
|
|
42
|
+
*
|
|
43
|
+
* if (isMobile) {
|
|
44
|
+
* return <Sheet><MobileNavContent /></Sheet>;
|
|
45
|
+
* }
|
|
46
|
+
*
|
|
47
|
+
* return <NavigationMenu><DesktopNavContent /></NavigationMenu>;
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* Responsive component behavior within forms
|
|
53
|
+
* ```tsx
|
|
54
|
+
* function SearchDialog() {
|
|
55
|
+
* const isMobile = useIsMobile();
|
|
56
|
+
*
|
|
57
|
+
* if (isMobile) {
|
|
58
|
+
* return <Drawer><SearchForm /></Drawer>;
|
|
59
|
+
* }
|
|
60
|
+
*
|
|
61
|
+
* return <Dialog><SearchForm /></Dialog>;
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @remarks
|
|
66
|
+
* - Uses the standard 768px breakpoint matching Tailwind's `md` breakpoint
|
|
67
|
+
* - Similar pattern to `isMobile` property in shadcn/ui's `useSidebar` hook
|
|
68
|
+
* - Handles viewport resize events automatically
|
|
69
|
+
* - SSR-safe implementation prevents hydration mismatches
|
|
70
|
+
* - Respects user's browser zoom settings
|
|
71
|
+
*
|
|
72
|
+
* @see {@link https://tailwindcss.com/docs/responsive-design} Tailwind CSS responsive design
|
|
73
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia} MDN matchMedia documentation
|
|
74
|
+
*
|
|
75
|
+
* @since 1.0.0
|
|
76
|
+
*/
|
|
77
|
+
export function useIsMobile() {
|
|
78
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
|
79
|
+
|
|
80
|
+
React.useEffect(() => {
|
|
81
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
|
82
|
+
const onChange = () => {
|
|
83
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
84
|
+
}
|
|
85
|
+
mql.addEventListener("change", onChange)
|
|
86
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
87
|
+
return () => mql.removeEventListener("change", onChange)
|
|
88
|
+
}, [])
|
|
89
|
+
|
|
90
|
+
return !!isMobile
|
|
91
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Theme preference options for the theme system
|
|
7
|
+
*
|
|
8
|
+
* Controls how the application determines the active theme:
|
|
9
|
+
* - `"system"` - Automatically follows the user's OS preference
|
|
10
|
+
* - `"light"` - Forces light theme regardless of system preference
|
|
11
|
+
* - `"dark"` - Forces dark theme regardless of system preference
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* const { preference, setPreference } = useTheme();
|
|
16
|
+
*
|
|
17
|
+
* // Switch to dark mode
|
|
18
|
+
* setPreference("dark");
|
|
19
|
+
*
|
|
20
|
+
* // Follow system preference
|
|
21
|
+
* setPreference("system");
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @since 1.0.0
|
|
25
|
+
*/
|
|
26
|
+
export type ThemePreference = "system" | "light" | "dark"
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Internal theme state structure stored in cookies
|
|
30
|
+
*
|
|
31
|
+
* Maintains both the user's preference and the actual mode value
|
|
32
|
+
* to handle system theme changes efficiently.
|
|
33
|
+
*
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
type ThemeState = {
|
|
37
|
+
/** User's selected theme preference */
|
|
38
|
+
preference: ThemePreference
|
|
39
|
+
/** Actual mode applied to the DOM */
|
|
40
|
+
mode: "light" | "dark"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Cookie configuration for theme persistence
|
|
45
|
+
*
|
|
46
|
+
* Uses secure defaults for cross-site compatibility and long-term storage.
|
|
47
|
+
*
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
const COOKIE_NAME = "theme"
|
|
51
|
+
const COOKIE_OPTIONS = {
|
|
52
|
+
path: "/",
|
|
53
|
+
maxAge: 60 * 60 * 24 * 365, // 1 year
|
|
54
|
+
sameSite: "lax" as const,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Retrieves the stored theme preference from browser cookies
|
|
59
|
+
*
|
|
60
|
+
* Safely parses the cookie value and handles malformed data gracefully.
|
|
61
|
+
* Returns null if no valid theme data is found or if running server-side.
|
|
62
|
+
*
|
|
63
|
+
* @returns The stored theme state or null if unavailable
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
function getThemeFromCookie(): ThemeState | null {
|
|
67
|
+
if (typeof document === "undefined") return null
|
|
68
|
+
const match = document.cookie.match(new RegExp(`(?:^|; )${COOKIE_NAME}=([^;]*)`))
|
|
69
|
+
if (!match) return null
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const decoded = decodeURIComponent(match[1] || "")
|
|
73
|
+
return JSON.parse(decoded)
|
|
74
|
+
} catch {
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Persists the current theme state to browser cookies
|
|
81
|
+
*
|
|
82
|
+
* Stores the complete theme state as a JSON string with appropriate
|
|
83
|
+
* cookie settings for security and longevity. Safe for server-side rendering.
|
|
84
|
+
*
|
|
85
|
+
* @param state - The theme state to persist
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
function setThemeCookie(state: ThemeState) {
|
|
89
|
+
if (typeof document === "undefined") return
|
|
90
|
+
|
|
91
|
+
const value = JSON.stringify(state)
|
|
92
|
+
let cookieString = `${COOKIE_NAME}=${encodeURIComponent(value)}`
|
|
93
|
+
|
|
94
|
+
cookieString += `; path=${COOKIE_OPTIONS.path}`
|
|
95
|
+
cookieString += `; max-age=${COOKIE_OPTIONS.maxAge}`
|
|
96
|
+
cookieString += `; samesite=${COOKIE_OPTIONS.sameSite}`
|
|
97
|
+
|
|
98
|
+
document.cookie = cookieString
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Detects the user's system theme preference
|
|
103
|
+
*
|
|
104
|
+
* Uses the CSS media query `prefers-color-scheme` to determine if the user
|
|
105
|
+
* has configured their OS to prefer dark or light themes. Falls back to
|
|
106
|
+
* light theme for server-side rendering or unsupported browsers.
|
|
107
|
+
*
|
|
108
|
+
* @returns The system's preferred theme
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
function getSystemTheme(): "light" | "dark" {
|
|
112
|
+
if (typeof window === "undefined") return "light"
|
|
113
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Applies the specified theme to the document
|
|
118
|
+
*
|
|
119
|
+
* Updates the document's class list and color-scheme CSS property to reflect
|
|
120
|
+
* the chosen theme. This triggers CSS-based theme switching throughout the
|
|
121
|
+
* application and ensures native elements respect the theme choice.
|
|
122
|
+
*
|
|
123
|
+
* @param theme - The theme to apply ("light" or "dark")
|
|
124
|
+
* @internal
|
|
125
|
+
*/
|
|
126
|
+
function applyTheme(theme: "light" | "dark") {
|
|
127
|
+
if (typeof document === "undefined") return
|
|
128
|
+
|
|
129
|
+
// For light theme, remove dark class (light is default)
|
|
130
|
+
// For dark theme, add dark class
|
|
131
|
+
if (theme === "dark") {
|
|
132
|
+
document.documentElement.classList.add("dark")
|
|
133
|
+
} else {
|
|
134
|
+
document.documentElement.classList.remove("dark")
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Set color-scheme for native elements
|
|
138
|
+
document.documentElement.style.colorScheme = theme
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Hook for managing application theme with automatic system preference detection
|
|
143
|
+
*
|
|
144
|
+
* A complete theme management solution that handles persistence, system preference
|
|
145
|
+
* detection, and DOM manipulation without requiring any context providers. Designed
|
|
146
|
+
* for applications that need simple light/dark mode switching with optional system
|
|
147
|
+
* preference following.
|
|
148
|
+
*
|
|
149
|
+
* This hook works in conjunction with the {@link Theme} component to provide a complete
|
|
150
|
+
* theming solution with FOUC prevention and perfect synchronization across multiple
|
|
151
|
+
* instances.
|
|
152
|
+
*
|
|
153
|
+
* Features:
|
|
154
|
+
* - **Perfect synchronization**: Multiple hook instances stay in sync via custom events
|
|
155
|
+
* - **Automatic persistence**: Theme preference saved to cookies across sessions
|
|
156
|
+
* - **System preference detection**: Automatically follows OS dark/light mode when set to "system"
|
|
157
|
+
* - **Live system updates**: Responds to system theme changes in real-time
|
|
158
|
+
* - **SSR compatible**: Works safely during server-side rendering
|
|
159
|
+
* - **Type safe**: Full TypeScript support with proper type definitions
|
|
160
|
+
* - **No provider required**: Self-contained hook that works out of the box
|
|
161
|
+
*
|
|
162
|
+
* @example Basic theme switching
|
|
163
|
+
* ```tsx
|
|
164
|
+
* function ThemeToggle() {
|
|
165
|
+
* const { preference, mode, setPreference } = useTheme();
|
|
166
|
+
*
|
|
167
|
+
* const toggleTheme = () => {
|
|
168
|
+
* setPreference(mode === "dark" ? "light" : "dark");
|
|
169
|
+
* };
|
|
170
|
+
*
|
|
171
|
+
* return (
|
|
172
|
+
* <button onClick={toggleTheme}>
|
|
173
|
+
* Switch to {mode === "dark" ? "light" : "dark"} mode
|
|
174
|
+
* </button>
|
|
175
|
+
* );
|
|
176
|
+
* }
|
|
177
|
+
* ```
|
|
178
|
+
*
|
|
179
|
+
* @example Theme selector dropdown
|
|
180
|
+
* ```tsx
|
|
181
|
+
* function ThemeSelector() {
|
|
182
|
+
* const { preference, setPreference } = useTheme();
|
|
183
|
+
*
|
|
184
|
+
* return (
|
|
185
|
+
* <select value={preference} onChange={(e) => setPreference(e.target.value as ThemePreference)}>
|
|
186
|
+
* <option value="system">Follow system</option>
|
|
187
|
+
* <option value="light">Light mode</option>
|
|
188
|
+
* <option value="dark">Dark mode</option>
|
|
189
|
+
* </select>
|
|
190
|
+
* );
|
|
191
|
+
* }
|
|
192
|
+
* ```
|
|
193
|
+
*
|
|
194
|
+
* @example Multiple synchronized toggles
|
|
195
|
+
* ```tsx
|
|
196
|
+
* function App() {
|
|
197
|
+
* return (
|
|
198
|
+
* <>
|
|
199
|
+
* <Header>
|
|
200
|
+
* <ThemeToggle />
|
|
201
|
+
* </Header>
|
|
202
|
+
* <Sidebar>
|
|
203
|
+
* <ThemeToggle />
|
|
204
|
+
* </Sidebar>
|
|
205
|
+
* </>
|
|
206
|
+
* );
|
|
207
|
+
* }
|
|
208
|
+
* ```
|
|
209
|
+
*
|
|
210
|
+
* @example Conditional rendering based on theme
|
|
211
|
+
* ```tsx
|
|
212
|
+
* function ThemedIcon() {
|
|
213
|
+
* const { mode } = useTheme();
|
|
214
|
+
*
|
|
215
|
+
* return mode === "dark" ? <MoonIcon /> : <SunIcon />;
|
|
216
|
+
* }
|
|
217
|
+
* ```
|
|
218
|
+
*
|
|
219
|
+
* @returns Object containing current theme state and setter function
|
|
220
|
+
* @returns returns.preference - The user's selected theme preference
|
|
221
|
+
* @returns returns.mode - The actual theme currently applied (always "light" or "dark")
|
|
222
|
+
* @returns returns.setPreference - Function to change the theme preference
|
|
223
|
+
*
|
|
224
|
+
* @remarks
|
|
225
|
+
* **Synchronization Architecture:**
|
|
226
|
+
* - All theme changes are broadcasted via custom 'theme-change' events
|
|
227
|
+
* - Single setState per change across ALL hook instances (no duplicates)
|
|
228
|
+
* - Event-driven updates ensure perfect synchronization
|
|
229
|
+
* - Cookie serves as the single source of truth for persistence
|
|
230
|
+
*
|
|
231
|
+
* **Implementation Details:**
|
|
232
|
+
* - Theme state is automatically persisted to cookies with 1-year expiration
|
|
233
|
+
* - System preference changes are detected via `matchMedia` API
|
|
234
|
+
* - DOM updates are applied immediately when theme changes
|
|
235
|
+
* - Hook is safe to use during SSR (falls back to light theme)
|
|
236
|
+
* - Custom events handle cross-instance synchronization
|
|
237
|
+
*
|
|
238
|
+
* **Performance:**
|
|
239
|
+
* - Single setState per theme change (no duplicate updates)
|
|
240
|
+
* - Efficient event-driven architecture
|
|
241
|
+
* - No context overhead: direct hook usage without providers
|
|
242
|
+
* - Automatic cleanup: removes event listeners on component unmount
|
|
243
|
+
*
|
|
244
|
+
* **Browser Support:**
|
|
245
|
+
* - Modern browsers with `matchMedia` support for system detection
|
|
246
|
+
* - CustomEvent support for synchronization (IE11+ with polyfill)
|
|
247
|
+
* - Graceful degradation for older browsers (defaults to light theme)
|
|
248
|
+
* - Cookie support required for persistence across sessions
|
|
249
|
+
*
|
|
250
|
+
* @see {@link ThemePreference} - Available theme preference options
|
|
251
|
+
* @see {@link Theme} - Component for FOUC prevention
|
|
252
|
+
* @see {@link ThemeToggle} - UI component for theme switching
|
|
253
|
+
* @since 1.0.0
|
|
254
|
+
*/
|
|
255
|
+
export function useTheme() {
|
|
256
|
+
// Initialize state from cookie or default to system
|
|
257
|
+
const [state, setState] = useState<ThemeState>(() => {
|
|
258
|
+
const saved = getThemeFromCookie()
|
|
259
|
+
if (saved) return saved
|
|
260
|
+
|
|
261
|
+
const mode = getSystemTheme()
|
|
262
|
+
return { preference: "system", mode }
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// Set theme preference - dispatch event, let event handler update state
|
|
266
|
+
const setPreference = (newPreference: ThemePreference) => {
|
|
267
|
+
const mode = newPreference === "system" ? getSystemTheme() : newPreference
|
|
268
|
+
const newState = { preference: newPreference, mode }
|
|
269
|
+
|
|
270
|
+
// Update cookie and DOM immediately
|
|
271
|
+
setThemeCookie(newState)
|
|
272
|
+
applyTheme(mode)
|
|
273
|
+
|
|
274
|
+
// Broadcast change - ALL instances (including this one) will update state from this event
|
|
275
|
+
window.dispatchEvent(new CustomEvent('theme-change', {
|
|
276
|
+
detail: newState
|
|
277
|
+
}))
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Single event handler for ALL state updates (including from this instance)
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
const handleThemeChange = (event: CustomEvent) => {
|
|
283
|
+
setState(event.detail) // Single setState for all instances
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
window.addEventListener('theme-change', handleThemeChange as EventListener)
|
|
287
|
+
return () => window.removeEventListener('theme-change', handleThemeChange as EventListener)
|
|
288
|
+
}, [])
|
|
289
|
+
|
|
290
|
+
// Listen for system theme changes when in system mode
|
|
291
|
+
useEffect(() => {
|
|
292
|
+
if (state.preference !== "system") return
|
|
293
|
+
|
|
294
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
|
|
295
|
+
const handleChange = () => {
|
|
296
|
+
const mode = getSystemTheme()
|
|
297
|
+
const newState = { preference: state.preference, mode }
|
|
298
|
+
|
|
299
|
+
// Same pattern: update cookie/DOM, then broadcast
|
|
300
|
+
setThemeCookie(newState)
|
|
301
|
+
applyTheme(mode)
|
|
302
|
+
window.dispatchEvent(new CustomEvent('theme-change', { detail: newState }))
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
mediaQuery.addEventListener("change", handleChange)
|
|
306
|
+
return () => mediaQuery.removeEventListener("change", handleChange)
|
|
307
|
+
}, [state.preference])
|
|
308
|
+
|
|
309
|
+
// Apply theme on mount (only for initial hydration)
|
|
310
|
+
useEffect(() => {
|
|
311
|
+
applyTheme(state.mode)
|
|
312
|
+
}, [state.mode])
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
preference: state.preference,
|
|
316
|
+
mode: state.mode,
|
|
317
|
+
setPreference,
|
|
318
|
+
}
|
|
319
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Form & Input Components
|
|
2
|
+
export * from "./components/ui/button";
|
|
3
|
+
export * from "./components/ui/text-field";
|
|
4
|
+
export * from "./components/ui/select";
|
|
5
|
+
export * from "./components/ui/combobox";
|
|
6
|
+
export * from "./components/ui/input";
|
|
7
|
+
export * from "./components/ui/textarea";
|
|
8
|
+
export * from "./components/ui/checkbox";
|
|
9
|
+
export * from "./components/ui/switch";
|
|
10
|
+
export * from "./components/ui/radio-group";
|
|
11
|
+
export * from "./components/ui/date-picker";
|
|
12
|
+
export * from "./components/ui/calendar";
|
|
13
|
+
export * from "./components/ui/label";
|
|
14
|
+
|
|
15
|
+
// Layout & Structure
|
|
16
|
+
export * from "./components/ui/container";
|
|
17
|
+
export * from "./components/ui/stack";
|
|
18
|
+
export * from "./components/ui/card";
|
|
19
|
+
export * from "./components/ui/separator";
|
|
20
|
+
export * from "./components/ui/aspect-ratio";
|
|
21
|
+
export * from "./components/ui/accordion";
|
|
22
|
+
export * from "./components/ui/collapsible";
|
|
23
|
+
export * from "./components/ui/resizable";
|
|
24
|
+
export * from "./components/ui/scroll-area";
|
|
25
|
+
|
|
26
|
+
// Navigation & Menus
|
|
27
|
+
export * from "./components/ui/navigation-menu";
|
|
28
|
+
export * from "./components/ui/tabs";
|
|
29
|
+
export * from "./components/ui/breadcrumb";
|
|
30
|
+
export * from "./components/ui/pagination";
|
|
31
|
+
export * from "./components/ui/sidebar";
|
|
32
|
+
export * from "./components/ui/dropdown-menu";
|
|
33
|
+
export * from "./components/ui/context-menu";
|
|
34
|
+
export * from "./components/ui/menubar";
|
|
35
|
+
|
|
36
|
+
// Overlays & Dialogs
|
|
37
|
+
export * from "./components/ui/dialog";
|
|
38
|
+
export * from "./components/ui/alert-dialog";
|
|
39
|
+
export * from "./components/ui/popover";
|
|
40
|
+
export * from "./components/ui/tooltip";
|
|
41
|
+
export * from "./components/ui/sheet";
|
|
42
|
+
export * from "./components/ui/hover-card";
|
|
43
|
+
export * from "./components/ui/drawer";
|
|
44
|
+
|
|
45
|
+
// Typography
|
|
46
|
+
export * from "./components/ui/typography";
|
|
47
|
+
|
|
48
|
+
// Data Display
|
|
49
|
+
export * from "./components/ui/table";
|
|
50
|
+
export * from "./components/ui/badge";
|
|
51
|
+
export * from "./components/ui/avatar";
|
|
52
|
+
export * from "./components/ui/alert";
|
|
53
|
+
export * from "./components/ui/progress";
|
|
54
|
+
export * from "./components/ui/skeleton";
|
|
55
|
+
export * from "./components/ui/empty-state";
|
|
56
|
+
export * from "./components/ui/sonner";
|
|
57
|
+
|
|
58
|
+
// Interactive Controls
|
|
59
|
+
export * from "./components/ui/toggle";
|
|
60
|
+
export * from "./components/ui/toggle-group";
|
|
61
|
+
export * from "./components/ui/slider";
|
|
62
|
+
export * from "./components/ui/command";
|
|
63
|
+
|
|
64
|
+
// Charts & Visualization
|
|
65
|
+
export * from "./components/ui/chart";
|
|
66
|
+
export * from "./components/ui/carousel";
|
|
67
|
+
|
|
68
|
+
// Theme & Utilities
|
|
69
|
+
export * from "./components/ui/theme";
|
|
70
|
+
export * from "./components/ui/theme-toggle";
|
|
71
|
+
|
|
72
|
+
// Utilities
|
|
73
|
+
export * from "./lib/utils";
|
|
74
|
+
|
|
75
|
+
// Hooks
|
|
76
|
+
export * from "./hooks/use-mobile";
|
|
77
|
+
export * from "./hooks/use-theme";
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from "clsx";
|
|
2
|
+
import { extendTailwindMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Custom tailwind-merge instance with support for custom classes
|
|
6
|
+
*/
|
|
7
|
+
const twMerge = extendTailwindMerge({
|
|
8
|
+
extend: {
|
|
9
|
+
classGroups: {
|
|
10
|
+
'font-size': ['text-microcopy'],
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* cn - Combines class names with Tailwind CSS conflict resolution
|
|
17
|
+
*
|
|
18
|
+
* This utility function combines multiple class names into a single string,
|
|
19
|
+
* automatically resolving Tailwind CSS class conflicts. It uses clsx for
|
|
20
|
+
* conditional class handling and tailwind-merge to ensure later classes
|
|
21
|
+
* override earlier ones when they conflict.
|
|
22
|
+
*
|
|
23
|
+
* @param inputs - Class names to combine. Can be strings, objects, arrays,
|
|
24
|
+
* undefined, null, or boolean values
|
|
25
|
+
* @returns A single string of combined and merged class names
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* // Basic usage
|
|
30
|
+
* cn("px-2 py-1", "bg-blue-500", "text-white")
|
|
31
|
+
* // Returns: "px-2 py-1 bg-blue-500 text-white"
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* // With conditional classes
|
|
37
|
+
* cn(
|
|
38
|
+
* "base-class",
|
|
39
|
+
* isActive && "active-class",
|
|
40
|
+
* isDisabled && "disabled-class"
|
|
41
|
+
* )
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* // Overriding Tailwind classes
|
|
47
|
+
* cn("px-4", "px-2") // Returns: "px-2" (later class wins)
|
|
48
|
+
* cn("text-red-500", "text-blue-500") // Returns: "text-blue-500"
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @see {@link https://github.com/lukeed/clsx} - clsx documentation
|
|
52
|
+
* @see {@link https://github.com/dcastil/tailwind-merge} - tailwind-merge documentation
|
|
53
|
+
* @since 1.0.0
|
|
54
|
+
*/
|
|
55
|
+
export function cn(...inputs: ClassValue[]) {
|
|
56
|
+
return twMerge(clsx(inputs));
|
|
57
|
+
}
|