@kilivi/payloadcms-theme-management 2.1.0 → 2.2.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/README.md +37 -7
- package/dist/components/ServerThemeInjector.d.ts +13 -1
- package/dist/components/ServerThemeInjector.d.ts.map +1 -1
- package/dist/components/ServerThemeInjector.js +24 -1
- package/dist/components/ServerThemeInjector.js.map +1 -1
- package/dist/components/ThemeLivePreviewSync.d.ts +3 -0
- package/dist/components/ThemeLivePreviewSync.d.ts.map +1 -1
- package/dist/components/ThemeLivePreviewSync.js +28 -4
- package/dist/components/ThemeLivePreviewSync.js.map +1 -1
- package/dist/components/typographyPreviewUtils.d.ts +5 -0
- package/dist/components/typographyPreviewUtils.d.ts.map +1 -1
- package/dist/components/typographyPreviewUtils.js +16 -10
- package/dist/components/typographyPreviewUtils.js.map +1 -1
- package/dist/constants/themeFonts.d.ts +94 -168
- package/dist/constants/themeFonts.d.ts.map +1 -1
- package/dist/constants/themeFonts.js +260 -140
- package/dist/constants/themeFonts.js.map +1 -1
- package/dist/endpoints/themeFonts.d.ts +5 -0
- package/dist/endpoints/themeFonts.d.ts.map +1 -0
- package/dist/endpoints/themeFonts.js +138 -0
- package/dist/endpoints/themeFonts.js.map +1 -0
- package/dist/fields/AppearancePreviewField.d.ts.map +1 -1
- package/dist/fields/AppearancePreviewField.js +196 -133
- package/dist/fields/AppearancePreviewField.js.map +1 -1
- package/dist/fields/FontSelectField.css +155 -0
- package/dist/fields/FontSelectField.d.ts +4 -2
- package/dist/fields/FontSelectField.d.ts.map +1 -1
- package/dist/fields/FontSelectField.js +80 -118
- package/dist/fields/FontSelectField.js.map +1 -1
- package/dist/fields/StylePresetField.d.ts.map +1 -1
- package/dist/fields/StylePresetField.js +31 -30
- package/dist/fields/StylePresetField.js.map +1 -1
- package/dist/fields/ThemePreviewField.css +63 -1
- package/dist/fields/ThemePreviewField.d.ts.map +1 -1
- package/dist/fields/ThemePreviewField.js +2 -5
- package/dist/fields/ThemePreviewField.js.map +1 -1
- package/dist/fields/themeConfigurationField.d.ts.map +1 -1
- package/dist/fields/themeConfigurationField.js +320 -271
- package/dist/fields/themeConfigurationField.js.map +1 -1
- package/dist/hooks/useNativeLivePreview.d.ts +15 -0
- package/dist/hooks/useNativeLivePreview.d.ts.map +1 -0
- package/dist/hooks/useNativeLivePreview.js +18 -0
- package/dist/hooks/useNativeLivePreview.js.map +1 -0
- package/dist/hooks/useThemeFonts.d.ts +10 -0
- package/dist/hooks/useThemeFonts.d.ts.map +1 -0
- package/dist/hooks/useThemeFonts.js +36 -0
- package/dist/hooks/useThemeFonts.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/next-fonts.d.ts +3 -0
- package/dist/next-fonts.d.ts.map +1 -0
- package/dist/next-fonts.js +360 -0
- package/dist/next-fonts.js.map +1 -0
- package/dist/payload-types.d.ts +10 -6
- package/dist/payload-types.d.ts.map +1 -1
- package/dist/payload-types.js.map +1 -1
- package/dist/presets.d.ts +6 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js.map +1 -1
- package/dist/style-presets.d.ts +27 -18
- package/dist/style-presets.d.ts.map +1 -1
- package/dist/style-presets.js +399 -217
- package/dist/style-presets.js.map +1 -1
- package/dist/translations.d.ts +5 -0
- package/dist/translations.d.ts.map +1 -1
- package/dist/translations.js +10 -0
- package/dist/translations.js.map +1 -1
- package/dist/utils/livePreviewThemeRuntime.d.ts +6 -1
- package/dist/utils/livePreviewThemeRuntime.d.ts.map +1 -1
- package/dist/utils/livePreviewThemeRuntime.js +11 -1
- package/dist/utils/livePreviewThemeRuntime.js.map +1 -1
- package/dist/utils/resolveThemeConfiguration.d.ts +15 -0
- package/dist/utils/resolveThemeConfiguration.d.ts.map +1 -1
- package/dist/utils/resolveThemeConfiguration.js +41 -8
- package/dist/utils/resolveThemeConfiguration.js.map +1 -1
- package/dist/utils/themeUtils.d.ts.map +1 -1
- package/dist/utils/themeUtils.js +60 -2
- package/dist/utils/themeUtils.js.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -115,6 +115,32 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|
|
115
115
|
}
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
+
That is all an app needs. The fonts the editor picks are served from **your own
|
|
119
|
+
domain** by the plugin's `/api/theme-fonts/*` endpoints (fetched and cached on the
|
|
120
|
+
server), so visitors' browsers never contact Google (GDPR-friendly), with no setup.
|
|
121
|
+
|
|
122
|
+
### 3) Optional: fonts via `next/font` (Next.js best practice)
|
|
123
|
+
|
|
124
|
+
For build-time self-hosting with metric-matched fallbacks (no layout shift), add
|
|
125
|
+
one import. Every catalog font is declared with `preload: false`, so a page only
|
|
126
|
+
downloads the face it actually uses:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { themeFontVariables } from '@kilivi/payloadcms-theme-management/next-fonts'
|
|
130
|
+
|
|
131
|
+
<html lang="en" className={themeFontVariables}>
|
|
132
|
+
<head>
|
|
133
|
+
<ServerThemeInjector themeConfiguration={appearance?.themeConfiguration} fontLoading="none" />
|
|
134
|
+
</head>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
If your Next.js setup does not compile packages from `node_modules`, add
|
|
138
|
+
`'@kilivi/payloadcms-theme-management'` to `transpilePackages` in `next.config`.
|
|
139
|
+
Pass `fontLoading="none"` to `ThemeLivePreviewSync` as well if you use it.
|
|
140
|
+
|
|
141
|
+
`fontLoading` options: `'self-hosted'` (default), `'google'` (Google Fonts CDN),
|
|
142
|
+
`'none'` (you load the faces, e.g. with `next/font`).
|
|
143
|
+
|
|
118
144
|
## The Appearance Settings tab
|
|
119
145
|
|
|
120
146
|
The tab is ordered as a **presets first, fine-tuning second** workflow:
|
|
@@ -123,17 +149,21 @@ The tab is ordered as a **presets first, fine-tuning second** workflow:
|
|
|
123
149
|
- *Keep my: Colours · Fonts · Style* locks
|
|
124
150
|
- the palette generator
|
|
125
151
|
- the collapsible theme preset list
|
|
126
|
-
2. **Live preview:** one preview
|
|
127
|
-
3. **Style preset:**
|
|
128
|
-
4. **
|
|
129
|
-
5. **
|
|
130
|
-
|
|
131
|
-
- Typography
|
|
132
|
-
- Custom theme presets (JSON import)
|
|
152
|
+
2. **Live preview:** with Payload's native Live Preview configured (`livePreview` option), the real site is the preview: the tab shows an *Open Live Preview* button instead of a mock, and nothing while the panel is open, so the fields get the full width. Without it, one mock preview covers colours, fonts and component styles. Once the settings group is at least 960 px wide (a container query, not the viewport width), it moves into a sticky right-hand column that stays visible while you edit anything below it.
|
|
153
|
+
3. **Style preset:** 17 complete looks (Swiss, Editorial, Luxury, Art Deco, Tech, Poster, Brutalism, Retro, Glass …). Each sets fonts, text size, heading weight and tracking, radius, spacing, effects and every component style — but never colours. Combine any style with any colour theme.
|
|
154
|
+
4. **Typography:** body and heading font (30 curated Google fonts with Czech diacritics, grouped by category), base size, line height, heading weight and heading letter spacing.
|
|
155
|
+
5. **Border radius and spacing scale.**
|
|
156
|
+
6. **Fine-tuning sections:** these are collapsed by default.
|
|
133
157
|
- Visual effects
|
|
134
158
|
- Component styles
|
|
159
|
+
- Colour mode (all light and dark tokens, the WCAG audit and export)
|
|
160
|
+
- Custom theme presets (JSON import, for developers)
|
|
135
161
|
- Advanced (animation level, custom CSS)
|
|
136
162
|
|
|
163
|
+
The generated CSS applies the chosen fonts to `body` and headings with
|
|
164
|
+
zero-specificity `:where()` rules — only once the editor has picked typography,
|
|
165
|
+
so untouched sites render as before and your own CSS always wins.
|
|
166
|
+
|
|
137
167
|
### Locks and undo
|
|
138
168
|
|
|
139
169
|
Each bulk action honours the locks:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ThemeFontLoading } from '../constants/themeFonts.js';
|
|
1
2
|
interface ServerThemeInjectorProps {
|
|
2
3
|
/**
|
|
3
4
|
* Theme configuration object from your site settings.
|
|
@@ -10,11 +11,22 @@ interface ServerThemeInjectorProps {
|
|
|
10
11
|
* ```
|
|
11
12
|
*/
|
|
12
13
|
themeConfiguration?: unknown;
|
|
14
|
+
/**
|
|
15
|
+
* How the body / heading fonts picked in the admin are loaded:
|
|
16
|
+
* - `'self-hosted'` (default) — served from your own origin by the plugin's
|
|
17
|
+
* `/api/theme-fonts/*` endpoints; visitors never contact Google (GDPR-friendly).
|
|
18
|
+
* - `'google'` — straight from the Google Fonts CDN.
|
|
19
|
+
* - `'none'` — you load the faces yourself, e.g. with `next/font` exposing the
|
|
20
|
+
* `--font-*` variables listed in `FONT_CATALOG` (they take precedence).
|
|
21
|
+
*/
|
|
22
|
+
fontLoading?: ThemeFontLoading;
|
|
23
|
+
/** Payload `routes.api` when it is not '/api'. */
|
|
24
|
+
apiRoute?: string;
|
|
13
25
|
}
|
|
14
26
|
/**
|
|
15
27
|
* Server-side theme CSS injector to prevent FOUC
|
|
16
28
|
* This component injects critical theme CSS directly into the HTML head during SSR
|
|
17
29
|
*/
|
|
18
|
-
export declare function ServerThemeInjector({ themeConfiguration, }: Readonly<ServerThemeInjectorProps>): Promise<import("react").JSX.Element>;
|
|
30
|
+
export declare function ServerThemeInjector({ themeConfiguration, fontLoading, apiRoute, }: Readonly<ServerThemeInjectorProps>): Promise<import("react").JSX.Element>;
|
|
19
31
|
export {};
|
|
20
32
|
//# sourceMappingURL=ServerThemeInjector.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerThemeInjector.d.ts","sourceRoot":"","sources":["../../src/components/ServerThemeInjector.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ServerThemeInjector.d.ts","sourceRoot":"","sources":["../../src/components/ServerThemeInjector.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAWvF,UAAU,wBAAwB;IAChC;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAA;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAMD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,EACxC,kBAAkB,EAClB,WAA2B,EAC3B,QAAiB,GAClB,EAAE,QAAQ,CAAC,wBAAwB,CAAC,wCAiIpC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { buildThemeFontsHref } from '../constants/themeFonts.js';
|
|
2
3
|
import { getBorderRadiusConfig } from '../providers/Theme/themeConfig.js';
|
|
3
4
|
import { resolveThemeConfiguration } from '../utils/resolveThemeConfiguration.js';
|
|
4
5
|
import { createFallbackCriticalCSS, getThemeCriticalCSS } from '../utils/themeAssets.js';
|
|
@@ -8,9 +9,13 @@ import { InitTheme } from './InitTheme.js';
|
|
|
8
9
|
/**
|
|
9
10
|
* Server-side theme CSS injector to prevent FOUC
|
|
10
11
|
* This component injects critical theme CSS directly into the HTML head during SSR
|
|
11
|
-
*/ export async function ServerThemeInjector({ themeConfiguration }) {
|
|
12
|
+
*/ export async function ServerThemeInjector({ themeConfiguration, fontLoading = 'self-hosted', apiRoute = '/api' }) {
|
|
12
13
|
// resolveThemeConfiguration accepts `unknown` — no cast needed
|
|
13
14
|
const resolvedConfiguration = resolveThemeConfiguration(themeConfiguration);
|
|
15
|
+
const fontsHref = buildThemeFontsHref(resolvedConfiguration.fontSelections ?? [], {
|
|
16
|
+
mode: fontLoading,
|
|
17
|
+
apiRoute
|
|
18
|
+
});
|
|
14
19
|
const { theme, borderRadius, customCSS, lightMode, darkMode, fontScale, spacing, animationLevel, allowColorModeToggle, colorMode, typography, visualEffects, componentStyles } = resolvedConfiguration;
|
|
15
20
|
// Use resolved configuration directly
|
|
16
21
|
const normalizedThemeConfiguration = {
|
|
@@ -70,6 +75,24 @@ ${borderRadiusCSS}
|
|
|
70
75
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
71
76
|
children: [
|
|
72
77
|
/*#__PURE__*/ _jsx(InitTheme, {}),
|
|
78
|
+
fontsHref && fontLoading === 'google' ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
79
|
+
children: [
|
|
80
|
+
/*#__PURE__*/ _jsx("link", {
|
|
81
|
+
rel: "preconnect",
|
|
82
|
+
href: "https://fonts.googleapis.com"
|
|
83
|
+
}),
|
|
84
|
+
/*#__PURE__*/ _jsx("link", {
|
|
85
|
+
rel: "preconnect",
|
|
86
|
+
href: "https://fonts.gstatic.com",
|
|
87
|
+
crossOrigin: ""
|
|
88
|
+
})
|
|
89
|
+
]
|
|
90
|
+
}) : null,
|
|
91
|
+
fontsHref ? /*#__PURE__*/ _jsx("link", {
|
|
92
|
+
rel: "stylesheet",
|
|
93
|
+
href: fontsHref,
|
|
94
|
+
precedence: "theme-fonts"
|
|
95
|
+
}) : null,
|
|
73
96
|
emitBothSchemes ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
74
97
|
children: [
|
|
75
98
|
lightBg ? /*#__PURE__*/ _jsx("meta", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/ServerThemeInjector.tsx"],"sourcesContent":["import type { ThemeTypographyPreset } from '../presets.js'\nimport { getBorderRadiusConfig } from '../providers/Theme/themeConfig.js'\nimport type { ThemeDefaults } from '../providers/Theme/types.js'\nimport { resolveThemeConfiguration } from '../utils/resolveThemeConfiguration.js'\nimport type { ResolvedThemeConfiguration } from '../utils/resolveThemeConfiguration.js'\nimport { createFallbackCriticalCSS, getThemeCriticalCSS } from '../utils/themeAssets.js'\nimport { generateThemeColorsCss } from '../utils/themeColors.js'\nimport { generateThemeCSS } from '../utils/themeUtils.js'\nimport { InitTheme } from './InitTheme.js'\n\ninterface ServerThemeInjectorProps {\n /**\n * Theme configuration object from your site settings.\n * Pass the themeConfiguration field directly, not the entire site settings object.\n *\n * @example\n * ```tsx\n * const siteSettings = await payload.findGlobal({ slug: 'site-settings' })\n * <ServerThemeInjector themeConfiguration={siteSettings.themeConfiguration} />\n * ```\n */\n themeConfiguration?: unknown\n}\n\ntype RuntimeThemeConfiguration = Omit<ResolvedThemeConfiguration, 'typography'> & {\n typography?: ThemeTypographyPreset | null\n}\n/**\n * Server-side theme CSS injector to prevent FOUC\n * This component injects critical theme CSS directly into the HTML head during SSR\n */\nexport async function ServerThemeInjector({\n themeConfiguration,\n}: Readonly<ServerThemeInjectorProps>) {\n // resolveThemeConfiguration accepts `unknown` — no cast needed\n const resolvedConfiguration = resolveThemeConfiguration(themeConfiguration)\n const {\n theme,\n borderRadius,\n customCSS,\n lightMode,\n darkMode,\n fontScale,\n spacing,\n animationLevel,\n allowColorModeToggle,\n colorMode,\n typography,\n visualEffects,\n componentStyles,\n } = resolvedConfiguration\n\n // Use resolved configuration directly\n const normalizedThemeConfiguration: RuntimeThemeConfiguration = {\n theme,\n colorMode,\n allowColorModeToggle,\n borderRadius,\n fontScale,\n spacing,\n animationLevel,\n customCSS: customCSS ?? null,\n lightMode,\n darkMode,\n typography: typography ?? undefined,\n visualEffects: visualEffects ?? null,\n componentStyles: componentStyles ?? null,\n }\n\n let criticalCSS = await getThemeCriticalCSS(theme as ThemeDefaults)\n criticalCSS ??= createFallbackCriticalCSS(theme as ThemeDefaults)\n\n const borderRadiusConfig = getBorderRadiusConfig(borderRadius ?? 'medium')\n const cssRecord =\n typeof borderRadiusConfig?.css === 'string'\n ? { '--radius-default': borderRadiusConfig.css }\n : (borderRadiusConfig?.css ?? {})\n\n const borderRadiusCSS = Object.entries(cssRecord)\n .map(([property, value]) => ` ${property}: ${value};`)\n .join('\\n')\n\n const borderRadiusBlock = borderRadiusCSS\n ? `:root {\n${borderRadiusCSS}\n}`\n : ''\n\n const themeConfigurationCSS = generateThemeCSS(normalizedThemeConfiguration)\n const colorModesCSS = generateThemeColorsCss({ themeName: theme, lightMode, darkMode })\n const customCSSBlock = typeof customCSS === 'string' ? customCSS.trim() : ''\n\n // `color-scheme` makes native UI (form controls, scrollbars) match the theme and\n // avoids a white flash on dark sites. Fixed mode → single scheme; toggle/auto → both.\n const colorScheme =\n allowColorModeToggle === false\n ? colorMode === 'dark'\n ? 'dark'\n : 'light'\n : 'light dark'\n const colorSchemeBlock = `:root {\\n color-scheme: ${colorScheme};\\n}`\n\n const combinedCSS = [\n criticalCSS,\n colorSchemeBlock,\n borderRadiusBlock,\n themeConfigurationCSS,\n colorModesCSS,\n customCSSBlock,\n ]\n .filter((block) => Boolean(block?.trim?.().length))\n .join('\\n\\n')\n\n // <meta name=\"theme-color\"> colours the mobile browser chrome to match the page\n // background. Emit per-scheme variants when the mode can change, otherwise a\n // single tag matching the fixed mode.\n const pick = (mode: typeof lightMode, key: 'background'): string | undefined => {\n const value = (mode as Record<string, unknown> | null | undefined)?.[key]\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined\n }\n const lightBg = pick(lightMode, 'background')\n const darkBg = pick(darkMode, 'background')\n const emitBothSchemes = allowColorModeToggle !== false || colorMode === 'auto'\n\n return (\n <>\n <InitTheme />\n {emitBothSchemes ? (\n <>\n {lightBg ? (\n <meta name=\"theme-color\" media=\"(prefers-color-scheme: light)\" content={lightBg} />\n ) : null}\n {darkBg ? (\n <meta name=\"theme-color\" media=\"(prefers-color-scheme: dark)\" content={darkBg} />\n ) : null}\n </>\n ) : (\n (() => {\n const fixed = colorMode === 'dark' ? darkBg : lightBg\n return fixed ? <meta name=\"theme-color\" content={fixed} /> : null\n })()\n )}\n <style\n id=\"theme-critical-css\"\n dangerouslySetInnerHTML={{\n __html: combinedCSS,\n }}\n />\n </>\n )\n}\n"],"names":["getBorderRadiusConfig","resolveThemeConfiguration","createFallbackCriticalCSS","getThemeCriticalCSS","generateThemeColorsCss","generateThemeCSS","InitTheme","ServerThemeInjector","themeConfiguration","resolvedConfiguration","theme","borderRadius","customCSS","lightMode","darkMode","fontScale","spacing","animationLevel","allowColorModeToggle","colorMode","typography","visualEffects","componentStyles","normalizedThemeConfiguration","undefined","criticalCSS","borderRadiusConfig","cssRecord","css","borderRadiusCSS","Object","entries","map","property","value","join","borderRadiusBlock","themeConfigurationCSS","colorModesCSS","themeName","customCSSBlock","trim","colorScheme","colorSchemeBlock","combinedCSS","filter","block","Boolean","length","pick","mode","key","lightBg","darkBg","emitBothSchemes","meta","name","media","content","fixed","style","id","dangerouslySetInnerHTML","__html"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/components/ServerThemeInjector.tsx"],"sourcesContent":["import { buildThemeFontsHref, type ThemeFontLoading } from '../constants/themeFonts.js'\nimport type { ThemeTypographyPreset } from '../presets.js'\nimport { getBorderRadiusConfig } from '../providers/Theme/themeConfig.js'\nimport type { ThemeDefaults } from '../providers/Theme/types.js'\nimport { resolveThemeConfiguration } from '../utils/resolveThemeConfiguration.js'\nimport type { ResolvedThemeConfiguration } from '../utils/resolveThemeConfiguration.js'\nimport { createFallbackCriticalCSS, getThemeCriticalCSS } from '../utils/themeAssets.js'\nimport { generateThemeColorsCss } from '../utils/themeColors.js'\nimport { generateThemeCSS } from '../utils/themeUtils.js'\nimport { InitTheme } from './InitTheme.js'\n\ninterface ServerThemeInjectorProps {\n /**\n * Theme configuration object from your site settings.\n * Pass the themeConfiguration field directly, not the entire site settings object.\n *\n * @example\n * ```tsx\n * const siteSettings = await payload.findGlobal({ slug: 'site-settings' })\n * <ServerThemeInjector themeConfiguration={siteSettings.themeConfiguration} />\n * ```\n */\n themeConfiguration?: unknown\n /**\n * How the body / heading fonts picked in the admin are loaded:\n * - `'self-hosted'` (default) — served from your own origin by the plugin's\n * `/api/theme-fonts/*` endpoints; visitors never contact Google (GDPR-friendly).\n * - `'google'` — straight from the Google Fonts CDN.\n * - `'none'` — you load the faces yourself, e.g. with `next/font` exposing the\n * `--font-*` variables listed in `FONT_CATALOG` (they take precedence).\n */\n fontLoading?: ThemeFontLoading\n /** Payload `routes.api` when it is not '/api'. */\n apiRoute?: string\n}\n\ntype RuntimeThemeConfiguration = Omit<ResolvedThemeConfiguration, 'typography'> & {\n typography?: ThemeTypographyPreset | null\n}\n\n/**\n * Server-side theme CSS injector to prevent FOUC\n * This component injects critical theme CSS directly into the HTML head during SSR\n */\nexport async function ServerThemeInjector({\n themeConfiguration,\n fontLoading = 'self-hosted',\n apiRoute = '/api',\n}: Readonly<ServerThemeInjectorProps>) {\n // resolveThemeConfiguration accepts `unknown` — no cast needed\n const resolvedConfiguration = resolveThemeConfiguration(themeConfiguration)\n const fontsHref = buildThemeFontsHref(resolvedConfiguration.fontSelections ?? [], {\n mode: fontLoading,\n apiRoute,\n })\n const {\n theme,\n borderRadius,\n customCSS,\n lightMode,\n darkMode,\n fontScale,\n spacing,\n animationLevel,\n allowColorModeToggle,\n colorMode,\n typography,\n visualEffects,\n componentStyles,\n } = resolvedConfiguration\n\n // Use resolved configuration directly\n const normalizedThemeConfiguration: RuntimeThemeConfiguration = {\n theme,\n colorMode,\n allowColorModeToggle,\n borderRadius,\n fontScale,\n spacing,\n animationLevel,\n customCSS: customCSS ?? null,\n lightMode,\n darkMode,\n typography: typography ?? undefined,\n visualEffects: visualEffects ?? null,\n componentStyles: componentStyles ?? null,\n }\n\n let criticalCSS = await getThemeCriticalCSS(theme as ThemeDefaults)\n criticalCSS ??= createFallbackCriticalCSS(theme as ThemeDefaults)\n\n const borderRadiusConfig = getBorderRadiusConfig(borderRadius ?? 'medium')\n const cssRecord =\n typeof borderRadiusConfig?.css === 'string'\n ? { '--radius-default': borderRadiusConfig.css }\n : (borderRadiusConfig?.css ?? {})\n\n const borderRadiusCSS = Object.entries(cssRecord)\n .map(([property, value]) => ` ${property}: ${value};`)\n .join('\\n')\n\n const borderRadiusBlock = borderRadiusCSS\n ? `:root {\n${borderRadiusCSS}\n}`\n : ''\n\n const themeConfigurationCSS = generateThemeCSS(normalizedThemeConfiguration)\n const colorModesCSS = generateThemeColorsCss({ themeName: theme, lightMode, darkMode })\n const customCSSBlock = typeof customCSS === 'string' ? customCSS.trim() : ''\n\n // `color-scheme` makes native UI (form controls, scrollbars) match the theme and\n // avoids a white flash on dark sites. Fixed mode → single scheme; toggle/auto → both.\n const colorScheme =\n allowColorModeToggle === false\n ? colorMode === 'dark'\n ? 'dark'\n : 'light'\n : 'light dark'\n const colorSchemeBlock = `:root {\\n color-scheme: ${colorScheme};\\n}`\n\n const combinedCSS = [\n criticalCSS,\n colorSchemeBlock,\n borderRadiusBlock,\n themeConfigurationCSS,\n colorModesCSS,\n customCSSBlock,\n ]\n .filter((block) => Boolean(block?.trim?.().length))\n .join('\\n\\n')\n\n // <meta name=\"theme-color\"> colours the mobile browser chrome to match the page\n // background. Emit per-scheme variants when the mode can change, otherwise a\n // single tag matching the fixed mode.\n const pick = (mode: typeof lightMode, key: 'background'): string | undefined => {\n const value = (mode as Record<string, unknown> | null | undefined)?.[key]\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined\n }\n const lightBg = pick(lightMode, 'background')\n const darkBg = pick(darkMode, 'background')\n const emitBothSchemes = allowColorModeToggle !== false || colorMode === 'auto'\n\n return (\n <>\n <InitTheme />\n {fontsHref && fontLoading === 'google' ? (\n <>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossOrigin=\"\" />\n </>\n ) : null}\n {/* `precedence` lets React 19 hoist the stylesheet into <head> and de-duplicate it. */}\n {fontsHref ? <link rel=\"stylesheet\" href={fontsHref} precedence=\"theme-fonts\" /> : null}\n {emitBothSchemes ? (\n <>\n {lightBg ? (\n <meta name=\"theme-color\" media=\"(prefers-color-scheme: light)\" content={lightBg} />\n ) : null}\n {darkBg ? (\n <meta name=\"theme-color\" media=\"(prefers-color-scheme: dark)\" content={darkBg} />\n ) : null}\n </>\n ) : (\n (() => {\n const fixed = colorMode === 'dark' ? darkBg : lightBg\n return fixed ? <meta name=\"theme-color\" content={fixed} /> : null\n })()\n )}\n <style\n id=\"theme-critical-css\"\n dangerouslySetInnerHTML={{\n __html: combinedCSS,\n }}\n />\n </>\n )\n}\n"],"names":["buildThemeFontsHref","getBorderRadiusConfig","resolveThemeConfiguration","createFallbackCriticalCSS","getThemeCriticalCSS","generateThemeColorsCss","generateThemeCSS","InitTheme","ServerThemeInjector","themeConfiguration","fontLoading","apiRoute","resolvedConfiguration","fontsHref","fontSelections","mode","theme","borderRadius","customCSS","lightMode","darkMode","fontScale","spacing","animationLevel","allowColorModeToggle","colorMode","typography","visualEffects","componentStyles","normalizedThemeConfiguration","undefined","criticalCSS","borderRadiusConfig","cssRecord","css","borderRadiusCSS","Object","entries","map","property","value","join","borderRadiusBlock","themeConfigurationCSS","colorModesCSS","themeName","customCSSBlock","trim","colorScheme","colorSchemeBlock","combinedCSS","filter","block","Boolean","length","pick","key","lightBg","darkBg","emitBothSchemes","link","rel","href","crossOrigin","precedence","meta","name","media","content","fixed","style","id","dangerouslySetInnerHTML","__html"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import type { ThemeFontLoading } from '../constants/themeFonts.js';
|
|
2
3
|
interface ThemeLivePreviewSyncProps {
|
|
3
4
|
initialData: Record<string, unknown>;
|
|
4
5
|
serverURL: string;
|
|
@@ -6,6 +7,8 @@ interface ThemeLivePreviewSyncProps {
|
|
|
6
7
|
depth?: number;
|
|
7
8
|
themeConfigurationKey?: string;
|
|
8
9
|
styleElementId?: string;
|
|
10
|
+
/** Same as ServerThemeInjector's `fontLoading`; use 'none' with next/font. */
|
|
11
|
+
fontLoading?: ThemeFontLoading;
|
|
9
12
|
}
|
|
10
13
|
export declare const ThemeLivePreviewSync: React.FC<ThemeLivePreviewSyncProps>;
|
|
11
14
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemeLivePreviewSync.d.ts","sourceRoot":"","sources":["../../src/components/ThemeLivePreviewSync.tsx"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"ThemeLivePreviewSync.d.ts","sourceRoot":"","sources":["../../src/components/ThemeLivePreviewSync.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAqC,MAAM,OAAO,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAMlE,UAAU,yBAAyB;IACjC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,gBAAgB,CAAA;CAC/B;AAED,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CA0EpE,CAAA"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useLivePreview } from '@payloadcms/live-preview-react';
|
|
3
|
-
import React, { useEffect, useMemo } from 'react';
|
|
3
|
+
import React, { useEffect, useMemo, useRef } from 'react';
|
|
4
4
|
import { buildLivePreviewThemeRuntime, extractThemeConfigurationFromLiveData } from '../utils/livePreviewThemeRuntime.js';
|
|
5
|
-
export const ThemeLivePreviewSync = ({ initialData, serverURL, apiRoute, depth, themeConfigurationKey = 'themeConfiguration', styleElementId = 'theme-live-preview-css' })=>{
|
|
5
|
+
export const ThemeLivePreviewSync = ({ initialData, serverURL, apiRoute, depth, themeConfigurationKey = 'themeConfiguration', styleElementId = 'theme-live-preview-css', fontLoading = 'self-hosted' })=>{
|
|
6
6
|
const { data } = useLivePreview({
|
|
7
7
|
apiRoute,
|
|
8
8
|
depth,
|
|
9
9
|
initialData,
|
|
10
10
|
serverURL
|
|
11
11
|
});
|
|
12
|
+
const appliedAttributes = useRef([]);
|
|
12
13
|
const themeConfiguration = useMemo(()=>extractThemeConfigurationFromLiveData(data, themeConfigurationKey), [
|
|
13
14
|
data,
|
|
14
15
|
themeConfigurationKey
|
|
@@ -17,7 +18,7 @@ export const ThemeLivePreviewSync = ({ initialData, serverURL, apiRoute, depth,
|
|
|
17
18
|
if (typeof document === 'undefined' || !themeConfiguration) {
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
20
|
-
const runtime = buildLivePreviewThemeRuntime(themeConfiguration);
|
|
21
|
+
const runtime = buildLivePreviewThemeRuntime(themeConfiguration, apiRoute, fontLoading);
|
|
21
22
|
let styleElement = document.getElementById(styleElementId);
|
|
22
23
|
if (!styleElement) {
|
|
23
24
|
styleElement = document.createElement('style');
|
|
@@ -25,6 +26,27 @@ export const ThemeLivePreviewSync = ({ initialData, serverURL, apiRoute, depth,
|
|
|
25
26
|
document.head.appendChild(styleElement);
|
|
26
27
|
}
|
|
27
28
|
styleElement.innerHTML = runtime.css;
|
|
29
|
+
const fontsLinkId = `${styleElementId}-fonts`;
|
|
30
|
+
let fontsLink = document.getElementById(fontsLinkId);
|
|
31
|
+
const fontsAlreadyLoaded = runtime.fontsHref !== null && [
|
|
32
|
+
...document.querySelectorAll('link[rel="stylesheet"]')
|
|
33
|
+
].some((link)=>link !== fontsLink && link.getAttribute('href') === runtime.fontsHref);
|
|
34
|
+
if (runtime.fontsHref && !fontsAlreadyLoaded) {
|
|
35
|
+
if (!fontsLink) {
|
|
36
|
+
fontsLink = document.createElement('link');
|
|
37
|
+
fontsLink.id = fontsLinkId;
|
|
38
|
+
fontsLink.rel = 'stylesheet';
|
|
39
|
+
document.head.appendChild(fontsLink);
|
|
40
|
+
}
|
|
41
|
+
if (fontsLink.getAttribute('href') !== runtime.fontsHref) fontsLink.href = runtime.fontsHref;
|
|
42
|
+
} else {
|
|
43
|
+
fontsLink?.remove();
|
|
44
|
+
}
|
|
45
|
+
const root = document.documentElement;
|
|
46
|
+
// Drop style attributes that are no longer configured, then apply the current set.
|
|
47
|
+
appliedAttributes.current.filter((name)=>!(name in runtime.attributes)).forEach((name)=>root.removeAttribute(name));
|
|
48
|
+
Object.entries(runtime.attributes).forEach(([name, value])=>root.setAttribute(name, value));
|
|
49
|
+
appliedAttributes.current = Object.keys(runtime.attributes);
|
|
28
50
|
document.documentElement.dataset.theme = runtime.theme;
|
|
29
51
|
document.documentElement.dataset.colorMode = runtime.mode;
|
|
30
52
|
document.documentElement.dataset.themeMode = runtime.mode;
|
|
@@ -32,7 +54,9 @@ export const ThemeLivePreviewSync = ({ initialData, serverURL, apiRoute, depth,
|
|
|
32
54
|
document.documentElement.classList.add(runtime.mode);
|
|
33
55
|
}, [
|
|
34
56
|
themeConfiguration,
|
|
35
|
-
styleElementId
|
|
57
|
+
styleElementId,
|
|
58
|
+
apiRoute,
|
|
59
|
+
fontLoading
|
|
36
60
|
]);
|
|
37
61
|
return null;
|
|
38
62
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/ThemeLivePreviewSync.tsx"],"sourcesContent":["'use client'\n\nimport { useLivePreview } from '@payloadcms/live-preview-react'\nimport React, { useEffect, useMemo } from 'react'\nimport {\n buildLivePreviewThemeRuntime,\n extractThemeConfigurationFromLiveData,\n} from '../utils/livePreviewThemeRuntime.js'\n\ninterface ThemeLivePreviewSyncProps {\n initialData: Record<string, unknown>\n serverURL: string\n apiRoute?: string\n depth?: number\n themeConfigurationKey?: string\n styleElementId?: string\n}\n\nexport const ThemeLivePreviewSync: React.FC<ThemeLivePreviewSyncProps> = ({\n initialData,\n serverURL,\n apiRoute,\n depth,\n themeConfigurationKey = 'themeConfiguration',\n styleElementId = 'theme-live-preview-css',\n}) => {\n const { data } = useLivePreview<Record<string, unknown>>({\n apiRoute,\n depth,\n initialData,\n serverURL,\n })\n\n const themeConfiguration = useMemo(\n () => extractThemeConfigurationFromLiveData(data, themeConfigurationKey),\n [data, themeConfigurationKey],\n )\n\n useEffect(() => {\n if (typeof document === 'undefined' || !themeConfiguration) {\n return\n }\n\n const runtime = buildLivePreviewThemeRuntime(themeConfiguration)\n\n let styleElement = document.getElementById(styleElementId) as HTMLStyleElement | null\n if (!styleElement) {\n styleElement = document.createElement('style')\n styleElement.id = styleElementId\n document.head.appendChild(styleElement)\n }\n\n styleElement.innerHTML = runtime.css\n\n document.documentElement.dataset.theme = runtime.theme\n document.documentElement.dataset.colorMode = runtime.mode\n document.documentElement.dataset.themeMode = runtime.mode\n document.documentElement.classList.remove('light', 'dark', 'auto')\n document.documentElement.classList.add(runtime.mode)\n }, [themeConfiguration, styleElementId])\n\n return null\n}\n"],"names":["useLivePreview","React","useEffect","useMemo","buildLivePreviewThemeRuntime","extractThemeConfigurationFromLiveData","ThemeLivePreviewSync","initialData","serverURL","apiRoute","depth","themeConfigurationKey","styleElementId","data","themeConfiguration","document","runtime","styleElement","getElementById","createElement","id","head","appendChild","innerHTML","css","documentElement","dataset","theme","colorMode","mode","themeMode","classList","
|
|
1
|
+
{"version":3,"sources":["../../src/components/ThemeLivePreviewSync.tsx"],"sourcesContent":["'use client'\n\nimport { useLivePreview } from '@payloadcms/live-preview-react'\nimport React, { useEffect, useMemo, useRef } from 'react'\nimport type { ThemeFontLoading } from '../constants/themeFonts.js'\nimport {\n buildLivePreviewThemeRuntime,\n extractThemeConfigurationFromLiveData,\n} from '../utils/livePreviewThemeRuntime.js'\n\ninterface ThemeLivePreviewSyncProps {\n initialData: Record<string, unknown>\n serverURL: string\n apiRoute?: string\n depth?: number\n themeConfigurationKey?: string\n styleElementId?: string\n /** Same as ServerThemeInjector's `fontLoading`; use 'none' with next/font. */\n fontLoading?: ThemeFontLoading\n}\n\nexport const ThemeLivePreviewSync: React.FC<ThemeLivePreviewSyncProps> = ({\n initialData,\n serverURL,\n apiRoute,\n depth,\n themeConfigurationKey = 'themeConfiguration',\n styleElementId = 'theme-live-preview-css',\n fontLoading = 'self-hosted',\n}) => {\n const { data } = useLivePreview<Record<string, unknown>>({\n apiRoute,\n depth,\n initialData,\n serverURL,\n })\n\n const appliedAttributes = useRef<string[]>([])\n\n const themeConfiguration = useMemo(\n () => extractThemeConfigurationFromLiveData(data, themeConfigurationKey),\n [data, themeConfigurationKey],\n )\n\n useEffect(() => {\n if (typeof document === 'undefined' || !themeConfiguration) {\n return\n }\n\n const runtime = buildLivePreviewThemeRuntime(themeConfiguration, apiRoute, fontLoading)\n\n let styleElement = document.getElementById(styleElementId) as HTMLStyleElement | null\n if (!styleElement) {\n styleElement = document.createElement('style')\n styleElement.id = styleElementId\n document.head.appendChild(styleElement)\n }\n\n styleElement.innerHTML = runtime.css\n\n const fontsLinkId = `${styleElementId}-fonts`\n let fontsLink = document.getElementById(fontsLinkId) as HTMLLinkElement | null\n const fontsAlreadyLoaded =\n runtime.fontsHref !== null &&\n [...document.querySelectorAll<HTMLLinkElement>('link[rel=\"stylesheet\"]')].some(\n (link) => link !== fontsLink && link.getAttribute('href') === runtime.fontsHref,\n )\n if (runtime.fontsHref && !fontsAlreadyLoaded) {\n if (!fontsLink) {\n fontsLink = document.createElement('link')\n fontsLink.id = fontsLinkId\n fontsLink.rel = 'stylesheet'\n document.head.appendChild(fontsLink)\n }\n if (fontsLink.getAttribute('href') !== runtime.fontsHref) fontsLink.href = runtime.fontsHref\n } else {\n fontsLink?.remove()\n }\n\n const root = document.documentElement\n // Drop style attributes that are no longer configured, then apply the current set.\n appliedAttributes.current\n .filter((name) => !(name in runtime.attributes))\n .forEach((name) => root.removeAttribute(name))\n Object.entries(runtime.attributes).forEach(([name, value]) => root.setAttribute(name, value))\n appliedAttributes.current = Object.keys(runtime.attributes)\n\n document.documentElement.dataset.theme = runtime.theme\n document.documentElement.dataset.colorMode = runtime.mode\n document.documentElement.dataset.themeMode = runtime.mode\n document.documentElement.classList.remove('light', 'dark', 'auto')\n document.documentElement.classList.add(runtime.mode)\n }, [themeConfiguration, styleElementId, apiRoute, fontLoading])\n\n return null\n}\n"],"names":["useLivePreview","React","useEffect","useMemo","useRef","buildLivePreviewThemeRuntime","extractThemeConfigurationFromLiveData","ThemeLivePreviewSync","initialData","serverURL","apiRoute","depth","themeConfigurationKey","styleElementId","fontLoading","data","appliedAttributes","themeConfiguration","document","runtime","styleElement","getElementById","createElement","id","head","appendChild","innerHTML","css","fontsLinkId","fontsLink","fontsAlreadyLoaded","fontsHref","querySelectorAll","some","link","getAttribute","rel","href","remove","root","documentElement","current","filter","name","attributes","forEach","removeAttribute","Object","entries","value","setAttribute","keys","dataset","theme","colorMode","mode","themeMode","classList","add"],"mappings":"AAAA;AAEA,SAASA,cAAc,QAAQ,iCAAgC;AAC/D,OAAOC,SAASC,SAAS,EAAEC,OAAO,EAAEC,MAAM,QAAQ,QAAO;AAEzD,SACEC,4BAA4B,EAC5BC,qCAAqC,QAChC,sCAAqC;AAa5C,OAAO,MAAMC,uBAA4D,CAAC,EACxEC,WAAW,EACXC,SAAS,EACTC,QAAQ,EACRC,KAAK,EACLC,wBAAwB,oBAAoB,EAC5CC,iBAAiB,wBAAwB,EACzCC,cAAc,aAAa,EAC5B;IACC,MAAM,EAAEC,IAAI,EAAE,GAAGf,eAAwC;QACvDU;QACAC;QACAH;QACAC;IACF;IAEA,MAAMO,oBAAoBZ,OAAiB,EAAE;IAE7C,MAAMa,qBAAqBd,QACzB,IAAMG,sCAAsCS,MAAMH,wBAClD;QAACG;QAAMH;KAAsB;IAG/BV,UAAU;QACR,IAAI,OAAOgB,aAAa,eAAe,CAACD,oBAAoB;YAC1D;QACF;QAEA,MAAME,UAAUd,6BAA6BY,oBAAoBP,UAAUI;QAE3E,IAAIM,eAAeF,SAASG,cAAc,CAACR;QAC3C,IAAI,CAACO,cAAc;YACjBA,eAAeF,SAASI,aAAa,CAAC;YACtCF,aAAaG,EAAE,GAAGV;YAClBK,SAASM,IAAI,CAACC,WAAW,CAACL;QAC5B;QAEAA,aAAaM,SAAS,GAAGP,QAAQQ,GAAG;QAEpC,MAAMC,cAAc,GAAGf,eAAe,MAAM,CAAC;QAC7C,IAAIgB,YAAYX,SAASG,cAAc,CAACO;QACxC,MAAME,qBACJX,QAAQY,SAAS,KAAK,QACtB;eAAIb,SAASc,gBAAgB,CAAkB;SAA0B,CAACC,IAAI,CAC5E,CAACC,OAASA,SAASL,aAAaK,KAAKC,YAAY,CAAC,YAAYhB,QAAQY,SAAS;QAEnF,IAAIZ,QAAQY,SAAS,IAAI,CAACD,oBAAoB;YAC5C,IAAI,CAACD,WAAW;gBACdA,YAAYX,SAASI,aAAa,CAAC;gBACnCO,UAAUN,EAAE,GAAGK;gBACfC,UAAUO,GAAG,GAAG;gBAChBlB,SAASM,IAAI,CAACC,WAAW,CAACI;YAC5B;YACA,IAAIA,UAAUM,YAAY,CAAC,YAAYhB,QAAQY,SAAS,EAAEF,UAAUQ,IAAI,GAAGlB,QAAQY,SAAS;QAC9F,OAAO;YACLF,WAAWS;QACb;QAEA,MAAMC,OAAOrB,SAASsB,eAAe;QACrC,mFAAmF;QACnFxB,kBAAkByB,OAAO,CACtBC,MAAM,CAAC,CAACC,OAAS,CAAEA,CAAAA,QAAQxB,QAAQyB,UAAU,AAAD,GAC5CC,OAAO,CAAC,CAACF,OAASJ,KAAKO,eAAe,CAACH;QAC1CI,OAAOC,OAAO,CAAC7B,QAAQyB,UAAU,EAAEC,OAAO,CAAC,CAAC,CAACF,MAAMM,MAAM,GAAKV,KAAKW,YAAY,CAACP,MAAMM;QACtFjC,kBAAkByB,OAAO,GAAGM,OAAOI,IAAI,CAAChC,QAAQyB,UAAU;QAE1D1B,SAASsB,eAAe,CAACY,OAAO,CAACC,KAAK,GAAGlC,QAAQkC,KAAK;QACtDnC,SAASsB,eAAe,CAACY,OAAO,CAACE,SAAS,GAAGnC,QAAQoC,IAAI;QACzDrC,SAASsB,eAAe,CAACY,OAAO,CAACI,SAAS,GAAGrC,QAAQoC,IAAI;QACzDrC,SAASsB,eAAe,CAACiB,SAAS,CAACnB,MAAM,CAAC,SAAS,QAAQ;QAC3DpB,SAASsB,eAAe,CAACiB,SAAS,CAACC,GAAG,CAACvC,QAAQoC,IAAI;IACrD,GAAG;QAACtC;QAAoBJ;QAAgBH;QAAUI;KAAY;IAE9D,OAAO;AACT,EAAC"}
|
|
@@ -5,12 +5,17 @@ export interface TypographySelection {
|
|
|
5
5
|
headingFontCustom?: string | null;
|
|
6
6
|
baseFontSize?: string | null;
|
|
7
7
|
lineHeight?: string | null;
|
|
8
|
+
headingWeight?: string | null;
|
|
9
|
+
letterSpacing?: string | null;
|
|
8
10
|
}
|
|
9
11
|
export interface ResolvedTypographyPreview {
|
|
10
12
|
bodyFont: string;
|
|
11
13
|
headingFont: string;
|
|
12
14
|
baseFontSize: string;
|
|
13
15
|
lineHeight: string;
|
|
16
|
+
headingWeight: string;
|
|
17
|
+
headingLetterSpacing: string;
|
|
18
|
+
headingTransform: 'none' | 'uppercase';
|
|
14
19
|
bodyLabel: string;
|
|
15
20
|
headingLabel: string;
|
|
16
21
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typographyPreviewUtils.d.ts","sourceRoot":"","sources":["../../src/components/typographyPreviewUtils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"typographyPreviewUtils.d.ts","sourceRoot":"","sources":["../../src/components/typographyPreviewUtils.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,gBAAgB,EAAE,MAAM,GAAG,WAAW,CAAA;IACtC,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;CACrB;AA+ED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,mBAAmB,GAAG,IAAI,GAAG,SAAS,EACjD,SAAS,CAAC,EAAE,MAAM,GACjB,yBAAyB,CAyC3B"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BODY_FONT_OPTIONS,
|
|
1
|
+
import { BODY_FONT_OPTIONS, getFontCatalogEntry, HEADING_FONT_OPTIONS, LETTER_SPACING_TO_CSS, toFontSizeCss } from '../constants/themeFonts.js';
|
|
2
2
|
import { defaultThemePresets } from '../presets.js';
|
|
3
3
|
const FALLBACK_THEME = defaultThemePresets[0]?.name ?? 'cool';
|
|
4
4
|
const fallbackPreset = defaultThemePresets.find((preset)=>preset.name === FALLBACK_THEME) ?? defaultThemePresets[0];
|
|
@@ -20,12 +20,14 @@ const FONT_VAR_FALLBACK = {
|
|
|
20
20
|
'var(--font-urbanist)': '"Urbanist", system-ui, sans-serif'
|
|
21
21
|
};
|
|
22
22
|
function withFallback(fontFamily, fallback) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
const value = fontFamily?.trim() || fallback;
|
|
24
|
+
// A `var(--font-x)` without a fallback whose variable is not defined (always the
|
|
25
|
+
// case in the admin) makes the whole font-family declaration invalid. Give every
|
|
26
|
+
// such reference a fallback stack so the preview always renders a real face.
|
|
27
|
+
return value.replace(/var\((--[\w-]+)\)/g, (match, name)=>{
|
|
28
|
+
const mapped = FONT_VAR_FALLBACK[match] ?? 'system-ui, sans-serif';
|
|
29
|
+
return `var(${name}, ${mapped})`;
|
|
30
|
+
});
|
|
29
31
|
}
|
|
30
32
|
function getPresetTypography(themeName) {
|
|
31
33
|
const preset = defaultThemePresets.find((item)=>item.name === themeName) ?? fallbackPreset;
|
|
@@ -41,9 +43,10 @@ function resolveFontFamily(value, customValue, fallback) {
|
|
|
41
43
|
}
|
|
42
44
|
return withFallback(fallback, fallbackPreset?.typography?.fontFamily ?? 'system-ui, sans-serif');
|
|
43
45
|
}
|
|
44
|
-
|
|
46
|
+
// Catalog stacks are complete (variable with fallback + generic family).
|
|
47
|
+
const preconfigured = getFontCatalogEntry(value)?.fontFamily;
|
|
45
48
|
if (preconfigured) {
|
|
46
|
-
return
|
|
49
|
+
return preconfigured;
|
|
47
50
|
}
|
|
48
51
|
return withFallback(fallback, fallbackPreset?.typography?.fontFamily ?? 'system-ui, sans-serif');
|
|
49
52
|
}
|
|
@@ -67,13 +70,16 @@ export function resolveTypographyPreview(selection, themeName) {
|
|
|
67
70
|
const headingFontCustom = selection?.headingFontCustom ?? undefined;
|
|
68
71
|
const resolvedBodyFont = resolveFontFamily(bodyFontValue, bodyFontCustom, fallbackBody);
|
|
69
72
|
const resolvedHeadingFont = resolveFontFamily(headingFontValue, headingFontCustom, fallbackHeading);
|
|
70
|
-
const baseFontSize = selection?.baseFontSize
|
|
73
|
+
const baseFontSize = toFontSizeCss(selection?.baseFontSize) ?? fallbackSize;
|
|
71
74
|
const lineHeight = selection?.lineHeight && selection.lineHeight !== 'preset' ? selection.lineHeight : fallbackLineHeight;
|
|
72
75
|
return {
|
|
73
76
|
bodyFont: resolvedBodyFont,
|
|
74
77
|
headingFont: resolvedHeadingFont,
|
|
75
78
|
baseFontSize,
|
|
76
79
|
lineHeight,
|
|
80
|
+
headingWeight: selection?.headingWeight && selection.headingWeight !== 'preset' ? selection.headingWeight : presetTypography?.headingWeight ?? '700',
|
|
81
|
+
headingLetterSpacing: (selection?.letterSpacing && LETTER_SPACING_TO_CSS[selection.letterSpacing]) ?? presetTypography?.headingLetterSpacing ?? 'normal',
|
|
82
|
+
headingTransform: selection?.letterSpacing === 'wider' ? 'uppercase' : 'none',
|
|
77
83
|
bodyLabel: resolveOptionLabel(BODY_FONT_OPTIONS, bodyFontValue),
|
|
78
84
|
headingLabel: resolveOptionLabel(HEADING_FONT_OPTIONS, headingFontValue)
|
|
79
85
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/typographyPreviewUtils.ts"],"sourcesContent":["import {\n BODY_FONT_OPTIONS,\n
|
|
1
|
+
{"version":3,"sources":["../../src/components/typographyPreviewUtils.ts"],"sourcesContent":["import {\n BODY_FONT_OPTIONS,\n getFontCatalogEntry,\n HEADING_FONT_OPTIONS,\n LETTER_SPACING_TO_CSS,\n toFontSizeCss,\n} from '../constants/themeFonts.js'\nimport { defaultThemePresets } from '../presets.js'\n\nexport interface TypographySelection {\n bodyFont?: string | null\n headingFont?: string | null\n bodyFontCustom?: string | null\n headingFontCustom?: string | null\n baseFontSize?: string | null\n lineHeight?: string | null\n headingWeight?: string | null\n letterSpacing?: string | null\n}\n\nexport interface ResolvedTypographyPreview {\n bodyFont: string\n headingFont: string\n baseFontSize: string\n lineHeight: string\n headingWeight: string\n headingLetterSpacing: string\n headingTransform: 'none' | 'uppercase'\n bodyLabel: string\n headingLabel: string\n}\n\nconst FALLBACK_THEME = defaultThemePresets[0]?.name ?? 'cool'\nconst fallbackPreset =\n defaultThemePresets.find((preset) => preset.name === FALLBACK_THEME) ?? defaultThemePresets[0]\n\nconst FONT_VAR_FALLBACK: Record<string, string> = {\n 'var(--font-inter)': '\"Inter\", system-ui, sans-serif',\n 'var(--font-geist-sans)': '\"Geist Sans\", system-ui, sans-serif',\n 'var(--font-geist-mono)': '\"Geist Mono\", SFMono-Regular, ui-monospace, monospace',\n 'var(--font-roboto)': '\"Roboto\", system-ui, sans-serif',\n 'var(--font-open-sans)': '\"Open Sans\", system-ui, sans-serif',\n 'var(--font-lato)': '\"Lato\", system-ui, sans-serif',\n 'var(--font-montserrat)': '\"Montserrat\", system-ui, sans-serif',\n 'var(--font-poppins)': '\"Poppins\", system-ui, sans-serif',\n 'var(--font-outfit)': '\"Outfit\", system-ui, sans-serif',\n 'var(--font-playfair)': '\"Playfair Display\", \"Times New Roman\", serif',\n 'var(--font-merriweather)': '\"Merriweather\", \"Times New Roman\", serif',\n 'var(--font-raleway)': '\"Raleway\", system-ui, sans-serif',\n 'var(--font-lora)': '\"Lora\", \"Times New Roman\", serif',\n 'var(--font-manrope)': '\"Manrope\", system-ui, sans-serif',\n 'var(--font-urbanist)': '\"Urbanist\", system-ui, sans-serif',\n}\n\nfunction withFallback(fontFamily: string, fallback: string): string {\n const value = fontFamily?.trim() || fallback\n\n // A `var(--font-x)` without a fallback whose variable is not defined (always the\n // case in the admin) makes the whole font-family declaration invalid. Give every\n // such reference a fallback stack so the preview always renders a real face.\n return value.replace(/var\\((--[\\w-]+)\\)/g, (match, name: string) => {\n const mapped = FONT_VAR_FALLBACK[match] ?? 'system-ui, sans-serif'\n return `var(${name}, ${mapped})`\n })\n}\n\nfunction getPresetTypography(themeName?: string) {\n const preset = defaultThemePresets.find((item) => item.name === themeName) ?? fallbackPreset\n return preset?.typography\n}\n\nfunction resolveFontFamily(\n value: string | null | undefined,\n customValue: string | null | undefined,\n fallback: string,\n): string {\n if (!value || value === 'preset') {\n return withFallback(fallback, fallbackPreset?.typography?.fontFamily ?? 'system-ui, sans-serif')\n }\n\n if (value === 'custom') {\n if (customValue && customValue.trim().length > 0) {\n return customValue.trim()\n }\n return withFallback(fallback, fallbackPreset?.typography?.fontFamily ?? 'system-ui, sans-serif')\n }\n\n // Catalog stacks are complete (variable with fallback + generic family).\n const preconfigured = getFontCatalogEntry(value)?.fontFamily\n if (preconfigured) {\n return preconfigured\n }\n\n return withFallback(fallback, fallbackPreset?.typography?.fontFamily ?? 'system-ui, sans-serif')\n}\n\nfunction resolveOptionLabel(\n options: ReadonlyArray<{ label: string | Record<string, string>; value: string }>,\n value?: string | null,\n locale: 'en' | 'cs' = 'en',\n): string {\n const option = options.find((item) => item.value === value)\n if (!option) return value ?? ''\n if (typeof option.label === 'string') {\n return option.label\n }\n return option.label?.[locale] ?? Object.values(option.label)[0] ?? option.value\n}\n\nexport function resolveTypographyPreview(\n selection: TypographySelection | null | undefined,\n themeName?: string,\n): ResolvedTypographyPreview {\n const presetTypography = getPresetTypography(themeName)\n const fallbackBody = presetTypography?.fontFamily ?? 'system-ui, sans-serif'\n const fallbackHeading = presetTypography?.headingFamily ?? fallbackBody\n const fallbackSize = presetTypography?.baseFontSize ?? '16px'\n const fallbackLineHeight = presetTypography?.lineHeight ?? '1.6'\n\n const bodyFontValue = selection?.bodyFont ?? 'preset'\n const headingFontValue = selection?.headingFont ?? 'preset'\n const bodyFontCustom = selection?.bodyFontCustom ?? undefined\n const headingFontCustom = selection?.headingFontCustom ?? undefined\n const resolvedBodyFont = resolveFontFamily(bodyFontValue, bodyFontCustom, fallbackBody)\n const resolvedHeadingFont = resolveFontFamily(\n headingFontValue,\n headingFontCustom,\n fallbackHeading,\n )\n\n const baseFontSize = toFontSizeCss(selection?.baseFontSize) ?? fallbackSize\n const lineHeight =\n selection?.lineHeight && selection.lineHeight !== 'preset'\n ? selection.lineHeight\n : fallbackLineHeight\n\n return {\n bodyFont: resolvedBodyFont,\n headingFont: resolvedHeadingFont,\n baseFontSize,\n lineHeight,\n headingWeight:\n selection?.headingWeight && selection.headingWeight !== 'preset'\n ? selection.headingWeight\n : (presetTypography?.headingWeight ?? '700'),\n headingLetterSpacing:\n (selection?.letterSpacing && LETTER_SPACING_TO_CSS[selection.letterSpacing]) ??\n presetTypography?.headingLetterSpacing ??\n 'normal',\n headingTransform: selection?.letterSpacing === 'wider' ? 'uppercase' : 'none',\n bodyLabel: resolveOptionLabel(BODY_FONT_OPTIONS, bodyFontValue),\n headingLabel: resolveOptionLabel(HEADING_FONT_OPTIONS, headingFontValue),\n }\n}\n"],"names":["BODY_FONT_OPTIONS","getFontCatalogEntry","HEADING_FONT_OPTIONS","LETTER_SPACING_TO_CSS","toFontSizeCss","defaultThemePresets","FALLBACK_THEME","name","fallbackPreset","find","preset","FONT_VAR_FALLBACK","withFallback","fontFamily","fallback","value","trim","replace","match","mapped","getPresetTypography","themeName","item","typography","resolveFontFamily","customValue","length","preconfigured","resolveOptionLabel","options","locale","option","label","Object","values","resolveTypographyPreview","selection","presetTypography","fallbackBody","fallbackHeading","headingFamily","fallbackSize","baseFontSize","fallbackLineHeight","lineHeight","bodyFontValue","bodyFont","headingFontValue","headingFont","bodyFontCustom","undefined","headingFontCustom","resolvedBodyFont","resolvedHeadingFont","headingWeight","headingLetterSpacing","letterSpacing","headingTransform","bodyLabel","headingLabel"],"mappings":"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"}
|