@xosen/site-sdk 0.0.10 → 0.0.12
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/dist/composables/useDynamicLayout.d.ts +37 -0
- package/dist/composables/useLocaleSwitcher.d.ts +12 -0
- package/dist/composables/useSiteComponents.d.ts +15 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +255 -130
- package/dist/types/config.d.ts +18 -0
- package/package.json +14 -14
- package/src/composables/useDynamicLayout.ts +146 -0
- package/src/composables/useLocaleSwitcher.ts +40 -0
- package/src/composables/useSiteComponents.ts +59 -0
- package/src/index.ts +4 -0
- package/src/types/config.ts +22 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface GridBreakpoint {
|
|
2
|
+
areas?: string[];
|
|
3
|
+
columns?: string;
|
|
4
|
+
rows?: string;
|
|
5
|
+
gap?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface LayoutZone {
|
|
8
|
+
type?: 'slot';
|
|
9
|
+
component?: string;
|
|
10
|
+
class?: string;
|
|
11
|
+
container?: boolean;
|
|
12
|
+
containerStyle?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
export interface LayoutConfig {
|
|
15
|
+
grid?: Record<string, GridBreakpoint>;
|
|
16
|
+
zones: Record<string, LayoutZone>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate responsive CSS for a layout config.
|
|
20
|
+
* Returns a CSS string with media queries for each breakpoint.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildLayoutCSS(layout: LayoutConfig, selector: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the current layout config from site data, route, and preview state.
|
|
25
|
+
*
|
|
26
|
+
* Returns:
|
|
27
|
+
* - `layoutName` — current layout name (e.g. 'default', 'sidebar')
|
|
28
|
+
* - `layoutConfig` — resolved LayoutConfig with grid + zones
|
|
29
|
+
* - `layoutCSS` — generated responsive CSS string
|
|
30
|
+
* - `layoutClass` — unique CSS class for this layout instance
|
|
31
|
+
*/
|
|
32
|
+
export declare function useDynamicLayout(): {
|
|
33
|
+
layoutName: import("vue").ComputedRef<any>;
|
|
34
|
+
layoutConfig: import("vue").ComputedRef<LayoutConfig>;
|
|
35
|
+
layoutCSS: import("vue").ComputedRef<string>;
|
|
36
|
+
layoutClass: import("vue").ComputedRef<string[]>;
|
|
37
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale switcher composable.
|
|
3
|
+
* Manages locale state, available locales, and persistence.
|
|
4
|
+
*/
|
|
5
|
+
export declare function useLocaleSwitcher(): {
|
|
6
|
+
locale: import("vue").WritableComputedRef<string, string>;
|
|
7
|
+
locales: import("vue").ComputedRef<string[]>;
|
|
8
|
+
defaultLocale: import("vue").ComputedRef<string>;
|
|
9
|
+
hasMultipleLocales: import("vue").ComputedRef<boolean>;
|
|
10
|
+
switchLocale: (loc: string) => void;
|
|
11
|
+
resolveText: (text: string | Record<string, string>) => string;
|
|
12
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* Locale-aware access to site components (navigation, footer, sidebar, etc.).
|
|
4
|
+
*
|
|
5
|
+
* On initial load, reads from preloaded __SITE_DATA__.components.
|
|
6
|
+
* When locale changes, fetches updated components from the API.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useSiteComponents(): {
|
|
9
|
+
/** All components (reactive, locale-aware) */
|
|
10
|
+
components: Ref<Record<string, any>, Record<string, any>>;
|
|
11
|
+
/** Get a single component by key */
|
|
12
|
+
getComponent: <T = any>(key: string) => Ref<T | null>;
|
|
13
|
+
/** Force refetch for current locale */
|
|
14
|
+
reload: () => Promise<void>;
|
|
15
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ export { useSiteApi } from './composables/useSiteApi.js';
|
|
|
5
5
|
export { useLivePreview, setupPreviewRouter, isInPreview } from './composables/useLivePreview.js';
|
|
6
6
|
export { usePageData } from './composables/usePageData.js';
|
|
7
7
|
export type { UsePageDataReturn } from './composables/usePageData.js';
|
|
8
|
+
export { useSiteComponents } from './composables/useSiteComponents.js';
|
|
9
|
+
export { useLocaleSwitcher } from './composables/useLocaleSwitcher.js';
|
|
10
|
+
export { useDynamicLayout, buildLayoutCSS } from './composables/useDynamicLayout.js';
|
|
11
|
+
export type { LayoutConfig, LayoutZone, GridBreakpoint } from './composables/useDynamicLayout.js';
|
|
8
12
|
export type { PreviewMessage } from './composables/useLivePreview.js';
|
|
9
13
|
export { createSiteWorker } from './worker/index.js';
|
|
10
14
|
export type { WorkerEnv, WorkerConfig, PageData } from './worker/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,50 +1,51 @@
|
|
|
1
|
-
import { ref as
|
|
2
|
-
import { useI18n as
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { ref as w, onMounted as T, watch as _, computed as u, onUnmounted as N, provide as I } from "vue";
|
|
2
|
+
import { useI18n as $ } from "vue-i18n";
|
|
3
|
+
import { useRoute as P } from "vue-router";
|
|
4
|
+
function B(o, n) {
|
|
5
|
+
const t = w(null), { locale: e } = $(), a = window.__SITE_DATA__, s = a?.locale;
|
|
6
|
+
function i() {
|
|
7
|
+
return a && a[o] && e.value === s ? (t.value = a[o], !0) : !1;
|
|
7
8
|
}
|
|
8
|
-
async function
|
|
9
|
+
async function l() {
|
|
9
10
|
try {
|
|
10
|
-
const
|
|
11
|
-
|
|
11
|
+
const c = await fetch(`/api/content/${n}:${e.value}`);
|
|
12
|
+
c.ok && (t.value = await c.json());
|
|
12
13
|
} catch {
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
|
-
return
|
|
16
|
-
|
|
16
|
+
return T(() => {
|
|
17
|
+
i() || l();
|
|
17
18
|
}), _(e, () => {
|
|
18
|
-
|
|
19
|
+
l();
|
|
19
20
|
}), t;
|
|
20
21
|
}
|
|
21
|
-
function
|
|
22
|
-
const o = window.__SITE_DATA__, n =
|
|
23
|
-
function
|
|
24
|
-
const v =
|
|
22
|
+
function G() {
|
|
23
|
+
const o = window.__SITE_DATA__, n = u(() => o?.config || null), t = u(() => o?.components || {}), e = u(() => n.value?.navigation || []), a = u(() => n.value?.locales || []), s = u(() => n.value?.defaultLocale || "en"), i = u(() => n.value?.branding || {}), l = u(() => n.value?.footer || {}), c = u(() => n.value?.features || {});
|
|
24
|
+
function f(d) {
|
|
25
|
+
const v = c.value[d];
|
|
25
26
|
return typeof v == "boolean" ? v : typeof v == "object";
|
|
26
27
|
}
|
|
27
28
|
return {
|
|
28
29
|
config: n,
|
|
29
30
|
components: t,
|
|
30
31
|
navigation: e,
|
|
31
|
-
locales:
|
|
32
|
-
defaultLocale:
|
|
33
|
-
branding:
|
|
34
|
-
footer:
|
|
35
|
-
features:
|
|
36
|
-
hasFeature:
|
|
32
|
+
locales: a,
|
|
33
|
+
defaultLocale: s,
|
|
34
|
+
branding: i,
|
|
35
|
+
footer: l,
|
|
36
|
+
features: c,
|
|
37
|
+
hasFeature: f
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
|
-
function
|
|
40
|
+
function Y(o) {
|
|
40
41
|
function n(t) {
|
|
41
42
|
const e = document.documentElement;
|
|
42
43
|
if (t.colors)
|
|
43
|
-
for (const [
|
|
44
|
-
|
|
44
|
+
for (const [a, s] of Object.entries(t.colors))
|
|
45
|
+
s && e.style.setProperty(`--x-color-${x(a)}`, s);
|
|
45
46
|
t.typography?.fontFamily && e.style.setProperty("--x-font-family", t.typography.fontFamily), t.typography?.headingFont && e.style.setProperty("--x-font-heading", t.typography.headingFont), t.typography?.baseFontSize && e.style.setProperty("--x-font-size", t.typography.baseFontSize), t.shape?.borderRadius && e.style.setProperty("--x-border-radius", t.shape.borderRadius), t.shape?.maxWidth && e.style.setProperty("--x-max-width", t.shape.maxWidth);
|
|
46
47
|
}
|
|
47
|
-
|
|
48
|
+
T(() => {
|
|
48
49
|
if (o) {
|
|
49
50
|
n(o);
|
|
50
51
|
return;
|
|
@@ -57,132 +58,252 @@ function U(o) {
|
|
|
57
58
|
});
|
|
58
59
|
});
|
|
59
60
|
}
|
|
60
|
-
function
|
|
61
|
+
function x(o) {
|
|
61
62
|
return o.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
62
63
|
}
|
|
63
|
-
function
|
|
64
|
+
function X() {
|
|
64
65
|
const n = window.__SITE_DATA__?.apiBase || "";
|
|
65
|
-
async function t(
|
|
66
|
-
const
|
|
67
|
-
if (!
|
|
68
|
-
throw new Error(`API error: ${
|
|
69
|
-
return
|
|
66
|
+
async function t(s) {
|
|
67
|
+
const i = await fetch(`${n}${s}`);
|
|
68
|
+
if (!i.ok)
|
|
69
|
+
throw new Error(`API error: ${i.status}`);
|
|
70
|
+
return i.json();
|
|
70
71
|
}
|
|
71
72
|
async function e() {
|
|
72
73
|
return t("/v1/billing/tariffs");
|
|
73
74
|
}
|
|
74
|
-
async function
|
|
75
|
+
async function a() {
|
|
75
76
|
return t("/v1/billing/services");
|
|
76
77
|
}
|
|
77
78
|
return {
|
|
78
79
|
get: t,
|
|
79
80
|
getTariffs: e,
|
|
80
|
-
getProducts:
|
|
81
|
+
getProducts: a
|
|
81
82
|
};
|
|
82
83
|
}
|
|
83
|
-
function
|
|
84
|
+
function b() {
|
|
84
85
|
try {
|
|
85
86
|
return window.self !== window.top;
|
|
86
87
|
} catch {
|
|
87
88
|
return !0;
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
|
-
function
|
|
91
|
-
|
|
91
|
+
function Z(o) {
|
|
92
|
+
b() && o.beforeEach((n, t) => !t.name);
|
|
92
93
|
}
|
|
93
|
-
function
|
|
94
|
-
const o =
|
|
94
|
+
function C() {
|
|
95
|
+
const o = w(null), n = w(b());
|
|
95
96
|
function t(e) {
|
|
96
|
-
const
|
|
97
|
-
|
|
97
|
+
const a = e.data;
|
|
98
|
+
a?.type === "xosen-preview-update" && a.page && (o.value = a.page, n.value = !0);
|
|
98
99
|
}
|
|
99
|
-
return
|
|
100
|
+
return T(() => {
|
|
100
101
|
window.addEventListener("message", t);
|
|
101
|
-
}),
|
|
102
|
+
}), N(() => {
|
|
102
103
|
window.removeEventListener("message", t);
|
|
103
104
|
}), {
|
|
104
105
|
previewPage: o,
|
|
105
106
|
isPreviewMode: n
|
|
106
107
|
};
|
|
107
108
|
}
|
|
108
|
-
const
|
|
109
|
-
function
|
|
110
|
-
const { locale: n } =
|
|
111
|
-
const r = e.value ? t.value :
|
|
109
|
+
const D = /* @__PURE__ */ Symbol("pageContext");
|
|
110
|
+
function H(o) {
|
|
111
|
+
const { locale: n } = $(), { previewPage: t, isPreviewMode: e } = C(), a = window.__SITE_DATA__, s = w(null), i = w(!0), l = w(!1), c = u(() => typeof o == "string" ? o : o.value), f = u(() => `page:${c.value}`), d = u(() => {
|
|
112
|
+
const r = e.value ? t.value : s.value;
|
|
112
113
|
if (!r) return {};
|
|
113
|
-
const { blocks:
|
|
114
|
-
return
|
|
114
|
+
const { blocks: y, meta: p, slug: m, locale: z, ...L } = r;
|
|
115
|
+
return L;
|
|
115
116
|
});
|
|
116
|
-
|
|
117
|
-
const v =
|
|
118
|
-
function
|
|
119
|
-
const r =
|
|
120
|
-
return r && n.value ===
|
|
117
|
+
I(D, d);
|
|
118
|
+
const v = u(() => e.value && t.value?.slug === c.value ? t.value.blocks || [] : s.value?.blocks || []);
|
|
119
|
+
function S() {
|
|
120
|
+
const r = a?.[f.value];
|
|
121
|
+
return r && n.value === a?.locale ? (s.value = r, l.value = !1, h(), !0) : !1;
|
|
121
122
|
}
|
|
122
|
-
async function
|
|
123
|
-
|
|
123
|
+
async function g() {
|
|
124
|
+
i.value = !0, l.value = !1;
|
|
124
125
|
try {
|
|
125
|
-
const r = await fetch(`/api/content/${
|
|
126
|
-
r.ok ? (
|
|
126
|
+
const r = await fetch(`/api/content/${f.value}:${n.value}`);
|
|
127
|
+
r.ok ? (s.value = await r.json(), h()) : (s.value = null, l.value = !0);
|
|
127
128
|
} catch {
|
|
128
|
-
|
|
129
|
+
s.value = null, l.value = !0;
|
|
129
130
|
} finally {
|
|
130
|
-
|
|
131
|
+
i.value = !1;
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
134
|
function h() {
|
|
134
|
-
if (!
|
|
135
|
-
if (
|
|
136
|
-
const
|
|
137
|
-
document.title = `${
|
|
135
|
+
if (!s.value) return;
|
|
136
|
+
if (s.value.title) {
|
|
137
|
+
const y = a?.config?.branding, p = y?.siteName ? ` — ${y.siteName}` : "";
|
|
138
|
+
document.title = `${s.value.title}${p}`;
|
|
138
139
|
}
|
|
139
|
-
const r =
|
|
140
|
+
const r = s.value.meta?.description;
|
|
140
141
|
if (r) {
|
|
141
|
-
const
|
|
142
|
-
|
|
142
|
+
const y = document.querySelector('meta[name="description"]');
|
|
143
|
+
y && y.setAttribute("content", r);
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
146
|
return _(t, (r) => {
|
|
146
|
-
r?.slug ===
|
|
147
|
-
...
|
|
147
|
+
r?.slug === c.value && (i.value = !1, l.value = !1, (r.title || r.blocks) && (s.value = {
|
|
148
|
+
...s.value || {},
|
|
148
149
|
...r.title ? { title: r.title } : {},
|
|
149
150
|
...r.blocks ? { blocks: r.blocks } : {}
|
|
150
151
|
}, h()));
|
|
151
|
-
}),
|
|
152
|
-
|
|
153
|
-
}), _(n, () =>
|
|
154
|
-
|
|
152
|
+
}), T(() => {
|
|
153
|
+
S() ? i.value = !1 : g();
|
|
154
|
+
}), _(n, () => g()), _(c, () => {
|
|
155
|
+
S() ? i.value = !1 : g();
|
|
155
156
|
}), {
|
|
156
|
-
page:
|
|
157
|
+
page: s,
|
|
157
158
|
blocks: v,
|
|
158
|
-
pageContext:
|
|
159
|
-
loading:
|
|
160
|
-
notFound:
|
|
159
|
+
pageContext: d,
|
|
160
|
+
loading: i,
|
|
161
|
+
notFound: l,
|
|
161
162
|
isPreviewMode: e,
|
|
162
|
-
reload:
|
|
163
|
+
reload: g
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function Q() {
|
|
167
|
+
const { locale: o } = $(), n = window.__SITE_DATA__, t = n?.locale, e = w({ ...n?.components || {} });
|
|
168
|
+
async function a(i) {
|
|
169
|
+
if (i === t) {
|
|
170
|
+
e.value = { ...n?.components || {} };
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const l = await fetch(`/api/content/components:${i}`);
|
|
175
|
+
l.ok && (e.value = await l.json());
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
_(o, (i) => a(i)), T(() => {
|
|
180
|
+
o.value !== t && a(o.value);
|
|
181
|
+
});
|
|
182
|
+
function s(i) {
|
|
183
|
+
return u(() => e.value[i] || null);
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
/** All components (reactive, locale-aware) */
|
|
187
|
+
components: e,
|
|
188
|
+
/** Get a single component by key */
|
|
189
|
+
getComponent: s,
|
|
190
|
+
/** Force refetch for current locale */
|
|
191
|
+
reload: () => a(o.value)
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function V() {
|
|
195
|
+
const { locale: o } = $(), n = window.__SITE_DATA__, t = u(() => n?.config?.locales || []), e = u(() => n?.config?.defaultLocale || "en"), a = u(() => t.value.length > 1);
|
|
196
|
+
function s(l) {
|
|
197
|
+
o.value = l, localStorage.setItem("x-site-locale", l);
|
|
198
|
+
}
|
|
199
|
+
function i(l) {
|
|
200
|
+
return typeof l == "string" ? l : l[o.value] || l[e.value] || Object.values(l)[0] || "";
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
locale: o,
|
|
204
|
+
locales: t,
|
|
205
|
+
defaultLocale: e,
|
|
206
|
+
hasMultipleLocales: a,
|
|
207
|
+
switchLocale: s,
|
|
208
|
+
resolveText: i
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const j = {
|
|
212
|
+
sm: 600,
|
|
213
|
+
md: 960,
|
|
214
|
+
lg: 1280,
|
|
215
|
+
xl: 1920
|
|
216
|
+
}, O = {
|
|
217
|
+
grid: {
|
|
218
|
+
default: {
|
|
219
|
+
areas: ["header", "main", "footer"],
|
|
220
|
+
rows: "auto 1fr auto"
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
zones: {
|
|
224
|
+
header: { component: "nav-menu" },
|
|
225
|
+
main: { type: "slot" },
|
|
226
|
+
footer: { component: "footer" }
|
|
227
|
+
}
|
|
228
|
+
}, R = {
|
|
229
|
+
grid: {
|
|
230
|
+
default: {
|
|
231
|
+
areas: ["main"]
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
zones: {
|
|
235
|
+
main: { type: "slot" }
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
function A(o, n) {
|
|
239
|
+
const t = [];
|
|
240
|
+
if (o.areas?.length) {
|
|
241
|
+
const e = o.areas.map((a) => `"${a}"`).join(" ");
|
|
242
|
+
t.push(`grid-template-areas: ${e}`);
|
|
243
|
+
}
|
|
244
|
+
return o.columns && t.push(`grid-template-columns: ${o.columns}`), o.rows && t.push(`grid-template-rows: ${o.rows}`), o.gap && t.push(`gap: ${o.gap}`), t.length ? `${n} { ${t.join("; ")}; }` : "";
|
|
245
|
+
}
|
|
246
|
+
function F(o, n) {
|
|
247
|
+
const t = [`${n} { display: grid; min-height: 100vh; }`], e = o.grid;
|
|
248
|
+
if (!e) return t.join(`
|
|
249
|
+
`);
|
|
250
|
+
if (e.default) {
|
|
251
|
+
const a = A(e.default, n);
|
|
252
|
+
a && t.push(a);
|
|
253
|
+
}
|
|
254
|
+
for (const a of ["sm", "md", "lg", "xl"]) {
|
|
255
|
+
const s = e[a];
|
|
256
|
+
if (!s) continue;
|
|
257
|
+
const i = A(s, n);
|
|
258
|
+
i && t.push(`@media (min-width: ${j[a]}px) { ${i} }`);
|
|
259
|
+
}
|
|
260
|
+
return t.join(`
|
|
261
|
+
`);
|
|
262
|
+
}
|
|
263
|
+
function q() {
|
|
264
|
+
const o = P(), { previewPage: n, isPreviewMode: t } = C(), e = window.__SITE_DATA__, a = `dl-${Math.random().toString(36).slice(2, 8)}`, s = u(() => ["dynamic-layout", a]), i = u(() => {
|
|
265
|
+
if (o.path === "/_preview_")
|
|
266
|
+
return n.value?.layout || "landing";
|
|
267
|
+
if (t.value && n.value?.layout)
|
|
268
|
+
return n.value.layout;
|
|
269
|
+
const f = o.path === "/" ? "page:home" : `page:${o.params.slug || ""}`;
|
|
270
|
+
return e?.[f]?.layout || "default";
|
|
271
|
+
}), l = u(() => {
|
|
272
|
+
const f = e?.config?.layouts;
|
|
273
|
+
if (f) {
|
|
274
|
+
const d = f[i.value] || f.default;
|
|
275
|
+
if (d?.zones) return d;
|
|
276
|
+
}
|
|
277
|
+
return i.value === "landing" ? R : O;
|
|
278
|
+
}), c = u(() => F(l.value, `.${a}`));
|
|
279
|
+
return {
|
|
280
|
+
layoutName: i,
|
|
281
|
+
layoutConfig: l,
|
|
282
|
+
layoutCSS: c,
|
|
283
|
+
layoutClass: s
|
|
163
284
|
};
|
|
164
285
|
}
|
|
165
|
-
const
|
|
166
|
-
function
|
|
286
|
+
const M = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|webp|avif|map|json|txt|xml|webmanifest)$/;
|
|
287
|
+
function k(o, n) {
|
|
167
288
|
const e = new URL(o.url).searchParams.get("lang");
|
|
168
289
|
if (e && n.supportedLocales?.includes(e)) return e;
|
|
169
290
|
if (n.supportedLocales?.length) {
|
|
170
|
-
const
|
|
171
|
-
for (const
|
|
172
|
-
if (
|
|
291
|
+
const a = o.headers.get("Accept-Language") || "";
|
|
292
|
+
for (const s of n.supportedLocales)
|
|
293
|
+
if (a.includes(s)) return s;
|
|
173
294
|
}
|
|
174
295
|
return n.defaultLocale;
|
|
175
296
|
}
|
|
176
|
-
function
|
|
297
|
+
function U(o, n, t) {
|
|
177
298
|
if (!o.pathname.startsWith("/api/content/")) return null;
|
|
178
299
|
const e = decodeURIComponent(o.pathname.replace("/api/content/", ""));
|
|
179
300
|
return e ? (async () => {
|
|
180
|
-
let
|
|
181
|
-
if (!
|
|
182
|
-
const
|
|
183
|
-
|
|
301
|
+
let a = await n.SITE_CONTENT.get(e);
|
|
302
|
+
if (!a) {
|
|
303
|
+
const s = e.lastIndexOf(":");
|
|
304
|
+
s > 0 && e.slice(s + 1) !== t && (a = await n.SITE_CONTENT.get(e.slice(0, s + 1) + t));
|
|
184
305
|
}
|
|
185
|
-
return
|
|
306
|
+
return a ? new Response(a, {
|
|
186
307
|
headers: {
|
|
187
308
|
"Content-Type": "application/json",
|
|
188
309
|
"Cache-Control": "public, max-age=60",
|
|
@@ -191,54 +312,54 @@ function O(o, n, t) {
|
|
|
191
312
|
}) : Response.json({ error: "Not found" }, { status: 404 });
|
|
192
313
|
})() : Promise.resolve(Response.json({ error: "Key is required" }, { status: 400 }));
|
|
193
314
|
}
|
|
194
|
-
function
|
|
315
|
+
function E(o, n, t) {
|
|
195
316
|
let e = o;
|
|
196
317
|
if (n.title) {
|
|
197
|
-
const
|
|
198
|
-
e = e.replace(/<title>[^<]*<\/title>/, `<title>${n.title}${
|
|
318
|
+
const a = t ? ` — ${t}` : "";
|
|
319
|
+
e = e.replace(/<title>[^<]*<\/title>/, `<title>${n.title}${a}</title>`);
|
|
199
320
|
}
|
|
200
321
|
return n.meta?.description && (e = e.replace(/(<meta\s+name="description"\s+content=")[^"]*(")/, `$1${n.meta.description}$2`)), n.meta?.ogTitle && (e = e.replace(/(<meta\s+property="og:title"\s+content=")[^"]*(")/, `$1${n.meta.ogTitle}$2`)), n.html && (e = e.replace('<div id="app"></div>', `<div id="app">${n.html}</div>`)), e;
|
|
201
322
|
}
|
|
202
|
-
function
|
|
323
|
+
function ee(o) {
|
|
203
324
|
return {
|
|
204
325
|
async fetch(n, t) {
|
|
205
|
-
const e = new URL(n.url),
|
|
206
|
-
if (
|
|
207
|
-
if (
|
|
326
|
+
const e = new URL(n.url), a = U(e, t, o.defaultLocale);
|
|
327
|
+
if (a) return a;
|
|
328
|
+
if (M.test(e.pathname))
|
|
208
329
|
return t.ASSETS.fetch(n);
|
|
209
|
-
const
|
|
210
|
-
let
|
|
211
|
-
const
|
|
330
|
+
const s = k(n, o), i = new URL("/index.html", n.url);
|
|
331
|
+
let c = await (await t.ASSETS.fetch(new Request(i))).text();
|
|
332
|
+
const f = o.defaultLocale, [d, v] = await Promise.all([
|
|
212
333
|
t.SITE_CONTENT.get("config"),
|
|
213
|
-
t.SITE_CONTENT.get(`content:${
|
|
214
|
-
]),
|
|
215
|
-
let
|
|
216
|
-
if (!v &&
|
|
217
|
-
const
|
|
218
|
-
|
|
334
|
+
t.SITE_CONTENT.get(`content:${s}`)
|
|
335
|
+
]), S = d ? JSON.parse(d) : {};
|
|
336
|
+
let g = v ? JSON.parse(v) : {};
|
|
337
|
+
if (!v && s !== f) {
|
|
338
|
+
const p = await t.SITE_CONTENT.get(`content:${f}`);
|
|
339
|
+
p && (g = JSON.parse(p));
|
|
219
340
|
}
|
|
220
341
|
const h = e.pathname.match(/^\/p\/(.+)$/);
|
|
221
342
|
if (h) {
|
|
222
|
-
const
|
|
223
|
-
if (!p
|
|
224
|
-
let
|
|
225
|
-
!
|
|
343
|
+
const p = h[1];
|
|
344
|
+
if (!g[p]) {
|
|
345
|
+
let m = await t.SITE_CONTENT.get(`page:${p}:${s}`);
|
|
346
|
+
!m && s !== f && (m = await t.SITE_CONTENT.get(`page:${p}:${f}`)), m && (g[p] = JSON.parse(m));
|
|
226
347
|
}
|
|
227
348
|
}
|
|
228
349
|
const r = {
|
|
229
|
-
locale:
|
|
230
|
-
config:
|
|
350
|
+
locale: s,
|
|
351
|
+
config: S
|
|
231
352
|
};
|
|
232
|
-
for (const [
|
|
233
|
-
r[`page:${
|
|
353
|
+
for (const [p, m] of Object.entries(g))
|
|
354
|
+
r[`page:${p}`] = m;
|
|
234
355
|
if (h) {
|
|
235
|
-
const
|
|
236
|
-
|
|
356
|
+
const p = h[1], m = r[`page:${p}`];
|
|
357
|
+
m && (c = E(c, m, o.siteName));
|
|
237
358
|
}
|
|
238
|
-
e.pathname === "/" &&
|
|
239
|
-
const
|
|
240
|
-
return
|
|
241
|
-
</head>`), new Response(
|
|
359
|
+
e.pathname === "/" && g.home && (c = E(c, g.home, o.siteName));
|
|
360
|
+
const y = `<script>window.__SITE_DATA__ = ${JSON.stringify(r)};<\/script>`;
|
|
361
|
+
return c = c.replace("</head>", `${y}
|
|
362
|
+
</head>`), new Response(c, {
|
|
242
363
|
headers: {
|
|
243
364
|
"Content-Type": "text/html;charset=utf-8",
|
|
244
365
|
"Cache-Control": "public, max-age=60"
|
|
@@ -248,14 +369,18 @@ function W(o) {
|
|
|
248
369
|
};
|
|
249
370
|
}
|
|
250
371
|
export {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
372
|
+
D as PAGE_CONTEXT_KEY,
|
|
373
|
+
F as buildLayoutCSS,
|
|
374
|
+
ee as createSiteWorker,
|
|
375
|
+
b as isInPreview,
|
|
376
|
+
Z as setupPreviewRouter,
|
|
377
|
+
q as useDynamicLayout,
|
|
378
|
+
C as useLivePreview,
|
|
379
|
+
V as useLocaleSwitcher,
|
|
380
|
+
H as usePageData,
|
|
381
|
+
X as useSiteApi,
|
|
382
|
+
Q as useSiteComponents,
|
|
383
|
+
G as useSiteConfig,
|
|
384
|
+
B as useSiteData,
|
|
385
|
+
Y as useSkin
|
|
261
386
|
};
|
package/dist/types/config.d.ts
CHANGED
|
@@ -33,6 +33,24 @@ export interface SiteConfig {
|
|
|
33
33
|
}>;
|
|
34
34
|
};
|
|
35
35
|
features?: Record<string, boolean | Record<string, any>>;
|
|
36
|
+
layouts?: Record<string, LayoutConfig>;
|
|
37
|
+
}
|
|
38
|
+
export interface LayoutZone {
|
|
39
|
+
type?: 'slot';
|
|
40
|
+
component?: string;
|
|
41
|
+
class?: string;
|
|
42
|
+
container?: boolean;
|
|
43
|
+
containerStyle?: Record<string, string>;
|
|
44
|
+
}
|
|
45
|
+
export interface LayoutGridBreakpoint {
|
|
46
|
+
areas: string[];
|
|
47
|
+
columns?: string;
|
|
48
|
+
rows?: string;
|
|
49
|
+
gap?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface LayoutConfig {
|
|
52
|
+
grid: Record<string, LayoutGridBreakpoint>;
|
|
53
|
+
zones: Record<string, LayoutZone>;
|
|
36
54
|
}
|
|
37
55
|
export interface NavItem {
|
|
38
56
|
text: string | Record<string, string>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xosen/site-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"description": "Shared Vue components and composables for Xosen site templates",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -24,23 +24,23 @@
|
|
|
24
24
|
"dist",
|
|
25
25
|
"src"
|
|
26
26
|
],
|
|
27
|
-
"scripts": {
|
|
28
|
-
"build": "vite build && vue-tsc --declaration --emitDeclarationOnly --outDir dist",
|
|
29
|
-
"dev": "vite build --watch",
|
|
30
|
-
"clean": "rm -rf dist"
|
|
31
|
-
},
|
|
32
27
|
"peerDependencies": {
|
|
33
28
|
"vue": "^3.5.0",
|
|
34
29
|
"vue-i18n": "^11.0.0",
|
|
35
30
|
"vue-router": "^5.0.0"
|
|
36
31
|
},
|
|
37
32
|
"devDependencies": {
|
|
38
|
-
"vue": "
|
|
39
|
-
"vue-i18n": "
|
|
40
|
-
"vue-router": "
|
|
41
|
-
"vue-tsc": "
|
|
42
|
-
"typescript": "
|
|
43
|
-
"vite": "
|
|
44
|
-
"@vitejs/plugin-vue": "
|
|
33
|
+
"vue": "^3.5.30",
|
|
34
|
+
"vue-i18n": "^11.3.0",
|
|
35
|
+
"vue-router": "^5.0.3",
|
|
36
|
+
"vue-tsc": "^3.2.5",
|
|
37
|
+
"typescript": "^5.9.3",
|
|
38
|
+
"vite": "^7.3.1",
|
|
39
|
+
"@vitejs/plugin-vue": "^6.0.5"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "vite build && vue-tsc --declaration --emitDeclarationOnly --outDir dist",
|
|
43
|
+
"dev": "vite build --watch",
|
|
44
|
+
"clean": "rm -rf dist"
|
|
45
45
|
}
|
|
46
|
-
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { computed, type Ref } from 'vue';
|
|
2
|
+
import { useRoute } from 'vue-router';
|
|
3
|
+
|
|
4
|
+
import type { SiteData } from '../types/config.js';
|
|
5
|
+
import { useLivePreview } from './useLivePreview.js';
|
|
6
|
+
|
|
7
|
+
export interface GridBreakpoint {
|
|
8
|
+
areas?: string[];
|
|
9
|
+
columns?: string;
|
|
10
|
+
rows?: string;
|
|
11
|
+
gap?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface LayoutZone {
|
|
15
|
+
type?: 'slot';
|
|
16
|
+
component?: string;
|
|
17
|
+
class?: string;
|
|
18
|
+
container?: boolean;
|
|
19
|
+
containerStyle?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LayoutConfig {
|
|
23
|
+
grid?: Record<string, GridBreakpoint>;
|
|
24
|
+
zones: Record<string, LayoutZone>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const BREAKPOINTS: Record<string, number> = {
|
|
28
|
+
sm: 600,
|
|
29
|
+
md: 960,
|
|
30
|
+
lg: 1280,
|
|
31
|
+
xl: 1920,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const DEFAULT_LAYOUT: LayoutConfig = {
|
|
35
|
+
grid: {
|
|
36
|
+
default: {
|
|
37
|
+
areas: ['header', 'main', 'footer'],
|
|
38
|
+
rows: 'auto 1fr auto',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
zones: {
|
|
42
|
+
header: { component: 'nav-menu' },
|
|
43
|
+
main: { type: 'slot' },
|
|
44
|
+
footer: { component: 'footer' },
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const LANDING_LAYOUT: LayoutConfig = {
|
|
49
|
+
grid: {
|
|
50
|
+
default: {
|
|
51
|
+
areas: ['main'],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
zones: {
|
|
55
|
+
main: { type: 'slot' },
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function buildGridCSS(bp: GridBreakpoint, selector: string): string {
|
|
60
|
+
const rules: string[] = [];
|
|
61
|
+
if (bp.areas?.length) {
|
|
62
|
+
const areas = bp.areas.map((row) => `"${row}"`).join(' ');
|
|
63
|
+
rules.push(`grid-template-areas: ${areas}`);
|
|
64
|
+
}
|
|
65
|
+
if (bp.columns) rules.push(`grid-template-columns: ${bp.columns}`);
|
|
66
|
+
if (bp.rows) rules.push(`grid-template-rows: ${bp.rows}`);
|
|
67
|
+
if (bp.gap) rules.push(`gap: ${bp.gap}`);
|
|
68
|
+
if (!rules.length) return '';
|
|
69
|
+
return `${selector} { ${rules.join('; ')}; }`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Generate responsive CSS for a layout config.
|
|
74
|
+
* Returns a CSS string with media queries for each breakpoint.
|
|
75
|
+
*/
|
|
76
|
+
export function buildLayoutCSS(layout: LayoutConfig, selector: string): string {
|
|
77
|
+
const parts: string[] = [`${selector} { display: grid; min-height: 100vh; }`];
|
|
78
|
+
const grid = layout.grid;
|
|
79
|
+
|
|
80
|
+
if (!grid) return parts.join('\n');
|
|
81
|
+
|
|
82
|
+
if (grid.default) {
|
|
83
|
+
const css = buildGridCSS(grid.default, selector);
|
|
84
|
+
if (css) parts.push(css);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const bp of ['sm', 'md', 'lg', 'xl']) {
|
|
88
|
+
const config = grid[bp];
|
|
89
|
+
if (!config) continue;
|
|
90
|
+
const css = buildGridCSS(config, selector);
|
|
91
|
+
if (css) {
|
|
92
|
+
parts.push(`@media (min-width: ${BREAKPOINTS[bp]}px) { ${css} }`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return parts.join('\n');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resolve the current layout config from site data, route, and preview state.
|
|
101
|
+
*
|
|
102
|
+
* Returns:
|
|
103
|
+
* - `layoutName` — current layout name (e.g. 'default', 'sidebar')
|
|
104
|
+
* - `layoutConfig` — resolved LayoutConfig with grid + zones
|
|
105
|
+
* - `layoutCSS` — generated responsive CSS string
|
|
106
|
+
* - `layoutClass` — unique CSS class for this layout instance
|
|
107
|
+
*/
|
|
108
|
+
export function useDynamicLayout() {
|
|
109
|
+
const route = useRoute();
|
|
110
|
+
const { previewPage, isPreviewMode } = useLivePreview();
|
|
111
|
+
const siteData = (window as any).__SITE_DATA__ as SiteData | undefined;
|
|
112
|
+
|
|
113
|
+
const layoutId = `dl-${Math.random().toString(36).slice(2, 8)}`;
|
|
114
|
+
const layoutClass = computed(() => ['dynamic-layout', layoutId]);
|
|
115
|
+
|
|
116
|
+
const layoutName = computed(() => {
|
|
117
|
+
if (route.path === '/_preview_') {
|
|
118
|
+
return previewPage.value?.layout || 'landing';
|
|
119
|
+
}
|
|
120
|
+
if (isPreviewMode.value && previewPage.value?.layout) {
|
|
121
|
+
return previewPage.value.layout;
|
|
122
|
+
}
|
|
123
|
+
const pageKey = route.path === '/' ? 'page:home' : `page:${route.params.slug || ''}`;
|
|
124
|
+
const pageData = siteData?.[pageKey];
|
|
125
|
+
return pageData?.layout || 'default';
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const layoutConfig = computed<LayoutConfig>(() => {
|
|
129
|
+
const layouts = siteData?.config?.layouts;
|
|
130
|
+
if (layouts) {
|
|
131
|
+
const config = layouts[layoutName.value] || layouts.default;
|
|
132
|
+
if (config?.zones) return config;
|
|
133
|
+
}
|
|
134
|
+
if (layoutName.value === 'landing') return LANDING_LAYOUT;
|
|
135
|
+
return DEFAULT_LAYOUT;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const layoutCSS = computed(() => buildLayoutCSS(layoutConfig.value, `.${layoutId}`));
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
layoutName,
|
|
142
|
+
layoutConfig,
|
|
143
|
+
layoutCSS,
|
|
144
|
+
layoutClass,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { computed } from 'vue';
|
|
2
|
+
import { useI18n } from 'vue-i18n';
|
|
3
|
+
|
|
4
|
+
import type { SiteData } from '../types/config.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Locale switcher composable.
|
|
8
|
+
* Manages locale state, available locales, and persistence.
|
|
9
|
+
*/
|
|
10
|
+
export function useLocaleSwitcher() {
|
|
11
|
+
const { locale } = useI18n();
|
|
12
|
+
const siteData = (window as any).__SITE_DATA__ as SiteData | undefined;
|
|
13
|
+
|
|
14
|
+
const locales = computed(() => siteData?.config?.locales || []);
|
|
15
|
+
const defaultLocale = computed(() => siteData?.config?.defaultLocale || 'en');
|
|
16
|
+
const hasMultipleLocales = computed(() => locales.value.length > 1);
|
|
17
|
+
|
|
18
|
+
function switchLocale(loc: string) {
|
|
19
|
+
locale.value = loc;
|
|
20
|
+
localStorage.setItem('x-site-locale', loc);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a text value that may be a string or a locale map.
|
|
25
|
+
* e.g. "Home" or { "en": "Home", "uk": "Головна" }
|
|
26
|
+
*/
|
|
27
|
+
function resolveText(text: string | Record<string, string>): string {
|
|
28
|
+
if (typeof text === 'string') return text;
|
|
29
|
+
return text[locale.value] || text[defaultLocale.value] || Object.values(text)[0] || '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
locale,
|
|
34
|
+
locales,
|
|
35
|
+
defaultLocale,
|
|
36
|
+
hasMultipleLocales,
|
|
37
|
+
switchLocale,
|
|
38
|
+
resolveText,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ref, computed, watch, onMounted, type Ref } from 'vue';
|
|
2
|
+
import { useI18n } from 'vue-i18n';
|
|
3
|
+
|
|
4
|
+
import type { SiteData } from '../types/config.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Locale-aware access to site components (navigation, footer, sidebar, etc.).
|
|
8
|
+
*
|
|
9
|
+
* On initial load, reads from preloaded __SITE_DATA__.components.
|
|
10
|
+
* When locale changes, fetches updated components from the API.
|
|
11
|
+
*/
|
|
12
|
+
export function useSiteComponents() {
|
|
13
|
+
const { locale } = useI18n();
|
|
14
|
+
const siteData = (window as any).__SITE_DATA__ as SiteData | undefined;
|
|
15
|
+
const initialLocale = siteData?.locale;
|
|
16
|
+
|
|
17
|
+
const components = ref<Record<string, any>>({ ...(siteData as any)?.components || {} });
|
|
18
|
+
|
|
19
|
+
async function fetchComponents(loc: string) {
|
|
20
|
+
// If same as initial locale, use preloaded data
|
|
21
|
+
if (loc === initialLocale) {
|
|
22
|
+
components.value = { ...(siteData as any)?.components || {} };
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch(`/api/content/components:${loc}`);
|
|
27
|
+
if (res.ok) {
|
|
28
|
+
components.value = await res.json();
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// Keep current
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
watch(locale, (newLocale) => fetchComponents(newLocale));
|
|
36
|
+
|
|
37
|
+
onMounted(() => {
|
|
38
|
+
if (locale.value !== initialLocale) {
|
|
39
|
+
fetchComponents(locale.value);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get a specific component's data by key.
|
|
45
|
+
* Returns a computed ref that updates on locale change.
|
|
46
|
+
*/
|
|
47
|
+
function getComponent<T = any>(key: string): Ref<T | null> {
|
|
48
|
+
return computed(() => components.value[key] || null) as Ref<T | null>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
/** All components (reactive, locale-aware) */
|
|
53
|
+
components,
|
|
54
|
+
/** Get a single component by key */
|
|
55
|
+
getComponent,
|
|
56
|
+
/** Force refetch for current locale */
|
|
57
|
+
reload: () => fetchComponents(locale.value),
|
|
58
|
+
};
|
|
59
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,10 @@ export { useSiteApi } from './composables/useSiteApi.js';
|
|
|
6
6
|
export { useLivePreview, setupPreviewRouter, isInPreview } from './composables/useLivePreview.js';
|
|
7
7
|
export { usePageData } from './composables/usePageData.js';
|
|
8
8
|
export type { UsePageDataReturn } from './composables/usePageData.js';
|
|
9
|
+
export { useSiteComponents } from './composables/useSiteComponents.js';
|
|
10
|
+
export { useLocaleSwitcher } from './composables/useLocaleSwitcher.js';
|
|
11
|
+
export { useDynamicLayout, buildLayoutCSS } from './composables/useDynamicLayout.js';
|
|
12
|
+
export type { LayoutConfig, LayoutZone, GridBreakpoint } from './composables/useDynamicLayout.js';
|
|
9
13
|
export type { PreviewMessage } from './composables/useLivePreview.js';
|
|
10
14
|
|
|
11
15
|
// Worker
|
package/src/types/config.ts
CHANGED
|
@@ -38,6 +38,28 @@ export interface SiteConfig {
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
features?: Record<string, boolean | Record<string, any>>;
|
|
41
|
+
|
|
42
|
+
layouts?: Record<string, LayoutConfig>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface LayoutZone {
|
|
46
|
+
type?: 'slot';
|
|
47
|
+
component?: string;
|
|
48
|
+
class?: string;
|
|
49
|
+
container?: boolean;
|
|
50
|
+
containerStyle?: Record<string, string>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface LayoutGridBreakpoint {
|
|
54
|
+
areas: string[];
|
|
55
|
+
columns?: string;
|
|
56
|
+
rows?: string;
|
|
57
|
+
gap?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface LayoutConfig {
|
|
61
|
+
grid: Record<string, LayoutGridBreakpoint>;
|
|
62
|
+
zones: Record<string, LayoutZone>;
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
export interface NavItem {
|