@stacksjs/defaults 0.70.54

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 (286) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +2 -0
  3. package/package.json +20 -0
  4. package/resources/assets/fonts/013717e9.woff +0 -0
  5. package/resources/assets/fonts/03355817.woff2 +0 -0
  6. package/resources/assets/fonts/0380de7e.woff2 +0 -0
  7. package/resources/assets/fonts/0ba5d32a.woff +0 -0
  8. package/resources/assets/fonts/0bd57ddf.woff2 +0 -0
  9. package/resources/assets/fonts/19de0a60.woff2 +0 -0
  10. package/resources/assets/fonts/20b87e84.woff +0 -0
  11. package/resources/assets/fonts/4bbc67e1.woff +0 -0
  12. package/resources/assets/fonts/576fb0d7.woff +0 -0
  13. package/resources/assets/fonts/5ea10ee5.woff2 +0 -0
  14. package/resources/assets/fonts/6413fc0a.woff +0 -0
  15. package/resources/assets/fonts/72c114cf.woff +0 -0
  16. package/resources/assets/fonts/73c57e73.woff2 +0 -0
  17. package/resources/assets/fonts/813dca6c.woff +0 -0
  18. package/resources/assets/fonts/8a9b0995.woff2 +0 -0
  19. package/resources/assets/fonts/90b4832f.woff +0 -0
  20. package/resources/assets/fonts/9394ab85.woff2 +0 -0
  21. package/resources/assets/fonts/9b3c7d7f.woff2 +0 -0
  22. package/resources/assets/fonts/Monaco.ttf +0 -0
  23. package/resources/assets/fonts/b1b36d74.woff +0 -0
  24. package/resources/assets/fonts/b5d74798.woff2 +0 -0
  25. package/resources/assets/fonts/b9f81a4d.woff +0 -0
  26. package/resources/assets/fonts/c9784008.woff2 +0 -0
  27. package/resources/assets/fonts/cf692227.woff +0 -0
  28. package/resources/assets/fonts/d1c6ab51.woff2 +0 -0
  29. package/resources/assets/fonts/e2eb62fc.woff +0 -0
  30. package/resources/assets/fonts/fe21f8d8.woff2 +0 -0
  31. package/resources/assets/fonts/nps/CampmateScript-Regular.woff2 +0 -0
  32. package/resources/assets/fonts/nps/NPS_2026-variable.woff2 +0 -0
  33. package/resources/assets/fonts/nps/RedwoodSerif-Regular.woff2 +0 -0
  34. package/resources/assets/fonts/nps/SequoiaSans-Regular.woff2 +0 -0
  35. package/resources/assets/fonts/nps/Switchback-Regular.woff2 +0 -0
  36. package/resources/assets/images/app-icon.png +0 -0
  37. package/resources/assets/images/bg-img.png +0 -0
  38. package/resources/assets/images/park-ridge.svg +15 -0
  39. package/resources/assets/images/topography.svg +1 -0
  40. package/resources/assets/scripts/main.ts +2153 -0
  41. package/resources/assets/styles/README.md +19 -0
  42. package/resources/assets/styles/docs/main.css +1 -0
  43. package/resources/assets/styles/docs/overrides.css +1 -0
  44. package/resources/assets/styles/docs/vars.css +8 -0
  45. package/resources/assets/styles/main.css +1419 -0
  46. package/resources/components/Button.stx +70 -0
  47. package/resources/components/Buttons/BaseButton.stx +39 -0
  48. package/resources/components/Buttons/Counter.stx +28 -0
  49. package/resources/components/Buttons/ToggleDark.stx +11 -0
  50. package/resources/components/Container.stx +10 -0
  51. package/resources/components/Dashboard/.gitkeep +0 -0
  52. package/resources/components/Dashboard/Auth/ForgotPassword.stx +127 -0
  53. package/resources/components/Dashboard/Auth/Login.stx +212 -0
  54. package/resources/components/Dashboard/Auth/Register.stx +306 -0
  55. package/resources/components/Dashboard/Avatar.stx +123 -0
  56. package/resources/components/Dashboard/Billing/ActivePlan.stx +57 -0
  57. package/resources/components/Dashboard/Billing/CardBrands.stx +32 -0
  58. package/resources/components/Dashboard/Billing/CardForm.stx +34 -0
  59. package/resources/components/Dashboard/Billing/OneTimePayment.stx +28 -0
  60. package/resources/components/Dashboard/Billing/PaymentForm.stx +33 -0
  61. package/resources/components/Dashboard/Billing/PaymentMethod.stx +66 -0
  62. package/resources/components/Dashboard/Billing/PaymentMethodList.stx +52 -0
  63. package/resources/components/Dashboard/Billing/Plans.stx +136 -0
  64. package/resources/components/Dashboard/Buttons/AppButton.stx +48 -0
  65. package/resources/components/Dashboard/Buttons/IconButton.stx +20 -0
  66. package/resources/components/Dashboard/Card.stx +78 -0
  67. package/resources/components/Dashboard/Chart.stx +75 -0
  68. package/resources/components/Dashboard/CodeEditor.stx +41 -0
  69. package/resources/components/Dashboard/Commerce/BulkActionBar.stx +70 -0
  70. package/resources/components/Dashboard/Commerce/CategoriesTable.stx +133 -0
  71. package/resources/components/Dashboard/Commerce/CouponsTable.stx +161 -0
  72. package/resources/components/Dashboard/Commerce/CustomersTable.stx +142 -0
  73. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesTable.stx +80 -0
  74. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryTable.stx +85 -0
  75. package/resources/components/Dashboard/Commerce/Delivery/DriversTable.stx +99 -0
  76. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysTable.stx +92 -0
  77. package/resources/components/Dashboard/Commerce/Delivery/LicenseTemplatesTable.stx +88 -0
  78. package/resources/components/Dashboard/Commerce/Delivery/Pagination.stx +122 -0
  79. package/resources/components/Dashboard/Commerce/Delivery/SearchFilter.stx +23 -0
  80. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsTable.stx +80 -0
  81. package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesTable.stx +59 -0
  82. package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesTable.stx +71 -0
  83. package/resources/components/Dashboard/Commerce/Delivery/TabNavigation.stx +36 -0
  84. package/resources/components/Dashboard/Commerce/Forms/CouponsForm.stx +191 -0
  85. package/resources/components/Dashboard/Commerce/OrdersTable.stx +131 -0
  86. package/resources/components/Dashboard/Commerce/ProductsTable.stx +170 -0
  87. package/resources/components/Dashboard/CreateRecordModal.stx +363 -0
  88. package/resources/components/Dashboard/DashboardLayout.stx +155 -0
  89. package/resources/components/Dashboard/Deployments/ActivityFeed.stx +54 -0
  90. package/resources/components/Dashboard/Deployments/DeployScript.stx +23 -0
  91. package/resources/components/Dashboard/Deployments/DeploymentHistory.stx +97 -0
  92. package/resources/components/Dashboard/Deployments/DeploymentList.stx +94 -0
  93. package/resources/components/Dashboard/Deployments/LiveTerminalOutput.stx +18 -0
  94. package/resources/components/Dashboard/Elements/Dropdown.stx +57 -0
  95. package/resources/components/Dashboard/Email/EmailActivityChart.stx +111 -0
  96. package/resources/components/Dashboard/Email/EmailCompose.stx +72 -0
  97. package/resources/components/Dashboard/Email/EmailDetail.stx +105 -0
  98. package/resources/components/Dashboard/Email/EmailList.stx +117 -0
  99. package/resources/components/Dashboard/Email/EmailSearch.stx +40 -0
  100. package/resources/components/Dashboard/Email/EmailSidebar.stx +133 -0
  101. package/resources/components/Dashboard/GlobalSearch.stx +124 -0
  102. package/resources/components/Dashboard/MobileSidebar.stx +47 -0
  103. package/resources/components/Dashboard/Modals/BaseModal.stx +50 -0
  104. package/resources/components/Dashboard/Modals/Popups/Alert.stx +112 -0
  105. package/resources/components/Dashboard/Modals/Popups/Toast.stx +70 -0
  106. package/resources/components/Dashboard/Modals/ToastWrapper.stx +35 -0
  107. package/resources/components/Dashboard/Monitoring/ErrorDetailModal.stx +228 -0
  108. package/resources/components/Dashboard/Monitoring/ErrorsTable.stx +184 -0
  109. package/resources/components/Dashboard/Navbar.stx +194 -0
  110. package/resources/components/Dashboard/NavbarModern.stx +188 -0
  111. package/resources/components/Dashboard/NotificationErrorModal.stx +105 -0
  112. package/resources/components/Dashboard/NotificationStatusBadge.stx +20 -0
  113. package/resources/components/Dashboard/Pagination.stx +90 -0
  114. package/resources/components/Dashboard/Queue/QueueTable.stx +127 -0
  115. package/resources/components/Dashboard/Settings/Forms/AIForm.stx +126 -0
  116. package/resources/components/Dashboard/Settings/Forms/AnalyticsForm.stx +52 -0
  117. package/resources/components/Dashboard/Settings/Forms/AppForm.stx +157 -0
  118. package/resources/components/Dashboard/Settings/Forms/CLIForm.stx +66 -0
  119. package/resources/components/Dashboard/Settings/Forms/CacheForm.stx +253 -0
  120. package/resources/components/Dashboard/Settings/Forms/CloudForm.stx +441 -0
  121. package/resources/components/Dashboard/Settings/Forms/DNSForm.stx +307 -0
  122. package/resources/components/Dashboard/Settings/Forms/DatabaseForm.stx +168 -0
  123. package/resources/components/Dashboard/Settings/Forms/EmailForm.stx +100 -0
  124. package/resources/components/Dashboard/Settings/Forms/FileSystemForm.stx +259 -0
  125. package/resources/components/Dashboard/Settings/Forms/HashingForm.stx +119 -0
  126. package/resources/components/Dashboard/Settings/Forms/LibraryForm.stx +389 -0
  127. package/resources/components/Dashboard/Settings/Forms/QueueForm.stx +219 -0
  128. package/resources/components/Dashboard/Settings/Forms/SearchEngineForm.stx +48 -0
  129. package/resources/components/Dashboard/Settings/Forms/SecurityForm.stx +143 -0
  130. package/resources/components/Dashboard/Settings/Forms/ServicesForm.stx +194 -0
  131. package/resources/components/Dashboard/Settings/Forms/StorageForm.stx +191 -0
  132. package/resources/components/Dashboard/Settings/Forms/TeamsForm.stx +33 -0
  133. package/resources/components/Dashboard/Settings/SettingsFormManager.stx +181 -0
  134. package/resources/components/Dashboard/SettingsHeader.stx +88 -0
  135. package/resources/components/Dashboard/SettingsSidebar.stx +234 -0
  136. package/resources/components/Dashboard/Sidebar.stx +23 -0
  137. package/resources/components/Dashboard/SidebarModern.stx +249 -0
  138. package/resources/components/Dashboard/Skeleton/LoadingCard.stx +15 -0
  139. package/resources/components/Dashboard/Skeleton/LoadingDetails.stx +26 -0
  140. package/resources/components/Dashboard/Table.stx +82 -0
  141. package/resources/components/Dashboard/TerminalContainer.stx +97 -0
  142. package/resources/components/Dashboard/Tooltip.stx +166 -0
  143. package/resources/components/Dashboard/Transaction/index.stx +88 -0
  144. package/resources/components/Dashboard/UI/ActivityTable.stx +54 -0
  145. package/resources/components/Dashboard/UI/Avatar.stx +102 -0
  146. package/resources/components/Dashboard/UI/Badge.stx +66 -0
  147. package/resources/components/Dashboard/UI/Button.stx +73 -0
  148. package/resources/components/Dashboard/UI/Card.stx +51 -0
  149. package/resources/components/Dashboard/UI/ChartCard.stx +36 -0
  150. package/resources/components/Dashboard/UI/Checkbox.stx +74 -0
  151. package/resources/components/Dashboard/UI/ConfirmDialog.stx +74 -0
  152. package/resources/components/Dashboard/UI/DataTable.stx +45 -0
  153. package/resources/components/Dashboard/UI/Dropdown.stx +72 -0
  154. package/resources/components/Dashboard/UI/DropdownItem.stx +80 -0
  155. package/resources/components/Dashboard/UI/EmptyState.stx +50 -0
  156. package/resources/components/Dashboard/UI/FilterBar.stx +48 -0
  157. package/resources/components/Dashboard/UI/Input.stx +77 -0
  158. package/resources/components/Dashboard/UI/Modal.stx +75 -0
  159. package/resources/components/Dashboard/UI/PageHeader.stx +69 -0
  160. package/resources/components/Dashboard/UI/PageLayout.stx +84 -0
  161. package/resources/components/Dashboard/UI/Pagination.stx +126 -0
  162. package/resources/components/Dashboard/UI/QuickLinks.stx +31 -0
  163. package/resources/components/Dashboard/UI/RouterLink.stx +31 -0
  164. package/resources/components/Dashboard/UI/Select.stx +84 -0
  165. package/resources/components/Dashboard/UI/ServiceHealth.stx +38 -0
  166. package/resources/components/Dashboard/UI/StatsCard.stx +79 -0
  167. package/resources/components/Dashboard/UI/Table.stx +143 -0
  168. package/resources/components/Dashboard/UI/Tabs.stx +45 -0
  169. package/resources/components/Dashboard/UI/Textarea.stx +93 -0
  170. package/resources/components/Dashboard/UI/Toggle.stx +84 -0
  171. package/resources/components/Dashboard/UI/WindowControls.stx +135 -0
  172. package/resources/components/Dashboard/Widget.stx +89 -0
  173. package/resources/components/Dashboard/WindowControls.stx +119 -0
  174. package/resources/components/Dashboard/index.ts +106 -0
  175. package/resources/components/Docs/Demo/ComboboxCode.md +114 -0
  176. package/resources/components/Docs/Demo/ComboboxDemo.stx +91 -0
  177. package/resources/components/Docs/Demo/DialogCode.md +42 -0
  178. package/resources/components/Docs/Demo/DialogDemo.stx +76 -0
  179. package/resources/components/Docs/Demo/DocsPlayground.stx +39 -0
  180. package/resources/components/Docs/Demo/DropdownCode.md +109 -0
  181. package/resources/components/Docs/Demo/DropdownDemo.stx +82 -0
  182. package/resources/components/Docs/Demo/Hero.stx +100 -0
  183. package/resources/components/Docs/Demo/ListboxCode.md +87 -0
  184. package/resources/components/Docs/Demo/ListboxDemo.stx +59 -0
  185. package/resources/components/Docs/Demo/NotificationCode.md +173 -0
  186. package/resources/components/Docs/Demo/NotificationDemo.stx +99 -0
  187. package/resources/components/Docs/Demo/PopoverCode.md +161 -0
  188. package/resources/components/Docs/Demo/PopoverDemo.stx +96 -0
  189. package/resources/components/Docs/Demo/RadioGroupCode.md +98 -0
  190. package/resources/components/Docs/Demo/RadioGroupDemo.stx +53 -0
  191. package/resources/components/Docs/Demo/StepperCode.md +216 -0
  192. package/resources/components/Docs/Demo/StepperDemo.stx +137 -0
  193. package/resources/components/Docs/Demo/SwitchCode.md +24 -0
  194. package/resources/components/Docs/Demo/SwitchDemo.stx +28 -0
  195. package/resources/components/Docs/Home.stx +7 -0
  196. package/resources/components/Docs/HomeContributors.stx +23 -0
  197. package/resources/components/Docs/HomeSponsors.stx +17 -0
  198. package/resources/components/Docs/HomeTeam.stx +18 -0
  199. package/resources/components/Docs/TeamMember.stx +79 -0
  200. package/resources/components/Email/EmailButton.stx +32 -0
  201. package/resources/components/Email/EmailDivider.stx +20 -0
  202. package/resources/components/Email/EmailImage.stx +24 -0
  203. package/resources/components/Email/EmailLayout.stx +55 -0
  204. package/resources/components/Email/EmailLink.stx +22 -0
  205. package/resources/components/Email/EmailSection.stx +20 -0
  206. package/resources/components/Email/EmailText.stx +30 -0
  207. package/resources/components/Forum/ForumBreadcrumb.stx +38 -0
  208. package/resources/components/Forum/ForumCategory.stx +16 -0
  209. package/resources/components/Forum/ForumCategoryItem.stx +46 -0
  210. package/resources/components/Forum/ForumLayout.stx +36 -0
  211. package/resources/components/Forum/ForumPagination.stx +39 -0
  212. package/resources/components/Forum/ForumPost.stx +71 -0
  213. package/resources/components/Forum/ForumReplyForm.stx +76 -0
  214. package/resources/components/Forum/ForumSidebar.stx +86 -0
  215. package/resources/components/Forum/ForumTopicItem.stx +77 -0
  216. package/resources/components/Forum/ForumTopicList.stx +23 -0
  217. package/resources/components/HelloWorld.stx +253 -0
  218. package/resources/components/Logo.stx +10 -0
  219. package/resources/components/Marketing/CallToAction.stx +30 -0
  220. package/resources/components/Marketing/ComingSoon.stx +155 -0
  221. package/resources/components/Marketing/Faqs.stx +99 -0
  222. package/resources/components/Marketing/Feature.stx +32 -0
  223. package/resources/components/Marketing/Fields.stx +28 -0
  224. package/resources/components/Marketing/Footer.stx +33 -0
  225. package/resources/components/Marketing/Header.stx +51 -0
  226. package/resources/components/Marketing/Hero.stx +46 -0
  227. package/resources/components/Marketing/Layout.stx +26 -0
  228. package/resources/components/Marketing/NavLink.stx +13 -0
  229. package/resources/components/Marketing/Plan.stx +43 -0
  230. package/resources/components/Marketing/Pricing.stx +120 -0
  231. package/resources/components/Marketing/PrimaryFeatures.stx +80 -0
  232. package/resources/components/Marketing/PrimaryFeatures2.stx +98 -0
  233. package/resources/components/Marketing/SecondaryFeatures.stx +80 -0
  234. package/resources/components/Marketing/Testimonials.stx +120 -0
  235. package/resources/components/Pagination.stx +149 -0
  236. package/resources/components/Playground.stx +9 -0
  237. package/resources/components/README.md +87 -0
  238. package/resources/components/Storefront/AnnouncementBar.stx +17 -0
  239. package/resources/components/Storefront/CartDrawer.stx +227 -0
  240. package/resources/components/Storefront/CheckoutSteps.stx +50 -0
  241. package/resources/components/Storefront/Footer.stx +81 -0
  242. package/resources/components/Storefront/Header.stx +66 -0
  243. package/resources/components/Storefront/OrderSummary.stx +79 -0
  244. package/resources/components/Storefront/ProductCard.stx +63 -0
  245. package/resources/components/Storefront/ProductGallery.stx +88 -0
  246. package/resources/components/Storefront/QuantitySelector.stx +97 -0
  247. package/resources/components/index.ts +13 -0
  248. package/resources/emails/email-verification.stx +46 -0
  249. package/resources/emails/layouts/base.html +113 -0
  250. package/resources/emails/password-changed.stx +55 -0
  251. package/resources/emails/password-reset.stx +43 -0
  252. package/resources/emails/subscription-confirmation.stx +99 -0
  253. package/resources/emails/welcome.stx +48 -0
  254. package/resources/functions/README.md +28 -0
  255. package/resources/functions/counter.ts +13 -0
  256. package/resources/functions/dark.ts +4 -0
  257. package/resources/functions/dashboard/analytics.ts +238 -0
  258. package/resources/functions/dashboard/config-io.ts +316 -0
  259. package/resources/functions/dashboard/data.ts +375 -0
  260. package/resources/functions/dashboard/library.ts +250 -0
  261. package/resources/functions/dashboard/sidebar.ts +537 -0
  262. package/resources/functions/dashboard/system.ts +150 -0
  263. package/resources/layouts/403.stx +188 -0
  264. package/resources/layouts/404.stx +34 -0
  265. package/resources/layouts/README.md +14 -0
  266. package/resources/layouts/dashboard/default.stx +20 -0
  267. package/resources/layouts/dashboard/guest.stx +10 -0
  268. package/resources/layouts/default.stx +3 -0
  269. package/resources/layouts/maintenance.stx +11 -0
  270. package/resources/layouts/storefront.stx +73 -0
  271. package/resources/layouts/system-tray/default.stx +3 -0
  272. package/resources/modules/.gitkeep +0 -0
  273. package/resources/plugins/.gitkeep +0 -0
  274. package/resources/plugins/preloader.ts +308 -0
  275. package/resources/types/README.md +3 -0
  276. package/resources/views/cart.stx +149 -0
  277. package/resources/views/checkout/contact.stx +101 -0
  278. package/resources/views/checkout/payment.stx +114 -0
  279. package/resources/views/checkout/shipping.stx +120 -0
  280. package/resources/views/coming-soon.stx +9 -0
  281. package/resources/views/dashboard/custom-page.stx +45 -0
  282. package/resources/views/dashboard/index.stx +24 -0
  283. package/resources/views/emails/welcome.stx +0 -0
  284. package/resources/views/errors/tester.stx +26 -0
  285. package/resources/views/index.stx +2865 -0
  286. package/resources/views/orders/[id].stx +130 -0
@@ -0,0 +1,120 @@
1
+ <script>
2
+ interface TestimonialsProps {
3
+ testimonials?: string
4
+ }
5
+
6
+ const { testimonials = [ } = defineProps<TestimonialsProps>()
7
+
8
+ [
9
+ {
10
+ content: 'Stacks is a rapid development framework that has helped me create and maintain frontends, backends, and clouds without worrying about the boilerplate.',
11
+ author: {
12
+ name: 'Sheryl Berge',
13
+ role: 'CEO at Lynch LLC',
14
+ image: '/images/avatars/avatar-1.png',
15
+ },
16
+ },
17
+ {
18
+ content: 'The Stacks mantra, "Convention over configuration", has made my development process much smoother and efficient.',
19
+ author: {
20
+ name: 'Amy Hahn',
21
+ role: 'Director at Velocity Industries',
22
+ image: '/images/avatars/avatar-4.png',
23
+ },
24
+ },
25
+ ],
26
+ [
27
+ {
28
+ content: "Getting started with Stacks was incredibly easy. The Buddy toolkit has been a game changer for my project. I suggest you guys give it a try today! It's probably one of the best decisions you'll ever do for yourself this year.",
29
+ author: {
30
+ name: 'Leland Kiehn',
31
+ role: 'Founder of Kiehn and Sons',
32
+ image: '/images/avatars/avatar-5.png',
33
+ },
34
+ },
35
+ {
36
+ content: 'The Stacks framework is a harmony of several engines that has allowed me to build web and desktop applications in highly scalable and privacy-friendly ways.',
37
+ author: {
38
+ name: 'Erin Powlowski',
39
+ role: 'COO at Armstrong Inc',
40
+ image: '/images/avatars/avatar-2.png',
41
+ },
42
+ },
43
+ ],
44
+ [
45
+ {
46
+ content: 'Stacks has enhanced my productivity as a developer. No more creating boilerplate, and the IDE integration is fantastic.',
47
+ author: {
48
+ name: 'Peter Renolds',
49
+ role: 'Founder of West Inc',
50
+ image: '/images/avatars/avatar-3.png',
51
+ },
52
+ },
53
+ {
54
+ content: 'The Stacks CI/CD has allowed me to focus on coding, not publishing. The CI/CD is superb!',
55
+ author: {
56
+ name: 'Amy Hahn',
57
+ role: 'Director at Velocity Industries',
58
+ image: '/images/avatars/avatar-4.png',
59
+ },
60
+ },
61
+ ],
62
+ ]
63
+ </script>
64
+ <section
65
+ id="testimonials"
66
+ aria-label="What our customers are saying"
67
+ class="py-20 sm:py-32 bg-slate-50"
68
+ >
69
+ <Container>
70
+ <div class="mx-auto max-w-2xl md:text-center">
71
+ <h2 class="font-display text-3xl text-slate-900 tracking-tight sm:text-4xl">
72
+ Enjoyed by businesses and highly recommended by devs across the globe.
73
+ </h2>
74
+ <p class="mt-4 text-lg text-slate-700 tracking-tight">
75
+ The goal of the framework is to help you create & maintain frontends, backends, and clouds—without having to worry about the boilerplate. Stacks is a rapid development framework, meeting all your full stack needs.
76
+ </p>
77
+ </div>
78
+ <ul
79
+ role="list"
80
+ class="grid gap-6 grid-cols-1 sm:gap-8 lg:grid-cols-3 mt-16 mx-auto lg:mt-20 max-w-2xl lg:max-w-none"
81
+ >
82
+ @foreach(testimonials as column)
83
+ <li>
84
+ <ul role="list" class="flex flex-col gap-y-6 sm:gap-y-8">
85
+ @foreach(column as testimonial)
86
+ <li>
87
+ <figure class="relative p-6 bg-white rounded-2xl shadow-slate-900/10 shadow-xl">
88
+ <blockquote class="relative">
89
+ <p class="text-lg text-slate-900 tracking-tight">
90
+ {{ testimonial.content }}
91
+ </p>
92
+ </blockquote>
93
+ <figcaption class="flex relative items-center justify-between mt-6 pt-6 border-slate-100 border-t">
94
+ <div>
95
+ <div class="font-display text-base text-slate-900">
96
+ {{ testimonial.author.name }}
97
+ </div>
98
+ <div class="mt-1 text-slate-500 text-sm">
99
+ {{ testimonial.author.role }}
100
+ </div>
101
+ </div>
102
+ <div class="overflow-hidden bg-slate-50 rounded-full">
103
+ <img
104
+ class="object-cover h-14 w-14"
105
+ src="{{ testimonial.author.image }}"
106
+ alt=""
107
+ width="56"
108
+ height="56"
109
+ >
110
+ </div>
111
+ </figcaption>
112
+ </figure>
113
+ </li>
114
+ @endforeach
115
+ </ul>
116
+ </li>
117
+ @endforeach
118
+ </ul>
119
+ </Container>
120
+ </section>
@@ -0,0 +1,149 @@
1
+ <script>
2
+ /**
3
+ * Pagination component (stacksjs/stacks#1909, P5 from #1910).
4
+ *
5
+ * Renders the right shape for whichever paginator variant comes in:
6
+ *
7
+ * - Paginator<T> → prev / page-numbers / next + first/last jumps
8
+ * - SimplePaginator<T> → prev / next only
9
+ * - CursorPaginator<T> → prev / next only (cursor-based, no page #s)
10
+ *
11
+ * The paginator picks up its prev/next URLs from `paginator-request.ts`
12
+ * (P2) — they include query-string preservation, so search filters
13
+ * survive across page navigations without extra work here.
14
+ *
15
+ * Accessibility: wraps everything in `<nav aria-label="Pagination">`,
16
+ * sets `aria-current="page"` on the active page, uses real `<a>` tags
17
+ * (not `<button>`) because pagination is navigation, not action.
18
+ *
19
+ * Window-size: the `window` prop controls how many neighbors of the
20
+ * current page render before the ellipsis kicks in. Default 2 gives
21
+ * a compact "1 ... 3 4 [5] 6 7 ... 12" shape.
22
+ *
23
+ * Algorithm lives in `@stacksjs/ui/pagination` (buildPageSequence /
24
+ * urlForPage / paginatorVariant) so it's unit-testable independently
25
+ * of the stx render pipeline.
26
+ */
27
+
28
+ import { buildPageSequence, paginatorVariant, urlForPage } from '@stacksjs/ui'
29
+
30
+ interface PaginationLike {
31
+ data: unknown[]
32
+ per_page: number
33
+ has_more_pages: boolean
34
+ prev_page_url?: string | null
35
+ next_page_url?: string | null
36
+ first_page_url?: string
37
+ last_page_url?: string
38
+ // Full paginator only:
39
+ current_page?: number
40
+ total?: number
41
+ last_page?: number
42
+ from?: number | null
43
+ to?: number | null
44
+ // Cursor paginator only:
45
+ next_cursor?: string | null
46
+ prev_cursor?: string | null
47
+ }
48
+
49
+ interface PaginationProps {
50
+ /** The paginator to render — accepts all three Stacks paginator shapes. */
51
+ paginator: PaginationLike
52
+ /**
53
+ * Page numbers shown on either side of the current page in the full
54
+ * Paginator variant. `1 ... [3] 4 5 6 7 ... 12` is `window=2`.
55
+ * @default 2
56
+ */
57
+ window?: number
58
+ }
59
+
60
+ const { paginator, window: windowSize = 2 } = defineProps<PaginationProps>()
61
+
62
+ const variant = paginatorVariant(paginator)
63
+ const isFull = variant === 'full'
64
+ const isSimple = variant === 'simple'
65
+ const isCursor = variant === 'cursor'
66
+
67
+ const pages = isFull
68
+ ? buildPageSequence(paginator.current_page!, paginator.last_page!, windowSize)
69
+ : []
70
+
71
+ const baseLink = 'inline-flex items-center px-3 py-2 text-sm font-medium border'
72
+ const activeLink = `${baseLink} border-indigo-500 text-indigo-600 dark:text-indigo-400 bg-indigo-50 dark:bg-indigo-900/30`
73
+ const inactiveLink = `${baseLink} border-gray-300 dark:border-neutral-600 text-gray-700 dark:text-neutral-300 hover:bg-gray-50 dark:hover:bg-neutral-700`
74
+ const disabledLink = `${baseLink} border-gray-200 dark:border-neutral-700 text-gray-400 dark:text-neutral-500 cursor-not-allowed`
75
+ </script>
76
+
77
+ <nav aria-label="Pagination" class="flex gap-2 items-center justify-between py-3 select-none">
78
+ @if(isFull)
79
+ {{-- Mobile: prev / next only (page numbers hidden) --}}
80
+ <div class="flex sm:hidden flex-1 justify-between">
81
+ @if(paginator.prev_page_url)
82
+ <a href="{{ paginator.prev_page_url }}" class="{{ inactiveLink }} rounded-md">Previous</a>
83
+ @else
84
+ <span class="{{ disabledLink }} rounded-md">Previous</span>
85
+ @endif
86
+ @if(paginator.next_page_url)
87
+ <a href="{{ paginator.next_page_url }}" class="{{ inactiveLink }} rounded-md">Next</a>
88
+ @else
89
+ <span class="{{ disabledLink }} rounded-md">Next</span>
90
+ @endif
91
+ </div>
92
+
93
+ {{-- Desktop: prev / page numbers / next + "Showing X to Y of Z" --}}
94
+ <div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
95
+ <p class="text-gray-700 text-sm dark:text-neutral-300">
96
+ @if(paginator.from && paginator.to)
97
+ Showing <span class="font-medium">{{ paginator.from }}</span>
98
+ to <span class="font-medium">{{ paginator.to }}</span>
99
+ of <span class="font-medium">{{ paginator.total }}</span> results
100
+ @else
101
+ No results
102
+ @endif
103
+ </p>
104
+
105
+ <div class="inline-flex -space-x-px rounded-md shadow-sm isolate">
106
+ @if(paginator.prev_page_url)
107
+ <a href="{{ paginator.prev_page_url }}" rel="prev" class="{{ inactiveLink }} rounded-l-md" aria-label="Previous page">←</a>
108
+ @else
109
+ <span class="{{ disabledLink }} rounded-l-md" aria-label="Previous page">←</span>
110
+ @endif
111
+
112
+ @foreach(pages as p)
113
+ @if(p === '…')
114
+ <span class="{{ inactiveLink }}">…</span>
115
+ @else
116
+ @if(p === paginator.current_page)
117
+ <a href="{{ urlForPage(paginator, p) }}" aria-current="page" class="{{ activeLink }}">{{ p }}</a>
118
+ @else
119
+ <a href="{{ urlForPage(paginator, p) }}" class="{{ inactiveLink }}">{{ p }}</a>
120
+ @endif
121
+ @endif
122
+ @endforeach
123
+
124
+ @if(paginator.next_page_url)
125
+ <a href="{{ paginator.next_page_url }}" rel="next" class="{{ inactiveLink }} rounded-r-md" aria-label="Next page">→</a>
126
+ @else
127
+ <span class="{{ disabledLink }} rounded-r-md" aria-label="Next page">→</span>
128
+ @endif
129
+ </div>
130
+ </div>
131
+ @endif
132
+
133
+ @if(isSimple || isCursor)
134
+ {{-- Simple / cursor: just prev / next. No page numbers because we
135
+ don't know last_page (simple) or it doesn't apply (cursor). --}}
136
+ <div class="flex flex-1 justify-between">
137
+ @if(paginator.prev_page_url)
138
+ <a href="{{ paginator.prev_page_url }}" rel="prev" class="{{ inactiveLink }} rounded-md">← Previous</a>
139
+ @else
140
+ <span class="{{ disabledLink }} rounded-md">← Previous</span>
141
+ @endif
142
+ @if(paginator.next_page_url)
143
+ <a href="{{ paginator.next_page_url }}" rel="next" class="{{ inactiveLink }} rounded-md">Next →</a>
144
+ @else
145
+ <span class="{{ disabledLink }} rounded-md">Next →</span>
146
+ @endif
147
+ </div>
148
+ @endif
149
+ </nav>
@@ -0,0 +1,9 @@
1
+ <!--
2
+ You may reuse this file to demo your component library.
3
+ All of your components & functions are automatically imported.
4
+ It is rendered when you run `buddy dev:components`.
5
+ -->
6
+ <HelloWorld
7
+ greeting="Jane Doe"
8
+ :show-buttons="true"
9
+ />
@@ -0,0 +1,87 @@
1
+ # The Components Folder
2
+
3
+ This is where you create your components.
4
+
5
+ > **Note**
6
+ > If your project does not require any UI components, you can remove this folder. If your library does include components, you may use this readme as the boilerplate of your component library.
7
+
8
+ ## 💡 Get Started
9
+
10
+ It's easy to get started. The only prerequisite is a basic understanding of HTML, sprinkled with *minimal* JavaScript. In other words, there is virtually no learning curve.
11
+
12
+ ```bash
13
+ # you may use this GitHub template or the following command
14
+ bunx stacks new hello-world
15
+ cd hello-world
16
+
17
+ buddy install # install deps for all packages
18
+ buddy dev:components # starts the component dev server
19
+ buddy build:components # builds the component library for production-ready use
20
+ ```
21
+
22
+ Additionally, the `package.json` contains some useful snippets you likely want to be aware of.
23
+
24
+ ## 🤖 Usage
25
+
26
+ Because this project is optimized toward the development of easily reusable & composable component libraries, it’s very easy to use (and distribute):
27
+
28
+ ```vue
29
+ <script setup lang="ts">
30
+ import HelloWorld from 'hello-world-stack'
31
+ </script>
32
+
33
+ <template>
34
+ <HelloWorld name="J Doe" />
35
+ </template>
36
+ ```
37
+
38
+ ### Tips
39
+
40
+ This project also includes a simple way to handle your versioning. Through semantic commit names, it will also generate the two changelogs: one as part of the GitHub releases & the one markdown file that's stored within the root of the project.
41
+
42
+ ```bash
43
+ # how to create a git commit
44
+ git add . # select the changes you want to commit
45
+ buddy commit # then simply follow the prompts
46
+
47
+ # after you successfully committed, you may create a "release"
48
+ buddy release # automates git commits, versioning, and CHANGELOG generation
49
+ ```
50
+
51
+ Read more about these tips in the docs.
52
+
53
+ ### Coding Style
54
+
55
+ You may want to consider using the "Composition API" with [`<script setup>` SFC syntax](https://github.com/vuejs/rfcs/pull/227), as it is perfectly suited for building component libraries via Stacks. [ESLint](https://github.com/eslint) also comes pre-configured to statically analyze, fix and format your code without the need for Prettier.
56
+
57
+ When using this template, feel free to adjust it to your needs. It is a framework to help you quickly & efficiently bootstrap and design component libraries using industry best practices.
58
+
59
+ ## 🧪 Testing
60
+
61
+ ```bash
62
+ bun test
63
+ ```
64
+
65
+ ## 📈 Changelog
66
+
67
+ Please see our [releases](https://github.com/stacksjs/stacks/releases) page for more information on what has changed recently.
68
+
69
+ ## 🚜 Contributing
70
+
71
+ Please review the [Contributing Guide](https://github.com/stacksjs/contributing) for details.
72
+
73
+ ## 🏝 Community
74
+
75
+ For help, discussion about best practices, or any other conversation that would benefit from being searchable:
76
+
77
+ [Discussions on GitHub](https://github.com/stacksjs/stacks/discussions)
78
+
79
+ For casual chit-chat with others using this package:
80
+
81
+ [Join the Stacks Discord Server](https://discord.gg/stacksjs)
82
+
83
+ ## 📄 License
84
+
85
+ The MIT License (MIT). Please see [LICENSE](https://github.com/stacksjs/stacks/tree/main/LICENSE.md) for more information.
86
+
87
+ Made with 💙
@@ -0,0 +1,17 @@
1
+ <script>
2
+ interface AnnouncementBarProps {
3
+ message?: string
4
+ className?: string
5
+ }
6
+
7
+ const {
8
+ message = 'Free shipping on orders over $40 · Vet-formulated · Made in the USA',
9
+ className = '',
10
+ } = defineProps<AnnouncementBarProps>()
11
+ </script>
12
+
13
+ <div class="bg-stone-900 text-stone-100 text-xs {{ className }}">
14
+ <div class="mx-auto px-6 py-2 max-w-6xl text-center tracking-wide">
15
+ {{ message }}
16
+ </div>
17
+ </div>
@@ -0,0 +1,227 @@
1
+ <script>
2
+ /**
3
+ * Slide-in cart drawer.
4
+ *
5
+ * Server-rendered closed by default. The storefront's add-to-cart button
6
+ * triggers an XHR POST to `/api/cart/add`; on a 200 the drawer's contents
7
+ * are refreshed (via `/api/cart`) and the drawer slides in. We use small
8
+ * vanilla DOM for the open/close because stx signals can't reach across
9
+ * the layout slot in this case — the data-stx-* hooks below are the
10
+ * agreed-upon interface other components use to drive it.
11
+ */
12
+ </script>
13
+
14
+ <div data-stx-cart-drawer class="fixed inset-0 z-50 pointer-events-none" aria-hidden="true">
15
+ <!-- Backdrop -->
16
+ <div
17
+ data-stx-cart-backdrop
18
+ class="absolute inset-0 bg-stone-900/40 opacity-0 duration-300 ease-out transition-opacity"
19
+ style="will-change: opacity"
20
+ ></div>
21
+
22
+ <!-- Panel — translate3d + will-change promote it to its own GPU layer so
23
+ the slide-in doesn't re-paint the underlying header / content layers
24
+ and cause the perceived flicker on neighboring elements. -->
25
+ <aside
26
+ data-stx-cart-panel
27
+ class="flex absolute bottom-0 right-0 top-0 flex-col max-w-md w-full bg-stone-50 shadow-2xl duration-300 ease-out transition-transform"
28
+ style="transform: translate3d(100%, 0, 0); will-change: transform"
29
+ role="dialog"
30
+ aria-modal="true"
31
+ aria-label="Cart"
32
+ tabindex="-1"
33
+ >
34
+ <header class="flex items-center justify-between p-6 border-b border-stone-200">
35
+ <h2 class="font-bold text-lg text-stone-900">Your bowl</h2>
36
+ <button type="button" data-stx-cart-close class="text-stone-500 focus-visible:text-stone-900 hover:text-stone-900 focus-visible:outline-none" aria-label="Close cart">
37
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
38
+ </button>
39
+ </header>
40
+
41
+ <div data-stx-cart-body class="overflow-y-auto flex-1 p-6 space-y-4">
42
+ <div data-stx-cart-empty class="py-12 text-center">
43
+ <div class="inline-flex items-center justify-center mx-auto h-14 w-14 bg-stone-100 rounded-full">
44
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7 text-stone-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 00-3 3h15.75m-12.75-3h11.218c.51 0 .956-.343 1.087-.835l1.872-7.024A1.125 1.125 0 0019.797 5.25H5.106M7.5 14.25L5.106 5.25M6 20.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm12.75 0a.75.75 0 11-1.5 0 .75.75 0 011.5 0z" /></svg>
45
+ </div>
46
+ <p class="mt-4 font-medium text-sm text-stone-700">Your bowl is empty.</p>
47
+ <p class="mt-1 text-stone-500 text-xs">Pick a treat and we'll start one.</p>
48
+ <a href="/products" class="inline-flex gap-2 items-center mt-5 px-5 py-2.5 font-semibold text-stone-50 text-xs bg-stone-900 hover:bg-stone-800 rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-stone-50 focus-visible:ring-stone-900 transition-colors">
49
+ Browse the shop
50
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"/></svg>
51
+ </a>
52
+ </div>
53
+ <ul data-stx-cart-list class="hidden space-y-4"></ul>
54
+ </div>
55
+
56
+ <footer data-stx-cart-footer class="hidden p-6 space-y-4 border-stone-200 border-t">
57
+ <div class="flex justify-between text-sm"><span class="text-stone-600">Subtotal</span><span class="font-bold text-stone-900" data-stx-cart-subtotal>$0.00</span></div>
58
+ <a href="/cart" class="block px-6 py-3 w-full font-semibold text-center text-sm text-stone-900 border border-stone-300 rounded-full hover:border-stone-900 transition-colors">View cart</a>
59
+ <a href="/checkout/contact" class="block px-6 py-3 w-full font-semibold text-center text-sm text-stone-50 bg-stone-900 hover:bg-stone-800 rounded-full transition-colors">Checkout</a>
60
+ </footer>
61
+ </aside>
62
+ </div>
63
+
64
+ <script data-stx-scoped>
65
+ ;(() => {
66
+ // Idempotency guard. SPA fragment swaps re-inject scoped scripts when
67
+ // the cart link is followed; without this, every navigation stacked
68
+ // another set of click listeners on [data-stx-add-to-cart] buttons,
69
+ // and a single click would fire two/three/N XHRs — the user-visible
70
+ // tell was a brief flicker as the drawer opened, refreshed, then
71
+ // refreshed again from the duplicate handlers.
72
+ if (window.__stxCartWired) return
73
+ window.__stxCartWired = true
74
+
75
+ const root = document.querySelector('[data-stx-cart-drawer]')
76
+ if (!root) return
77
+ const backdrop = root.querySelector('[data-stx-cart-backdrop]')
78
+ const panel = root.querySelector('[data-stx-cart-panel]')
79
+ const closeBtn = root.querySelector('[data-stx-cart-close]')
80
+ const list = root.querySelector('[data-stx-cart-list]')
81
+ const empty = root.querySelector('[data-stx-cart-empty]')
82
+ const footer = root.querySelector('[data-stx-cart-footer]')
83
+ const subtotalEl = root.querySelector('[data-stx-cart-subtotal]')
84
+
85
+ // Reserve the scrollbar gutter on <html> so locking page scroll while
86
+ // the drawer is open doesn't cause the page width to jump (~17px on
87
+ // platforms with overlay scrollbars off). This was the visible flicker
88
+ // on neighboring elements when the drawer slid in: the page reflowed
89
+ // because the scrollbar disappeared.
90
+ document.documentElement.style.scrollbarGutter = 'stable'
91
+
92
+ let lastFocus = null
93
+
94
+ function open() {
95
+ lastFocus = document.activeElement
96
+ root.style.pointerEvents = 'auto'
97
+ root.setAttribute('aria-hidden', 'false')
98
+ backdrop.style.opacity = '1'
99
+ panel.style.transform = 'translate3d(0, 0, 0)'
100
+ document.body.style.overflow = 'hidden'
101
+ // Move focus into the panel so Esc + Tab work as expected.
102
+ requestAnimationFrame(() => { panel.focus({ preventScroll: true }) })
103
+ }
104
+ function shut() {
105
+ root.style.pointerEvents = 'none'
106
+ root.setAttribute('aria-hidden', 'true')
107
+ backdrop.style.opacity = '0'
108
+ panel.style.transform = 'translate3d(100%, 0, 0)'
109
+ document.body.style.overflow = ''
110
+ if (lastFocus && typeof lastFocus.focus === 'function') {
111
+ try { lastFocus.focus({ preventScroll: true }) } catch { /* element gone */ }
112
+ }
113
+ }
114
+
115
+ backdrop?.addEventListener('click', shut)
116
+ closeBtn?.addEventListener('click', shut)
117
+ document.addEventListener('keydown', (e) => {
118
+ if (e.key === 'Escape' && root.getAttribute('aria-hidden') === 'false') shut()
119
+ })
120
+
121
+ function escapeHtml(s) {
122
+ return String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c])
123
+ }
124
+
125
+ function render(items, subtotal) {
126
+ if (!list || !empty || !footer) return
127
+ if (!items || items.length === 0) {
128
+ list.classList.add('hidden')
129
+ empty.classList.remove('hidden')
130
+ footer.classList.add('hidden')
131
+ return
132
+ }
133
+ empty.classList.add('hidden')
134
+ list.classList.remove('hidden')
135
+ footer.classList.remove('hidden')
136
+ list.innerHTML = items.map((it) => {
137
+ const slug = escapeHtml(it.slug || '')
138
+ const name = escapeHtml(it.name || '')
139
+ const image = escapeHtml(it.image || '')
140
+ const qty = Number(it.qty) || 0
141
+ const price = Number(it.price) || 0
142
+ const lineTotal = Number(it.lineTotal) || 0
143
+ return [
144
+ '<li class="flex gap-3 items-start">',
145
+ ' <a href="/products/' + slug + '" class="block overflow-hidden flex-shrink-0 h-16 w-16 bg-stone-200 rounded-xl">',
146
+ ' <img src="' + image + '" alt="' + name + '" class="object-cover h-full w-full">',
147
+ ' </a>',
148
+ ' <div class="flex-1 min-w-0">',
149
+ ' <p class="font-semibold text-sm text-stone-900 truncate">' + name + '</p>',
150
+ ' <p class="mt-0.5 text-stone-500 text-xs">Qty ' + qty + ' · $' + price.toFixed(2) + '</p>',
151
+ ' </div>',
152
+ ' <p class="font-semibold text-sm text-stone-900">$' + lineTotal.toFixed(2) + '</p>',
153
+ '</li>',
154
+ ].join('')
155
+ }).join('')
156
+ if (subtotalEl) subtotalEl.textContent = '$' + (Number(subtotal) || 0).toFixed(2)
157
+ }
158
+
159
+ let inflightRefresh = null
160
+ async function refresh() {
161
+ // Coalesce concurrent refreshes — if a click fires two refresh() in a
162
+ // row (e.g. add-to-cart + open both call refresh), they reuse the
163
+ // same network request and the drawer doesn't briefly toggle between
164
+ // empty and populated.
165
+ if (inflightRefresh) return inflightRefresh
166
+ inflightRefresh = (async () => {
167
+ try {
168
+ const res = await fetch('/api/cart', {
169
+ credentials: 'same-origin',
170
+ headers: { 'X-Requested-With': 'XMLHttpRequest' },
171
+ })
172
+ if (!res.ok) return
173
+ const data = await res.json()
174
+ render(data.items || [], data.subtotal || 0)
175
+ const badge = document.querySelector('[data-stx-cart-count]')
176
+ if (badge) {
177
+ const total = (data.items || []).reduce((s, i) => s + (Number(i.qty) || 0), 0)
178
+ badge.textContent = String(total)
179
+ badge.classList.toggle('hidden', total === 0)
180
+ }
181
+ }
182
+ catch { /* leave existing render in place */ }
183
+ finally { inflightRefresh = null }
184
+ })()
185
+ return inflightRefresh
186
+ }
187
+
188
+ // Public hooks for other components to call.
189
+ window.stxCart = {
190
+ open: () => { open(); refresh() },
191
+ shut,
192
+ refresh,
193
+ }
194
+
195
+ // Wire any add-to-cart submit buttons via a single document-level
196
+ // delegated listener. Compared to attaching per-button listeners, this:
197
+ // - Stays correct when the catalog page fragment swaps in new
198
+ // [data-stx-add-to-cart] buttons.
199
+ // - Can't double-fire if the scoped script runs more than once (the
200
+ // guard at the top is the primary defense; this is belt-and-braces).
201
+ document.addEventListener('click', async (e) => {
202
+ const target = e.target instanceof Element ? e.target : null
203
+ if (!target) return
204
+ const btn = target.closest('[data-stx-add-to-cart]')
205
+ if (!btn) return
206
+ const form = btn.closest('form')
207
+ if (!form) return
208
+ e.preventDefault()
209
+ const fd = new FormData(form)
210
+ btn.setAttribute('aria-busy', 'true')
211
+ try {
212
+ const res = await fetch(form.action || '/api/cart/add', {
213
+ method: form.method || 'POST',
214
+ body: fd,
215
+ credentials: 'same-origin',
216
+ headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' },
217
+ })
218
+ if (res.ok) {
219
+ await refresh()
220
+ open()
221
+ }
222
+ else { form.submit() }
223
+ } catch { form.submit() }
224
+ finally { btn.removeAttribute('aria-busy') }
225
+ })
226
+ })()
227
+ </script>
@@ -0,0 +1,50 @@
1
+ <script>
2
+ interface CheckoutStep {
3
+ key: string
4
+ label: string
5
+ href?: string
6
+ }
7
+
8
+ interface CheckoutStepsProps {
9
+ current: string
10
+ steps?: CheckoutStep[]
11
+ }
12
+
13
+ const {
14
+ current,
15
+ steps = [
16
+ { key: 'contact', label: 'Contact', href: '/checkout/contact' },
17
+ { key: 'shipping', label: 'Shipping', href: '/checkout/shipping' },
18
+ { key: 'payment', label: 'Payment', href: '/checkout/payment' },
19
+ ],
20
+ } = defineProps<CheckoutStepsProps>()
21
+
22
+ const currentIndex = steps.findIndex(s => s.key === current)
23
+ </script>
24
+
25
+ <nav aria-label="Checkout progress" class="mb-10">
26
+ <ol class="flex gap-3 items-center text-sm">
27
+ @foreach (steps as step, i)
28
+ <li class="flex flex-1 gap-3 items-center min-w-0">
29
+ <span class="inline-flex h-7 w-7 items-center justify-center rounded-full text-xs font-semibold flex-shrink-0
30
+ {{ i < currentIndex ? 'bg-stone-900 text-stone-50' : '' }}
31
+ {{ i === currentIndex ? 'bg-stone-900 text-stone-50 ring-4 ring-stone-200' : '' }}
32
+ {{ i > currentIndex ? 'bg-stone-200 text-stone-500' : '' }}">
33
+ @if (i < currentIndex)
34
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke-width="3" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg>
35
+ @else
36
+ {{ i + 1 }}
37
+ @endif
38
+ </span>
39
+ @if (step.href && i <= currentIndex)
40
+ <a href="{{ step.href }}" class="font-semibold text-stone-900 truncate hover:underline">{{ step.label }}</a>
41
+ @else
42
+ <span class="{{ i === currentIndex ? 'text-stone-900 font-semibold' : 'text-stone-500' }} truncate">{{ step.label }}</span>
43
+ @endif
44
+ @if (i < steps.length - 1)
45
+ <span class="hidden md:block flex-1 h-px bg-stone-200"></span>
46
+ @endif
47
+ </li>
48
+ @endforeach
49
+ </ol>
50
+ </nav>