@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,316 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A tabbed interface component for organizing content into multiple sections.
|
|
8
|
+
*
|
|
9
|
+
* Tabs provide an intuitive way to organize and display content in separate panels,
|
|
10
|
+
* with only one panel visible at a time. Built on Radix UI's robust accessibility
|
|
11
|
+
* foundation with full keyboard navigation, focus management, and screen reader support.
|
|
12
|
+
*
|
|
13
|
+
* The component supports both controlled and uncontrolled usage patterns, allowing
|
|
14
|
+
* for flexible integration in various application contexts. Each tab consists of a
|
|
15
|
+
* trigger (button) that activates its corresponding content panel.
|
|
16
|
+
*
|
|
17
|
+
* @example Basic uncontrolled tabs
|
|
18
|
+
* ```tsx
|
|
19
|
+
* <Tabs defaultValue="account" className="w-[400px]">
|
|
20
|
+
* <TabsList>
|
|
21
|
+
* <TabsTrigger value="account">Account</TabsTrigger>
|
|
22
|
+
* <TabsTrigger value="password">Password</TabsTrigger>
|
|
23
|
+
* </TabsList>
|
|
24
|
+
* <TabsContent value="account">
|
|
25
|
+
* <Card>
|
|
26
|
+
* <CardContent>
|
|
27
|
+
* <p>Manage your account settings here.</p>
|
|
28
|
+
* </CardContent>
|
|
29
|
+
* </Card>
|
|
30
|
+
* </TabsContent>
|
|
31
|
+
* <TabsContent value="password">
|
|
32
|
+
* <Card>
|
|
33
|
+
* <CardContent>
|
|
34
|
+
* <p>Change your password here.</p>
|
|
35
|
+
* </CardContent>
|
|
36
|
+
* </Card>
|
|
37
|
+
* </TabsContent>
|
|
38
|
+
* </Tabs>
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @example Controlled tabs with external state management
|
|
42
|
+
* ```tsx
|
|
43
|
+
* const [activeTab, setActiveTab] = useState("overview");
|
|
44
|
+
*
|
|
45
|
+
* // External controls can change the active tab
|
|
46
|
+
* const handleQuickNav = (tab: string) => {
|
|
47
|
+
* setActiveTab(tab);
|
|
48
|
+
* };
|
|
49
|
+
*
|
|
50
|
+
* <Tabs value={activeTab} onValueChange={setActiveTab}>
|
|
51
|
+
* <TabsList>
|
|
52
|
+
* <TabsTrigger value="overview">Overview</TabsTrigger>
|
|
53
|
+
* <TabsTrigger value="analytics">Analytics</TabsTrigger>
|
|
54
|
+
* <TabsTrigger value="reports">Reports</TabsTrigger>
|
|
55
|
+
* </TabsList>
|
|
56
|
+
* <TabsContent value="overview">
|
|
57
|
+
* <DashboardOverview />
|
|
58
|
+
* </TabsContent>
|
|
59
|
+
* <TabsContent value="analytics">
|
|
60
|
+
* <AnalyticsPanel />
|
|
61
|
+
* </TabsContent>
|
|
62
|
+
* <TabsContent value="reports">
|
|
63
|
+
* <ReportsPanel />
|
|
64
|
+
* </TabsContent>
|
|
65
|
+
* </Tabs>
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example Vertical orientation
|
|
69
|
+
* ```tsx
|
|
70
|
+
* <Tabs defaultValue="general" orientation="vertical" className="flex gap-4">
|
|
71
|
+
* <TabsList className="flex-col h-auto">
|
|
72
|
+
* <TabsTrigger value="general">General</TabsTrigger>
|
|
73
|
+
* <TabsTrigger value="security">Security</TabsTrigger>
|
|
74
|
+
* <TabsTrigger value="notifications">Notifications</TabsTrigger>
|
|
75
|
+
* </TabsList>
|
|
76
|
+
* <div className="flex-1">
|
|
77
|
+
* <TabsContent value="general">
|
|
78
|
+
* <SettingsGeneral />
|
|
79
|
+
* </TabsContent>
|
|
80
|
+
* <TabsContent value="security">
|
|
81
|
+
* <SettingsSecurity />
|
|
82
|
+
* </TabsContent>
|
|
83
|
+
* <TabsContent value="notifications">
|
|
84
|
+
* <SettingsNotifications />
|
|
85
|
+
* </TabsContent>
|
|
86
|
+
* </div>
|
|
87
|
+
* </Tabs>
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* @param defaultValue - The initial active tab (uncontrolled usage)
|
|
91
|
+
* @param value - The currently active tab (controlled usage)
|
|
92
|
+
* @param onValueChange - Callback fired when the active tab changes
|
|
93
|
+
* @param orientation - Layout direction: "horizontal" (default) or "vertical"
|
|
94
|
+
* @param dir - Text direction for RTL support: "ltr" or "rtl"
|
|
95
|
+
* @param activationMode - When tabs activate: "automatic" (on focus) or "manual" (on click/enter)
|
|
96
|
+
* @param className - Additional CSS classes to apply
|
|
97
|
+
* @param children - Tab list, content panels, and other child elements
|
|
98
|
+
*
|
|
99
|
+
* @accessibility
|
|
100
|
+
* - **Keyboard Navigation**: Arrow keys navigate between tabs, Tab/Shift+Tab moves focus into/out of tab list
|
|
101
|
+
* - **Focus Management**: Home/End keys jump to first/last tab, proper focus visible indicators
|
|
102
|
+
* - **Screen Readers**: Proper ARIA roles (tablist, tab, tabpanel), labels, and state announcements
|
|
103
|
+
* - **Touch Support**: Touch-friendly trigger areas with appropriate spacing
|
|
104
|
+
* - **RTL Support**: Respects text direction for proper layout in right-to-left languages
|
|
105
|
+
*
|
|
106
|
+
* @see {@link https://ui.shadcn.com/docs/components/tabs} shadcn/ui tabs documentation
|
|
107
|
+
* @see {@link https://www.radix-ui.com/primitives/docs/components/tabs} Radix UI tabs primitive
|
|
108
|
+
* @see {@link TabsList} - Container component for tab triggers
|
|
109
|
+
* @see {@link TabsTrigger} - Individual clickable tab button
|
|
110
|
+
* @see {@link TabsContent} - Content panel associated with each tab
|
|
111
|
+
* @since 1.0.0
|
|
112
|
+
*/
|
|
113
|
+
function Tabs({
|
|
114
|
+
className,
|
|
115
|
+
...props
|
|
116
|
+
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
|
117
|
+
return (
|
|
118
|
+
<TabsPrimitive.Root
|
|
119
|
+
data-slot="tabs"
|
|
120
|
+
className={cn("flex flex-col gap-2", className)}
|
|
121
|
+
{...props}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Container component that groups all tab trigger buttons together.
|
|
128
|
+
*
|
|
129
|
+
* Provides consistent styling and layout for tab triggers with a unified
|
|
130
|
+
* background and proper spacing. The list maintains the tab order and
|
|
131
|
+
* handles focus management within the tabbed interface.
|
|
132
|
+
*
|
|
133
|
+
* @example Standard horizontal layout
|
|
134
|
+
* ```tsx
|
|
135
|
+
* <TabsList>
|
|
136
|
+
* <TabsTrigger value="home">Home</TabsTrigger>
|
|
137
|
+
* <TabsTrigger value="about">About</TabsTrigger>
|
|
138
|
+
* <TabsTrigger value="contact">Contact</TabsTrigger>
|
|
139
|
+
* </TabsList>
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* @example Grid layout for equal-width tabs
|
|
143
|
+
* ```tsx
|
|
144
|
+
* <TabsList className="grid w-full grid-cols-3">
|
|
145
|
+
* <TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
|
146
|
+
* <TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
|
147
|
+
* <TabsTrigger value="tab3">Tab 3</TabsTrigger>
|
|
148
|
+
* </TabsList>
|
|
149
|
+
* ```
|
|
150
|
+
*
|
|
151
|
+
* @example Vertical orientation
|
|
152
|
+
* ```tsx
|
|
153
|
+
* <TabsList className="flex-col h-auto w-[200px]">
|
|
154
|
+
* <TabsTrigger value="settings" className="w-full justify-start">
|
|
155
|
+
* Settings
|
|
156
|
+
* </TabsTrigger>
|
|
157
|
+
* <TabsTrigger value="profile" className="w-full justify-start">
|
|
158
|
+
* Profile
|
|
159
|
+
* </TabsTrigger>
|
|
160
|
+
* </TabsList>
|
|
161
|
+
* ```
|
|
162
|
+
*
|
|
163
|
+
* @param className - Additional CSS classes for custom styling
|
|
164
|
+
* @param children - TabsTrigger components to be grouped
|
|
165
|
+
*
|
|
166
|
+
* @accessibility
|
|
167
|
+
* - Implements ARIA tablist role for screen readers
|
|
168
|
+
* - Maintains proper tab order and focus management
|
|
169
|
+
* - Supports keyboard navigation between triggers
|
|
170
|
+
*
|
|
171
|
+
* @since 1.0.0
|
|
172
|
+
*/
|
|
173
|
+
function TabsList({
|
|
174
|
+
className,
|
|
175
|
+
...props
|
|
176
|
+
}: React.ComponentProps<typeof TabsPrimitive.List>) {
|
|
177
|
+
return (
|
|
178
|
+
<TabsPrimitive.List
|
|
179
|
+
data-slot="tabs-list"
|
|
180
|
+
className={cn(
|
|
181
|
+
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
|
|
182
|
+
className,
|
|
183
|
+
)}
|
|
184
|
+
{...props}
|
|
185
|
+
/>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Individual clickable button that activates its associated tab content.
|
|
191
|
+
*
|
|
192
|
+
* Each trigger is associated with a specific tab panel via the `value` prop.
|
|
193
|
+
* The component automatically handles active states, focus management, and
|
|
194
|
+
* accessibility attributes. Supports icons, text, and custom content.
|
|
195
|
+
*
|
|
196
|
+
* @example Basic text trigger
|
|
197
|
+
* ```tsx
|
|
198
|
+
* <TabsTrigger value="dashboard">Dashboard</TabsTrigger>
|
|
199
|
+
* ```
|
|
200
|
+
*
|
|
201
|
+
* @example Trigger with icon
|
|
202
|
+
* ```tsx
|
|
203
|
+
* <TabsTrigger value="settings">
|
|
204
|
+
* <Settings className="h-4 w-4" />
|
|
205
|
+
* Settings
|
|
206
|
+
* </TabsTrigger>
|
|
207
|
+
* ```
|
|
208
|
+
*
|
|
209
|
+
* @example Disabled trigger
|
|
210
|
+
* ```tsx
|
|
211
|
+
* <TabsTrigger value="premium" disabled>
|
|
212
|
+
* Premium Features
|
|
213
|
+
* </TabsTrigger>
|
|
214
|
+
* ```
|
|
215
|
+
*
|
|
216
|
+
* @param value - Unique identifier that associates this trigger with its content panel
|
|
217
|
+
* @param disabled - When true, prevents interaction and shows disabled styling
|
|
218
|
+
* @param className - Additional CSS classes for custom styling
|
|
219
|
+
* @param children - Content to display within the trigger (text, icons, etc.)
|
|
220
|
+
*
|
|
221
|
+
* @accessibility
|
|
222
|
+
* - Implements ARIA tab role with proper attributes
|
|
223
|
+
* - Announces selected state to screen readers
|
|
224
|
+
* - Supports keyboard activation (Space, Enter)
|
|
225
|
+
* - Shows focus visible indicators for keyboard users
|
|
226
|
+
* - Respects disabled state for assistive technology
|
|
227
|
+
*
|
|
228
|
+
* @since 1.0.0
|
|
229
|
+
*/
|
|
230
|
+
function TabsTrigger({
|
|
231
|
+
className,
|
|
232
|
+
...props
|
|
233
|
+
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
|
234
|
+
return (
|
|
235
|
+
<TabsPrimitive.Trigger
|
|
236
|
+
data-slot="tabs-trigger"
|
|
237
|
+
className={cn(
|
|
238
|
+
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
239
|
+
className,
|
|
240
|
+
)}
|
|
241
|
+
{...props}
|
|
242
|
+
/>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Content panel that displays when its associated tab trigger is active.
|
|
248
|
+
*
|
|
249
|
+
* Each content panel is linked to a specific trigger via the `value` prop.
|
|
250
|
+
* Only one content panel is visible at a time, creating a clean tabbed
|
|
251
|
+
* interface. The component handles enter/exit animations and proper
|
|
252
|
+
* focus management when tabs change.
|
|
253
|
+
*
|
|
254
|
+
* @example Basic content panel
|
|
255
|
+
* ```tsx
|
|
256
|
+
* <TabsContent value="profile">
|
|
257
|
+
* <Card>
|
|
258
|
+
* <CardHeader>
|
|
259
|
+
* <CardTitle>Profile Settings</CardTitle>
|
|
260
|
+
* </CardHeader>
|
|
261
|
+
* <CardContent>
|
|
262
|
+
* <ProfileForm />
|
|
263
|
+
* </CardContent>
|
|
264
|
+
* </Card>
|
|
265
|
+
* </TabsContent>
|
|
266
|
+
* ```
|
|
267
|
+
*
|
|
268
|
+
* @example Content with loading state
|
|
269
|
+
* ```tsx
|
|
270
|
+
* <TabsContent value="analytics">
|
|
271
|
+
* {isLoading ? (
|
|
272
|
+
* <div className="flex items-center justify-center p-8">
|
|
273
|
+
* <Spinner className="h-6 w-6" />
|
|
274
|
+
* </div>
|
|
275
|
+
* ) : (
|
|
276
|
+
* <AnalyticsCharts data={analyticsData} />
|
|
277
|
+
* )}
|
|
278
|
+
* </TabsContent>
|
|
279
|
+
* ```
|
|
280
|
+
*
|
|
281
|
+
* @example Lazy-loaded content
|
|
282
|
+
* ```tsx
|
|
283
|
+
* <TabsContent value="reports" className="space-y-4">
|
|
284
|
+
* <Suspense fallback={<ReportsSkeleton />}>
|
|
285
|
+
* <ReportsPanel />
|
|
286
|
+
* </Suspense>
|
|
287
|
+
* </TabsContent>
|
|
288
|
+
* ```
|
|
289
|
+
*
|
|
290
|
+
* @param value - Unique identifier that associates this content with its trigger
|
|
291
|
+
* @param forceMount - When true, content stays mounted even when inactive (useful for preserving state)
|
|
292
|
+
* @param className - Additional CSS classes for custom styling
|
|
293
|
+
* @param children - The content to display when this tab is active
|
|
294
|
+
*
|
|
295
|
+
* @accessibility
|
|
296
|
+
* - Implements ARIA tabpanel role with proper labeling
|
|
297
|
+
* - Hidden from screen readers when inactive
|
|
298
|
+
* - Receives focus when tab is activated via keyboard
|
|
299
|
+
* - Maintains focus within panel content
|
|
300
|
+
*
|
|
301
|
+
* @since 1.0.0
|
|
302
|
+
*/
|
|
303
|
+
function TabsContent({
|
|
304
|
+
className,
|
|
305
|
+
...props
|
|
306
|
+
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
307
|
+
return (
|
|
308
|
+
<TabsPrimitive.Content
|
|
309
|
+
data-slot="tabs-content"
|
|
310
|
+
className={cn("flex-1 outline-none", className)}
|
|
311
|
+
{...props}
|
|
312
|
+
/>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
import { Input } from "./input";
|
|
4
|
+
import { Label } from "./label";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Props for the TextField component
|
|
8
|
+
*
|
|
9
|
+
* Extends standard HTML input properties while omitting 'id' to allow
|
|
10
|
+
* automatic ID generation for accessibility.
|
|
11
|
+
*/
|
|
12
|
+
export type TextFieldProps = Omit<React.ComponentProps<"input">, "id"> & {
|
|
13
|
+
/**
|
|
14
|
+
* Label text displayed above the input
|
|
15
|
+
*
|
|
16
|
+
* When provided, creates a semantic label-input relationship
|
|
17
|
+
* for improved accessibility and user experience.
|
|
18
|
+
*/
|
|
19
|
+
label?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Helper text displayed below the input
|
|
22
|
+
*
|
|
23
|
+
* Provides additional context or instructions to help users
|
|
24
|
+
* understand the expected input format or purpose.
|
|
25
|
+
*/
|
|
26
|
+
helperText?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Error message displayed below the input
|
|
29
|
+
*
|
|
30
|
+
* When provided, sets the input to an error state with
|
|
31
|
+
* destructive styling and proper ARIA attributes.
|
|
32
|
+
* Takes precedence over helperText when both are present.
|
|
33
|
+
*/
|
|
34
|
+
error?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Whether the field is required
|
|
37
|
+
*
|
|
38
|
+
* Adds a visual asterisk (*) indicator to the label
|
|
39
|
+
* and semantic meaning for form validation.
|
|
40
|
+
*
|
|
41
|
+
* @default false
|
|
42
|
+
*/
|
|
43
|
+
required?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Custom ID for the input field
|
|
46
|
+
*
|
|
47
|
+
* If not provided, an automatic ID will be generated
|
|
48
|
+
* using React.useId() for accessibility compliance.
|
|
49
|
+
*/
|
|
50
|
+
id?: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A complete form field component combining input, label, and descriptive text
|
|
55
|
+
*
|
|
56
|
+
* TextField provides a complete form field solution by composing the base Input
|
|
57
|
+
* and Label components with additional helper text and error handling. It automatically
|
|
58
|
+
* manages accessibility attributes, ID generation, and ARIA relationships to ensure
|
|
59
|
+
* forms are usable by all users including those using assistive technologies.
|
|
60
|
+
*
|
|
61
|
+
* This component follows form design patterns similar to those found in modern UI
|
|
62
|
+
* libraries while providing Neynar-specific styling and behavior. It supports all
|
|
63
|
+
* standard HTML input types and attributes while adding enhanced UX features.
|
|
64
|
+
*
|
|
65
|
+
* @example Basic usage
|
|
66
|
+
* ```tsx
|
|
67
|
+
* <TextField
|
|
68
|
+
* label="Email Address"
|
|
69
|
+
* type="email"
|
|
70
|
+
* placeholder="Enter your email"
|
|
71
|
+
* />
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* @example With helper text
|
|
75
|
+
* ```tsx
|
|
76
|
+
* <TextField
|
|
77
|
+
* label="Username"
|
|
78
|
+
* placeholder="@username"
|
|
79
|
+
* helperText="Choose a unique username (3-20 characters)"
|
|
80
|
+
* />
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @example Error state
|
|
84
|
+
* ```tsx
|
|
85
|
+
* <TextField
|
|
86
|
+
* label="Password"
|
|
87
|
+
* type="password"
|
|
88
|
+
* required
|
|
89
|
+
* error="Password must be at least 8 characters"
|
|
90
|
+
* value={password}
|
|
91
|
+
* onChange={(e) => setPassword(e.target.value)}
|
|
92
|
+
* />
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* @example Controlled component with validation
|
|
96
|
+
* ```tsx
|
|
97
|
+
* const [email, setEmail] = useState("");
|
|
98
|
+
* const [error, setError] = useState("");
|
|
99
|
+
*
|
|
100
|
+
* const validateEmail = (value: string) => {
|
|
101
|
+
* if (!value.includes("@")) {
|
|
102
|
+
* setError("Please enter a valid email");
|
|
103
|
+
* } else {
|
|
104
|
+
* setError("");
|
|
105
|
+
* }
|
|
106
|
+
* };
|
|
107
|
+
*
|
|
108
|
+
* <TextField
|
|
109
|
+
* label="Email"
|
|
110
|
+
* type="email"
|
|
111
|
+
* value={email}
|
|
112
|
+
* onChange={(e) => {
|
|
113
|
+
* setEmail(e.target.value);
|
|
114
|
+
* validateEmail(e.target.value);
|
|
115
|
+
* }}
|
|
116
|
+
* error={error}
|
|
117
|
+
* required
|
|
118
|
+
* />
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @example Form integration
|
|
122
|
+
* ```tsx
|
|
123
|
+
* <form onSubmit={handleSubmit}>
|
|
124
|
+
* <TextField
|
|
125
|
+
* label="Full Name"
|
|
126
|
+
* name="fullName"
|
|
127
|
+
* required
|
|
128
|
+
* helperText="First and last name"
|
|
129
|
+
* />
|
|
130
|
+
* <TextField
|
|
131
|
+
* label="API Key"
|
|
132
|
+
* type="password"
|
|
133
|
+
* name="apiKey"
|
|
134
|
+
* required
|
|
135
|
+
* helperText="Get your key from the Neynar dashboard"
|
|
136
|
+
* />
|
|
137
|
+
* </form>
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* ## Accessibility Features
|
|
141
|
+
*
|
|
142
|
+
* - **Semantic HTML**: Uses proper label-input association via `htmlFor` attribute
|
|
143
|
+
* - **ARIA Support**: Implements `aria-invalid`, `aria-describedby`, and `aria-required`
|
|
144
|
+
* - **Focus Management**: Keyboard navigation and focus indicators work correctly
|
|
145
|
+
* - **Screen Readers**: Helper text and error messages are properly announced
|
|
146
|
+
* - **Required Fields**: Visual and semantic indication with asterisk and ARIA attributes
|
|
147
|
+
* - **Error States**: Clear visual and programmatic error indication
|
|
148
|
+
*
|
|
149
|
+
* ## Styling & Variants
|
|
150
|
+
*
|
|
151
|
+
* The component inherits styling from the base Input component and adds:
|
|
152
|
+
* - Error state styling with destructive colors
|
|
153
|
+
* - Consistent spacing with `space-y-2` utility
|
|
154
|
+
* - Muted foreground color for helper text
|
|
155
|
+
* - Required field indicator styling
|
|
156
|
+
*
|
|
157
|
+
* @see {@link Input} The underlying input component
|
|
158
|
+
* @see {@link Label} The label component used for field labels
|
|
159
|
+
* @since 1.0.0 */
|
|
160
|
+
const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>(
|
|
161
|
+
({ label, helperText, error, required, className, id, ...props }, ref) => {
|
|
162
|
+
const idIfNeeded = React.useId();
|
|
163
|
+
const fieldId = id || idIfNeeded;
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div className={cn("space-y-2", className)}>
|
|
167
|
+
{label && (
|
|
168
|
+
<Label htmlFor={fieldId} className={error ? "text-destructive" : ""}>
|
|
169
|
+
{label}
|
|
170
|
+
{required && <span className="text-destructive ml-1">*</span>}
|
|
171
|
+
</Label>
|
|
172
|
+
)}
|
|
173
|
+
<Input
|
|
174
|
+
id={fieldId}
|
|
175
|
+
ref={ref}
|
|
176
|
+
className={
|
|
177
|
+
error ? "border-destructive focus-visible:ring-destructive" : ""
|
|
178
|
+
}
|
|
179
|
+
aria-invalid={!!error}
|
|
180
|
+
aria-describedby={
|
|
181
|
+
error
|
|
182
|
+
? `${fieldId}-error`
|
|
183
|
+
: helperText
|
|
184
|
+
? `${fieldId}-helper`
|
|
185
|
+
: undefined
|
|
186
|
+
}
|
|
187
|
+
{...props}
|
|
188
|
+
/>
|
|
189
|
+
{error && (
|
|
190
|
+
<p id={`${fieldId}-error`} className="text-sm text-destructive">
|
|
191
|
+
{error}
|
|
192
|
+
</p>
|
|
193
|
+
)}
|
|
194
|
+
{helperText && !error && (
|
|
195
|
+
<p id={`${fieldId}-helper`} className="text-sm text-muted-foreground">
|
|
196
|
+
{helperText}
|
|
197
|
+
</p>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
},
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
TextField.displayName = "TextField";
|
|
205
|
+
|
|
206
|
+
export { TextField };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A multi-line text input control built on the native HTML textarea element
|
|
7
|
+
*
|
|
8
|
+
* The Textarea component provides a styled textarea for entering longer pieces
|
|
9
|
+
* of text that span multiple lines, such as comments, descriptions, or messages.
|
|
10
|
+
* Features automatic content-based resizing using CSS field-sizing when supported
|
|
11
|
+
* by the browser.
|
|
12
|
+
*
|
|
13
|
+
* Built following shadcn/ui design patterns with consistent styling and behavior
|
|
14
|
+
* across form elements.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* // Basic usage
|
|
19
|
+
* <Textarea placeholder="Type your message here." />
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* // With label and validation
|
|
25
|
+
* <div className="space-y-2">
|
|
26
|
+
* <Label htmlFor="description">Description</Label>
|
|
27
|
+
* <Textarea
|
|
28
|
+
* id="description"
|
|
29
|
+
* placeholder="Enter a description..."
|
|
30
|
+
* rows={4}
|
|
31
|
+
* />
|
|
32
|
+
* </div>
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* // Controlled with character limit
|
|
38
|
+
* const [bio, setBio] = useState("");
|
|
39
|
+
* const maxLength = 160;
|
|
40
|
+
*
|
|
41
|
+
* <div className="space-y-2">
|
|
42
|
+
* <Label htmlFor="bio">Bio</Label>
|
|
43
|
+
* <Textarea
|
|
44
|
+
* id="bio"
|
|
45
|
+
* value={bio}
|
|
46
|
+
* onChange={(e) => setBio(e.target.value)}
|
|
47
|
+
* placeholder="Tell us about yourself..."
|
|
48
|
+
* maxLength={maxLength}
|
|
49
|
+
* />
|
|
50
|
+
* <p className="text-sm text-muted-foreground">
|
|
51
|
+
* {bio.length}/{maxLength} characters
|
|
52
|
+
* </p>
|
|
53
|
+
* </div>
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```tsx
|
|
58
|
+
* // With error state
|
|
59
|
+
* <Textarea
|
|
60
|
+
* placeholder="Enter your feedback"
|
|
61
|
+
* aria-invalid="true"
|
|
62
|
+
* aria-describedby="feedback-error"
|
|
63
|
+
* className="border-destructive"
|
|
64
|
+
* />
|
|
65
|
+
* <p id="feedback-error" className="text-sm text-destructive">
|
|
66
|
+
* This field is required
|
|
67
|
+
* </p>
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @param className - Additional CSS classes to apply
|
|
71
|
+
* @param ...props - All standard HTML textarea attributes are supported
|
|
72
|
+
*
|
|
73
|
+
* @accessibility
|
|
74
|
+
* - Inherits all native textarea accessibility features
|
|
75
|
+
* - Supports keyboard navigation with visible focus indicators
|
|
76
|
+
* - Compatible with screen readers and assistive technologies
|
|
77
|
+
* - Properly handles aria-invalid for validation states
|
|
78
|
+
* - Works seamlessly with Label components via htmlFor/id association
|
|
79
|
+
* - Respects disabled and readonly states
|
|
80
|
+
* - Auto-resizing maintains proper focus and scroll behavior
|
|
81
|
+
*
|
|
82
|
+
* @see {@link https://ui.shadcn.com/docs/components/textarea} shadcn/ui Textarea documentation
|
|
83
|
+
* @since 1.0.0
|
|
84
|
+
*/
|
|
85
|
+
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
86
|
+
return (
|
|
87
|
+
<textarea
|
|
88
|
+
data-slot="textarea"
|
|
89
|
+
className={cn(
|
|
90
|
+
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
91
|
+
className,
|
|
92
|
+
)}
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { Textarea };
|