@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,353 @@
1
+ <script>
2
+ export const products = $props.products || []
3
+ export const stripePublicKey = $props.stripePublicKey || ''
4
+ export const apiUrl = $props.apiUrl || ''
5
+ export const onSubmit = $props.onSubmit
6
+ export const onRemoveProduct = $props.onRemoveProduct
7
+ export const shipping = $props.shipping || 0
8
+ export const taxes = $props.taxes || 0
9
+ export const className = $props.className || ''
10
+
11
+ // Calculate totals
12
+ export const calculateSubtotal = () => {
13
+ return products.reduce((sum, product) => sum + (product.price * (product.quantity || 1)), 0)
14
+ }
15
+
16
+ export const total = calculateSubtotal() + shipping + taxes
17
+
18
+ export const containerClasses = `bg-gray-50 dark:bg-blue-gray-900 ${className}`.trim()
19
+ </script>
20
+
21
+ <div class="{{ containerClasses }}">
22
+ <div class="mx-auto max-w-2xl px-4 pb-24 pt-16 lg:max-w-7xl lg:px-8 sm:px-6">
23
+ <h2 class="sr-only">Checkout</h2>
24
+
25
+ <form class="lg:grid lg:grid-cols-2 lg:gap-x-12 xl:gap-x-16" onsubmit="event.preventDefault(); handleCheckoutSubmit(event)">
26
+ <!-- Left Column - Contact & Payment -->
27
+ <div>
28
+ <!-- Contact Information -->
29
+ <div>
30
+ <h2 class="text-lg text-gray-900 dark:text-gray-100 font-medium">
31
+ Contact information
32
+ </h2>
33
+
34
+ <div class="mt-4">
35
+ <label for="email-address" class="block text-sm text-gray-700 dark:text-gray-300 font-medium">
36
+ Email address
37
+ </label>
38
+ <div class="mt-1">
39
+ <input
40
+ id="email-address"
41
+ type="email"
42
+ name="email-address"
43
+ autocomplete="email"
44
+ required
45
+ class="block w-full border border-gray-300 dark:border-gray-600 rounded-md p-2 shadow-sm bg-white dark:bg-blue-gray-700 text-gray-900 dark:text-gray-100 focus:border-indigo-500 dark:focus:border-indigo-400 sm:text-sm focus:ring-indigo-500 dark:focus:ring-indigo-400"
46
+ />
47
+ </div>
48
+ </div>
49
+ </div>
50
+
51
+ <!-- Billing Information -->
52
+ <div class="mt-10 border-t border-gray-200 dark:border-gray-700 pt-10">
53
+ <h2 class="text-lg text-gray-900 dark:text-gray-100 font-medium">
54
+ Billing information
55
+ </h2>
56
+
57
+ <div class="mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:gap-x-4">
58
+ <div class="sm:col-span-2">
59
+ <label for="card-name" class="block text-sm text-gray-700 dark:text-gray-300 font-medium">
60
+ Name on card
61
+ </label>
62
+ <div class="mt-1">
63
+ <input
64
+ type="text"
65
+ id="card-name"
66
+ name="card-name"
67
+ autocomplete="cc-name"
68
+ required
69
+ class="block w-full border border-gray-300 dark:border-gray-600 rounded-md p-2 shadow-sm bg-white dark:bg-blue-gray-700 text-gray-900 dark:text-gray-100 focus:border-indigo-500 sm:text-sm focus:ring-indigo-500"
70
+ />
71
+ </div>
72
+ </div>
73
+
74
+ <!-- Stripe Element Mount Point -->
75
+ <div class="sm:col-span-2">
76
+ <div id="payment-element" class="mt-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-blue-gray-700 p-3"></div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+
81
+ <!-- Payment Type -->
82
+ <div class="mt-10 border-t border-gray-200 dark:border-gray-700 pt-10">
83
+ <h2 class="text-lg text-gray-900 dark:text-gray-100 font-medium">
84
+ Payment
85
+ </h2>
86
+
87
+ <fieldset class="mt-4">
88
+ <legend class="sr-only">Payment type</legend>
89
+ <div class="sm:flex sm:items-center space-y-4 sm:space-x-10 sm:space-y-0">
90
+ <div class="flex items-center">
91
+ <input
92
+ id="credit-card"
93
+ name="payment-type"
94
+ type="radio"
95
+ checked
96
+ class="h-4 w-4 border-gray-300 dark:border-gray-600 text-indigo-600 dark:text-indigo-400 focus:ring-indigo-500 dark:focus:ring-indigo-400"
97
+ />
98
+ <label for="credit-card" class="ml-3 block text-sm text-gray-700 dark:text-gray-300 font-medium">
99
+ Credit card
100
+ </label>
101
+ </div>
102
+ </div>
103
+ </fieldset>
104
+ </div>
105
+ </div>
106
+
107
+ <!-- Right Column - Order Summary -->
108
+ <div class="mt-10 lg:mt-0">
109
+ <h2 class="text-lg text-gray-900 dark:text-gray-100 font-medium">
110
+ Order summary
111
+ </h2>
112
+
113
+ <div class="mt-4 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-blue-gray-800 shadow-sm">
114
+ <h3 class="sr-only">Items in your cart</h3>
115
+
116
+ <!-- Product List -->
117
+ <ul role="list" class="divide-y divide-gray-200 dark:divide-gray-700">
118
+ @foreach(product in products)
119
+ <li class="flex px-4 py-6 sm:px-6">
120
+ <div class="flex-shrink-0">
121
+ @if(product.images)
122
+ <img src="{{ product.images }}" alt="{{ product.name }}" class="w-20 rounded-md" />
123
+ @else
124
+ <div class="w-20 h-20 bg-gray-200 dark:bg-gray-700 rounded-md flex items-center justify-center">
125
+ <svg class="h-8 w-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
126
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
127
+ </svg>
128
+ </div>
129
+ @endif
130
+ </div>
131
+
132
+ <div class="ml-6 flex flex-1 flex-col">
133
+ <div class="flex">
134
+ <div class="min-w-0 flex-1">
135
+ <h4 class="text-sm">
136
+ <a href="#" class="text-gray-700 dark:text-gray-300 font-medium hover:text-gray-800 dark:hover:text-gray-200">
137
+ {{ product.name }}
138
+ </a>
139
+ </h4>
140
+ </div>
141
+
142
+ @if(onRemoveProduct)
143
+ <div class="ml-4 flow-root flex-shrink-0">
144
+ <button
145
+ type="button"
146
+ class="flex items-center justify-center bg-white dark:bg-blue-gray-700 p-2.5 text-gray-400 dark:text-gray-500 -m-2.5 hover:text-gray-500 dark:hover:text-gray-400"
147
+ onclick="handleRemoveProduct('{{ product.id }}')"
148
+ title="Remove"
149
+ >
150
+ <span class="sr-only">Remove</span>
151
+ <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
152
+ <path fill-rule="evenodd" d="M8.75 1A2.75 2.75 0 0 0 6 3.75v.443c-.795.077-1.584.176-2.365.298a.75.75 0 1 0 .23 1.482l.149-.022.841 10.518A2.75 2.75 0 0 0 7.596 19h4.807a2.75 2.75 0 0 0 2.742-2.53l.841-10.52.149.023a.75.75 0 0 0 .23-1.482A41.03 41.03 0 0 0 14 4.193V3.75A2.75 2.75 0 0 0 11.25 1h-2.5ZM10 4c.84 0 1.673.025 2.5.075V3.75c0-.69-.56-1.25-1.25-1.25h-2.5c-.69 0-1.25.56-1.25 1.25v.325C8.327 4.025 9.16 4 10 4ZM8.58 7.72a.75.75 0 0 0-1.5.06l.3 7.5a.75.75 0 1 0 1.5-.06l-.3-7.5Zm4.34.06a.75.75 0 1 0-1.5-.06l-.3 7.5a.75.75 0 1 0 1.5.06l.3-7.5Z" clip-rule="evenodd" />
153
+ </svg>
154
+ </button>
155
+ </div>
156
+ @endif
157
+ </div>
158
+
159
+ <div class="flex flex-1 items-end justify-between pt-2">
160
+ <p class="mt-1 text-sm text-gray-900 dark:text-gray-100 font-medium">
161
+ ${{ (product.price / 100).toFixed(2) }}
162
+ </p>
163
+
164
+ <div class="ml-4">
165
+ <label for="quantity-{{ product.id }}" class="sr-only">Quantity</label>
166
+ <select
167
+ id="quantity-{{ product.id }}"
168
+ name="quantity"
169
+ class="border border-gray-300 dark:border-gray-600 rounded-md text-left text-base text-gray-700 dark:text-gray-300 font-medium shadow-sm bg-white dark:bg-blue-gray-700 focus:border-indigo-500 sm:text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500"
170
+ >
171
+ <option value="1" {{ (product.quantity || 1) === 1 ? 'selected' : '' }}>1</option>
172
+ <option value="2" {{ (product.quantity || 1) === 2 ? 'selected' : '' }}>2</option>
173
+ <option value="3" {{ (product.quantity || 1) === 3 ? 'selected' : '' }}>3</option>
174
+ <option value="4" {{ (product.quantity || 1) === 4 ? 'selected' : '' }}>4</option>
175
+ <option value="5" {{ (product.quantity || 1) === 5 ? 'selected' : '' }}>5</option>
176
+ <option value="6" {{ (product.quantity || 1) === 6 ? 'selected' : '' }}>6</option>
177
+ <option value="7" {{ (product.quantity || 1) === 7 ? 'selected' : '' }}>7</option>
178
+ <option value="8" {{ (product.quantity || 1) === 8 ? 'selected' : '' }}>8</option>
179
+ </select>
180
+ </div>
181
+ </div>
182
+ </div>
183
+ </li>
184
+ @endforeach
185
+ </ul>
186
+
187
+ <!-- Summary Totals -->
188
+ <dl class="border-t border-gray-200 dark:border-gray-700 px-4 py-6 space-y-6 sm:px-6">
189
+ <div class="flex items-center justify-between">
190
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Subtotal</dt>
191
+ <dd class="text-sm text-gray-900 dark:text-gray-100 font-medium">
192
+ ${{ (calculateSubtotal() / 100).toFixed(2) }}
193
+ </dd>
194
+ </div>
195
+
196
+ @if(shipping > 0)
197
+ <div class="flex items-center justify-between">
198
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Shipping</dt>
199
+ <dd class="text-sm text-gray-900 dark:text-gray-100 font-medium">
200
+ ${{ (shipping / 100).toFixed(2) }}
201
+ </dd>
202
+ </div>
203
+ @endif
204
+
205
+ @if(taxes > 0)
206
+ <div class="flex items-center justify-between">
207
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Taxes</dt>
208
+ <dd class="text-sm text-gray-900 dark:text-gray-100 font-medium">
209
+ ${{ (taxes / 100).toFixed(2) }}
210
+ </dd>
211
+ </div>
212
+ @endif
213
+
214
+ <div class="flex items-center justify-between border-t border-gray-200 dark:border-gray-700 pt-6">
215
+ <dt class="text-base text-gray-900 dark:text-gray-100 font-medium">Total</dt>
216
+ <dd class="text-base text-gray-900 dark:text-gray-100 font-medium">
217
+ ${{ (total / 100).toFixed(2) }}
218
+ </dd>
219
+ </div>
220
+ </dl>
221
+
222
+ <!-- Submit Button -->
223
+ <div class="border-t border-gray-200 dark:border-gray-700 px-4 py-6 sm:px-6">
224
+ <button
225
+ type="submit"
226
+ class="w-full border border-transparent rounded-md bg-indigo-600 dark:bg-indigo-500 px-4 py-3 text-base text-white font-medium shadow-sm hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 focus:ring-offset-gray-50 dark:focus:ring-offset-gray-900"
227
+ >
228
+ Confirm order
229
+ </button>
230
+ </div>
231
+ </div>
232
+ </div>
233
+ </form>
234
+ </div>
235
+ </div>
236
+
237
+ <script>
238
+ // Stripe integration
239
+ let stripe = null
240
+ let elements = null
241
+ let paymentElement = null
242
+
243
+ function handleRemoveProduct(productId) {
244
+ if (onRemoveProduct) {
245
+ onRemoveProduct(productId)
246
+ }
247
+ }
248
+
249
+ async function initializeStripe() {
250
+ if (!stripePublicKey) {
251
+ console.error('Stripe public key is required')
252
+ return
253
+ }
254
+
255
+ try {
256
+ // Load Stripe.js
257
+ if (typeof window !== 'undefined' && window.Stripe) {
258
+ stripe = window.Stripe(stripePublicKey)
259
+ } else {
260
+ // Dynamically load Stripe.js
261
+ const script = document.createElement('script')
262
+ script.src = 'https://js.stripe.com/v3/'
263
+ script.async = true
264
+ script.onload = () => {
265
+ stripe = window.Stripe(stripePublicKey)
266
+ setupStripeElements()
267
+ }
268
+ document.head.appendChild(script)
269
+ }
270
+
271
+ if (stripe) {
272
+ await setupStripeElements()
273
+ }
274
+ } catch (error) {
275
+ console.error('Error initializing Stripe:', error)
276
+ }
277
+ }
278
+
279
+ async function setupStripeElements() {
280
+ if (!stripe || !apiUrl) return
281
+
282
+ try {
283
+ // Create payment intent for subscription
284
+ const response = await fetch(`${apiUrl}/stripe/create-subscription`, {
285
+ method: 'POST',
286
+ headers: {
287
+ 'Content-Type': 'application/json',
288
+ 'Accept': 'application/json',
289
+ },
290
+ body: JSON.stringify({
291
+ amount: total,
292
+ quantity: products.reduce((sum, p) => sum + (p.quantity || 1), 0),
293
+ }),
294
+ })
295
+
296
+ const paymentIntent = await response.json()
297
+ const { clientSecret } = paymentIntent
298
+ if (!clientSecret) {
299
+ console.error('No client secret returned from API')
300
+ return
301
+ }
302
+
303
+ // Create elements instance
304
+ elements = stripe.elements({
305
+ clientSecret,
306
+ appearance: {
307
+ theme: document.documentElement.classList.contains('dark') ? 'night' : 'stripe',
308
+ },
309
+ })
310
+
311
+ // Create and mount payment element
312
+ paymentElement = elements.create('payment')
313
+ paymentElement.mount('#payment-element')
314
+ } catch (error) {
315
+ console.error('Error setting up Stripe elements:', error)
316
+ }
317
+ }
318
+
319
+ async function handleCheckoutSubmit(event) {
320
+ if (!stripe || !elements) {
321
+ console.error('Stripe not initialized')
322
+ return
323
+ }
324
+
325
+ try {
326
+ const { error } = await stripe.confirmPayment({
327
+ elements,
328
+ redirect: 'if_required',
329
+ confirmParams: {
330
+ return_url: window.location.href,
331
+ },
332
+ })
333
+
334
+ if (error) {
335
+ console.error('Payment error:', error)
336
+ alert(error.message)
337
+ } else {
338
+ // Success!
339
+ if (onSubmit) {
340
+ onSubmit({ success: true })
341
+ }
342
+ }
343
+ } catch (error) {
344
+ console.error('Checkout error:', error)
345
+ alert('An error occurred during checkout')
346
+ }
347
+ }
348
+
349
+ // Initialize on component mount
350
+ if (typeof window !== 'undefined') {
351
+ initializeStripe()
352
+ }
353
+ </script>
@@ -0,0 +1,74 @@
1
+ <script>
2
+ export const checked = $props.checked || false
3
+ export const disabled = $props.disabled || false
4
+ export const size = $props.size || 'md'
5
+ export const className = $props.className || ''
6
+ export const onChange = $props.onChange
7
+ export const label = $props.label || ''
8
+
9
+ // Size configurations using headwind
10
+ const sizeConfig = {
11
+ sm: {
12
+ track: 'h-5 w-9',
13
+ thumb: 'h-4 w-4',
14
+ translate: 'translate-x-4',
15
+ },
16
+ md: {
17
+ track: 'h-[38px] w-[74px]',
18
+ thumb: 'h-[34px] w-[34px]',
19
+ translate: 'translate-x-9',
20
+ },
21
+ lg: {
22
+ track: 'h-12 w-24',
23
+ thumb: 'h-11 w-11',
24
+ translate: 'translate-x-12',
25
+ },
26
+ }
27
+
28
+ const config = sizeConfig[size]
29
+
30
+ // Track classes
31
+ const trackBaseClasses = 'relative inline-flex shrink-0 cursor-pointer border-2 border-transparent rounded-full transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-300 dark:focus-visible:ring-offset-neutral-700'
32
+ const trackStateClasses = checked
33
+ ? 'bg-blue-500 dark:bg-blue-600'
34
+ : 'bg-neutral-400 dark:bg-neutral-600'
35
+ const trackDisabledClasses = disabled
36
+ ? 'opacity-50 cursor-not-allowed pointer-events-none'
37
+ : ''
38
+
39
+ export const trackClasses = `${trackBaseClasses} ${trackStateClasses} ${trackDisabledClasses} ${config.track} ${className}`.trim()
40
+
41
+ // Thumb classes
42
+ const thumbBaseClasses = 'pointer-events-none inline-block transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out'
43
+ const thumbPositionClasses = checked ? config.translate : 'translate-x-0'
44
+ export const thumbClasses = `${thumbBaseClasses} ${thumbPositionClasses} ${config.thumb}`.trim()
45
+
46
+ // Toggle function
47
+ export function toggle() {
48
+ if (disabled) return
49
+ onChange?.(!checked)
50
+ }
51
+ </script>
52
+
53
+ <label class="inline-flex items-center gap-3 cursor-pointer">
54
+ @if(label)
55
+ <span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">
56
+ {{ label }}
57
+ </span>
58
+ @endif
59
+
60
+ <button
61
+ type="button"
62
+ role="switch"
63
+ aria-checked="{{ checked ? 'true' : 'false' }}"
64
+ class="{{ trackClasses }}"
65
+ @if(disabled)disabled aria-disabled="true"@endif
66
+ @click="toggle"
67
+ >
68
+ <span class="sr-only">@if(label){{ label }}@else Toggle switch@endif</span>
69
+ <span
70
+ aria-hidden="true"
71
+ class="{{ thumbClasses }}"
72
+ />
73
+ </button>
74
+ </label>
@@ -0,0 +1,14 @@
1
+ <script>
2
+ export const className = $props.className || ''
3
+ export const striped = $props.striped || false
4
+ export const hoverable = $props.hoverable !== false
5
+
6
+ const baseClasses = 'min-w-full divide-y divide-neutral-200 dark:divide-neutral-700'
7
+ const tableClasses = `${baseClasses} ${className}`.trim()
8
+ </script>
9
+
10
+ <div class="overflow-x-auto">
11
+ <table class="{{ tableClasses }}">
12
+ <slot :striped="striped" :hoverable="hoverable" />
13
+ </table>
14
+ </div>
@@ -0,0 +1,10 @@
1
+ <script>
2
+ export const className = $props.className || ''
3
+
4
+ const baseClasses = 'bg-white divide-y divide-neutral-200 dark:bg-neutral-900 dark:divide-neutral-700'
5
+ const bodyClasses = `${baseClasses} ${className}`.trim()
6
+ </script>
7
+
8
+ <tbody class="{{ bodyClasses }}">
9
+ <slot />
10
+ </tbody>
@@ -0,0 +1,10 @@
1
+ <script>
2
+ export const className = $props.className || ''
3
+
4
+ const baseClasses = 'px-6 py-4 whitespace-nowrap text-sm text-neutral-900 dark:text-neutral-100'
5
+ const cellClasses = `${baseClasses} ${className}`.trim()
6
+ </script>
7
+
8
+ <td class="{{ cellClasses }}">
9
+ <slot />
10
+ </td>
@@ -0,0 +1,10 @@
1
+ <script>
2
+ export const className = $props.className || ''
3
+
4
+ const baseClasses = 'bg-neutral-50 dark:bg-neutral-800'
5
+ const headClasses = `${baseClasses} ${className}`.trim()
6
+ </script>
7
+
8
+ <thead class="{{ headClasses }}">
9
+ <slot />
10
+ </thead>
@@ -0,0 +1,13 @@
1
+ <script>
2
+ export const className = $props.className || ''
3
+ export const sortable = $props.sortable || false
4
+ export const onClick = $props.onClick
5
+
6
+ const baseClasses = 'px-6 py-3 text-left text-xs font-medium text-neutral-500 uppercase tracking-wider dark:text-neutral-400'
7
+ const sortableClasses = sortable ? 'cursor-pointer hover:text-neutral-700 dark:hover:text-neutral-200' : ''
8
+ const headerClasses = `${baseClasses} ${sortableClasses} ${className}`.trim()
9
+ </script>
10
+
11
+ <th class="{{ headerClasses }}" @if(sortable)@click="onClick"@endif>
12
+ <slot />
13
+ </th>
@@ -0,0 +1,11 @@
1
+ <script>
2
+ export const className = $props.className || ''
3
+ export const hoverable = $props.hoverable !== false
4
+
5
+ const hoverClasses = hoverable ? 'hover:bg-neutral-50 dark:hover:bg-neutral-800' : ''
6
+ const rowClasses = `${hoverClasses} ${className}`.trim()
7
+ </script>
8
+
9
+ <tr class="{{ rowClasses }}">
10
+ <slot />
11
+ </tr>
@@ -0,0 +1,145 @@
1
+ <script>
2
+ export const tabs = $props.tabs || []
3
+ export const defaultTab = $props.defaultTab || 0
4
+ export const orientation = $props.orientation || 'horizontal'
5
+ export const variant = $props.variant || 'line'
6
+ export const className = $props.className || ''
7
+ export const onChange = $props.onChange
8
+
9
+ let activeTab = defaultTab
10
+
11
+ function selectTab(index) {
12
+ activeTab = index
13
+ if (onChange) {
14
+ onChange(index, tabs[index])
15
+ }
16
+ }
17
+
18
+ // Orientation classes
19
+ const orientationClasses = {
20
+ horizontal: 'flex-col',
21
+ vertical: 'flex-row',
22
+ }
23
+
24
+ // Tab list orientation classes
25
+ const tabListOrientationClasses = {
26
+ horizontal: 'flex-row border-b',
27
+ vertical: 'flex-col border-r',
28
+ }
29
+
30
+ // Variant classes for tab list
31
+ const variantClasses = {
32
+ line: 'border-gray-200 dark:border-gray-700',
33
+ pills: 'gap-2 border-0 bg-gray-100 dark:bg-gray-800 p-1 rounded-lg',
34
+ enclosed: 'border-gray-200 dark:border-gray-700',
35
+ }
36
+
37
+ // Tab button classes based on variant
38
+ function getTabClasses(index, variant, orientation) {
39
+ const isActive = index === activeTab
40
+ const baseClasses = 'px-4 py-2 font-medium text-sm transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900'
41
+
42
+ if (variant === 'line') {
43
+ const borderPosition = orientation === 'horizontal' ? 'border-b-2' : 'border-r-2'
44
+ return `${baseClasses} ${borderPosition} ${isActive ? 'border-blue-500 text-blue-600 dark:text-blue-400' : 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:border-gray-300 dark:hover:border-gray-600'}`
45
+ } else if (variant === 'pills') {
46
+ return `${baseClasses} rounded-md ${isActive ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'}`
47
+ } else if (variant === 'enclosed') {
48
+ const borderClasses = orientation === 'horizontal' ? 'border-t border-l border-r rounded-t-md' : 'border-t border-l border-b rounded-l-md'
49
+ return `${baseClasses} ${borderClasses} ${isActive ? 'bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 -mb-px' : 'bg-gray-50 dark:bg-gray-900 border-transparent hover:border-gray-200 dark:hover:border-gray-700'}`
50
+ }
51
+ }
52
+
53
+ export const containerClasses = `flex ${orientationClasses[orientation]} ${className}`.trim()
54
+ export const tabListClasses = `flex ${tabListOrientationClasses[orientation]} ${variantClasses[variant]}`.trim()
55
+ export const tabPanelClasses = `p-4 focus:outline-none`.trim()
56
+ </script>
57
+
58
+ <div class="{{ containerClasses }}">
59
+ <!-- Tab List -->
60
+ <div
61
+ class="{{ tabListClasses }}"
62
+ role="tablist"
63
+ aria-orientation="{{ orientation }}"
64
+ >
65
+ @foreach(tab in tabs)
66
+ <button
67
+ type="button"
68
+ class="{{ getTabClasses($loop.index, variant, orientation) }}"
69
+ role="tab"
70
+ aria-selected="{{ activeTab === $loop.index ? 'true' : 'false' }}"
71
+ aria-controls="tab-panel-{{ $loop.index }}"
72
+ id="tab-{{ $loop.index }}"
73
+ tabindex="{{ activeTab === $loop.index ? '0' : '-1' }}"
74
+ onclick="selectTab({{ $loop.index }})"
75
+ onkeydown="handleKeyDown(event, {{ $loop.index }})"
76
+ >
77
+ @if(tab.icon)
78
+ <span class="inline-flex items-center gap-2">
79
+ <span innerHTML="{!! tab.icon !!}"></span>
80
+ <span>{{ tab.label }}</span>
81
+ </span>
82
+ @else
83
+ {{ tab.label }}
84
+ @endif
85
+ </button>
86
+ @endforeach
87
+ </div>
88
+
89
+ <!-- Tab Panels -->
90
+ @foreach(tab in tabs)
91
+ @if(activeTab === $loop.index)
92
+ <div
93
+ id="tab-panel-{{ $loop.index }}"
94
+ class="{{ tabPanelClasses }}"
95
+ role="tabpanel"
96
+ aria-labelledby="tab-{{ $loop.index }}"
97
+ tabindex="0"
98
+ >
99
+ {!! tab.content !!}
100
+ </div>
101
+ @endif
102
+ @endforeach
103
+ </div>
104
+
105
+ <script>
106
+ function handleKeyDown(event, currentIndex) {
107
+ const tabCount = tabs.length
108
+ let newIndex = currentIndex
109
+
110
+ if (orientation === 'horizontal') {
111
+ if (event.key === 'ArrowRight') {
112
+ event.preventDefault()
113
+ newIndex = (currentIndex + 1) % tabCount
114
+ } else if (event.key === 'ArrowLeft') {
115
+ event.preventDefault()
116
+ newIndex = (currentIndex - 1 + tabCount) % tabCount
117
+ }
118
+ } else {
119
+ if (event.key === 'ArrowDown') {
120
+ event.preventDefault()
121
+ newIndex = (currentIndex + 1) % tabCount
122
+ } else if (event.key === 'ArrowUp') {
123
+ event.preventDefault()
124
+ newIndex = (currentIndex - 1 + tabCount) % tabCount
125
+ }
126
+ }
127
+
128
+ if (event.key === 'Home') {
129
+ event.preventDefault()
130
+ newIndex = 0
131
+ } else if (event.key === 'End') {
132
+ event.preventDefault()
133
+ newIndex = tabCount - 1
134
+ }
135
+
136
+ if (newIndex !== currentIndex) {
137
+ selectTab(newIndex)
138
+ // Focus the new tab
139
+ if (typeof window !== 'undefined') {
140
+ const newTab = document.getElementById(`tab-${newIndex}`)
141
+ if (newTab) newTab.focus()
142
+ }
143
+ }
144
+ }
145
+ </script>