@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.
Files changed (235) hide show
  1. package/README.md +346 -845
  2. package/dist/components/AppearanceSessionControls.d.ts +15 -0
  3. package/dist/components/AppearanceSessionControls.d.ts.map +1 -0
  4. package/dist/components/AppearanceSessionControls.js +152 -0
  5. package/dist/components/AppearanceSessionControls.js.map +1 -0
  6. package/dist/components/ColorModeToggle.css +42 -0
  7. package/dist/components/ColorModeToggle.d.ts +40 -0
  8. package/dist/components/ColorModeToggle.d.ts.map +1 -0
  9. package/dist/components/ColorModeToggle.js +111 -0
  10. package/dist/components/ColorModeToggle.js.map +1 -0
  11. package/dist/components/ServerThemeInjector.d.ts +13 -1
  12. package/dist/components/ServerThemeInjector.d.ts.map +1 -1
  13. package/dist/components/ServerThemeInjector.js +65 -15
  14. package/dist/components/ServerThemeInjector.js.map +1 -1
  15. package/dist/components/ThemeLivePreviewSync.d.ts +4 -0
  16. package/dist/components/ThemeLivePreviewSync.d.ts.map +1 -1
  17. package/dist/components/ThemeLivePreviewSync.js +28 -4
  18. package/dist/components/ThemeLivePreviewSync.js.map +1 -1
  19. package/dist/components/ThemeSwitcher.d.ts +36 -0
  20. package/dist/components/ThemeSwitcher.d.ts.map +1 -0
  21. package/dist/components/ThemeSwitcher.js +89 -0
  22. package/dist/components/ThemeSwitcher.js.map +1 -0
  23. package/dist/components/typographyPreviewUtils.d.ts +5 -0
  24. package/dist/components/typographyPreviewUtils.d.ts.map +1 -1
  25. package/dist/components/typographyPreviewUtils.js +24 -11
  26. package/dist/components/typographyPreviewUtils.js.map +1 -1
  27. package/dist/constants/devicePreviews.d.ts +35 -0
  28. package/dist/constants/devicePreviews.d.ts.map +1 -0
  29. package/dist/constants/devicePreviews.js +43 -0
  30. package/dist/constants/devicePreviews.js.map +1 -0
  31. package/dist/constants/themeFonts.d.ts +94 -168
  32. package/dist/constants/themeFonts.d.ts.map +1 -1
  33. package/dist/constants/themeFonts.js +260 -140
  34. package/dist/constants/themeFonts.js.map +1 -1
  35. package/dist/endpoints/themeFonts.d.ts +5 -0
  36. package/dist/endpoints/themeFonts.d.ts.map +1 -0
  37. package/dist/endpoints/themeFonts.js +138 -0
  38. package/dist/endpoints/themeFonts.js.map +1 -0
  39. package/dist/extended-presets.d.ts +0 -4
  40. package/dist/extended-presets.d.ts.map +1 -1
  41. package/dist/extended-presets.js +1 -18
  42. package/dist/extended-presets.js.map +1 -1
  43. package/dist/extended-themes.d.ts.map +1 -1
  44. package/dist/extended-themes.js +428 -0
  45. package/dist/extended-themes.js.map +1 -1
  46. package/dist/fields/AccessibilityAuditField.d.ts +2 -0
  47. package/dist/fields/AccessibilityAuditField.d.ts.map +1 -0
  48. package/dist/fields/AccessibilityAuditField.js +303 -0
  49. package/dist/fields/AccessibilityAuditField.js.map +1 -0
  50. package/dist/fields/AppearancePreviewField.d.ts +2 -0
  51. package/dist/fields/AppearancePreviewField.d.ts.map +1 -0
  52. package/dist/fields/AppearancePreviewField.js +826 -0
  53. package/dist/fields/AppearancePreviewField.js.map +1 -0
  54. package/dist/fields/ColorPickerField.d.ts.map +1 -1
  55. package/dist/fields/ColorPickerField.js +89 -85
  56. package/dist/fields/ColorPickerField.js.map +1 -1
  57. package/dist/fields/FontSelectField.css +155 -0
  58. package/dist/fields/FontSelectField.d.ts +8 -0
  59. package/dist/fields/FontSelectField.d.ts.map +1 -1
  60. package/dist/fields/FontSelectField.js +223 -188
  61. package/dist/fields/FontSelectField.js.map +1 -1
  62. package/dist/fields/PaletteGeneratorField.d.ts +7 -0
  63. package/dist/fields/PaletteGeneratorField.d.ts.map +1 -0
  64. package/dist/fields/PaletteGeneratorField.js +327 -0
  65. package/dist/fields/PaletteGeneratorField.js.map +1 -0
  66. package/dist/fields/StylePresetField.d.ts +3 -0
  67. package/dist/fields/StylePresetField.d.ts.map +1 -0
  68. package/dist/fields/StylePresetField.js +463 -0
  69. package/dist/fields/StylePresetField.js.map +1 -0
  70. package/dist/fields/ThemeColorPickerField.css +115 -35
  71. package/dist/fields/ThemeColorPickerField.d.ts.map +1 -1
  72. package/dist/fields/ThemeColorPickerField.js +204 -70
  73. package/dist/fields/ThemeColorPickerField.js.map +1 -1
  74. package/dist/fields/ThemeExportField.d.ts +6 -0
  75. package/dist/fields/ThemeExportField.d.ts.map +1 -0
  76. package/dist/fields/ThemeExportField.js +202 -0
  77. package/dist/fields/ThemeExportField.js.map +1 -0
  78. package/dist/fields/ThemePresetImportField.d.ts.map +1 -1
  79. package/dist/fields/ThemePresetImportField.js +56 -25
  80. package/dist/fields/ThemePresetImportField.js.map +1 -1
  81. package/dist/fields/ThemePreviewField.css +444 -5
  82. package/dist/fields/ThemePreviewField.d.ts.map +1 -1
  83. package/dist/fields/ThemePreviewField.js +265 -170
  84. package/dist/fields/ThemePreviewField.js.map +1 -1
  85. package/dist/fields/ThemeTokenSelectField.d.ts.map +1 -1
  86. package/dist/fields/ThemeTokenSelectField.js +278 -80
  87. package/dist/fields/ThemeTokenSelectField.js.map +1 -1
  88. package/dist/fields/colorFieldHelpers.d.ts +90 -0
  89. package/dist/fields/colorFieldHelpers.d.ts.map +1 -0
  90. package/dist/fields/colorFieldHelpers.js +133 -0
  91. package/dist/fields/colorFieldHelpers.js.map +1 -0
  92. package/dist/fields/colorModeFields.d.ts +1 -1
  93. package/dist/fields/colorModeFields.d.ts.map +1 -1
  94. package/dist/fields/colorModeFields.js +49 -7
  95. package/dist/fields/colorModeFields.js.map +1 -1
  96. package/dist/fields/themeConfigurationField.d.ts +8 -1
  97. package/dist/fields/themeConfigurationField.d.ts.map +1 -1
  98. package/dist/fields/themeConfigurationField.js +1209 -228
  99. package/dist/fields/themeConfigurationField.js.map +1 -1
  100. package/dist/globals/ThemeSettings/FontPicker/FontPreview.d.ts.map +1 -1
  101. package/dist/globals/ThemeSettings/FontPicker/FontPreview.js +8 -4
  102. package/dist/globals/ThemeSettings/FontPicker/FontPreview.js.map +1 -1
  103. package/dist/globals/ThemeSettings/FontPicker/index.d.ts.map +1 -1
  104. package/dist/globals/ThemeSettings/FontPicker/index.js +81 -26
  105. package/dist/globals/ThemeSettings/FontPicker/index.js.map +1 -1
  106. package/dist/globals/ThemeSettings/FontPicker/styles.css +98 -41
  107. package/dist/globals/ThemeSettings/FontPicker/translations.d.ts +6 -0
  108. package/dist/globals/ThemeSettings/FontPicker/translations.d.ts.map +1 -1
  109. package/dist/globals/ThemeSettings/FontPicker/translations.js +14 -2
  110. package/dist/globals/ThemeSettings/FontPicker/translations.js.map +1 -1
  111. package/dist/globals/ThemeSettings/config.d.ts.map +1 -1
  112. package/dist/globals/ThemeSettings/config.js +76 -79
  113. package/dist/globals/ThemeSettings/config.js.map +1 -1
  114. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.d.ts.map +1 -1
  115. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js +63 -62
  116. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js.map +1 -1
  117. package/dist/globals/ThemeSettings/fields/TypographyInfoField.d.ts.map +1 -1
  118. package/dist/globals/ThemeSettings/fields/TypographyInfoField.js +22 -25
  119. package/dist/globals/ThemeSettings/fields/TypographyInfoField.js.map +1 -1
  120. package/dist/hooks/useAppearanceSession.d.ts +50 -0
  121. package/dist/hooks/useAppearanceSession.d.ts.map +1 -0
  122. package/dist/hooks/useAppearanceSession.js +91 -0
  123. package/dist/hooks/useAppearanceSession.js.map +1 -0
  124. package/dist/hooks/useNativeLivePreview.d.ts +15 -0
  125. package/dist/hooks/useNativeLivePreview.d.ts.map +1 -0
  126. package/dist/hooks/useNativeLivePreview.js +18 -0
  127. package/dist/hooks/useNativeLivePreview.js.map +1 -0
  128. package/dist/hooks/useThemeFonts.d.ts +10 -0
  129. package/dist/hooks/useThemeFonts.d.ts.map +1 -0
  130. package/dist/hooks/useThemeFonts.js +36 -0
  131. package/dist/hooks/useThemeFonts.js.map +1 -0
  132. package/dist/hooks/useThemeTranslations.d.ts +13 -0
  133. package/dist/hooks/useThemeTranslations.d.ts.map +1 -0
  134. package/dist/hooks/useThemeTranslations.js +22 -0
  135. package/dist/hooks/useThemeTranslations.js.map +1 -0
  136. package/dist/i18n.d.ts +53 -0
  137. package/dist/i18n.d.ts.map +1 -0
  138. package/dist/i18n.js +53 -0
  139. package/dist/i18n.js.map +1 -0
  140. package/dist/index.d.ts +13 -4
  141. package/dist/index.d.ts.map +1 -1
  142. package/dist/index.js +59 -29
  143. package/dist/index.js.map +1 -1
  144. package/dist/next-fonts.d.ts +3 -0
  145. package/dist/next-fonts.d.ts.map +1 -0
  146. package/dist/next-fonts.js +360 -0
  147. package/dist/next-fonts.js.map +1 -0
  148. package/dist/payload-types.d.ts +34 -6
  149. package/dist/payload-types.d.ts.map +1 -1
  150. package/dist/payload-types.js.map +1 -1
  151. package/dist/presets.d.ts +14 -0
  152. package/dist/presets.d.ts.map +1 -1
  153. package/dist/presets.js.map +1 -1
  154. package/dist/providers/Theme/index.d.ts.map +1 -1
  155. package/dist/providers/Theme/index.js +75 -11
  156. package/dist/providers/Theme/index.js.map +1 -1
  157. package/dist/providers/Theme/themeConfig.d.ts.map +1 -1
  158. package/dist/providers/Theme/themeConfig.js +104 -0
  159. package/dist/providers/Theme/themeConfig.js.map +1 -1
  160. package/dist/providers/Theme/types.d.ts +1 -1
  161. package/dist/providers/Theme/types.d.ts.map +1 -1
  162. package/dist/providers/Theme/types.js +10 -1
  163. package/dist/providers/Theme/types.js.map +1 -1
  164. package/dist/style-presets.d.ts +75 -0
  165. package/dist/style-presets.d.ts.map +1 -0
  166. package/dist/style-presets.js +738 -0
  167. package/dist/style-presets.js.map +1 -0
  168. package/dist/translations.d.ts +164 -1
  169. package/dist/translations.d.ts.map +1 -1
  170. package/dist/translations.js +320 -9
  171. package/dist/translations.js.map +1 -1
  172. package/dist/tweakcn-special-themes.d.ts.map +1 -1
  173. package/dist/tweakcn-special-themes.js +585 -9
  174. package/dist/tweakcn-special-themes.js.map +1 -1
  175. package/dist/types/css.d.js +3 -0
  176. package/dist/types/css.d.js.map +1 -0
  177. package/dist/types.d.ts +26 -0
  178. package/dist/types.d.ts.map +1 -1
  179. package/dist/types.js +1 -1
  180. package/dist/types.js.map +1 -1
  181. package/dist/utilities/getThemeSettings.d.ts.map +1 -1
  182. package/dist/utilities/getThemeSettings.js +2 -14
  183. package/dist/utilities/getThemeSettings.js.map +1 -1
  184. package/dist/utils/contrast.d.ts +63 -0
  185. package/dist/utils/contrast.d.ts.map +1 -0
  186. package/dist/utils/contrast.js +158 -0
  187. package/dist/utils/contrast.js.map +1 -0
  188. package/dist/utils/exportTokens.d.ts +30 -0
  189. package/dist/utils/exportTokens.d.ts.map +1 -0
  190. package/dist/utils/exportTokens.js +183 -0
  191. package/dist/utils/exportTokens.js.map +1 -0
  192. package/dist/utils/generatePalette.d.ts +46 -0
  193. package/dist/utils/generatePalette.d.ts.map +1 -0
  194. package/dist/utils/generatePalette.js +127 -0
  195. package/dist/utils/generatePalette.js.map +1 -0
  196. package/dist/utils/generateThemeCSS.d.ts.map +1 -1
  197. package/dist/utils/generateThemeCSS.js +2 -40
  198. package/dist/utils/generateThemeCSS.js.map +1 -1
  199. package/dist/utils/livePreviewThemeRuntime.d.ts +22 -3
  200. package/dist/utils/livePreviewThemeRuntime.d.ts.map +1 -1
  201. package/dist/utils/livePreviewThemeRuntime.js +48 -15
  202. package/dist/utils/livePreviewThemeRuntime.js.map +1 -1
  203. package/dist/utils/localizedText.d.ts +8 -0
  204. package/dist/utils/localizedText.d.ts.map +1 -0
  205. package/dist/utils/localizedText.js +21 -0
  206. package/dist/utils/localizedText.js.map +1 -0
  207. package/dist/utils/resolveThemeConfiguration.d.ts +60 -1
  208. package/dist/utils/resolveThemeConfiguration.d.ts.map +1 -1
  209. package/dist/utils/resolveThemeConfiguration.js +53 -13
  210. package/dist/utils/resolveThemeConfiguration.js.map +1 -1
  211. package/dist/utils/themeColors.d.ts +3 -0
  212. package/dist/utils/themeColors.d.ts.map +1 -1
  213. package/dist/utils/themeColors.js +30 -1
  214. package/dist/utils/themeColors.js.map +1 -1
  215. package/dist/utils/themeHtmlAttributes.d.ts +32 -12
  216. package/dist/utils/themeHtmlAttributes.d.ts.map +1 -1
  217. package/dist/utils/themeHtmlAttributes.js +38 -5
  218. package/dist/utils/themeHtmlAttributes.js.map +1 -1
  219. package/dist/utils/themeUtils.d.ts +3 -5
  220. package/dist/utils/themeUtils.d.ts.map +1 -1
  221. package/dist/utils/themeUtils.js +474 -6
  222. package/dist/utils/themeUtils.js.map +1 -1
  223. package/dist/utils/tweakcnConverter.d.ts +1 -3
  224. package/dist/utils/tweakcnConverter.d.ts.map +1 -1
  225. package/dist/utils/tweakcnConverter.js +1 -3
  226. package/dist/utils/tweakcnConverter.js.map +1 -1
  227. package/package.json +15 -5
  228. package/dist/components/ThemePreview.d.ts +0 -3
  229. package/dist/components/ThemePreview.d.ts.map +0 -1
  230. package/dist/components/ThemePreview.js +0 -321
  231. package/dist/components/ThemePreview.js.map +0 -1
  232. package/dist/plugin.d.ts +0 -2
  233. package/dist/plugin.d.ts.map +0 -1
  234. package/dist/plugin.js +0 -3
  235. package/dist/plugin.js.map +0 -1
package/README.md CHANGED
@@ -1,117 +1,98 @@
1
- # Theme Management Plugin for Payload CMS v3
1
+ # @kilivi/payloadcms-theme-management
2
2
 
3
- A comprehensive theme management plugin for Payload CMS v3 that provides powerful theming capabilities with SSR support, preventing FOUC (Flash of Unstyled Content).
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
- ## 🎉 Version 1.0.2 - Production Ready!
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
- - **✅ Standalone Global Support** - Create separate appearance settings as a global
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
- - 🎨 **Multiple Theme Presets** - Cool, Brutal, Neon, Solar, and more
19
- - 🎯 **Auto-Populate Colors** - Theme selection automatically sets Light/Dark mode colors
20
- - 🌍 **Standalone Global** - Create separate appearance settings global (v0.6.0+)
21
- - 🖼️ **Live Theme Preview** - Real-time preview in admin panel (no configuration needed!)
22
- - 🎨 **Extended Themes** - OKLCH-based themes with full shadcn/ui token support
23
- - 🎨 **Custom Color Palette** - Full HSL color customization (19 semantic tokens)
24
- - 🔤 **Typography Control** - Google Fonts integration for headings, body, and code
25
- - 📐 **Border Radius Presets** - Sharp, Rounded, or Pill styles
26
- - 🌓 **Dark Mode Support** - Built-in light/dark/system mode toggle
27
- - 📊 **Chart Colors** - Data visualization color palette
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 install @kilivi/payloadcms-theme-management
27
+ npm i @kilivi/payloadcms-theme-management
40
28
  # or
41
29
  yarn add @kilivi/payloadcms-theme-management
42
30
  ```
43
31
 
44
- ## Quick Start
32
+ **Peer dependencies:**
45
33
 
46
- ### 1. Add Plugin to Payload Config
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
- #### Option A: Add as Tab to Existing Collection (Default)
41
+ ## Quick start
49
42
 
50
- ```typescript
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
- includeColorModeToggle: true,
76
- enableLogging: true,
62
+ livePreview: true,
77
63
  }),
78
64
  ],
79
65
  })
80
66
  ```
81
67
 
82
- > **Note:** Option A injects an **Appearance Settings** tab into the specified collection (`targetCollection`). If you prefer a separate global (standalone settings), use **Option B** below and set `useStandaloneCollection: true` in the plugin options.
83
-
84
- #### Option B: Create Standalone Global (Separate Settings)
68
+ #### B) As a standalone appearance global
85
69
 
86
- ```typescript
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
- enabled: true,
96
- useStandaloneCollection: true, // Creates a separate global
97
- standaloneCollectionSlug: 'appearance-settings', // Optional: custom slug
98
- standaloneCollectionLabel: 'Appearance Settings', // Optional: custom label
77
+ useStandaloneCollection: true,
78
+ standaloneCollectionSlug: 'appearance-settings',
79
+ standaloneCollectionLabel: 'Appearance Settings',
99
80
  defaultTheme: 'cool',
100
- includeColorModeToggle: true,
101
- enableLogging: true,
81
+ livePreview: true,
102
82
  }),
103
83
  ],
104
84
  })
105
85
  ```
106
86
 
107
- ### 2. Add Server Theme Injection (Next.js)
87
+ After adding the plugin, regenerate the admin import map:
108
88
 
109
- **⚠️ IMPORTANT:** Import from `/server` entry point!
89
+ ```bash
90
+ pnpm payload generate:importmap
91
+ ```
110
92
 
111
- #### If Using Default (Tab in existing collection):
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 { docs } = await payload.find({
123
- collection: 'site-settings', // Your target collection
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={docs[0]?.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
- #### If Using Standalone Global:
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
- ```tsx
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
- export default async function RootLayout({ children }: { children: React.ReactNode }) {
147
- const payload = await getPayload({ config: configPromise })
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
- // Fetch from standalone global
150
- const appearanceSettings = await payload.findGlobal({
151
- slug: 'appearance-settings', // Your standalone global slug
152
- })
128
+ ```tsx
129
+ import { themeFontVariables } from '@kilivi/payloadcms-theme-management/next-fonts'
153
130
 
154
- return (
155
- <html lang="en">
156
- <head>
157
- <ServerThemeInjector themeConfiguration={appearanceSettings?.themeConfiguration} />
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
- #### Cache Optimization with Next.js (Out of the Box)
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
- When using standalone global, the plugin automatically invalidates cache after saving appearance settings using the tag `global_appearance-settings` (or `global_{your-slug}` if you use a custom slug).
141
+ `fontLoading` options: `'self-hosted'` (default), `'google'` (Google Fonts CDN),
142
+ `'none'` (you load the faces, e.g. with `next/font`).
168
143
 
169
- You can optimize your theme fetching by using the built-in server helper:
144
+ ## The Appearance Settings tab
170
145
 
171
- ```tsx
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
- const getCachedTheme = createCachedThemeFetcher({
181
- globalSlug: 'appearance-settings',
182
- revalidate: 3600,
183
- loadAppearanceSettings: async () => {
184
- const payload = await getPayload({ config: configPromise })
185
- return payload.findGlobal({
186
- slug: 'appearance-settings',
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
- export default async function RootLayout({ children }: { children: React.ReactNode }) {
192
- const themeConfiguration = await getCachedTheme()
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
- return (
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
- **Benefits:**
169
+ Each bulk action honours the locks:
206
170
 
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
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
- **Cache Tag Format:**
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
- - Default: `global_appearance-settings`
214
- - Custom slug: `global_{standaloneCollectionSlug}`
181
+ ## Live Preview
215
182
 
216
- #### Injected Revalidation Route
183
+ Payload's Live Preview points at your client pages by slug:
217
184
 
218
- The plugin now injects a Payload endpoint (default: `POST /api/theme/revalidate`) that triggers the same tag/path invalidation logic used by live preview saves.
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
- useStandaloneCollection: true,
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
- Set `cacheRevalidation.injectRoute` to `false` if you want to keep only automatic hook-based invalidation.
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
- ### 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'
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
- ### Server Entry Point
216
+ With `livePreview.injectRoute` enabled, the plugin adds `GET /api/theme/preview` (or your custom `routePath`).
338
217
 
339
- ⚠️ **Import from `/server` for server components only:**
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
- ```typescript
342
- import {
343
- getThemeCriticalCSS,
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
- > **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.
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
- ### Subpath Exports
234
+ ### Multi-tenant example
352
235
 
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'
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
- ## Integration Examples
363
-
364
- ### With Tailwind CSS
260
+ See [MULTI_TENANT_GUIDE.md](./MULTI_TENANT_GUIDE.md) for shared vs per-tenant setups.
365
261
 
366
- #### tailwind.config.mjs
262
+ ## Cache revalidation
367
263
 
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'
264
+ When `useStandaloneCollection: true`, the default cache tag is `global_{standaloneCollectionSlug}`. The plugin can inject `POST /api/theme/revalidate`:
373
265
 
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
- },
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
- 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
- }
277
+ })
794
278
  ```
795
279
 
796
- ### Fetching Theme Configuration
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
- ```typescript
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
- // Cached theme fetcher with automatic invalidation
806
- const getCachedTheme = unstable_cache(
807
- async () => {
290
+ const getCachedTheme = createCachedThemeFetcher({
291
+ globalSlug: 'appearance-settings',
292
+ revalidate: 3600,
293
+ loadAppearanceSettings: async () => {
808
294
  const payload = await getPayload({ config: configPromise })
809
- const global = await payload.findGlobal({
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
- // Use themeConfiguration...
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
- **Legacy Helper (still available):**
313
+ ### Fetching the configuration
829
314
 
830
- If you need the helper function, `fetchThemeConfiguration` is still exported:
831
-
832
- ```typescript
315
+ ```ts
833
316
  import { fetchThemeConfiguration } from '@kilivi/payloadcms-theme-management'
834
317
 
835
- // Simple fetch without caching
836
- const theme = await fetchThemeConfiguration({
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
- However, prefer the native Payload API + `unstable_cache` approach for better performance and automatic invalidation support.
843
-
844
- ### Custom Theme Preset
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
- ```typescript
847
- import type { ThemePreset } from '@kilivi/payloadcms-theme-management'
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
- themePresets: [myTheme],
868
- defaultTheme: 'my-custom-theme',
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
- ## Migrating from Older Versions
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
- ```diff
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
- See [TEST_APP_GUIDE.md](./docs/TEST_APP_GUIDE.md) for instructions on creating a test application.
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
- ## Troubleshooting
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
- ### Type Conflicts: `Type 'SiteSetting' is not assignable to type 'SiteSetting'`
415
+ Full reference: [docs/APPEARANCE_CONTROLS.md](./docs/APPEARANCE_CONTROLS.md)
894
416
 
895
- **Problem:** You see errors about incompatible types even though you're passing the correct data structure.
417
+ ```tsx
418
+ import { fetchThemeConfiguration } from '@kilivi/payloadcms-theme-management'
896
419
 
897
- **Why This Happens:** Your app's generated `payload-types.ts` might have slightly different type definitions than the plugin's (e.g., different font options, field variations).
420
+ const theme = await fetchThemeConfiguration({ collectionSlug: 'site-settings' })
898
421
 
899
- **Solution:** Update to v0.1.11+ which uses generic types instead of strict payload-types:
422
+ const effect = theme?.visualEffects?.effectStyle ?? 'flat'
423
+ const button = theme?.componentStyles?.buttonVariant ?? 'filled'
900
424
 
901
- ```bash
902
- pnpm update @kilivi/payloadcms-theme-management@latest
425
+ return (
426
+ <html data-effect={effect} data-button={button}>
427
+ ...
428
+ </html>
429
+ )
903
430
  ```
904
431
 
905
- The plugin now accepts any compatible theme configuration structure, regardless of your Payload version or type variations. See [TYPE_INDEPENDENCE_GUIDE.md](./docs/TYPE_INDEPENDENCE_GUIDE.md) for technical details.
906
-
907
- ### `Module not found: Can't resolve 'fs/promises'`
432
+ ## Theme presets
908
433
 
909
- **Solution:** Make sure you're using v0.1.9+ and importing server components from `/server`:
434
+ Built-in presets are exported as `allThemePresets`, and style presets as `allStylePresets`:
910
435
 
911
- ```typescript
912
- import { ServerThemeInjector } from '@kilivi/payloadcms-theme-management/server'
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
- ### Theme Not Applying
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
- ### TypeScript Errors
442
+ Full preset reference: [docs/THEME_PRESETS_EXTENDED.md](./docs/THEME_PRESETS_EXTENDED.md)
930
443
 
931
- ```bash
932
- # Regenerate Payload types
933
- pnpm payload generate:types
444
+ ## Visitor components
934
445
 
935
- # Restart TypeScript server
936
- # VS Code: Ctrl+Shift+P → "TypeScript: Restart TS Server"
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
- ## Documentation
451
+ ## Public API
940
452
 
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
453
+ ### Main package
946
454
 
947
- ## Development
948
-
949
- ```bash
950
- # Install dependencies
951
- pnpm install
952
-
953
- # Build the plugin
954
- pnpm build
955
-
956
- # Watch mode for development
957
- pnpm dev
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
- # Clean build artifacts
960
- pnpm clean
471
+ import type {
472
+ SiteThemeConfiguration,
473
+ ThemeComponentStyles,
474
+ ThemeHeroBackground,
475
+ ThemeVisualEffects,
476
+ } from '@kilivi/payloadcms-theme-management'
961
477
  ```
962
478
 
963
- ## Contributing
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
- ### Common Issues
976
-
977
- **Error: "right-hand side of 'in' should be an object, got undefined"**
978
-
979
- This is a known Payload CMS issue related to user preferences. See [TROUBLESHOOTING.md](./docs/TROUBLESHOOTING.md) for solutions.
980
-
981
- **Quick Fix:** Go to `/admin/account` and click "Reset Preferences" at the bottom of the page.
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
- ## License
492
+ Always import server-only helpers from `/server`.
984
493
 
985
- Apache-2.0
494
+ ## Migrating from `@kilivi-dev`
986
495
 
987
- ## Author
496
+ The API is unchanged. Only the package name differs.
988
497
 
989
- Created for Payload CMS v3 applications.
498
+ 1. Swap the dependency:
990
499
 
991
- ## Links
500
+ ```bash
501
+ pnpm remove @kilivi-dev/payloadcms-theme-management
502
+ pnpm add @kilivi/payloadcms-theme-management
503
+ ```
992
504
 
993
- - [NPM Package](https://www.npmjs.com/package/@kilivi/payloadcms-theme-management)
994
- - [GitHub Repository](https://github.com/vitakili/payload-plugins)
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
- ## Changelog
508
+ ```bash
509
+ pnpm payload generate:importmap
510
+ ```
998
511
 
999
- ### v1.0.2 (Latest)
512
+ 4. If you list the package in `next.config` under `transpilePackages`, rename it there too.
1000
513
 
1001
- - ✅ **Added:** Automatic cache invalidation for standalone globals
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
- ### v1.0.0
516
+ ## Notes
1008
517
 
1009
- - ✅ **Added:** Standalone Global Support — create separate appearance settings as a global
1010
- - ✅ **Added:** Auto-populate Light/Dark colors when selecting a theme
1011
- - ✅ **Added:** Multi-tenant support and tenant-aware theme fetching
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
- ### v0.6.0
522
+ ## License
1022
523
 
1023
- - Initial release with core theming functionality
524
+ MIT