@kilivi/payloadcms-theme-management 2.0.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 +346 -845
- package/dist/components/AppearanceSessionControls.d.ts +15 -0
- package/dist/components/AppearanceSessionControls.d.ts.map +1 -0
- package/dist/components/AppearanceSessionControls.js +152 -0
- package/dist/components/AppearanceSessionControls.js.map +1 -0
- package/dist/components/ColorModeToggle.css +42 -0
- package/dist/components/ColorModeToggle.d.ts +40 -0
- package/dist/components/ColorModeToggle.d.ts.map +1 -0
- package/dist/components/ColorModeToggle.js +111 -0
- package/dist/components/ColorModeToggle.js.map +1 -0
- package/dist/components/ServerThemeInjector.d.ts +13 -1
- package/dist/components/ServerThemeInjector.d.ts.map +1 -1
- package/dist/components/ServerThemeInjector.js +65 -15
- package/dist/components/ServerThemeInjector.js.map +1 -1
- package/dist/components/ThemeLivePreviewSync.d.ts +4 -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/ThemeSwitcher.d.ts +36 -0
- package/dist/components/ThemeSwitcher.d.ts.map +1 -0
- package/dist/components/ThemeSwitcher.js +89 -0
- package/dist/components/ThemeSwitcher.js.map +1 -0
- package/dist/components/typographyPreviewUtils.d.ts +5 -0
- package/dist/components/typographyPreviewUtils.d.ts.map +1 -1
- package/dist/components/typographyPreviewUtils.js +24 -11
- package/dist/components/typographyPreviewUtils.js.map +1 -1
- package/dist/constants/devicePreviews.d.ts +35 -0
- package/dist/constants/devicePreviews.d.ts.map +1 -0
- package/dist/constants/devicePreviews.js +43 -0
- package/dist/constants/devicePreviews.js.map +1 -0
- 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/extended-presets.d.ts +0 -4
- package/dist/extended-presets.d.ts.map +1 -1
- package/dist/extended-presets.js +1 -18
- package/dist/extended-presets.js.map +1 -1
- package/dist/extended-themes.d.ts.map +1 -1
- package/dist/extended-themes.js +428 -0
- package/dist/extended-themes.js.map +1 -1
- package/dist/fields/AccessibilityAuditField.d.ts +2 -0
- package/dist/fields/AccessibilityAuditField.d.ts.map +1 -0
- package/dist/fields/AccessibilityAuditField.js +303 -0
- package/dist/fields/AccessibilityAuditField.js.map +1 -0
- package/dist/fields/AppearancePreviewField.d.ts +2 -0
- package/dist/fields/AppearancePreviewField.d.ts.map +1 -0
- package/dist/fields/AppearancePreviewField.js +826 -0
- package/dist/fields/AppearancePreviewField.js.map +1 -0
- package/dist/fields/ColorPickerField.d.ts.map +1 -1
- package/dist/fields/ColorPickerField.js +89 -85
- package/dist/fields/ColorPickerField.js.map +1 -1
- package/dist/fields/FontSelectField.css +155 -0
- package/dist/fields/FontSelectField.d.ts +8 -0
- package/dist/fields/FontSelectField.d.ts.map +1 -1
- package/dist/fields/FontSelectField.js +223 -188
- package/dist/fields/FontSelectField.js.map +1 -1
- package/dist/fields/PaletteGeneratorField.d.ts +7 -0
- package/dist/fields/PaletteGeneratorField.d.ts.map +1 -0
- package/dist/fields/PaletteGeneratorField.js +327 -0
- package/dist/fields/PaletteGeneratorField.js.map +1 -0
- package/dist/fields/StylePresetField.d.ts +3 -0
- package/dist/fields/StylePresetField.d.ts.map +1 -0
- package/dist/fields/StylePresetField.js +463 -0
- package/dist/fields/StylePresetField.js.map +1 -0
- package/dist/fields/ThemeColorPickerField.css +115 -35
- package/dist/fields/ThemeColorPickerField.d.ts.map +1 -1
- package/dist/fields/ThemeColorPickerField.js +204 -70
- package/dist/fields/ThemeColorPickerField.js.map +1 -1
- package/dist/fields/ThemeExportField.d.ts +6 -0
- package/dist/fields/ThemeExportField.d.ts.map +1 -0
- package/dist/fields/ThemeExportField.js +202 -0
- package/dist/fields/ThemeExportField.js.map +1 -0
- package/dist/fields/ThemePresetImportField.d.ts.map +1 -1
- package/dist/fields/ThemePresetImportField.js +56 -25
- package/dist/fields/ThemePresetImportField.js.map +1 -1
- package/dist/fields/ThemePreviewField.css +444 -5
- package/dist/fields/ThemePreviewField.d.ts.map +1 -1
- package/dist/fields/ThemePreviewField.js +265 -170
- package/dist/fields/ThemePreviewField.js.map +1 -1
- package/dist/fields/ThemeTokenSelectField.d.ts.map +1 -1
- package/dist/fields/ThemeTokenSelectField.js +278 -80
- package/dist/fields/ThemeTokenSelectField.js.map +1 -1
- package/dist/fields/colorFieldHelpers.d.ts +90 -0
- package/dist/fields/colorFieldHelpers.d.ts.map +1 -0
- package/dist/fields/colorFieldHelpers.js +133 -0
- package/dist/fields/colorFieldHelpers.js.map +1 -0
- package/dist/fields/colorModeFields.d.ts +1 -1
- package/dist/fields/colorModeFields.d.ts.map +1 -1
- package/dist/fields/colorModeFields.js +49 -7
- package/dist/fields/colorModeFields.js.map +1 -1
- package/dist/fields/themeConfigurationField.d.ts +8 -1
- package/dist/fields/themeConfigurationField.d.ts.map +1 -1
- package/dist/fields/themeConfigurationField.js +1209 -228
- package/dist/fields/themeConfigurationField.js.map +1 -1
- package/dist/globals/ThemeSettings/FontPicker/FontPreview.d.ts.map +1 -1
- package/dist/globals/ThemeSettings/FontPicker/FontPreview.js +8 -4
- package/dist/globals/ThemeSettings/FontPicker/FontPreview.js.map +1 -1
- package/dist/globals/ThemeSettings/FontPicker/index.d.ts.map +1 -1
- package/dist/globals/ThemeSettings/FontPicker/index.js +81 -26
- package/dist/globals/ThemeSettings/FontPicker/index.js.map +1 -1
- package/dist/globals/ThemeSettings/FontPicker/styles.css +98 -41
- package/dist/globals/ThemeSettings/FontPicker/translations.d.ts +6 -0
- package/dist/globals/ThemeSettings/FontPicker/translations.d.ts.map +1 -1
- package/dist/globals/ThemeSettings/FontPicker/translations.js +14 -2
- package/dist/globals/ThemeSettings/FontPicker/translations.js.map +1 -1
- package/dist/globals/ThemeSettings/config.d.ts.map +1 -1
- package/dist/globals/ThemeSettings/config.js +76 -79
- package/dist/globals/ThemeSettings/config.js.map +1 -1
- package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.d.ts.map +1 -1
- package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js +63 -62
- package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js.map +1 -1
- package/dist/globals/ThemeSettings/fields/TypographyInfoField.d.ts.map +1 -1
- package/dist/globals/ThemeSettings/fields/TypographyInfoField.js +22 -25
- package/dist/globals/ThemeSettings/fields/TypographyInfoField.js.map +1 -1
- package/dist/hooks/useAppearanceSession.d.ts +50 -0
- package/dist/hooks/useAppearanceSession.d.ts.map +1 -0
- package/dist/hooks/useAppearanceSession.js +91 -0
- package/dist/hooks/useAppearanceSession.js.map +1 -0
- 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/hooks/useThemeTranslations.d.ts +13 -0
- package/dist/hooks/useThemeTranslations.d.ts.map +1 -0
- package/dist/hooks/useThemeTranslations.js +22 -0
- package/dist/hooks/useThemeTranslations.js.map +1 -0
- package/dist/i18n.d.ts +53 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +53 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +59 -29
- 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 +34 -6
- package/dist/payload-types.d.ts.map +1 -1
- package/dist/payload-types.js.map +1 -1
- package/dist/presets.d.ts +14 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js.map +1 -1
- package/dist/providers/Theme/index.d.ts.map +1 -1
- package/dist/providers/Theme/index.js +75 -11
- package/dist/providers/Theme/index.js.map +1 -1
- package/dist/providers/Theme/themeConfig.d.ts.map +1 -1
- package/dist/providers/Theme/themeConfig.js +104 -0
- package/dist/providers/Theme/themeConfig.js.map +1 -1
- package/dist/providers/Theme/types.d.ts +1 -1
- package/dist/providers/Theme/types.d.ts.map +1 -1
- package/dist/providers/Theme/types.js +10 -1
- package/dist/providers/Theme/types.js.map +1 -1
- package/dist/style-presets.d.ts +75 -0
- package/dist/style-presets.d.ts.map +1 -0
- package/dist/style-presets.js +738 -0
- package/dist/style-presets.js.map +1 -0
- package/dist/translations.d.ts +164 -1
- package/dist/translations.d.ts.map +1 -1
- package/dist/translations.js +320 -9
- package/dist/translations.js.map +1 -1
- package/dist/tweakcn-special-themes.d.ts.map +1 -1
- package/dist/tweakcn-special-themes.js +585 -9
- package/dist/tweakcn-special-themes.js.map +1 -1
- package/dist/types/css.d.js +3 -0
- package/dist/types/css.d.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utilities/getThemeSettings.d.ts.map +1 -1
- package/dist/utilities/getThemeSettings.js +2 -14
- package/dist/utilities/getThemeSettings.js.map +1 -1
- package/dist/utils/contrast.d.ts +63 -0
- package/dist/utils/contrast.d.ts.map +1 -0
- package/dist/utils/contrast.js +158 -0
- package/dist/utils/contrast.js.map +1 -0
- package/dist/utils/exportTokens.d.ts +30 -0
- package/dist/utils/exportTokens.d.ts.map +1 -0
- package/dist/utils/exportTokens.js +183 -0
- package/dist/utils/exportTokens.js.map +1 -0
- package/dist/utils/generatePalette.d.ts +46 -0
- package/dist/utils/generatePalette.d.ts.map +1 -0
- package/dist/utils/generatePalette.js +127 -0
- package/dist/utils/generatePalette.js.map +1 -0
- package/dist/utils/generateThemeCSS.d.ts.map +1 -1
- package/dist/utils/generateThemeCSS.js +2 -40
- package/dist/utils/generateThemeCSS.js.map +1 -1
- package/dist/utils/livePreviewThemeRuntime.d.ts +22 -3
- package/dist/utils/livePreviewThemeRuntime.d.ts.map +1 -1
- package/dist/utils/livePreviewThemeRuntime.js +48 -15
- package/dist/utils/livePreviewThemeRuntime.js.map +1 -1
- package/dist/utils/localizedText.d.ts +8 -0
- package/dist/utils/localizedText.d.ts.map +1 -0
- package/dist/utils/localizedText.js +21 -0
- package/dist/utils/localizedText.js.map +1 -0
- package/dist/utils/resolveThemeConfiguration.d.ts +60 -1
- package/dist/utils/resolveThemeConfiguration.d.ts.map +1 -1
- package/dist/utils/resolveThemeConfiguration.js +53 -13
- package/dist/utils/resolveThemeConfiguration.js.map +1 -1
- package/dist/utils/themeColors.d.ts +3 -0
- package/dist/utils/themeColors.d.ts.map +1 -1
- package/dist/utils/themeColors.js +30 -1
- package/dist/utils/themeColors.js.map +1 -1
- package/dist/utils/themeHtmlAttributes.d.ts +32 -12
- package/dist/utils/themeHtmlAttributes.d.ts.map +1 -1
- package/dist/utils/themeHtmlAttributes.js +38 -5
- package/dist/utils/themeHtmlAttributes.js.map +1 -1
- package/dist/utils/themeUtils.d.ts +3 -5
- package/dist/utils/themeUtils.d.ts.map +1 -1
- package/dist/utils/themeUtils.js +474 -6
- package/dist/utils/themeUtils.js.map +1 -1
- package/dist/utils/tweakcnConverter.d.ts +1 -3
- package/dist/utils/tweakcnConverter.d.ts.map +1 -1
- package/dist/utils/tweakcnConverter.js +1 -3
- package/dist/utils/tweakcnConverter.js.map +1 -1
- package/package.json +15 -5
- package/dist/components/ThemePreview.d.ts +0 -3
- package/dist/components/ThemePreview.d.ts.map +0 -1
- package/dist/components/ThemePreview.js +0 -321
- package/dist/components/ThemePreview.js.map +0 -1
- package/dist/plugin.d.ts +0 -2
- package/dist/plugin.d.ts.map +0 -1
- package/dist/plugin.js +0 -3
- package/dist/plugin.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,117 +1,98 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @kilivi/payloadcms-theme-management
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Theme management for Payload CMS v3: 60+ presets, a live preview inside the admin, an accessibility audit, and theme CSS variables that are rendered server-side (SSR-ready) for your Next.js front-end. It is built so that a non-technical site owner can restyle a site from the Payload admin without breaking it.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Repository:** [github.com/vitakili/payload-plugins → packages/theme-management](https://github.com/vitakili/payload-plugins/tree/main/packages/theme-management) · [Changelog](./CHANGELOG.md) · [Issues](https://github.com/vitakili/payload-plugins/issues)
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
- **✅ Auto-Populate Light/Dark Colors** - Theme selection hook automatically fills color fields
|
|
9
|
-
- **✅ Multi-Tenant Ready** - Full support for multi-tenant applications with isolated themes
|
|
10
|
-
- **✅ Cache Invalidation** - Automatic cache invalidation after saving appearance settings
|
|
11
|
-
- **✅ Live Theme Preview** - Real-time theme preview in admin panel at `/admin/theme-preview`
|
|
12
|
-
- **✅ Professional Color Picker** - Enhanced color picker with react-colorful library
|
|
13
|
-
- **✅ shadcn/ui Compatible** - Works with https://ui.shadcn.com/themes
|
|
14
|
-
- **✅ TweakCN Compatible** - Works with https://tweakcn.com/editor/theme
|
|
7
|
+
> **Package name:** the plugin is published as **`@kilivi/payloadcms-theme-management`**. Versions 1.x–2.1.x were also published under `@kilivi-dev/payloadcms-theme-management`, which is no longer updated. See [Migrating from `@kilivi-dev`](#migrating-from-kilivi-dev).
|
|
15
8
|
|
|
16
9
|
## Features
|
|
17
10
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
- 🚀 **SSR Theme Injection** - Zero FOUC with server-side rendering
|
|
29
|
-
- ⚡ **Performance Optimized** - Critical CSS inlining, preload links
|
|
30
|
-
- 🎯 **Type Safe** - Full TypeScript support
|
|
31
|
-
- 🔧 **Highly Configurable** - Flexible plugin options
|
|
32
|
-
- 👥 **Multi-Tenant Support** - Built-in multi-tenant capabilities
|
|
11
|
+
- **Theme presets:** 60+ colour themes (including tweakcn and OKLCH styles) plus visual style presets that set effects, component styles, radius and typography.
|
|
12
|
+
- **"Keep my" locks:** lock *Colours*, *Fonts* or *Style* so that picking a theme, applying a style or generating a palette leaves those parts untouched.
|
|
13
|
+
- **One-step undo:** every bulk change (theme, style, generated palette) can be undone from an *Applied: … · Undo* bar.
|
|
14
|
+
- **One live preview:** colours, fonts and component styles in a single site mock-up with device widths and light/dark mode. On wide admin screens it stays in view in a column on the right while you edit.
|
|
15
|
+
- **Palette generator:** a full light and dark palette from one brand colour or an uploaded logo.
|
|
16
|
+
- **WCAG audit:** a live contrast check of the palette in both modes, with a one-click *Fix* for each failing pair. Paired colour fields (for example `primary` and `primaryForeground`) also show their contrast right in the field.
|
|
17
|
+
- **Export and import:** W3C design tokens (JSON), Tailwind v4 `@theme` and Tailwind v3 config. Custom presets can be imported as JSON and appear in the theme picker straight away.
|
|
18
|
+
- **Front-end rendering:** SSR theme injection (`ServerThemeInjector`), `<meta name="theme-color">`, cache revalidation and Payload Live Preview.
|
|
19
|
+
- **Visitor components:** `ColorModeToggle` (light/dark/auto) and `ThemeSwitcher`.
|
|
20
|
+
- **Native to Payload:** uses Payload's theme tokens, so it works in the dark admin, has keyboard-accessible pickers and respects `prefers-reduced-motion`. It ships with English and Czech admin translations, and you can add more.
|
|
33
21
|
|
|
34
22
|
## Installation
|
|
35
23
|
|
|
36
24
|
```bash
|
|
37
25
|
pnpm add @kilivi/payloadcms-theme-management
|
|
38
26
|
# or
|
|
39
|
-
npm
|
|
27
|
+
npm i @kilivi/payloadcms-theme-management
|
|
40
28
|
# or
|
|
41
29
|
yarn add @kilivi/payloadcms-theme-management
|
|
42
30
|
```
|
|
43
31
|
|
|
44
|
-
|
|
32
|
+
**Peer dependencies:**
|
|
45
33
|
|
|
46
|
-
|
|
34
|
+
| Package | Versions |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `payload`, `@payloadcms/ui` | `^3` |
|
|
37
|
+
| `next` | `14` / `15` / `16` |
|
|
38
|
+
| `react` | `18.3+` or `19` |
|
|
39
|
+
| `lucide-react` | `>=0.400` |
|
|
47
40
|
|
|
48
|
-
|
|
41
|
+
## Quick start
|
|
49
42
|
|
|
50
|
-
|
|
43
|
+
### 1) Register the plugin
|
|
44
|
+
|
|
45
|
+
#### A) As a tab in an existing collection (default)
|
|
46
|
+
|
|
47
|
+
```ts
|
|
51
48
|
import { themeManagementPlugin } from '@kilivi/payloadcms-theme-management'
|
|
52
49
|
import { buildConfig } from 'payload'
|
|
53
50
|
|
|
54
51
|
export default buildConfig({
|
|
55
|
-
// ... other config
|
|
56
|
-
|
|
57
52
|
collections: [
|
|
58
53
|
{
|
|
59
54
|
slug: 'site-settings',
|
|
60
|
-
fields: [
|
|
61
|
-
{
|
|
62
|
-
name: 'siteName',
|
|
63
|
-
type: 'text',
|
|
64
|
-
},
|
|
65
|
-
// Theme configuration will be injected here as a tab inside this collection
|
|
66
|
-
],
|
|
55
|
+
fields: [{ name: 'siteName', type: 'text' }],
|
|
67
56
|
},
|
|
68
57
|
],
|
|
69
|
-
|
|
70
58
|
plugins: [
|
|
71
59
|
themeManagementPlugin({
|
|
72
|
-
enabled: true,
|
|
73
60
|
targetCollection: 'site-settings',
|
|
74
61
|
defaultTheme: 'cool',
|
|
75
|
-
|
|
76
|
-
enableLogging: true,
|
|
62
|
+
livePreview: true,
|
|
77
63
|
}),
|
|
78
64
|
],
|
|
79
65
|
})
|
|
80
66
|
```
|
|
81
67
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
#### Option B: Create Standalone Global (Separate Settings)
|
|
68
|
+
#### B) As a standalone appearance global
|
|
85
69
|
|
|
86
|
-
```
|
|
70
|
+
```ts
|
|
87
71
|
import { themeManagementPlugin } from '@kilivi/payloadcms-theme-management'
|
|
88
72
|
import { buildConfig } from 'payload'
|
|
89
73
|
|
|
90
74
|
export default buildConfig({
|
|
91
|
-
// ... other config
|
|
92
|
-
|
|
93
75
|
plugins: [
|
|
94
76
|
themeManagementPlugin({
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
standaloneCollectionLabel: 'Appearance Settings', // Optional: custom label
|
|
77
|
+
useStandaloneCollection: true,
|
|
78
|
+
standaloneCollectionSlug: 'appearance-settings',
|
|
79
|
+
standaloneCollectionLabel: 'Appearance Settings',
|
|
99
80
|
defaultTheme: 'cool',
|
|
100
|
-
|
|
101
|
-
enableLogging: true,
|
|
81
|
+
livePreview: true,
|
|
102
82
|
}),
|
|
103
83
|
],
|
|
104
84
|
})
|
|
105
85
|
```
|
|
106
86
|
|
|
107
|
-
|
|
87
|
+
After adding the plugin, regenerate the admin import map:
|
|
108
88
|
|
|
109
|
-
|
|
89
|
+
```bash
|
|
90
|
+
pnpm payload generate:importmap
|
|
91
|
+
```
|
|
110
92
|
|
|
111
|
-
|
|
93
|
+
### 2) Inject the theme in your Next.js layout
|
|
112
94
|
|
|
113
95
|
```tsx
|
|
114
|
-
// app/layout.tsx
|
|
115
96
|
import { ServerThemeInjector } from '@kilivi/payloadcms-theme-management/server'
|
|
116
97
|
import configPromise from '@payload-config'
|
|
117
98
|
import { getPayload } from 'payload'
|
|
@@ -119,15 +100,14 @@ import { getPayload } from 'payload'
|
|
|
119
100
|
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
120
101
|
const payload = await getPayload({ config: configPromise })
|
|
121
102
|
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
limit: 1,
|
|
103
|
+
const appearance = await payload.findGlobal({
|
|
104
|
+
slug: 'appearance-settings',
|
|
125
105
|
})
|
|
126
106
|
|
|
127
107
|
return (
|
|
128
108
|
<html lang="en">
|
|
129
109
|
<head>
|
|
130
|
-
<ServerThemeInjector themeConfiguration={
|
|
110
|
+
<ServerThemeInjector themeConfiguration={appearance?.themeConfiguration} />
|
|
131
111
|
</head>
|
|
132
112
|
<body>{children}</body>
|
|
133
113
|
</html>
|
|
@@ -135,889 +115,410 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|
|
135
115
|
}
|
|
136
116
|
```
|
|
137
117
|
|
|
138
|
-
|
|
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.
|
|
139
121
|
|
|
140
|
-
|
|
141
|
-
// app/layout.tsx
|
|
142
|
-
import { ServerThemeInjector } from '@kilivi/payloadcms-theme-management/server'
|
|
143
|
-
import configPromise from '@payload-config'
|
|
144
|
-
import { getPayload } from 'payload'
|
|
122
|
+
### 3) Optional: fonts via `next/font` (Next.js best practice)
|
|
145
123
|
|
|
146
|
-
|
|
147
|
-
|
|
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:
|
|
148
127
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
slug: 'appearance-settings', // Your standalone global slug
|
|
152
|
-
})
|
|
128
|
+
```tsx
|
|
129
|
+
import { themeFontVariables } from '@kilivi/payloadcms-theme-management/next-fonts'
|
|
153
130
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
</head>
|
|
159
|
-
<body>{children}</body>
|
|
160
|
-
</html>
|
|
161
|
-
)
|
|
162
|
-
}
|
|
131
|
+
<html lang="en" className={themeFontVariables}>
|
|
132
|
+
<head>
|
|
133
|
+
<ServerThemeInjector themeConfiguration={appearance?.themeConfiguration} fontLoading="none" />
|
|
134
|
+
</head>
|
|
163
135
|
```
|
|
164
136
|
|
|
165
|
-
|
|
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.
|
|
166
140
|
|
|
167
|
-
|
|
141
|
+
`fontLoading` options: `'self-hosted'` (default), `'google'` (Google Fonts CDN),
|
|
142
|
+
`'none'` (you load the faces, e.g. with `next/font`).
|
|
168
143
|
|
|
169
|
-
|
|
144
|
+
## The Appearance Settings tab
|
|
170
145
|
|
|
171
|
-
|
|
172
|
-
// app/layout.tsx
|
|
173
|
-
import {
|
|
174
|
-
createCachedThemeFetcher,
|
|
175
|
-
ServerThemeInjector,
|
|
176
|
-
} from '@kilivi/payloadcms-theme-management/server'
|
|
177
|
-
import configPromise from '@payload-config'
|
|
178
|
-
import { getPayload } from 'payload'
|
|
146
|
+
The tab is ordered as a **presets first, fine-tuning second** workflow:
|
|
179
147
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
148
|
+
1. **Theme selection:**
|
|
149
|
+
- *Keep my: Colours · Fonts · Style* locks
|
|
150
|
+
- the palette generator
|
|
151
|
+
- the collapsible theme preset list
|
|
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.
|
|
157
|
+
- Visual effects
|
|
158
|
+
- Component styles
|
|
159
|
+
- Colour mode (all light and dark tokens, the WCAG audit and export)
|
|
160
|
+
- Custom theme presets (JSON import, for developers)
|
|
161
|
+
- Advanced (animation level, custom CSS)
|
|
190
162
|
|
|
191
|
-
|
|
192
|
-
|
|
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.
|
|
193
166
|
|
|
194
|
-
|
|
195
|
-
<html lang="en">
|
|
196
|
-
<head>
|
|
197
|
-
<ServerThemeInjector themeConfiguration={themeConfiguration} />
|
|
198
|
-
</head>
|
|
199
|
-
<body>{children}</body>
|
|
200
|
-
</html>
|
|
201
|
-
)
|
|
202
|
-
}
|
|
203
|
-
```
|
|
167
|
+
### Locks and undo
|
|
204
168
|
|
|
205
|
-
|
|
169
|
+
Each bulk action honours the locks:
|
|
206
170
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
171
|
+
| Action | Colours | Fonts | Style (effects, components, radius) |
|
|
172
|
+
| --- | --- | --- | --- |
|
|
173
|
+
| Pick a theme preset | writes colours | pins the current preset font instead of switching it | writes the theme's visual effects |
|
|
174
|
+
| Pick a style preset | never | writes the style's typography | writes effects, components, radius, motion and spacing |
|
|
175
|
+
| Generate a palette | writes colours (disabled while locked) | never | never |
|
|
210
176
|
|
|
211
|
-
|
|
177
|
+
- Locks last for the current editing session. They reset on reload and nothing extra is stored in your documents.
|
|
178
|
+
- After each bulk action an *Applied: … · Undo* bar appears for 10 seconds. It pauses while you hover it or it has focus.
|
|
179
|
+
- Undo restores exactly the fields that the action changed.
|
|
212
180
|
|
|
213
|
-
|
|
214
|
-
- Custom slug: `global_{standaloneCollectionSlug}`
|
|
181
|
+
## Live Preview
|
|
215
182
|
|
|
216
|
-
|
|
183
|
+
Payload's Live Preview points at your client pages by slug:
|
|
217
184
|
|
|
218
|
-
|
|
185
|
+
- the `home` slug resolves to `/`
|
|
186
|
+
- any other slug resolves to `/{slug}`
|
|
187
|
+
- a tenant query is appended when one is available
|
|
219
188
|
|
|
220
189
|
```ts
|
|
221
190
|
themeManagementPlugin({
|
|
222
|
-
|
|
223
|
-
cacheRevalidation: {
|
|
224
|
-
routePath: '/theme/revalidate',
|
|
225
|
-
secret: process.env.THEME_REVALIDATE_SECRET,
|
|
226
|
-
tags: ['tenant:default'],
|
|
227
|
-
paths: ['/'],
|
|
228
|
-
},
|
|
191
|
+
livePreview: true,
|
|
229
192
|
})
|
|
230
193
|
```
|
|
231
194
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
> **Note:** The cache invalidation hook is only available for standalone global mode. When using the tab mode (default), implement your own caching strategy based on your collection structure.
|
|
235
|
-
|
|
236
|
-
### 3. Use Theme Variables in Your CSS
|
|
237
|
-
|
|
238
|
-
The plugin injects CSS custom properties you can use:
|
|
239
|
-
|
|
240
|
-
```css
|
|
241
|
-
.my-component {
|
|
242
|
-
background-color: hsl(var(--background));
|
|
243
|
-
color: hsl(var(--foreground));
|
|
244
|
-
border-radius: var(--radius);
|
|
245
|
-
font-family: var(--font-body);
|
|
246
|
-
}
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
Or with Tailwind CSS:
|
|
250
|
-
|
|
251
|
-
```tsx
|
|
252
|
-
<div className="bg-primary text-primary-foreground rounded-lg">Themed Content</div>
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
## Plugin Configuration Options
|
|
256
|
-
|
|
257
|
-
| Option | Type | Default | Description |
|
|
258
|
-
| --------------------------- | ------------------------------------- | ----------------------- | -------------------------------------------------------------------- |
|
|
259
|
-
| `enabled` | `boolean` | `true` | Enable/disable the plugin |
|
|
260
|
-
| `targetCollection` | `string` | `'site-settings'` | Collection slug to add theme field to (when not using standalone) |
|
|
261
|
-
| `useStandaloneCollection` | `boolean` | `false` | Create a separate global instead of adding as a tab |
|
|
262
|
-
| `standaloneCollectionSlug` | `string` | `'appearance-settings'` | Slug for the standalone global |
|
|
263
|
-
| `standaloneCollectionLabel` | `string \| Record<string, string>` | `'Appearance Settings'` | Label for the standalone global (supports i18n) |
|
|
264
|
-
| `themePresets` | `ThemePreset[]` | Built-in presets | Custom theme presets |
|
|
265
|
-
| `defaultTheme` | `string` | `'cool'` | Default theme preset name |
|
|
266
|
-
| `includeColorModeToggle` | `boolean` | `true` | Show light/dark mode toggle |
|
|
267
|
-
| `includeCustomCSS` | `boolean` | `true` | Allow custom CSS injection |
|
|
268
|
-
| `includeBrandIdentity` | `boolean` | `false` | Show brand identity fields |
|
|
269
|
-
| `enableAdvancedFeatures` | `boolean` | `true` | Enable advanced customization |
|
|
270
|
-
| `enableLogging` | `boolean` | `false` | Log plugin actions to console (includes cache invalidation logs) |
|
|
271
|
-
| `livePreview` | `boolean \| LivePreviewOptions` | `true` | Enable/administer live preview URL resolution (`home` → fallback) |
|
|
272
|
-
| `cacheRevalidation` | `boolean \| CacheRevalidationOptions` | `standalone: true` | Configure injected route + tags/paths for Next.js cache invalidation |
|
|
273
|
-
|
|
274
|
-
**Cache Invalidation Note:** In standalone mode, the plugin revalidates `global_{standaloneCollectionSlug}` automatically and injects `POST /api/theme/revalidate` by default. Pair it with `createCachedThemeFetcher` from `@kilivi/payloadcms-theme-management/server` for a zero-boilerplate setup.
|
|
275
|
-
|
|
276
|
-
## Available Theme Presets
|
|
277
|
-
|
|
278
|
-
- **Cool** - Professional blue theme
|
|
279
|
-
- **Brutal** - High contrast, bold design
|
|
280
|
-
- **Neon** - Vibrant, energetic colors
|
|
281
|
-
- **Solar** - Warm, golden tones
|
|
282
|
-
- **Dealership** - Automotive-inspired
|
|
283
|
-
- **Real Estate** - Professional property theme (+ Gold, Neutral variants)
|
|
284
|
-
|
|
285
|
-
## Live Theme Preview
|
|
286
|
-
|
|
287
|
-
The plugin automatically adds a **Live Preview** page to your admin panel at `/admin/theme-preview`.
|
|
288
|
-
|
|
289
|
-
### Features:
|
|
290
|
-
|
|
291
|
-
- ✅ **Zero Configuration** - Works automatically when plugin is installed
|
|
292
|
-
- ✅ **Real-time Updates** - See changes instantly as you edit theme settings
|
|
293
|
-
- ✅ **Light/Dark Toggle** - Preview both modes side-by-side
|
|
294
|
-
- ✅ **Component Showcase** - View cards, buttons, inputs, badges, and more
|
|
295
|
-
- ✅ **Professional UI** - Clean, modern preview interface
|
|
296
|
-
|
|
297
|
-
### How to Access:
|
|
298
|
-
|
|
299
|
-
1. Install the plugin (see Quick Start above)
|
|
300
|
-
2. Navigate to `/admin/theme-preview` in your Payload admin panel
|
|
301
|
-
3. Open theme settings in another tab and see changes update live!
|
|
302
|
-
|
|
303
|
-
The preview automatically watches your `themeConfiguration` field and displays real-time updates without any additional setup.
|
|
304
|
-
|
|
305
|
-
## Translations / i18n
|
|
306
|
-
|
|
307
|
-
Extend or override translations at runtime with `registerTranslations`. The plugin merges your translations with English defaults, so missing keys fall back to English. See `docs/TRANSLATIONS.md` for details and examples.
|
|
308
|
-
|
|
309
|
-
## API Reference
|
|
195
|
+
### Advanced setup (with the injected endpoint)
|
|
310
196
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
} from '@kilivi/payloadcms-theme-management'
|
|
329
|
-
// Types
|
|
330
|
-
import type {
|
|
331
|
-
ThemeDefaults,
|
|
332
|
-
ThemeManagementPluginOptions,
|
|
333
|
-
ThemePreset,
|
|
334
|
-
} from '@kilivi/payloadcms-theme-management'
|
|
197
|
+
```ts
|
|
198
|
+
themeManagementPlugin({
|
|
199
|
+
livePreview: {
|
|
200
|
+
enabled: true,
|
|
201
|
+
injectRoute: true,
|
|
202
|
+
routePath: '/theme/preview',
|
|
203
|
+
pageCollection: 'pages',
|
|
204
|
+
pageSlug: 'home',
|
|
205
|
+
fallbackToFirstPage: true,
|
|
206
|
+
tenantField: 'tenant',
|
|
207
|
+
tenantQueryParam: 'tenant',
|
|
208
|
+
breakpoints: [
|
|
209
|
+
{ name: 'tablet', label: 'Tablet', width: 1024, height: 768 },
|
|
210
|
+
{ name: 'desktop', label: 'Desktop', width: 1440, height: 900 },
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
})
|
|
335
214
|
```
|
|
336
215
|
|
|
337
|
-
|
|
216
|
+
With `livePreview.injectRoute` enabled, the plugin adds `GET /api/theme/preview` (or your custom `routePath`).
|
|
338
217
|
|
|
339
|
-
|
|
218
|
+
- **Query parameters:**
|
|
219
|
+
- `pageSlug`
|
|
220
|
+
- `previewSecret` (or `preview`)
|
|
221
|
+
- `tenant`
|
|
222
|
+
- **Secret:** read from `PREVIEW_SECRET` or `PAYLOAD_PREVIEW_SECRET`.
|
|
223
|
+
- **Responses:**
|
|
224
|
+
- `307` redirects to the resolved client page
|
|
225
|
+
- `401` means a secret is configured but is missing or wrong
|
|
340
226
|
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
getThemeCSS,
|
|
345
|
-
ServerThemeInjector,
|
|
346
|
-
} from '@kilivi/payloadcms-theme-management/server'
|
|
227
|
+
```txt
|
|
228
|
+
/api/theme/preview?pageSlug=home&previewSecret=your-secret → /
|
|
229
|
+
/api/theme/preview?pageSlug=home&tenant=acme&previewSecret=your-secret → /?tenant=acme
|
|
347
230
|
```
|
|
348
231
|
|
|
349
|
-
|
|
232
|
+
**Testing tip:** preview is most useful when the previewed page uses many theme tokens: buttons, cards, banners, links and several heading levels. The repository's `dev-local` app includes `pnpm seed:theme`, which fills the `home` page with blocks like these.
|
|
350
233
|
|
|
351
|
-
###
|
|
234
|
+
### Multi-tenant example
|
|
352
235
|
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
236
|
+
```ts
|
|
237
|
+
themeManagementPlugin({
|
|
238
|
+
useStandaloneCollection: true,
|
|
239
|
+
standaloneCollectionSlug: 'appearance-settings',
|
|
240
|
+
livePreview: {
|
|
241
|
+
enabled: true,
|
|
242
|
+
injectRoute: true,
|
|
243
|
+
routePath: '/theme/preview',
|
|
244
|
+
pageCollection: 'pages',
|
|
245
|
+
pageSlug: 'home',
|
|
246
|
+
tenantField: 'tenant',
|
|
247
|
+
tenantQueryParam: 'tenant',
|
|
248
|
+
},
|
|
249
|
+
cacheRevalidation: {
|
|
250
|
+
enabled: true,
|
|
251
|
+
injectRoute: true,
|
|
252
|
+
routePath: '/theme/revalidate',
|
|
253
|
+
secret: process.env.THEME_REVALIDATE_SECRET,
|
|
254
|
+
tags: ['tenant:acme'],
|
|
255
|
+
paths: ['/'],
|
|
256
|
+
},
|
|
257
|
+
})
|
|
360
258
|
```
|
|
361
259
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
### With Tailwind CSS
|
|
260
|
+
See [MULTI_TENANT_GUIDE.md](./MULTI_TENANT_GUIDE.md) for shared vs per-tenant setups.
|
|
365
261
|
|
|
366
|
-
|
|
262
|
+
## Cache revalidation
|
|
367
263
|
|
|
368
|
-
|
|
369
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
370
|
-
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
371
|
-
/* eslint-disable import/no-anonymous-default-export */
|
|
372
|
-
import defaultTheme from 'tailwindcss/defaultTheme'
|
|
264
|
+
When `useStandaloneCollection: true`, the default cache tag is `global_{standaloneCollectionSlug}`. The plugin can inject `POST /api/theme/revalidate`:
|
|
373
265
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
'
|
|
381
|
-
|
|
382
|
-
'
|
|
383
|
-
|
|
384
|
-
safelist: [
|
|
385
|
-
// Grid columns
|
|
386
|
-
'col-span-4',
|
|
387
|
-
'md:col-span-2',
|
|
388
|
-
'lg:col-span-4',
|
|
389
|
-
'lg:col-span-6',
|
|
390
|
-
'lg:col-span-8',
|
|
391
|
-
'lg:col-span-12',
|
|
392
|
-
// Border colors
|
|
393
|
-
'border-border',
|
|
394
|
-
'border-error',
|
|
395
|
-
'border-success',
|
|
396
|
-
'border-warning',
|
|
397
|
-
// Background colors
|
|
398
|
-
'bg-card',
|
|
399
|
-
'bg-error/30',
|
|
400
|
-
'bg-success/30',
|
|
401
|
-
'bg-warning/30',
|
|
402
|
-
],
|
|
403
|
-
theme: {
|
|
404
|
-
container: {
|
|
405
|
-
center: true,
|
|
406
|
-
padding: {
|
|
407
|
-
DEFAULT: 'var(--layout-containerPadding)',
|
|
408
|
-
md: 'var(--layout-containerPaddingTablet)',
|
|
409
|
-
lg: 'var(--layout-containerPaddingDesktop)',
|
|
410
|
-
},
|
|
411
|
-
screens: {
|
|
412
|
-
sm: '640px',
|
|
413
|
-
md: '768px',
|
|
414
|
-
lg: '1024px',
|
|
415
|
-
xl: '1280px',
|
|
416
|
-
},
|
|
417
|
-
},
|
|
418
|
-
extend: {
|
|
419
|
-
colors: {
|
|
420
|
-
background: 'var(--background)',
|
|
421
|
-
foreground: 'var(--foreground)',
|
|
422
|
-
card: 'var(--card)',
|
|
423
|
-
'card-foreground': 'var(--card-foreground)',
|
|
424
|
-
popover: 'var(--popover)',
|
|
425
|
-
'popover-foreground': 'var(--popover-foreground)',
|
|
426
|
-
primary: 'var(--primary)',
|
|
427
|
-
'primary-foreground': 'var(--primary-foreground)',
|
|
428
|
-
secondary: 'var(--secondary)',
|
|
429
|
-
'secondary-foreground': 'var(--secondary-foreground)',
|
|
430
|
-
muted: 'var(--muted)',
|
|
431
|
-
'muted-foreground': 'var(--muted-foreground)',
|
|
432
|
-
accent: 'var(--accent)',
|
|
433
|
-
'accent-foreground': 'var(--accent-foreground)',
|
|
434
|
-
destructive: 'var(--destructive)',
|
|
435
|
-
'destructive-foreground': 'var(--destructive-foreground)',
|
|
436
|
-
border: 'var(--border)',
|
|
437
|
-
input: 'var(--input)',
|
|
438
|
-
ring: 'var(--ring)',
|
|
439
|
-
success: 'var(--success)',
|
|
440
|
-
'success-foreground': 'var(--success-foreground)',
|
|
441
|
-
warning: 'var(--warning)',
|
|
442
|
-
'warning-foreground': 'var(--warning-foreground)',
|
|
443
|
-
error: 'var(--error)',
|
|
444
|
-
'error-foreground': 'var(--error-foreground)',
|
|
445
|
-
info: 'var(--info)',
|
|
446
|
-
'info-foreground': 'var(--info-foreground)',
|
|
447
|
-
},
|
|
448
|
-
borderRadius: {
|
|
449
|
-
sm: 'var(--radius-small)',
|
|
450
|
-
DEFAULT: 'var(--radius-default)',
|
|
451
|
-
md: 'var(--radius-medium)',
|
|
452
|
-
lg: 'var(--radius-large)',
|
|
453
|
-
},
|
|
454
|
-
fontFamily: {
|
|
455
|
-
sans: ['var(--typography-fontFamily)', ...defaultTheme.fontFamily.sans],
|
|
456
|
-
heading: ['var(--typography-headingFamily)', ...defaultTheme.fontFamily.sans],
|
|
457
|
-
mono: ['var(--font-geist-mono)', ...defaultTheme.fontFamily.mono],
|
|
458
|
-
},
|
|
459
|
-
fontSize: {
|
|
460
|
-
base: ['var(--typography-baseFontSize)', { lineHeight: 'var(--typography-lineHeight)' }],
|
|
461
|
-
},
|
|
462
|
-
letterSpacing: {
|
|
463
|
-
tight: 'var(--typography-letterSpacing-tight)',
|
|
464
|
-
normal: 'var(--typography-letterSpacing-normal)',
|
|
465
|
-
wide: 'var(--typography-letterSpacing-wide)',
|
|
466
|
-
},
|
|
467
|
-
fontWeight: {
|
|
468
|
-
normal: 'var(--typography-fontWeights-normal)',
|
|
469
|
-
medium: 'var(--typography-fontWeights-medium)',
|
|
470
|
-
semibold: 'var(--typography-fontWeights-semibold)',
|
|
471
|
-
bold: 'var(--typography-fontWeights-bold)',
|
|
472
|
-
},
|
|
473
|
-
spacing: {
|
|
474
|
-
section: {
|
|
475
|
-
DEFAULT: 'var(--layout-sectionSpacing)',
|
|
476
|
-
md: 'var(--layout-sectionSpacingTablet)',
|
|
477
|
-
lg: 'var(--layout-sectionSpacingDesktop)',
|
|
478
|
-
},
|
|
479
|
-
},
|
|
480
|
-
transitionProperty: {
|
|
481
|
-
button: 'var(--components-button-transition)',
|
|
482
|
-
},
|
|
483
|
-
scale: {
|
|
484
|
-
'button-hover': 'var(--components-button-hover-scale)',
|
|
485
|
-
},
|
|
486
|
-
opacity: {
|
|
487
|
-
'button-hover': 'var(--components-button-hover-opacity)',
|
|
488
|
-
},
|
|
489
|
-
boxShadow: {
|
|
490
|
-
card: 'var(--components-card-shadow)',
|
|
491
|
-
},
|
|
492
|
-
padding: {
|
|
493
|
-
card: 'var(--components-card-padding)',
|
|
494
|
-
button: 'var(--components-button-padding)',
|
|
495
|
-
input: 'var(--components-input-padding)',
|
|
496
|
-
},
|
|
497
|
-
height: {
|
|
498
|
-
input: 'var(--components-input-height)',
|
|
499
|
-
},
|
|
500
|
-
typography: ({ theme }) => ({
|
|
501
|
-
DEFAULT: {
|
|
502
|
-
css: {
|
|
503
|
-
'--tw-prose-body': theme('colors.foreground'),
|
|
504
|
-
'--tw-prose-headings': theme('colors.foreground'),
|
|
505
|
-
'--tw-prose-lead': theme('colors.muted.foreground'),
|
|
506
|
-
'--tw-prose-links': theme('colors.primary'),
|
|
507
|
-
'--tw-prose-bold': theme('colors.foreground'),
|
|
508
|
-
'--tw-prose-counters': theme('colors.foreground'),
|
|
509
|
-
'--tw-prose-bullets': theme('colors.foreground'),
|
|
510
|
-
'--tw-prose-hr': theme('colors.border'),
|
|
511
|
-
'--tw-prose-quotes': theme('colors.foreground'),
|
|
512
|
-
'--tw-prose-quote-borders': theme('colors.border'),
|
|
513
|
-
'--tw-prose-captions': theme('colors.muted.foreground'),
|
|
514
|
-
'--tw-prose-code': theme('colors.foreground'),
|
|
515
|
-
'--tw-prose-pre-code': theme('colors.foreground'),
|
|
516
|
-
'--tw-prose-pre-bg': theme('colors.muted'),
|
|
517
|
-
'--tw-prose-th-borders': theme('colors.border'),
|
|
518
|
-
'--tw-prose-td-borders': theme('colors.border'),
|
|
519
|
-
h1: {
|
|
520
|
-
fontWeight: 'var(--typography-fontWeights-bold)',
|
|
521
|
-
marginBottom: '0.25em',
|
|
522
|
-
fontFamily: theme('fontFamily.heading'),
|
|
523
|
-
letterSpacing: 'var(--typography-letterSpacing-tight)',
|
|
524
|
-
},
|
|
525
|
-
h2: {
|
|
526
|
-
fontFamily: theme('fontFamily.heading'),
|
|
527
|
-
fontWeight: 'var(--typography-fontWeights-semibold)',
|
|
528
|
-
letterSpacing: 'var(--typography-letterSpacing-tight)',
|
|
529
|
-
},
|
|
530
|
-
h3: {
|
|
531
|
-
fontFamily: theme('fontFamily.heading'),
|
|
532
|
-
fontWeight: 'var(--typography-fontWeights-semibold)',
|
|
533
|
-
letterSpacing: 'var(--typography-letterSpacing-tight)',
|
|
534
|
-
},
|
|
535
|
-
h4: {
|
|
536
|
-
fontFamily: theme('fontFamily.heading'),
|
|
537
|
-
fontWeight: 'var(--typography-fontWeights-medium)',
|
|
538
|
-
letterSpacing: 'var(--typography-letterSpacing-normal)',
|
|
539
|
-
},
|
|
540
|
-
},
|
|
541
|
-
},
|
|
542
|
-
base: {
|
|
543
|
-
css: [
|
|
544
|
-
{
|
|
545
|
-
h1: { fontSize: '2.5rem' },
|
|
546
|
-
h2: { fontSize: '1.25rem' },
|
|
547
|
-
},
|
|
548
|
-
],
|
|
549
|
-
},
|
|
550
|
-
md: {
|
|
551
|
-
css: [
|
|
552
|
-
{
|
|
553
|
-
h1: { fontSize: '3.5rem' },
|
|
554
|
-
h2: { fontSize: '1.5rem' },
|
|
555
|
-
},
|
|
556
|
-
],
|
|
557
|
-
},
|
|
558
|
-
invert: {
|
|
559
|
-
css: {
|
|
560
|
-
'--tw-prose-body': 'var(--foreground)',
|
|
561
|
-
'--tw-prose-headings': 'var(--foreground)',
|
|
562
|
-
'--tw-prose-lead': 'var(--muted-foreground)',
|
|
563
|
-
'--tw-prose-links': 'var(--primary)',
|
|
564
|
-
'--tw-prose-bold': 'var(--foreground)',
|
|
565
|
-
'--tw-prose-counters': 'var(--foreground)',
|
|
566
|
-
'--tw-prose-bullets': 'var(--foreground)',
|
|
567
|
-
'--tw-prose-hr': 'var(--border)',
|
|
568
|
-
'--tw-prose-quotes': 'var(--foreground)',
|
|
569
|
-
'--tw-prose-quote-borders': 'var(--border)',
|
|
570
|
-
'--tw-prose-captions': 'var(--muted-foreground)',
|
|
571
|
-
'--tw-prose-code': 'var(--foreground)',
|
|
572
|
-
'--tw-prose-pre-code': 'var(--foreground)',
|
|
573
|
-
'--tw-prose-pre-bg': 'var(--muted)',
|
|
574
|
-
'--tw-prose-th-borders': 'var(--border)',
|
|
575
|
-
'--tw-prose-td-borders': 'var(--border)',
|
|
576
|
-
},
|
|
577
|
-
},
|
|
578
|
-
}),
|
|
579
|
-
animation: {
|
|
580
|
-
'accordion-down': 'accordion-down 0.2s ease-out',
|
|
581
|
-
'accordion-up': 'accordion-up 0.2s ease-out',
|
|
582
|
-
},
|
|
583
|
-
keyframes: {
|
|
584
|
-
'accordion-down': {
|
|
585
|
-
from: { height: 0 },
|
|
586
|
-
to: { height: 'var(--radix-accordion-content-height)' },
|
|
587
|
-
},
|
|
588
|
-
'accordion-up': {
|
|
589
|
-
from: { height: 'var(--radix-accordion-content-height)' },
|
|
590
|
-
to: { height: 0 },
|
|
591
|
-
},
|
|
592
|
-
},
|
|
593
|
-
},
|
|
266
|
+
```ts
|
|
267
|
+
themeManagementPlugin({
|
|
268
|
+
useStandaloneCollection: true,
|
|
269
|
+
cacheRevalidation: {
|
|
270
|
+
enabled: true,
|
|
271
|
+
injectRoute: true,
|
|
272
|
+
routePath: '/theme/revalidate',
|
|
273
|
+
secret: process.env.THEME_REVALIDATE_SECRET,
|
|
274
|
+
tags: ['tenant:default'],
|
|
275
|
+
paths: ['/'],
|
|
594
276
|
},
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
#### globals.css
|
|
600
|
-
|
|
601
|
-
```css
|
|
602
|
-
@import 'tailwindcss';
|
|
603
|
-
|
|
604
|
-
@config '../../../../../tailwind.config.mjs';
|
|
605
|
-
|
|
606
|
-
@layer base {
|
|
607
|
-
/* Theme Configuration CSS Variables */
|
|
608
|
-
:root {
|
|
609
|
-
/* Font Scale Presets */
|
|
610
|
-
--font-scale-small: 0.875;
|
|
611
|
-
--font-scale-medium: 1;
|
|
612
|
-
--font-scale-large: 1.125;
|
|
613
|
-
--font-scale-xl: 1.25;
|
|
614
|
-
|
|
615
|
-
/* Spacing Presets */
|
|
616
|
-
--spacing-compact: 0.75;
|
|
617
|
-
--spacing-comfortable: 1;
|
|
618
|
-
--spacing-spacious: 1.25;
|
|
619
|
-
--spacing-xl: 1.5;
|
|
620
|
-
|
|
621
|
-
/* Animation Levels */
|
|
622
|
-
--animation-none-duration: 0ms;
|
|
623
|
-
--animation-reduced-duration: 100ms;
|
|
624
|
-
--animation-normal-duration: 200ms;
|
|
625
|
-
--animation-enhanced-duration: 300ms;
|
|
626
|
-
|
|
627
|
-
/* Dynamic theme configuration (set by JS) */
|
|
628
|
-
--theme-font-scale: var(--font-scale-medium);
|
|
629
|
-
--theme-spacing: var(--spacing-comfortable);
|
|
630
|
-
--theme-animation-level: normal;
|
|
631
|
-
|
|
632
|
-
/* Apply spacing multiplier to layout */
|
|
633
|
-
--layout-containerPadding-scaled: calc(
|
|
634
|
-
var(--layout-containerPadding) * var(--theme-spacing, 1)
|
|
635
|
-
);
|
|
636
|
-
--layout-containerPaddingTablet-scaled: calc(
|
|
637
|
-
var(--layout-containerPaddingTablet) * var(--theme-spacing, 1)
|
|
638
|
-
);
|
|
639
|
-
--layout-containerPaddingDesktop-scaled: calc(
|
|
640
|
-
var(--layout-containerPaddingDesktop) * var(--theme-spacing, 1)
|
|
641
|
-
);
|
|
642
|
-
--layout-sectionSpacing-scaled: calc(var(--layout-sectionSpacing) * var(--theme-spacing, 1));
|
|
643
|
-
--layout-sectionSpacingTablet-scaled: calc(
|
|
644
|
-
var(--layout-sectionSpacingTablet) * var(--theme-spacing, 1)
|
|
645
|
-
);
|
|
646
|
-
--layout-sectionSpacingDesktop-scaled: calc(
|
|
647
|
-
var(--layout-sectionSpacingDesktop) * var(--theme-spacing, 1)
|
|
648
|
-
);
|
|
649
|
-
--components-card-padding-scaled: calc(
|
|
650
|
-
var(--components-card-padding) * var(--theme-spacing, 1)
|
|
651
|
-
);
|
|
652
|
-
--components-button-padding-scaled: var(--components-button-padding);
|
|
653
|
-
--components-input-padding-scaled: var(--components-input-padding);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
*,
|
|
657
|
-
*::before,
|
|
658
|
-
*::after {
|
|
659
|
-
box-sizing: border-box;
|
|
660
|
-
margin: 0;
|
|
661
|
-
padding: 0;
|
|
662
|
-
@apply border-border;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
html {
|
|
666
|
-
-webkit-text-size-adjust: 100%;
|
|
667
|
-
font-size: calc(var(--typography-baseFontSize, 16px) * var(--theme-font-scale, 1));
|
|
668
|
-
line-height: var(--typography-lineHeight, 1.5);
|
|
669
|
-
font-family: var(--typography-fontFamily, system-ui, sans-serif);
|
|
670
|
-
visibility: hidden;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/* Animation level controls */
|
|
674
|
-
[data-animation-level='none'] * {
|
|
675
|
-
animation-duration: var(--animation-none-duration) !important;
|
|
676
|
-
transition-duration: var(--animation-none-duration) !important;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
[data-animation-level='reduced'] * {
|
|
680
|
-
animation-duration: var(--animation-reduced-duration) !important;
|
|
681
|
-
transition-duration: var(--animation-reduced-duration) !important;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
[data-animation-level='normal'] * {
|
|
685
|
-
animation-duration: var(--animation-normal-duration);
|
|
686
|
-
transition-duration: var(--animation-normal-duration);
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
[data-animation-level='enhanced'] * {
|
|
690
|
-
animation-duration: var(--animation-enhanced-duration);
|
|
691
|
-
transition-duration: var(--animation-enhanced-duration);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
h1,
|
|
695
|
-
h2,
|
|
696
|
-
h3,
|
|
697
|
-
h4,
|
|
698
|
-
h5,
|
|
699
|
-
h6 {
|
|
700
|
-
font-family: var(
|
|
701
|
-
--typography-headingFamily,
|
|
702
|
-
var(--typography-fontFamily, system-ui, sans-serif)
|
|
703
|
-
);
|
|
704
|
-
font-weight: var(--typography-fontWeights-bold, 700);
|
|
705
|
-
line-height: 1.2;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
/* Prevent flash of unstyled content except for Storybook */
|
|
709
|
-
:root:not([data-theme]):not([data-theme-mode]):not(.sb-show-main) {
|
|
710
|
-
visibility: hidden;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
:root[data-theme][data-theme-mode],
|
|
714
|
-
.sb-show-main {
|
|
715
|
-
visibility: visible;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
html:not(.sb-show-main) {
|
|
719
|
-
opacity: 0;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
html[data-theme-mode='dark'],
|
|
723
|
-
html[data-theme-mode='light'],
|
|
724
|
-
html.sb-show-main {
|
|
725
|
-
opacity: 1;
|
|
726
|
-
transition: opacity 0ms;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
/* Force immediate opacity when JS is disabled */
|
|
730
|
-
html:not([data-theme-mode]) {
|
|
731
|
-
opacity: 1;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
/* Media queries for responsive layout */
|
|
735
|
-
@media (min-width: 640px) {
|
|
736
|
-
:root {
|
|
737
|
-
--layout-containerPadding: var(--layout-containerPaddingTablet, 2rem);
|
|
738
|
-
--layout-sectionSpacing: var(--layout-sectionSpacingTablet, 3rem);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
@media (min-width: 1024px) {
|
|
743
|
-
:root {
|
|
744
|
-
--layout-containerPadding: var(--layout-containerPaddingDesktop, 2rem);
|
|
745
|
-
--layout-sectionSpacing: var(--layout-sectionSpacingDesktop, 4rem);
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
/* Ensure dark mode styles take precedence */
|
|
750
|
-
html[data-theme-mode='dark'] {
|
|
751
|
-
color-scheme: dark;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
/* Native View Transitions API - React 19 */
|
|
755
|
-
@view-transition {
|
|
756
|
-
navigation: auto;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
/* Smooth fade transition for page navigation */
|
|
760
|
-
::view-transition-old(root),
|
|
761
|
-
::view-transition-new(root) {
|
|
762
|
-
animation-duration: 0.3s;
|
|
763
|
-
animation-timing-function: ease-in-out;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
::view-transition-old(root) {
|
|
767
|
-
animation-name: fade-out;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
::view-transition-new(root) {
|
|
771
|
-
animation-name: fade-in;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
@keyframes fade-out {
|
|
775
|
-
to {
|
|
776
|
-
opacity: 0;
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
@keyframes fade-in {
|
|
781
|
-
from {
|
|
782
|
-
opacity: 0;
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
/* Respect user's reduced motion preference */
|
|
787
|
-
@media (prefers-reduced-motion: reduce) {
|
|
788
|
-
::view-transition-old(root),
|
|
789
|
-
::view-transition-new(root) {
|
|
790
|
-
animation-duration: 0.01ms !important;
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
}
|
|
277
|
+
})
|
|
794
278
|
```
|
|
795
279
|
|
|
796
|
-
###
|
|
797
|
-
|
|
798
|
-
The recommended way to fetch theme configuration is using Payload's native API with Next.js caching:
|
|
280
|
+
### Next.js server caching helper
|
|
799
281
|
|
|
800
|
-
```
|
|
282
|
+
```tsx
|
|
283
|
+
import {
|
|
284
|
+
createCachedThemeFetcher,
|
|
285
|
+
ServerThemeInjector,
|
|
286
|
+
} from '@kilivi/payloadcms-theme-management/server'
|
|
801
287
|
import configPromise from '@payload-config'
|
|
802
288
|
import { getPayload } from 'payload'
|
|
803
|
-
import { unstable_cache } from 'next/cache'
|
|
804
289
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
290
|
+
const getCachedTheme = createCachedThemeFetcher({
|
|
291
|
+
globalSlug: 'appearance-settings',
|
|
292
|
+
revalidate: 3600,
|
|
293
|
+
loadAppearanceSettings: async () => {
|
|
808
294
|
const payload = await getPayload({ config: configPromise })
|
|
809
|
-
|
|
810
|
-
slug: 'appearance-settings', // or 'your-global-slug'
|
|
811
|
-
})
|
|
812
|
-
return global?.themeConfiguration
|
|
813
|
-
},
|
|
814
|
-
['appearance-settings'],
|
|
815
|
-
{
|
|
816
|
-
tags: ['global_appearance-settings'],
|
|
817
|
-
revalidate: 3600, // Revalidate every hour
|
|
295
|
+
return payload.findGlobal({ slug: 'appearance-settings' })
|
|
818
296
|
},
|
|
819
|
-
)
|
|
297
|
+
})
|
|
820
298
|
|
|
821
|
-
// Use in your layout
|
|
822
299
|
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
823
300
|
const themeConfiguration = await getCachedTheme()
|
|
824
|
-
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<html lang="en">
|
|
304
|
+
<head>
|
|
305
|
+
<ServerThemeInjector themeConfiguration={themeConfiguration} />
|
|
306
|
+
</head>
|
|
307
|
+
<body>{children}</body>
|
|
308
|
+
</html>
|
|
309
|
+
)
|
|
825
310
|
}
|
|
826
311
|
```
|
|
827
312
|
|
|
828
|
-
|
|
313
|
+
### Fetching the configuration
|
|
829
314
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
```typescript
|
|
315
|
+
```ts
|
|
833
316
|
import { fetchThemeConfiguration } from '@kilivi/payloadcms-theme-management'
|
|
834
317
|
|
|
835
|
-
//
|
|
836
|
-
const
|
|
318
|
+
// Single tenant
|
|
319
|
+
const themeConfiguration = await fetchThemeConfiguration({
|
|
320
|
+
useGlobal: true,
|
|
837
321
|
collectionSlug: 'appearance-settings',
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
// Multi-tenant
|
|
325
|
+
const tenantThemeConfiguration = await fetchThemeConfiguration({
|
|
838
326
|
useGlobal: true,
|
|
327
|
+
collectionSlug: 'appearance-settings',
|
|
328
|
+
tenantSlug: 'acme',
|
|
839
329
|
})
|
|
840
330
|
```
|
|
841
331
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
332
|
+
## Plugin options
|
|
333
|
+
|
|
334
|
+
| Option | Type | Default | Description |
|
|
335
|
+
| --- | --- | --- | --- |
|
|
336
|
+
| `enabled` | `boolean` | `true` | Enables or disables the plugin |
|
|
337
|
+
| `targetCollection` | `string` | `'site-settings'` | Collection slug for tab mode |
|
|
338
|
+
| `useStandaloneCollection` | `boolean` | `false` | Creates a standalone global instead of injecting a tab |
|
|
339
|
+
| `standaloneCollectionSlug` | `string` | `'appearance-settings'` | Global slug in standalone mode |
|
|
340
|
+
| `standaloneCollectionLabel` | `string \| Record<string, string>` | translated label | Label of the standalone global |
|
|
341
|
+
| `themePresets` | `ThemePreset[]` | built-in presets | Custom preset list |
|
|
342
|
+
| `defaultTheme` | `string` | `'cool'` | Default preset name |
|
|
343
|
+
| `includeColorModeToggle` | `boolean` | `true` | Colour mode section (light/dark tokens, audit, export) |
|
|
344
|
+
| `includeCustomCSS` | `boolean` | `true` | Enables the custom CSS field |
|
|
345
|
+
| `enableAdvancedFeatures` | `boolean` | `true` | Advanced controls such as animation level |
|
|
346
|
+
| `customThemeConfigurationFields` | `Field[]` | `undefined` | Extra fields appended inside the `themeConfiguration` group |
|
|
347
|
+
| `customThemeConfigurationSections` | `Array<{ label; fields; initCollapsed?; description? }>` | `undefined` | Extra collapsible sections inside the group |
|
|
348
|
+
| `enableLogging` | `boolean` | `false` | Logs plugin actions |
|
|
349
|
+
| `livePreview` | `boolean \| ThemeManagementLivePreviewOptions` | `true` | Live preview URL behaviour |
|
|
350
|
+
| `cacheRevalidation` | `boolean \| ThemeManagementCacheRevalidationOptions` | on in standalone mode | Cache invalidation endpoint, tags and paths |
|
|
351
|
+
| `i18n` | `ThemeManagementI18nOptions` | `undefined` | Extend or override admin translations |
|
|
352
|
+
| `includeBrandIdentity` | `boolean` | `false` | Reserved for future use |
|
|
353
|
+
|
|
354
|
+
### `livePreview` options
|
|
355
|
+
|
|
356
|
+
| Option | Type | Default |
|
|
357
|
+
| --- | --- | --- |
|
|
358
|
+
| `enabled` | `boolean` | `true` |
|
|
359
|
+
| `injectRoute` | `boolean` | `false` |
|
|
360
|
+
| `routePath` | `string` | `'/theme/preview'` |
|
|
361
|
+
| `pageCollection` | `string` | `'pages'` |
|
|
362
|
+
| `pageSlug` | `string` | `'home'` |
|
|
363
|
+
| `fallbackToFirstPage` | `boolean` | `true` |
|
|
364
|
+
| `tenantField` | `string` | `'tenant'` |
|
|
365
|
+
| `tenantQueryParam` | `string` | `'tenant'` |
|
|
366
|
+
| `breakpoints` | `Array<{ name; label; width; height }>` | `undefined` |
|
|
367
|
+
| `url` | `(args) => string \| Promise<string>` | slug-based URL |
|
|
368
|
+
|
|
369
|
+
### `cacheRevalidation` options
|
|
370
|
+
|
|
371
|
+
| Option | Type | Default |
|
|
372
|
+
| --- | --- | --- |
|
|
373
|
+
| `enabled` | `boolean` | `true` in standalone mode, otherwise `false` |
|
|
374
|
+
| `injectRoute` | `boolean` | `true` |
|
|
375
|
+
| `routePath` | `string` | `'/theme/revalidate'` |
|
|
376
|
+
| `secret` | `string` | `undefined` |
|
|
377
|
+
| `tags` | `string[]` | `[global_{slug}]` |
|
|
378
|
+
| `paths` | `string[]` | `[]` |
|
|
379
|
+
|
|
380
|
+
## Internationalization
|
|
381
|
+
|
|
382
|
+
The plugin ships with **English (`en`)** and **Czech (`cs`)** admin translations. It registers them in Payload's native `config.i18n` under the `theme-management` namespace.
|
|
383
|
+
|
|
384
|
+
- Every admin string follows the active admin language, including field labels, the pickers, the audit, the import drawer and the font guides.
|
|
385
|
+
- Localised label objects (`{ en, cs, … }`) resolve to the admin language first, then to English.
|
|
386
|
+
|
|
387
|
+
To add languages or override strings, use the `i18n` option. It is deep-merged over the built-in strings, and missing keys fall back to English.
|
|
845
388
|
|
|
846
|
-
```
|
|
847
|
-
import
|
|
848
|
-
|
|
849
|
-
const myTheme: ThemePreset = {
|
|
850
|
-
name: 'my-custom-theme',
|
|
851
|
-
label: 'My Custom Theme',
|
|
852
|
-
colors: {
|
|
853
|
-
primary: { h: 220, s: 70, l: 50 },
|
|
854
|
-
secondary: { h: 180, s: 60, l: 45 },
|
|
855
|
-
// ... other colors
|
|
856
|
-
},
|
|
857
|
-
borderRadius: 'rounded',
|
|
858
|
-
typography: {
|
|
859
|
-
heading: 'Poppins',
|
|
860
|
-
body: 'Inter',
|
|
861
|
-
code: 'Fira Code',
|
|
862
|
-
},
|
|
863
|
-
}
|
|
389
|
+
```ts
|
|
390
|
+
import { de } from '@payloadcms/translations/languages/de'
|
|
864
391
|
|
|
865
|
-
// Use in plugin config
|
|
866
392
|
themeManagementPlugin({
|
|
867
|
-
|
|
868
|
-
|
|
393
|
+
i18n: {
|
|
394
|
+
translations: {
|
|
395
|
+
de: { tabLabel: 'Darstellung', ui: { lightMode: 'Heller Modus' } },
|
|
396
|
+
en: { tabLabel: 'Theme' }, // override a built-in string
|
|
397
|
+
},
|
|
398
|
+
supportedLanguages: { de }, // optional: register a new admin language
|
|
399
|
+
},
|
|
869
400
|
})
|
|
870
401
|
```
|
|
871
402
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
See [MIGRATION_GUIDE.md](./docs/MIGRATION_GUIDE.md) for detailed migration instructions.
|
|
875
|
-
|
|
876
|
-
### Key Changes in v0.1.9+
|
|
877
|
-
|
|
878
|
-
- **Server components must import from `/server`**
|
|
879
|
-
- Added `server-only` package to prevent client bundling errors
|
|
880
|
-
- Removed `ServerThemeInjector` from main entry point
|
|
403
|
+
Full guide: [docs/TRANSLATIONS.md](./docs/TRANSLATIONS.md)
|
|
881
404
|
|
|
882
|
-
|
|
883
|
-
- import { ServerThemeInjector } from '@kilivi/payloadcms-theme-management'
|
|
884
|
-
+ import { ServerThemeInjector } from '@kilivi/payloadcms-theme-management/server'
|
|
885
|
-
```
|
|
886
|
-
|
|
887
|
-
## Testing
|
|
405
|
+
## Appearance controls
|
|
888
406
|
|
|
889
|
-
|
|
407
|
+
| Section | Admin group | Fields |
|
|
408
|
+
| --- | --- | --- |
|
|
409
|
+
| Visual effects | `themeConfiguration.visualEffects` | effectStyle, shadowIntensity, backdropBlur, borderStyle, borderWidth, glassOpacity |
|
|
410
|
+
| Component styles | `themeConfiguration.componentStyles` | buttonVariant, buttonSize, cardStyle, cardHoverEffect, navbarStyle, footerStyle, imageStyle, linkStyle, enableScrollReveal, enableHoverAnimations |
|
|
411
|
+
| Typography | `themeConfiguration.typography` | bodyFont, headingFont, bodyFontCustom, headingFontCustom, baseFontSize, lineHeight |
|
|
890
412
|
|
|
891
|
-
|
|
413
|
+
`heroBackground` (hero style, patterns, dividers) is part of the `ThemeHeroBackground` type and the CSS generator, but the tab has no admin fields for it. Add them through `customThemeConfigurationSections` if you need them.
|
|
892
414
|
|
|
893
|
-
|
|
415
|
+
Full reference: [docs/APPEARANCE_CONTROLS.md](./docs/APPEARANCE_CONTROLS.md)
|
|
894
416
|
|
|
895
|
-
|
|
417
|
+
```tsx
|
|
418
|
+
import { fetchThemeConfiguration } from '@kilivi/payloadcms-theme-management'
|
|
896
419
|
|
|
897
|
-
|
|
420
|
+
const theme = await fetchThemeConfiguration({ collectionSlug: 'site-settings' })
|
|
898
421
|
|
|
899
|
-
|
|
422
|
+
const effect = theme?.visualEffects?.effectStyle ?? 'flat'
|
|
423
|
+
const button = theme?.componentStyles?.buttonVariant ?? 'filled'
|
|
900
424
|
|
|
901
|
-
|
|
902
|
-
|
|
425
|
+
return (
|
|
426
|
+
<html data-effect={effect} data-button={button}>
|
|
427
|
+
...
|
|
428
|
+
</html>
|
|
429
|
+
)
|
|
903
430
|
```
|
|
904
431
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
### `Module not found: Can't resolve 'fs/promises'`
|
|
432
|
+
## Theme presets
|
|
908
433
|
|
|
909
|
-
|
|
434
|
+
Built-in presets are exported as `allThemePresets`, and style presets as `allStylePresets`:
|
|
910
435
|
|
|
911
|
-
```
|
|
912
|
-
import {
|
|
913
|
-
```
|
|
914
|
-
|
|
915
|
-
Then clear your cache:
|
|
916
|
-
|
|
917
|
-
```bash
|
|
918
|
-
rm -rf .next node_modules/.cache
|
|
919
|
-
pnpm install
|
|
436
|
+
```ts
|
|
437
|
+
import { allStylePresets, allThemePresets } from '@kilivi/payloadcms-theme-management'
|
|
920
438
|
```
|
|
921
439
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
1. Verify `ServerThemeInjector` is in your `<head>` tag
|
|
925
|
-
2. Check that site settings exist with theme configuration
|
|
926
|
-
3. Inspect page source - should see `<style>` tag with CSS variables
|
|
927
|
-
4. Ensure Tailwind/CSS is configured to use the CSS variables
|
|
440
|
+
Editors can also import extra presets as JSON (*Custom theme presets* section). They are merged into the theme picker, and they override built-in presets that have the same `name`.
|
|
928
441
|
|
|
929
|
-
|
|
442
|
+
Full preset reference: [docs/THEME_PRESETS_EXTENDED.md](./docs/THEME_PRESETS_EXTENDED.md)
|
|
930
443
|
|
|
931
|
-
|
|
932
|
-
# Regenerate Payload types
|
|
933
|
-
pnpm payload generate:types
|
|
444
|
+
## Visitor components
|
|
934
445
|
|
|
935
|
-
|
|
936
|
-
|
|
446
|
+
```tsx
|
|
447
|
+
import ColorModeToggle from '@kilivi/payloadcms-theme-management/components/ColorModeToggle'
|
|
448
|
+
import ThemeSwitcher from '@kilivi/payloadcms-theme-management/components/ThemeSwitcher'
|
|
937
449
|
```
|
|
938
450
|
|
|
939
|
-
##
|
|
451
|
+
## Public API
|
|
940
452
|
|
|
941
|
-
|
|
942
|
-
- [Migration Guide](./docs/MIGRATION_GUIDE.md) - Upgrading from older versions
|
|
943
|
-
- [Test App Guide](./docs/TEST_APP_GUIDE.md) - Create a test application
|
|
944
|
-
- [Server/Client Separation](./docs/SERVER_CLIENT_SEPARATION.md) - Technical details
|
|
945
|
-
- [Build Setup](./docs/BUILD_SETUP.md) - Build configuration details
|
|
453
|
+
### Main package
|
|
946
454
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
455
|
+
```ts
|
|
456
|
+
import {
|
|
457
|
+
allStylePresets,
|
|
458
|
+
allThemePresets,
|
|
459
|
+
fetchThemeConfiguration,
|
|
460
|
+
generateThemeColorsCss,
|
|
461
|
+
generateThemeCSS,
|
|
462
|
+
getAvailableThemePresets,
|
|
463
|
+
getThemeHtmlAttributes,
|
|
464
|
+
getThemePreset,
|
|
465
|
+
getThemeStyles,
|
|
466
|
+
resolveThemeConfiguration,
|
|
467
|
+
themeManagementPlugin,
|
|
468
|
+
ThemeProvider,
|
|
469
|
+
} from '@kilivi/payloadcms-theme-management'
|
|
958
470
|
|
|
959
|
-
|
|
960
|
-
|
|
471
|
+
import type {
|
|
472
|
+
SiteThemeConfiguration,
|
|
473
|
+
ThemeComponentStyles,
|
|
474
|
+
ThemeHeroBackground,
|
|
475
|
+
ThemeVisualEffects,
|
|
476
|
+
} from '@kilivi/payloadcms-theme-management'
|
|
961
477
|
```
|
|
962
478
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
Contributions are welcome! Please:
|
|
966
|
-
|
|
967
|
-
1. Fork the repository
|
|
968
|
-
2. Create a feature branch
|
|
969
|
-
3. Make your changes
|
|
970
|
-
4. Add tests if applicable
|
|
971
|
-
5. Submit a pull request
|
|
972
|
-
|
|
973
|
-
## Troubleshooting
|
|
479
|
+
### Server package
|
|
974
480
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
481
|
+
```ts
|
|
482
|
+
import {
|
|
483
|
+
createCachedThemeFetcher,
|
|
484
|
+
getThemeCacheTag,
|
|
485
|
+
getThemeCriticalCSS,
|
|
486
|
+
getThemeCSS,
|
|
487
|
+
revalidateThemeCache,
|
|
488
|
+
ServerThemeInjector,
|
|
489
|
+
} from '@kilivi/payloadcms-theme-management/server'
|
|
490
|
+
```
|
|
982
491
|
|
|
983
|
-
|
|
492
|
+
Always import server-only helpers from `/server`.
|
|
984
493
|
|
|
985
|
-
|
|
494
|
+
## Migrating from `@kilivi-dev`
|
|
986
495
|
|
|
987
|
-
|
|
496
|
+
The API is unchanged. Only the package name differs.
|
|
988
497
|
|
|
989
|
-
|
|
498
|
+
1. Swap the dependency:
|
|
990
499
|
|
|
991
|
-
|
|
500
|
+
```bash
|
|
501
|
+
pnpm remove @kilivi-dev/payloadcms-theme-management
|
|
502
|
+
pnpm add @kilivi/payloadcms-theme-management
|
|
503
|
+
```
|
|
992
504
|
|
|
993
|
-
-
|
|
994
|
-
|
|
995
|
-
- [Payload CMS](https://payloadcms.com)
|
|
505
|
+
2. Replace the import prefix in your code: `@kilivi-dev/payloadcms-theme-management` → `@kilivi/payloadcms-theme-management`. This covers `/server`, `/components/*` and `/fields/*`.
|
|
506
|
+
3. Regenerate the admin import map so Payload resolves the admin components under the new name:
|
|
996
507
|
|
|
997
|
-
|
|
508
|
+
```bash
|
|
509
|
+
pnpm payload generate:importmap
|
|
510
|
+
```
|
|
998
511
|
|
|
999
|
-
|
|
512
|
+
4. If you list the package in `next.config` under `transpilePackages`, rename it there too.
|
|
1000
513
|
|
|
1001
|
-
|
|
1002
|
-
- ✅ **Fixed:** Correct Payload CMS v3 API usage (findGlobal instead of non-existent findBySlug)
|
|
1003
|
-
- ✅ **Improved:** Multi-tenant documentation with proper examples
|
|
1004
|
-
- ✅ **Improved:** Cache optimization examples with unstable_cache
|
|
1005
|
-
- ✅ **Updated:** README and Multi-Tenant guide with accurate API methods
|
|
514
|
+
Stored data does not need a migration: field names and database columns stay the same.
|
|
1006
515
|
|
|
1007
|
-
|
|
516
|
+
## Notes
|
|
1008
517
|
|
|
1009
|
-
-
|
|
1010
|
-
-
|
|
1011
|
-
-
|
|
1012
|
-
- ✅ **Fixed:** Cleaner data structure for improved API integration
|
|
1013
|
-
- ✅ **Added:** Live Theme Preview at `/admin/theme-preview` (real-time updates)
|
|
1014
|
-
- ✅ **Added:** Professional color picker (react-colorful)
|
|
1015
|
-
- ✅ **Improved:** shadcn/ui and TweakCN compatibility
|
|
1016
|
-
- ✅ **Improved:** SSR theme injection, zero FOUC, and performance optimizations
|
|
1017
|
-
- ✅ **Added:** Server/client component separation
|
|
1018
|
-
- ✅ **Added:** `server-only` package to prevent bundling errors
|
|
1019
|
-
- ✅ **Changed:** `ServerThemeInjector` now exported from `/server` entry
|
|
518
|
+
- For strict preview security, set `PREVIEW_SECRET` and enable `livePreview.injectRoute`.
|
|
519
|
+
- For multi-tenant apps, use `tenantField` and `tenantQueryParam` to keep preview URLs tenant-aware.
|
|
520
|
+
- Task-focused guides for AI coding assistants live in `.claude/skills/`.
|
|
1020
521
|
|
|
1021
|
-
|
|
522
|
+
## License
|
|
1022
523
|
|
|
1023
|
-
|
|
524
|
+
MIT
|