@mkbabb/glass-ui 0.2.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 (335) hide show
  1. package/README.md +172 -0
  2. package/dist/glass-ui.css +1 -0
  3. package/dist/glass-ui.js +10019 -0
  4. package/dist/index.d.ts +6619 -0
  5. package/package.json +65 -0
  6. package/src/components/custom/aurora/Aurora.vue +34 -0
  7. package/src/components/custom/aurora/composables/color.ts +122 -0
  8. package/src/components/custom/aurora/composables/useAurora.ts +355 -0
  9. package/src/components/custom/aurora/index.ts +8 -0
  10. package/src/components/custom/confirm-dialog/ConfirmDialog.vue +88 -0
  11. package/src/components/custom/confirm-dialog/index.ts +1 -0
  12. package/src/components/custom/controls/DarkModeToggle.vue +96 -0
  13. package/src/components/custom/controls/index.ts +1 -0
  14. package/src/components/custom/dock/DockLayerGroup.vue +21 -0
  15. package/src/components/custom/dock/DockPopover.vue +263 -0
  16. package/src/components/custom/dock/GlassDock.vue +276 -0
  17. package/src/components/custom/dock/composables/index.ts +16 -0
  18. package/src/components/custom/dock/composables/isTeleportedTarget.ts +19 -0
  19. package/src/components/custom/dock/composables/useDockActionBar.ts +33 -0
  20. package/src/components/custom/dock/composables/useDockState.ts +301 -0
  21. package/src/components/custom/dock/composables/useDockTransition.ts +146 -0
  22. package/src/components/custom/dock/composables/useLayerTransition.ts +135 -0
  23. package/src/components/custom/dock/composables/usePopupMutex.ts +83 -0
  24. package/src/components/custom/dock/index.ts +9 -0
  25. package/src/components/custom/expandable-container/ExpandableContainer.vue +64 -0
  26. package/src/components/custom/expandable-container/index.ts +1 -0
  27. package/src/components/custom/glass-panel/GlassPanel.vue +98 -0
  28. package/src/components/custom/glass-panel/index.ts +2 -0
  29. package/src/components/custom/icon-tooltip/IconTooltip.vue +20 -0
  30. package/src/components/custom/icon-tooltip/index.ts +1 -0
  31. package/src/components/custom/index.ts +15 -0
  32. package/src/components/custom/infinite-scroll/InfiniteScroll.vue +55 -0
  33. package/src/components/custom/infinite-scroll/composables/index.ts +2 -0
  34. package/src/components/custom/infinite-scroll/composables/types.ts +23 -0
  35. package/src/components/custom/infinite-scroll/composables/useInfiniteScroll.ts +73 -0
  36. package/src/components/custom/infinite-scroll/index.ts +1 -0
  37. package/src/components/custom/labeled-field/LabeledInput.vue +29 -0
  38. package/src/components/custom/labeled-field/LabeledSelect.vue +59 -0
  39. package/src/components/custom/labeled-field/LabeledSlider.vue +32 -0
  40. package/src/components/custom/labeled-field/LabeledSwitch.vue +27 -0
  41. package/src/components/custom/labeled-field/index.ts +4 -0
  42. package/src/components/custom/metaballs/MetaballCanvas.vue +23 -0
  43. package/src/components/custom/metaballs/index.ts +4 -0
  44. package/src/components/custom/metaballs/shaders.ts +63 -0
  45. package/src/components/custom/metaballs/types.ts +29 -0
  46. package/src/components/custom/metaballs/useMetaballs.ts +252 -0
  47. package/src/components/custom/search/FuzzySearch.vue +589 -0
  48. package/src/components/custom/search/SearchBar.vue +44 -0
  49. package/src/components/custom/search/composables/fuzzySearchIndex.ts +224 -0
  50. package/src/components/custom/search/composables/index.ts +5 -0
  51. package/src/components/custom/search/composables/types.ts +34 -0
  52. package/src/components/custom/search/composables/useFuzzySearch.ts +115 -0
  53. package/src/components/custom/search/index.ts +7 -0
  54. package/src/components/custom/sidebar/ProgressiveSidebar.vue +256 -0
  55. package/src/components/custom/sidebar/composables/index.ts +6 -0
  56. package/src/components/custom/sidebar/composables/useScrollTracker.ts +242 -0
  57. package/src/components/custom/sidebar/composables/useSidebarFollow.ts +247 -0
  58. package/src/components/custom/sidebar/composables/useSidebarState.ts +72 -0
  59. package/src/components/custom/sidebar/composables/useTreeIndex.ts +152 -0
  60. package/src/components/custom/sidebar/index.ts +15 -0
  61. package/src/components/custom/sidebar/types.ts +50 -0
  62. package/src/components/custom/tabs/BouncyTabs.vue +39 -0
  63. package/src/components/custom/tabs/BouncyToggle.vue +352 -0
  64. package/src/components/custom/tabs/UnderlineTabs.vue +115 -0
  65. package/src/components/custom/tabs/index.ts +5 -0
  66. package/src/components/custom/timeline/GlassTimeline.vue +174 -0
  67. package/src/components/custom/timeline/index.ts +1 -0
  68. package/src/components/custom/typewriter/TypewriterText.vue +239 -0
  69. package/src/components/custom/typewriter/composables/index.ts +1 -0
  70. package/src/components/custom/typewriter/composables/useTypewriter.ts +413 -0
  71. package/src/components/custom/typewriter/index.ts +7 -0
  72. package/src/components/custom/typewriter/types.ts +159 -0
  73. package/src/components/custom/typewriter/utils/keyboard.ts +213 -0
  74. package/src/components/custom/typewriter/utils/pausePatterns.ts +55 -0
  75. package/src/components/custom/typewriter/utils/timing.ts +104 -0
  76. package/src/components/custom/typewriter/utils/typoStateMachine.ts +197 -0
  77. package/src/components/index.ts +2 -0
  78. package/src/components/ui/accordion/Accordion.vue +19 -0
  79. package/src/components/ui/accordion/AccordionContent.vue +24 -0
  80. package/src/components/ui/accordion/AccordionItem.vue +24 -0
  81. package/src/components/ui/accordion/AccordionTrigger.vue +39 -0
  82. package/src/components/ui/accordion/index.ts +4 -0
  83. package/src/components/ui/alert/Alert.vue +20 -0
  84. package/src/components/ui/alert/AlertDescription.vue +17 -0
  85. package/src/components/ui/alert/AlertTitle.vue +17 -0
  86. package/src/components/ui/alert/index.ts +23 -0
  87. package/src/components/ui/avatar/Avatar.vue +21 -0
  88. package/src/components/ui/avatar/AvatarFallback.vue +11 -0
  89. package/src/components/ui/avatar/AvatarImage.vue +9 -0
  90. package/src/components/ui/avatar/index.ts +24 -0
  91. package/src/components/ui/badge/Badge.vue +16 -0
  92. package/src/components/ui/badge/index.ts +25 -0
  93. package/src/components/ui/button/Button.vue +26 -0
  94. package/src/components/ui/button/index.ts +43 -0
  95. package/src/components/ui/card/Card.vue +28 -0
  96. package/src/components/ui/card/CardContent.vue +14 -0
  97. package/src/components/ui/card/CardDescription.vue +14 -0
  98. package/src/components/ui/card/CardFooter.vue +14 -0
  99. package/src/components/ui/card/CardHeader.vue +14 -0
  100. package/src/components/ui/card/CardTitle.vue +21 -0
  101. package/src/components/ui/card/index.ts +6 -0
  102. package/src/components/ui/carousel/Carousel.vue +53 -0
  103. package/src/components/ui/carousel/CarouselContent.vue +35 -0
  104. package/src/components/ui/carousel/CarouselItem.vue +24 -0
  105. package/src/components/ui/carousel/CarouselNext.vue +40 -0
  106. package/src/components/ui/carousel/CarouselPrevious.vue +40 -0
  107. package/src/components/ui/carousel/index.ts +10 -0
  108. package/src/components/ui/carousel/interface.ts +26 -0
  109. package/src/components/ui/carousel/useCarousel.ts +56 -0
  110. package/src/components/ui/checkbox/Checkbox.vue +33 -0
  111. package/src/components/ui/checkbox/index.ts +1 -0
  112. package/src/components/ui/collapsible/Collapsible.vue +15 -0
  113. package/src/components/ui/collapsible/CollapsibleContent.vue +11 -0
  114. package/src/components/ui/collapsible/CollapsibleTrigger.vue +11 -0
  115. package/src/components/ui/collapsible/index.ts +3 -0
  116. package/src/components/ui/combobox/Combobox.vue +17 -0
  117. package/src/components/ui/combobox/ComboboxAnchor.vue +23 -0
  118. package/src/components/ui/combobox/ComboboxEmpty.vue +21 -0
  119. package/src/components/ui/combobox/ComboboxGroup.vue +27 -0
  120. package/src/components/ui/combobox/ComboboxInput.vue +41 -0
  121. package/src/components/ui/combobox/ComboboxItem.vue +24 -0
  122. package/src/components/ui/combobox/ComboboxItemIndicator.vue +23 -0
  123. package/src/components/ui/combobox/ComboboxList.vue +29 -0
  124. package/src/components/ui/combobox/ComboboxSeparator.vue +21 -0
  125. package/src/components/ui/combobox/ComboboxViewport.vue +23 -0
  126. package/src/components/ui/combobox/index.ts +12 -0
  127. package/src/components/ui/command/Command.vue +30 -0
  128. package/src/components/ui/command/CommandDialog.vue +21 -0
  129. package/src/components/ui/command/CommandEmpty.vue +20 -0
  130. package/src/components/ui/command/CommandGroup.vue +29 -0
  131. package/src/components/ui/command/CommandInput.vue +33 -0
  132. package/src/components/ui/command/CommandItem.vue +26 -0
  133. package/src/components/ui/command/CommandList.vue +27 -0
  134. package/src/components/ui/command/CommandSeparator.vue +23 -0
  135. package/src/components/ui/command/CommandShortcut.vue +14 -0
  136. package/src/components/ui/command/index.ts +9 -0
  137. package/src/components/ui/context-menu/ContextMenu.vue +15 -0
  138. package/src/components/ui/context-menu/ContextMenuCheckboxItem.vue +40 -0
  139. package/src/components/ui/context-menu/ContextMenuContent.vue +36 -0
  140. package/src/components/ui/context-menu/ContextMenuGroup.vue +11 -0
  141. package/src/components/ui/context-menu/ContextMenuItem.vue +34 -0
  142. package/src/components/ui/context-menu/ContextMenuLabel.vue +25 -0
  143. package/src/components/ui/context-menu/ContextMenuPortal.vue +11 -0
  144. package/src/components/ui/context-menu/ContextMenuRadioGroup.vue +19 -0
  145. package/src/components/ui/context-menu/ContextMenuRadioItem.vue +40 -0
  146. package/src/components/ui/context-menu/ContextMenuSeparator.vue +20 -0
  147. package/src/components/ui/context-menu/ContextMenuShortcut.vue +14 -0
  148. package/src/components/ui/context-menu/ContextMenuSub.vue +19 -0
  149. package/src/components/ui/context-menu/ContextMenuSubContent.vue +35 -0
  150. package/src/components/ui/context-menu/ContextMenuSubTrigger.vue +34 -0
  151. package/src/components/ui/context-menu/ContextMenuTrigger.vue +13 -0
  152. package/src/components/ui/context-menu/index.ts +14 -0
  153. package/src/components/ui/data-table/DataTable.vue +167 -0
  154. package/src/components/ui/data-table/DataTablePagination.vue +112 -0
  155. package/src/components/ui/data-table/index.ts +3 -0
  156. package/src/components/ui/data-table/types.ts +48 -0
  157. package/src/components/ui/dialog/Dialog.vue +14 -0
  158. package/src/components/ui/dialog/DialogClose.vue +11 -0
  159. package/src/components/ui/dialog/DialogContent.vue +61 -0
  160. package/src/components/ui/dialog/DialogDescription.vue +24 -0
  161. package/src/components/ui/dialog/DialogFooter.vue +19 -0
  162. package/src/components/ui/dialog/DialogHeader.vue +16 -0
  163. package/src/components/ui/dialog/DialogScrollContent.vue +65 -0
  164. package/src/components/ui/dialog/DialogTitle.vue +29 -0
  165. package/src/components/ui/dialog/DialogTrigger.vue +11 -0
  166. package/src/components/ui/dialog/index.ts +9 -0
  167. package/src/components/ui/drawer/Drawer.vue +19 -0
  168. package/src/components/ui/drawer/DrawerContent.vue +28 -0
  169. package/src/components/ui/drawer/DrawerDescription.vue +20 -0
  170. package/src/components/ui/drawer/DrawerFooter.vue +14 -0
  171. package/src/components/ui/drawer/DrawerHeader.vue +14 -0
  172. package/src/components/ui/drawer/DrawerOverlay.vue +18 -0
  173. package/src/components/ui/drawer/DrawerTitle.vue +20 -0
  174. package/src/components/ui/drawer/index.ts +8 -0
  175. package/src/components/ui/dropdown-menu/DropdownMenu.vue +14 -0
  176. package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +40 -0
  177. package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +44 -0
  178. package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +11 -0
  179. package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +28 -0
  180. package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +24 -0
  181. package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +19 -0
  182. package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +40 -0
  183. package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +22 -0
  184. package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +14 -0
  185. package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
  186. package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +36 -0
  187. package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +33 -0
  188. package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +13 -0
  189. package/src/components/ui/dropdown-menu/index.ts +16 -0
  190. package/src/components/ui/hover-card/HoverCard.vue +14 -0
  191. package/src/components/ui/hover-card/HoverCardContent.vue +41 -0
  192. package/src/components/ui/hover-card/HoverCardTrigger.vue +11 -0
  193. package/src/components/ui/hover-card/index.ts +3 -0
  194. package/src/components/ui/index.ts +41 -0
  195. package/src/components/ui/input/Input.vue +24 -0
  196. package/src/components/ui/input/index.ts +1 -0
  197. package/src/components/ui/label/Label.vue +27 -0
  198. package/src/components/ui/label/index.ts +1 -0
  199. package/src/components/ui/multi-select/MultiSelect.vue +141 -0
  200. package/src/components/ui/multi-select/index.ts +7 -0
  201. package/src/components/ui/notification/Notification.vue +85 -0
  202. package/src/components/ui/notification/index.ts +1 -0
  203. package/src/components/ui/number-field/NumberField.vue +23 -0
  204. package/src/components/ui/number-field/NumberFieldContent.vue +14 -0
  205. package/src/components/ui/number-field/NumberFieldDecrement.vue +25 -0
  206. package/src/components/ui/number-field/NumberFieldIncrement.vue +25 -0
  207. package/src/components/ui/number-field/NumberFieldInput.vue +8 -0
  208. package/src/components/ui/number-field/index.ts +5 -0
  209. package/src/components/ui/popover/Popover.vue +15 -0
  210. package/src/components/ui/popover/PopoverContent.vue +61 -0
  211. package/src/components/ui/popover/PopoverTrigger.vue +11 -0
  212. package/src/components/ui/popover/index.ts +3 -0
  213. package/src/components/ui/progress/Progress.vue +39 -0
  214. package/src/components/ui/progress/index.ts +1 -0
  215. package/src/components/ui/radio-group/RadioGroup.vue +25 -0
  216. package/src/components/ui/radio-group/RadioGroupItem.vue +39 -0
  217. package/src/components/ui/radio-group/index.ts +2 -0
  218. package/src/components/ui/scroll-area/ScrollArea.vue +29 -0
  219. package/src/components/ui/scroll-area/ScrollBar.vue +30 -0
  220. package/src/components/ui/scroll-area/index.ts +2 -0
  221. package/src/components/ui/scroll-pane/ScrollPane.vue +25 -0
  222. package/src/components/ui/scroll-pane/ScrollPaneHeader.vue +75 -0
  223. package/src/components/ui/scroll-pane/index.ts +2 -0
  224. package/src/components/ui/select/Select.vue +15 -0
  225. package/src/components/ui/select/SelectContent.vue +57 -0
  226. package/src/components/ui/select/SelectGroup.vue +19 -0
  227. package/src/components/ui/select/SelectItem.vue +47 -0
  228. package/src/components/ui/select/SelectItemText.vue +11 -0
  229. package/src/components/ui/select/SelectLabel.vue +13 -0
  230. package/src/components/ui/select/SelectScrollDownButton.vue +24 -0
  231. package/src/components/ui/select/SelectScrollUpButton.vue +24 -0
  232. package/src/components/ui/select/SelectSeparator.vue +17 -0
  233. package/src/components/ui/select/SelectTrigger.vue +45 -0
  234. package/src/components/ui/select/SelectValue.vue +11 -0
  235. package/src/components/ui/select/index.ts +11 -0
  236. package/src/components/ui/separator/Separator.vue +35 -0
  237. package/src/components/ui/separator/index.ts +1 -0
  238. package/src/components/ui/sheet/Sheet.vue +14 -0
  239. package/src/components/ui/sheet/SheetClose.vue +11 -0
  240. package/src/components/ui/sheet/SheetContent.vue +56 -0
  241. package/src/components/ui/sheet/SheetDescription.vue +22 -0
  242. package/src/components/ui/sheet/SheetFooter.vue +19 -0
  243. package/src/components/ui/sheet/SheetHeader.vue +16 -0
  244. package/src/components/ui/sheet/SheetTitle.vue +22 -0
  245. package/src/components/ui/sheet/SheetTrigger.vue +11 -0
  246. package/src/components/ui/sheet/index.ts +31 -0
  247. package/src/components/ui/skeleton/Skeleton.vue +14 -0
  248. package/src/components/ui/skeleton/index.ts +1 -0
  249. package/src/components/ui/slider/Slider.vue +66 -0
  250. package/src/components/ui/slider/index.ts +1 -0
  251. package/src/components/ui/switch/Switch.vue +37 -0
  252. package/src/components/ui/switch/index.ts +1 -0
  253. package/src/components/ui/table/Table.vue +16 -0
  254. package/src/components/ui/table/TableBody.vue +14 -0
  255. package/src/components/ui/table/TableCaption.vue +14 -0
  256. package/src/components/ui/table/TableCell.vue +14 -0
  257. package/src/components/ui/table/TableEmpty.vue +39 -0
  258. package/src/components/ui/table/TableFooter.vue +16 -0
  259. package/src/components/ui/table/TableHead.vue +21 -0
  260. package/src/components/ui/table/TableHeader.vue +14 -0
  261. package/src/components/ui/table/TableRow.vue +21 -0
  262. package/src/components/ui/table/index.ts +9 -0
  263. package/src/components/ui/tabs/Tabs.vue +15 -0
  264. package/src/components/ui/tabs/TabsContent.vue +22 -0
  265. package/src/components/ui/tabs/TabsIndicator.vue +22 -0
  266. package/src/components/ui/tabs/TabsList.vue +25 -0
  267. package/src/components/ui/tabs/TabsTrigger.vue +29 -0
  268. package/src/components/ui/tabs/index.ts +5 -0
  269. package/src/components/ui/tags-input/TagsInput.vue +22 -0
  270. package/src/components/ui/tags-input/TagsInputInput.vue +19 -0
  271. package/src/components/ui/tags-input/TagsInputItem.vue +22 -0
  272. package/src/components/ui/tags-input/TagsInputItemDelete.vue +24 -0
  273. package/src/components/ui/tags-input/TagsInputItemText.vue +19 -0
  274. package/src/components/ui/tags-input/index.ts +5 -0
  275. package/src/components/ui/textarea/Textarea.vue +24 -0
  276. package/src/components/ui/textarea/index.ts +1 -0
  277. package/src/components/ui/toast/Toast.vue +57 -0
  278. package/src/components/ui/toast/ToastAction.vue +30 -0
  279. package/src/components/ui/toast/ToastClose.vue +31 -0
  280. package/src/components/ui/toast/ToastDescription.vue +25 -0
  281. package/src/components/ui/toast/ToastTitle.vue +25 -0
  282. package/src/components/ui/toast/Toaster.vue +31 -0
  283. package/src/components/ui/toast/index.ts +8 -0
  284. package/src/components/ui/toast/use-toast.ts +136 -0
  285. package/src/components/ui/toggle/Toggle.vue +35 -0
  286. package/src/components/ui/toggle/index.ts +27 -0
  287. package/src/components/ui/toggle-group/ToggleGroup.vue +34 -0
  288. package/src/components/ui/toggle-group/ToggleGroupItem.vue +35 -0
  289. package/src/components/ui/toggle-group/index.ts +2 -0
  290. package/src/components/ui/tooltip/Tooltip.vue +14 -0
  291. package/src/components/ui/tooltip/TooltipContent.vue +31 -0
  292. package/src/components/ui/tooltip/TooltipProvider.vue +11 -0
  293. package/src/components/ui/tooltip/TooltipTrigger.vue +11 -0
  294. package/src/components/ui/tooltip/index.ts +4 -0
  295. package/src/composables/glass/index.ts +8 -0
  296. package/src/composables/glass/useGlassRenderer.ts +252 -0
  297. package/src/composables/glass/webgl/frostShader.ts +221 -0
  298. package/src/composables/glass/webgpu/glassShader.wgsl +173 -0
  299. package/src/composables/index.ts +32 -0
  300. package/src/composables/infinite-scroll/index.ts +2 -0
  301. package/src/composables/infinite-scroll/types.ts +25 -0
  302. package/src/composables/infinite-scroll/useInfiniteScroll.ts +101 -0
  303. package/src/composables/interaction/index.ts +5 -0
  304. package/src/composables/interaction/useHeightTransition.ts +82 -0
  305. package/src/composables/interaction/useHoverPopover.ts +64 -0
  306. package/src/composables/interaction/useHoverToggle.ts +103 -0
  307. package/src/composables/interaction/useLeaveTimer.ts +17 -0
  308. package/src/composables/interaction/useTouchGate.ts +207 -0
  309. package/src/composables/pagination/index.ts +2 -0
  310. package/src/composables/pagination/useOffsetPagination.ts +70 -0
  311. package/src/composables/prng.ts +32 -0
  312. package/src/composables/useCharSplit.ts +31 -0
  313. package/src/composables/useClipboard.ts +46 -0
  314. package/src/composables/useGlobalDark.ts +61 -0
  315. package/src/composables/useKeyboardShortcuts.ts +205 -0
  316. package/src/composables/useWatercolorBlob.ts +136 -0
  317. package/src/composables/virtual/index.ts +22 -0
  318. package/src/composables/virtual/useVirtualSectionWindow.ts +338 -0
  319. package/src/composables/virtual/useWindowedStore.ts +86 -0
  320. package/src/composables/virtual/virtualSectionLayout.ts +212 -0
  321. package/src/index.ts +9 -0
  322. package/src/styles/animations.css +233 -0
  323. package/src/styles/cards.css +66 -0
  324. package/src/styles/dock.css +221 -0
  325. package/src/styles/floating-panel.css +49 -0
  326. package/src/styles/glass.css +266 -0
  327. package/src/styles/index.css +26 -0
  328. package/src/styles/scroll-pane.css +10 -0
  329. package/src/styles/theme.css +138 -0
  330. package/src/styles/tokens.css +333 -0
  331. package/src/styles/transitions.css +226 -0
  332. package/src/styles/typography.css +277 -0
  333. package/src/styles/utilities.css +697 -0
  334. package/src/utils/cn.ts +6 -0
  335. package/src/utils/index.ts +1 -0
@@ -0,0 +1,213 @@
1
+ // --- Key position types and QWERTY map ---
2
+
3
+ export interface KeyPosition {
4
+ row: number;
5
+ col: number;
6
+ finger: "pinky" | "ring" | "middle" | "index" | "thumb";
7
+ hand: "left" | "right";
8
+ }
9
+
10
+ export const QWERTY_MAP: Record<string, KeyPosition> = {
11
+ // Row 0 - Numbers row
12
+ "1": { row: 0, col: 1, finger: "pinky", hand: "left" },
13
+ "2": { row: 0, col: 2, finger: "ring", hand: "left" },
14
+ "3": { row: 0, col: 3, finger: "middle", hand: "left" },
15
+ "4": { row: 0, col: 4, finger: "index", hand: "left" },
16
+ "5": { row: 0, col: 5, finger: "index", hand: "left" },
17
+ "6": { row: 0, col: 6, finger: "index", hand: "right" },
18
+ "7": { row: 0, col: 7, finger: "index", hand: "right" },
19
+ "8": { row: 0, col: 8, finger: "middle", hand: "right" },
20
+ "9": { row: 0, col: 9, finger: "ring", hand: "right" },
21
+ "0": { row: 0, col: 10, finger: "pinky", hand: "right" },
22
+
23
+ // Row 1 - QWERTY row
24
+ q: { row: 1, col: 1, finger: "pinky", hand: "left" },
25
+ w: { row: 1, col: 2, finger: "ring", hand: "left" },
26
+ e: { row: 1, col: 3, finger: "middle", hand: "left" },
27
+ r: { row: 1, col: 4, finger: "index", hand: "left" },
28
+ t: { row: 1, col: 5, finger: "index", hand: "left" },
29
+ y: { row: 1, col: 6, finger: "index", hand: "right" },
30
+ u: { row: 1, col: 7, finger: "index", hand: "right" },
31
+ i: { row: 1, col: 8, finger: "middle", hand: "right" },
32
+ o: { row: 1, col: 9, finger: "ring", hand: "right" },
33
+ p: { row: 1, col: 10, finger: "pinky", hand: "right" },
34
+
35
+ // Row 2 - ASDF row (home row)
36
+ a: { row: 2, col: 1, finger: "pinky", hand: "left" },
37
+ s: { row: 2, col: 2, finger: "ring", hand: "left" },
38
+ d: { row: 2, col: 3, finger: "middle", hand: "left" },
39
+ f: { row: 2, col: 4, finger: "index", hand: "left" },
40
+ g: { row: 2, col: 5, finger: "index", hand: "left" },
41
+ h: { row: 2, col: 6, finger: "index", hand: "right" },
42
+ j: { row: 2, col: 7, finger: "index", hand: "right" },
43
+ k: { row: 2, col: 8, finger: "middle", hand: "right" },
44
+ l: { row: 2, col: 9, finger: "ring", hand: "right" },
45
+ ";": { row: 2, col: 10, finger: "pinky", hand: "right" },
46
+
47
+ // Row 3 - ZXCV row
48
+ z: { row: 3, col: 1, finger: "pinky", hand: "left" },
49
+ x: { row: 3, col: 2, finger: "ring", hand: "left" },
50
+ c: { row: 3, col: 3, finger: "middle", hand: "left" },
51
+ v: { row: 3, col: 4, finger: "index", hand: "left" },
52
+ b: { row: 3, col: 5, finger: "index", hand: "left" },
53
+ n: { row: 3, col: 6, finger: "index", hand: "right" },
54
+ m: { row: 3, col: 7, finger: "index", hand: "right" },
55
+ ",": { row: 3, col: 8, finger: "middle", hand: "right" },
56
+ ".": { row: 3, col: 9, finger: "ring", hand: "right" },
57
+ "/": { row: 3, col: 10, finger: "pinky", hand: "right" },
58
+
59
+ // Space bar
60
+ " ": { row: 4, col: 5, finger: "thumb", hand: "right" },
61
+
62
+ // Common punctuation
63
+ "'": { row: 2, col: 11, finger: "pinky", hand: "right" },
64
+ '"': { row: 2, col: 11, finger: "pinky", hand: "right" },
65
+ "?": { row: 3, col: 10, finger: "pinky", hand: "right" },
66
+ "!": { row: 0, col: 1, finger: "pinky", hand: "left" },
67
+ "-": { row: 0, col: 11, finger: "pinky", hand: "right" },
68
+ "=": { row: 0, col: 12, finger: "pinky", hand: "right" },
69
+ };
70
+
71
+ // --- Key delay calculation ---
72
+
73
+ export const calculateKeyDelay = (prevChar: string, nextChar: string): number => {
74
+ const prev = QWERTY_MAP[prevChar.toLowerCase()];
75
+ const next = QWERTY_MAP[nextChar.toLowerCase()];
76
+
77
+ if (!prev || !next) return 250;
78
+
79
+ // Fastest: alternating hands
80
+ if (prev.hand !== next.hand) {
81
+ return 150 + Math.random() * 50;
82
+ }
83
+
84
+ // Same key pressed twice
85
+ if (prev.row === next.row && prev.col === next.col) {
86
+ return 350 + Math.random() * 100;
87
+ }
88
+
89
+ // Adjacent fingers, same hand
90
+ const colDiff = Math.abs(prev.col - next.col);
91
+ if (colDiff === 1 && prev.row === next.row) {
92
+ return 180 + Math.random() * 40;
93
+ }
94
+
95
+ // Home row combinations
96
+ if (prev.row === 2 && next.row === 2) {
97
+ return 200 + Math.random() * 50;
98
+ }
99
+
100
+ // Reaching from home row
101
+ if ((prev.row === 2 && next.row !== 2) || (prev.row !== 2 && next.row === 2)) {
102
+ return 280 + Math.random() * 70;
103
+ }
104
+
105
+ // Bottom row with pinky/ring fingers
106
+ if (
107
+ (prev.row === 3 || next.row === 3) &&
108
+ (["pinky", "ring"].includes(prev.finger) || ["pinky", "ring"].includes(next.finger))
109
+ ) {
110
+ return 320 + Math.random() * 80;
111
+ }
112
+
113
+ // Large movements
114
+ const rowDiff = Math.abs(prev.row - next.row);
115
+ if (rowDiff > 1 || colDiff > 2) {
116
+ return 300 + Math.random() * 100;
117
+ }
118
+
119
+ // Default
120
+ return 250 + Math.random() * 50;
121
+ };
122
+
123
+ // --- Adjacency map for typo generation ---
124
+
125
+ /** Physical row stagger offsets (in key-width units) */
126
+ const ROW_STAGGER: Record<number, number> = {
127
+ 0: 0,
128
+ 1: 0.25,
129
+ 2: 0.5,
130
+ 3: 0.75,
131
+ 4: 0, // space bar row
132
+ };
133
+
134
+ /** Maximum distance for keys to be considered adjacent */
135
+ const MAX_DISTANCE = 1.2;
136
+ const MAX_ROW_DIFF = 1;
137
+
138
+ function physicalDistance(a: KeyPosition, b: KeyPosition): number {
139
+ const staggerA = ROW_STAGGER[a.row] ?? 0;
140
+ const staggerB = ROW_STAGGER[b.row] ?? 0;
141
+ const dx = a.col + staggerA - (b.col + staggerB);
142
+ const dy = a.row - b.row;
143
+ return Math.sqrt(dx * dx + dy * dy);
144
+ }
145
+
146
+ /** Precomputed adjacency: for each key char, the list of adjacent key chars */
147
+ const ADJACENCY_MAP: Record<string, string[]> = {};
148
+
149
+ // Build adjacency map at module load
150
+ const alphaKeys = Object.entries(QWERTY_MAP).filter(([ch]) => /^[a-z]$/.test(ch));
151
+ for (const [charA, posA] of alphaKeys) {
152
+ const neighbors: string[] = [];
153
+ for (const [charB, posB] of alphaKeys) {
154
+ if (charA === charB) continue;
155
+ if (Math.abs(posA.row - posB.row) > MAX_ROW_DIFF) continue;
156
+ if (physicalDistance(posA, posB) <= MAX_DISTANCE) {
157
+ neighbors.push(charB);
158
+ }
159
+ }
160
+ ADJACENCY_MAP[charA] = neighbors;
161
+ }
162
+
163
+ export function getAdjacentKeys(char: string): string[] {
164
+ return ADJACENCY_MAP[char.toLowerCase()] ?? [];
165
+ }
166
+
167
+ /**
168
+ * Pick a plausible typo character for the intended character.
169
+ * Weighted: same-finger 2x, same-hand 1.5x, other 1x.
170
+ * Preserves case. Falls back to home row for unmapped chars.
171
+ */
172
+ export function pickTypoChar(intendedChar: string): string {
173
+ const lower = intendedChar.toLowerCase();
174
+ const isUpper = intendedChar !== lower;
175
+ const intended = QWERTY_MAP[lower];
176
+ const adjacent = ADJACENCY_MAP[lower];
177
+
178
+ // Fallback: random home row key
179
+ if (!intended || !adjacent || adjacent.length === 0) {
180
+ const homeRow = "asdfghjkl";
181
+ const fallback = homeRow[Math.floor(Math.random() * homeRow.length)];
182
+ return isUpper ? fallback.toUpperCase() : fallback;
183
+ }
184
+
185
+ // Build weighted pool
186
+ const pool: { char: string; weight: number }[] = [];
187
+ for (const adj of adjacent) {
188
+ const pos = QWERTY_MAP[adj];
189
+ if (!pos) continue;
190
+
191
+ let weight = 1;
192
+ if (pos.finger === intended.finger) {
193
+ weight = 2;
194
+ } else if (pos.hand === intended.hand) {
195
+ weight = 1.5;
196
+ }
197
+ pool.push({ char: adj, weight });
198
+ }
199
+
200
+ // Weighted random selection
201
+ const totalWeight = pool.reduce((sum, p) => sum + p.weight, 0);
202
+ let roll = Math.random() * totalWeight;
203
+ for (const entry of pool) {
204
+ roll -= entry.weight;
205
+ if (roll <= 0) {
206
+ return isUpper ? entry.char.toUpperCase() : entry.char;
207
+ }
208
+ }
209
+
210
+ // Should not reach here, but fallback
211
+ const last = pool[pool.length - 1].char;
212
+ return isUpper ? last.toUpperCase() : last;
213
+ }
@@ -0,0 +1,55 @@
1
+ export interface PausePatterns {
2
+ sentenceEnd: { min: number; max: number };
3
+ commaBreak: { min: number; max: number };
4
+ wordBoundary: { min: number; max: number };
5
+ thinkingPause: { min: number; max: number };
6
+ colonPause: { min: number; max: number };
7
+ dashPause: { min: number; max: number };
8
+ }
9
+
10
+ export const PAUSE_PATTERNS: PausePatterns = {
11
+ sentenceEnd: { min: 400, max: 800 },
12
+ commaBreak: { min: 200, max: 400 },
13
+ wordBoundary: { min: 50, max: 150 },
14
+ thinkingPause: { min: 800, max: 2000 },
15
+ colonPause: { min: 300, max: 500 },
16
+ dashPause: { min: 250, max: 450 },
17
+ };
18
+
19
+ const random = (min: number, max: number): number => {
20
+ return Math.floor(Math.random() * (max - min + 1)) + min;
21
+ };
22
+
23
+ export const getPauseDelay = (char: string, nextChar: string, patterns: PausePatterns): number => {
24
+ // Sentence endings
25
+ if ([".", "!", "?"].includes(char) && nextChar === " ") {
26
+ return random(patterns.sentenceEnd.min, patterns.sentenceEnd.max);
27
+ }
28
+
29
+ // Comma breaks
30
+ if (char === "," && nextChar === " ") {
31
+ return random(patterns.commaBreak.min, patterns.commaBreak.max);
32
+ }
33
+
34
+ // Colon or semicolon pause
35
+ if ([":", ";"].includes(char) && nextChar === " ") {
36
+ return random(patterns.colonPause.min, patterns.colonPause.max);
37
+ }
38
+
39
+ // Dash pause
40
+ if (char === "-" && nextChar === " ") {
41
+ return random(patterns.dashPause.min, patterns.dashPause.max);
42
+ }
43
+
44
+ // Word boundaries (occasional micro-pauses)
45
+ if (char === " " && Math.random() < 0.3) {
46
+ return random(patterns.wordBoundary.min, patterns.wordBoundary.max);
47
+ }
48
+
49
+ // Occasional thinking pauses (5% chance)
50
+ if (Math.random() < 0.05) {
51
+ return random(patterns.thinkingPause.min, patterns.thinkingPause.max);
52
+ }
53
+
54
+ return 0;
55
+ };
@@ -0,0 +1,104 @@
1
+ import type { CancellationToken } from "../types";
2
+
3
+ // --- Cancellation ---
4
+
5
+ export function createCancellationToken(): CancellationToken {
6
+ const listeners = new Set<() => void>();
7
+ let _cancelled = false;
8
+
9
+ return {
10
+ get cancelled() {
11
+ return _cancelled;
12
+ },
13
+ cancel() {
14
+ if (_cancelled) return;
15
+ _cancelled = true;
16
+ for (const fn of listeners) fn();
17
+ listeners.clear();
18
+ },
19
+ onCancel(fn: () => void) {
20
+ if (_cancelled) {
21
+ fn();
22
+ } else {
23
+ listeners.add(fn);
24
+ }
25
+ },
26
+ offCancel(fn: () => void) {
27
+ listeners.delete(fn);
28
+ },
29
+ };
30
+ }
31
+
32
+ // --- Cancellable sleep ---
33
+
34
+ /** Resolves `true` if elapsed normally, `false` if cancelled. */
35
+ export function sleep(ms: number, token: CancellationToken): Promise<boolean> {
36
+ if (token.cancelled) return Promise.resolve(false);
37
+ if (ms <= 0) return Promise.resolve(true);
38
+
39
+ return new Promise<boolean>((resolve) => {
40
+ const timer = setTimeout(() => {
41
+ token.offCancel(onCancel);
42
+ resolve(true);
43
+ }, ms);
44
+
45
+ function onCancel() {
46
+ clearTimeout(timer);
47
+ resolve(false);
48
+ }
49
+
50
+ token.onCancel(onCancel);
51
+ });
52
+ }
53
+
54
+ // --- Stochastic delays ---
55
+
56
+ /** Uniform noise around base: `base * (1 + uniform(-variance, +variance))`, clamped to [min, max]. */
57
+ export function stochasticDelay(base: number, variance: number, min = 30, max = 600): number {
58
+ const noise = (Math.random() * 2 - 1) * variance;
59
+ return Math.max(min, Math.min(max, base * (1 + noise)));
60
+ }
61
+
62
+ /** Accelerating backspace delay: gets faster with each consecutive backspace. */
63
+ export function backspaceDelay(
64
+ base: number,
65
+ variance: number,
66
+ index: number,
67
+ acceleration: number,
68
+ ): number {
69
+ const factor = Math.max(0.3, 1 - acceleration * index);
70
+ const raw = base * factor;
71
+ return stochasticDelay(raw, variance, 20, 200);
72
+ }
73
+
74
+ // --- N-gram sizing ---
75
+
76
+ /** Pick n-gram size. Fixed number passthrough, or geometric distribution biased small. */
77
+ export function pickNgramSize(config: number | { min: number; max: number }): number {
78
+ if (typeof config === "number") return config;
79
+ const { min, max } = config;
80
+ if (min === max) return min;
81
+
82
+ // Geometric-ish: smaller sizes are more likely
83
+ // P(k) ~ (1/2)^(k - min)
84
+ let roll = Math.random();
85
+ for (let k = min; k < max; k++) {
86
+ // Each size has 50% chance of being selected vs continuing
87
+ if (roll < 0.5) return k;
88
+ roll = (roll - 0.5) * 2; // renormalize
89
+ }
90
+ return max;
91
+ }
92
+
93
+ // --- Utilities ---
94
+
95
+ /** Uniform random integer in [min, max] (inclusive). */
96
+ export function randomInRange(min: number, max: number): number {
97
+ return Math.floor(Math.random() * (max - min + 1)) + min;
98
+ }
99
+
100
+ /** Check if user prefers reduced motion. SSR-safe. */
101
+ export function prefersReducedMotion(): boolean {
102
+ if (typeof window === "undefined") return false;
103
+ return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
104
+ }
@@ -0,0 +1,197 @@
1
+ import type { TypoContext, TypoAction } from "../types";
2
+ import { pickTypoChar } from "./keyboard";
3
+ import { randomInRange } from "./timing";
4
+
5
+ export interface TypoConfig {
6
+ maxCharsBeforeNotice: number;
7
+ continueAfterTypoProbability: number;
8
+ sequentialTypoDecay: number;
9
+ noticePauseMin: number;
10
+ noticePauseMax: number;
11
+ resumePauseMin: number;
12
+ resumePauseMax: number;
13
+ }
14
+
15
+ export const DEFAULT_TYPO_CONFIG: TypoConfig = {
16
+ maxCharsBeforeNotice: 4,
17
+ continueAfterTypoProbability: 0.6,
18
+ sequentialTypoDecay: 0.3,
19
+ noticePauseMin: 200,
20
+ noticePauseMax: 500,
21
+ resumePauseMin: 100,
22
+ resumePauseMax: 300,
23
+ };
24
+
25
+ export function createTypoContext(): TypoContext {
26
+ return {
27
+ state: "normal",
28
+ charsPastTypo: 0,
29
+ charsToDelete: 0,
30
+ sequentialTypoCount: 0,
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Pure FSM transition.
36
+ *
37
+ * States: normal -> typo_injected -> typing_past -> noticed -> correcting -> resuming -> normal
38
+ *
39
+ * The caller provides the intended character(s) at the current position.
40
+ * Returns the action to perform and the new context.
41
+ */
42
+ export function nextTypoAction(
43
+ ctx: TypoContext,
44
+ intendedChar: string,
45
+ config: TypoConfig,
46
+ errorRate: number,
47
+ ): { action: TypoAction; ctx: TypoContext } {
48
+ switch (ctx.state) {
49
+ case "normal":
50
+ return handleNormal(ctx, intendedChar, errorRate);
51
+
52
+ case "typo_injected":
53
+ return handleTypoInjected(ctx, intendedChar, config);
54
+
55
+ case "typing_past":
56
+ return handleTypingPast(ctx, intendedChar, config);
57
+
58
+ case "noticed":
59
+ return handleNoticed(ctx, config);
60
+
61
+ case "correcting":
62
+ return handleCorrecting(ctx);
63
+
64
+ case "resuming":
65
+ return handleResuming(ctx, config);
66
+ }
67
+ }
68
+
69
+ function handleNormal(
70
+ ctx: TypoContext,
71
+ intendedChar: string,
72
+ errorRate: number,
73
+ ): { action: TypoAction; ctx: TypoContext } {
74
+ // Sequential typos decay: each consecutive typo is less likely
75
+ const effectiveRate = errorRate * Math.pow(1 - 0.3, ctx.sequentialTypoCount);
76
+
77
+ if (Math.random() < effectiveRate && /[a-zA-Z]/.test(intendedChar)) {
78
+ const wrongChar = pickTypoChar(intendedChar);
79
+ return {
80
+ action: { type: "type_wrong", char: wrongChar, intended: intendedChar },
81
+ ctx: {
82
+ ...ctx,
83
+ state: "typo_injected",
84
+ charsPastTypo: 0,
85
+ charsToDelete: 1, // the wrong char itself
86
+ sequentialTypoCount: ctx.sequentialTypoCount + 1,
87
+ },
88
+ };
89
+ }
90
+
91
+ return {
92
+ action: { type: "type_correct", char: intendedChar },
93
+ ctx: { ...ctx, sequentialTypoCount: 0 },
94
+ };
95
+ }
96
+
97
+ function handleTypoInjected(
98
+ ctx: TypoContext,
99
+ intendedChar: string,
100
+ config: TypoConfig,
101
+ ): { action: TypoAction; ctx: TypoContext } {
102
+ // Decide: continue typing past, or notice immediately?
103
+ if (Math.random() < config.continueAfterTypoProbability) {
104
+ return {
105
+ action: { type: "type_past_correct", char: intendedChar },
106
+ ctx: {
107
+ ...ctx,
108
+ state: "typing_past",
109
+ charsPastTypo: 1,
110
+ charsToDelete: ctx.charsToDelete + 1,
111
+ },
112
+ };
113
+ }
114
+
115
+ // Notice immediately
116
+ return {
117
+ action: {
118
+ type: "notice",
119
+ pauseMs: randomInRange(config.noticePauseMin, config.noticePauseMax),
120
+ },
121
+ ctx: { ...ctx, state: "noticed" },
122
+ };
123
+ }
124
+
125
+ function handleTypingPast(
126
+ ctx: TypoContext,
127
+ intendedChar: string,
128
+ config: TypoConfig,
129
+ ): { action: TypoAction; ctx: TypoContext } {
130
+ // Hard cap
131
+ if (ctx.charsPastTypo >= config.maxCharsBeforeNotice) {
132
+ return {
133
+ action: {
134
+ type: "notice",
135
+ pauseMs: randomInRange(config.noticePauseMin, config.noticePauseMax),
136
+ },
137
+ ctx: { ...ctx, state: "noticed" },
138
+ };
139
+ }
140
+
141
+ // Roll to continue or notice
142
+ if (Math.random() < config.continueAfterTypoProbability) {
143
+ return {
144
+ action: { type: "type_past_correct", char: intendedChar },
145
+ ctx: {
146
+ ...ctx,
147
+ charsPastTypo: ctx.charsPastTypo + 1,
148
+ charsToDelete: ctx.charsToDelete + 1,
149
+ },
150
+ };
151
+ }
152
+
153
+ return {
154
+ action: {
155
+ type: "notice",
156
+ pauseMs: randomInRange(config.noticePauseMin, config.noticePauseMax),
157
+ },
158
+ ctx: { ...ctx, state: "noticed" },
159
+ };
160
+ }
161
+
162
+ function handleNoticed(
163
+ ctx: TypoContext,
164
+ _config: TypoConfig,
165
+ ): { action: TypoAction; ctx: TypoContext } {
166
+ return {
167
+ action: { type: "backspace", count: ctx.charsToDelete, frantic: true },
168
+ ctx: { ...ctx, state: "correcting" },
169
+ };
170
+ }
171
+
172
+ function handleCorrecting(ctx: TypoContext): { action: TypoAction; ctx: TypoContext } {
173
+ // After backspace completes, transition to resuming
174
+ return {
175
+ action: { type: "resume", pauseMs: randomInRange(100, 300) },
176
+ ctx: {
177
+ ...ctx,
178
+ state: "resuming",
179
+ },
180
+ };
181
+ }
182
+
183
+ function handleResuming(
184
+ ctx: TypoContext,
185
+ _config: TypoConfig,
186
+ ): { action: TypoAction; ctx: TypoContext } {
187
+ // Reset to normal
188
+ return {
189
+ action: { type: "type_correct", char: "" }, // caller will re-drive with actual char
190
+ ctx: {
191
+ ...ctx,
192
+ state: "normal",
193
+ charsPastTypo: 0,
194
+ charsToDelete: 0,
195
+ },
196
+ };
197
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./ui";
2
+ export * from "./custom";
@@ -0,0 +1,19 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ AccordionRoot,
4
+ type AccordionRootEmits,
5
+ type AccordionRootProps,
6
+ useForwardPropsEmits,
7
+ } from 'reka-ui'
8
+
9
+ const props = defineProps<AccordionRootProps>()
10
+ const emits = defineEmits<AccordionRootEmits>()
11
+
12
+ const forwarded = useForwardPropsEmits(props, emits)
13
+ </script>
14
+
15
+ <template>
16
+ <AccordionRoot v-bind="forwarded">
17
+ <slot />
18
+ </AccordionRoot>
19
+ </template>
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ import { type HTMLAttributes, computed } from 'vue'
3
+ import { AccordionContent, type AccordionContentProps } from 'reka-ui'
4
+ import { cn } from '../../../utils'
5
+
6
+ const props = defineProps<AccordionContentProps & { class?: HTMLAttributes['class'] }>()
7
+
8
+ const delegatedProps = computed(() => {
9
+ const { class: _, ...delegated } = props
10
+
11
+ return delegated
12
+ })
13
+ </script>
14
+
15
+ <template>
16
+ <AccordionContent
17
+ v-bind="delegatedProps"
18
+ class="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
19
+ >
20
+ <div :class="cn('pb-4 pt-0', props.class)">
21
+ <slot />
22
+ </div>
23
+ </AccordionContent>
24
+ </template>
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ import { type HTMLAttributes, computed } from 'vue'
3
+ import { AccordionItem, type AccordionItemProps, useForwardProps } from 'reka-ui'
4
+ import { cn } from '../../../utils'
5
+
6
+ const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()
7
+
8
+ const delegatedProps = computed(() => {
9
+ const { class: _, ...delegated } = props
10
+
11
+ return delegated
12
+ })
13
+
14
+ const forwardedProps = useForwardProps(delegatedProps)
15
+ </script>
16
+
17
+ <template>
18
+ <AccordionItem
19
+ v-bind="forwardedProps"
20
+ :class="cn('border-b', props.class)"
21
+ >
22
+ <slot />
23
+ </AccordionItem>
24
+ </template>
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ import { type HTMLAttributes, computed } from 'vue'
3
+ import {
4
+ AccordionHeader,
5
+ AccordionTrigger,
6
+ type AccordionTriggerProps,
7
+ } from 'reka-ui'
8
+ import { ChevronDown } from 'lucide-vue-next'
9
+ import { cn } from '../../../utils'
10
+
11
+ const props = defineProps<AccordionTriggerProps & { class?: HTMLAttributes['class'] }>()
12
+
13
+ const delegatedProps = computed(() => {
14
+ const { class: _, ...delegated } = props
15
+
16
+ return delegated
17
+ })
18
+ </script>
19
+
20
+ <template>
21
+ <AccordionHeader class="flex">
22
+ <AccordionTrigger
23
+ v-bind="delegatedProps"
24
+ :class="
25
+ cn(
26
+ 'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
27
+ props.class,
28
+ )
29
+ "
30
+ >
31
+ <slot />
32
+ <slot name="icon">
33
+ <ChevronDown
34
+ class="h-4 w-4 shrink-0 transition-transform duration-200"
35
+ />
36
+ </slot>
37
+ </AccordionTrigger>
38
+ </AccordionHeader>
39
+ </template>
@@ -0,0 +1,4 @@
1
+ export { default as Accordion } from './Accordion.vue'
2
+ export { default as AccordionContent } from './AccordionContent.vue'
3
+ export { default as AccordionItem } from './AccordionItem.vue'
4
+ export { default as AccordionTrigger } from './AccordionTrigger.vue'