acinguiux-preact-components 0.0.1

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 (313) hide show
  1. package/package.json +56 -0
  2. package/src/content/themes/theme-acinguiux-amg/theme-acinguiux-amg.css +23 -0
  3. package/src/content/themes/theme-acinguiux-cafe/theme-acinguiux-cafe.css +47 -0
  4. package/src/content/themes/theme-acinguiux-energy/theme-acinguiux-energy.css +45 -0
  5. package/src/content/themes/theme-acinguiux-livewire/theme-acinguiux-livewire.css +22 -0
  6. package/src/content/themes/theme-acinguiux-livewire-italy/theme-acinguiux-livewire-italy.css +22 -0
  7. package/src/content/themes/theme-acinguiux-recharge/theme-acinguiux-recharge.css +49 -0
  8. package/src/content/themes/theme-allon/theme-allon.css +25 -0
  9. package/src/content/themes/theme-atlas/theme-atlas.css +31 -0
  10. package/src/content/themes/theme-aurvana/resources/favicon/apple-touch-icon.png +0 -0
  11. package/src/content/themes/theme-aurvana/resources/favicon/favico.ico +0 -0
  12. package/src/content/themes/theme-aurvana/resources/favicon/favicon-96x96.png +0 -0
  13. package/src/content/themes/theme-aurvana/resources/favicon/favicon.ico +0 -0
  14. package/src/content/themes/theme-aurvana/resources/favicon/favicon.png +0 -0
  15. package/src/content/themes/theme-aurvana/resources/favicon/favicon.svg +13 -0
  16. package/src/content/themes/theme-aurvana/resources/favicon/google-touch-icon.png +0 -0
  17. package/src/content/themes/theme-aurvana/resources/favicon/manifest.json +14 -0
  18. package/src/content/themes/theme-aurvana/resources/favicon/site.webmanifest +21 -0
  19. package/src/content/themes/theme-aurvana/resources/favicon/web-app-manifest-192x192.png +0 -0
  20. package/src/content/themes/theme-aurvana/resources/favicon/web-app-manifest-512x512.png +0 -0
  21. package/src/content/themes/theme-aurvana/theme-aurvana.css +49 -0
  22. package/src/content/themes/theme-base/theme-base.css +49 -0
  23. package/src/content/themes/theme-base2/resources/favicon/android-chrome-192x192.png +0 -0
  24. package/src/content/themes/theme-base2/resources/favicon/android-chrome-512x512.png +0 -0
  25. package/src/content/themes/theme-base2/resources/favicon/apple-touch-icon.png +0 -0
  26. package/src/content/themes/theme-base2/resources/favicon/favico.ico +0 -0
  27. package/src/content/themes/theme-base2/resources/favicon/favicon-16x16.png +0 -0
  28. package/src/content/themes/theme-base2/resources/favicon/favicon-32x32.png +0 -0
  29. package/src/content/themes/theme-base2/resources/favicon/favicon-96x96.png +0 -0
  30. package/src/content/themes/theme-base2/resources/favicon/favicon.ico +0 -0
  31. package/src/content/themes/theme-base2/resources/favicon/favicon.png +0 -0
  32. package/src/content/themes/theme-base2/resources/favicon/favicon.svg +9 -0
  33. package/src/content/themes/theme-base2/resources/favicon/google-touch-icon.png +0 -0
  34. package/src/content/themes/theme-base2/resources/favicon/manifest.json +14 -0
  35. package/src/content/themes/theme-base2/resources/favicon/site.webmanifest +1 -0
  36. package/src/content/themes/theme-base2/resources/favicon/web-app-manifest-192x192.png +0 -0
  37. package/src/content/themes/theme-base2/resources/favicon/web-app-manifest-512x512.png +0 -0
  38. package/src/content/themes/theme-base2/resources/fonts/acinguiux-typeface-la-heavy-221208.woff2 +0 -0
  39. package/src/content/themes/theme-base2/theme-base2.css +47 -0
  40. package/src/content/themes/theme-eco-marathon/theme-eco-marathon.css +22 -0
  41. package/src/content/themes/theme-energy-transition-campus-amsterdam/theme-energy-transition-campus-amsterdam.css +26 -0
  42. package/src/content/themes/theme-evpass/theme-evpass.css +46 -0
  43. package/src/content/themes/theme-nam-2025/resources/favicon/apple-touch-icon.png +0 -0
  44. package/src/content/themes/theme-nam-2025/resources/favicon/favico.ico +0 -0
  45. package/src/content/themes/theme-nam-2025/resources/favicon/favicon-96x96.png +0 -0
  46. package/src/content/themes/theme-nam-2025/resources/favicon/favicon.ico +0 -0
  47. package/src/content/themes/theme-nam-2025/resources/favicon/favicon.png +0 -0
  48. package/src/content/themes/theme-nam-2025/resources/favicon/favicon.svg +9 -0
  49. package/src/content/themes/theme-nam-2025/resources/favicon/google-touch-icon.png +0 -0
  50. package/src/content/themes/theme-nam-2025/resources/favicon/manifest.json +14 -0
  51. package/src/content/themes/theme-nam-2025/resources/favicon/site.webmanifest +21 -0
  52. package/src/content/themes/theme-nam-2025/resources/favicon/web-app-manifest-192x192.png +0 -0
  53. package/src/content/themes/theme-nam-2025/resources/favicon/web-app-manifest-512x512.png +0 -0
  54. package/src/content/themes/theme-nam-2025/theme-nam-2025.css +47 -0
  55. package/src/content/themes/theme-pennzoil/theme-pennzoil.css +36 -0
  56. package/src/content/themes/theme-quaker-state/theme-quaker-state.css +63 -0
  57. package/src/content/themes/theme-tafawoq/theme-tafawoq.css +26 -0
  58. package/src/content/themes/theme-vegetable/resources/favicon/apple-touch-icon.png +0 -0
  59. package/src/content/themes/theme-vegetable/resources/favicon/favico.ico +0 -0
  60. package/src/content/themes/theme-vegetable/resources/favicon/favicon-96x96.png +0 -0
  61. package/src/content/themes/theme-vegetable/resources/favicon/favicon.ico +0 -0
  62. package/src/content/themes/theme-vegetable/resources/favicon/favicon.png +0 -0
  63. package/src/content/themes/theme-vegetable/resources/favicon/favicon.svg +13 -0
  64. package/src/content/themes/theme-vegetable/resources/favicon/google-touch-icon.png +0 -0
  65. package/src/content/themes/theme-vegetable/resources/favicon/manifest.json +14 -0
  66. package/src/content/themes/theme-vegetable/resources/favicon/site.webmanifest +21 -0
  67. package/src/content/themes/theme-vegetable/resources/favicon/web-app-manifest-192x192.png +0 -0
  68. package/src/content/themes/theme-vegetable/resources/favicon/web-app-manifest-512x512.png +0 -0
  69. package/src/content/themes/theme-vegetable/theme-vegetable.css +49 -0
  70. package/src/content/themes/theme-zeolyst/resources/fonts/type-ar-medium.woff2 +0 -0
  71. package/src/content/themes/theme-zeolyst/theme-zeolyst.css +29 -0
  72. package/src/main/atoms/audio.js +16 -0
  73. package/src/main/atoms/box.js +5 -0
  74. package/src/main/atoms/button.js +40 -0
  75. package/src/main/atoms/card.js +22 -0
  76. package/src/main/atoms/form.js +30 -0
  77. package/src/main/atoms/heading.js +17 -0
  78. package/src/main/atoms/icon.js +24 -0
  79. package/src/main/atoms/img.js +131 -0
  80. package/src/main/atoms/input.js +55 -0
  81. package/src/main/atoms/link-text.js +21 -0
  82. package/src/main/atoms/link.js +60 -0
  83. package/src/main/atoms/list.js +12 -0
  84. package/src/main/atoms/logo.js +9 -0
  85. package/src/main/atoms/menu.js +10 -0
  86. package/src/main/atoms/message.js +5 -0
  87. package/src/main/atoms/nav-link.js +49 -0
  88. package/src/main/atoms/popup.js +47 -0
  89. package/src/main/atoms/rich-text.js +128 -0
  90. package/src/main/atoms/scroller.js +224 -0
  91. package/src/main/atoms/svg.js +65 -0
  92. package/src/main/atoms/table.js +32 -0
  93. package/src/main/atoms/textarea.js +10 -0
  94. package/src/main/atoms/time.js +12 -0
  95. package/src/main/atoms/video.js +100 -0
  96. package/src/main/export-main.js +12 -0
  97. package/src/main/export-matter.js +86 -0
  98. package/src/main/export-preact-hooks.js +1 -0
  99. package/src/main/export-preact.js +1 -0
  100. package/src/main/index.js +13 -0
  101. package/src/main/molecules/asset.js +23 -0
  102. package/src/main/molecules/glossary.js +44 -0
  103. package/src/main/molecules/links.js +23 -0
  104. package/src/main/molecules/promo-text.js +27 -0
  105. package/src/main/molecules/tags.js +15 -0
  106. package/src/main/molecules/tree.js +51 -0
  107. package/src/main/organisms/accordion-item.js +106 -0
  108. package/src/main/organisms/author.js +29 -0
  109. package/src/main/organisms/breadcrumb.js +69 -0
  110. package/src/main/organisms/call-to-action.js +24 -0
  111. package/src/main/organisms/carousel.js +178 -0
  112. package/src/main/organisms/cart-item.js +156 -0
  113. package/src/main/organisms/cart.js +162 -0
  114. package/src/main/organisms/contact-form.js +141 -0
  115. package/src/main/organisms/container/ab-test.js +47 -0
  116. package/src/main/organisms/container/default.js +6 -0
  117. package/src/main/organisms/container/filtered-section.js +293 -0
  118. package/src/main/organisms/container/footer.js +12 -0
  119. package/src/main/organisms/container/grid.js +44 -0
  120. package/src/main/organisms/container/header.js +13 -0
  121. package/src/main/organisms/container/list.js +7 -0
  122. package/src/main/organisms/container/main.js +6 -0
  123. package/src/main/organisms/container/raw.js +7 -0
  124. package/src/main/organisms/container/section.js +28 -0
  125. package/src/main/organisms/container.js +29 -0
  126. package/src/main/organisms/content-owner.js +15 -0
  127. package/src/main/organisms/date-entry.js +56 -0
  128. package/src/main/organisms/external-search.js +73 -0
  129. package/src/main/organisms/filtered-item.js +163 -0
  130. package/src/main/organisms/footer-item.js +17 -0
  131. package/src/main/organisms/image-gallery.js +164 -0
  132. package/src/main/organisms/last-modified.js +20 -0
  133. package/src/main/organisms/legal-footer.js +16 -0
  134. package/src/main/organisms/list-item.js +48 -0
  135. package/src/main/organisms/metadata.js +11 -0
  136. package/src/main/organisms/navigation.js +232 -0
  137. package/src/main/organisms/notification.js +87 -0
  138. package/src/main/organisms/order-tracker.js +203 -0
  139. package/src/main/organisms/page-header-banner.js +26 -0
  140. package/src/main/organisms/page-header.js +33 -0
  141. package/src/main/organisms/page-tags.js +14 -0
  142. package/src/main/organisms/page.js +260 -0
  143. package/src/main/organisms/press-release.js +24 -0
  144. package/src/main/organisms/product-admin.js +204 -0
  145. package/src/main/organisms/promo-banner.js +28 -0
  146. package/src/main/organisms/promo-bottom.js +23 -0
  147. package/src/main/organisms/promo-button.js +8 -0
  148. package/src/main/organisms/promo-card-cover.js +35 -0
  149. package/src/main/organisms/promo-card.js +33 -0
  150. package/src/main/organisms/promo-full.js +20 -0
  151. package/src/main/organisms/promo-image.js +22 -0
  152. package/src/main/organisms/promo-lure.js +22 -0
  153. package/src/main/organisms/promo-product-card.js +187 -0
  154. package/src/main/organisms/promo-product-full.js +293 -0
  155. package/src/main/organisms/promo-simple.js +23 -0
  156. package/src/main/organisms/quote.js +21 -0
  157. package/src/main/organisms/search-form.js +42 -0
  158. package/src/main/organisms/search-nav.js +66 -0
  159. package/src/main/organisms/search-result.js +53 -0
  160. package/src/main/organisms/slider.js +26 -0
  161. package/src/main/organisms/standalone-asset.js +22 -0
  162. package/src/main/organisms/tabs.js +277 -0
  163. package/src/main/organisms/topbar.js +83 -0
  164. package/src/main/organisms/web-component.js +53 -0
  165. package/src/main/routing/annotation.js +9 -0
  166. package/src/main/routing/component.js +138 -0
  167. package/src/main/routing/empty.js +5 -0
  168. package/src/main/routing/error-handler.js +64 -0
  169. package/src/main/routing/placeholder-image.svg +5 -0
  170. package/src/main/routing/router.js +219 -0
  171. package/src/main/shared/analytics.js +677 -0
  172. package/src/main/shared/bubble-event.js +11 -0
  173. package/src/main/shared/custom-element.js +21 -0
  174. package/src/main/shared/deep-selector.js +28 -0
  175. package/src/main/shared/disable-transparency.js +10 -0
  176. package/src/main/shared/format-time.js +8 -0
  177. package/src/main/shared/get-id.js +5 -0
  178. package/src/main/shared/get-meta.js +3 -0
  179. package/src/main/shared/get-size-class.js +3 -0
  180. package/src/main/shared/get-size.js +11 -0
  181. package/src/main/shared/h.js +88 -0
  182. package/src/main/shared/hash-jump.js +33 -0
  183. package/src/main/shared/icons/arrow-back.svg +1 -0
  184. package/src/main/shared/icons/arrow-down.svg +1 -0
  185. package/src/main/shared/icons/arrow-next.svg +1 -0
  186. package/src/main/shared/icons/arrow-tail-right.svg +1 -0
  187. package/src/main/shared/icons/arrow-tail-up.svg +1 -0
  188. package/src/main/shared/icons/arrow-up.svg +1 -0
  189. package/src/main/shared/icons/asset-download.svg +1 -0
  190. package/src/main/shared/icons/logo.svg +5 -0
  191. package/src/main/shared/icons/low-carbon-placeholder.svg +9 -0
  192. package/src/main/shared/icons/media-pause.svg +1 -0
  193. package/src/main/shared/icons/media-play.svg +1 -0
  194. package/src/main/shared/icons/navigation-burger.svg +1 -0
  195. package/src/main/shared/icons/navigation-close.svg +1 -0
  196. package/src/main/shared/icons/navigation-link.svg +1 -0
  197. package/src/main/shared/icons/navigation-refresh.svg +1 -0
  198. package/src/main/shared/icons/navigation-search.svg +1 -0
  199. package/src/main/shared/icons/navigation-share.svg +1 -0
  200. package/src/main/shared/icons/toggle-newwindow.svg +1 -0
  201. package/src/main/shared/icons.js +18 -0
  202. package/src/main/shared/id-from-string.js +5 -0
  203. package/src/main/shared/mark-selection.js +19 -0
  204. package/src/main/shared/register.js +26 -0
  205. package/src/main/shared/renderer.js +43 -0
  206. package/src/main/shared/simple-consent-api.js +70 -0
  207. package/src/main/shared/split-links.js +11 -0
  208. package/src/main/shared/t.js +60 -0
  209. package/src/main/shared/twind.js +837 -0
  210. package/src/main/shared/update-head.js +34 -0
  211. package/src/main/shared/update-scrollbar-width.js +30 -0
  212. package/src/main/shared/use-link.js +151 -0
  213. package/src/main/shared/use-persistent-state.js +42 -0
  214. package/src/main/shared/wait-for-dom-ready.js +6 -0
  215. package/src/main/shared/wcm-mode.js +4 -0
  216. package/src/wcs/components/acinguiux-preact-doc/acinguiux-preact-doc.js +207 -0
  217. package/src/wcs/components/admin-dashboard/admin-dashboard.js +487 -0
  218. package/src/wcs/components/admin-login/admin-login.js +91 -0
  219. package/src/wcs/components/bazaar-voice/bazaar-voice.js +56 -0
  220. package/src/wcs/components/chatbot-koreai/chatbot-koreai.js +176 -0
  221. package/src/wcs/components/chatbot-koreai/koreai-transport.js +217 -0
  222. package/src/wcs/components/chatbot-ms/chatbot-ms.js +210 -0
  223. package/src/wcs/components/chatbot-test/chatbot-test.js +44 -0
  224. package/src/wcs/components/comparison-chart/comparison-chart.js +111 -0
  225. package/src/wcs/components/consent-banner/consent-banner.js +248 -0
  226. package/src/wcs/components/consent-banner/icons/ccpa.svg +6 -0
  227. package/src/wcs/components/consent-banner/icons/info.svg +1 -0
  228. package/src/wcs/components/consent-banner/provider-onetrust.js +131 -0
  229. package/src/wcs/components/decision-tree/arrow-back.svg +3 -0
  230. package/src/wcs/components/decision-tree/badges.js +37 -0
  231. package/src/wcs/components/decision-tree/decision-tree.js +162 -0
  232. package/src/wcs/components/dynamic-contact-details/dynamic-contact-details.js +111 -0
  233. package/src/wcs/components/example-accordion/example-accordion.js +10 -0
  234. package/src/wcs/components/example-asset/example-asset.js +12 -0
  235. package/src/wcs/components/example-form/example-form.js +59 -0
  236. package/src/wcs/components/example-nested/example-nested.js +10 -0
  237. package/src/wcs/components/example-routing/example-routing.js +51 -0
  238. package/src/wcs/components/example-rtl/example-rtl.js +28 -0
  239. package/src/wcs/components/example-tabs/example-tabs.js +12 -0
  240. package/src/wcs/components/example-web-component/example-web-component.js +34 -0
  241. package/src/wcs/components/floating-button/floating-button.js +17 -0
  242. package/src/wcs/components/formstack-form/fields/address.js +38 -0
  243. package/src/wcs/components/formstack-form/fields/checkbox.js +42 -0
  244. package/src/wcs/components/formstack-form/fields/date.js +22 -0
  245. package/src/wcs/components/formstack-form/fields/description.js +8 -0
  246. package/src/wcs/components/formstack-form/fields/input.js +8 -0
  247. package/src/wcs/components/formstack-form/fields/name.js +39 -0
  248. package/src/wcs/components/formstack-form/fields/radio.js +24 -0
  249. package/src/wcs/components/formstack-form/fields/rating.js +53 -0
  250. package/src/wcs/components/formstack-form/fields/section.js +8 -0
  251. package/src/wcs/components/formstack-form/fields/select.js +10 -0
  252. package/src/wcs/components/formstack-form/fields/textarea.js +8 -0
  253. package/src/wcs/components/formstack-form/fields/wrapper.js +11 -0
  254. package/src/wcs/components/formstack-form/formstack-form.js +280 -0
  255. package/src/wcs/components/fuel-prices/fuel-prices.js +45 -0
  256. package/src/wcs/components/furniture-overview/furniture-overview.js +115 -0
  257. package/src/wcs/components/gauge-value/gauge-value.js +65 -0
  258. package/src/wcs/components/help-centre/api.js +150 -0
  259. package/src/wcs/components/help-centre/help-centre.js +400 -0
  260. package/src/wcs/components/help-centre/icon-search.svg +1 -0
  261. package/src/wcs/components/image-gen/admin-panel.js +248 -0
  262. package/src/wcs/components/image-gen/image-gen.js +385 -0
  263. package/src/wcs/components/image-gen/labels.js +37 -0
  264. package/src/wcs/components/image-gen/use-api.js +392 -0
  265. package/src/wcs/components/inspired-gallery/inspired-gallery.js +118 -0
  266. package/src/wcs/components/launch-container/launch-container.js +95 -0
  267. package/src/wcs/components/launch-container/ledger.js +140 -0
  268. package/src/wcs/components/lng-map/lng-map.js +44 -0
  269. package/src/wcs/components/mouseflow-analytics/mouseflow-analytics.js +39 -0
  270. package/src/wcs/components/msds-search/msds-search.js +127 -0
  271. package/src/wcs/components/msds-search/navigation-search.svg +3 -0
  272. package/src/wcs/components/product-catalogue/icon-back.svg +3 -0
  273. package/src/wcs/components/product-catalogue/icon-cart.svg +3 -0
  274. package/src/wcs/components/product-catalogue/icon-close.svg +3 -0
  275. package/src/wcs/components/product-catalogue/product-catalogue.js +215 -0
  276. package/src/wcs/components/product-links/icon-cart.svg +3 -0
  277. package/src/wcs/components/product-links/product-links.js +43 -0
  278. package/src/wcs/components/rio-iframe/rio-iframe.js +137 -0
  279. package/src/wcs/components/salsify-products/filter-tools.js +60 -0
  280. package/src/wcs/components/salsify-products/icon-cart.svg +3 -0
  281. package/src/wcs/components/salsify-products/process-products.js +53 -0
  282. package/src/wcs/components/salsify-products/route-tools.js +54 -0
  283. package/src/wcs/components/salsify-products/salsify-products.js +281 -0
  284. package/src/wcs/components/shout-out/shout-out.js +51 -0
  285. package/src/wcs/components/simple-chart/simple-chart.js +53 -0
  286. package/src/wcs/components/single-stat/single-stat.js +85 -0
  287. package/src/wcs/components/site-feedback/site-feedback.js +56 -0
  288. package/src/wcs/components/skds-search/navigation-search.svg +3 -0
  289. package/src/wcs/components/skds-search/skds-search.js +103 -0
  290. package/src/wcs/components/smart-banner/smart-banner.js +104 -0
  291. package/src/wcs/components/standalone-table/arrow-up-down.svg +3 -0
  292. package/src/wcs/components/standalone-table/arrow-up.svg +3 -0
  293. package/src/wcs/components/standalone-table/standalone-table.js +440 -0
  294. package/src/wcs/components/station-locator/station-locator.js +49 -0
  295. package/src/wcs/components/store-badges/badges.js +60 -0
  296. package/src/wcs/components/store-badges/store-badges.js +93 -0
  297. package/src/wcs/components/topbar-button/person.svg +1 -0
  298. package/src/wcs/components/topbar-button/topbar-button.js +22 -0
  299. package/src/wcs/components/universal-gallery/universal-gallery.js +308 -0
  300. package/src/wcs/components/zendesk-chat/zendesk-chat.js +133 -0
  301. package/src/wcs/shared/chat-bot/README.md +61 -0
  302. package/src/wcs/shared/chat-bot/chat-bot.js +216 -0
  303. package/src/wcs/shared/chat-bot/resources/arrow-next.svg +1 -0
  304. package/src/wcs/shared/chat-bot/resources/navigation-close.svg +1 -0
  305. package/src/wcs/shared/chat-bot/resources/person.svg +1 -0
  306. package/src/wcs/shared/chat-bot/resources/upload.svg +1 -0
  307. package/src/wcs/shared/filtered-data/README.md +52 -0
  308. package/src/wcs/shared/filtered-data/fetch-data.js +33 -0
  309. package/src/wcs/shared/filtered-data/filtered-data.js +337 -0
  310. package/src/wcs/shared/promo-with-popup/icon-close.svg +3 -0
  311. package/src/wcs/shared/promo-with-popup/icon-next.svg +3 -0
  312. package/src/wcs/shared/promo-with-popup/icon-prev.svg +3 -0
  313. package/src/wcs/shared/promo-with-popup/promo-with-popup.js +93 -0
@@ -0,0 +1,22 @@
1
+ import { Asset } from '../molecules/asset.js'
2
+ import { PromoText } from '../molecules/promo-text.js'
3
+ import { ValidationError } from '../routing/error-handler.js'
4
+ import h from '../shared/h.js'
5
+
6
+ export function PromoLure ({ _model, ...props }) {
7
+ const { title, text, asset } = _model
8
+ if (!title || !text || !asset.src) {
9
+ throw new ValidationError()
10
+ }
11
+
12
+ const { _variant } = props
13
+
14
+ return h('div', { ...props, className: 'bg-bga text-txa rounded-2xl overflow-hidden lg:grid lg:grid-cols-12 gap-x-5' },
15
+ h('div', { className: `col-span-8 row-start-1 ${_variant === 'right' ? '' : 'col-start-5'}` },
16
+ h(Asset, { _model, _tall: true })
17
+ ),
18
+ h('div', { className: 'col-span-4 row-start-1' },
19
+ h(PromoText, { _model })
20
+ )
21
+ )
22
+ }
@@ -0,0 +1,187 @@
1
+ import { useState, useEffect } from 'preact/hooks'
2
+ import { Img } from '../atoms/img.js'
3
+ import { Link } from '../atoms/link.js'
4
+ import { ValidationError } from '../routing/error-handler.js'
5
+ import h from '../shared/h.js'
6
+
7
+ const DEFAULT_UNIT = '100 g'
8
+
9
+ export function PromoProductCard ({ _model, count, ...props }) {
10
+ const {
11
+ title,
12
+ links,
13
+ image,
14
+ brand,
15
+ offerLabel,
16
+ eta,
17
+ price,
18
+ oldPrice,
19
+ currency = '\u20b9',
20
+ initialQuantity,
21
+ maxQuantity
22
+ } = _model
23
+
24
+ if (!title || !image?.src) {
25
+ throw new ValidationError()
26
+ }
27
+
28
+ const productLink = links?.[0]?.value
29
+ const units = getUnitOptions(_model)
30
+ const [selectedUnit, setSelectedUnit] = useState(units[0]?.value || DEFAULT_UNIT)
31
+
32
+ // count can be a number (single unit) or an object { "100 gm": 2, "250 gm": 1 } (per-unit from DB)
33
+ const getCountForUnit = (unit) => {
34
+ if (count == null) return null
35
+ if (typeof count === 'object' && !Array.isArray(count)) {
36
+ const val = count[unit]
37
+ return val != null ? Math.max(0, Number.parseInt(val, 10) || 0) : 0
38
+ }
39
+ return Math.max(0, Number.parseInt(count, 10) || 0)
40
+ }
41
+
42
+ const externalCount = getCountForUnit(selectedUnit)
43
+ const [quantity, setQuantity] = useState(Math.max(0, externalCount ?? (Number.parseInt(initialQuantity ?? 0, 10) || 0)))
44
+
45
+ // Sync quantity when count prop changes OR when selectedUnit changes
46
+ useEffect(() => {
47
+ const unitCount = getCountForUnit(selectedUnit)
48
+ if (unitCount != null) setQuantity(unitCount)
49
+ }, [count, selectedUnit])
50
+
51
+ const maxQty = Number.parseInt(maxQuantity, 10)
52
+ const unitId = `promo-product-unit-${slugify(title)}`
53
+
54
+ const onDecrease = () => setQuantity(current => Math.max(0, current - 1))
55
+ const onIncrease = () => {
56
+ setQuantity(current => {
57
+ if (Number.isFinite(maxQty) && current >= maxQty) {
58
+ return current
59
+ }
60
+
61
+ return current + 1
62
+ })
63
+ }
64
+
65
+ const etaLabel = eta || '12 mins'
66
+ const addLabel = 'Add'
67
+
68
+ return h('article', {
69
+ ...props,
70
+ className: 'bg-bga text-txa border border-txa/20 rounded-xl sm:p-2 md:p-2.5 lg:p-3 w-full shadow-sm sm:space-y-1 md:space-y-1.5 lg:space-y-2',
71
+ 'data-product-id': _model.id || slugify(title),
72
+ 'data-quantity': quantity,
73
+ 'data-selected-unit': selectedUnit
74
+ },
75
+ h('div', { className: 'relative rounded-lg border border-txa/15 overflow-hidden bg-bgb/10' },
76
+ h(Link, {
77
+ _model: { value: productLink, name: title },
78
+ target: '_blank',
79
+ className: 'block'
80
+ },
81
+ h(Img, {
82
+ _model: image,
83
+ _fit: 'contain',
84
+ className: 'w-full bg-bga',
85
+ style: 'aspect-ratio: 1 / 1;'
86
+ })
87
+ ),
88
+ offerLabel && h('div', { className: 'absolute top-1 left-1 lg:top-2 lg:left-2 bg-bgb text-txb rounded-sm px-1 lg:px-2 py-0.5 text-[10px] lg:text-xs font-bold' }, offerLabel)
89
+ ),
90
+
91
+ h('div', { className: 'inline-flex items-center rounded-sm bg-txa/10 text-txa/80 px-1 lg:px-2 py-0.5 lg:py-1 text-[10px] lg:text-xs font-semibold uppercase' }, etaLabel),
92
+
93
+ h('div', { className: 'space-y-0.5 lg:space-y-1 sm:min-h-8 lg:min-h-14' },
94
+ brand && h('div', { className: 'text-[10px] lg:text-xs font-bold uppercase opacity-80' }, brand),
95
+ h(Link, {
96
+ _model: { value: productLink, name: title },
97
+ target: '_blank',
98
+ className: 'block font-semibold leading-tight sm:text-xs lg:text-sm'
99
+ },
100
+ title
101
+ )
102
+ ),
103
+
104
+ h('div', { className: 'flex items-center gap-1 lg:gap-2' },
105
+ h('label', { className: 'sr-only', for: unitId }, 'Unit selector'),
106
+ h('select', {
107
+ id: unitId,
108
+ value: selectedUnit,
109
+ onChange (event) {
110
+ setSelectedUnit(event.currentTarget.value)
111
+ },
112
+ className: 'w-full rounded-md border border-txa/30 bg-bga text-txa px-1 lg:px-2 py-0.5 lg:py-1 text-[10px] lg:text-sm'
113
+ }, units.map(option => h('option', { value: option.value }, option.label)))
114
+ ),
115
+
116
+ h('div', { className: 'flex items-end justify-between gap-1 lg:gap-2' },
117
+ h('div', { className: 'flex items-center gap-1 lg:gap-2' },
118
+ h('span', { className: 'sm:text-xs lg:text-base font-bold' }, formatPrice(price, currency)),
119
+ oldPrice && h('span', { className: 'sm:text-[10px] lg:text-sm line-through opacity-60' }, formatPrice(oldPrice, currency))
120
+ ),
121
+
122
+ quantity === 0
123
+ ? h('button', {
124
+ type: 'button',
125
+ className: 'clickable inline-flex items-center gap-0.5 lg:gap-1 rounded-md border border-bgb bg-bgb text-txb px-2 lg:px-3 py-1 lg:py-1.5 text-xs lg:text-sm font-bold transition hover:bg-txb hover:text-bgb',
126
+ onClick: onIncrease,
127
+ 'aria-label': 'Add item'
128
+ },
129
+ h('span', { 'aria-hidden': true }, '+'),
130
+ addLabel
131
+ )
132
+ : h('div', {
133
+ className: 'inline-flex items-center rounded-md border border-bgb overflow-hidden',
134
+ role: 'group',
135
+ 'aria-label': 'Quantity selector'
136
+ },
137
+ h('button', {
138
+ type: 'button',
139
+ className: 'clickable bg-bgb text-txb sm:w-6 sm:h-6 lg:w-8 lg:h-8 text-sm lg:text-lg leading-none font-bold',
140
+ onClick: onDecrease,
141
+ 'aria-label': 'Decrease quantity'
142
+ }, '-'),
143
+ h('span', { className: 'px-2 lg:px-3 min-w-6 lg:min-w-8 text-center text-xs lg:text-base font-semibold' }, quantity),
144
+ h('button', {
145
+ type: 'button',
146
+ className: 'clickable bg-bgb text-txb sm:w-6 sm:h-6 lg:w-8 lg:h-8 text-sm lg:text-lg leading-none font-bold',
147
+ onClick: onIncrease,
148
+ 'aria-label': 'Increase quantity'
149
+ }, '+')
150
+ )
151
+ )
152
+ )
153
+ }
154
+
155
+ function getUnitOptions (model) {
156
+ const raw = model.units || model.sizes || model.options || model.tags?.map(tag => tag.value)
157
+
158
+ if (!Array.isArray(raw) || raw.length === 0) {
159
+ return [{ label: model.unit || DEFAULT_UNIT, value: model.unit || DEFAULT_UNIT }]
160
+ }
161
+
162
+ return raw.map((option, index) => {
163
+ if (typeof option === 'string') {
164
+ return { label: option, value: option }
165
+ }
166
+
167
+ const label = option?.name || option?.label || option?.value || `${DEFAULT_UNIT}-${index + 1}`
168
+ const value = option?.value || label
169
+ return { label, value }
170
+ })
171
+ }
172
+
173
+ function formatPrice (value, currency) {
174
+ if (typeof value === 'number') {
175
+ return `${currency}${value.toFixed(2)}`
176
+ }
177
+
178
+ if (typeof value === 'string' && value.length > 0) {
179
+ return value
180
+ }
181
+
182
+ return `${currency}0.00`
183
+ }
184
+
185
+ function slugify (value) {
186
+ return String(value || 'item').toLowerCase().replaceAll(/[^a-z0-9]+/g, '-').replaceAll(/(^-|-$)/g, '')
187
+ }
@@ -0,0 +1,293 @@
1
+ import { useState } from 'preact/hooks'
2
+ import { Img } from '../atoms/img.js'
3
+ import { Link } from '../atoms/link.js'
4
+ import { Popup } from '../atoms/popup.js'
5
+ import { Button } from '../atoms/button.js'
6
+ import { Carousel } from './carousel.js'
7
+ import { ValidationError } from '../routing/error-handler.js'
8
+ import h from '../shared/h.js'
9
+ import { NAVIGATION_CLOSE } from '../shared/icons.js'
10
+
11
+ const DEFAULT_UNIT = '100 g'
12
+
13
+ export function PromoProductFull ({ _model, ...props }) {
14
+ const {
15
+ title,
16
+ links,
17
+ image,
18
+ images = [],
19
+ brand,
20
+ offerLabel,
21
+ eta,
22
+ price,
23
+ oldPrice,
24
+ currency = '\u20b9',
25
+ initialQuantity,
26
+ maxQuantity,
27
+ description
28
+ } = _model
29
+
30
+ if (!title || !image?.src) {
31
+ throw new ValidationError()
32
+ }
33
+
34
+ const productLink = links?.[0]?.value
35
+ const allImages = images.length > 0 ? images : [image]
36
+ const units = getUnitOptions(_model)
37
+ const [selectedUnit, setSelectedUnit] = useState(units[0]?.value || DEFAULT_UNIT)
38
+ const [quantity, setQuantity] = useState(Math.max(0, Number.parseInt(initialQuantity ?? 0, 10) || 0))
39
+ const [showDetail, setShowDetail] = useState(false)
40
+ const maxQty = Number.parseInt(maxQuantity, 10)
41
+ const unitId = `promo-full-unit-${slugify(title)}`
42
+
43
+ const onDecrease = () => setQuantity(current => Math.max(0, current - 1))
44
+ const onIncrease = () => {
45
+ setQuantity(current => {
46
+ if (Number.isFinite(maxQty) && current >= maxQty) return current
47
+ return current + 1
48
+ })
49
+ }
50
+
51
+ const etaLabel = eta || '12 mins'
52
+
53
+ return h('article', {
54
+ ...props,
55
+ className: 'bg-bga text-txa border border-txa/20 rounded-xl p-3 lg:p-4 w-full shadow-sm space-y-2 lg:space-y-3'
56
+ },
57
+
58
+ // Image carousel (clickable to open detail)
59
+ h('div', {
60
+ className: 'relative rounded-lg border border-txa/15 overflow-hidden bg-bgb/10 cursor-pointer',
61
+ onClick: () => setShowDetail(true),
62
+ role: 'button',
63
+ 'aria-label': 'View product details'
64
+ },
65
+ allImages.length > 1
66
+ ? h(Carousel, { dots: true, ariaLabel: `${title} images` },
67
+ allImages.map((img, idx) =>
68
+ h('div', { key: idx, className: 'w-full' },
69
+ h(Img, {
70
+ _model: img,
71
+ _fit: 'contain',
72
+ className: 'w-full bg-bga',
73
+ style: 'aspect-ratio: 1 / 1;'
74
+ })
75
+ )
76
+ )
77
+ )
78
+ : h(Img, {
79
+ _model: image,
80
+ _fit: 'contain',
81
+ className: 'w-full bg-bga',
82
+ style: 'aspect-ratio: 1 / 1;'
83
+ }),
84
+ offerLabel && h('div', { className: 'absolute top-2 left-2 bg-bgb text-txb rounded-sm px-2 py-0.5 text-xs font-bold z-10' }, offerLabel)
85
+ ),
86
+
87
+ // ETA badge
88
+ h('div', { className: 'inline-flex items-center rounded-sm bg-txa/10 text-txa/80 px-2 py-1 text-xs font-semibold uppercase' }, etaLabel),
89
+
90
+ // Product info
91
+ h('div', { className: 'space-y-1 min-h-14' },
92
+ brand && h('div', { className: 'text-xs font-bold uppercase opacity-80' }, brand),
93
+ h(Link, {
94
+ _model: { value: productLink, name: title },
95
+ target: '_blank',
96
+ className: 'block text-sm lg:text-base font-semibold leading-tight'
97
+ }, title),
98
+ description && h('p', { className: 'text-xs text-txa/70 leading-relaxed line-clamp-2 mt-1' }, description)
99
+ ),
100
+
101
+ // Unit selector
102
+ h('div', { className: 'flex items-center gap-2' },
103
+ h('label', { className: 'sr-only', for: unitId }, 'Unit selector'),
104
+ h('select', {
105
+ id: unitId,
106
+ value: selectedUnit,
107
+ onChange (event) { setSelectedUnit(event.currentTarget.value) },
108
+ className: 'w-full rounded-md border border-txa/30 bg-bga text-txa px-2 py-1 text-sm'
109
+ }, units.map(option => h('option', { value: option.value }, option.label)))
110
+ ),
111
+
112
+ // Price + quantity controls
113
+ h('div', { className: 'flex items-end justify-between gap-2' },
114
+ h('div', { className: 'flex items-center gap-2' },
115
+ h('span', { className: 'text-base lg:text-lg font-bold' }, formatPrice(price, currency)),
116
+ oldPrice && h('span', { className: 'text-sm line-through opacity-60' }, formatPrice(oldPrice, currency))
117
+ ),
118
+
119
+ quantity === 0
120
+ ? h('button', {
121
+ type: 'button',
122
+ className: 'clickable inline-flex items-center gap-1 rounded-md border border-bgb bg-bgb text-txb px-3 py-1.5 text-sm font-bold transition hover:bg-txb hover:text-bgb',
123
+ onClick: onIncrease,
124
+ 'aria-label': 'Add item'
125
+ },
126
+ h('span', { 'aria-hidden': true }, '+'),
127
+ 'Add'
128
+ )
129
+ : h('div', {
130
+ className: 'inline-flex items-center rounded-md border border-bgb overflow-hidden',
131
+ role: 'group',
132
+ 'aria-label': 'Quantity selector'
133
+ },
134
+ h('button', {
135
+ type: 'button',
136
+ className: 'clickable bg-bgb text-txb w-8 h-8 text-lg leading-none font-bold',
137
+ onClick: onDecrease,
138
+ 'aria-label': 'Decrease quantity'
139
+ }, '\u2212'),
140
+ h('span', { className: 'px-3 min-w-8 text-center font-semibold' }, quantity),
141
+ h('button', {
142
+ type: 'button',
143
+ className: 'clickable bg-bgb text-txb w-8 h-8 text-lg leading-none font-bold',
144
+ onClick: onIncrease,
145
+ 'aria-label': 'Increase quantity'
146
+ }, '+')
147
+ )
148
+ ),
149
+
150
+ // Detail popup (image gallery + full description)
151
+ h(Popup, {
152
+ _layout: 'full',
153
+ 'aria-label': `${title} details`,
154
+ _show: showDetail,
155
+ onKeyDown (event) {
156
+ if (event.key === 'Escape') {
157
+ event.preventDefault()
158
+ setShowDetail(false)
159
+ }
160
+ }
161
+ },
162
+ h('div', { className: 'flex flex-col h-full bg-bga text-txa' },
163
+
164
+ // Header with close
165
+ h('div', { className: 'flex justify-between items-center p-4 border-b border-txa/10' },
166
+ h('h2', { className: 'text-lg font-bold' }, title),
167
+ h(Button, {
168
+ onClick: () => setShowDetail(false),
169
+ _textless: true,
170
+ _model: { name: 'Close', icon: NAVIGATION_CLOSE }
171
+ })
172
+ ),
173
+
174
+ // Content
175
+ h('div', { className: 'flex-1 overflow-y-auto p-4 lg:p-6' },
176
+ h('div', { className: 'grid sm:grid-cols-1 lg:grid-cols-2 gap-6' },
177
+
178
+ // Image gallery / carousel
179
+ h('div', { className: 'space-y-3' },
180
+ h(Carousel, { dots: true, ariaLabel: `${title} gallery` },
181
+ allImages.map((img, idx) =>
182
+ h('div', { key: idx, className: 'w-full' },
183
+ h(Img, {
184
+ _model: img,
185
+ _fit: 'contain',
186
+ className: 'w-full bg-bga rounded-lg',
187
+ style: 'aspect-ratio: 1 / 1;'
188
+ })
189
+ )
190
+ )
191
+ ),
192
+ // Thumbnails
193
+ allImages.length > 1 && h('div', { className: 'flex gap-2 overflow-x-auto' },
194
+ allImages.map((img, idx) =>
195
+ h('div', { key: idx, className: 'shrink-0 w-14 h-14 lg:w-16 lg:h-16 rounded-md border border-txa/10 overflow-hidden cursor-pointer' },
196
+ h(Img, { _model: img, _fit: 'contain', className: 'w-full h-full object-contain' })
197
+ )
198
+ )
199
+ )
200
+ ),
201
+
202
+ // Product details
203
+ h('div', { className: 'space-y-4' },
204
+ brand && h('div', { className: 'text-xs font-bold uppercase opacity-70' }, brand),
205
+ h('h3', { className: 'text-xl font-bold' }, title),
206
+
207
+ h('div', { className: 'flex items-center gap-3' },
208
+ h('span', { className: 'text-2xl font-bold' }, formatPrice(price, currency)),
209
+ oldPrice && h('span', { className: 'text-lg line-through opacity-50' }, formatPrice(oldPrice, currency)),
210
+ offerLabel && h('span', { className: 'bg-bgb text-txb rounded-sm px-2 py-0.5 text-xs font-bold' }, offerLabel)
211
+ ),
212
+
213
+ h('div', { className: 'flex items-center gap-2' },
214
+ h('span', { className: 'inline-flex items-center rounded-sm bg-txa/10 px-2 py-1 text-xs font-semibold uppercase' }, etaLabel)
215
+ ),
216
+
217
+ // Unit selector in detail
218
+ h('div', { className: 'max-w-xs' },
219
+ h('label', { className: 'block text-xs font-semibold text-txa/70 mb-1' }, 'Select size'),
220
+ h('select', {
221
+ value: selectedUnit,
222
+ onChange (event) { setSelectedUnit(event.currentTarget.value) },
223
+ className: 'w-full rounded-md border border-txa/30 bg-bga text-txa px-3 py-2 text-sm'
224
+ }, units.map(option => h('option', { value: option.value }, option.label)))
225
+ ),
226
+
227
+ // Description
228
+ description && h('div', { className: 'space-y-2' },
229
+ h('h4', { className: 'text-sm font-bold' }, 'Description'),
230
+ h('p', { className: 'text-sm text-txa/80 leading-relaxed whitespace-pre-line' }, description)
231
+ ),
232
+
233
+ // Add to cart in detail view
234
+ h('div', { className: 'pt-3' },
235
+ quantity === 0
236
+ ? h('button', {
237
+ type: 'button',
238
+ className: 'clickable inline-flex items-center gap-2 rounded-md bg-bgb text-txb px-5 py-2.5 text-sm font-bold hover:bg-txb hover:text-bgb transition',
239
+ onClick: onIncrease,
240
+ 'aria-label': 'Add item'
241
+ }, '+ Add to Cart')
242
+ : h('div', {
243
+ className: 'inline-flex items-center rounded-md border border-bgb overflow-hidden',
244
+ role: 'group',
245
+ 'aria-label': 'Quantity selector'
246
+ },
247
+ h('button', {
248
+ type: 'button',
249
+ className: 'clickable bg-bgb text-txb w-10 h-10 text-xl font-bold',
250
+ onClick: onDecrease,
251
+ 'aria-label': 'Decrease quantity'
252
+ }, '\u2212'),
253
+ h('span', { className: 'px-4 min-w-10 text-center text-base font-semibold' }, quantity),
254
+ h('button', {
255
+ type: 'button',
256
+ className: 'clickable bg-bgb text-txb w-10 h-10 text-xl font-bold',
257
+ onClick: onIncrease,
258
+ 'aria-label': 'Increase quantity'
259
+ }, '+')
260
+ )
261
+ )
262
+ )
263
+ )
264
+ )
265
+ )
266
+ )
267
+ )
268
+ }
269
+
270
+ function getUnitOptions (model) {
271
+ const raw = model.units || model.sizes || model.options || model.tags?.map(tag => tag.value)
272
+
273
+ if (!Array.isArray(raw) || raw.length === 0) {
274
+ return [{ label: model.unit || DEFAULT_UNIT, value: model.unit || DEFAULT_UNIT }]
275
+ }
276
+
277
+ return raw.map((option, index) => {
278
+ if (typeof option === 'string') return { label: option, value: option }
279
+ const label = option?.name || option?.label || option?.value || `${DEFAULT_UNIT}-${index + 1}`
280
+ const value = option?.value || label
281
+ return { label, value }
282
+ })
283
+ }
284
+
285
+ function formatPrice (value, currency) {
286
+ if (typeof value === 'number') return `${currency}${value.toFixed(2)}`
287
+ if (typeof value === 'string' && value.length > 0) return value
288
+ return `${currency}0.00`
289
+ }
290
+
291
+ function slugify (value) {
292
+ return String(value || 'item').toLowerCase().replaceAll(/[^a-z0-9]+/g, '-').replaceAll(/(^-|-$)/g, '')
293
+ }
@@ -0,0 +1,23 @@
1
+ import { Asset } from '../molecules/asset.js'
2
+ import { PromoText } from '../molecules/promo-text.js'
3
+ import { ValidationError } from '../routing/error-handler.js'
4
+ import h from '../shared/h.js'
5
+
6
+ export function PromoSimple ({ _model, ...props }) {
7
+ const { title, text, links, asset } = _model
8
+ if (!title && !text && links.length === 0) {
9
+ throw new ValidationError()
10
+ }
11
+
12
+ const titleOnly = !text && links.length === 0
13
+ const titleSize = asset.src ? '2xl' : '4xl'
14
+
15
+ return h('div', { ...props, className: 'bg-bga text-txa h-full rounded-2xl overflow-hidden' },
16
+ asset.src && h('div',
17
+ h(Asset, { _model })
18
+ ),
19
+ titleOnly
20
+ ? h('p', { className: `p-6 font-custom font-bold text-txc text-${titleSize}` }, title)
21
+ : h(PromoText, { _model })
22
+ )
23
+ }
@@ -0,0 +1,21 @@
1
+ import { RichText } from '../atoms/rich-text.js'
2
+ import { ValidationError } from '../routing/error-handler.js'
3
+ import h from '../shared/h.js'
4
+
5
+ export function Quote ({ _model, _variant, ...props }) {
6
+ const { title, text } = _model
7
+ if (!title || !text) {
8
+ throw new ValidationError()
9
+ }
10
+
11
+ const sizes = { small: 3, medium: 7, large: 8 }
12
+ const textSize = sizes[_variant] || sizes.medium
13
+
14
+ return h('blockquote', { ...props, className: 'space-y-5 p-6 bg-bga text-txa h-full rounded-2xl' },
15
+ h('div', {
16
+ className: `font-custom text-${textSize}xl`,
17
+ children: h(RichText, text)
18
+ }),
19
+ h('cite', { className: 'block' }, title)
20
+ )
21
+ }
@@ -0,0 +1,42 @@
1
+ import { Box } from '../atoms/box.js'
2
+ import { Button } from '../atoms/button.js'
3
+ import { Form } from '../atoms/form.js'
4
+ import { Input } from '../atoms/input.js'
5
+ import h from '../shared/h.js'
6
+ import { NAVIGATION_SEARCH } from '../shared/icons.js'
7
+ import t from '../shared/t.js'
8
+
9
+ export function SearchForm ({ _model, ...props }) {
10
+ const { links } = _model
11
+ const search = links?.[1] || { name: '', value: null }
12
+ const count = search.value
13
+ const title = t('Enter your search term')
14
+
15
+ return h(Box, { ...props, className: 'rounded-2xl text-txa bg-bga' },
16
+ h(Form, { name: t('Search'), action: '?', role: 'search' },
17
+ h('div', { className: 'flex space-x-5' },
18
+ h('div', { className: 'grow' },
19
+ h(Input, {
20
+ type: 'search',
21
+ name: 'q',
22
+ value: new URLSearchParams(globalThis.location.search)?.get?.('q'),
23
+ placeholder: links?.[0]?.name,
24
+ title,
25
+ 'data-label': title
26
+ })
27
+ ),
28
+ h('div', { className: 'grow-0' },
29
+ h(Button, {
30
+ _textless: true,
31
+ _model: {
32
+ name: t('Submit'),
33
+ icon: NAVIGATION_SEARCH
34
+ },
35
+ type: 'submit'
36
+ })
37
+ )
38
+ )
39
+ ),
40
+ count && h('p', t('{0} results found', count))
41
+ )
42
+ }
@@ -0,0 +1,66 @@
1
+ import { Link } from '../atoms/link.js'
2
+ import h from '../shared/h.js'
3
+ import { ARROW_BACK, ARROW_NEXT } from '../shared/icons.js'
4
+ import t from '../shared/t.js'
5
+ import { Icon } from '../atoms/icon.js'
6
+
7
+ const REACH = 4
8
+
9
+ export function SearchNav ({ _model, ...props }) {
10
+ const { links } = _model
11
+
12
+ // Do not render if no selected link.
13
+ const selectedIndex = links.findIndex(l => l.selected) || 0
14
+
15
+ // Get base indexes.
16
+ let startIndex = selectedIndex - REACH
17
+ let endIndex = selectedIndex + REACH + 1
18
+
19
+ // Offset
20
+ endIndex -= Math.min(0, startIndex)
21
+ startIndex += Math.min(0, links.length - endIndex)
22
+
23
+ // Cap
24
+ startIndex = Math.max(startIndex, 0)
25
+ endIndex = Math.min(endIndex, links.length)
26
+
27
+ const items = links.slice(startIndex, endIndex).map(link => h('li',
28
+ h(Link, {
29
+ _model: link,
30
+ _variant: link.selected ? 'bold' : 'simple',
31
+ children: h('div', { className: 'w-6 text-center' }, link.name)
32
+ })
33
+ ))
34
+
35
+ const prev = links[selectedIndex - 1]
36
+ items.unshift(h('li', { className: prev ? null : 'opacity-50' },
37
+ h(Link, {
38
+ 'aria-label': prev?.value && t('Previous'),
39
+ _variant: prev ? 'simple' : null,
40
+ _model: { value: prev?.value, icon: ARROW_BACK },
41
+ children: h('div', { className: 'flex items-center justify-center min-w-6' },
42
+ h(Icon, { _size: 'sm' }, ARROW_BACK),
43
+ h('div', { className: 'sm:hidden md:hidden ms-1 me-3' }, t('Previous'))
44
+ )
45
+ })
46
+ ))
47
+
48
+ const next = links[selectedIndex + 1]
49
+ items.push(h('li', { className: next ? null : 'opacity-50' },
50
+ h(Link, {
51
+ 'aria-label': next?.value && t('Next'),
52
+ _variant: next ? 'simple' : null,
53
+ _model: { value: next?.value, icon: ARROW_NEXT },
54
+ children: h('div', { className: 'flex items-center justify-center min-w-6' },
55
+ h('div', { className: 'sm:hidden md:hidden ms-3 me-1' }, t('Next')),
56
+ h(Icon, { _size: 'sm' }, ARROW_NEXT)
57
+ )
58
+ })
59
+ ))
60
+
61
+ return h('ol', {
62
+ ...props,
63
+ className: 'w-full p-8 flex flex-wrap justify-center items-center bg-bga text-txa rounded-2xl',
64
+ children: items
65
+ })
66
+ }