@dogsbay/ui 0.2.0-beta.0

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.
Files changed (322) hide show
  1. package/package.json +38 -0
  2. package/src/accordion/Accordion.astro +38 -0
  3. package/src/accordion/AccordionContent.astro +25 -0
  4. package/src/accordion/AccordionItem.astro +26 -0
  5. package/src/accordion/AccordionTrigger.astro +38 -0
  6. package/src/accordion/accordion.ts +189 -0
  7. package/src/accordion/index.ts +4 -0
  8. package/src/alert/Alert.astro +69 -0
  9. package/src/alert/AlertDescription.astro +17 -0
  10. package/src/alert/AlertTitle.astro +21 -0
  11. package/src/alert/CollapsibleAlert.astro +103 -0
  12. package/src/alert/icon.ts +32 -0
  13. package/src/alert/index.ts +4 -0
  14. package/src/alert/variants.ts +29 -0
  15. package/src/alert-dialog/AlertDialog.astro +16 -0
  16. package/src/alert-dialog/AlertDialogAction.astro +23 -0
  17. package/src/alert-dialog/AlertDialogCancel.astro +23 -0
  18. package/src/alert-dialog/AlertDialogContent.astro +37 -0
  19. package/src/alert-dialog/AlertDialogDescription.astro +18 -0
  20. package/src/alert-dialog/AlertDialogFooter.astro +18 -0
  21. package/src/alert-dialog/AlertDialogHeader.astro +18 -0
  22. package/src/alert-dialog/AlertDialogTitle.astro +18 -0
  23. package/src/alert-dialog/AlertDialogTrigger.astro +24 -0
  24. package/src/alert-dialog/alert-dialog.ts +154 -0
  25. package/src/alert-dialog/index.ts +9 -0
  26. package/src/api-doc/ApiDoc.astro +52 -0
  27. package/src/api-doc/index.ts +1 -0
  28. package/src/api-layout/ApiCodePanel.astro +21 -0
  29. package/src/api-layout/ApiLayout.astro +29 -0
  30. package/src/api-layout/ExampleBlock.astro +63 -0
  31. package/src/api-params/ApiParams.astro +96 -0
  32. package/src/api-params/index.ts +1 -0
  33. package/src/api-source/ApiSource.astro +20 -0
  34. package/src/api-source/api-source.css +32 -0
  35. package/src/api-source/index.ts +1 -0
  36. package/src/api-symbol/ApiSymbol.astro +48 -0
  37. package/src/api-symbol/api-symbol.css +89 -0
  38. package/src/api-symbol/index.ts +1 -0
  39. package/src/api-url-bar/ApiUrlBar.astro +63 -0
  40. package/src/avatar/Avatar.astro +57 -0
  41. package/src/avatar/index.ts +1 -0
  42. package/src/badge/Badge.astro +37 -0
  43. package/src/badge/index.ts +1 -0
  44. package/src/breadcrumb/Breadcrumb.astro +32 -0
  45. package/src/breadcrumb/BreadcrumbEllipsis.astro +32 -0
  46. package/src/breadcrumb/BreadcrumbItem.astro +41 -0
  47. package/src/breadcrumb/BreadcrumbSeparator.astro +33 -0
  48. package/src/breadcrumb/index.ts +4 -0
  49. package/src/button/Button.astro +70 -0
  50. package/src/button/index.ts +1 -0
  51. package/src/card/Card.astro +17 -0
  52. package/src/card/CardContent.astro +17 -0
  53. package/src/card/CardDescription.astro +17 -0
  54. package/src/card/CardFooter.astro +17 -0
  55. package/src/card/CardHeader.astro +17 -0
  56. package/src/card/CardTitle.astro +17 -0
  57. package/src/card/index.ts +6 -0
  58. package/src/checkbox/Checkbox.astro +70 -0
  59. package/src/checkbox/index.ts +1 -0
  60. package/src/checkbox-group/CheckboxGroup.astro +38 -0
  61. package/src/checkbox-group/checkbox-group.ts +53 -0
  62. package/src/checkbox-group/index.ts +1 -0
  63. package/src/code-block/CodeBlock.astro +160 -0
  64. package/src/code-rich/CodeRich.astro +267 -0
  65. package/src/code-rich/code-rich.css +68 -0
  66. package/src/code-rich/index.ts +1 -0
  67. package/src/code-samples/CodeSamples.astro +59 -0
  68. package/src/collapsible/Collapsible.astro +35 -0
  69. package/src/collapsible/CollapsibleContent.astro +24 -0
  70. package/src/collapsible/CollapsibleTrigger.astro +23 -0
  71. package/src/collapsible/collapsible.ts +42 -0
  72. package/src/collapsible/index.ts +3 -0
  73. package/src/combobox/Combobox.astro +53 -0
  74. package/src/combobox/ComboboxEmpty.astro +18 -0
  75. package/src/combobox/ComboboxItem.astro +28 -0
  76. package/src/combobox/combobox.ts +179 -0
  77. package/src/combobox/index.ts +3 -0
  78. package/src/command/Command.astro +22 -0
  79. package/src/command/CommandEmpty.astro +18 -0
  80. package/src/command/CommandGroup.astro +24 -0
  81. package/src/command/CommandInput.astro +49 -0
  82. package/src/command/CommandItem.astro +26 -0
  83. package/src/command/CommandList.astro +19 -0
  84. package/src/command/CommandSeparator.astro +16 -0
  85. package/src/command/CommandShortcut.astro +18 -0
  86. package/src/command/command.ts +212 -0
  87. package/src/command/index.ts +8 -0
  88. package/src/content-renderer/ContentRenderer.astro +410 -0
  89. package/src/content-renderer/InlineRenderer.astro +149 -0
  90. package/src/context-menu/ContextMenu.astro +22 -0
  91. package/src/context-menu/ContextMenuContent.astro +24 -0
  92. package/src/context-menu/ContextMenuItem.astro +49 -0
  93. package/src/context-menu/ContextMenuSeparator.astro +14 -0
  94. package/src/context-menu/ContextMenuShortcut.astro +14 -0
  95. package/src/context-menu/ContextMenuTrigger.astro +22 -0
  96. package/src/context-menu/context-menu.ts +238 -0
  97. package/src/context-menu/index.ts +6 -0
  98. package/src/copy-button/CopyButton.astro +88 -0
  99. package/src/copy-button/index.ts +1 -0
  100. package/src/diagram/Diagram.astro +15 -0
  101. package/src/diagram/index.ts +1 -0
  102. package/src/dialog/Dialog.astro +22 -0
  103. package/src/dialog/DialogClose.astro +32 -0
  104. package/src/dialog/DialogContent.astro +37 -0
  105. package/src/dialog/DialogDescription.astro +22 -0
  106. package/src/dialog/DialogFooter.astro +18 -0
  107. package/src/dialog/DialogHeader.astro +18 -0
  108. package/src/dialog/DialogTitle.astro +18 -0
  109. package/src/dialog/DialogTrigger.astro +33 -0
  110. package/src/dialog/dialog.ts +291 -0
  111. package/src/dialog/index.ts +8 -0
  112. package/src/docs-footer/DocsFooter.astro +50 -0
  113. package/src/docs-header/DocsHeader.astro +32 -0
  114. package/src/docs-layout/DocsLayout.astro +187 -0
  115. package/src/docs-layout/index.ts +1 -0
  116. package/src/drawer/Drawer.astro +12 -0
  117. package/src/drawer/DrawerClose.astro +12 -0
  118. package/src/drawer/DrawerContent.astro +46 -0
  119. package/src/drawer/DrawerDescription.astro +10 -0
  120. package/src/drawer/DrawerFooter.astro +10 -0
  121. package/src/drawer/DrawerHandle.astro +16 -0
  122. package/src/drawer/DrawerHeader.astro +10 -0
  123. package/src/drawer/DrawerTitle.astro +10 -0
  124. package/src/drawer/DrawerTrigger.astro +25 -0
  125. package/src/drawer/drawer.ts +262 -0
  126. package/src/drawer/index.ts +9 -0
  127. package/src/dropdown-menu/DropdownMenu.astro +22 -0
  128. package/src/dropdown-menu/DropdownMenuContent.astro +33 -0
  129. package/src/dropdown-menu/DropdownMenuGroup.astro +17 -0
  130. package/src/dropdown-menu/DropdownMenuItem.astro +48 -0
  131. package/src/dropdown-menu/DropdownMenuLabel.astro +28 -0
  132. package/src/dropdown-menu/DropdownMenuSeparator.astro +21 -0
  133. package/src/dropdown-menu/DropdownMenuShortcut.astro +18 -0
  134. package/src/dropdown-menu/DropdownMenuTrigger.astro +34 -0
  135. package/src/dropdown-menu/dropdown-menu.ts +348 -0
  136. package/src/dropdown-menu/index.ts +8 -0
  137. package/src/endpoint-card/EndpointCard.astro +60 -0
  138. package/src/endpoint-card/EndpointCardSection.astro +22 -0
  139. package/src/endpoint-card/EndpointSection.astro +35 -0
  140. package/src/field/Field.astro +31 -0
  141. package/src/field/FieldDescription.astro +22 -0
  142. package/src/field/FieldLabel.astro +24 -0
  143. package/src/field/field.ts +61 -0
  144. package/src/field/index.ts +3 -0
  145. package/src/fieldset/Fieldset.astro +24 -0
  146. package/src/fieldset/FieldsetLegend.astro +18 -0
  147. package/src/fieldset/index.ts +2 -0
  148. package/src/footnote/Footnote.astro +37 -0
  149. package/src/footnote/footnote.ts +79 -0
  150. package/src/footnote/index.ts +1 -0
  151. package/src/form/Form.astro +27 -0
  152. package/src/form/form.ts +160 -0
  153. package/src/form/index.ts +1 -0
  154. package/src/grid/Grid.astro +117 -0
  155. package/src/grid/GridItem.astro +95 -0
  156. package/src/grid/index.ts +2 -0
  157. package/src/image/Image.astro +115 -0
  158. package/src/image/index.ts +1 -0
  159. package/src/input/Input.astro +34 -0
  160. package/src/input/index.ts +1 -0
  161. package/src/kbd/Kbd.astro +17 -0
  162. package/src/kbd/index.ts +1 -0
  163. package/src/label/Label.astro +17 -0
  164. package/src/label/index.ts +1 -0
  165. package/src/link-card/LinkCard.astro +47 -0
  166. package/src/link-card/index.ts +1 -0
  167. package/src/map-view/MapView.astro +88 -0
  168. package/src/map-view/index.ts +1 -0
  169. package/src/map-view/map-view.ts +194 -0
  170. package/src/markdown-example/MarkdownExample.astro +138 -0
  171. package/src/markdown-example/index.ts +1 -0
  172. package/src/math/MathKatex.astro +35 -0
  173. package/src/math/index.ts +1 -0
  174. package/src/menubar/Menubar.astro +25 -0
  175. package/src/menubar/MenubarContent.astro +24 -0
  176. package/src/menubar/MenubarItem.astro +26 -0
  177. package/src/menubar/MenubarMenu.astro +12 -0
  178. package/src/menubar/MenubarSeparator.astro +11 -0
  179. package/src/menubar/MenubarShortcut.astro +11 -0
  180. package/src/menubar/MenubarTrigger.astro +27 -0
  181. package/src/menubar/index.ts +7 -0
  182. package/src/menubar/menubar.ts +258 -0
  183. package/src/meter/Meter.astro +75 -0
  184. package/src/meter/index.ts +1 -0
  185. package/src/method-badge/MethodBadge.astro +32 -0
  186. package/src/navigation-menu/NavigationMenu.astro +24 -0
  187. package/src/navigation-menu/NavigationMenuContent.astro +24 -0
  188. package/src/navigation-menu/NavigationMenuItem.astro +22 -0
  189. package/src/navigation-menu/NavigationMenuLink.astro +26 -0
  190. package/src/navigation-menu/NavigationMenuList.astro +22 -0
  191. package/src/navigation-menu/NavigationMenuTrigger.astro +40 -0
  192. package/src/navigation-menu/index.ts +6 -0
  193. package/src/navigation-menu/navigation-menu.ts +192 -0
  194. package/src/number-field/NumberField.astro +84 -0
  195. package/src/number-field/index.ts +1 -0
  196. package/src/number-field/number-field.ts +68 -0
  197. package/src/pagination/Pagination.astro +59 -0
  198. package/src/pagination/index.ts +1 -0
  199. package/src/parameter-table/ParameterTable.astro +75 -0
  200. package/src/popover/Popover.astro +21 -0
  201. package/src/popover/PopoverContent.astro +31 -0
  202. package/src/popover/PopoverTrigger.astro +31 -0
  203. package/src/popover/index.ts +3 -0
  204. package/src/popover/popover.ts +144 -0
  205. package/src/preview-card/PreviewCard.astro +24 -0
  206. package/src/preview-card/PreviewCardContent.astro +24 -0
  207. package/src/preview-card/PreviewCardTrigger.astro +23 -0
  208. package/src/preview-card/index.ts +3 -0
  209. package/src/preview-card/preview-card.ts +91 -0
  210. package/src/progress/Progress.astro +61 -0
  211. package/src/progress/index.ts +1 -0
  212. package/src/property-item/PropertyItem.astro +183 -0
  213. package/src/property-list/PropertyList.astro +68 -0
  214. package/src/radio-group/RadioGroup.astro +41 -0
  215. package/src/radio-group/RadioGroupItem.astro +36 -0
  216. package/src/radio-group/index.ts +2 -0
  217. package/src/radio-group/radio-group.ts +126 -0
  218. package/src/response-tabs/ResponseTabs.astro +60 -0
  219. package/src/schema-viewer/SchemaViewer.astro +42 -0
  220. package/src/scroll-area/ScrollArea.astro +44 -0
  221. package/src/scroll-area/index.ts +1 -0
  222. package/src/scroll-area/scroll-area.ts +176 -0
  223. package/src/select/Select.astro +34 -0
  224. package/src/select/SelectContent.astro +24 -0
  225. package/src/select/SelectGroup.astro +23 -0
  226. package/src/select/SelectItem.astro +28 -0
  227. package/src/select/SelectLabel.astro +18 -0
  228. package/src/select/SelectSeparator.astro +22 -0
  229. package/src/select/SelectTrigger.astro +28 -0
  230. package/src/select/SelectValue.astro +18 -0
  231. package/src/select/index.ts +8 -0
  232. package/src/select/select.ts +381 -0
  233. package/src/separator/Separator.astro +35 -0
  234. package/src/separator/index.ts +1 -0
  235. package/src/sheet/Sheet.astro +12 -0
  236. package/src/sheet/SheetClose.astro +35 -0
  237. package/src/sheet/SheetContent.astro +52 -0
  238. package/src/sheet/SheetDescription.astro +18 -0
  239. package/src/sheet/SheetFooter.astro +18 -0
  240. package/src/sheet/SheetHeader.astro +18 -0
  241. package/src/sheet/SheetTitle.astro +18 -0
  242. package/src/sheet/SheetTrigger.astro +32 -0
  243. package/src/sheet/index.ts +8 -0
  244. package/src/sheet/sheet.ts +173 -0
  245. package/src/sidebar/Sidebar.astro +113 -0
  246. package/src/sidebar/SidebarContent.astro +18 -0
  247. package/src/sidebar/SidebarFooter.astro +18 -0
  248. package/src/sidebar/SidebarGroup.astro +18 -0
  249. package/src/sidebar/SidebarGroupContent.astro +18 -0
  250. package/src/sidebar/SidebarGroupLabel.astro +18 -0
  251. package/src/sidebar/SidebarHeader.astro +18 -0
  252. package/src/sidebar/SidebarInset.astro +18 -0
  253. package/src/sidebar/SidebarMenu.astro +18 -0
  254. package/src/sidebar/SidebarMenuBadge.astro +18 -0
  255. package/src/sidebar/SidebarMenuButton.astro +71 -0
  256. package/src/sidebar/SidebarMenuItem.astro +18 -0
  257. package/src/sidebar/SidebarMenuSub.astro +18 -0
  258. package/src/sidebar/SidebarMenuSubButton.astro +45 -0
  259. package/src/sidebar/SidebarMenuSubItem.astro +12 -0
  260. package/src/sidebar/SidebarNavTree.astro +106 -0
  261. package/src/sidebar/SidebarProvider.astro +23 -0
  262. package/src/sidebar/SidebarRail.astro +25 -0
  263. package/src/sidebar/SidebarSeparator.astro +21 -0
  264. package/src/sidebar/SidebarTrigger.astro +40 -0
  265. package/src/sidebar/index.ts +19 -0
  266. package/src/sidebar/sidebar.ts +265 -0
  267. package/src/skeleton/Skeleton.astro +16 -0
  268. package/src/skeleton/index.ts +1 -0
  269. package/src/skip-link/SkipLink.astro +24 -0
  270. package/src/slider/Slider.astro +61 -0
  271. package/src/slider/index.ts +1 -0
  272. package/src/slider/slider.ts +111 -0
  273. package/src/spinner/Spinner.astro +54 -0
  274. package/src/spinner/index.ts +1 -0
  275. package/src/steps/Step.astro +16 -0
  276. package/src/steps/Steps.astro +122 -0
  277. package/src/steps/SubSteps.astro +73 -0
  278. package/src/switch/Switch.astro +52 -0
  279. package/src/switch/index.ts +1 -0
  280. package/src/switch/switch.ts +109 -0
  281. package/src/table/Table.astro +24 -0
  282. package/src/table/TableBody.astro +18 -0
  283. package/src/table/TableCaption.astro +18 -0
  284. package/src/table/TableCell.astro +18 -0
  285. package/src/table/TableFooter.astro +18 -0
  286. package/src/table/TableHead.astro +18 -0
  287. package/src/table/TableHeader.astro +18 -0
  288. package/src/table/TableRow.astro +18 -0
  289. package/src/table/index.ts +8 -0
  290. package/src/tabs/Tabs.astro +37 -0
  291. package/src/tabs/TabsContent.astro +32 -0
  292. package/src/tabs/TabsList.astro +23 -0
  293. package/src/tabs/TabsTrigger.astro +29 -0
  294. package/src/tabs/index.ts +4 -0
  295. package/src/tabs/tabs.ts +220 -0
  296. package/src/textarea/Textarea.astro +25 -0
  297. package/src/textarea/index.ts +1 -0
  298. package/src/theme-toggle/ThemeToggle.astro +67 -0
  299. package/src/theme-toggle/index.ts +1 -0
  300. package/src/toast/Toaster.astro +39 -0
  301. package/src/toast/index.ts +3 -0
  302. package/src/toast/toast.ts +104 -0
  303. package/src/toc/TableOfContents.astro +225 -0
  304. package/src/toggle/Toggle.astro +56 -0
  305. package/src/toggle/index.ts +1 -0
  306. package/src/toggle/toggle.ts +38 -0
  307. package/src/toggle-group/ToggleGroup.astro +39 -0
  308. package/src/toggle-group/ToggleGroupItem.astro +56 -0
  309. package/src/toggle-group/index.ts +2 -0
  310. package/src/toggle-group/toggle-group.ts +121 -0
  311. package/src/toolbar/Toolbar.astro +26 -0
  312. package/src/toolbar/ToolbarButton.astro +29 -0
  313. package/src/toolbar/ToolbarSeparator.astro +14 -0
  314. package/src/toolbar/index.ts +3 -0
  315. package/src/toolbar/toolbar.ts +65 -0
  316. package/src/tooltip/Tooltip.astro +21 -0
  317. package/src/tooltip/TooltipContent.astro +25 -0
  318. package/src/tooltip/TooltipTrigger.astro +31 -0
  319. package/src/tooltip/index.ts +3 -0
  320. package/src/tooltip/tooltip.ts +156 -0
  321. package/src/youtube/YouTube.astro +119 -0
  322. package/src/youtube/index.ts +1 -0
@@ -0,0 +1,106 @@
1
+ ---
2
+ /**
3
+ * Recursive sidebar navigation tree.
4
+ * Renders a data-driven tree with native <details>/<summary> for zero-JS expand/collapse.
5
+ * Left-side chevrons, consistent indentation per level, active page highlighting.
6
+ *
7
+ * Usage:
8
+ * <SidebarNavTree items={navItems} currentPath={Astro.url.pathname} />
9
+ */
10
+
11
+ interface NavItem {
12
+ label: string;
13
+ href?: string;
14
+ icon?: string; // Raw SVG string (rendered via set:html)
15
+ children?: NavItem[];
16
+ }
17
+
18
+ interface Props {
19
+ items: NavItem[];
20
+ currentPath: string;
21
+ level?: number;
22
+ class?: string;
23
+ }
24
+
25
+ const { items, currentPath, level = 0, class: className } = Astro.props;
26
+
27
+ const normalizedPath = currentPath.replace(/\/$/, "") || "/";
28
+
29
+ function isActive(href: string) {
30
+ return normalizedPath === href;
31
+ }
32
+
33
+ function hasActiveDescendant(item: NavItem): boolean {
34
+ if (item.href && isActive(item.href)) return true;
35
+ if (item.children) return item.children.some(hasActiveDescendant);
36
+ return false;
37
+ }
38
+
39
+ // Indentation: 8px base + 12px per level
40
+ const paddingLeft = `${8 + level * 12}px`;
41
+ ---
42
+
43
+ <ul
44
+ class:list={[
45
+ "flex min-w-0 flex-col",
46
+ level === 0 && "w-full",
47
+ level === 0 && "group-data-[collapsible=icon]:hidden",
48
+ className,
49
+ ]}
50
+ data-sidebar="nav-tree"
51
+ data-level={level}
52
+ >
53
+ {items.map((item) => {
54
+ const active = item.href ? isActive(item.href) : false;
55
+ const hasChildren = item.children && item.children.length > 0;
56
+ const shouldOpen = hasChildren && hasActiveDescendant(item);
57
+
58
+ return (
59
+ <li data-sidebar="nav-tree-item">
60
+ {hasChildren ? (
61
+ <details open={shouldOpen || active}>
62
+ <summary
63
+ class:list={[
64
+ "flex w-full min-w-0 cursor-pointer items-center gap-2 rounded-md text-sm text-sidebar-foreground outline-none [list-style:none] ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&::-webkit-details-marker]:hidden",
65
+ level === 0 ? "h-8" : "h-7",
66
+ active && "bg-sidebar-accent font-medium text-sidebar-accent-foreground",
67
+ ]}
68
+ style={`padding-left: ${paddingLeft};`}
69
+ data-nav-href={item.href || undefined}
70
+ data-active={active || undefined}
71
+ >
72
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 shrink-0 transition-transform duration-200" data-chevron aria-hidden="true"><polyline points="9 18 15 12 9 6" /></svg>
73
+ {item.icon && <span class="shrink-0 [&>svg]:size-4" set:html={item.icon} />}
74
+ <span class="truncate">{item.label}</span>
75
+ </summary>
76
+ <Astro.self items={item.children!} currentPath={currentPath} level={level + 1} />
77
+ </details>
78
+ ) : (
79
+ <a
80
+ href={item.href || "#"}
81
+ class:list={[
82
+ "flex w-full min-w-0 items-center gap-2 rounded-md text-sm text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2",
83
+ level === 0 ? "h-8" : "h-7",
84
+ active && "bg-sidebar-accent font-medium text-sidebar-accent-foreground",
85
+ ]}
86
+ style={`padding-left: ${paddingLeft};`}
87
+ data-active={active || undefined}
88
+ data-nav-href={item.href}
89
+ >
90
+ {/* Spacer to align with chevron in branch items */}
91
+ <span class="size-4 shrink-0" />
92
+ {/* Icon (level 0 only) */}
93
+ {item.icon && <span class="shrink-0 [&>svg]:size-4" set:html={item.icon} />}
94
+ <span class="truncate">{item.label}</span>
95
+ </a>
96
+ )}
97
+ </li>
98
+ );
99
+ })}
100
+ </ul>
101
+
102
+ <style is:global>
103
+ details[open] > summary [data-chevron] {
104
+ transform: rotate(90deg);
105
+ }
106
+ </style>
@@ -0,0 +1,23 @@
1
+ ---
2
+ import { tv } from "tailwind-variants";
3
+
4
+ const sidebarProvider = tv({
5
+ base: "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
6
+ });
7
+
8
+ interface Props {
9
+ class?: string;
10
+ [key: string]: unknown;
11
+ }
12
+
13
+ const { class: className, ...rest } = Astro.props;
14
+ ---
15
+
16
+ <div
17
+ class={sidebarProvider({ class: className })}
18
+ data-component="sidebar-provider"
19
+ style="--sidebar-width: 16rem; --sidebar-width-icon: 3rem;"
20
+ {...rest}
21
+ >
22
+ <slot />
23
+ </div>
@@ -0,0 +1,25 @@
1
+ ---
2
+ import { tv } from "tailwind-variants";
3
+
4
+ const sidebarRail = tv({
5
+ base: "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar",
6
+ });
7
+
8
+ interface Props {
9
+ class?: string;
10
+ [key: string]: unknown;
11
+ }
12
+
13
+ const { class: className, ...rest } = Astro.props;
14
+ ---
15
+
16
+ <button
17
+ class={sidebarRail({ class: className })}
18
+ data-part="rail"
19
+ aria-label="Toggle Sidebar"
20
+ tabindex={-1}
21
+ title="Toggle Sidebar"
22
+ type="button"
23
+ {...rest}
24
+ >
25
+ </button>
@@ -0,0 +1,21 @@
1
+ ---
2
+ import { tv } from "tailwind-variants";
3
+
4
+ const sidebarSeparator = tv({
5
+ base: "mx-2 w-auto bg-sidebar-border h-px shrink-0",
6
+ });
7
+
8
+ interface Props {
9
+ class?: string;
10
+ [key: string]: unknown;
11
+ }
12
+
13
+ const { class: className, ...rest } = Astro.props;
14
+ ---
15
+
16
+ <div
17
+ class={sidebarSeparator({ class: className })}
18
+ role="separator"
19
+ {...rest}
20
+ >
21
+ </div>
@@ -0,0 +1,40 @@
1
+ ---
2
+ import { tv } from "tailwind-variants";
3
+
4
+ const sidebarTrigger = tv({
5
+ base: "inline-flex h-7 w-7 items-center justify-center rounded-md text-sm hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
6
+ });
7
+
8
+ interface Props {
9
+ class?: string;
10
+ [key: string]: unknown;
11
+ }
12
+
13
+ const { class: className, ...rest } = Astro.props;
14
+ ---
15
+
16
+ <button
17
+ class={sidebarTrigger({ class: className })}
18
+ data-part="sidebar-trigger"
19
+ type="button"
20
+ {...rest}
21
+ >
22
+ <slot>
23
+ <svg
24
+ xmlns="http://www.w3.org/2000/svg"
25
+ width="16"
26
+ height="16"
27
+ viewBox="0 0 24 24"
28
+ fill="none"
29
+ stroke="currentColor"
30
+ stroke-width="2"
31
+ stroke-linecap="round"
32
+ stroke-linejoin="round"
33
+ aria-hidden="true"
34
+ >
35
+ <rect width="18" height="18" x="3" y="3" rx="2"></rect>
36
+ <path d="M9 3v18"></path>
37
+ </svg>
38
+ </slot>
39
+ <span class="sr-only">Toggle Sidebar</span>
40
+ </button>
@@ -0,0 +1,19 @@
1
+ export { default as SidebarProvider } from "./SidebarProvider.astro";
2
+ export { default as Sidebar } from "./Sidebar.astro";
3
+ export { default as SidebarHeader } from "./SidebarHeader.astro";
4
+ export { default as SidebarContent } from "./SidebarContent.astro";
5
+ export { default as SidebarFooter } from "./SidebarFooter.astro";
6
+ export { default as SidebarTrigger } from "./SidebarTrigger.astro";
7
+ export { default as SidebarInset } from "./SidebarInset.astro";
8
+ export { default as SidebarRail } from "./SidebarRail.astro";
9
+ export { default as SidebarGroup } from "./SidebarGroup.astro";
10
+ export { default as SidebarGroupLabel } from "./SidebarGroupLabel.astro";
11
+ export { default as SidebarGroupContent } from "./SidebarGroupContent.astro";
12
+ export { default as SidebarMenu } from "./SidebarMenu.astro";
13
+ export { default as SidebarMenuItem } from "./SidebarMenuItem.astro";
14
+ export { default as SidebarMenuButton } from "./SidebarMenuButton.astro";
15
+ export { default as SidebarMenuSub } from "./SidebarMenuSub.astro";
16
+ export { default as SidebarMenuSubItem } from "./SidebarMenuSubItem.astro";
17
+ export { default as SidebarMenuSubButton } from "./SidebarMenuSubButton.astro";
18
+ export { default as SidebarSeparator } from "./SidebarSeparator.astro";
19
+ export { default as SidebarMenuBadge } from "./SidebarMenuBadge.astro";
@@ -0,0 +1,265 @@
1
+ import { ScrollLock } from "@dogsbay/primitives/scroll-lock";
2
+
3
+ const SIDEBAR_STORAGE_KEY = "sidebar_state";
4
+ const SIDEBAR_KEYBOARD_SHORTCUT = "b";
5
+ const MOBILE_BREAKPOINT = 768;
6
+
7
+ interface SidebarInstance {
8
+ provider: HTMLElement;
9
+ sidebar: HTMLElement;
10
+ desktopSidebar: HTMLElement | null;
11
+ mobileSidebar: HTMLElement | null;
12
+ mobileBackdrop: HTMLElement | null;
13
+ collapsible: "offcanvas" | "icon" | "none";
14
+ side: "left" | "right";
15
+ open: boolean;
16
+ openMobile: boolean;
17
+ isMobile: boolean;
18
+ mediaQuery: MediaQueryList;
19
+ }
20
+
21
+ const instances = new WeakMap<HTMLElement, SidebarInstance>();
22
+
23
+ function getStoredState(key: string): string | null {
24
+ try {
25
+ return localStorage.getItem(key);
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ function setStoredState(key: string, value: string) {
32
+ try {
33
+ localStorage.setItem(key, value);
34
+ } catch {
35
+ // localStorage unavailable (private browsing, etc.)
36
+ }
37
+ }
38
+
39
+ function initSidebar(provider: HTMLElement) {
40
+ if (instances.has(provider)) return;
41
+
42
+ const sidebar = provider.querySelector(
43
+ "[data-component='sidebar']",
44
+ ) as HTMLElement | null;
45
+ if (!sidebar) return;
46
+
47
+ const collapsible =
48
+ (sidebar.dataset.collapsible as SidebarInstance["collapsible"]) ||
49
+ "offcanvas";
50
+ const side = (sidebar.dataset.side as "left" | "right") || "left";
51
+ const defaultOpen = sidebar.dataset.defaultOpen !== "false";
52
+
53
+ // Read persisted state from localStorage
54
+ const storedState = getStoredState(SIDEBAR_STORAGE_KEY);
55
+ const initialOpen =
56
+ storedState !== null ? storedState === "true" : defaultOpen;
57
+
58
+ // Find desktop and mobile sidebar containers
59
+ const desktopSidebar = sidebar.querySelector(
60
+ "[data-sidebar-desktop]",
61
+ ) as HTMLElement | null;
62
+ const mobileSidebar = sidebar.querySelector(
63
+ "[data-sidebar-mobile]",
64
+ ) as HTMLElement | null;
65
+ const mobileBackdrop = sidebar.querySelector(
66
+ "[data-sidebar-mobile-backdrop]",
67
+ ) as HTMLElement | null;
68
+
69
+ const mediaQuery = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
70
+
71
+ const instance: SidebarInstance = {
72
+ provider,
73
+ sidebar,
74
+ desktopSidebar,
75
+ mobileSidebar,
76
+ mobileBackdrop,
77
+ collapsible,
78
+ side,
79
+ open: initialOpen,
80
+ openMobile: false,
81
+ isMobile: mediaQuery.matches,
82
+ mediaQuery,
83
+ };
84
+
85
+ instances.set(provider, instance);
86
+
87
+ // Set initial state
88
+ updateDesktopState(instance);
89
+
90
+ // Responsive listener
91
+ const handleMediaChange = (e: MediaQueryListEvent | MediaQueryList) => {
92
+ instance.isMobile = e.matches;
93
+ if (!e.matches && instance.openMobile) {
94
+ // Switched to desktop while mobile sidebar was open — close it
95
+ closeMobile(provider);
96
+ }
97
+ };
98
+ mediaQuery.addEventListener("change", handleMediaChange);
99
+
100
+ // Keyboard: Cmd+B / Ctrl+B toggle, Escape closes mobile
101
+ const handleKeyDown = (e: KeyboardEvent) => {
102
+ if (
103
+ e.key === SIDEBAR_KEYBOARD_SHORTCUT &&
104
+ (e.metaKey || e.ctrlKey)
105
+ ) {
106
+ e.preventDefault();
107
+ toggleSidebar(provider);
108
+ } else if (e.key === "Escape" && instance.openMobile) {
109
+ e.preventDefault();
110
+ closeMobile(provider);
111
+ }
112
+ };
113
+ document.addEventListener("keydown", handleKeyDown);
114
+
115
+ // Bind triggers
116
+ bindTriggers(provider, instance);
117
+
118
+ // Bind mobile backdrop click
119
+ if (mobileBackdrop) {
120
+ mobileBackdrop.addEventListener("click", () => {
121
+ closeMobile(provider);
122
+ });
123
+ }
124
+
125
+ // Bind rail
126
+ const rail = sidebar.querySelector("[data-part='rail']") as HTMLElement | null;
127
+ if (rail) {
128
+ rail.addEventListener("click", () => {
129
+ toggleSidebar(provider);
130
+ });
131
+ }
132
+ }
133
+
134
+ function bindTriggers(provider: HTMLElement, instance: SidebarInstance) {
135
+ // Find all triggers within the provider (or globally if using data-sidebar-for)
136
+ provider.querySelectorAll("[data-part='sidebar-trigger']").forEach((el) => {
137
+ el.addEventListener("click", () => {
138
+ toggleSidebar(provider);
139
+ });
140
+ });
141
+ }
142
+
143
+ function toggleSidebar(provider: HTMLElement) {
144
+ const instance = instances.get(provider);
145
+ if (!instance) return;
146
+
147
+ if (instance.isMobile) {
148
+ if (instance.openMobile) {
149
+ closeMobile(provider);
150
+ } else {
151
+ openMobile(provider);
152
+ }
153
+ } else {
154
+ instance.open = !instance.open;
155
+ updateDesktopState(instance);
156
+ setStoredState(SIDEBAR_STORAGE_KEY, String(instance.open));
157
+ }
158
+ }
159
+
160
+ function updateDesktopState(instance: SidebarInstance) {
161
+ const state = instance.open ? "expanded" : "collapsed";
162
+ instance.provider.setAttribute("data-state", state);
163
+ instance.sidebar.setAttribute("data-state", state);
164
+
165
+ if (instance.desktopSidebar) {
166
+ instance.desktopSidebar.setAttribute("data-state", state);
167
+
168
+ // Set data-collapsible only when collapsed
169
+ if (!instance.open) {
170
+ instance.desktopSidebar.setAttribute(
171
+ "data-collapsible",
172
+ instance.collapsible,
173
+ );
174
+ } else {
175
+ instance.desktopSidebar.setAttribute("data-collapsible", "");
176
+ }
177
+ }
178
+ }
179
+
180
+ function openMobile(provider: HTMLElement) {
181
+ const instance = instances.get(provider);
182
+ if (!instance || instance.openMobile || !instance.mobileSidebar) return;
183
+
184
+ instance.openMobile = true;
185
+
186
+ // Move sidebar inner content to mobile shell
187
+ const sidebarInner = instance.desktopSidebar?.querySelector(
188
+ "[data-sidebar='sidebar-inner']",
189
+ ) as HTMLElement | null;
190
+ if (sidebarInner) {
191
+ // Move all children to mobile shell
192
+ while (sidebarInner.firstChild) {
193
+ instance.mobileSidebar.appendChild(sidebarInner.firstChild);
194
+ }
195
+ }
196
+
197
+ instance.mobileSidebar.hidden = false;
198
+ instance.mobileSidebar.setAttribute("data-state", "open");
199
+
200
+ if (instance.mobileBackdrop) {
201
+ instance.mobileBackdrop.hidden = false;
202
+ }
203
+
204
+ // Lock scroll
205
+ ScrollLock.lock();
206
+
207
+ // Focus first focusable element in sidebar
208
+ requestAnimationFrame(() => {
209
+ const focusable = instance.mobileSidebar?.querySelector(
210
+ 'a, button, [tabindex]:not([tabindex="-1"])',
211
+ ) as HTMLElement | null;
212
+ focusable?.focus();
213
+ });
214
+ }
215
+
216
+ function closeMobile(provider: HTMLElement) {
217
+ const instance = instances.get(provider);
218
+ if (!instance || !instance.openMobile || !instance.mobileSidebar) return;
219
+
220
+ instance.openMobile = false;
221
+
222
+ // Move content back to desktop inner
223
+ const sidebarInner = instance.desktopSidebar?.querySelector(
224
+ "[data-sidebar='sidebar-inner']",
225
+ ) as HTMLElement | null;
226
+ if (sidebarInner && instance.mobileSidebar) {
227
+ while (instance.mobileSidebar.firstChild) {
228
+ sidebarInner.appendChild(instance.mobileSidebar.firstChild);
229
+ }
230
+ }
231
+
232
+ instance.mobileSidebar.setAttribute("data-state", "closed");
233
+ instance.mobileSidebar.hidden = true;
234
+
235
+ if (instance.mobileBackdrop) {
236
+ instance.mobileBackdrop.hidden = true;
237
+ }
238
+
239
+ // Unlock scroll
240
+ ScrollLock.unlock();
241
+
242
+ // Return focus to trigger
243
+ const trigger = instance.provider.querySelector(
244
+ "[data-part='sidebar-trigger']",
245
+ ) as HTMLElement | null;
246
+ trigger?.focus();
247
+ }
248
+
249
+ // Auto-initialize
250
+ function init() {
251
+ document
252
+ .querySelectorAll("[data-component='sidebar-provider']")
253
+ .forEach((el) => {
254
+ initSidebar(el as HTMLElement);
255
+ });
256
+ }
257
+
258
+ if (document.readyState === "loading") {
259
+ document.addEventListener("DOMContentLoaded", init);
260
+ } else {
261
+ init();
262
+ }
263
+ document.addEventListener("astro:page-load", init);
264
+
265
+ export { initSidebar, toggleSidebar, openMobile, closeMobile };
@@ -0,0 +1,16 @@
1
+ ---
2
+ import { tv } from "tailwind-variants";
3
+
4
+ const skeleton = tv({
5
+ base: "animate-pulse rounded-md bg-muted",
6
+ });
7
+
8
+ interface Props {
9
+ class?: string;
10
+ [key: string]: unknown;
11
+ }
12
+ const { class: className, ...rest } = Astro.props;
13
+ ---
14
+
15
+ <div class={skeleton({ class: className })} aria-hidden="true" {...rest}>
16
+ </div>
@@ -0,0 +1 @@
1
+ export { default as Skeleton } from "./Skeleton.astro";
@@ -0,0 +1,24 @@
1
+ ---
2
+ import { tv } from "tailwind-variants";
3
+
4
+ const skipLink = tv({
5
+ base: "sr-only focus:not-sr-only fixed left-4 top-4 z-[100] rounded-md bg-background px-4 py-2 text-sm font-medium text-foreground shadow-md ring-2 ring-ring focus:outline-none",
6
+ });
7
+
8
+ interface Props {
9
+ /** Target element ID (without #). Defaults to "content" */
10
+ target?: string;
11
+ class?: string;
12
+ [key: string]: unknown;
13
+ }
14
+
15
+ const { target = "content", class: className, ...rest } = Astro.props;
16
+ ---
17
+
18
+ <a
19
+ href={`#${target}`}
20
+ class={skipLink({ class: className })}
21
+ {...rest}
22
+ >
23
+ Skip to content
24
+ </a>
@@ -0,0 +1,61 @@
1
+ ---
2
+ import { tv } from "tailwind-variants";
3
+
4
+ const slider = tv({
5
+ base: "relative flex w-full touch-none select-none items-center",
6
+ });
7
+
8
+ interface Props {
9
+ name?: string;
10
+ defaultValue?: number;
11
+ min?: number;
12
+ max?: number;
13
+ step?: number;
14
+ disabled?: boolean;
15
+ class?: string;
16
+ [key: string]: unknown;
17
+ }
18
+
19
+ const {
20
+ name,
21
+ defaultValue = 50,
22
+ min = 0,
23
+ max = 100,
24
+ step = 1,
25
+ disabled = false,
26
+ class: className,
27
+ ...rest
28
+ } = Astro.props;
29
+
30
+ const pct = ((defaultValue - min) / (max - min)) * 100;
31
+ ---
32
+
33
+ <div
34
+ class={slider({ class: className })}
35
+ data-component="slider"
36
+ data-min={min}
37
+ data-max={max}
38
+ data-step={step}
39
+ data-name={name}
40
+ data-disabled={disabled || undefined}
41
+ {...rest}
42
+ >
43
+ <div class="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary" data-part="track">
44
+ <div class="absolute h-full bg-primary" data-part="range" style={`width: ${pct}%`}></div>
45
+ </div>
46
+ <button
47
+ class="absolute block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
48
+ data-part="thumb"
49
+ type="button"
50
+ role="slider"
51
+ aria-valuenow={defaultValue}
52
+ aria-valuemin={min}
53
+ aria-valuemax={max}
54
+ aria-label={name || "Value"}
55
+ aria-orientation="horizontal"
56
+ disabled={disabled || undefined}
57
+ tabindex={disabled ? -1 : 0}
58
+ style={`left: calc(${pct}% - 10px)`}
59
+ ></button>
60
+ {name && <input type="hidden" name={name} value={String(defaultValue)} data-part="hidden-input" />}
61
+ </div>
@@ -0,0 +1 @@
1
+ export { default as Slider } from "./Slider.astro";