@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.
- package/LICENSE.md +49 -0
- package/README.md +63 -0
- package/dist/commerce/countdown.d.ts +18 -0
- package/dist/commerce/countdown.js +28 -0
- package/dist/commerce/events.d.ts +29 -0
- package/dist/commerce/events.js +14 -0
- package/dist/commerce/facets.d.ts +27 -0
- package/dist/commerce/facets.js +71 -0
- package/dist/commerce/index.d.ts +11 -0
- package/dist/commerce/index.js +11 -0
- package/dist/commerce/money.d.ts +51 -0
- package/dist/commerce/money.js +83 -0
- package/dist/commerce/product.d.ts +102 -0
- package/dist/commerce/product.js +93 -0
- package/dist/editor/frame.d.ts +57 -0
- package/dist/editor/frame.js +241 -0
- package/dist/editor/host.d.ts +31 -0
- package/dist/editor/host.js +67 -0
- package/dist/editor/index.d.ts +9 -0
- package/dist/editor/index.js +9 -0
- package/dist/react/adapter.d.ts +49 -0
- package/dist/react/adapter.js +23 -0
- package/dist/react/blocks/_box.d.ts +10 -0
- package/dist/react/blocks/_box.js +17 -0
- package/dist/react/blocks/_slide.d.ts +24 -0
- package/dist/react/blocks/_slide.js +21 -0
- package/dist/react/blocks/_video-card.d.ts +11 -0
- package/dist/react/blocks/_video-card.js +39 -0
- package/dist/react/blocks/accordion.d.ts +12 -0
- package/dist/react/blocks/accordion.js +5 -0
- package/dist/react/blocks/button.d.ts +10 -0
- package/dist/react/blocks/button.js +15 -0
- package/dist/react/blocks/countdown.d.ts +9 -0
- package/dist/react/blocks/countdown.js +64 -0
- package/dist/react/blocks/custom-liquid.d.ts +11 -0
- package/dist/react/blocks/custom-liquid.js +4 -0
- package/dist/react/blocks/group.d.ts +9 -0
- package/dist/react/blocks/group.js +8 -0
- package/dist/react/blocks/image.d.ts +8 -0
- package/dist/react/blocks/image.js +7 -0
- package/dist/react/blocks/message.d.ts +14 -0
- package/dist/react/blocks/message.js +5 -0
- package/dist/react/blocks/quote.d.ts +9 -0
- package/dist/react/blocks/quote.js +5 -0
- package/dist/react/blocks/spacer.d.ts +8 -0
- package/dist/react/blocks/spacer.js +5 -0
- package/dist/react/blocks/story.d.ts +17 -0
- package/dist/react/blocks/story.js +7 -0
- package/dist/react/blocks/text.d.ts +9 -0
- package/dist/react/blocks/text.js +5 -0
- package/dist/react/blocks/video.d.ts +17 -0
- package/dist/react/blocks/video.js +56 -0
- package/dist/react/components/Facets.d.ts +21 -0
- package/dist/react/components/Facets.js +220 -0
- package/dist/react/components/Icon.d.ts +12 -0
- package/dist/react/components/Icon.js +66 -0
- package/dist/react/components/PaymentIcons.d.ts +5 -0
- package/dist/react/components/PaymentIcons.generated.d.ts +22 -0
- package/dist/react/components/PaymentIcons.generated.js +21 -0
- package/dist/react/components/PaymentIcons.js +18 -0
- package/dist/react/components/Price.d.ts +24 -0
- package/dist/react/components/Price.js +9 -0
- package/dist/react/components/ProductBadges.d.ts +24 -0
- package/dist/react/components/ProductBadges.js +57 -0
- package/dist/react/components/ProductCard.d.ts +17 -0
- package/dist/react/components/ProductCard.js +202 -0
- package/dist/react/components/Rating.d.ts +11 -0
- package/dist/react/components/Rating.js +35 -0
- package/dist/react/components/SwatchStyle.d.ts +23 -0
- package/dist/react/components/SwatchStyle.js +22 -0
- package/dist/react/components/VideoModal.d.ts +55 -0
- package/dist/react/components/VideoModal.js +102 -0
- package/dist/react/engine/CssVariables.d.ts +1 -0
- package/dist/react/engine/CssVariables.js +105 -0
- package/dist/react/engine/context.d.ts +43 -0
- package/dist/react/engine/context.js +45 -0
- package/dist/react/engine/fonts.d.ts +15 -0
- package/dist/react/engine/fonts.js +33 -0
- package/dist/react/engine/images.d.ts +24 -0
- package/dist/react/engine/images.js +40 -0
- package/dist/react/engine/render.d.ts +29 -0
- package/dist/react/engine/render.js +87 -0
- package/dist/react/engine/settings.d.ts +8 -0
- package/dist/react/engine/settings.js +68 -0
- package/dist/react/engine/store.d.ts +64 -0
- package/dist/react/engine/store.js +42 -0
- package/dist/react/engine/translate.d.ts +7 -0
- package/dist/react/engine/translate.js +33 -0
- package/dist/react/engine/types.d.ts +128 -0
- package/dist/react/engine/types.js +1 -0
- package/dist/react/index.d.ts +38 -0
- package/dist/react/index.js +39 -0
- package/dist/react/registries.d.ts +10 -0
- package/dist/react/registries.js +68 -0
- package/dist/react/registries.server.d.ts +2 -0
- package/dist/react/registries.server.js +98 -0
- package/dist/react/sections/404.d.ts +10 -0
- package/dist/react/sections/404.js +10 -0
- package/dist/react/sections/announcement-bar.d.ts +14 -0
- package/dist/react/sections/announcement-bar.js +84 -0
- package/dist/react/sections/article.d.ts +17 -0
- package/dist/react/sections/article.js +31 -0
- package/dist/react/sections/contact-form.d.ts +16 -0
- package/dist/react/sections/contact-form.js +8 -0
- package/dist/react/sections/custom-liquid.d.ts +14 -0
- package/dist/react/sections/custom-liquid.js +7 -0
- package/dist/react/sections/custom-section.d.ts +14 -0
- package/dist/react/sections/custom-section.js +9 -0
- package/dist/react/sections/featured-collection.d.ts +10 -0
- package/dist/react/sections/featured-collection.js +23 -0
- package/dist/react/sections/hero.d.ts +6 -0
- package/dist/react/sections/hero.js +35 -0
- package/dist/react/sections/image-with-text.d.ts +13 -0
- package/dist/react/sections/image-with-text.js +9 -0
- package/dist/react/sections/multicolumn.d.ts +15 -0
- package/dist/react/sections/multicolumn.js +8 -0
- package/dist/react/sections/page.d.ts +13 -0
- package/dist/react/sections/page.js +11 -0
- package/dist/react/sections/password.d.ts +15 -0
- package/dist/react/sections/password.js +9 -0
- package/dist/react/sections/rich-text.d.ts +12 -0
- package/dist/react/sections/rich-text.js +7 -0
- package/dist/react/sections/stories.d.ts +18 -0
- package/dist/react/sections/stories.js +10 -0
- package/dist/react/sections/video-bubble.d.ts +13 -0
- package/dist/react/sections/video-bubble.js +54 -0
- package/dist/react/sections/video-carousel.d.ts +19 -0
- package/dist/react/sections/video-carousel.js +9 -0
- package/dist/react/server.d.ts +7 -0
- package/dist/react/server.js +42 -0
- package/dist/schemas/bridge.d.ts +98 -0
- package/dist/schemas/bridge.js +50 -0
- package/dist/schemas/data.d.ts +54 -0
- package/dist/schemas/data.js +22 -0
- package/dist/schemas/index.d.ts +14 -0
- package/dist/schemas/index.js +14 -0
- package/dist/schemas/manifest.d.ts +63 -0
- package/dist/schemas/manifest.js +32 -0
- package/dist/schemas/schema.d.ts +159 -0
- package/dist/schemas/schema.js +129 -0
- package/dist/schemas/validate.d.ts +22 -0
- package/dist/schemas/validate.js +199 -0
- package/package.json +84 -0
- package/src/commerce/countdown.ts +38 -0
- package/src/commerce/events.ts +43 -0
- package/src/commerce/facets.ts +88 -0
- package/src/commerce/index.ts +11 -0
- package/src/commerce/money.ts +120 -0
- package/src/commerce/product.ts +177 -0
- package/src/editor/frame.ts +305 -0
- package/src/editor/host.ts +101 -0
- package/src/editor/index.ts +9 -0
- package/src/react/adapter.tsx +83 -0
- package/src/react/blocks/_box.tsx +37 -0
- package/src/react/blocks/_slide.tsx +96 -0
- package/src/react/blocks/_video-card.tsx +131 -0
- package/src/react/blocks/accordion.tsx +33 -0
- package/src/react/blocks/button.tsx +36 -0
- package/src/react/blocks/countdown.tsx +105 -0
- package/src/react/blocks/custom-liquid.tsx +21 -0
- package/src/react/blocks/group.tsx +32 -0
- package/src/react/blocks/image.tsx +29 -0
- package/src/react/blocks/message.tsx +28 -0
- package/src/react/blocks/quote.tsx +24 -0
- package/src/react/blocks/spacer.tsx +17 -0
- package/src/react/blocks/story.tsx +40 -0
- package/src/react/blocks/text.tsx +24 -0
- package/src/react/blocks/video.tsx +132 -0
- package/src/react/components/Facets.tsx +424 -0
- package/src/react/components/Icon.tsx +96 -0
- package/src/react/components/PaymentIcons.generated.ts +24 -0
- package/src/react/components/PaymentIcons.tsx +27 -0
- package/src/react/components/Price.tsx +72 -0
- package/src/react/components/ProductBadges.tsx +95 -0
- package/src/react/components/ProductCard.tsx +417 -0
- package/src/react/components/Rating.tsx +59 -0
- package/src/react/components/SwatchStyle.tsx +50 -0
- package/src/react/components/VideoModal.tsx +236 -0
- package/src/react/engine/CssVariables.tsx +142 -0
- package/src/react/engine/context.tsx +108 -0
- package/src/react/engine/fonts.ts +42 -0
- package/src/react/engine/images.tsx +58 -0
- package/src/react/engine/render.tsx +190 -0
- package/src/react/engine/settings.ts +89 -0
- package/src/react/engine/store.ts +103 -0
- package/src/react/engine/translate.ts +40 -0
- package/src/react/engine/types.ts +142 -0
- package/src/react/index.ts +78 -0
- package/src/react/registries.server.ts +110 -0
- package/src/react/registries.ts +80 -0
- package/src/react/sections/404.tsx +37 -0
- package/src/react/sections/announcement-bar.tsx +170 -0
- package/src/react/sections/article.tsx +97 -0
- package/src/react/sections/contact-form.tsx +104 -0
- package/src/react/sections/custom-liquid.tsx +39 -0
- package/src/react/sections/custom-section.tsx +44 -0
- package/src/react/sections/featured-collection.tsx +89 -0
- package/src/react/sections/hero.tsx +84 -0
- package/src/react/sections/image-with-text.tsx +61 -0
- package/src/react/sections/multicolumn.tsx +46 -0
- package/src/react/sections/page.tsx +43 -0
- package/src/react/sections/password.tsx +60 -0
- package/src/react/sections/rich-text.tsx +37 -0
- package/src/react/sections/stories.tsx +51 -0
- package/src/react/sections/video-bubble.tsx +145 -0
- package/src/react/sections/video-carousel.tsx +57 -0
- package/src/react/server.ts +60 -0
- package/src/schemas/bridge.ts +143 -0
- package/src/schemas/data.ts +72 -0
- package/src/schemas/index.ts +14 -0
- package/src/schemas/manifest.ts +85 -0
- package/src/schemas/schema.ts +287 -0
- package/src/schemas/validate.ts +302 -0
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
let cache = null;
|
|
9
|
+
function resolve() {
|
|
10
|
+
if (cache)
|
|
11
|
+
return cache;
|
|
12
|
+
const store = getThemeStore();
|
|
13
|
+
const { settingsSchema, settingsData } = store.schema;
|
|
14
|
+
const defaults = {};
|
|
15
|
+
for (const group of settingsSchema) {
|
|
16
|
+
for (const setting of group.settings ?? []) {
|
|
17
|
+
const id = setting.id;
|
|
18
|
+
if (!id || setting.type === 'color_scheme_group')
|
|
19
|
+
continue;
|
|
20
|
+
if ('default' in setting)
|
|
21
|
+
defaults[id] = setting.default;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const current = typeof settingsData.current === 'string'
|
|
25
|
+
? (settingsData.presets?.[settingsData.current] ?? {})
|
|
26
|
+
: (settingsData.current ?? {});
|
|
27
|
+
const settings = {
|
|
28
|
+
...defaults,
|
|
29
|
+
...current,
|
|
30
|
+
...store.settingsOverride,
|
|
31
|
+
};
|
|
32
|
+
const rawSchemes = settings.color_schemes;
|
|
33
|
+
let schemes;
|
|
34
|
+
if (rawSchemes) {
|
|
35
|
+
schemes = Object.entries(rawSchemes).map(([id, value]) => ({
|
|
36
|
+
id,
|
|
37
|
+
settings: value.settings,
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Fall back to the schema definition defaults as a single scheme.
|
|
42
|
+
const group = settingsSchema
|
|
43
|
+
.flatMap((g) => g.settings ?? [])
|
|
44
|
+
.find((s) => s.type === 'color_scheme_group');
|
|
45
|
+
const definition = (group?.definition ?? []);
|
|
46
|
+
const schemeSettings = Object.fromEntries(definition.map((d) => [d.id, d.default ?? '#000000']));
|
|
47
|
+
schemes = [{ id: 'scheme-1', settings: schemeSettings }];
|
|
48
|
+
}
|
|
49
|
+
cache = { settings, schemes };
|
|
50
|
+
return cache;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Global theme settings. A lazy proxy so call sites can keep reading
|
|
54
|
+
* `themeSettings.card_density` while resolution waits for installTheme().
|
|
55
|
+
*/
|
|
56
|
+
export const themeSettings = new Proxy({}, {
|
|
57
|
+
get: (_target, key) => resolve().settings[key],
|
|
58
|
+
has: (_target, key) => key in resolve().settings,
|
|
59
|
+
ownKeys: () => Reflect.ownKeys(resolve().settings),
|
|
60
|
+
getOwnPropertyDescriptor: (_target, key) => Object.getOwnPropertyDescriptor(resolve().settings, key),
|
|
61
|
+
});
|
|
62
|
+
export function colorSchemes() {
|
|
63
|
+
return resolve().schemes;
|
|
64
|
+
}
|
|
65
|
+
export function getColorScheme(id) {
|
|
66
|
+
const schemes = resolve().schemes;
|
|
67
|
+
return schemes.find((s) => s.id === id) ?? schemes[0];
|
|
68
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
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 { BlockComponent, SectionComponent, SectionGroupData, SectionLoader, TemplateData } from './types';
|
|
14
|
+
export interface ThemeSchema {
|
|
15
|
+
/** templates/*.json keyed by name ("index", "product"…). */
|
|
16
|
+
templates: Record<string, TemplateData>;
|
|
17
|
+
/** templates/customers/*.json keyed by name ("login"…). */
|
|
18
|
+
customerTemplates?: Record<string, TemplateData>;
|
|
19
|
+
/** sections/*-group.json keyed by file name ("header-group"…). */
|
|
20
|
+
sectionGroups: Record<string, SectionGroupData>;
|
|
21
|
+
/** config/settings_schema.json. */
|
|
22
|
+
settingsSchema: Array<{
|
|
23
|
+
name: string;
|
|
24
|
+
settings?: Array<Record<string, unknown>>;
|
|
25
|
+
}>;
|
|
26
|
+
/** config/settings_data.json. */
|
|
27
|
+
settingsData: {
|
|
28
|
+
current: string | Record<string, unknown>;
|
|
29
|
+
presets?: Record<string, Record<string, unknown>>;
|
|
30
|
+
};
|
|
31
|
+
/** locales/*.json keyed by file name ("en.default", "en.default.schema"). */
|
|
32
|
+
locales: Record<string, Record<string, unknown>>;
|
|
33
|
+
}
|
|
34
|
+
export interface InstallThemeOptions {
|
|
35
|
+
schema: ThemeSchema;
|
|
36
|
+
/** Full section registry (spread builtinSections, then app extras). */
|
|
37
|
+
sections: Record<string, SectionComponent>;
|
|
38
|
+
/** Full block registry (spread builtinBlocks, then app extras). */
|
|
39
|
+
blocks: Record<string, BlockComponent>;
|
|
40
|
+
/** Server loaders keyed by section type (spread builtinSectionLoaders…). */
|
|
41
|
+
sectionLoaders?: Record<string, SectionLoader>;
|
|
42
|
+
/**
|
|
43
|
+
* App-level overrides applied on top of the resolved theme settings
|
|
44
|
+
* (schema defaults + current preset). The synced schema stays the
|
|
45
|
+
* shared source of truth; this is the seam for one app to diverge
|
|
46
|
+
* deliberately (e.g. a different Google font in one storefront).
|
|
47
|
+
*/
|
|
48
|
+
settingsOverride?: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
interface ThemeStore extends InstallThemeOptions {
|
|
51
|
+
}
|
|
52
|
+
export declare function installTheme(options: InstallThemeOptions): void;
|
|
53
|
+
export declare function getThemeStore(): ThemeStore;
|
|
54
|
+
/** Look up a page template ("index", "product", "customers/login"…). */
|
|
55
|
+
export declare function getTemplate(name: string): TemplateData;
|
|
56
|
+
/** Look up a section group ("header-group", "footer-group", "overlay-group"). */
|
|
57
|
+
export declare function getSectionGroup(name: string): SectionGroupData;
|
|
58
|
+
/**
|
|
59
|
+
* Parse theme JSON that may carry Shopify's leading block comment
|
|
60
|
+
* (templates/*.json are written by the theme editor with a header).
|
|
61
|
+
* Exposed for apps that load the schema from raw strings.
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseThemeJson<T>(raw: string, name?: string): T;
|
|
64
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
let store = null;
|
|
2
|
+
export function installTheme(options) {
|
|
3
|
+
store = options;
|
|
4
|
+
}
|
|
5
|
+
export function getThemeStore() {
|
|
6
|
+
if (!store) {
|
|
7
|
+
throw new Error('[storefront-kit] installTheme() has not been called. Import your app\'s ' +
|
|
8
|
+
'theme-setup module (which calls installTheme) before rendering.');
|
|
9
|
+
}
|
|
10
|
+
return store;
|
|
11
|
+
}
|
|
12
|
+
/** Look up a page template ("index", "product", "customers/login"…). */
|
|
13
|
+
export function getTemplate(name) {
|
|
14
|
+
const { schema } = getThemeStore();
|
|
15
|
+
const template = name.startsWith('customers/')
|
|
16
|
+
? schema.customerTemplates?.[name.slice('customers/'.length)]
|
|
17
|
+
: schema.templates[name];
|
|
18
|
+
if (!template)
|
|
19
|
+
throw new Error(`Unknown theme template: ${name}`);
|
|
20
|
+
return template;
|
|
21
|
+
}
|
|
22
|
+
/** Look up a section group ("header-group", "footer-group", "overlay-group"). */
|
|
23
|
+
export function getSectionGroup(name) {
|
|
24
|
+
const group = getThemeStore().schema.sectionGroups[name];
|
|
25
|
+
if (!group)
|
|
26
|
+
throw new Error(`Unknown section group: ${name}`);
|
|
27
|
+
return group;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse theme JSON that may carry Shopify's leading block comment
|
|
31
|
+
* (templates/*.json are written by the theme editor with a header).
|
|
32
|
+
* Exposed for apps that load the schema from raw strings.
|
|
33
|
+
*/
|
|
34
|
+
export function parseThemeJson(raw, name = 'theme JSON') {
|
|
35
|
+
const stripped = raw.replace(/^\s*\/\*[\s\S]*?\*\//, '').trim();
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(stripped);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
throw new Error(`Failed to parse ${name}: ${String(error)}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
function lookup(tree, key) {
|
|
9
|
+
let node = tree;
|
|
10
|
+
for (const part of key.split('.')) {
|
|
11
|
+
if (node == null || typeof node !== 'object')
|
|
12
|
+
return undefined;
|
|
13
|
+
node = node[part];
|
|
14
|
+
}
|
|
15
|
+
return typeof node === 'string' ? node : undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Translate a locale key with `{{ var }}` interpolation.
|
|
19
|
+
*
|
|
20
|
+
* t('general.go_to_slide', {number: 2}) // "Go to slide 2"
|
|
21
|
+
* t('t:general.hero') // schema label
|
|
22
|
+
*/
|
|
23
|
+
export function t(key, vars) {
|
|
24
|
+
const { locales } = getThemeStore().schema;
|
|
25
|
+
const isSchemaKey = key.startsWith('t:');
|
|
26
|
+
const cleanKey = isSchemaKey ? key.slice(2) : key;
|
|
27
|
+
const tree = (locales[isSchemaKey ? 'en.default.schema' : 'en.default'] ??
|
|
28
|
+
{});
|
|
29
|
+
const raw = lookup(tree, cleanKey) ?? cleanKey.split('.').pop() ?? cleanKey;
|
|
30
|
+
if (!vars)
|
|
31
|
+
return raw;
|
|
32
|
+
return raw.replace(/\{\{\s*(\w+)\s*\}\}/g, (match, name) => name in vars ? String(vars[name]) : match);
|
|
33
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
export type SettingsBag = Record<string, unknown>;
|
|
9
|
+
export interface BlockData {
|
|
10
|
+
type: string;
|
|
11
|
+
settings?: SettingsBag;
|
|
12
|
+
blocks?: Record<string, BlockData>;
|
|
13
|
+
block_order?: string[];
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface SectionData {
|
|
17
|
+
type: string;
|
|
18
|
+
settings?: SettingsBag;
|
|
19
|
+
blocks?: Record<string, BlockData>;
|
|
20
|
+
block_order?: string[];
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
custom_css?: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface TemplateData {
|
|
25
|
+
layout?: string | false;
|
|
26
|
+
wrapper?: string;
|
|
27
|
+
sections: Record<string, SectionData>;
|
|
28
|
+
order: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface SectionGroupData extends TemplateData {
|
|
31
|
+
type: string;
|
|
32
|
+
name: string;
|
|
33
|
+
}
|
|
34
|
+
/** One color scheme from the color_scheme_group setting. */
|
|
35
|
+
export interface ColorScheme {
|
|
36
|
+
id: string;
|
|
37
|
+
settings: {
|
|
38
|
+
background: string;
|
|
39
|
+
text: string;
|
|
40
|
+
accent: string;
|
|
41
|
+
button: string;
|
|
42
|
+
button_label: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Global theme settings: schema defaults from config/settings_schema.json
|
|
47
|
+
* overlaid with the current preset in config/settings_data.json.
|
|
48
|
+
* Individual ids are looked up loosely; well-known ones are typed below.
|
|
49
|
+
*/
|
|
50
|
+
export interface ThemeSettings extends SettingsBag {
|
|
51
|
+
type_body_font?: string;
|
|
52
|
+
type_body_google?: string;
|
|
53
|
+
type_heading_font?: string;
|
|
54
|
+
type_heading_google?: string;
|
|
55
|
+
type_mono_google?: string;
|
|
56
|
+
max_page_width?: string;
|
|
57
|
+
min_page_margin?: number;
|
|
58
|
+
button_corner_radius?: number;
|
|
59
|
+
input_corner_radius?: number;
|
|
60
|
+
card_corner_radius?: number;
|
|
61
|
+
card_swatch_style?: 'swatch' | 'variant_image' | 'none';
|
|
62
|
+
card_density?: 'comfortable' | 'compact';
|
|
63
|
+
card_show_quick_add?: boolean;
|
|
64
|
+
card_show_secondary_image?: boolean;
|
|
65
|
+
card_show_rating?: boolean;
|
|
66
|
+
card_badge_position?: 'corner' | 'below';
|
|
67
|
+
card_badge_background?: string;
|
|
68
|
+
card_badge_text?: string;
|
|
69
|
+
card_badge_sale_background?: string;
|
|
70
|
+
card_badge_sale_text?: string;
|
|
71
|
+
badge_style_1_labels?: string;
|
|
72
|
+
badge_style_1_background?: string;
|
|
73
|
+
badge_style_1_text?: string;
|
|
74
|
+
badge_style_2_labels?: string;
|
|
75
|
+
badge_style_2_background?: string;
|
|
76
|
+
badge_style_2_text?: string;
|
|
77
|
+
favicon?: string;
|
|
78
|
+
social_instagram?: string;
|
|
79
|
+
social_tiktok?: string;
|
|
80
|
+
social_x?: string;
|
|
81
|
+
social_facebook?: string;
|
|
82
|
+
social_youtube?: string;
|
|
83
|
+
social_pinterest?: string;
|
|
84
|
+
}
|
|
85
|
+
/** Props every ported section component (app/sections/*.tsx) receives. */
|
|
86
|
+
export interface SectionProps<S extends object = SettingsBag> {
|
|
87
|
+
/** Section key inside its template, e.g. "hero" or "main". */
|
|
88
|
+
id: string;
|
|
89
|
+
section: SectionData;
|
|
90
|
+
/** section.settings (defaults are applied by the component itself). */
|
|
91
|
+
settings: S;
|
|
92
|
+
/** Pre-rendered child blocks — the mirror of `{% content_for 'blocks' %}`. */
|
|
93
|
+
children?: ReactNode;
|
|
94
|
+
}
|
|
95
|
+
/** Props every ported block component (app/blocks/*.tsx) receives. */
|
|
96
|
+
export interface BlockProps<S extends object = SettingsBag> {
|
|
97
|
+
id: string;
|
|
98
|
+
block: BlockData;
|
|
99
|
+
settings: S;
|
|
100
|
+
/** Position among siblings — mirror of forloop/find_index tricks. */
|
|
101
|
+
index: number;
|
|
102
|
+
total: number;
|
|
103
|
+
/** Key of the section this block ultimately lives in. */
|
|
104
|
+
sectionId: string;
|
|
105
|
+
/** Pre-rendered nested blocks. */
|
|
106
|
+
children?: ReactNode;
|
|
107
|
+
}
|
|
108
|
+
export type SectionComponent = ComponentType<SectionProps>;
|
|
109
|
+
export type BlockComponent = ComponentType<BlockProps>;
|
|
110
|
+
/**
|
|
111
|
+
* Optional per-section server loader (app/sections/<type>.server.ts).
|
|
112
|
+
* Runs from route loaders for every instance of the section in the
|
|
113
|
+
* page's template; its result is delivered to the component via
|
|
114
|
+
* useSectionData().
|
|
115
|
+
*/
|
|
116
|
+
export interface SectionLoaderArgs {
|
|
117
|
+
settings: SettingsBag;
|
|
118
|
+
section: SectionData;
|
|
119
|
+
sectionId: string;
|
|
120
|
+
storefront: {
|
|
121
|
+
query: (query: string, options?: Record<string, unknown>) => Promise<any>;
|
|
122
|
+
CacheLong?: () => unknown;
|
|
123
|
+
CacheShort?: () => unknown;
|
|
124
|
+
};
|
|
125
|
+
params: Record<string, string | undefined>;
|
|
126
|
+
request: Request;
|
|
127
|
+
}
|
|
128
|
+
export type SectionLoader = (args: SectionLoaderArgs) => Promise<unknown>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
export * from './engine/types';
|
|
16
|
+
export { installTheme, getTemplate, getSectionGroup, parseThemeJson, } from './engine/store';
|
|
17
|
+
export type { ThemeSchema, InstallThemeOptions } from './engine/store';
|
|
18
|
+
export { themeSettings, colorSchemes, getColorScheme } from './engine/settings';
|
|
19
|
+
export { t } from './engine/translate';
|
|
20
|
+
export { ThemeTemplate, SectionGroup, RenderSection, RenderSections, } from './engine/render';
|
|
21
|
+
export { ThemeDataProvider, TemplateProvider, useThemeGlobals, useResource, useSectionData, useTemplateScope, } from './engine/context';
|
|
22
|
+
export { CssVariables } from './engine/CssVariables';
|
|
23
|
+
export { parseFontHandle, googleFontsHref } from './engine/fonts';
|
|
24
|
+
export { settingImageUrl, imageUrl, imageSrcSet, PlaceholderSvg, } from './engine/images';
|
|
25
|
+
export { AdapterProvider, useAdapter } from './adapter';
|
|
26
|
+
export type { ThemeAdapter, AdapterLinkProps, CartAddFormProps, CartLineInput, } from './adapter';
|
|
27
|
+
export { Icon } from './components/Icon';
|
|
28
|
+
export type { IconName } from './components/Icon';
|
|
29
|
+
export { PaymentIcons } from './components/PaymentIcons';
|
|
30
|
+
export type { PaymentType } from './components/PaymentIcons';
|
|
31
|
+
export { Price } from './components/Price';
|
|
32
|
+
export { Rating } from './components/Rating';
|
|
33
|
+
export { ProductBadges, hasProductBadges } from './components/ProductBadges';
|
|
34
|
+
export { swatchStyle } from './components/SwatchStyle';
|
|
35
|
+
export { ProductCard, PRODUCT_CARD_FRAGMENT } from './components/ProductCard';
|
|
36
|
+
export * from './components/VideoModal';
|
|
37
|
+
export { Facets, COLLECTION_SORT_OPTIONS, SEARCH_SORT_OPTIONS, } from './components/Facets';
|
|
38
|
+
export { builtinSections, builtinBlocks } from './registries';
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
// Engine
|
|
16
|
+
export * from './engine/types';
|
|
17
|
+
export { installTheme, getTemplate, getSectionGroup, parseThemeJson, } from './engine/store';
|
|
18
|
+
export { themeSettings, colorSchemes, getColorScheme } from './engine/settings';
|
|
19
|
+
export { t } from './engine/translate';
|
|
20
|
+
export { ThemeTemplate, SectionGroup, RenderSection, RenderSections, } from './engine/render';
|
|
21
|
+
export { ThemeDataProvider, TemplateProvider, useThemeGlobals, useResource, useSectionData, useTemplateScope, } from './engine/context';
|
|
22
|
+
export { CssVariables } from './engine/CssVariables';
|
|
23
|
+
export { parseFontHandle, googleFontsHref } from './engine/fonts';
|
|
24
|
+
export { settingImageUrl, imageUrl, imageSrcSet, PlaceholderSvg, } from './engine/images';
|
|
25
|
+
// Adapter seam
|
|
26
|
+
export { AdapterProvider, useAdapter } from './adapter';
|
|
27
|
+
// Shared components
|
|
28
|
+
export { Icon } from './components/Icon';
|
|
29
|
+
export { PaymentIcons } from './components/PaymentIcons';
|
|
30
|
+
export { Price } from './components/Price';
|
|
31
|
+
export { Rating } from './components/Rating';
|
|
32
|
+
export { ProductBadges, hasProductBadges } from './components/ProductBadges';
|
|
33
|
+
export { swatchStyle } from './components/SwatchStyle';
|
|
34
|
+
export { ProductCard, PRODUCT_CARD_FRAGMENT } from './components/ProductCard';
|
|
35
|
+
export * from './components/VideoModal';
|
|
36
|
+
export { Facets, COLLECTION_SORT_OPTIONS, SEARCH_SORT_OPTIONS, } from './components/Facets';
|
|
37
|
+
// Built-in registries (spread into installTheme with app extras).
|
|
38
|
+
// Server-only loaders live in '@zalify/storefront-kit/react/server'.
|
|
39
|
+
export { builtinSections, builtinBlocks } from './registries';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in component registries. Keys are the theme type names — the
|
|
3
|
+
* section/block `type` values in templates/*.json, which equal the old
|
|
4
|
+
* app/sections/*.tsx and app/blocks/*.tsx file names (including leading
|
|
5
|
+
* underscores and '404'). Apps spread these into installTheme() and add
|
|
6
|
+
* their own app-local sections/blocks on top.
|
|
7
|
+
*/
|
|
8
|
+
import type { BlockComponent, SectionComponent } from './engine/types';
|
|
9
|
+
export declare const builtinSections: Record<string, SectionComponent>;
|
|
10
|
+
export declare const builtinBlocks: Record<string, BlockComponent>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Blocks
|
|
2
|
+
import text from './blocks/text';
|
|
3
|
+
import button from './blocks/button';
|
|
4
|
+
import group from './blocks/group';
|
|
5
|
+
import spacer from './blocks/spacer';
|
|
6
|
+
import image from './blocks/image';
|
|
7
|
+
import box from './blocks/_box';
|
|
8
|
+
import slide from './blocks/_slide';
|
|
9
|
+
import story from './blocks/story';
|
|
10
|
+
import message from './blocks/message';
|
|
11
|
+
import quote from './blocks/quote';
|
|
12
|
+
import accordion from './blocks/accordion';
|
|
13
|
+
import countdown from './blocks/countdown';
|
|
14
|
+
import customLiquidBlock from './blocks/custom-liquid';
|
|
15
|
+
import video from './blocks/video';
|
|
16
|
+
import videoCard from './blocks/_video-card';
|
|
17
|
+
// Sections
|
|
18
|
+
import hero from './sections/hero';
|
|
19
|
+
import stories from './sections/stories';
|
|
20
|
+
import featuredCollection from './sections/featured-collection';
|
|
21
|
+
import multicolumn from './sections/multicolumn';
|
|
22
|
+
import imageWithText from './sections/image-with-text';
|
|
23
|
+
import richText from './sections/rich-text';
|
|
24
|
+
import customSection from './sections/custom-section';
|
|
25
|
+
import customLiquidSection from './sections/custom-liquid';
|
|
26
|
+
import announcementBar from './sections/announcement-bar';
|
|
27
|
+
import notFound from './sections/404';
|
|
28
|
+
import page from './sections/page';
|
|
29
|
+
import article from './sections/article';
|
|
30
|
+
import password from './sections/password';
|
|
31
|
+
import contactForm from './sections/contact-form';
|
|
32
|
+
import videoCarousel from './sections/video-carousel';
|
|
33
|
+
import videoBubble from './sections/video-bubble';
|
|
34
|
+
export const builtinSections = {
|
|
35
|
+
hero,
|
|
36
|
+
stories,
|
|
37
|
+
'featured-collection': featuredCollection,
|
|
38
|
+
multicolumn,
|
|
39
|
+
'image-with-text': imageWithText,
|
|
40
|
+
'rich-text': richText,
|
|
41
|
+
'custom-section': customSection,
|
|
42
|
+
'custom-liquid': customLiquidSection,
|
|
43
|
+
'announcement-bar': announcementBar,
|
|
44
|
+
'404': notFound,
|
|
45
|
+
page,
|
|
46
|
+
article,
|
|
47
|
+
password,
|
|
48
|
+
'contact-form': contactForm,
|
|
49
|
+
'video-carousel': videoCarousel,
|
|
50
|
+
'video-bubble': videoBubble,
|
|
51
|
+
};
|
|
52
|
+
export const builtinBlocks = {
|
|
53
|
+
text,
|
|
54
|
+
button,
|
|
55
|
+
group,
|
|
56
|
+
spacer,
|
|
57
|
+
image,
|
|
58
|
+
_box: box,
|
|
59
|
+
_slide: slide,
|
|
60
|
+
story,
|
|
61
|
+
message,
|
|
62
|
+
quote,
|
|
63
|
+
accordion,
|
|
64
|
+
countdown,
|
|
65
|
+
'custom-liquid': customLiquidBlock,
|
|
66
|
+
video,
|
|
67
|
+
'_video-card': videoCard,
|
|
68
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in server loaders, keyed by section type (import from
|
|
3
|
+
* '@zalify/storefront-kit/react/server'). Apps spread these into installTheme()
|
|
4
|
+
* alongside their own app-local loaders.
|
|
5
|
+
*
|
|
6
|
+
* featured-collection: server loader for sections/featured-collection
|
|
7
|
+
* .liquid's data needs — the Liquid `section.settings.collection
|
|
8
|
+
* .products` lookup becomes a Storefront API query. When the collection
|
|
9
|
+
* setting is blank (fresh install), fall back to the first available
|
|
10
|
+
* collection so the section still merchandises real products; if
|
|
11
|
+
* nothing resolves the component renders its placeholder cards (the
|
|
12
|
+
* Liquid onboarding state).
|
|
13
|
+
*/
|
|
14
|
+
import { PRODUCT_CARD_FRAGMENT } from './components/ProductCard';
|
|
15
|
+
const FEATURED_COLLECTION_QUERY = `#graphql
|
|
16
|
+
query ThemeFeaturedCollection($handle: String!, $first: Int!) {
|
|
17
|
+
collection(handle: $handle) {
|
|
18
|
+
id
|
|
19
|
+
handle
|
|
20
|
+
title
|
|
21
|
+
products(first: $first) {
|
|
22
|
+
nodes {
|
|
23
|
+
...ProductCard
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
${PRODUCT_CARD_FRAGMENT}
|
|
29
|
+
`;
|
|
30
|
+
const FIRST_COLLECTIONS_QUERY = `#graphql
|
|
31
|
+
query ThemeFirstCollections($first: Int!) {
|
|
32
|
+
collections(first: 10) {
|
|
33
|
+
nodes {
|
|
34
|
+
id
|
|
35
|
+
handle
|
|
36
|
+
title
|
|
37
|
+
products(first: $first) {
|
|
38
|
+
nodes {
|
|
39
|
+
...ProductCard
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
${PRODUCT_CARD_FRAGMENT}
|
|
46
|
+
`;
|
|
47
|
+
const LATEST_PRODUCTS_QUERY = `#graphql
|
|
48
|
+
query ThemeLatestProducts($first: Int!) {
|
|
49
|
+
products(first: $first, sortKey: CREATED_AT, reverse: true) {
|
|
50
|
+
nodes {
|
|
51
|
+
...ProductCard
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
${PRODUCT_CARD_FRAGMENT}
|
|
56
|
+
`;
|
|
57
|
+
const featuredCollectionLoader = async ({ settings, storefront, }) => {
|
|
58
|
+
const first = Number(settings.products_to_show ?? 4) || 4;
|
|
59
|
+
const handle = typeof settings.collection === 'string' && settings.collection
|
|
60
|
+
? settings.collection
|
|
61
|
+
: null;
|
|
62
|
+
if (handle) {
|
|
63
|
+
const data = await storefront.query(FEATURED_COLLECTION_QUERY, {
|
|
64
|
+
variables: { handle, first },
|
|
65
|
+
cache: storefront.CacheLong?.(),
|
|
66
|
+
});
|
|
67
|
+
if (data?.collection)
|
|
68
|
+
return { collection: data.collection };
|
|
69
|
+
}
|
|
70
|
+
// Fresh-install fallback: first collection that actually has
|
|
71
|
+
// products, then the latest products as a synthetic collection —
|
|
72
|
+
// placeholders only when the store is truly empty.
|
|
73
|
+
const data = await storefront.query(FIRST_COLLECTIONS_QUERY, {
|
|
74
|
+
variables: { first },
|
|
75
|
+
cache: storefront.CacheLong?.(),
|
|
76
|
+
});
|
|
77
|
+
const nonEmpty = (data?.collections?.nodes ?? []).find((node) => node.products?.nodes?.length);
|
|
78
|
+
if (nonEmpty)
|
|
79
|
+
return { collection: nonEmpty };
|
|
80
|
+
const latest = await storefront.query(LATEST_PRODUCTS_QUERY, {
|
|
81
|
+
variables: { first },
|
|
82
|
+
cache: storefront.CacheLong?.(),
|
|
83
|
+
});
|
|
84
|
+
const products = latest?.products?.nodes ?? [];
|
|
85
|
+
if (!products.length)
|
|
86
|
+
return { collection: null };
|
|
87
|
+
return {
|
|
88
|
+
collection: {
|
|
89
|
+
id: 'latest',
|
|
90
|
+
handle: 'all',
|
|
91
|
+
title: 'Featured products',
|
|
92
|
+
products: { nodes: products },
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
export const builtinSectionLoaders = {
|
|
97
|
+
'featured-collection': featuredCollectionLoader,
|
|
98
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port of sections/404.liquid. CSS: app/styles/components/sections-404.css
|
|
3
|
+
*/
|
|
4
|
+
import type { SectionProps } from '../engine/types';
|
|
5
|
+
interface NotFoundSettings {
|
|
6
|
+
color_scheme?: string;
|
|
7
|
+
section_spacing?: string;
|
|
8
|
+
}
|
|
9
|
+
export default function NotFoundSection({ settings, }: SectionProps<NotFoundSettings>): import("react").JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { t } from '../engine/translate';
|
|
3
|
+
import { useAdapter } from '../adapter';
|
|
4
|
+
export default function NotFoundSection({ settings, }) {
|
|
5
|
+
const { Link } = useAdapter();
|
|
6
|
+
const { color_scheme: colorScheme = 'scheme-1', section_spacing: spacing = 'md' } = settings;
|
|
7
|
+
return (_jsx("div", { className: `not-found section color-${colorScheme} full-width`, style: {
|
|
8
|
+
'--section-spacing': `var(--space-section-${spacing})`,
|
|
9
|
+
}, children: _jsxs("div", { className: "not-found__inner stack", children: [_jsx("h1", { className: "not-found__title", children: t('404.title') }), _jsx("p", { children: t('404.not_found') }), _jsx(Link, { to: "/collections/all", className: "button", children: t('404.back_to_shopping') })] }) }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SectionProps } from '../engine/types';
|
|
2
|
+
declare global {
|
|
3
|
+
namespace JSX {
|
|
4
|
+
interface IntrinsicElements {
|
|
5
|
+
'announcement-bar': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
interface AnnouncementBarSettings {
|
|
10
|
+
rotation_speed?: number;
|
|
11
|
+
color_scheme?: string;
|
|
12
|
+
}
|
|
13
|
+
export default function AnnouncementBarSection({ section, settings, children, }: SectionProps<AnnouncementBarSettings>): import("react").JSX.Element;
|
|
14
|
+
export {};
|