@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,190 @@
1
+ /**
2
+ * The rendering mirror of Shopify's section/blocks runtime:
3
+ *
4
+ * - <ThemeTemplate name="index" …/> ≙ {{ content_for_layout }} for a JSON template
5
+ * - <SectionGroup name="header-group"/> ≙ {% sections 'header-group' %}
6
+ * - Child blocks arrive as `children` ≙ {% content_for 'blocks' %}
7
+ *
8
+ * Sections are wrapped in `.shopify-section` divs and blocks in
9
+ * `.shopify-block` divs, matching the wrappers Shopify emits — the
10
+ * extracted component CSS relies on them.
11
+ *
12
+ * Every wrapper also carries a `data-z-path` attribute (the wire form
13
+ * of a @zalify/storefront-kit/schemas ThemeJsonPath) identifying the node in
14
+ * the theme JSON. The canvas-editor bridge hit-tests against it; it is
15
+ * emitted unconditionally so server and client markup stay identical.
16
+ */
17
+ import {formatThemePath, DATA_PATH_ATTR} from '../../schemas/index.ts';
18
+ import {getSectionGroup, getTemplate, getThemeStore} from './store';
19
+ import {SectionProvider, TemplateProvider} from './context';
20
+ import type {BlockData, SectionData, TemplateData} from './types';
21
+
22
+ /** The scope half of a ThemeJsonPath, threaded through rendering. */
23
+ export interface RenderScope {
24
+ kind: 'template' | 'group';
25
+ name: string;
26
+ }
27
+
28
+ const warned = new Set<string>();
29
+ function warnMissing(kind: 'section' | 'block', type: string) {
30
+ const key = `${kind}:${type}`;
31
+ if (warned.has(key)) return;
32
+ warned.add(key);
33
+ // eslint-disable-next-line no-console
34
+ console.warn(`[theme] No ${kind} component registered for type "${type}"`);
35
+ }
36
+
37
+ function pathAttr(
38
+ scope: RenderScope | undefined,
39
+ sectionKey: string,
40
+ blockKeys: string[],
41
+ ): Record<string, string> {
42
+ if (!scope) return {};
43
+ return {
44
+ [DATA_PATH_ATTR]: formatThemePath({scope, sectionKey, blockKeys}),
45
+ };
46
+ }
47
+
48
+ function RenderBlocks({
49
+ container,
50
+ sectionId,
51
+ scope,
52
+ parentBlockKeys = [],
53
+ }: {
54
+ container: {blocks?: Record<string, BlockData>; block_order?: string[]};
55
+ sectionId: string;
56
+ scope?: RenderScope;
57
+ parentBlockKeys?: string[];
58
+ }) {
59
+ const {blocks: blockRegistry} = getThemeStore();
60
+ const order = container.block_order ?? [];
61
+ if (!order.length || !container.blocks) return null;
62
+ const visible = order.filter((key) => {
63
+ const block = container.blocks![key];
64
+ return block && !block.disabled;
65
+ });
66
+ return (
67
+ <>
68
+ {visible.map((key, index) => {
69
+ const block = container.blocks![key];
70
+ const Component = blockRegistry[block.type];
71
+ if (!Component) {
72
+ warnMissing('block', block.type);
73
+ return null;
74
+ }
75
+ const blockKeys = [...parentBlockKeys, key];
76
+ return (
77
+ <div
78
+ key={key}
79
+ className="shopify-block"
80
+ data-block-type={block.type}
81
+ {...pathAttr(scope, sectionId, blockKeys)}
82
+ >
83
+ <Component
84
+ id={key}
85
+ block={block}
86
+ settings={block.settings ?? {}}
87
+ index={index}
88
+ total={visible.length}
89
+ sectionId={sectionId}
90
+ >
91
+ <RenderBlocks
92
+ container={block}
93
+ sectionId={sectionId}
94
+ scope={scope}
95
+ parentBlockKeys={blockKeys}
96
+ />
97
+ </Component>
98
+ </div>
99
+ );
100
+ })}
101
+ </>
102
+ );
103
+ }
104
+
105
+ export function RenderSection({
106
+ id,
107
+ section,
108
+ scope,
109
+ }: {
110
+ id: string;
111
+ section: SectionData;
112
+ scope?: RenderScope;
113
+ }) {
114
+ if (section.disabled) return null;
115
+ const Component = getThemeStore().sections[section.type];
116
+ if (!Component) {
117
+ warnMissing('section', section.type);
118
+ return null;
119
+ }
120
+ return (
121
+ <div
122
+ id={`shopify-section-${id}`}
123
+ className={`shopify-section shopify-section--${section.type}`}
124
+ {...pathAttr(scope, id, [])}
125
+ >
126
+ <SectionProvider id={id}>
127
+ <Component id={id} section={section} settings={section.settings ?? {}}>
128
+ <RenderBlocks container={section} sectionId={id} scope={scope} />
129
+ </Component>
130
+ </SectionProvider>
131
+ </div>
132
+ );
133
+ }
134
+
135
+ export function RenderSections({
136
+ template,
137
+ scope,
138
+ }: {
139
+ template: TemplateData;
140
+ scope?: RenderScope;
141
+ }) {
142
+ return (
143
+ <>
144
+ {template.order.map((key) => {
145
+ const section = template.sections[key];
146
+ if (!section) return null;
147
+ return (
148
+ <RenderSection key={key} id={key} section={section} scope={scope} />
149
+ );
150
+ })}
151
+ </>
152
+ );
153
+ }
154
+
155
+ /**
156
+ * Render a page template by name with its route resources and
157
+ * per-section loader data (from loadSectionData in the server module).
158
+ */
159
+ export function ThemeTemplate({
160
+ name,
161
+ resources = {},
162
+ sectionData = {},
163
+ }: {
164
+ name: string;
165
+ resources?: Record<string, unknown>;
166
+ sectionData?: Record<string, unknown>;
167
+ }) {
168
+ const template = getTemplate(name);
169
+ return (
170
+ <TemplateProvider value={{name, kind: 'template', resources, sectionData}}>
171
+ <RenderSections template={template} scope={{kind: 'template', name}} />
172
+ </TemplateProvider>
173
+ );
174
+ }
175
+
176
+ /** Mirror of `{% sections 'header-group' %}` in layout/theme.liquid. */
177
+ export function SectionGroup({
178
+ name,
179
+ sectionData = {},
180
+ }: {
181
+ name: string;
182
+ sectionData?: Record<string, unknown>;
183
+ }) {
184
+ const group = getSectionGroup(name);
185
+ return (
186
+ <TemplateProvider value={{name, kind: 'group', resources: {}, sectionData}}>
187
+ <RenderSections template={group} scope={{kind: 'group', name}} />
188
+ </TemplateProvider>
189
+ );
190
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Resolves global theme settings the same way Shopify does: schema
3
+ * defaults from config/settings_schema.json, overlaid with the current
4
+ * preset (or flat values) in config/settings_data.json — both provided
5
+ * at installTheme() time.
6
+ */
7
+ import {getThemeStore} from './store';
8
+ import type {ColorScheme, ThemeSettings} from './types';
9
+
10
+ interface Resolved {
11
+ settings: ThemeSettings;
12
+ schemes: ColorScheme[];
13
+ }
14
+
15
+ let cache: Resolved | null = null;
16
+
17
+ function resolve(): Resolved {
18
+ if (cache) return cache;
19
+ const store = getThemeStore();
20
+ const {settingsSchema, settingsData} = store.schema;
21
+
22
+ const defaults: Record<string, unknown> = {};
23
+ for (const group of settingsSchema) {
24
+ for (const setting of group.settings ?? []) {
25
+ const id = setting.id as string | undefined;
26
+ if (!id || setting.type === 'color_scheme_group') continue;
27
+ if ('default' in setting) defaults[id] = setting.default;
28
+ }
29
+ }
30
+
31
+ const current =
32
+ typeof settingsData.current === 'string'
33
+ ? (settingsData.presets?.[settingsData.current] ?? {})
34
+ : (settingsData.current ?? {});
35
+
36
+ const settings = {
37
+ ...defaults,
38
+ ...current,
39
+ ...store.settingsOverride,
40
+ } as ThemeSettings;
41
+
42
+ const rawSchemes = settings.color_schemes as
43
+ | Record<string, {settings: ColorScheme['settings']}>
44
+ | undefined;
45
+ let schemes: ColorScheme[];
46
+ if (rawSchemes) {
47
+ schemes = Object.entries(rawSchemes).map(([id, value]) => ({
48
+ id,
49
+ settings: value.settings,
50
+ }));
51
+ } else {
52
+ // Fall back to the schema definition defaults as a single scheme.
53
+ const group = settingsSchema
54
+ .flatMap((g) => g.settings ?? [])
55
+ .find((s) => s.type === 'color_scheme_group');
56
+ const definition = (group?.definition ?? []) as Array<{
57
+ id: string;
58
+ default?: string;
59
+ }>;
60
+ const schemeSettings = Object.fromEntries(
61
+ definition.map((d) => [d.id, d.default ?? '#000000']),
62
+ ) as unknown as ColorScheme['settings'];
63
+ schemes = [{id: 'scheme-1', settings: schemeSettings}];
64
+ }
65
+
66
+ cache = {settings, schemes};
67
+ return cache;
68
+ }
69
+
70
+ /**
71
+ * Global theme settings. A lazy proxy so call sites can keep reading
72
+ * `themeSettings.card_density` while resolution waits for installTheme().
73
+ */
74
+ export const themeSettings: ThemeSettings = new Proxy({} as ThemeSettings, {
75
+ get: (_target, key) => resolve().settings[key as keyof ThemeSettings],
76
+ has: (_target, key) => key in resolve().settings,
77
+ ownKeys: () => Reflect.ownKeys(resolve().settings),
78
+ getOwnPropertyDescriptor: (_target, key) =>
79
+ Object.getOwnPropertyDescriptor(resolve().settings, key),
80
+ });
81
+
82
+ export function colorSchemes(): ColorScheme[] {
83
+ return resolve().schemes;
84
+ }
85
+
86
+ export function getColorScheme(id: string | undefined): ColorScheme {
87
+ const schemes = resolve().schemes;
88
+ return schemes.find((s) => s.id === id) ?? schemes[0];
89
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The engine's data store. Apps install the theme schema (the JSON
3
+ * copied from apps/liquid by `pnpm sync`) plus their component
4
+ * registries once at startup:
5
+ *
6
+ * // app/lib/theme-setup.ts — imported for side effects by root
7
+ * installTheme({schema, sections, blocks, sectionLoaders});
8
+ *
9
+ * No import.meta.glob in here — data loading is the app's business
10
+ * (Vite apps glob, other bundlers can use generated modules), which
11
+ * keeps this package bundler-agnostic.
12
+ */
13
+ import type {
14
+ BlockComponent,
15
+ SectionComponent,
16
+ SectionGroupData,
17
+ SectionLoader,
18
+ TemplateData,
19
+ } from './types';
20
+
21
+ export interface ThemeSchema {
22
+ /** templates/*.json keyed by name ("index", "product"…). */
23
+ templates: Record<string, TemplateData>;
24
+ /** templates/customers/*.json keyed by name ("login"…). */
25
+ customerTemplates?: Record<string, TemplateData>;
26
+ /** sections/*-group.json keyed by file name ("header-group"…). */
27
+ sectionGroups: Record<string, SectionGroupData>;
28
+ /** config/settings_schema.json. */
29
+ settingsSchema: Array<{name: string; settings?: Array<Record<string, unknown>>}>;
30
+ /** config/settings_data.json. */
31
+ settingsData: {
32
+ current: string | Record<string, unknown>;
33
+ presets?: Record<string, Record<string, unknown>>;
34
+ };
35
+ /** locales/*.json keyed by file name ("en.default", "en.default.schema"). */
36
+ locales: Record<string, Record<string, unknown>>;
37
+ }
38
+
39
+ export interface InstallThemeOptions {
40
+ schema: ThemeSchema;
41
+ /** Full section registry (spread builtinSections, then app extras). */
42
+ sections: Record<string, SectionComponent>;
43
+ /** Full block registry (spread builtinBlocks, then app extras). */
44
+ blocks: Record<string, BlockComponent>;
45
+ /** Server loaders keyed by section type (spread builtinSectionLoaders…). */
46
+ sectionLoaders?: Record<string, SectionLoader>;
47
+ /**
48
+ * App-level overrides applied on top of the resolved theme settings
49
+ * (schema defaults + current preset). The synced schema stays the
50
+ * shared source of truth; this is the seam for one app to diverge
51
+ * deliberately (e.g. a different Google font in one storefront).
52
+ */
53
+ settingsOverride?: Record<string, unknown>;
54
+ }
55
+
56
+ interface ThemeStore extends InstallThemeOptions {}
57
+
58
+ let store: ThemeStore | null = null;
59
+
60
+ export function installTheme(options: InstallThemeOptions): void {
61
+ store = options;
62
+ }
63
+
64
+ export function getThemeStore(): ThemeStore {
65
+ if (!store) {
66
+ throw new Error(
67
+ '[storefront-kit] installTheme() has not been called. Import your app\'s ' +
68
+ 'theme-setup module (which calls installTheme) before rendering.',
69
+ );
70
+ }
71
+ return store;
72
+ }
73
+
74
+ /** Look up a page template ("index", "product", "customers/login"…). */
75
+ export function getTemplate(name: string): TemplateData {
76
+ const {schema} = getThemeStore();
77
+ const template = name.startsWith('customers/')
78
+ ? schema.customerTemplates?.[name.slice('customers/'.length)]
79
+ : schema.templates[name];
80
+ if (!template) throw new Error(`Unknown theme template: ${name}`);
81
+ return template;
82
+ }
83
+
84
+ /** Look up a section group ("header-group", "footer-group", "overlay-group"). */
85
+ export function getSectionGroup(name: string): SectionGroupData {
86
+ const group = getThemeStore().schema.sectionGroups[name];
87
+ if (!group) throw new Error(`Unknown section group: ${name}`);
88
+ return group;
89
+ }
90
+
91
+ /**
92
+ * Parse theme JSON that may carry Shopify's leading block comment
93
+ * (templates/*.json are written by the theme editor with a header).
94
+ * Exposed for apps that load the schema from raw strings.
95
+ */
96
+ export function parseThemeJson<T>(raw: string, name = 'theme JSON'): T {
97
+ const stripped = raw.replace(/^\s*\/\*[\s\S]*?\*\//, '').trim();
98
+ try {
99
+ return JSON.parse(stripped) as T;
100
+ } catch (error) {
101
+ throw new Error(`Failed to parse ${name}: ${String(error)}`);
102
+ }
103
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Mirror of Liquid's `| t` filter over the theme's locale files
3
+ * (locales/en.default.json, provided at installTheme() time). Keys
4
+ * prefixed with "t:" resolve against the schema locale file
5
+ * (en.default.schema.json), matching the theme editor.
6
+ */
7
+ import {getThemeStore} from './store';
8
+
9
+ type LocaleTree = Record<string, unknown>;
10
+
11
+ function lookup(tree: LocaleTree, key: string): string | undefined {
12
+ let node: unknown = tree;
13
+ for (const part of key.split('.')) {
14
+ if (node == null || typeof node !== 'object') return undefined;
15
+ node = (node as LocaleTree)[part];
16
+ }
17
+ return typeof node === 'string' ? node : undefined;
18
+ }
19
+
20
+ /**
21
+ * Translate a locale key with `{{ var }}` interpolation.
22
+ *
23
+ * t('general.go_to_slide', {number: 2}) // "Go to slide 2"
24
+ * t('t:general.hero') // schema label
25
+ */
26
+ export function t(
27
+ key: string,
28
+ vars?: Record<string, string | number>,
29
+ ): string {
30
+ const {locales} = getThemeStore().schema;
31
+ const isSchemaKey = key.startsWith('t:');
32
+ const cleanKey = isSchemaKey ? key.slice(2) : key;
33
+ const tree = (locales[isSchemaKey ? 'en.default.schema' : 'en.default'] ??
34
+ {}) as LocaleTree;
35
+ const raw = lookup(tree, cleanKey) ?? cleanKey.split('.').pop() ?? cleanKey;
36
+ if (!vars) return raw;
37
+ return raw.replace(/\{\{\s*(\w+)\s*\}\}/g, (match, name: string) =>
38
+ name in vars ? String(vars[name]) : match,
39
+ );
40
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Data shapes mirrored from the Zalify Liquid theme's JSON schema
3
+ * (templates/*.json, sections/*-group.json, config/settings_data.json).
4
+ * Those files are copied verbatim from the theme and are the source of
5
+ * truth for page structure; these types describe them.
6
+ */
7
+ import type {ComponentType, ReactNode} from 'react';
8
+
9
+ export type SettingsBag = Record<string, unknown>;
10
+
11
+ export interface BlockData {
12
+ type: string;
13
+ settings?: SettingsBag;
14
+ blocks?: Record<string, BlockData>;
15
+ block_order?: string[];
16
+ disabled?: boolean;
17
+ }
18
+
19
+ export interface SectionData {
20
+ type: string;
21
+ settings?: SettingsBag;
22
+ blocks?: Record<string, BlockData>;
23
+ block_order?: string[];
24
+ disabled?: boolean;
25
+ custom_css?: string[];
26
+ }
27
+
28
+ export interface TemplateData {
29
+ layout?: string | false;
30
+ wrapper?: string;
31
+ sections: Record<string, SectionData>;
32
+ order: string[];
33
+ }
34
+
35
+ export interface SectionGroupData extends TemplateData {
36
+ type: string;
37
+ name: string;
38
+ }
39
+
40
+ /** One color scheme from the color_scheme_group setting. */
41
+ export interface ColorScheme {
42
+ id: string;
43
+ settings: {
44
+ background: string;
45
+ text: string;
46
+ accent: string;
47
+ button: string;
48
+ button_label: string;
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Global theme settings: schema defaults from config/settings_schema.json
54
+ * overlaid with the current preset in config/settings_data.json.
55
+ * Individual ids are looked up loosely; well-known ones are typed below.
56
+ */
57
+ export interface ThemeSettings extends SettingsBag {
58
+ type_body_font?: string;
59
+ type_body_google?: string;
60
+ type_heading_font?: string;
61
+ type_heading_google?: string;
62
+ type_mono_google?: string;
63
+ max_page_width?: string;
64
+ min_page_margin?: number;
65
+ button_corner_radius?: number;
66
+ input_corner_radius?: number;
67
+ card_corner_radius?: number;
68
+ card_swatch_style?: 'swatch' | 'variant_image' | 'none';
69
+ card_density?: 'comfortable' | 'compact';
70
+ card_show_quick_add?: boolean;
71
+ card_show_secondary_image?: boolean;
72
+ card_show_rating?: boolean;
73
+ card_badge_position?: 'corner' | 'below';
74
+ card_badge_background?: string;
75
+ card_badge_text?: string;
76
+ card_badge_sale_background?: string;
77
+ card_badge_sale_text?: string;
78
+ badge_style_1_labels?: string;
79
+ badge_style_1_background?: string;
80
+ badge_style_1_text?: string;
81
+ badge_style_2_labels?: string;
82
+ badge_style_2_background?: string;
83
+ badge_style_2_text?: string;
84
+ favicon?: string;
85
+ social_instagram?: string;
86
+ social_tiktok?: string;
87
+ social_x?: string;
88
+ social_facebook?: string;
89
+ social_youtube?: string;
90
+ social_pinterest?: string;
91
+ }
92
+
93
+ /** Props every ported section component (app/sections/*.tsx) receives. */
94
+ export interface SectionProps<S extends object = SettingsBag> {
95
+ /** Section key inside its template, e.g. "hero" or "main". */
96
+ id: string;
97
+ section: SectionData;
98
+ /** section.settings (defaults are applied by the component itself). */
99
+ settings: S;
100
+ /** Pre-rendered child blocks — the mirror of `{% content_for 'blocks' %}`. */
101
+ children?: ReactNode;
102
+ }
103
+
104
+ /** Props every ported block component (app/blocks/*.tsx) receives. */
105
+ export interface BlockProps<S extends object = SettingsBag> {
106
+ id: string;
107
+ block: BlockData;
108
+ settings: S;
109
+ /** Position among siblings — mirror of forloop/find_index tricks. */
110
+ index: number;
111
+ total: number;
112
+ /** Key of the section this block ultimately lives in. */
113
+ sectionId: string;
114
+ /** Pre-rendered nested blocks. */
115
+ children?: ReactNode;
116
+ }
117
+
118
+ export type SectionComponent = ComponentType<SectionProps>;
119
+ export type BlockComponent = ComponentType<BlockProps>;
120
+
121
+ /**
122
+ * Optional per-section server loader (app/sections/<type>.server.ts).
123
+ * Runs from route loaders for every instance of the section in the
124
+ * page's template; its result is delivered to the component via
125
+ * useSectionData().
126
+ */
127
+ export interface SectionLoaderArgs {
128
+ settings: SettingsBag;
129
+ section: SectionData;
130
+ sectionId: string;
131
+ /* Storefront client from Hydrogen context (typed loosely to keep the
132
+ theme engine decoupled from generated types). */
133
+ storefront: {
134
+ query: (query: string, options?: Record<string, unknown>) => Promise<any>;
135
+ CacheLong?: () => unknown;
136
+ CacheShort?: () => unknown;
137
+ };
138
+ params: Record<string, string | undefined>;
139
+ request: Request;
140
+ }
141
+
142
+ export type SectionLoader = (args: SectionLoaderArgs) => Promise<unknown>;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @zalify/storefront-kit/react — the Zalify theme engine and shared React
3
+ * components, host-framework-agnostic. An app:
4
+ *
5
+ * 1. installTheme({schema, sections, blocks, sectionLoaders}) at
6
+ * startup, merging builtinSections/builtinBlocks with its own
7
+ * app-local components;
8
+ * 2. wraps the tree in <AdapterProvider adapter={…}> injecting its
9
+ * router/cart primitives;
10
+ * 3. renders pages with <ThemeTemplate>/<SectionGroup> and
11
+ * <CssVariables/> in <head>.
12
+ *
13
+ * Server loaders: import {loadSectionData} from '@zalify/storefront-kit/react/server'.
14
+ */
15
+
16
+ // Engine
17
+ export * from './engine/types';
18
+ export {
19
+ installTheme,
20
+ getTemplate,
21
+ getSectionGroup,
22
+ parseThemeJson,
23
+ } from './engine/store';
24
+ export type {ThemeSchema, InstallThemeOptions} from './engine/store';
25
+ export {themeSettings, colorSchemes, getColorScheme} from './engine/settings';
26
+ export {t} from './engine/translate';
27
+ export {
28
+ ThemeTemplate,
29
+ SectionGroup,
30
+ RenderSection,
31
+ RenderSections,
32
+ } from './engine/render';
33
+ export {
34
+ ThemeDataProvider,
35
+ TemplateProvider,
36
+ useThemeGlobals,
37
+ useResource,
38
+ useSectionData,
39
+ useTemplateScope,
40
+ } from './engine/context';
41
+ export {CssVariables} from './engine/CssVariables';
42
+ export {parseFontHandle, googleFontsHref} from './engine/fonts';
43
+ export {
44
+ settingImageUrl,
45
+ imageUrl,
46
+ imageSrcSet,
47
+ PlaceholderSvg,
48
+ } from './engine/images';
49
+
50
+ // Adapter seam
51
+ export {AdapterProvider, useAdapter} from './adapter';
52
+ export type {
53
+ ThemeAdapter,
54
+ AdapterLinkProps,
55
+ CartAddFormProps,
56
+ CartLineInput,
57
+ } from './adapter';
58
+
59
+ // Shared components
60
+ export {Icon} from './components/Icon';
61
+ export type {IconName} from './components/Icon';
62
+ export {PaymentIcons} from './components/PaymentIcons';
63
+ export type {PaymentType} from './components/PaymentIcons';
64
+ export {Price} from './components/Price';
65
+ export {Rating} from './components/Rating';
66
+ export {ProductBadges, hasProductBadges} from './components/ProductBadges';
67
+ export {swatchStyle} from './components/SwatchStyle';
68
+ export {ProductCard, PRODUCT_CARD_FRAGMENT} from './components/ProductCard';
69
+ export * from './components/VideoModal';
70
+ export {
71
+ Facets,
72
+ COLLECTION_SORT_OPTIONS,
73
+ SEARCH_SORT_OPTIONS,
74
+ } from './components/Facets';
75
+
76
+ // Built-in registries (spread into installTheme with app extras).
77
+ // Server-only loaders live in '@zalify/storefront-kit/react/server'.
78
+ export {builtinSections, builtinBlocks} from './registries';