@kilivi/payloadcms-theme-management 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +318 -847
- 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.map +1 -1
- package/dist/components/ServerThemeInjector.js +41 -14
- package/dist/components/ServerThemeInjector.js.map +1 -1
- package/dist/components/ThemeLivePreviewSync.d.ts +1 -0
- package/dist/components/ThemeLivePreviewSync.d.ts.map +1 -1
- package/dist/components/ThemeLivePreviewSync.js +1 -1
- 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.map +1 -1
- package/dist/components/typographyPreviewUtils.js +8 -1
- 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.js +9 -9
- package/dist/constants/themeFonts.js.map +1 -1
- 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 +763 -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.d.ts +6 -0
- package/dist/fields/FontSelectField.d.ts.map +1 -1
- package/dist/fields/FontSelectField.js +195 -122
- 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 +462 -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 +382 -5
- package/dist/fields/ThemePreviewField.d.ts.map +1 -1
- package/dist/fields/ThemePreviewField.js +268 -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 +961 -29
- 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/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 +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +50 -29
- package/dist/index.js.map +1 -1
- package/dist/payload-types.d.ts +24 -0
- package/dist/payload-types.d.ts.map +1 -1
- package/dist/payload-types.js.map +1 -1
- package/dist/presets.d.ts +8 -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 +66 -0
- package/dist/style-presets.d.ts.map +1 -0
- package/dist/style-presets.js +556 -0
- package/dist/style-presets.js.map +1 -0
- package/dist/translations.d.ts +159 -1
- package/dist/translations.d.ts.map +1 -1
- package/dist/translations.js +310 -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 +16 -2
- package/dist/utils/livePreviewThemeRuntime.d.ts.map +1 -1
- package/dist/utils/livePreviewThemeRuntime.js +37 -14
- 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 +45 -1
- package/dist/utils/resolveThemeConfiguration.d.ts.map +1 -1
- package/dist/utils/resolveThemeConfiguration.js +12 -5
- 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 +416 -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 +11 -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,380 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|
|
135
115
|
}
|
|
136
116
|
```
|
|
137
117
|
|
|
138
|
-
|
|
118
|
+
## The Appearance Settings tab
|
|
139
119
|
|
|
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'
|
|
120
|
+
The tab is ordered as a **presets first, fine-tuning second** workflow:
|
|
145
121
|
|
|
146
|
-
|
|
147
|
-
|
|
122
|
+
1. **Theme selection:**
|
|
123
|
+
- *Keep my: Colours · Fonts · Style* locks
|
|
124
|
+
- the palette generator
|
|
125
|
+
- the collapsible theme preset list
|
|
126
|
+
2. **Live preview:** one preview for 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.
|
|
127
|
+
3. **Style preset:** visual cards that set effects, component styles, radius and typography, but never colours.
|
|
128
|
+
4. **Border radius and spacing scale.**
|
|
129
|
+
5. **Fine-tuning sections:** these are collapsed by default.
|
|
130
|
+
- Colour mode (all light and dark tokens, the WCAG audit and export)
|
|
131
|
+
- Typography
|
|
132
|
+
- Custom theme presets (JSON import)
|
|
133
|
+
- Visual effects
|
|
134
|
+
- Component styles
|
|
135
|
+
- Advanced (animation level, custom CSS)
|
|
148
136
|
|
|
149
|
-
|
|
150
|
-
const appearanceSettings = await payload.findGlobal({
|
|
151
|
-
slug: 'appearance-settings', // Your standalone global slug
|
|
152
|
-
})
|
|
137
|
+
### Locks and undo
|
|
153
138
|
|
|
154
|
-
|
|
155
|
-
<html lang="en">
|
|
156
|
-
<head>
|
|
157
|
-
<ServerThemeInjector themeConfiguration={appearanceSettings?.themeConfiguration} />
|
|
158
|
-
</head>
|
|
159
|
-
<body>{children}</body>
|
|
160
|
-
</html>
|
|
161
|
-
)
|
|
162
|
-
}
|
|
163
|
-
```
|
|
139
|
+
Each bulk action honours the locks:
|
|
164
140
|
|
|
165
|
-
|
|
141
|
+
| Action | Colours | Fonts | Style (effects, components, radius) |
|
|
142
|
+
| --- | --- | --- | --- |
|
|
143
|
+
| Pick a theme preset | writes colours | pins the current preset font instead of switching it | writes the theme's visual effects |
|
|
144
|
+
| Pick a style preset | never | writes the style's typography | writes effects, components, radius, motion and spacing |
|
|
145
|
+
| Generate a palette | writes colours (disabled while locked) | never | never |
|
|
166
146
|
|
|
167
|
-
|
|
147
|
+
- Locks last for the current editing session. They reset on reload and nothing extra is stored in your documents.
|
|
148
|
+
- After each bulk action an *Applied: … · Undo* bar appears for 10 seconds. It pauses while you hover it or it has focus.
|
|
149
|
+
- Undo restores exactly the fields that the action changed.
|
|
168
150
|
|
|
169
|
-
|
|
151
|
+
## Live Preview
|
|
170
152
|
|
|
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'
|
|
153
|
+
Payload's Live Preview points at your client pages by slug:
|
|
179
154
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
})
|
|
188
|
-
},
|
|
155
|
+
- the `home` slug resolves to `/`
|
|
156
|
+
- any other slug resolves to `/{slug}`
|
|
157
|
+
- a tenant query is appended when one is available
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
themeManagementPlugin({
|
|
161
|
+
livePreview: true,
|
|
189
162
|
})
|
|
163
|
+
```
|
|
190
164
|
|
|
191
|
-
|
|
192
|
-
const themeConfiguration = await getCachedTheme()
|
|
165
|
+
### Advanced setup (with the injected endpoint)
|
|
193
166
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
167
|
+
```ts
|
|
168
|
+
themeManagementPlugin({
|
|
169
|
+
livePreview: {
|
|
170
|
+
enabled: true,
|
|
171
|
+
injectRoute: true,
|
|
172
|
+
routePath: '/theme/preview',
|
|
173
|
+
pageCollection: 'pages',
|
|
174
|
+
pageSlug: 'home',
|
|
175
|
+
fallbackToFirstPage: true,
|
|
176
|
+
tenantField: 'tenant',
|
|
177
|
+
tenantQueryParam: 'tenant',
|
|
178
|
+
breakpoints: [
|
|
179
|
+
{ name: 'tablet', label: 'Tablet', width: 1024, height: 768 },
|
|
180
|
+
{ name: 'desktop', label: 'Desktop', width: 1440, height: 900 },
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
})
|
|
203
184
|
```
|
|
204
185
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
- ⚡ Theme settings are cached and reused across requests
|
|
208
|
-
- 🔄 Cache automatically invalidates when you save appearance settings in admin
|
|
209
|
-
- 🚀 Reduces database queries and improves performance
|
|
186
|
+
With `livePreview.injectRoute` enabled, the plugin adds `GET /api/theme/preview` (or your custom `routePath`).
|
|
210
187
|
|
|
211
|
-
**
|
|
188
|
+
- **Query parameters:**
|
|
189
|
+
- `pageSlug`
|
|
190
|
+
- `previewSecret` (or `preview`)
|
|
191
|
+
- `tenant`
|
|
192
|
+
- **Secret:** read from `PREVIEW_SECRET` or `PAYLOAD_PREVIEW_SECRET`.
|
|
193
|
+
- **Responses:**
|
|
194
|
+
- `307` redirects to the resolved client page
|
|
195
|
+
- `401` means a secret is configured but is missing or wrong
|
|
212
196
|
|
|
213
|
-
|
|
214
|
-
-
|
|
197
|
+
```txt
|
|
198
|
+
/api/theme/preview?pageSlug=home&previewSecret=your-secret → /
|
|
199
|
+
/api/theme/preview?pageSlug=home&tenant=acme&previewSecret=your-secret → /?tenant=acme
|
|
200
|
+
```
|
|
215
201
|
|
|
216
|
-
|
|
202
|
+
**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.
|
|
217
203
|
|
|
218
|
-
|
|
204
|
+
### Multi-tenant example
|
|
219
205
|
|
|
220
206
|
```ts
|
|
221
207
|
themeManagementPlugin({
|
|
222
208
|
useStandaloneCollection: true,
|
|
209
|
+
standaloneCollectionSlug: 'appearance-settings',
|
|
210
|
+
livePreview: {
|
|
211
|
+
enabled: true,
|
|
212
|
+
injectRoute: true,
|
|
213
|
+
routePath: '/theme/preview',
|
|
214
|
+
pageCollection: 'pages',
|
|
215
|
+
pageSlug: 'home',
|
|
216
|
+
tenantField: 'tenant',
|
|
217
|
+
tenantQueryParam: 'tenant',
|
|
218
|
+
},
|
|
223
219
|
cacheRevalidation: {
|
|
220
|
+
enabled: true,
|
|
221
|
+
injectRoute: true,
|
|
224
222
|
routePath: '/theme/revalidate',
|
|
225
223
|
secret: process.env.THEME_REVALIDATE_SECRET,
|
|
226
|
-
tags: ['tenant:
|
|
224
|
+
tags: ['tenant:acme'],
|
|
227
225
|
paths: ['/'],
|
|
228
226
|
},
|
|
229
227
|
})
|
|
230
228
|
```
|
|
231
229
|
|
|
232
|
-
|
|
230
|
+
See [MULTI_TENANT_GUIDE.md](./MULTI_TENANT_GUIDE.md) for shared vs per-tenant setups.
|
|
233
231
|
|
|
234
|
-
|
|
232
|
+
## Cache revalidation
|
|
235
233
|
|
|
236
|
-
|
|
234
|
+
When `useStandaloneCollection: true`, the default cache tag is `global_{standaloneCollectionSlug}`. The plugin can inject `POST /api/theme/revalidate`:
|
|
237
235
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
236
|
+
```ts
|
|
237
|
+
themeManagementPlugin({
|
|
238
|
+
useStandaloneCollection: true,
|
|
239
|
+
cacheRevalidation: {
|
|
240
|
+
enabled: true,
|
|
241
|
+
injectRoute: true,
|
|
242
|
+
routePath: '/theme/revalidate',
|
|
243
|
+
secret: process.env.THEME_REVALIDATE_SECRET,
|
|
244
|
+
tags: ['tenant:default'],
|
|
245
|
+
paths: ['/'],
|
|
246
|
+
},
|
|
247
|
+
})
|
|
247
248
|
```
|
|
248
249
|
|
|
249
|
-
|
|
250
|
+
### Next.js server caching helper
|
|
250
251
|
|
|
251
252
|
```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
|
|
310
|
-
|
|
311
|
-
### Main Entry Point
|
|
312
|
-
|
|
313
|
-
Import from `@kilivi/payloadcms-theme-management` for client-safe code:
|
|
314
|
-
|
|
315
|
-
```typescript
|
|
316
|
-
// Plugin
|
|
317
|
-
// Client Components
|
|
318
|
-
|
|
319
|
-
// Utilities
|
|
320
|
-
import {
|
|
321
|
-
fetchThemeConfiguration,
|
|
322
|
-
generateThemeColorsCss,
|
|
323
|
-
generateThemeCSS,
|
|
324
|
-
getThemeStyles,
|
|
325
|
-
resolveThemeConfiguration,
|
|
326
|
-
themeManagementPlugin,
|
|
327
|
-
ThemeProvider,
|
|
328
|
-
} from '@kilivi/payloadcms-theme-management'
|
|
329
|
-
// Types
|
|
330
|
-
import type {
|
|
331
|
-
ThemeDefaults,
|
|
332
|
-
ThemeManagementPluginOptions,
|
|
333
|
-
ThemePreset,
|
|
334
|
-
} from '@kilivi/payloadcms-theme-management'
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
### Server Entry Point
|
|
338
|
-
|
|
339
|
-
⚠️ **Import from `/server` for server components only:**
|
|
340
|
-
|
|
341
|
-
```typescript
|
|
342
253
|
import {
|
|
343
|
-
|
|
344
|
-
getThemeCSS,
|
|
254
|
+
createCachedThemeFetcher,
|
|
345
255
|
ServerThemeInjector,
|
|
346
256
|
} from '@kilivi/payloadcms-theme-management/server'
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
> **Heads up:** Legacy helpers `getThemeCSSPath` and `generateThemePreloadLinks` are still exported for backwards compatibility but now return empty strings and emit console warnings. Prefer the inline CSS utilities above.
|
|
350
|
-
|
|
351
|
-
### Subpath Exports
|
|
352
|
-
|
|
353
|
-
```typescript
|
|
354
|
-
// Direct field imports
|
|
355
|
-
|
|
356
|
-
// Direct component imports
|
|
357
|
-
import { ThemePreview } from '@kilivi/payloadcms-theme-management/components/ThemePreview'
|
|
358
|
-
import { ThemeColorPickerField } from '@kilivi/payloadcms-theme-management/fields/ThemeColorPickerField'
|
|
359
|
-
import { ThemeTokenSelectField } from '@kilivi/payloadcms-theme-management/fields/ThemeTokenSelectField'
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
## Integration Examples
|
|
363
|
-
|
|
364
|
-
### With Tailwind CSS
|
|
365
|
-
|
|
366
|
-
#### tailwind.config.mjs
|
|
367
|
-
|
|
368
|
-
```typescript
|
|
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'
|
|
373
|
-
|
|
374
|
-
/** @type {import('tailwindcss').Config} */
|
|
375
|
-
export default {
|
|
376
|
-
darkMode: ['class'],
|
|
377
|
-
content: [
|
|
378
|
-
'./src/app/**/*.{ts,tsx}',
|
|
379
|
-
'./src/components/**/*.{ts,tsx}',
|
|
380
|
-
'./src/collections/**/*.{ts,tsx}',
|
|
381
|
-
'./src/providers/**/*.{ts,tsx}',
|
|
382
|
-
'./src/heros/**/*.{ts,tsx}',
|
|
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
|
-
},
|
|
594
|
-
},
|
|
595
|
-
plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')],
|
|
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
|
-
}
|
|
794
|
-
```
|
|
795
|
-
|
|
796
|
-
### Fetching Theme Configuration
|
|
797
|
-
|
|
798
|
-
The recommended way to fetch theme configuration is using Payload's native API with Next.js caching:
|
|
799
|
-
|
|
800
|
-
```typescript
|
|
801
257
|
import configPromise from '@payload-config'
|
|
802
258
|
import { getPayload } from 'payload'
|
|
803
|
-
import { unstable_cache } from 'next/cache'
|
|
804
259
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
260
|
+
const getCachedTheme = createCachedThemeFetcher({
|
|
261
|
+
globalSlug: 'appearance-settings',
|
|
262
|
+
revalidate: 3600,
|
|
263
|
+
loadAppearanceSettings: async () => {
|
|
808
264
|
const payload = await getPayload({ config: configPromise })
|
|
809
|
-
|
|
810
|
-
slug: 'appearance-settings', // or 'your-global-slug'
|
|
811
|
-
})
|
|
812
|
-
return global?.themeConfiguration
|
|
265
|
+
return payload.findGlobal({ slug: 'appearance-settings' })
|
|
813
266
|
},
|
|
814
|
-
|
|
815
|
-
{
|
|
816
|
-
tags: ['global_appearance-settings'],
|
|
817
|
-
revalidate: 3600, // Revalidate every hour
|
|
818
|
-
},
|
|
819
|
-
)
|
|
267
|
+
})
|
|
820
268
|
|
|
821
|
-
// Use in your layout
|
|
822
269
|
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
823
270
|
const themeConfiguration = await getCachedTheme()
|
|
824
|
-
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<html lang="en">
|
|
274
|
+
<head>
|
|
275
|
+
<ServerThemeInjector themeConfiguration={themeConfiguration} />
|
|
276
|
+
</head>
|
|
277
|
+
<body>{children}</body>
|
|
278
|
+
</html>
|
|
279
|
+
)
|
|
825
280
|
}
|
|
826
281
|
```
|
|
827
282
|
|
|
828
|
-
|
|
283
|
+
### Fetching the configuration
|
|
829
284
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
```typescript
|
|
285
|
+
```ts
|
|
833
286
|
import { fetchThemeConfiguration } from '@kilivi/payloadcms-theme-management'
|
|
834
287
|
|
|
835
|
-
//
|
|
836
|
-
const
|
|
288
|
+
// Single tenant
|
|
289
|
+
const themeConfiguration = await fetchThemeConfiguration({
|
|
290
|
+
useGlobal: true,
|
|
837
291
|
collectionSlug: 'appearance-settings',
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
// Multi-tenant
|
|
295
|
+
const tenantThemeConfiguration = await fetchThemeConfiguration({
|
|
838
296
|
useGlobal: true,
|
|
297
|
+
collectionSlug: 'appearance-settings',
|
|
298
|
+
tenantSlug: 'acme',
|
|
839
299
|
})
|
|
840
300
|
```
|
|
841
301
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
302
|
+
## Plugin options
|
|
303
|
+
|
|
304
|
+
| Option | Type | Default | Description |
|
|
305
|
+
| --- | --- | --- | --- |
|
|
306
|
+
| `enabled` | `boolean` | `true` | Enables or disables the plugin |
|
|
307
|
+
| `targetCollection` | `string` | `'site-settings'` | Collection slug for tab mode |
|
|
308
|
+
| `useStandaloneCollection` | `boolean` | `false` | Creates a standalone global instead of injecting a tab |
|
|
309
|
+
| `standaloneCollectionSlug` | `string` | `'appearance-settings'` | Global slug in standalone mode |
|
|
310
|
+
| `standaloneCollectionLabel` | `string \| Record<string, string>` | translated label | Label of the standalone global |
|
|
311
|
+
| `themePresets` | `ThemePreset[]` | built-in presets | Custom preset list |
|
|
312
|
+
| `defaultTheme` | `string` | `'cool'` | Default preset name |
|
|
313
|
+
| `includeColorModeToggle` | `boolean` | `true` | Colour mode section (light/dark tokens, audit, export) |
|
|
314
|
+
| `includeCustomCSS` | `boolean` | `true` | Enables the custom CSS field |
|
|
315
|
+
| `enableAdvancedFeatures` | `boolean` | `true` | Advanced controls such as animation level |
|
|
316
|
+
| `customThemeConfigurationFields` | `Field[]` | `undefined` | Extra fields appended inside the `themeConfiguration` group |
|
|
317
|
+
| `customThemeConfigurationSections` | `Array<{ label; fields; initCollapsed?; description? }>` | `undefined` | Extra collapsible sections inside the group |
|
|
318
|
+
| `enableLogging` | `boolean` | `false` | Logs plugin actions |
|
|
319
|
+
| `livePreview` | `boolean \| ThemeManagementLivePreviewOptions` | `true` | Live preview URL behaviour |
|
|
320
|
+
| `cacheRevalidation` | `boolean \| ThemeManagementCacheRevalidationOptions` | on in standalone mode | Cache invalidation endpoint, tags and paths |
|
|
321
|
+
| `i18n` | `ThemeManagementI18nOptions` | `undefined` | Extend or override admin translations |
|
|
322
|
+
| `includeBrandIdentity` | `boolean` | `false` | Reserved for future use |
|
|
323
|
+
|
|
324
|
+
### `livePreview` options
|
|
325
|
+
|
|
326
|
+
| Option | Type | Default |
|
|
327
|
+
| --- | --- | --- |
|
|
328
|
+
| `enabled` | `boolean` | `true` |
|
|
329
|
+
| `injectRoute` | `boolean` | `false` |
|
|
330
|
+
| `routePath` | `string` | `'/theme/preview'` |
|
|
331
|
+
| `pageCollection` | `string` | `'pages'` |
|
|
332
|
+
| `pageSlug` | `string` | `'home'` |
|
|
333
|
+
| `fallbackToFirstPage` | `boolean` | `true` |
|
|
334
|
+
| `tenantField` | `string` | `'tenant'` |
|
|
335
|
+
| `tenantQueryParam` | `string` | `'tenant'` |
|
|
336
|
+
| `breakpoints` | `Array<{ name; label; width; height }>` | `undefined` |
|
|
337
|
+
| `url` | `(args) => string \| Promise<string>` | slug-based URL |
|
|
338
|
+
|
|
339
|
+
### `cacheRevalidation` options
|
|
340
|
+
|
|
341
|
+
| Option | Type | Default |
|
|
342
|
+
| --- | --- | --- |
|
|
343
|
+
| `enabled` | `boolean` | `true` in standalone mode, otherwise `false` |
|
|
344
|
+
| `injectRoute` | `boolean` | `true` |
|
|
345
|
+
| `routePath` | `string` | `'/theme/revalidate'` |
|
|
346
|
+
| `secret` | `string` | `undefined` |
|
|
347
|
+
| `tags` | `string[]` | `[global_{slug}]` |
|
|
348
|
+
| `paths` | `string[]` | `[]` |
|
|
349
|
+
|
|
350
|
+
## Internationalization
|
|
351
|
+
|
|
352
|
+
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.
|
|
353
|
+
|
|
354
|
+
- Every admin string follows the active admin language, including field labels, the pickers, the audit, the import drawer and the font guides.
|
|
355
|
+
- Localised label objects (`{ en, cs, … }`) resolve to the admin language first, then to English.
|
|
356
|
+
|
|
357
|
+
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.
|
|
848
358
|
|
|
849
|
-
|
|
850
|
-
|
|
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
|
-
}
|
|
359
|
+
```ts
|
|
360
|
+
import { de } from '@payloadcms/translations/languages/de'
|
|
864
361
|
|
|
865
|
-
// Use in plugin config
|
|
866
362
|
themeManagementPlugin({
|
|
867
|
-
|
|
868
|
-
|
|
363
|
+
i18n: {
|
|
364
|
+
translations: {
|
|
365
|
+
de: { tabLabel: 'Darstellung', ui: { lightMode: 'Heller Modus' } },
|
|
366
|
+
en: { tabLabel: 'Theme' }, // override a built-in string
|
|
367
|
+
},
|
|
368
|
+
supportedLanguages: { de }, // optional: register a new admin language
|
|
369
|
+
},
|
|
869
370
|
})
|
|
870
371
|
```
|
|
871
372
|
|
|
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
|
|
881
|
-
|
|
882
|
-
```diff
|
|
883
|
-
- import { ServerThemeInjector } from '@kilivi/payloadcms-theme-management'
|
|
884
|
-
+ import { ServerThemeInjector } from '@kilivi/payloadcms-theme-management/server'
|
|
885
|
-
```
|
|
373
|
+
Full guide: [docs/TRANSLATIONS.md](./docs/TRANSLATIONS.md)
|
|
886
374
|
|
|
887
|
-
##
|
|
375
|
+
## Appearance controls
|
|
888
376
|
|
|
889
|
-
|
|
377
|
+
| Section | Admin group | Fields |
|
|
378
|
+
| --- | --- | --- |
|
|
379
|
+
| Visual effects | `themeConfiguration.visualEffects` | effectStyle, shadowIntensity, backdropBlur, borderStyle, borderWidth, glassOpacity |
|
|
380
|
+
| Component styles | `themeConfiguration.componentStyles` | buttonVariant, buttonSize, cardStyle, cardHoverEffect, navbarStyle, footerStyle, imageStyle, linkStyle, enableScrollReveal, enableHoverAnimations |
|
|
381
|
+
| Typography | `themeConfiguration.typography` | bodyFont, headingFont, bodyFontCustom, headingFontCustom, baseFontSize, lineHeight |
|
|
890
382
|
|
|
891
|
-
|
|
383
|
+
`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
384
|
|
|
893
|
-
|
|
385
|
+
Full reference: [docs/APPEARANCE_CONTROLS.md](./docs/APPEARANCE_CONTROLS.md)
|
|
894
386
|
|
|
895
|
-
|
|
387
|
+
```tsx
|
|
388
|
+
import { fetchThemeConfiguration } from '@kilivi/payloadcms-theme-management'
|
|
896
389
|
|
|
897
|
-
|
|
390
|
+
const theme = await fetchThemeConfiguration({ collectionSlug: 'site-settings' })
|
|
898
391
|
|
|
899
|
-
|
|
392
|
+
const effect = theme?.visualEffects?.effectStyle ?? 'flat'
|
|
393
|
+
const button = theme?.componentStyles?.buttonVariant ?? 'filled'
|
|
900
394
|
|
|
901
|
-
|
|
902
|
-
|
|
395
|
+
return (
|
|
396
|
+
<html data-effect={effect} data-button={button}>
|
|
397
|
+
...
|
|
398
|
+
</html>
|
|
399
|
+
)
|
|
903
400
|
```
|
|
904
401
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
### `Module not found: Can't resolve 'fs/promises'`
|
|
402
|
+
## Theme presets
|
|
908
403
|
|
|
909
|
-
|
|
404
|
+
Built-in presets are exported as `allThemePresets`, and style presets as `allStylePresets`:
|
|
910
405
|
|
|
911
|
-
```
|
|
912
|
-
import {
|
|
913
|
-
```
|
|
914
|
-
|
|
915
|
-
Then clear your cache:
|
|
916
|
-
|
|
917
|
-
```bash
|
|
918
|
-
rm -rf .next node_modules/.cache
|
|
919
|
-
pnpm install
|
|
406
|
+
```ts
|
|
407
|
+
import { allStylePresets, allThemePresets } from '@kilivi/payloadcms-theme-management'
|
|
920
408
|
```
|
|
921
409
|
|
|
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
|
|
410
|
+
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
411
|
|
|
929
|
-
|
|
412
|
+
Full preset reference: [docs/THEME_PRESETS_EXTENDED.md](./docs/THEME_PRESETS_EXTENDED.md)
|
|
930
413
|
|
|
931
|
-
|
|
932
|
-
# Regenerate Payload types
|
|
933
|
-
pnpm payload generate:types
|
|
414
|
+
## Visitor components
|
|
934
415
|
|
|
935
|
-
|
|
936
|
-
|
|
416
|
+
```tsx
|
|
417
|
+
import ColorModeToggle from '@kilivi/payloadcms-theme-management/components/ColorModeToggle'
|
|
418
|
+
import ThemeSwitcher from '@kilivi/payloadcms-theme-management/components/ThemeSwitcher'
|
|
937
419
|
```
|
|
938
420
|
|
|
939
|
-
##
|
|
940
|
-
|
|
941
|
-
- [Type Independence Guide](./docs/TYPE_INDEPENDENCE_GUIDE.md) - Understanding type safety without coupling
|
|
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
|
|
946
|
-
|
|
947
|
-
## Development
|
|
421
|
+
## Public API
|
|
948
422
|
|
|
949
|
-
|
|
950
|
-
# Install dependencies
|
|
951
|
-
pnpm install
|
|
952
|
-
|
|
953
|
-
# Build the plugin
|
|
954
|
-
pnpm build
|
|
423
|
+
### Main package
|
|
955
424
|
|
|
956
|
-
|
|
957
|
-
|
|
425
|
+
```ts
|
|
426
|
+
import {
|
|
427
|
+
allStylePresets,
|
|
428
|
+
allThemePresets,
|
|
429
|
+
fetchThemeConfiguration,
|
|
430
|
+
generateThemeColorsCss,
|
|
431
|
+
generateThemeCSS,
|
|
432
|
+
getAvailableThemePresets,
|
|
433
|
+
getThemeHtmlAttributes,
|
|
434
|
+
getThemePreset,
|
|
435
|
+
getThemeStyles,
|
|
436
|
+
resolveThemeConfiguration,
|
|
437
|
+
themeManagementPlugin,
|
|
438
|
+
ThemeProvider,
|
|
439
|
+
} from '@kilivi/payloadcms-theme-management'
|
|
958
440
|
|
|
959
|
-
|
|
960
|
-
|
|
441
|
+
import type {
|
|
442
|
+
SiteThemeConfiguration,
|
|
443
|
+
ThemeComponentStyles,
|
|
444
|
+
ThemeHeroBackground,
|
|
445
|
+
ThemeVisualEffects,
|
|
446
|
+
} from '@kilivi/payloadcms-theme-management'
|
|
961
447
|
```
|
|
962
448
|
|
|
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
|
|
449
|
+
### Server package
|
|
974
450
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
451
|
+
```ts
|
|
452
|
+
import {
|
|
453
|
+
createCachedThemeFetcher,
|
|
454
|
+
getThemeCacheTag,
|
|
455
|
+
getThemeCriticalCSS,
|
|
456
|
+
getThemeCSS,
|
|
457
|
+
revalidateThemeCache,
|
|
458
|
+
ServerThemeInjector,
|
|
459
|
+
} from '@kilivi/payloadcms-theme-management/server'
|
|
460
|
+
```
|
|
982
461
|
|
|
983
|
-
|
|
462
|
+
Always import server-only helpers from `/server`.
|
|
984
463
|
|
|
985
|
-
|
|
464
|
+
## Migrating from `@kilivi-dev`
|
|
986
465
|
|
|
987
|
-
|
|
466
|
+
The API is unchanged. Only the package name differs.
|
|
988
467
|
|
|
989
|
-
|
|
468
|
+
1. Swap the dependency:
|
|
990
469
|
|
|
991
|
-
|
|
470
|
+
```bash
|
|
471
|
+
pnpm remove @kilivi-dev/payloadcms-theme-management
|
|
472
|
+
pnpm add @kilivi/payloadcms-theme-management
|
|
473
|
+
```
|
|
992
474
|
|
|
993
|
-
-
|
|
994
|
-
|
|
995
|
-
- [Payload CMS](https://payloadcms.com)
|
|
475
|
+
2. Replace the import prefix in your code: `@kilivi-dev/payloadcms-theme-management` → `@kilivi/payloadcms-theme-management`. This covers `/server`, `/components/*` and `/fields/*`.
|
|
476
|
+
3. Regenerate the admin import map so Payload resolves the admin components under the new name:
|
|
996
477
|
|
|
997
|
-
|
|
478
|
+
```bash
|
|
479
|
+
pnpm payload generate:importmap
|
|
480
|
+
```
|
|
998
481
|
|
|
999
|
-
|
|
482
|
+
4. If you list the package in `next.config` under `transpilePackages`, rename it there too.
|
|
1000
483
|
|
|
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
|
|
484
|
+
Stored data does not need a migration: field names and database columns stay the same.
|
|
1006
485
|
|
|
1007
|
-
|
|
486
|
+
## Notes
|
|
1008
487
|
|
|
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
|
|
488
|
+
- For strict preview security, set `PREVIEW_SECRET` and enable `livePreview.injectRoute`.
|
|
489
|
+
- For multi-tenant apps, use `tenantField` and `tenantQueryParam` to keep preview URLs tenant-aware.
|
|
490
|
+
- Task-focused guides for AI coding assistants live in `.claude/skills/`.
|
|
1020
491
|
|
|
1021
|
-
|
|
492
|
+
## License
|
|
1022
493
|
|
|
1023
|
-
|
|
494
|
+
MIT
|