@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,8 @@
1
+ export { default as Select } from "./Select.astro";
2
+ export { default as SelectTrigger } from "./SelectTrigger.astro";
3
+ export { default as SelectValue } from "./SelectValue.astro";
4
+ export { default as SelectContent } from "./SelectContent.astro";
5
+ export { default as SelectItem } from "./SelectItem.astro";
6
+ export { default as SelectGroup } from "./SelectGroup.astro";
7
+ export { default as SelectLabel } from "./SelectLabel.astro";
8
+ export { default as SelectSeparator } from "./SelectSeparator.astro";
@@ -0,0 +1,381 @@
1
+ import { DismissController } from "@dogsbay/primitives/dismiss";
2
+ import { createPortal } from "@dogsbay/primitives/portal";
3
+ import { setupFloating } from "@dogsbay/primitives/floating";
4
+ import { Typeahead } from "@dogsbay/primitives/typeahead";
5
+
6
+ interface SelectInstance {
7
+ root: HTMLElement;
8
+ trigger: HTMLElement;
9
+ content: HTMLElement;
10
+ valueEl: HTMLElement | null;
11
+ items: HTMLElement[];
12
+ hiddenInput: HTMLInputElement | null;
13
+ dismiss: DismissController;
14
+ typeahead: Typeahead;
15
+ cleanupPortal: (() => void) | null;
16
+ cleanupFloating: (() => void) | null;
17
+ open: boolean;
18
+ value: string;
19
+ highlightedIndex: number;
20
+ placeholder: string;
21
+ }
22
+
23
+ const instances = new WeakMap<HTMLElement, SelectInstance>();
24
+
25
+ let idCounter = 0;
26
+ function uniqueId(prefix: string) {
27
+ return `${prefix}-${++idCounter}`;
28
+ }
29
+
30
+ function getEnabledItems(items: HTMLElement[]): HTMLElement[] {
31
+ return items.filter((item) => item.getAttribute("aria-disabled") !== "true");
32
+ }
33
+
34
+ function initSelect(root: HTMLElement) {
35
+ if (instances.has(root)) return;
36
+
37
+ const trigger = root.querySelector(
38
+ "[data-part='trigger']",
39
+ ) as HTMLElement | null;
40
+ const content = root.querySelector(
41
+ "[data-part='content']",
42
+ ) as HTMLElement | null;
43
+ const valueEl = root.querySelector(
44
+ "[data-part='value']",
45
+ ) as HTMLElement | null;
46
+
47
+ if (!trigger || !content) return;
48
+
49
+ const items = Array.from(
50
+ content.querySelectorAll("[data-part='item']"),
51
+ ) as HTMLElement[];
52
+ const name = root.dataset.name;
53
+ const defaultValue = root.dataset.defaultValue || "";
54
+ const required = root.hasAttribute("data-required");
55
+ const placeholder = valueEl?.dataset.placeholder || "Select...";
56
+
57
+ // Generate IDs
58
+ const selectId = uniqueId("base-select");
59
+ const contentId = content.id || `${selectId}-listbox`;
60
+ content.id = contentId;
61
+ trigger.setAttribute("aria-controls", contentId);
62
+
63
+ items.forEach((item, i) => {
64
+ const itemId = item.id || `${selectId}-item-${i}`;
65
+ item.id = itemId;
66
+ });
67
+
68
+ // Link groups to their labels
69
+ content.querySelectorAll("[data-part='group']").forEach((group) => {
70
+ const label = group.querySelector("[data-part='label']") as HTMLElement;
71
+ if (label) {
72
+ const labelId = label.id || uniqueId("base-select-label");
73
+ label.id = labelId;
74
+ (group as HTMLElement).setAttribute("aria-labelledby", labelId);
75
+ }
76
+ });
77
+
78
+ // Create hidden input for form submission
79
+ let hiddenInput: HTMLInputElement | null = null;
80
+ if (name) {
81
+ hiddenInput = document.createElement("input");
82
+ hiddenInput.type = "hidden";
83
+ hiddenInput.name = name;
84
+ hiddenInput.value = defaultValue;
85
+ if (required) hiddenInput.required = true;
86
+ root.appendChild(hiddenInput);
87
+ }
88
+
89
+ const typeahead = new Typeahead({
90
+ onMatch: (index) => {
91
+ const enabledItems = getEnabledItems(items);
92
+ if (index < enabledItems.length) {
93
+ const actualIndex = items.indexOf(enabledItems[index]);
94
+ highlightItem(root, actualIndex);
95
+ }
96
+ },
97
+ getItemText: (i) => {
98
+ const enabledItems = getEnabledItems(items);
99
+ return enabledItems[i]?.textContent?.trim() || "";
100
+ },
101
+ getItemCount: () => getEnabledItems(items).length,
102
+ });
103
+
104
+ const instance: SelectInstance = {
105
+ root,
106
+ trigger,
107
+ content,
108
+ valueEl,
109
+ items,
110
+ hiddenInput,
111
+ dismiss: new DismissController(content, {
112
+ onDismiss: () => closeSelect(root),
113
+ }),
114
+ typeahead,
115
+ cleanupPortal: null,
116
+ cleanupFloating: null,
117
+ open: false,
118
+ value: "",
119
+ highlightedIndex: -1,
120
+ placeholder,
121
+ };
122
+
123
+ instances.set(root, instance);
124
+
125
+ // Trigger click
126
+ trigger.addEventListener("click", () => {
127
+ if (instance.open) {
128
+ closeSelect(root);
129
+ } else {
130
+ openSelect(root);
131
+ }
132
+ });
133
+
134
+ // All keyboard handling on the trigger (focus stays here in virtual focus pattern)
135
+ trigger.addEventListener("keydown", (e) => {
136
+ if (!instance.open) {
137
+ // Closed: open on ArrowDown/ArrowUp/Enter/Space
138
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
139
+ e.preventDefault();
140
+ openSelect(root);
141
+ const enabledItems = getEnabledItems(items);
142
+ if (enabledItems.length > 0) {
143
+ const idx =
144
+ e.key === "ArrowDown"
145
+ ? items.indexOf(enabledItems[0])
146
+ : items.indexOf(enabledItems[enabledItems.length - 1]);
147
+ highlightItem(root, idx);
148
+ }
149
+ } else if (e.key === "Enter" || e.key === " ") {
150
+ e.preventDefault();
151
+ openSelect(root);
152
+ }
153
+ return;
154
+ }
155
+
156
+ // Open: navigate and select
157
+ const enabledItems = getEnabledItems(items);
158
+ if (enabledItems.length === 0) return;
159
+
160
+ const currentHighlighted =
161
+ instance.highlightedIndex >= 0 ? items[instance.highlightedIndex] : null;
162
+ const currentEnabledIndex = currentHighlighted
163
+ ? enabledItems.indexOf(currentHighlighted)
164
+ : -1;
165
+
166
+ if (e.key === "ArrowDown") {
167
+ e.preventDefault();
168
+ const nextIdx =
169
+ currentEnabledIndex < enabledItems.length - 1
170
+ ? currentEnabledIndex + 1
171
+ : 0;
172
+ highlightItem(root, items.indexOf(enabledItems[nextIdx]));
173
+ } else if (e.key === "ArrowUp") {
174
+ e.preventDefault();
175
+ const prevIdx =
176
+ currentEnabledIndex > 0
177
+ ? currentEnabledIndex - 1
178
+ : enabledItems.length - 1;
179
+ highlightItem(root, items.indexOf(enabledItems[prevIdx]));
180
+ } else if (e.key === "Home") {
181
+ e.preventDefault();
182
+ highlightItem(root, items.indexOf(enabledItems[0]));
183
+ } else if (e.key === "End") {
184
+ e.preventDefault();
185
+ highlightItem(root, items.indexOf(enabledItems[enabledItems.length - 1]));
186
+ } else if (e.key === "Enter" || e.key === " ") {
187
+ e.preventDefault();
188
+ if (instance.highlightedIndex >= 0) {
189
+ selectItem(root, items[instance.highlightedIndex]);
190
+ }
191
+ closeSelect(root);
192
+ } else if (e.key === "Tab") {
193
+ closeSelect(root);
194
+ } else {
195
+ typeahead.handle(e.key);
196
+ }
197
+ });
198
+
199
+ // Item click
200
+ items.forEach((item) => {
201
+ item.addEventListener("click", () => {
202
+ if (item.getAttribute("aria-disabled") === "true") return;
203
+ selectItem(root, item);
204
+ closeSelect(root);
205
+ });
206
+
207
+ item.addEventListener("pointerenter", () => {
208
+ if (item.getAttribute("aria-disabled") === "true") return;
209
+ highlightItem(root, items.indexOf(item));
210
+ });
211
+ });
212
+
213
+ // Set initial value
214
+ if (defaultValue) {
215
+ const defaultItem = items.find(
216
+ (item) => item.dataset.value === defaultValue,
217
+ );
218
+ if (defaultItem) {
219
+ selectItem(root, defaultItem);
220
+ }
221
+ }
222
+ }
223
+
224
+ function openSelect(root: HTMLElement) {
225
+ const instance = instances.get(root);
226
+ if (!instance || instance.open) return;
227
+ instance.open = true;
228
+
229
+ // Portal
230
+ instance.cleanupPortal = createPortal(instance.content, instance.content.id);
231
+
232
+ // Show
233
+ instance.content.hidden = false;
234
+ instance.content.setAttribute("data-state", "open");
235
+ instance.trigger.setAttribute("aria-expanded", "true");
236
+ instance.root.setAttribute("data-state", "open");
237
+
238
+ // Position
239
+ instance.cleanupFloating = setupFloating(instance.trigger, instance.content, {
240
+ placement: "bottom-start",
241
+ offset: 4,
242
+ matchWidth: true,
243
+ });
244
+
245
+ // Highlight selected item or first item
246
+ const selectedItem = instance.items.find(
247
+ (item) => item.dataset.value === instance.value,
248
+ );
249
+ const enabledItems = getEnabledItems(instance.items);
250
+ if (selectedItem && selectedItem.getAttribute("aria-disabled") !== "true") {
251
+ highlightItem(root, instance.items.indexOf(selectedItem));
252
+ } else if (enabledItems.length > 0) {
253
+ highlightItem(root, instance.items.indexOf(enabledItems[0]));
254
+ }
255
+
256
+ // Dismiss
257
+ instance.dismiss.activate();
258
+
259
+ // Keep focus on trigger (virtual focus pattern)
260
+ instance.trigger.focus();
261
+
262
+ // Reset typeahead
263
+ instance.typeahead.reset();
264
+ }
265
+
266
+ function closeSelect(root: HTMLElement) {
267
+ const instance = instances.get(root);
268
+ if (!instance || !instance.open) return;
269
+ instance.open = false;
270
+
271
+ instance.dismiss.deactivate();
272
+
273
+ // Update state
274
+ instance.content.setAttribute("data-state", "closed");
275
+ instance.trigger.setAttribute("aria-expanded", "false");
276
+ instance.trigger.removeAttribute("aria-activedescendant");
277
+ instance.root.removeAttribute("data-state");
278
+
279
+ // Clear highlights
280
+ instance.items.forEach((item) => {
281
+ item.removeAttribute("data-highlighted");
282
+ });
283
+ instance.highlightedIndex = -1;
284
+
285
+ // Hide and cleanup
286
+ instance.content.hidden = true;
287
+ if (instance.cleanupFloating) {
288
+ instance.cleanupFloating();
289
+ instance.cleanupFloating = null;
290
+ }
291
+ if (instance.cleanupPortal) {
292
+ instance.cleanupPortal();
293
+ instance.cleanupPortal = null;
294
+ }
295
+
296
+ // Return focus to trigger
297
+ instance.trigger.focus();
298
+
299
+ // Reset typeahead
300
+ instance.typeahead.reset();
301
+ }
302
+
303
+ function highlightItem(root: HTMLElement, index: number) {
304
+ const instance = instances.get(root);
305
+ if (!instance) return;
306
+ if (index < 0 || index >= instance.items.length) return;
307
+
308
+ // Clear previous highlight
309
+ if (instance.highlightedIndex >= 0) {
310
+ instance.items[instance.highlightedIndex].removeAttribute(
311
+ "data-highlighted",
312
+ );
313
+ }
314
+
315
+ // Set new highlight
316
+ instance.highlightedIndex = index;
317
+ const item = instance.items[index];
318
+ item.setAttribute("data-highlighted", "");
319
+
320
+ // Virtual focus — tell screen readers which item is active
321
+ instance.trigger.setAttribute("aria-activedescendant", item.id);
322
+
323
+ // Scroll into view within the dropdown container only
324
+ const container = instance.content;
325
+ const itemTop = item.offsetTop;
326
+ const itemBottom = itemTop + item.offsetHeight;
327
+ if (itemTop < container.scrollTop) {
328
+ container.scrollTop = itemTop;
329
+ } else if (itemBottom > container.scrollTop + container.clientHeight) {
330
+ container.scrollTop = itemBottom - container.clientHeight;
331
+ }
332
+ }
333
+
334
+ function selectItem(root: HTMLElement, item: HTMLElement) {
335
+ const instance = instances.get(root);
336
+ if (!instance) return;
337
+ if (item.getAttribute("aria-disabled") === "true") return;
338
+
339
+ const value = item.dataset.value || "";
340
+ instance.value = value;
341
+
342
+ // Update aria-selected on all items
343
+ instance.items.forEach((i) => {
344
+ i.setAttribute("aria-selected", i === item ? "true" : "false");
345
+ });
346
+
347
+ // Update display value
348
+ if (instance.valueEl) {
349
+ instance.valueEl.textContent = item.textContent?.trim() || "";
350
+ instance.valueEl.removeAttribute("data-placeholder");
351
+ }
352
+
353
+ // Update hidden input
354
+ if (instance.hiddenInput) {
355
+ instance.hiddenInput.value = value;
356
+ }
357
+
358
+ // Dispatch change event
359
+ root.dispatchEvent(
360
+ new CustomEvent("base-select:change", {
361
+ detail: { value },
362
+ bubbles: true,
363
+ }),
364
+ );
365
+ }
366
+
367
+ // Auto-initialize
368
+ function init() {
369
+ document.querySelectorAll("[data-component='select']").forEach((el) => {
370
+ initSelect(el as HTMLElement);
371
+ });
372
+ }
373
+
374
+ if (document.readyState === "loading") {
375
+ document.addEventListener("DOMContentLoaded", init);
376
+ } else {
377
+ init();
378
+ }
379
+ document.addEventListener("astro:page-load", init);
380
+
381
+ export { initSelect, openSelect, closeSelect };
@@ -0,0 +1,35 @@
1
+ ---
2
+ import { tv, type VariantProps } from "tailwind-variants";
3
+
4
+ const separator = tv({
5
+ base: "shrink-0 bg-border",
6
+ variants: {
7
+ orientation: {
8
+ horizontal: "h-[1px] w-full",
9
+ vertical: "w-[1px] self-stretch",
10
+ },
11
+ },
12
+ defaultVariants: {
13
+ orientation: "horizontal",
14
+ },
15
+ });
16
+
17
+ type SeparatorVariants = VariantProps<typeof separator>;
18
+
19
+ interface Props {
20
+ orientation?: SeparatorVariants["orientation"];
21
+ class?: string;
22
+ [key: string]: unknown;
23
+ }
24
+
25
+ const { orientation = "horizontal", class: className, ...rest } = Astro.props;
26
+ ---
27
+
28
+ <div
29
+ class={separator({ orientation, class: className })}
30
+ role="separator"
31
+ aria-orientation={orientation}
32
+ data-orientation={orientation}
33
+ {...rest}
34
+ >
35
+ </div>
@@ -0,0 +1 @@
1
+ export { default as Separator } from "./Separator.astro";
@@ -0,0 +1,12 @@
1
+ ---
2
+ interface Props {
3
+ class?: string;
4
+ [key: string]: unknown;
5
+ }
6
+
7
+ const { class: className, ...rest } = Astro.props;
8
+ ---
9
+
10
+ <div class={className} data-component="sheet" {...rest}>
11
+ <slot />
12
+ </div>
@@ -0,0 +1,35 @@
1
+ ---
2
+ import { tv } from "tailwind-variants";
3
+
4
+ const sheetClose = tv({
5
+ base: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
6
+ });
7
+
8
+ interface Props {
9
+ asChild?: boolean;
10
+ class?: string;
11
+ [key: string]: unknown;
12
+ }
13
+
14
+ const { asChild = false, class: className, ...rest } = Astro.props;
15
+ ---
16
+
17
+ {
18
+ asChild ? (
19
+ <span data-part="close" data-as-child>
20
+ <slot />
21
+ </span>
22
+ ) : (
23
+ <button
24
+ class={sheetClose({ class: className })}
25
+ data-part="close"
26
+ type="button"
27
+ aria-label="Close"
28
+ {...rest}
29
+ >
30
+ <slot>
31
+ <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" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
32
+ </slot>
33
+ </button>
34
+ )
35
+ }
@@ -0,0 +1,52 @@
1
+ ---
2
+ import { tv, type VariantProps } from "tailwind-variants";
3
+
4
+ const sheetBackdrop = tv({
5
+ base: "fixed inset-0 z-50 bg-black/80",
6
+ });
7
+
8
+ const sheetContent = tv({
9
+ base: "fixed z-[51] gap-4 bg-background p-6 shadow-lg transition-transform",
10
+ variants: {
11
+ side: {
12
+ top: "inset-x-0 top-0 border-b",
13
+ bottom: "inset-x-0 bottom-0 border-t",
14
+ left: "inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
15
+ right: "inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ side: "right",
20
+ },
21
+ });
22
+
23
+ type SheetContentVariants = VariantProps<typeof sheetContent>;
24
+
25
+ interface Props {
26
+ side?: SheetContentVariants["side"];
27
+ class?: string;
28
+ [key: string]: unknown;
29
+ }
30
+
31
+ const { side = "right", class: className, ...rest } = Astro.props;
32
+ ---
33
+
34
+ <div
35
+ class={sheetBackdrop()}
36
+ data-part="backdrop"
37
+ data-state="closed"
38
+ hidden
39
+ >
40
+ </div>
41
+ <div
42
+ class={sheetContent({ side, class: className })}
43
+ data-part="content"
44
+ data-side={side}
45
+ role="dialog"
46
+ aria-modal="true"
47
+ data-state="closed"
48
+ hidden
49
+ {...rest}
50
+ >
51
+ <slot />
52
+ </div>
@@ -0,0 +1,18 @@
1
+ ---
2
+ import { tv } from "tailwind-variants";
3
+
4
+ const sheetDescription = tv({
5
+ base: "text-sm text-muted-foreground",
6
+ });
7
+
8
+ interface Props {
9
+ class?: string;
10
+ [key: string]: unknown;
11
+ }
12
+
13
+ const { class: className, ...rest } = Astro.props;
14
+ ---
15
+
16
+ <p class={sheetDescription({ class: className })} data-part="description" {...rest}>
17
+ <slot />
18
+ </p>
@@ -0,0 +1,18 @@
1
+ ---
2
+ import { tv } from "tailwind-variants";
3
+
4
+ const sheetFooter = tv({
5
+ base: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
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 class={sheetFooter({ class: className })} {...rest}>
17
+ <slot />
18
+ </div>
@@ -0,0 +1,18 @@
1
+ ---
2
+ import { tv } from "tailwind-variants";
3
+
4
+ const sheetHeader = tv({
5
+ base: "flex flex-col space-y-2 text-center sm:text-left",
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 class={sheetHeader({ class: className })} {...rest}>
17
+ <slot />
18
+ </div>
@@ -0,0 +1,18 @@
1
+ ---
2
+ import { tv } from "tailwind-variants";
3
+
4
+ const sheetTitle = tv({
5
+ base: "text-lg font-semibold text-foreground",
6
+ });
7
+
8
+ interface Props {
9
+ class?: string;
10
+ [key: string]: unknown;
11
+ }
12
+
13
+ const { class: className, ...rest } = Astro.props;
14
+ ---
15
+
16
+ <h2 class={sheetTitle({ class: className })} data-part="title" {...rest}>
17
+ <slot />
18
+ </h2>
@@ -0,0 +1,32 @@
1
+ ---
2
+ import { tv } from "tailwind-variants";
3
+
4
+ const sheetTrigger = tv({
5
+ base: "inline-flex items-center justify-center",
6
+ });
7
+
8
+ interface Props {
9
+ asChild?: boolean;
10
+ class?: string;
11
+ [key: string]: unknown;
12
+ }
13
+
14
+ const { asChild = false, class: className, ...rest } = Astro.props;
15
+ ---
16
+
17
+ {
18
+ asChild ? (
19
+ <span data-part="trigger" data-as-child>
20
+ <slot />
21
+ </span>
22
+ ) : (
23
+ <button
24
+ class={sheetTrigger({ class: className })}
25
+ data-part="trigger"
26
+ type="button"
27
+ {...rest}
28
+ >
29
+ <slot />
30
+ </button>
31
+ )
32
+ }
@@ -0,0 +1,8 @@
1
+ export { default as Sheet } from "./Sheet.astro";
2
+ export { default as SheetTrigger } from "./SheetTrigger.astro";
3
+ export { default as SheetContent } from "./SheetContent.astro";
4
+ export { default as SheetHeader } from "./SheetHeader.astro";
5
+ export { default as SheetFooter } from "./SheetFooter.astro";
6
+ export { default as SheetTitle } from "./SheetTitle.astro";
7
+ export { default as SheetDescription } from "./SheetDescription.astro";
8
+ export { default as SheetClose } from "./SheetClose.astro";