@stacksjs/components 0.2.3

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 (342) hide show
  1. package/README.md +477 -0
  2. package/dist/Accordion-x2gjedap.stx +108 -0
  3. package/dist/Audio-0fk6p9tn.stx +50 -0
  4. package/dist/Avatar-5m0v0s4x.stx +88 -0
  5. package/dist/Badge-ynes82vv.stx +70 -0
  6. package/dist/Breadcrumb-h9n427sk.stx +79 -0
  7. package/dist/Button-7xtrzn14.stx +89 -0
  8. package/dist/Calendar-egvx1pma.stx +232 -0
  9. package/dist/Card-r9w7vy1a.stx +74 -0
  10. package/dist/Checkbox-m8v46pmx.stx +92 -0
  11. package/dist/Checkout-vcnnr176.stx +39 -0
  12. package/dist/CodeBlock-mwex82cm.stx +57 -0
  13. package/dist/Combobox-npb08vkp.stx +13 -0
  14. package/dist/ComboboxButton-vcdyq4zw.stx +13 -0
  15. package/dist/ComboboxInput-c9d2p02m.stx +25 -0
  16. package/dist/ComboboxOption-7d0h9ash.stx +52 -0
  17. package/dist/ComboboxOptions-4dtqc0qa.stx +12 -0
  18. package/dist/CommandPalette-ah00bf8v.stx +39 -0
  19. package/dist/CommandPaletteItem-2yx0gvtj.stx +32 -0
  20. package/dist/DefaultPaymentMethod-emw5fg8s.stx +62 -0
  21. package/dist/Dialog-1w4rz7x0.stx +19 -0
  22. package/dist/DialogBackdrop-qbm3y5ab.stx +9 -0
  23. package/dist/DialogDescription-01w5j74b.stx +11 -0
  24. package/dist/DialogPanel-brsszrrs.stx +16 -0
  25. package/dist/DialogTitle-3rxhwg21.stx +11 -0
  26. package/dist/Drawer-yfaz8q7f.stx +137 -0
  27. package/dist/Dropdown-ng7nx556.stx +24 -0
  28. package/dist/DropdownButton-vc4zpyrb.stx +23 -0
  29. package/dist/DropdownItem-z491y23c.stx +43 -0
  30. package/dist/DropdownItems-7ykf29sj.stx +18 -0
  31. package/dist/EmailInput-bfshpj92.stx +41 -0
  32. package/dist/Footer-xdaf02nr.stx +66 -0
  33. package/dist/Form-z4f7w6r1.stx +209 -0
  34. package/dist/Hero-9vvp7pxf.stx +25 -0
  35. package/dist/Image-ktk0451c.stx +197 -0
  36. package/dist/Installation-33bj4rm7.stx +39 -0
  37. package/dist/Listbox-xcejcmyc.stx +14 -0
  38. package/dist/ListboxButton-nx5kmw19.stx +17 -0
  39. package/dist/ListboxLabel-aky8a73n.stx +11 -0
  40. package/dist/ListboxOption-6h4hsdz5.stx +57 -0
  41. package/dist/ListboxOptions-2sacdtm5.stx +16 -0
  42. package/dist/Login-s2jkvd9y.stx +163 -0
  43. package/dist/Navigator-tqqdgdkw.stx +138 -0
  44. package/dist/Notification-sry8egea.stx +71 -0
  45. package/dist/NumberInput-zm93ddfw.stx +138 -0
  46. package/dist/Pagination-yza9v4qw.stx +183 -0
  47. package/dist/PasswordInput-4xnndp5y.stx +155 -0
  48. package/dist/PaymentMethods-jxba6kek.stx +113 -0
  49. package/dist/Popover-scbqatjq.stx +24 -0
  50. package/dist/PopoverButton-rbxhzj4k.stx +23 -0
  51. package/dist/PopoverPanel-9ng1rm6e.stx +23 -0
  52. package/dist/Portal-j111yvqp.stx +74 -0
  53. package/dist/Progress-gbmw3akr.stx +142 -0
  54. package/dist/Radio-1n9qamv1.stx +81 -0
  55. package/dist/RadioGroup-12dw2vhk.stx +13 -0
  56. package/dist/RadioGroupDescription-bjm3mdht.stx +11 -0
  57. package/dist/RadioGroupLabel-aky8a73n.stx +11 -0
  58. package/dist/RadioGroupOption-9pcxgwmd.stx +51 -0
  59. package/dist/SearchInput-g240e2nq.stx +63 -0
  60. package/dist/Select-p9f94b20.stx +89 -0
  61. package/dist/Sidebar-gwxw8q38.stx +288 -0
  62. package/dist/SidebarFooter-887kxret.stx +95 -0
  63. package/dist/SidebarHeader-qjeyx0hv.stx +100 -0
  64. package/dist/SidebarItem-4dpwtmb4.stx +118 -0
  65. package/dist/SidebarSection-kh92fk4m.stx +108 -0
  66. package/dist/Signup-3nxq5zag.stx +161 -0
  67. package/dist/Skeleton-g0nx1njm.stx +48 -0
  68. package/dist/Spinner-w4zj58xp.stx +109 -0
  69. package/dist/Stepper-rwc5actp.stx +17 -0
  70. package/dist/StepperStep-h0k657nm.stx +53 -0
  71. package/dist/SubscriptionCheckout-4cbxdg7w.stx +353 -0
  72. package/dist/Switch-kjq8qd2p.stx +74 -0
  73. package/dist/Table-59j9qwp0.stx +14 -0
  74. package/dist/TableBody-reka2gwv.stx +10 -0
  75. package/dist/TableCell-j7nh4sgr.stx +10 -0
  76. package/dist/TableHead-ecbrwpqm.stx +10 -0
  77. package/dist/TableHeader-jn2shwsv.stx +13 -0
  78. package/dist/TableRow-xz8rrxhc.stx +11 -0
  79. package/dist/Tabs-kb6ex6m7.stx +145 -0
  80. package/dist/Teleport-819cg9zm.stx +101 -0
  81. package/dist/TextInput-zx9kgr79.stx +146 -0
  82. package/dist/Textarea-5xvmrb0z.stx +119 -0
  83. package/dist/Tooltip-5ehqsy49.stx +76 -0
  84. package/dist/Transition-2j39s044.stx +174 -0
  85. package/dist/TwoFactorChallenge-2q4gg89m.stx +125 -0
  86. package/dist/Video-6aq4shh4.stx +45 -0
  87. package/dist/VirtualList-weh5qtm1.stx +83 -0
  88. package/dist/VirtualTable-59a1eskc.stx +113 -0
  89. package/dist/components/index.d.ts +4 -0
  90. package/dist/composables/index.d.ts +4 -0
  91. package/dist/composables/index.js +3 -0
  92. package/dist/composables/useCopyCode.d.ts +19 -0
  93. package/dist/composables/useDarkMode.d.ts +25 -0
  94. package/dist/composables/useSEO.d.ts +36 -0
  95. package/dist/composables/utilities/index.d.ts +5 -0
  96. package/dist/composables/utilities/useClickOutside.d.ts +28 -0
  97. package/dist/composables/utilities/useFocusTrap.d.ts +34 -0
  98. package/dist/composables/utilities/useKeyboard.d.ts +55 -0
  99. package/dist/composables/utilities/useLocalStorage.d.ts +33 -0
  100. package/dist/composables/utilities/useMediaQuery.d.ts +64 -0
  101. package/dist/index.d.ts +86 -0
  102. package/dist/index.js +19 -0
  103. package/dist/ui/accordion/index.d.ts +12 -0
  104. package/dist/ui/audio/index.d.ts +12 -0
  105. package/dist/ui/auth/index.d.ts +29 -0
  106. package/dist/ui/avatar/index.d.ts +11 -0
  107. package/dist/ui/badge/index.d.ts +9 -0
  108. package/dist/ui/breadcrumb/index.d.ts +13 -0
  109. package/dist/ui/button/index.d.ts +45 -0
  110. package/dist/ui/calendar/index.d.ts +13 -0
  111. package/dist/ui/card/index.d.ts +12 -0
  112. package/dist/ui/checkbox/index.d.ts +15 -0
  113. package/dist/ui/combobox/index.d.ts +35 -0
  114. package/dist/ui/command-palette/index.d.ts +13 -0
  115. package/dist/ui/dialog/index.d.ts +27 -0
  116. package/dist/ui/drawer/index.d.ts +8 -0
  117. package/dist/ui/dropdown/index.d.ts +25 -0
  118. package/dist/ui/form/index.d.ts +48 -0
  119. package/dist/ui/image/index.d.ts +21 -0
  120. package/dist/ui/input/index.d.ts +73 -0
  121. package/dist/ui/listbox/index.d.ts +34 -0
  122. package/dist/ui/navigator/index.d.ts +18 -0
  123. package/dist/ui/notification/index.d.ts +11 -0
  124. package/dist/ui/pagination/index.d.ts +10 -0
  125. package/dist/ui/payment/index.d.ts +53 -0
  126. package/dist/ui/popover/index.d.ts +19 -0
  127. package/dist/ui/portal/index.d.ts +5 -0
  128. package/dist/ui/progress/index.d.ts +11 -0
  129. package/dist/ui/radio/index.d.ts +14 -0
  130. package/dist/ui/radio-group/index.d.ts +26 -0
  131. package/dist/ui/select/index.d.ts +19 -0
  132. package/dist/ui/sidebar/index.d.ts +129 -0
  133. package/dist/ui/skeleton/index.d.ts +10 -0
  134. package/dist/ui/spinner/index.d.ts +8 -0
  135. package/dist/ui/stepper/index.d.ts +17 -0
  136. package/dist/ui/storage/index.d.ts +40 -0
  137. package/dist/ui/switch/index.d.ts +9 -0
  138. package/dist/ui/table/index.d.ts +29 -0
  139. package/dist/ui/tabs/index.d.ts +14 -0
  140. package/dist/ui/teleport/index.d.ts +6 -0
  141. package/dist/ui/textarea/index.d.ts +19 -0
  142. package/dist/ui/tooltip/index.d.ts +9 -0
  143. package/dist/ui/transition/index.d.ts +24 -0
  144. package/dist/ui/video/index.d.ts +15 -0
  145. package/dist/ui/virtual-list/index.d.ts +9 -0
  146. package/dist/ui/virtual-table/index.d.ts +19 -0
  147. package/dist/utils/accessibility.d.ts +285 -0
  148. package/dist/utils/animation/easing.d.ts +112 -0
  149. package/dist/utils/animation/helpers.d.ts +119 -0
  150. package/dist/utils/animation/index.d.ts +3 -0
  151. package/dist/utils/animation/keyframes.d.ts +334 -0
  152. package/dist/utils/error-handling.d.ts +182 -0
  153. package/dist/utils/highlighter.d.ts +27 -0
  154. package/dist/utils/highlighter.js +2 -0
  155. package/dist/utils/i18n.d.ts +245 -0
  156. package/dist/utils/keep-alive.d.ts +171 -0
  157. package/dist/utils/lazy-loading.d.ts +174 -0
  158. package/dist/utils/prop-validation.d.ts +106 -0
  159. package/dist/utils/theme.d.ts +234 -0
  160. package/package.json +69 -0
  161. package/src/components/CodeBlock.stx +57 -0
  162. package/src/components/Footer.stx +66 -0
  163. package/src/components/Hero.stx +25 -0
  164. package/src/components/Installation.stx +39 -0
  165. package/src/components/index.ts +4 -0
  166. package/src/composables/index.ts +4 -0
  167. package/src/composables/useCopyCode.ts +73 -0
  168. package/src/composables/useDarkMode.ts +100 -0
  169. package/src/composables/useSEO.ts +229 -0
  170. package/src/composables/utilities/index.ts +7 -0
  171. package/src/composables/utilities/useClickOutside.ts +72 -0
  172. package/src/composables/utilities/useFocusTrap.ts +159 -0
  173. package/src/composables/utilities/useKeyboard.ts +124 -0
  174. package/src/composables/utilities/useLocalStorage.ts +132 -0
  175. package/src/composables/utilities/useMediaQuery.ts +118 -0
  176. package/src/index.ts +99 -0
  177. package/src/stx.d.ts +4 -0
  178. package/src/ui/accordion/Accordion.stx +108 -0
  179. package/src/ui/accordion/index.ts +14 -0
  180. package/src/ui/audio/Audio.stx +50 -0
  181. package/src/ui/audio/README.md +42 -0
  182. package/src/ui/audio/index.ts +13 -0
  183. package/src/ui/auth/Login.stx +163 -0
  184. package/src/ui/auth/README.md +145 -0
  185. package/src/ui/auth/Signup.stx +161 -0
  186. package/src/ui/auth/TwoFactorChallenge.stx +125 -0
  187. package/src/ui/auth/index.ts +32 -0
  188. package/src/ui/avatar/Avatar.stx +88 -0
  189. package/src/ui/avatar/index.ts +12 -0
  190. package/src/ui/badge/Badge.stx +70 -0
  191. package/src/ui/badge/index.ts +10 -0
  192. package/src/ui/breadcrumb/Breadcrumb.stx +79 -0
  193. package/src/ui/breadcrumb/index.ts +15 -0
  194. package/src/ui/button/Button.stx +89 -0
  195. package/src/ui/button/README.md +100 -0
  196. package/src/ui/button/index.ts +85 -0
  197. package/src/ui/calendar/Calendar.stx +232 -0
  198. package/src/ui/calendar/README.md +254 -0
  199. package/src/ui/calendar/index.ts +14 -0
  200. package/src/ui/card/Card.stx +74 -0
  201. package/src/ui/card/index.ts +13 -0
  202. package/src/ui/checkbox/Checkbox.stx +92 -0
  203. package/src/ui/checkbox/index.ts +16 -0
  204. package/src/ui/combobox/Combobox.stx +13 -0
  205. package/src/ui/combobox/ComboboxButton.stx +13 -0
  206. package/src/ui/combobox/ComboboxInput.stx +25 -0
  207. package/src/ui/combobox/ComboboxOption.stx +52 -0
  208. package/src/ui/combobox/ComboboxOptions.stx +12 -0
  209. package/src/ui/combobox/README.md +154 -0
  210. package/src/ui/combobox/index.ts +40 -0
  211. package/src/ui/command-palette/CommandPalette.stx +39 -0
  212. package/src/ui/command-palette/CommandPaletteItem.stx +32 -0
  213. package/src/ui/command-palette/README.md +42 -0
  214. package/src/ui/command-palette/index.ts +15 -0
  215. package/src/ui/dialog/Dialog.stx +19 -0
  216. package/src/ui/dialog/DialogBackdrop.stx +9 -0
  217. package/src/ui/dialog/DialogDescription.stx +11 -0
  218. package/src/ui/dialog/DialogPanel.stx +16 -0
  219. package/src/ui/dialog/DialogTitle.stx +11 -0
  220. package/src/ui/dialog/README.md +133 -0
  221. package/src/ui/dialog/index.ts +32 -0
  222. package/src/ui/drawer/Drawer.stx +137 -0
  223. package/src/ui/drawer/README.md +110 -0
  224. package/src/ui/drawer/index.ts +9 -0
  225. package/src/ui/dropdown/Dropdown.stx +24 -0
  226. package/src/ui/dropdown/DropdownButton.stx +23 -0
  227. package/src/ui/dropdown/DropdownItem.stx +43 -0
  228. package/src/ui/dropdown/DropdownItems.stx +18 -0
  229. package/src/ui/dropdown/README.md +142 -0
  230. package/src/ui/dropdown/index.ts +29 -0
  231. package/src/ui/form/Form.stx +209 -0
  232. package/src/ui/form/index.ts +67 -0
  233. package/src/ui/heatmap/Heatmap.stx +315 -0
  234. package/src/ui/image/Image.stx +197 -0
  235. package/src/ui/image/README.md +171 -0
  236. package/src/ui/image/index.ts +22 -0
  237. package/src/ui/input/EmailInput.stx +41 -0
  238. package/src/ui/input/NumberInput.stx +138 -0
  239. package/src/ui/input/PasswordInput.stx +155 -0
  240. package/src/ui/input/SearchInput.stx +63 -0
  241. package/src/ui/input/TextInput.stx +146 -0
  242. package/src/ui/input/index.ts +180 -0
  243. package/src/ui/listbox/Listbox.stx +14 -0
  244. package/src/ui/listbox/ListboxButton.stx +17 -0
  245. package/src/ui/listbox/ListboxLabel.stx +11 -0
  246. package/src/ui/listbox/ListboxOption.stx +57 -0
  247. package/src/ui/listbox/ListboxOptions.stx +16 -0
  248. package/src/ui/listbox/README.md +143 -0
  249. package/src/ui/listbox/index.ts +39 -0
  250. package/src/ui/navigator/Navigator.stx +138 -0
  251. package/src/ui/navigator/README.md +352 -0
  252. package/src/ui/navigator/index.ts +20 -0
  253. package/src/ui/notification/Notification.stx +71 -0
  254. package/src/ui/notification/README.md +118 -0
  255. package/src/ui/notification/index.ts +12 -0
  256. package/src/ui/pagination/Pagination.stx +183 -0
  257. package/src/ui/pagination/index.ts +11 -0
  258. package/src/ui/payment/Checkout.stx +39 -0
  259. package/src/ui/payment/DefaultPaymentMethod.stx +62 -0
  260. package/src/ui/payment/PaymentMethods.stx +113 -0
  261. package/src/ui/payment/README.md +427 -0
  262. package/src/ui/payment/SubscriptionCheckout.stx +353 -0
  263. package/src/ui/payment/index.ts +59 -0
  264. package/src/ui/popover/Popover.stx +24 -0
  265. package/src/ui/popover/PopoverButton.stx +23 -0
  266. package/src/ui/popover/PopoverPanel.stx +23 -0
  267. package/src/ui/popover/README.md +95 -0
  268. package/src/ui/popover/index.ts +22 -0
  269. package/src/ui/portal/Portal.stx +74 -0
  270. package/src/ui/portal/index.ts +6 -0
  271. package/src/ui/progress/Progress.stx +142 -0
  272. package/src/ui/progress/index.ts +12 -0
  273. package/src/ui/radio/Radio.stx +81 -0
  274. package/src/ui/radio/index.ts +15 -0
  275. package/src/ui/radio-group/README.md +145 -0
  276. package/src/ui/radio-group/RadioGroup.stx +13 -0
  277. package/src/ui/radio-group/RadioGroupDescription.stx +11 -0
  278. package/src/ui/radio-group/RadioGroupLabel.stx +11 -0
  279. package/src/ui/radio-group/RadioGroupOption.stx +51 -0
  280. package/src/ui/radio-group/index.ts +30 -0
  281. package/src/ui/select/README.md +204 -0
  282. package/src/ui/select/Select.stx +89 -0
  283. package/src/ui/select/index.ts +21 -0
  284. package/src/ui/sidebar/Sidebar.stx +288 -0
  285. package/src/ui/sidebar/SidebarFooter.stx +95 -0
  286. package/src/ui/sidebar/SidebarHeader.stx +100 -0
  287. package/src/ui/sidebar/SidebarItem.stx +118 -0
  288. package/src/ui/sidebar/SidebarSection.stx +108 -0
  289. package/src/ui/sidebar/index.ts +175 -0
  290. package/src/ui/skeleton/Skeleton.stx +48 -0
  291. package/src/ui/skeleton/index.ts +11 -0
  292. package/src/ui/spinner/Spinner.stx +109 -0
  293. package/src/ui/spinner/index.ts +9 -0
  294. package/src/ui/stepper/README.md +36 -0
  295. package/src/ui/stepper/Stepper.stx +17 -0
  296. package/src/ui/stepper/StepperStep.stx +53 -0
  297. package/src/ui/stepper/index.ts +19 -0
  298. package/src/ui/storage/README.md +193 -0
  299. package/src/ui/storage/index.ts +347 -0
  300. package/src/ui/switch/README.md +133 -0
  301. package/src/ui/switch/Switch.stx +74 -0
  302. package/src/ui/switch/index.ts +10 -0
  303. package/src/ui/table/README.md +39 -0
  304. package/src/ui/table/Table.stx +14 -0
  305. package/src/ui/table/TableBody.stx +10 -0
  306. package/src/ui/table/TableCell.stx +10 -0
  307. package/src/ui/table/TableHead.stx +10 -0
  308. package/src/ui/table/TableHeader.stx +13 -0
  309. package/src/ui/table/TableRow.stx +11 -0
  310. package/src/ui/table/index.ts +35 -0
  311. package/src/ui/tabs/Tabs.stx +145 -0
  312. package/src/ui/tabs/index.ts +16 -0
  313. package/src/ui/teleport/Teleport.stx +101 -0
  314. package/src/ui/teleport/index.ts +7 -0
  315. package/src/ui/textarea/Textarea.stx +119 -0
  316. package/src/ui/textarea/index.ts +20 -0
  317. package/src/ui/tooltip/Tooltip.stx +76 -0
  318. package/src/ui/tooltip/index.ts +10 -0
  319. package/src/ui/transition/README.md +30 -0
  320. package/src/ui/transition/Transition.stx +174 -0
  321. package/src/ui/transition/index.ts +25 -0
  322. package/src/ui/video/README.md +48 -0
  323. package/src/ui/video/Video.stx +45 -0
  324. package/src/ui/video/index.ts +16 -0
  325. package/src/ui/virtual-list/VirtualList.stx +83 -0
  326. package/src/ui/virtual-list/index.ts +10 -0
  327. package/src/ui/virtual-table/VirtualTable.stx +113 -0
  328. package/src/ui/virtual-table/index.ts +21 -0
  329. package/src/utils/PROP_VALIDATION.md +458 -0
  330. package/src/utils/THEME_SYSTEM.md +702 -0
  331. package/src/utils/accessibility.ts +841 -0
  332. package/src/utils/animation/easing.ts +368 -0
  333. package/src/utils/animation/helpers.ts +223 -0
  334. package/src/utils/animation/index.ts +5 -0
  335. package/src/utils/animation/keyframes.ts +230 -0
  336. package/src/utils/error-handling.ts +473 -0
  337. package/src/utils/highlighter.ts +93 -0
  338. package/src/utils/i18n.ts +634 -0
  339. package/src/utils/keep-alive.ts +818 -0
  340. package/src/utils/lazy-loading.ts +530 -0
  341. package/src/utils/prop-validation.ts +478 -0
  342. package/src/utils/theme.ts +626 -0
@@ -0,0 +1,229 @@
1
+ /**
2
+ * SEO and meta tag utilities for STX components
3
+ */
4
+
5
+ export interface SEOMetaOptions {
6
+ title?: string
7
+ description?: string
8
+ author?: string
9
+ keywords?: string[]
10
+ ogImage?: string
11
+ ogImageWidth?: string
12
+ ogImageHeight?: string
13
+ twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player'
14
+ twitterSite?: string
15
+ twitterCreator?: string
16
+ canonical?: string
17
+ }
18
+
19
+ /**
20
+ * Set document title
21
+ */
22
+ export function setTitle(title: string, template?: string): void {
23
+ if (typeof document === 'undefined')
24
+ return
25
+
26
+ document.title = template ? template.replace('%s', title) : title
27
+ }
28
+
29
+ /**
30
+ * Set meta tag
31
+ */
32
+ export function setMeta(name: string, content: string, isProperty = false): void {
33
+ if (typeof document === 'undefined')
34
+ return
35
+
36
+ const attribute = isProperty ? 'property' : 'name'
37
+ let meta = document.querySelector(`meta[${attribute}="${name}"]`)
38
+
39
+ if (!meta) {
40
+ meta = document.createElement('meta')
41
+ meta.setAttribute(attribute, name)
42
+ document.head.appendChild(meta)
43
+ }
44
+
45
+ meta.setAttribute('content', content)
46
+ }
47
+
48
+ /**
49
+ * Set link tag
50
+ */
51
+ export function setLink(rel: string, href: string): void {
52
+ if (typeof document === 'undefined')
53
+ return
54
+
55
+ let link = document.querySelector(`link[rel="${rel}"]`) as HTMLLinkElement
56
+
57
+ if (!link) {
58
+ link = document.createElement('link')
59
+ link.setAttribute('rel', rel)
60
+ document.head.appendChild(link)
61
+ }
62
+
63
+ link.setAttribute('href', href)
64
+ }
65
+
66
+ /**
67
+ * Use SEO meta tags
68
+ */
69
+ export function useSEO(options: SEOMetaOptions): void {
70
+ const {
71
+ title,
72
+ description,
73
+ author,
74
+ keywords,
75
+ ogImage,
76
+ ogImageWidth,
77
+ ogImageHeight,
78
+ twitterCard = 'summary_large_image',
79
+ twitterSite,
80
+ twitterCreator,
81
+ canonical,
82
+ } = options
83
+
84
+ // Set title
85
+ if (title) {
86
+ setTitle(title)
87
+ setMeta('og:title', title, true)
88
+ setMeta('twitter:title', title)
89
+ }
90
+
91
+ // Set description
92
+ if (description) {
93
+ setMeta('description', description)
94
+ setMeta('og:description', description, true)
95
+ setMeta('twitter:description', description)
96
+ }
97
+
98
+ // Set author
99
+ if (author) {
100
+ setMeta('author', author)
101
+ }
102
+
103
+ // Set keywords
104
+ if (keywords && keywords.length > 0) {
105
+ setMeta('keywords', keywords.join(', '))
106
+ }
107
+
108
+ // Set Open Graph image
109
+ if (ogImage) {
110
+ setMeta('og:image', ogImage, true)
111
+ setMeta('twitter:image', ogImage)
112
+ }
113
+
114
+ if (ogImageWidth) {
115
+ setMeta('og:image:width', ogImageWidth, true)
116
+ }
117
+
118
+ if (ogImageHeight) {
119
+ setMeta('og:image:height', ogImageHeight, true)
120
+ }
121
+
122
+ // Set Twitter card
123
+ setMeta('twitter:card', twitterCard)
124
+
125
+ if (twitterSite) {
126
+ setMeta('twitter:site', twitterSite)
127
+ }
128
+
129
+ if (twitterCreator) {
130
+ setMeta('twitter:creator', twitterCreator)
131
+ }
132
+
133
+ // Set canonical URL
134
+ if (canonical) {
135
+ setLink('canonical', canonical)
136
+ }
137
+
138
+ // Set og:type
139
+ setMeta('og:type', 'website', true)
140
+
141
+ // Set og:url from current URL
142
+ if (typeof window !== 'undefined') {
143
+ setMeta('og:url', window.location.href, true)
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Generate SEO meta tags as HTML string for server-side rendering
149
+ */
150
+ export function generateSEOTags(options: SEOMetaOptions): string {
151
+ const tags: string[] = []
152
+
153
+ const {
154
+ title,
155
+ description,
156
+ author,
157
+ keywords,
158
+ ogImage,
159
+ ogImageWidth,
160
+ ogImageHeight,
161
+ twitterCard = 'summary_large_image',
162
+ twitterSite,
163
+ twitterCreator,
164
+ canonical,
165
+ } = options
166
+
167
+ if (title) {
168
+ tags.push(`<title>${escapeHtml(title)}</title>`)
169
+ tags.push(`<meta property="og:title" content="${escapeHtml(title)}" />`)
170
+ tags.push(`<meta name="twitter:title" content="${escapeHtml(title)}" />`)
171
+ }
172
+
173
+ if (description) {
174
+ tags.push(`<meta name="description" content="${escapeHtml(description)}" />`)
175
+ tags.push(`<meta property="og:description" content="${escapeHtml(description)}" />`)
176
+ tags.push(`<meta name="twitter:description" content="${escapeHtml(description)}" />`)
177
+ }
178
+
179
+ if (author) {
180
+ tags.push(`<meta name="author" content="${escapeHtml(author)}" />`)
181
+ }
182
+
183
+ if (keywords && keywords.length > 0) {
184
+ tags.push(`<meta name="keywords" content="${escapeHtml(keywords.join(', '))}" />`)
185
+ }
186
+
187
+ if (ogImage) {
188
+ tags.push(`<meta property="og:image" content="${escapeHtml(ogImage)}" />`)
189
+ tags.push(`<meta name="twitter:image" content="${escapeHtml(ogImage)}" />`)
190
+ }
191
+
192
+ if (ogImageWidth) {
193
+ tags.push(`<meta property="og:image:width" content="${escapeHtml(ogImageWidth)}" />`)
194
+ }
195
+
196
+ if (ogImageHeight) {
197
+ tags.push(`<meta property="og:image:height" content="${escapeHtml(ogImageHeight)}" />`)
198
+ }
199
+
200
+ tags.push(`<meta name="twitter:card" content="${escapeHtml(twitterCard)}" />`)
201
+
202
+ if (twitterSite) {
203
+ tags.push(`<meta name="twitter:site" content="${escapeHtml(twitterSite)}" />`)
204
+ }
205
+
206
+ if (twitterCreator) {
207
+ tags.push(`<meta name="twitter:creator" content="${escapeHtml(twitterCreator)}" />`)
208
+ }
209
+
210
+ if (canonical) {
211
+ tags.push(`<link rel="canonical" href="${escapeHtml(canonical)}" />`)
212
+ }
213
+
214
+ tags.push('<meta property="og:type" content="website" />')
215
+
216
+ return tags.join('\n')
217
+ }
218
+
219
+ /**
220
+ * Escape HTML special characters
221
+ */
222
+ function escapeHtml(str: string): string {
223
+ return str
224
+ .replace(/&/g, '&amp;')
225
+ .replace(/</g, '&lt;')
226
+ .replace(/>/g, '&gt;')
227
+ .replace(/"/g, '&quot;')
228
+ .replace(/'/g, '&#039;')
229
+ }
@@ -0,0 +1,7 @@
1
+ // Composable utilities exports
2
+
3
+ export * from './useClickOutside'
4
+ export * from './useFocusTrap'
5
+ export * from './useKeyboard'
6
+ export * from './useLocalStorage'
7
+ export * from './useMediaQuery'
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Detects clicks outside a specified element
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * const cleanup = useClickOutside(
7
+ * modalRef,
8
+ * () => closeModal(),
9
+ * { enabled: isOpen, ignore: ['.tooltip'] }
10
+ * )
11
+ * ```
12
+ */
13
+
14
+ /**
15
+ * Options for useClickOutside hook
16
+ */
17
+ export interface UseClickOutsideOptions {
18
+ /** Whether the hook is enabled */
19
+ enabled?: boolean
20
+ /** CSS selectors to ignore (clicks on these elements won't trigger callback) */
21
+ ignore?: string[]
22
+ }
23
+
24
+ /**
25
+ * Hook to detect clicks outside an element
26
+ *
27
+ * @param elementRef - The element to detect clicks outside of
28
+ * @param callback - Function to call when click occurs outside element
29
+ * @param options - Configuration options
30
+ * @returns Cleanup function to remove event listeners
31
+ */
32
+ export function useClickOutside(
33
+ elementRef: HTMLElement | null,
34
+ callback: (event: MouseEvent | TouchEvent) => void,
35
+ options: UseClickOutsideOptions = {},
36
+ ): () => void {
37
+ const { enabled = true, ignore = [] } = options
38
+
39
+ const handleClick = (event: MouseEvent | TouchEvent) => {
40
+ if (!enabled || !elementRef)
41
+ return
42
+
43
+ const target = event.target as Node
44
+
45
+ // Check if click is outside element
46
+ if (!elementRef.contains(target)) {
47
+ // Check if target matches any ignore selectors
48
+ const shouldIgnore = ignore.some((selector) => {
49
+ const elements = document.querySelectorAll(selector)
50
+ return Array.from(elements).some(el => el.contains(target))
51
+ })
52
+
53
+ if (!shouldIgnore) {
54
+ callback(event)
55
+ }
56
+ }
57
+ }
58
+
59
+ // Add event listeners
60
+ if (typeof document !== 'undefined') {
61
+ document.addEventListener('mousedown', handleClick)
62
+ document.addEventListener('touchstart', handleClick)
63
+ }
64
+
65
+ // Return cleanup function
66
+ return () => {
67
+ if (typeof document !== 'undefined') {
68
+ document.removeEventListener('mousedown', handleClick)
69
+ document.removeEventListener('touchstart', handleClick)
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Traps focus within a container element (useful for modals, dialogs, dropdowns)
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * const { activate, deactivate } = useFocusTrap(modalRef, {
7
+ * initialFocus: firstInputRef,
8
+ * returnFocus: true
9
+ * })
10
+ *
11
+ * // When modal opens
12
+ * activate()
13
+ *
14
+ * // When modal closes
15
+ * deactivate()
16
+ * ```
17
+ */
18
+
19
+ /**
20
+ * Options for useFocusTrap hook
21
+ */
22
+ export interface UseFocusTrapOptions {
23
+ /** Whether the focus trap is enabled */
24
+ enabled?: boolean
25
+ /** Element to focus initially (defaults to first focusable element) */
26
+ initialFocus?: HTMLElement | null
27
+ /** Whether to return focus to previously focused element on deactivate */
28
+ returnFocus?: boolean
29
+ /** Fallback element to focus if no focusable elements found */
30
+ fallbackFocus?: HTMLElement | null
31
+ }
32
+
33
+ /**
34
+ * Hook to trap focus within an element
35
+ *
36
+ * @param containerRef - The container element to trap focus within
37
+ * @param options - Configuration options
38
+ * @returns Object with activate and deactivate functions
39
+ */
40
+ export function useFocusTrap(
41
+ containerRef: HTMLElement | null,
42
+ options: UseFocusTrapOptions = {},
43
+ ): { activate: () => void, deactivate: () => void } {
44
+ const {
45
+ enabled = true,
46
+ initialFocus = null,
47
+ returnFocus = true,
48
+ fallbackFocus = null,
49
+ } = options
50
+
51
+ let previouslyFocusedElement: HTMLElement | null = null
52
+ let isActive = false
53
+
54
+ // Get all focusable elements within container
55
+ function getFocusableElements(): HTMLElement[] {
56
+ if (!containerRef)
57
+ return []
58
+
59
+ const focusableSelectors = [
60
+ 'a[href]',
61
+ 'button:not([disabled])',
62
+ 'textarea:not([disabled])',
63
+ 'input:not([disabled])',
64
+ 'select:not([disabled])',
65
+ '[tabindex]:not([tabindex="-1"])',
66
+ ].join(',')
67
+
68
+ return Array.from(containerRef.querySelectorAll(focusableSelectors))
69
+ }
70
+
71
+ // Handle tab key
72
+ function handleTab(event: KeyboardEvent) {
73
+ if (!enabled || !isActive || !containerRef)
74
+ return
75
+
76
+ const focusableElements = getFocusableElements()
77
+
78
+ if (focusableElements.length === 0) {
79
+ event.preventDefault()
80
+ return
81
+ }
82
+
83
+ const firstElement = focusableElements[0]
84
+ const lastElement = focusableElements[focusableElements.length - 1]
85
+
86
+ if (event.shiftKey) {
87
+ // Shift + Tab
88
+ if (document.activeElement === firstElement || document.activeElement === containerRef) {
89
+ event.preventDefault()
90
+ lastElement.focus()
91
+ }
92
+ }
93
+ else {
94
+ // Tab
95
+ if (document.activeElement === lastElement) {
96
+ event.preventDefault()
97
+ firstElement.focus()
98
+ }
99
+ }
100
+ }
101
+
102
+ // Handle escape key
103
+ function handleEscape(event: KeyboardEvent) {
104
+ if (event.key === 'Escape' && enabled && isActive) {
105
+ deactivate()
106
+ }
107
+ }
108
+
109
+ // Activate focus trap
110
+ function activate() {
111
+ if (!enabled || !containerRef || isActive)
112
+ return
113
+
114
+ // Store previously focused element
115
+ previouslyFocusedElement = document.activeElement as HTMLElement
116
+
117
+ // Focus initial element
118
+ if (initialFocus) {
119
+ initialFocus.focus()
120
+ }
121
+ else {
122
+ const focusableElements = getFocusableElements()
123
+ if (focusableElements.length > 0) {
124
+ focusableElements[0].focus()
125
+ }
126
+ else if (fallbackFocus) {
127
+ fallbackFocus.focus()
128
+ }
129
+ else {
130
+ containerRef.focus()
131
+ }
132
+ }
133
+
134
+ // Add event listeners
135
+ document.addEventListener('keydown', handleTab)
136
+ document.addEventListener('keydown', handleEscape)
137
+
138
+ isActive = true
139
+ }
140
+
141
+ // Deactivate focus trap
142
+ function deactivate() {
143
+ if (!isActive)
144
+ return
145
+
146
+ // Remove event listeners
147
+ document.removeEventListener('keydown', handleTab)
148
+ document.removeEventListener('keydown', handleEscape)
149
+
150
+ // Return focus
151
+ if (returnFocus && previouslyFocusedElement) {
152
+ previouslyFocusedElement.focus()
153
+ }
154
+
155
+ isActive = false
156
+ }
157
+
158
+ return { activate, deactivate }
159
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Hook for keyboard event handling with support for shortcuts and modifiers
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * useKeyboard([
7
+ * { key: 'Escape', handler: () => closeModal() },
8
+ * { key: 's', ctrl: true, handler: () => save() },
9
+ * { key: ['ArrowUp', 'ArrowDown'], handler: (e) => navigate(e.key) }
10
+ * ])
11
+ * ```
12
+ */
13
+
14
+ /**
15
+ * Options for useKeyboard hook
16
+ */
17
+ export interface UseKeyboardOptions {
18
+ /** Whether the hook is enabled */
19
+ enabled?: boolean
20
+ /** Whether to prevent default browser behavior */
21
+ preventDefault?: boolean
22
+ /** Whether to stop event propagation */
23
+ stopPropagation?: boolean
24
+ /** Target element for event listener (defaults to document) */
25
+ target?: HTMLElement | Document | Window | null
26
+ }
27
+
28
+ /**
29
+ * Keyboard event handler configuration
30
+ */
31
+ export interface KeyboardHandler {
32
+ /** Key or keys to listen for */
33
+ key: string | string[]
34
+ /** Handler function to execute */
35
+ handler: (event: KeyboardEvent) => void
36
+ /** Whether Ctrl key must be pressed */
37
+ ctrl?: boolean
38
+ /** Whether Shift key must be pressed */
39
+ shift?: boolean
40
+ /** Whether Alt key must be pressed */
41
+ alt?: boolean
42
+ /** Whether Meta (Command on Mac) key must be pressed */
43
+ meta?: boolean
44
+ }
45
+
46
+ /**
47
+ * Hook for keyboard event handling
48
+ *
49
+ * @param handlers - Single handler or array of keyboard handlers
50
+ * @param options - Configuration options
51
+ * @returns Cleanup function to remove event listeners
52
+ */
53
+ export function useKeyboard(
54
+ handlers: KeyboardHandler | KeyboardHandler[],
55
+ options: UseKeyboardOptions = {},
56
+ ): () => void {
57
+ const {
58
+ enabled = true,
59
+ preventDefault = true,
60
+ stopPropagation = false,
61
+ target = typeof document !== 'undefined' ? document : null,
62
+ } = options
63
+
64
+ const handlerArray = Array.isArray(handlers) ? handlers : [handlers]
65
+
66
+ const handleKeydown = (event: KeyboardEvent) => {
67
+ if (!enabled)
68
+ return
69
+
70
+ handlerArray.forEach((handler) => {
71
+ const keys = Array.isArray(handler.key) ? handler.key : [handler.key]
72
+ const keyMatches = keys.some(k => k.toLowerCase() === event.key.toLowerCase())
73
+
74
+ if (!keyMatches)
75
+ return
76
+
77
+ // Check modifier keys
78
+ if (handler.ctrl !== undefined && handler.ctrl !== event.ctrlKey)
79
+ return
80
+ if (handler.shift !== undefined && handler.shift !== event.shiftKey)
81
+ return
82
+ if (handler.alt !== undefined && handler.alt !== event.altKey)
83
+ return
84
+ if (handler.meta !== undefined && handler.meta !== event.metaKey)
85
+ return
86
+
87
+ // Execute handler
88
+ if (preventDefault)
89
+ event.preventDefault()
90
+ if (stopPropagation)
91
+ event.stopPropagation()
92
+
93
+ handler.handler(event)
94
+ })
95
+ }
96
+
97
+ // Add event listener
98
+ if (target) {
99
+ target.addEventListener('keydown', handleKeydown as EventListener)
100
+ }
101
+
102
+ // Return cleanup function
103
+ return () => {
104
+ if (target) {
105
+ target.removeEventListener('keydown', handleKeydown as EventListener)
106
+ }
107
+ }
108
+ }
109
+
110
+ // Common keyboard shortcuts
111
+ export const KeyboardShortcuts = {
112
+ ESCAPE: 'Escape',
113
+ ENTER: 'Enter',
114
+ SPACE: ' ',
115
+ TAB: 'Tab',
116
+ ARROW_UP: 'ArrowUp',
117
+ ARROW_DOWN: 'ArrowDown',
118
+ ARROW_LEFT: 'ArrowLeft',
119
+ ARROW_RIGHT: 'ArrowRight',
120
+ HOME: 'Home',
121
+ END: 'End',
122
+ PAGE_UP: 'PageUp',
123
+ PAGE_DOWN: 'PageDown',
124
+ } as const