@zalify/storefront-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/LICENSE.md +49 -0
  2. package/README.md +63 -0
  3. package/dist/commerce/countdown.d.ts +18 -0
  4. package/dist/commerce/countdown.js +28 -0
  5. package/dist/commerce/events.d.ts +29 -0
  6. package/dist/commerce/events.js +14 -0
  7. package/dist/commerce/facets.d.ts +27 -0
  8. package/dist/commerce/facets.js +71 -0
  9. package/dist/commerce/index.d.ts +11 -0
  10. package/dist/commerce/index.js +11 -0
  11. package/dist/commerce/money.d.ts +51 -0
  12. package/dist/commerce/money.js +83 -0
  13. package/dist/commerce/product.d.ts +102 -0
  14. package/dist/commerce/product.js +93 -0
  15. package/dist/editor/frame.d.ts +57 -0
  16. package/dist/editor/frame.js +241 -0
  17. package/dist/editor/host.d.ts +31 -0
  18. package/dist/editor/host.js +67 -0
  19. package/dist/editor/index.d.ts +9 -0
  20. package/dist/editor/index.js +9 -0
  21. package/dist/react/adapter.d.ts +49 -0
  22. package/dist/react/adapter.js +23 -0
  23. package/dist/react/blocks/_box.d.ts +10 -0
  24. package/dist/react/blocks/_box.js +17 -0
  25. package/dist/react/blocks/_slide.d.ts +24 -0
  26. package/dist/react/blocks/_slide.js +21 -0
  27. package/dist/react/blocks/_video-card.d.ts +11 -0
  28. package/dist/react/blocks/_video-card.js +39 -0
  29. package/dist/react/blocks/accordion.d.ts +12 -0
  30. package/dist/react/blocks/accordion.js +5 -0
  31. package/dist/react/blocks/button.d.ts +10 -0
  32. package/dist/react/blocks/button.js +15 -0
  33. package/dist/react/blocks/countdown.d.ts +9 -0
  34. package/dist/react/blocks/countdown.js +64 -0
  35. package/dist/react/blocks/custom-liquid.d.ts +11 -0
  36. package/dist/react/blocks/custom-liquid.js +4 -0
  37. package/dist/react/blocks/group.d.ts +9 -0
  38. package/dist/react/blocks/group.js +8 -0
  39. package/dist/react/blocks/image.d.ts +8 -0
  40. package/dist/react/blocks/image.js +7 -0
  41. package/dist/react/blocks/message.d.ts +14 -0
  42. package/dist/react/blocks/message.js +5 -0
  43. package/dist/react/blocks/quote.d.ts +9 -0
  44. package/dist/react/blocks/quote.js +5 -0
  45. package/dist/react/blocks/spacer.d.ts +8 -0
  46. package/dist/react/blocks/spacer.js +5 -0
  47. package/dist/react/blocks/story.d.ts +17 -0
  48. package/dist/react/blocks/story.js +7 -0
  49. package/dist/react/blocks/text.d.ts +9 -0
  50. package/dist/react/blocks/text.js +5 -0
  51. package/dist/react/blocks/video.d.ts +17 -0
  52. package/dist/react/blocks/video.js +56 -0
  53. package/dist/react/components/Facets.d.ts +21 -0
  54. package/dist/react/components/Facets.js +220 -0
  55. package/dist/react/components/Icon.d.ts +12 -0
  56. package/dist/react/components/Icon.js +66 -0
  57. package/dist/react/components/PaymentIcons.d.ts +5 -0
  58. package/dist/react/components/PaymentIcons.generated.d.ts +22 -0
  59. package/dist/react/components/PaymentIcons.generated.js +21 -0
  60. package/dist/react/components/PaymentIcons.js +18 -0
  61. package/dist/react/components/Price.d.ts +24 -0
  62. package/dist/react/components/Price.js +9 -0
  63. package/dist/react/components/ProductBadges.d.ts +24 -0
  64. package/dist/react/components/ProductBadges.js +57 -0
  65. package/dist/react/components/ProductCard.d.ts +17 -0
  66. package/dist/react/components/ProductCard.js +202 -0
  67. package/dist/react/components/Rating.d.ts +11 -0
  68. package/dist/react/components/Rating.js +35 -0
  69. package/dist/react/components/SwatchStyle.d.ts +23 -0
  70. package/dist/react/components/SwatchStyle.js +22 -0
  71. package/dist/react/components/VideoModal.d.ts +55 -0
  72. package/dist/react/components/VideoModal.js +102 -0
  73. package/dist/react/engine/CssVariables.d.ts +1 -0
  74. package/dist/react/engine/CssVariables.js +105 -0
  75. package/dist/react/engine/context.d.ts +43 -0
  76. package/dist/react/engine/context.js +45 -0
  77. package/dist/react/engine/fonts.d.ts +15 -0
  78. package/dist/react/engine/fonts.js +33 -0
  79. package/dist/react/engine/images.d.ts +24 -0
  80. package/dist/react/engine/images.js +40 -0
  81. package/dist/react/engine/render.d.ts +29 -0
  82. package/dist/react/engine/render.js +87 -0
  83. package/dist/react/engine/settings.d.ts +8 -0
  84. package/dist/react/engine/settings.js +68 -0
  85. package/dist/react/engine/store.d.ts +64 -0
  86. package/dist/react/engine/store.js +42 -0
  87. package/dist/react/engine/translate.d.ts +7 -0
  88. package/dist/react/engine/translate.js +33 -0
  89. package/dist/react/engine/types.d.ts +128 -0
  90. package/dist/react/engine/types.js +1 -0
  91. package/dist/react/index.d.ts +38 -0
  92. package/dist/react/index.js +39 -0
  93. package/dist/react/registries.d.ts +10 -0
  94. package/dist/react/registries.js +68 -0
  95. package/dist/react/registries.server.d.ts +2 -0
  96. package/dist/react/registries.server.js +98 -0
  97. package/dist/react/sections/404.d.ts +10 -0
  98. package/dist/react/sections/404.js +10 -0
  99. package/dist/react/sections/announcement-bar.d.ts +14 -0
  100. package/dist/react/sections/announcement-bar.js +84 -0
  101. package/dist/react/sections/article.d.ts +17 -0
  102. package/dist/react/sections/article.js +31 -0
  103. package/dist/react/sections/contact-form.d.ts +16 -0
  104. package/dist/react/sections/contact-form.js +8 -0
  105. package/dist/react/sections/custom-liquid.d.ts +14 -0
  106. package/dist/react/sections/custom-liquid.js +7 -0
  107. package/dist/react/sections/custom-section.d.ts +14 -0
  108. package/dist/react/sections/custom-section.js +9 -0
  109. package/dist/react/sections/featured-collection.d.ts +10 -0
  110. package/dist/react/sections/featured-collection.js +23 -0
  111. package/dist/react/sections/hero.d.ts +6 -0
  112. package/dist/react/sections/hero.js +35 -0
  113. package/dist/react/sections/image-with-text.d.ts +13 -0
  114. package/dist/react/sections/image-with-text.js +9 -0
  115. package/dist/react/sections/multicolumn.d.ts +15 -0
  116. package/dist/react/sections/multicolumn.js +8 -0
  117. package/dist/react/sections/page.d.ts +13 -0
  118. package/dist/react/sections/page.js +11 -0
  119. package/dist/react/sections/password.d.ts +15 -0
  120. package/dist/react/sections/password.js +9 -0
  121. package/dist/react/sections/rich-text.d.ts +12 -0
  122. package/dist/react/sections/rich-text.js +7 -0
  123. package/dist/react/sections/stories.d.ts +18 -0
  124. package/dist/react/sections/stories.js +10 -0
  125. package/dist/react/sections/video-bubble.d.ts +13 -0
  126. package/dist/react/sections/video-bubble.js +54 -0
  127. package/dist/react/sections/video-carousel.d.ts +19 -0
  128. package/dist/react/sections/video-carousel.js +9 -0
  129. package/dist/react/server.d.ts +7 -0
  130. package/dist/react/server.js +42 -0
  131. package/dist/schemas/bridge.d.ts +98 -0
  132. package/dist/schemas/bridge.js +50 -0
  133. package/dist/schemas/data.d.ts +54 -0
  134. package/dist/schemas/data.js +22 -0
  135. package/dist/schemas/index.d.ts +14 -0
  136. package/dist/schemas/index.js +14 -0
  137. package/dist/schemas/manifest.d.ts +63 -0
  138. package/dist/schemas/manifest.js +32 -0
  139. package/dist/schemas/schema.d.ts +159 -0
  140. package/dist/schemas/schema.js +129 -0
  141. package/dist/schemas/validate.d.ts +22 -0
  142. package/dist/schemas/validate.js +199 -0
  143. package/package.json +84 -0
  144. package/src/commerce/countdown.ts +38 -0
  145. package/src/commerce/events.ts +43 -0
  146. package/src/commerce/facets.ts +88 -0
  147. package/src/commerce/index.ts +11 -0
  148. package/src/commerce/money.ts +120 -0
  149. package/src/commerce/product.ts +177 -0
  150. package/src/editor/frame.ts +305 -0
  151. package/src/editor/host.ts +101 -0
  152. package/src/editor/index.ts +9 -0
  153. package/src/react/adapter.tsx +83 -0
  154. package/src/react/blocks/_box.tsx +37 -0
  155. package/src/react/blocks/_slide.tsx +96 -0
  156. package/src/react/blocks/_video-card.tsx +131 -0
  157. package/src/react/blocks/accordion.tsx +33 -0
  158. package/src/react/blocks/button.tsx +36 -0
  159. package/src/react/blocks/countdown.tsx +105 -0
  160. package/src/react/blocks/custom-liquid.tsx +21 -0
  161. package/src/react/blocks/group.tsx +32 -0
  162. package/src/react/blocks/image.tsx +29 -0
  163. package/src/react/blocks/message.tsx +28 -0
  164. package/src/react/blocks/quote.tsx +24 -0
  165. package/src/react/blocks/spacer.tsx +17 -0
  166. package/src/react/blocks/story.tsx +40 -0
  167. package/src/react/blocks/text.tsx +24 -0
  168. package/src/react/blocks/video.tsx +132 -0
  169. package/src/react/components/Facets.tsx +424 -0
  170. package/src/react/components/Icon.tsx +96 -0
  171. package/src/react/components/PaymentIcons.generated.ts +24 -0
  172. package/src/react/components/PaymentIcons.tsx +27 -0
  173. package/src/react/components/Price.tsx +72 -0
  174. package/src/react/components/ProductBadges.tsx +95 -0
  175. package/src/react/components/ProductCard.tsx +417 -0
  176. package/src/react/components/Rating.tsx +59 -0
  177. package/src/react/components/SwatchStyle.tsx +50 -0
  178. package/src/react/components/VideoModal.tsx +236 -0
  179. package/src/react/engine/CssVariables.tsx +142 -0
  180. package/src/react/engine/context.tsx +108 -0
  181. package/src/react/engine/fonts.ts +42 -0
  182. package/src/react/engine/images.tsx +58 -0
  183. package/src/react/engine/render.tsx +190 -0
  184. package/src/react/engine/settings.ts +89 -0
  185. package/src/react/engine/store.ts +103 -0
  186. package/src/react/engine/translate.ts +40 -0
  187. package/src/react/engine/types.ts +142 -0
  188. package/src/react/index.ts +78 -0
  189. package/src/react/registries.server.ts +110 -0
  190. package/src/react/registries.ts +80 -0
  191. package/src/react/sections/404.tsx +37 -0
  192. package/src/react/sections/announcement-bar.tsx +170 -0
  193. package/src/react/sections/article.tsx +97 -0
  194. package/src/react/sections/contact-form.tsx +104 -0
  195. package/src/react/sections/custom-liquid.tsx +39 -0
  196. package/src/react/sections/custom-section.tsx +44 -0
  197. package/src/react/sections/featured-collection.tsx +89 -0
  198. package/src/react/sections/hero.tsx +84 -0
  199. package/src/react/sections/image-with-text.tsx +61 -0
  200. package/src/react/sections/multicolumn.tsx +46 -0
  201. package/src/react/sections/page.tsx +43 -0
  202. package/src/react/sections/password.tsx +60 -0
  203. package/src/react/sections/rich-text.tsx +37 -0
  204. package/src/react/sections/stories.tsx +51 -0
  205. package/src/react/sections/video-bubble.tsx +145 -0
  206. package/src/react/sections/video-carousel.tsx +57 -0
  207. package/src/react/server.ts +60 -0
  208. package/src/schemas/bridge.ts +143 -0
  209. package/src/schemas/data.ts +72 -0
  210. package/src/schemas/index.ts +14 -0
  211. package/src/schemas/manifest.ts +85 -0
  212. package/src/schemas/schema.ts +287 -0
  213. package/src/schemas/validate.ts +302 -0
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Port of sections/announcement-bar.liquid — rotating messages bar.
4
+ * The Liquid theme drives rotation with the <announcement-bar> web
5
+ * component (src/entries/announcement.ts); here the same behavior
6
+ * lives in React: autoplay (paused for reduced motion), prev/next,
7
+ * pause/play. data-paused on the host drives the icon swap in CSS.
8
+ * CSS: app/styles/components/sections-announcement-bar.css
9
+ */
10
+ import { useEffect, useRef, useState } from 'react';
11
+ import { t } from '../engine/translate';
12
+ import { Icon } from '../components/Icon';
13
+ /*
14
+ * The bar's height is a CONSTANT, published as a token so layout math
15
+ * (e.g. the large hero's "viewport minus header stack") can be computed
16
+ * in pure CSS at first paint — mirror of the section's {% style %} tag.
17
+ * Removing this section removes the token (consumers fall back to 0).
18
+ */
19
+ const HEIGHT_TOKEN_CSS = `
20
+ :root {
21
+ --announcement-bar-height: calc(0.6875rem * 2.8 + 2 * var(--space-xs));
22
+ }
23
+ @media (min-width: 48rem) {
24
+ :root {
25
+ --announcement-bar-height: calc(var(--type-xs) * 2.8 + 2 * var(--space-xs));
26
+ }
27
+ }
28
+ `;
29
+ export default function AnnouncementBarSection({ section, settings, children, }) {
30
+ const { rotation_speed: rotationSpeed = 5, color_scheme: colorScheme = 'scheme-4' } = settings;
31
+ const messageCount = (section.block_order ?? []).filter((key) => {
32
+ const block = section.blocks?.[key];
33
+ return block && !block.disabled;
34
+ }).length;
35
+ const hostRef = useRef(null);
36
+ const indexRef = useRef(0);
37
+ const timerRef = useRef();
38
+ const pausedRef = useRef(false);
39
+ const [paused, setPaused] = useState(false);
40
+ const messages = () => Array.from(hostRef.current?.querySelectorAll('.announcement-bar__message') ?? []);
41
+ const show = (index) => {
42
+ const items = messages();
43
+ if (items.length < 2)
44
+ return;
45
+ indexRef.current = (index + items.length) % items.length;
46
+ items.forEach((message, i) => {
47
+ message.hidden = i !== indexRef.current;
48
+ });
49
+ };
50
+ const start = () => {
51
+ clearInterval(timerRef.current);
52
+ if (pausedRef.current || messageCount < 2)
53
+ return;
54
+ const speed = rotationSpeed || 5;
55
+ timerRef.current = setInterval(() => show(indexRef.current + 1), speed * 1000);
56
+ };
57
+ const setPausedState = (value) => {
58
+ pausedRef.current = value;
59
+ setPaused(value);
60
+ };
61
+ useEffect(() => {
62
+ // Autoplay starts paused when the user prefers reduced motion.
63
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
64
+ setPausedState(true);
65
+ }
66
+ start();
67
+ return () => clearInterval(timerRef.current);
68
+ // eslint-disable-next-line react-hooks/exhaustive-deps
69
+ }, [messageCount, rotationSpeed]);
70
+ const previous = () => {
71
+ show(indexRef.current - 1);
72
+ start();
73
+ };
74
+ const next = () => {
75
+ show(indexRef.current + 1);
76
+ start();
77
+ };
78
+ const toggle = () => {
79
+ setPausedState(!pausedRef.current);
80
+ // pausedRef is already updated; restart (or stop) the timer.
81
+ start();
82
+ };
83
+ return (_jsxs("announcement-bar", { ref: hostRef, className: `announcement-bar color-${colorScheme} full-width`, "data-speed": rotationSpeed, "data-paused": paused ? '' : undefined, children: [_jsx("style", { dangerouslySetInnerHTML: { __html: HEIGHT_TOKEN_CSS } }), _jsxs("div", { className: "announcement-bar__inner", children: [_jsx("div", { className: "announcement-bar__messages", children: children }), messageCount > 1 && (_jsxs("div", { className: "announcement-bar__controls", children: [_jsx("button", { type: "button", className: "announcement-bar__control", onClick: previous, "aria-label": t('general.previous'), children: _jsx(Icon, { name: "icon-chevron-left" }) }), _jsx("button", { type: "button", className: "announcement-bar__control", onClick: next, "aria-label": t('general.next'), children: _jsx(Icon, { name: "icon-chevron-right" }) }), _jsxs("button", { type: "button", className: "announcement-bar__control announcement-bar__toggle", onClick: toggle, "aria-label": t('general.pause'), children: [_jsx("span", { className: "announcement-bar__icon-pause", children: _jsx(Icon, { name: "icon-pause" }) }), _jsx("span", { className: "announcement-bar__icon-play", children: _jsx(Icon, { name: "icon-play" }) })] })] }))] })] }));
84
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Port of sections/article.liquid. CSS: app/styles/components/sections-article.css
3
+ *
4
+ * Data: useResource('article') — the scaffold ARTICLE query shape
5
+ * ({handle, title, contentHtml, publishedAt, author: {name}, image}),
6
+ * plus useResource('blog') ({handle, title}) for the back link.
7
+ *
8
+ * The Liquid comments block (blog.comments_enabled?, {% form
9
+ * 'new_comment' %}) has no Storefront API mirror and is omitted.
10
+ */
11
+ import type { SectionProps } from '../engine/types';
12
+ interface ArticleSettings {
13
+ color_scheme?: string;
14
+ section_spacing?: string;
15
+ }
16
+ export default function ArticleSection({ settings, }: SectionProps<ArticleSettings>): import("react").JSX.Element | null;
17
+ export {};
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useResource } from '../engine/context';
3
+ import { t } from '../engine/translate';
4
+ import { imageSrcSet, imageUrl } from '../engine/images';
5
+ import { useAdapter } from '../adapter';
6
+ function articleDateHtml(publishedAt) {
7
+ if (!publishedAt)
8
+ return '';
9
+ const formatted = new Intl.DateTimeFormat('en-US', {
10
+ year: 'numeric',
11
+ month: 'long',
12
+ day: 'numeric',
13
+ }).format(new Date(publishedAt));
14
+ return `<time datetime="${publishedAt}">${formatted}</time>`;
15
+ }
16
+ export default function ArticleSection({ settings, }) {
17
+ const { Link } = useAdapter();
18
+ const { color_scheme: colorScheme = 'scheme-1', section_spacing: spacing = 'md' } = settings;
19
+ const article = useResource('article');
20
+ const blog = useResource('blog') ?? article?.blog;
21
+ if (!article)
22
+ return null;
23
+ return (_jsx("article", { className: `article section color-${colorScheme} full-width`, style: {
24
+ '--section-spacing': `var(--space-section-${spacing})`,
25
+ }, children: _jsxs("div", { className: "article__inner stack", style: { '--stack-gap': 'var(--space-lg)' }, children: [_jsxs("header", { className: "article__header stack", style: { '--stack-gap': 'var(--space-sm)' }, children: [blog?.handle && (_jsx(Link, { className: "article__back", to: `/blogs/${blog.handle}`, children: t('blog.back_to_blog', { blog: blog.title ?? blog.handle }) })), _jsx("p", { className: "article__meta", dangerouslySetInnerHTML: {
26
+ __html: t('blog.article_metadata_html', {
27
+ date: articleDateHtml(article.publishedAt),
28
+ author: article.author?.name ?? '',
29
+ }),
30
+ } }), _jsx("h1", { className: "article__title", children: article.title })] }), article.image?.url && (_jsx("img", { className: "article__image", src: imageUrl(article.image.url, 1500), srcSet: imageSrcSet(article.image.url), sizes: "(min-width: 64rem) 60rem, 100vw", loading: "eager", width: article.image.width ?? undefined, height: article.image.height ?? undefined, alt: article.image.altText ?? article.title ?? '' })), _jsx("div", { className: "article__content prose", dangerouslySetInnerHTML: { __html: article.contentHtml ?? '' } })] }) }));
31
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Port of sections/contact-form.liquid.
3
+ * CSS: app/styles/components/sections-contact-form.css
4
+ *
5
+ * Shopify's native `{% form 'contact' %}` POST endpoint isn't available
6
+ * in Hydrogen — the markup is rendered faithfully but submission is a
7
+ * no-op. TODO: wire the submit to a route action (e.g. a proxy to the
8
+ * Online Store contact endpoint or a third-party form service).
9
+ */
10
+ import type { SectionProps } from '../engine/types';
11
+ interface ContactFormSettings {
12
+ color_scheme?: string;
13
+ section_spacing?: string;
14
+ }
15
+ export default function ContactFormSection({ id, settings, }: SectionProps<ContactFormSettings>): import("react").JSX.Element;
16
+ export {};
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { t } from '../engine/translate';
3
+ export default function ContactFormSection({ id, settings, }) {
4
+ const { color_scheme: colorScheme = 'scheme-1', section_spacing: spacing = 'md' } = settings;
5
+ return (_jsx("div", { className: `contact section color-${colorScheme} full-width`, style: {
6
+ '--section-spacing': `var(--space-section-${spacing})`,
7
+ }, children: _jsx("div", { className: "contact__inner stack", style: { '--stack-gap': 'var(--space-lg)' }, children: _jsxs("form", { className: "contact__form stack", method: "post", onSubmit: (event) => event.preventDefault(), children: [_jsxs("div", { className: "contact__row", children: [_jsxs("div", { className: "field", children: [_jsx("label", { className: "field__label", htmlFor: `ContactName-${id}`, children: t('contact.name') }), _jsx("input", { id: `ContactName-${id}`, type: "text", name: "contact[name]", autoComplete: "name" })] }), _jsxs("div", { className: "field", children: [_jsx("label", { className: "field__label", htmlFor: `ContactEmail-${id}`, children: t('contact.email') }), _jsx("input", { id: `ContactEmail-${id}`, type: "email", name: "contact[email]", autoComplete: "email", spellCheck: false, autoCapitalize: "off", required: true })] })] }), _jsxs("div", { className: "field", children: [_jsx("label", { className: "field__label", htmlFor: `ContactPhone-${id}`, children: t('contact.phone') }), _jsx("input", { id: `ContactPhone-${id}`, type: "tel", name: "contact[phone]", autoComplete: "tel", pattern: "[0-9\\-]*" })] }), _jsxs("div", { className: "field", children: [_jsx("label", { className: "field__label", htmlFor: `ContactMessage-${id}`, children: t('contact.message') }), _jsx("textarea", { id: `ContactMessage-${id}`, name: "contact[body]", rows: 8, required: true })] }), _jsx("button", { type: "submit", className: "button contact__submit", children: t('contact.send') })] }) }) }));
8
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Port of sections/custom-liquid.liquid — merchant-pasted markup.
3
+ * The setting's raw HTML is injected as-is; any Liquid tags/objects in
4
+ * it cannot execute here (no Liquid runtime), so only the plain-HTML
5
+ * use case survives the mirror. CSS: sections-custom-liquid.css
6
+ */
7
+ import type { SectionProps } from '../engine/types';
8
+ interface CustomLiquidSettings {
9
+ custom_liquid?: string;
10
+ color_scheme?: string;
11
+ section_spacing?: 'none' | 'sm' | 'md' | 'lg';
12
+ }
13
+ export default function CustomLiquidSection({ settings, }: SectionProps<CustomLiquidSettings>): import("react").JSX.Element;
14
+ export {};
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export default function CustomLiquidSection({ settings, }) {
3
+ const { custom_liquid: customLiquid = '', color_scheme: colorScheme = 'scheme-1', section_spacing: sectionSpacing = 'md', } = settings;
4
+ return (_jsx("div", { className: `custom-liquid section color-${colorScheme} full-width`, style: {
5
+ '--section-spacing': `var(--space-section-${sectionSpacing})`,
6
+ }, children: _jsx("div", { className: "custom-liquid__inner", dangerouslySetInnerHTML: { __html: customLiquid } }) }));
7
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Port of sections/custom-section.liquid — the canonical v3 section
3
+ * pattern: color_scheme + section_spacing on the wrapper, optional
4
+ * background image, blocks in a content grid.
5
+ * CSS: app/styles/components/sections-custom-section.css
6
+ */
7
+ import type { SectionProps } from '../engine/types';
8
+ interface CustomSectionSettings {
9
+ background_image?: string;
10
+ color_scheme?: string;
11
+ section_spacing?: 'none' | 'sm' | 'md' | 'lg';
12
+ }
13
+ export default function CustomSection({ settings, children, }: SectionProps<CustomSectionSettings>): import("react").JSX.Element;
14
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { imageUrl, settingImageUrl } from '../engine/images';
3
+ export default function CustomSection({ settings, children, }) {
4
+ const { color_scheme: colorScheme = 'scheme-1', section_spacing: sectionSpacing = 'md', } = settings;
5
+ const backgroundImage = settingImageUrl(settings.background_image);
6
+ return (_jsxs("div", { className: `custom-section section full-width color-${colorScheme}`, style: {
7
+ '--section-spacing': `var(--space-section-${sectionSpacing})`,
8
+ }, children: [backgroundImage ? (_jsx("div", { className: "custom-section__background", children: _jsx("img", { src: imageUrl(backgroundImage, 2000), alt: "" }) })) : null, _jsx("div", { className: "custom-section__content", children: children })] }));
9
+ }
@@ -0,0 +1,10 @@
1
+ import type { SectionProps } from '../engine/types';
2
+ interface FeaturedCollectionSettings {
3
+ heading?: string;
4
+ collection?: string;
5
+ products_to_show?: number;
6
+ color_scheme?: string;
7
+ section_spacing?: 'none' | 'sm' | 'md' | 'lg';
8
+ }
9
+ export default function FeaturedCollectionSection({ settings, }: SectionProps<FeaturedCollectionSettings>): import("react").JSX.Element;
10
+ export {};
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Port of sections/featured-collection.liquid — heading + product grid.
4
+ * Products come from this section's server loader
5
+ * (featured-collection.server.ts) via useSectionData(); without data it
6
+ * renders the Liquid onboarding placeholders.
7
+ * CSS: app/styles/components/sections-featured-collection.css
8
+ */
9
+ import { ProductCard } from '../components/ProductCard';
10
+ import { useSectionData } from '../engine/context';
11
+ import { PlaceholderSvg } from '../engine/images';
12
+ import { t } from '../engine/translate';
13
+ export default function FeaturedCollectionSection({ settings, }) {
14
+ const { heading = 'Featured collection', products_to_show: productsToShow = 4, color_scheme: colorScheme = 'scheme-1', section_spacing: sectionSpacing = 'md', } = settings;
15
+ const data = useSectionData();
16
+ const collection = data?.collection ?? null;
17
+ const products = (collection?.products?.nodes ?? []).slice(0, productsToShow);
18
+ return (_jsx("div", { className: `featured-collection section color-${colorScheme} full-width`, style: {
19
+ '--section-spacing': `var(--space-section-${sectionSpacing})`,
20
+ }, children: _jsxs("div", { className: "featured-collection__inner stack", style: { '--stack-gap': 'var(--space-xl)' }, children: [_jsxs("header", { className: "featured-collection__header cluster", children: [heading ? _jsx("h2", { children: heading }) : null, collection ? (_jsx("a", { className: "featured-collection__link", href: `/collections/${collection.handle}`, children: t('collections.view_all') })) : null] }), _jsx("ul", { className: "product-grid", role: "list", children: products.length > 0
21
+ ? products.map((product) => (_jsx("li", { children: _jsx(ProductCard, { product: product }) }, product.id)))
22
+ : Array.from({ length: productsToShow }, (_, i) => (_jsx("li", { children: _jsx("article", { className: "product-card", children: _jsx("div", { className: "product-card__media", children: _jsx(PlaceholderSvg, { className: "product-card__placeholder" }) }) }) }, i))) })] }) }));
23
+ }
@@ -0,0 +1,6 @@
1
+ import type { SectionProps } from '../engine/types';
2
+ interface HeroSettings {
3
+ height?: 'sm' | 'md' | 'lg';
4
+ }
5
+ export default function HeroSection({ section, settings, children, }: SectionProps<HeroSettings>): import("react").JSX.Element;
6
+ export {};
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Port of sections/hero.liquid — scroll-snap slideshow. The Liquid
4
+ * theme drives this with the <hero-slideshow> web component
5
+ * (src/entries/hero.ts); here the same behavior lives in React.
6
+ * CSS: app/styles/components/sections-hero.css
7
+ */
8
+ import { useEffect, useRef, useState } from 'react';
9
+ import { t } from '../engine/translate';
10
+ import { Icon } from '../components/Icon';
11
+ export default function HeroSection({ section, settings, children, }) {
12
+ const { height = 'md' } = settings;
13
+ const slideCount = section.block_order?.length ?? 0;
14
+ const trackRef = useRef(null);
15
+ const [current, setCurrent] = useState(0);
16
+ useEffect(() => {
17
+ const track = trackRef.current;
18
+ if (!track || slideCount <= 1)
19
+ return;
20
+ const onScroll = () => {
21
+ const index = Math.round(track.scrollLeft / track.clientWidth);
22
+ setCurrent(Math.max(0, Math.min(slideCount - 1, index)));
23
+ };
24
+ track.addEventListener('scroll', onScroll, { passive: true });
25
+ return () => track.removeEventListener('scroll', onScroll);
26
+ }, [slideCount]);
27
+ const goTo = (index) => {
28
+ const track = trackRef.current;
29
+ if (!track)
30
+ return;
31
+ const clamped = ((index % slideCount) + slideCount) % slideCount;
32
+ track.scrollTo({ left: clamped * track.clientWidth, behavior: 'smooth' });
33
+ };
34
+ return (_jsxs("section", { className: `hero hero--${height} full-width`, children: [_jsx("div", { className: "hero__track", ref: trackRef, children: children }), slideCount > 1 && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "hero__arrow hero__arrow--prev", onClick: () => goTo(current - 1), "aria-label": t('general.previous'), children: _jsx(Icon, { name: "icon-chevron-left" }) }), _jsx("button", { type: "button", className: "hero__arrow hero__arrow--next", onClick: () => goTo(current + 1), "aria-label": t('general.next'), children: _jsx(Icon, { name: "icon-chevron-right" }) }), _jsx("div", { className: "hero__dots", children: Array.from({ length: slideCount }, (_, i) => (_jsx("button", { type: "button", className: "hero__dot", onClick: () => goTo(i), "aria-current": i === current || undefined, "aria-label": t('general.go_to_slide', { number: i + 1 }) }, i))) })] }))] }));
35
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Port of sections/image-with-text.liquid — media column + block stack.
3
+ * CSS: app/styles/components/sections-image-with-text.css
4
+ */
5
+ import type { SectionProps } from '../engine/types';
6
+ interface ImageWithTextSettings {
7
+ image?: string;
8
+ image_position?: 'left' | 'right';
9
+ color_scheme?: string;
10
+ section_spacing?: 'none' | 'sm' | 'md' | 'lg';
11
+ }
12
+ export default function ImageWithTextSection({ settings, children, }: SectionProps<ImageWithTextSettings>): import("react").JSX.Element;
13
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { imageSrcSet, imageUrl, PlaceholderSvg, settingImageUrl, } from '../engine/images';
3
+ export default function ImageWithTextSection({ settings, children, }) {
4
+ const { image_position: imagePosition = 'left', color_scheme: colorScheme = 'scheme-1', section_spacing: sectionSpacing = 'lg', } = settings;
5
+ const image = settingImageUrl(settings.image);
6
+ return (_jsx("div", { className: `image-with-text section color-${colorScheme} full-width`, style: {
7
+ '--section-spacing': `var(--space-section-${sectionSpacing})`,
8
+ }, children: _jsxs("div", { className: `image-with-text__inner image-with-text--media-${imagePosition}`, children: [_jsx("div", { className: "image-with-text__media", children: image ? (_jsx("img", { className: "image-with-text__image", src: imageUrl(image, 1500), srcSet: imageSrcSet(image, [400, 700, 1000, 1500]), sizes: "(min-width: 48rem) 50vw, 100vw", loading: "lazy", alt: "" })) : (_jsx(PlaceholderSvg, { className: "image-with-text__image image-with-text__placeholder" })) }), _jsx("div", { className: "image-with-text__content stack", children: children })] }) }));
9
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Port of sections/multicolumn.liquid — optional heading + responsive
3
+ * column grid of theme blocks (image, quote, text …) rendered via
4
+ * `children`. Defines no section-local blocks.
5
+ * CSS: app/styles/components/sections-multicolumn.css
6
+ */
7
+ import type { SectionProps } from '../engine/types';
8
+ interface MulticolumnSettings {
9
+ heading?: string;
10
+ columns?: number;
11
+ color_scheme?: string;
12
+ section_spacing?: 'none' | 'sm' | 'md' | 'lg';
13
+ }
14
+ export default function MulticolumnSection({ settings, children, }: SectionProps<MulticolumnSettings>): import("react").JSX.Element;
15
+ export {};
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export default function MulticolumnSection({ settings, children, }) {
3
+ const { heading = '', columns = 3, color_scheme: colorScheme = 'scheme-1', section_spacing: sectionSpacing = 'lg', } = settings;
4
+ return (_jsx("div", { className: `multicolumn section color-${colorScheme} full-width`, style: {
5
+ '--section-spacing': `var(--space-section-${sectionSpacing})`,
6
+ '--columns': columns,
7
+ }, children: _jsxs("div", { className: "multicolumn__inner stack", style: { '--stack-gap': 'var(--space-xl)' }, children: [heading ? _jsx("h2", { className: "multicolumn__heading", children: heading }) : null, _jsx("div", { className: "multicolumn__grid", children: children })] }) }));
8
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Port of sections/page.liquid. CSS: app/styles/components/sections-page.css
3
+ *
4
+ * Data: useResource('page') — the scaffold PAGE query shape
5
+ * ({handle, id, title, body}).
6
+ */
7
+ import type { SectionProps } from '../engine/types';
8
+ interface PageSettings {
9
+ color_scheme?: string;
10
+ section_spacing?: string;
11
+ }
12
+ export default function PageSection({ settings }: SectionProps<PageSettings>): import("react").JSX.Element | null;
13
+ export {};
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useResource } from '../engine/context';
3
+ export default function PageSection({ settings }) {
4
+ const { color_scheme: colorScheme = 'scheme-1', section_spacing: spacing = 'md' } = settings;
5
+ const page = useResource('page');
6
+ if (!page)
7
+ return null;
8
+ return (_jsx("div", { className: `page section color-${colorScheme} full-width`, style: {
9
+ '--section-spacing': `var(--space-section-${spacing})`,
10
+ }, children: _jsxs("div", { className: "page__inner stack", style: { '--stack-gap': 'var(--space-lg)' }, children: [_jsx("h1", { className: "page__title", children: page.title }), _jsx("div", { className: "prose", dangerouslySetInnerHTML: { __html: page.body ?? '' } })] }) }));
11
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Port of sections/password.liquid.
3
+ * CSS: app/styles/components/sections-password.css
4
+ *
5
+ * `{% form 'storefront_password' %}` posts to the Online Store password
6
+ * endpoint, which has no Hydrogen equivalent — the markup renders
7
+ * faithfully but submission is a no-op. TODO: wire to a route action if
8
+ * a storefront-password flow is ever needed.
9
+ */
10
+ import type { SectionProps } from '../engine/types';
11
+ interface PasswordSettings {
12
+ color_scheme?: string;
13
+ }
14
+ export default function PasswordSection({ settings, }: SectionProps<PasswordSettings>): import("react").JSX.Element;
15
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useThemeGlobals } from '../engine/context';
3
+ import { t } from '../engine/translate';
4
+ export default function PasswordSection({ settings, }) {
5
+ const { color_scheme: colorScheme = 'scheme-3' } = settings;
6
+ const globals = useThemeGlobals();
7
+ const shop = globals.shop ?? globals.header?.shop;
8
+ return (_jsx("div", { className: `password-page section color-${colorScheme} full-width`, children: _jsxs("div", { className: "password-page__card stack", children: [_jsx("p", { className: "password-page__shop", children: shop?.name ?? '' }), _jsx("h1", { className: "password-page__title", children: t('password.title') }), shop?.passwordMessage ? (_jsx("p", { className: "password-page__message", children: shop.passwordMessage })) : null, _jsxs("form", { className: "password-page__form stack", method: "post", onSubmit: (event) => event.preventDefault(), children: [_jsx("label", { className: "visually-hidden", htmlFor: "PasswordInput", children: t('password.password') }), _jsxs("div", { className: "password-page__row", children: [_jsx("input", { id: "PasswordInput", type: "password", name: "password", placeholder: t('password.password'), autoComplete: "current-password" }), _jsx("button", { type: "submit", className: "button", children: t('password.enter') })] })] })] }) }));
9
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Port of sections/rich-text.liquid — narrow centered/left block stack.
3
+ * CSS: app/styles/components/sections-rich-text.css
4
+ */
5
+ import type { SectionProps } from '../engine/types';
6
+ interface RichTextSettings {
7
+ alignment?: 'left' | 'center';
8
+ color_scheme?: string;
9
+ section_spacing?: 'none' | 'sm' | 'md' | 'lg';
10
+ }
11
+ export default function RichTextSection({ settings, children, }: SectionProps<RichTextSettings>): import("react").JSX.Element;
12
+ export {};
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export default function RichTextSection({ settings, children, }) {
3
+ const { alignment = 'center', color_scheme: colorScheme = 'scheme-1', section_spacing: sectionSpacing = 'lg', } = settings;
4
+ return (_jsx("div", { className: `rich-text section color-${colorScheme} full-width`, style: {
5
+ '--section-spacing': `var(--space-section-${sectionSpacing})`,
6
+ }, children: _jsx("div", { className: `rich-text__inner rich-text--${alignment} stack`, children: children }) }));
7
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Port of sections/stories.liquid — horizontally scrolling story strip.
3
+ * Story items are section-local blocks (app/blocks/story.tsx) rendered
4
+ * via `children`. Pure JSON-settings section: story blocks only carry
5
+ * image/label/link (no collection references), so no server loader.
6
+ * CSS: app/styles/components/sections-stories.css
7
+ */
8
+ import type { SectionProps } from '../engine/types';
9
+ interface StoriesSettings {
10
+ shape?: 'stories--circle' | 'stories--square';
11
+ rows?: string | number;
12
+ desktop_size?: number;
13
+ mobile_size?: number;
14
+ color_scheme?: string;
15
+ section_spacing?: 'none' | 'sm' | 'md' | 'lg';
16
+ }
17
+ export default function StoriesSection({ settings, children, }: SectionProps<StoriesSettings>): import("react").JSX.Element;
18
+ export {};
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export default function StoriesSection({ settings, children, }) {
3
+ const { shape = 'stories--circle', rows = '1', desktop_size: desktopSize = 96, mobile_size: mobileSize = 72, color_scheme: colorScheme = 'scheme-1', section_spacing: sectionSpacing = 'md', } = settings;
4
+ return (_jsx("div", { className: `stories ${shape} color-${colorScheme} section full-width`, style: {
5
+ '--section-spacing': `var(--space-section-${sectionSpacing})`,
6
+ '--story-size-desktop': `${desktopSize}px`,
7
+ '--story-size-mobile': `${mobileSize}px`,
8
+ '--story-rows': `${rows}`,
9
+ }, children: _jsx("div", { className: "stories__inner", children: _jsx("div", { className: "stories__list", role: "list", children: children }) }) }));
10
+ }
@@ -0,0 +1,13 @@
1
+ import type { SectionProps } from '../engine/types';
2
+ import { type ThemeVideo } from '../components/VideoModal';
3
+ interface VideoBubbleSettings {
4
+ preview_video?: ThemeVideo;
5
+ video?: ThemeVideo;
6
+ image?: string;
7
+ product?: unknown;
8
+ shape?: 'portrait' | 'circle';
9
+ position?: 'left' | 'right';
10
+ size?: number;
11
+ }
12
+ export default function VideoBubbleSection({ id, settings, }: SectionProps<VideoBubbleSettings>): import("react").JSX.Element | null;
13
+ export {};
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * Port of sections/video-bubble.liquid — floating video bubble: a small
4
+ * muted preview loop pinned to a bottom corner that expands into the
5
+ * shared video modal (main video + optional product). Dismissible per
6
+ * browsing session. Behavior from the <video-bubble> island in
7
+ * src/entries/video.ts (viewport playback, session dismissal) plus the
8
+ * Liquid pre-paint script that keeps a dismissed bubble from flashing.
9
+ * CSS: app/styles/components/sections-video-bubble.css
10
+ */
11
+ import { useEffect, useState } from 'react';
12
+ import { t } from '../engine/translate';
13
+ import { Icon } from '../components/Icon';
14
+ import { imageUrl, PlaceholderSvg, settingImageUrl } from '../engine/images';
15
+ import { mp4Sources, resolveVideoProduct, useViewportPlayback, VideoModal, videoPreviewImage, } from '../components/VideoModal';
16
+ export default function VideoBubbleSection({ id, settings, }) {
17
+ const { shape = 'portrait', position = 'right', size = 120 } = settings;
18
+ const preview = settings.preview_video;
19
+ const mainVideo = settings.video ?? preview;
20
+ const poster = settingImageUrl(settings.image) ??
21
+ videoPreviewImage(preview) ??
22
+ videoPreviewImage(mainVideo);
23
+ // The bubble renders tiny — the smallest rendition is always enough
24
+ const previewMp4 = mp4Sources(preview)[0];
25
+ const product = resolveVideoProduct(settings.product);
26
+ const dismissKey = `zalify:video-bubble:${id}`;
27
+ const [dismissed, setDismissed] = useState(false);
28
+ const [open, setOpen] = useState(false);
29
+ const previewRef = useViewportPlayback();
30
+ // A bubble dismissed this session stays gone (≙ connectedCallback).
31
+ useEffect(() => {
32
+ try {
33
+ if (sessionStorage.getItem(dismissKey))
34
+ setDismissed(true);
35
+ }
36
+ catch {
37
+ // Storage unavailable: leave the bubble visible
38
+ }
39
+ }, [dismissKey]);
40
+ if (dismissed)
41
+ return null;
42
+ const dismiss = () => {
43
+ setDismissed(true);
44
+ try {
45
+ sessionStorage.setItem(dismissKey, '1');
46
+ }
47
+ catch {
48
+ // Storage can be unavailable (private mode); hiding is enough
49
+ }
50
+ };
51
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: `video-bubble video-bubble--${position} video-bubble--${shape}`, style: { '--bubble-size': `${size}px` }, "data-dismiss-key": id, children: [_jsx("button", { type: "button", className: "video-bubble__dismiss", onClick: dismiss, "aria-label": t('general.close'), children: _jsx(Icon, { name: "icon-close" }) }), _jsxs("button", { type: "button", className: "video-bubble__trigger", onClick: () => setOpen(true), "aria-label": t('general.play_video'), children: [previewMp4 ? (_jsx("video", { ref: previewRef, className: "video-bubble__media", autoPlay: true, loop: true, muted: true, playsInline: true, preload: "metadata", "data-video-preview": "", poster: poster ? imageUrl(poster, 400) : undefined, children: _jsx("source", { src: previewMp4.url, type: previewMp4.mime_type }) })) : poster ? (_jsx("img", { className: "video-bubble__media", src: imageUrl(poster, 400), loading: "lazy", alt: "" })) : (_jsx(PlaceholderSvg, { className: "video-bubble__media video-bubble__media--placeholder" })), _jsx("span", { className: "video-bubble__play", children: _jsx(Icon, { name: "icon-play" }) })] }), _jsx(VideoModal, { open: open, onClose: () => setOpen(false), video: mainVideo, poster: poster, product: product })] }), _jsx("script", { dangerouslySetInnerHTML: {
52
+ __html: `(function(){try{if(sessionStorage.getItem(${JSON.stringify(dismissKey)})){document.currentScript.previousElementSibling.hidden=true;}}catch(e){}})();`,
53
+ } })] }));
54
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Port of sections/video-carousel.liquid — a scroll-snap strip of
3
+ * _video-card blocks. The carousel itself is pure CSS (scroll-snap);
4
+ * the <video-carousel> island in src/entries/video.ts only wired
5
+ * preview playback and dialog pausing, which now live inside each
6
+ * card / the shared VideoModal. (The shopify:block:select editor
7
+ * scroll has no mirror equivalent.)
8
+ * CSS: app/styles/components/sections-video-carousel.css
9
+ */
10
+ import type { SectionProps } from '../engine/types';
11
+ interface VideoCarouselSettings {
12
+ heading?: string;
13
+ desktop_size?: number;
14
+ mobile_size?: number;
15
+ color_scheme?: string;
16
+ section_spacing?: 'none' | 'sm' | 'md' | 'lg';
17
+ }
18
+ export default function VideoCarouselSection({ settings, children, }: SectionProps<VideoCarouselSettings>): import("react").JSX.Element;
19
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export default function VideoCarouselSection({ settings, children, }) {
3
+ const { heading = 'Shop the look', desktop_size: desktopSize = 280, mobile_size: mobileSize = 200, color_scheme: colorScheme = 'scheme-1', section_spacing: sectionSpacing = 'md', } = settings;
4
+ return (_jsx("div", { className: `video-carousel section color-${colorScheme} full-width`, style: {
5
+ '--section-spacing': `var(--space-section-${sectionSpacing})`,
6
+ '--video-card-size-desktop': `${desktopSize}px`,
7
+ '--video-card-size-mobile': `${mobileSize}px`,
8
+ }, children: _jsxs("div", { className: "video-carousel__inner stack", style: { '--stack-gap': 'var(--space-xl)' }, children: [heading ? _jsx("h2", { children: heading }) : null, _jsx("div", { className: "video-carousel__list", role: "list", children: children })] }) }));
9
+ }
@@ -0,0 +1,7 @@
1
+ import type { SectionLoaderArgs } from './engine/types';
2
+ export { builtinSectionLoaders } from './registries.server';
3
+ type LoaderContext = Omit<SectionLoaderArgs, 'settings' | 'section' | 'sectionId'>;
4
+ /** Run all section loaders for a page template. */
5
+ export declare function loadSectionData(templateName: string, context: LoaderContext): Promise<Record<string, unknown>>;
6
+ /** Run all section loaders for a section group (header/footer/overlay). */
7
+ export declare function loadGroupSectionData(groupName: string, context: LoaderContext): Promise<Record<string, unknown>>;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Server side of the section engine (import from '@zalify/storefront-kit/react/server').
3
+ * Route loaders run the registered per-section-type loaders for a
4
+ * template and pass the result to <ThemeTemplate sectionData={…}/>.
5
+ */
6
+ import { getSectionGroup, getTemplate, getThemeStore } from './engine/store';
7
+ export { builtinSectionLoaders } from './registries.server';
8
+ async function runLoaders(template, context) {
9
+ const loaders = getThemeStore().sectionLoaders ?? {};
10
+ const entries = await Promise.all(template.order.map(async (sectionId) => {
11
+ const section = template.sections[sectionId];
12
+ if (!section || section.disabled)
13
+ return null;
14
+ const loader = loaders[section.type];
15
+ if (!loader)
16
+ return null;
17
+ try {
18
+ const data = await loader({
19
+ ...context,
20
+ settings: section.settings ?? {},
21
+ section,
22
+ sectionId,
23
+ });
24
+ return [sectionId, data];
25
+ }
26
+ catch (error) {
27
+ // A failing section must not take down the page.
28
+ // eslint-disable-next-line no-console
29
+ console.error(`[theme] loader for section "${sectionId}" failed`, error);
30
+ return null;
31
+ }
32
+ }));
33
+ return Object.fromEntries(entries.filter(Boolean));
34
+ }
35
+ /** Run all section loaders for a page template. */
36
+ export async function loadSectionData(templateName, context) {
37
+ return runLoaders(getTemplate(templateName), context);
38
+ }
39
+ /** Run all section loaders for a section group (header/footer/overlay). */
40
+ export async function loadGroupSectionData(groupName, context) {
41
+ return runLoaders(getSectionGroup(groupName), context);
42
+ }