@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.
Files changed (214) hide show
  1. package/README.md +318 -847
  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.map +1 -1
  12. package/dist/components/ServerThemeInjector.js +41 -14
  13. package/dist/components/ServerThemeInjector.js.map +1 -1
  14. package/dist/components/ThemeLivePreviewSync.d.ts +1 -0
  15. package/dist/components/ThemeLivePreviewSync.d.ts.map +1 -1
  16. package/dist/components/ThemeLivePreviewSync.js +1 -1
  17. package/dist/components/ThemeLivePreviewSync.js.map +1 -1
  18. package/dist/components/ThemeSwitcher.d.ts +36 -0
  19. package/dist/components/ThemeSwitcher.d.ts.map +1 -0
  20. package/dist/components/ThemeSwitcher.js +89 -0
  21. package/dist/components/ThemeSwitcher.js.map +1 -0
  22. package/dist/components/typographyPreviewUtils.d.ts.map +1 -1
  23. package/dist/components/typographyPreviewUtils.js +8 -1
  24. package/dist/components/typographyPreviewUtils.js.map +1 -1
  25. package/dist/constants/devicePreviews.d.ts +35 -0
  26. package/dist/constants/devicePreviews.d.ts.map +1 -0
  27. package/dist/constants/devicePreviews.js +43 -0
  28. package/dist/constants/devicePreviews.js.map +1 -0
  29. package/dist/constants/themeFonts.js +9 -9
  30. package/dist/constants/themeFonts.js.map +1 -1
  31. package/dist/extended-presets.d.ts +0 -4
  32. package/dist/extended-presets.d.ts.map +1 -1
  33. package/dist/extended-presets.js +1 -18
  34. package/dist/extended-presets.js.map +1 -1
  35. package/dist/extended-themes.d.ts.map +1 -1
  36. package/dist/extended-themes.js +428 -0
  37. package/dist/extended-themes.js.map +1 -1
  38. package/dist/fields/AccessibilityAuditField.d.ts +2 -0
  39. package/dist/fields/AccessibilityAuditField.d.ts.map +1 -0
  40. package/dist/fields/AccessibilityAuditField.js +303 -0
  41. package/dist/fields/AccessibilityAuditField.js.map +1 -0
  42. package/dist/fields/AppearancePreviewField.d.ts +2 -0
  43. package/dist/fields/AppearancePreviewField.d.ts.map +1 -0
  44. package/dist/fields/AppearancePreviewField.js +763 -0
  45. package/dist/fields/AppearancePreviewField.js.map +1 -0
  46. package/dist/fields/ColorPickerField.d.ts.map +1 -1
  47. package/dist/fields/ColorPickerField.js +89 -85
  48. package/dist/fields/ColorPickerField.js.map +1 -1
  49. package/dist/fields/FontSelectField.d.ts +6 -0
  50. package/dist/fields/FontSelectField.d.ts.map +1 -1
  51. package/dist/fields/FontSelectField.js +195 -122
  52. package/dist/fields/FontSelectField.js.map +1 -1
  53. package/dist/fields/PaletteGeneratorField.d.ts +7 -0
  54. package/dist/fields/PaletteGeneratorField.d.ts.map +1 -0
  55. package/dist/fields/PaletteGeneratorField.js +327 -0
  56. package/dist/fields/PaletteGeneratorField.js.map +1 -0
  57. package/dist/fields/StylePresetField.d.ts +3 -0
  58. package/dist/fields/StylePresetField.d.ts.map +1 -0
  59. package/dist/fields/StylePresetField.js +462 -0
  60. package/dist/fields/StylePresetField.js.map +1 -0
  61. package/dist/fields/ThemeColorPickerField.css +115 -35
  62. package/dist/fields/ThemeColorPickerField.d.ts.map +1 -1
  63. package/dist/fields/ThemeColorPickerField.js +204 -70
  64. package/dist/fields/ThemeColorPickerField.js.map +1 -1
  65. package/dist/fields/ThemeExportField.d.ts +6 -0
  66. package/dist/fields/ThemeExportField.d.ts.map +1 -0
  67. package/dist/fields/ThemeExportField.js +202 -0
  68. package/dist/fields/ThemeExportField.js.map +1 -0
  69. package/dist/fields/ThemePresetImportField.d.ts.map +1 -1
  70. package/dist/fields/ThemePresetImportField.js +56 -25
  71. package/dist/fields/ThemePresetImportField.js.map +1 -1
  72. package/dist/fields/ThemePreviewField.css +382 -5
  73. package/dist/fields/ThemePreviewField.d.ts.map +1 -1
  74. package/dist/fields/ThemePreviewField.js +268 -170
  75. package/dist/fields/ThemePreviewField.js.map +1 -1
  76. package/dist/fields/ThemeTokenSelectField.d.ts.map +1 -1
  77. package/dist/fields/ThemeTokenSelectField.js +278 -80
  78. package/dist/fields/ThemeTokenSelectField.js.map +1 -1
  79. package/dist/fields/colorFieldHelpers.d.ts +90 -0
  80. package/dist/fields/colorFieldHelpers.d.ts.map +1 -0
  81. package/dist/fields/colorFieldHelpers.js +133 -0
  82. package/dist/fields/colorFieldHelpers.js.map +1 -0
  83. package/dist/fields/colorModeFields.d.ts +1 -1
  84. package/dist/fields/colorModeFields.d.ts.map +1 -1
  85. package/dist/fields/colorModeFields.js +49 -7
  86. package/dist/fields/colorModeFields.js.map +1 -1
  87. package/dist/fields/themeConfigurationField.d.ts +8 -1
  88. package/dist/fields/themeConfigurationField.d.ts.map +1 -1
  89. package/dist/fields/themeConfigurationField.js +961 -29
  90. package/dist/fields/themeConfigurationField.js.map +1 -1
  91. package/dist/globals/ThemeSettings/FontPicker/FontPreview.d.ts.map +1 -1
  92. package/dist/globals/ThemeSettings/FontPicker/FontPreview.js +8 -4
  93. package/dist/globals/ThemeSettings/FontPicker/FontPreview.js.map +1 -1
  94. package/dist/globals/ThemeSettings/FontPicker/index.d.ts.map +1 -1
  95. package/dist/globals/ThemeSettings/FontPicker/index.js +81 -26
  96. package/dist/globals/ThemeSettings/FontPicker/index.js.map +1 -1
  97. package/dist/globals/ThemeSettings/FontPicker/styles.css +98 -41
  98. package/dist/globals/ThemeSettings/FontPicker/translations.d.ts +6 -0
  99. package/dist/globals/ThemeSettings/FontPicker/translations.d.ts.map +1 -1
  100. package/dist/globals/ThemeSettings/FontPicker/translations.js +14 -2
  101. package/dist/globals/ThemeSettings/FontPicker/translations.js.map +1 -1
  102. package/dist/globals/ThemeSettings/config.d.ts.map +1 -1
  103. package/dist/globals/ThemeSettings/config.js +76 -79
  104. package/dist/globals/ThemeSettings/config.js.map +1 -1
  105. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.d.ts.map +1 -1
  106. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js +63 -62
  107. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js.map +1 -1
  108. package/dist/globals/ThemeSettings/fields/TypographyInfoField.d.ts.map +1 -1
  109. package/dist/globals/ThemeSettings/fields/TypographyInfoField.js +22 -25
  110. package/dist/globals/ThemeSettings/fields/TypographyInfoField.js.map +1 -1
  111. package/dist/hooks/useAppearanceSession.d.ts +50 -0
  112. package/dist/hooks/useAppearanceSession.d.ts.map +1 -0
  113. package/dist/hooks/useAppearanceSession.js +91 -0
  114. package/dist/hooks/useAppearanceSession.js.map +1 -0
  115. package/dist/hooks/useThemeTranslations.d.ts +13 -0
  116. package/dist/hooks/useThemeTranslations.d.ts.map +1 -0
  117. package/dist/hooks/useThemeTranslations.js +22 -0
  118. package/dist/hooks/useThemeTranslations.js.map +1 -0
  119. package/dist/i18n.d.ts +53 -0
  120. package/dist/i18n.d.ts.map +1 -0
  121. package/dist/i18n.js +53 -0
  122. package/dist/i18n.js.map +1 -0
  123. package/dist/index.d.ts +10 -4
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +50 -29
  126. package/dist/index.js.map +1 -1
  127. package/dist/payload-types.d.ts +24 -0
  128. package/dist/payload-types.d.ts.map +1 -1
  129. package/dist/payload-types.js.map +1 -1
  130. package/dist/presets.d.ts +8 -0
  131. package/dist/presets.d.ts.map +1 -1
  132. package/dist/presets.js.map +1 -1
  133. package/dist/providers/Theme/index.d.ts.map +1 -1
  134. package/dist/providers/Theme/index.js +75 -11
  135. package/dist/providers/Theme/index.js.map +1 -1
  136. package/dist/providers/Theme/themeConfig.d.ts.map +1 -1
  137. package/dist/providers/Theme/themeConfig.js +104 -0
  138. package/dist/providers/Theme/themeConfig.js.map +1 -1
  139. package/dist/providers/Theme/types.d.ts +1 -1
  140. package/dist/providers/Theme/types.d.ts.map +1 -1
  141. package/dist/providers/Theme/types.js +10 -1
  142. package/dist/providers/Theme/types.js.map +1 -1
  143. package/dist/style-presets.d.ts +66 -0
  144. package/dist/style-presets.d.ts.map +1 -0
  145. package/dist/style-presets.js +556 -0
  146. package/dist/style-presets.js.map +1 -0
  147. package/dist/translations.d.ts +159 -1
  148. package/dist/translations.d.ts.map +1 -1
  149. package/dist/translations.js +310 -9
  150. package/dist/translations.js.map +1 -1
  151. package/dist/tweakcn-special-themes.d.ts.map +1 -1
  152. package/dist/tweakcn-special-themes.js +585 -9
  153. package/dist/tweakcn-special-themes.js.map +1 -1
  154. package/dist/types/css.d.js +3 -0
  155. package/dist/types/css.d.js.map +1 -0
  156. package/dist/types.d.ts +26 -0
  157. package/dist/types.d.ts.map +1 -1
  158. package/dist/types.js +1 -1
  159. package/dist/types.js.map +1 -1
  160. package/dist/utilities/getThemeSettings.d.ts.map +1 -1
  161. package/dist/utilities/getThemeSettings.js +2 -14
  162. package/dist/utilities/getThemeSettings.js.map +1 -1
  163. package/dist/utils/contrast.d.ts +63 -0
  164. package/dist/utils/contrast.d.ts.map +1 -0
  165. package/dist/utils/contrast.js +158 -0
  166. package/dist/utils/contrast.js.map +1 -0
  167. package/dist/utils/exportTokens.d.ts +30 -0
  168. package/dist/utils/exportTokens.d.ts.map +1 -0
  169. package/dist/utils/exportTokens.js +183 -0
  170. package/dist/utils/exportTokens.js.map +1 -0
  171. package/dist/utils/generatePalette.d.ts +46 -0
  172. package/dist/utils/generatePalette.d.ts.map +1 -0
  173. package/dist/utils/generatePalette.js +127 -0
  174. package/dist/utils/generatePalette.js.map +1 -0
  175. package/dist/utils/generateThemeCSS.d.ts.map +1 -1
  176. package/dist/utils/generateThemeCSS.js +2 -40
  177. package/dist/utils/generateThemeCSS.js.map +1 -1
  178. package/dist/utils/livePreviewThemeRuntime.d.ts +16 -2
  179. package/dist/utils/livePreviewThemeRuntime.d.ts.map +1 -1
  180. package/dist/utils/livePreviewThemeRuntime.js +37 -14
  181. package/dist/utils/livePreviewThemeRuntime.js.map +1 -1
  182. package/dist/utils/localizedText.d.ts +8 -0
  183. package/dist/utils/localizedText.d.ts.map +1 -0
  184. package/dist/utils/localizedText.js +21 -0
  185. package/dist/utils/localizedText.js.map +1 -0
  186. package/dist/utils/resolveThemeConfiguration.d.ts +45 -1
  187. package/dist/utils/resolveThemeConfiguration.d.ts.map +1 -1
  188. package/dist/utils/resolveThemeConfiguration.js +12 -5
  189. package/dist/utils/resolveThemeConfiguration.js.map +1 -1
  190. package/dist/utils/themeColors.d.ts +3 -0
  191. package/dist/utils/themeColors.d.ts.map +1 -1
  192. package/dist/utils/themeColors.js +30 -1
  193. package/dist/utils/themeColors.js.map +1 -1
  194. package/dist/utils/themeHtmlAttributes.d.ts +32 -12
  195. package/dist/utils/themeHtmlAttributes.d.ts.map +1 -1
  196. package/dist/utils/themeHtmlAttributes.js +38 -5
  197. package/dist/utils/themeHtmlAttributes.js.map +1 -1
  198. package/dist/utils/themeUtils.d.ts +3 -5
  199. package/dist/utils/themeUtils.d.ts.map +1 -1
  200. package/dist/utils/themeUtils.js +416 -6
  201. package/dist/utils/themeUtils.js.map +1 -1
  202. package/dist/utils/tweakcnConverter.d.ts +1 -3
  203. package/dist/utils/tweakcnConverter.d.ts.map +1 -1
  204. package/dist/utils/tweakcnConverter.js +1 -3
  205. package/dist/utils/tweakcnConverter.js.map +1 -1
  206. package/package.json +11 -5
  207. package/dist/components/ThemePreview.d.ts +0 -3
  208. package/dist/components/ThemePreview.d.ts.map +0 -1
  209. package/dist/components/ThemePreview.js +0 -321
  210. package/dist/components/ThemePreview.js.map +0 -1
  211. package/dist/plugin.d.ts +0 -2
  212. package/dist/plugin.d.ts.map +0 -1
  213. package/dist/plugin.js +0 -3
  214. package/dist/plugin.js.map +0 -1
@@ -0,0 +1,15 @@
1
+ import { type AppearanceSource } from '../hooks/useAppearanceSession.js';
2
+ export declare const formatTemplate: (template: string, values: Record<string, string | number>) => string;
3
+ /**
4
+ * "Keep my: Colours · Fonts · Style" toggles. Locks apply to every bulk action
5
+ * (theme preset, style preset, palette generator) for the current editing session.
6
+ */
7
+ export declare function AppearanceLocks(): import("react").JSX.Element;
8
+ /**
9
+ * One-step undo for the last bulk change made from `source`. Stays for 10 s,
10
+ * paused while the pointer or keyboard focus is inside it.
11
+ */
12
+ export declare function AppearanceUndoBar({ sources }: {
13
+ sources: AppearanceSource[];
14
+ }): import("react").JSX.Element;
15
+ //# sourceMappingURL=AppearanceSessionControls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppearanceSessionControls.d.ts","sourceRoot":"","sources":["../../src/components/AppearanceSessionControls.tsx"],"names":[],"mappings":"AAKA,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,kCAAkC,CAAA;AAMzC,eAAO,MAAM,cAAc,GAAI,UAAU,MAAM,EAAE,QAAQ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,WACF,CAAA;AAEtF;;;GAGG;AACH,wBAAgB,eAAe,gCA8B9B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,gBAAgB,EAAE,CAAA;CAAE,+BA+D7E"}
@@ -0,0 +1,152 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useForm } from '@payloadcms/ui';
4
+ import { Lock, LockOpen, Undo2, X } from 'lucide-react';
5
+ import { useEffect, useRef, useState } from 'react';
6
+ import { clearAppearanceUndo, toggleAppearanceLock, useAppearanceSession } from '../hooks/useAppearanceSession.js';
7
+ import { useThemeTranslations } from '../hooks/useThemeTranslations.js';
8
+ const LOCKS = [
9
+ 'colors',
10
+ 'fonts',
11
+ 'style'
12
+ ];
13
+ const UNDO_TIMEOUT_MS = 10_000;
14
+ export const formatTemplate = (template, values)=>template.replace(/\{(\w+)\}/g, (match, key)=>String(values[key] ?? match));
15
+ /**
16
+ * "Keep my: Colours · Fonts · Style" toggles. Locks apply to every bulk action
17
+ * (theme preset, style preset, palette generator) for the current editing session.
18
+ */ export function AppearanceLocks() {
19
+ const { locks } = useAppearanceSession();
20
+ const t = useThemeTranslations().appearance;
21
+ return /*#__PURE__*/ _jsxs("div", {
22
+ className: "tm-appearance-locks",
23
+ children: [
24
+ /*#__PURE__*/ _jsxs("div", {
25
+ className: "tm-appearance-locks__row",
26
+ role: "group",
27
+ "aria-label": t.locksLabel,
28
+ children: [
29
+ /*#__PURE__*/ _jsx("span", {
30
+ className: "tm-appearance-locks__label",
31
+ "aria-hidden": "true",
32
+ children: t.locksLabel
33
+ }),
34
+ LOCKS.map((lock)=>{
35
+ const locked = locks[lock];
36
+ const Icon = locked ? Lock : LockOpen;
37
+ return /*#__PURE__*/ _jsxs("button", {
38
+ type: "button",
39
+ className: `tm-appearance-lock${locked ? ' is-locked' : ''}`,
40
+ "aria-pressed": locked,
41
+ onClick: ()=>toggleAppearanceLock(lock),
42
+ children: [
43
+ /*#__PURE__*/ _jsx(Icon, {
44
+ size: 14,
45
+ "aria-hidden": true
46
+ }),
47
+ t[lock]
48
+ ]
49
+ }, lock);
50
+ })
51
+ ]
52
+ }),
53
+ /*#__PURE__*/ _jsx("p", {
54
+ className: "tm-appearance-locks__hint",
55
+ children: t.locksHint
56
+ })
57
+ ]
58
+ });
59
+ }
60
+ /**
61
+ * One-step undo for the last bulk change made from `source`. Stays for 10 s,
62
+ * paused while the pointer or keyboard focus is inside it.
63
+ */ export function AppearanceUndoBar({ sources }) {
64
+ const { undo } = useAppearanceSession();
65
+ const { dispatchFields, setModified } = useForm();
66
+ const t = useThemeTranslations().appearance;
67
+ const [paused, setPaused] = useState(false);
68
+ const [announcement, setAnnouncement] = useState('');
69
+ const barRef = useRef(null);
70
+ const visible = undo !== null && sources.includes(undo.source);
71
+ useEffect(()=>{
72
+ if (!visible || paused || !undo) return;
73
+ const timer = window.setTimeout(()=>clearAppearanceUndo(undo.id), UNDO_TIMEOUT_MS);
74
+ return ()=>window.clearTimeout(timer);
75
+ }, [
76
+ visible,
77
+ paused,
78
+ undo
79
+ ]);
80
+ useEffect(()=>{
81
+ if (visible && undo) setAnnouncement(formatTemplate(t.applied, {
82
+ name: undo.label
83
+ }));
84
+ }, [
85
+ visible,
86
+ undo,
87
+ t.applied
88
+ ]);
89
+ const handleUndo = ()=>{
90
+ if (!undo) return;
91
+ undo.entries.forEach(({ path, value })=>dispatchFields({
92
+ type: 'UPDATE',
93
+ path,
94
+ value
95
+ }));
96
+ setModified(true);
97
+ clearAppearanceUndo(undo.id);
98
+ setAnnouncement(t.undone);
99
+ };
100
+ return /*#__PURE__*/ _jsxs(_Fragment, {
101
+ children: [
102
+ /*#__PURE__*/ _jsx("span", {
103
+ className: "tm-sr-only",
104
+ role: "status",
105
+ "aria-live": "polite",
106
+ children: announcement
107
+ }),
108
+ visible && undo ? /*#__PURE__*/ _jsxs("div", {
109
+ ref: barRef,
110
+ className: "tm-appearance-undo",
111
+ onPointerEnter: ()=>setPaused(true),
112
+ onPointerLeave: ()=>setPaused(false),
113
+ onFocus: ()=>setPaused(true),
114
+ onBlur: (event)=>{
115
+ if (!barRef.current?.contains(event.relatedTarget)) setPaused(false);
116
+ },
117
+ children: [
118
+ /*#__PURE__*/ _jsx("span", {
119
+ className: "tm-appearance-undo__text",
120
+ children: formatTemplate(t.applied, {
121
+ name: undo.label
122
+ })
123
+ }),
124
+ /*#__PURE__*/ _jsxs("button", {
125
+ type: "button",
126
+ className: "tm-appearance-undo__action",
127
+ onClick: handleUndo,
128
+ children: [
129
+ /*#__PURE__*/ _jsx(Undo2, {
130
+ size: 14,
131
+ "aria-hidden": true
132
+ }),
133
+ t.undo
134
+ ]
135
+ }),
136
+ /*#__PURE__*/ _jsx("button", {
137
+ type: "button",
138
+ className: "tm-appearance-undo__dismiss",
139
+ "aria-label": t.dismiss,
140
+ onClick: ()=>clearAppearanceUndo(undo.id),
141
+ children: /*#__PURE__*/ _jsx(X, {
142
+ size: 14,
143
+ "aria-hidden": true
144
+ })
145
+ })
146
+ ]
147
+ }) : null
148
+ ]
149
+ });
150
+ }
151
+
152
+ //# sourceMappingURL=AppearanceSessionControls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/AppearanceSessionControls.tsx"],"sourcesContent":["'use client'\n\nimport { useForm } from '@payloadcms/ui'\nimport { Lock, LockOpen, Undo2, X } from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport {\n clearAppearanceUndo,\n toggleAppearanceLock,\n useAppearanceSession,\n type AppearanceLock,\n type AppearanceSource,\n} from '../hooks/useAppearanceSession.js'\nimport { useThemeTranslations } from '../hooks/useThemeTranslations.js'\n\nconst LOCKS: AppearanceLock[] = ['colors', 'fonts', 'style']\nconst UNDO_TIMEOUT_MS = 10_000\n\nexport const formatTemplate = (template: string, values: Record<string, string | number>) =>\n template.replace(/\\{(\\w+)\\}/g, (match, key: string) => String(values[key] ?? match))\n\n/**\n * \"Keep my: Colours · Fonts · Style\" toggles. Locks apply to every bulk action\n * (theme preset, style preset, palette generator) for the current editing session.\n */\nexport function AppearanceLocks() {\n const { locks } = useAppearanceSession()\n const t = useThemeTranslations().appearance\n\n return (\n <div className=\"tm-appearance-locks\">\n <div className=\"tm-appearance-locks__row\" role=\"group\" aria-label={t.locksLabel}>\n <span className=\"tm-appearance-locks__label\" aria-hidden=\"true\">\n {t.locksLabel}\n </span>\n {LOCKS.map((lock) => {\n const locked = locks[lock]\n const Icon = locked ? Lock : LockOpen\n return (\n <button\n key={lock}\n type=\"button\"\n className={`tm-appearance-lock${locked ? ' is-locked' : ''}`}\n aria-pressed={locked}\n onClick={() => toggleAppearanceLock(lock)}\n >\n <Icon size={14} aria-hidden />\n {t[lock]}\n </button>\n )\n })}\n </div>\n <p className=\"tm-appearance-locks__hint\">{t.locksHint}</p>\n </div>\n )\n}\n\n/**\n * One-step undo for the last bulk change made from `source`. Stays for 10 s,\n * paused while the pointer or keyboard focus is inside it.\n */\nexport function AppearanceUndoBar({ sources }: { sources: AppearanceSource[] }) {\n const { undo } = useAppearanceSession()\n const { dispatchFields, setModified } = useForm()\n const t = useThemeTranslations().appearance\n const [paused, setPaused] = useState(false)\n const [announcement, setAnnouncement] = useState('')\n const barRef = useRef<HTMLDivElement>(null)\n const visible = undo !== null && sources.includes(undo.source)\n\n useEffect(() => {\n if (!visible || paused || !undo) return\n const timer = window.setTimeout(() => clearAppearanceUndo(undo.id), UNDO_TIMEOUT_MS)\n return () => window.clearTimeout(timer)\n }, [visible, paused, undo])\n\n useEffect(() => {\n if (visible && undo) setAnnouncement(formatTemplate(t.applied, { name: undo.label }))\n }, [visible, undo, t.applied])\n\n const handleUndo = () => {\n if (!undo) return\n undo.entries.forEach(({ path, value }) => dispatchFields({ type: 'UPDATE', path, value }))\n setModified(true)\n clearAppearanceUndo(undo.id)\n setAnnouncement(t.undone)\n }\n\n return (\n <>\n {/* Always-mounted live region so both \"applied\" and \"reverted\" are announced. */}\n <span className=\"tm-sr-only\" role=\"status\" aria-live=\"polite\">\n {announcement}\n </span>\n {visible && undo ? (\n <div\n ref={barRef}\n className=\"tm-appearance-undo\"\n onPointerEnter={() => setPaused(true)}\n onPointerLeave={() => setPaused(false)}\n onFocus={() => setPaused(true)}\n onBlur={(event) => {\n if (!barRef.current?.contains(event.relatedTarget as Node)) setPaused(false)\n }}\n >\n <span className=\"tm-appearance-undo__text\">\n {formatTemplate(t.applied, { name: undo.label })}\n </span>\n <button type=\"button\" className=\"tm-appearance-undo__action\" onClick={handleUndo}>\n <Undo2 size={14} aria-hidden />\n {t.undo}\n </button>\n <button\n type=\"button\"\n className=\"tm-appearance-undo__dismiss\"\n aria-label={t.dismiss}\n onClick={() => clearAppearanceUndo(undo.id)}\n >\n <X size={14} aria-hidden />\n </button>\n </div>\n ) : null}\n </>\n )\n}\n"],"names":["useForm","Lock","LockOpen","Undo2","X","useEffect","useRef","useState","clearAppearanceUndo","toggleAppearanceLock","useAppearanceSession","useThemeTranslations","LOCKS","UNDO_TIMEOUT_MS","formatTemplate","template","values","replace","match","key","String","AppearanceLocks","locks","t","appearance","div","className","role","aria-label","locksLabel","span","aria-hidden","map","lock","locked","Icon","button","type","aria-pressed","onClick","size","p","locksHint","AppearanceUndoBar","sources","undo","dispatchFields","setModified","paused","setPaused","announcement","setAnnouncement","barRef","visible","includes","source","timer","window","setTimeout","id","clearTimeout","applied","name","label","handleUndo","entries","forEach","path","value","undone","aria-live","ref","onPointerEnter","onPointerLeave","onFocus","onBlur","event","current","contains","relatedTarget","dismiss"],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /* View Transitions ripple for ColorModeToggle.
2
+ Disable the default cross-fade so the clip-path circle reveal is clean. */
3
+ ::view-transition-old(root),
4
+ ::view-transition-new(root) {
5
+ animation: none;
6
+ mix-blend-mode: normal;
7
+ }
8
+
9
+ /* New (incoming) theme sits on top and is revealed by the expanding circle. */
10
+ ::view-transition-new(root) {
11
+ z-index: 1;
12
+ }
13
+ ::view-transition-old(root) {
14
+ z-index: 0;
15
+ }
16
+
17
+ .tm-color-mode-toggle {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ padding: 0;
22
+ margin: 0;
23
+ border: none;
24
+ background: transparent;
25
+ color: inherit;
26
+ cursor: pointer;
27
+ line-height: 0;
28
+ -webkit-tap-highlight-color: transparent;
29
+ }
30
+
31
+ .tm-color-mode-toggle:focus-visible {
32
+ outline: 2px solid currentColor;
33
+ outline-offset: 3px;
34
+ border-radius: 6px;
35
+ }
36
+
37
+ @media (prefers-reduced-motion: reduce) {
38
+ ::view-transition-old(root),
39
+ ::view-transition-new(root) {
40
+ animation: none !important;
41
+ }
42
+ }
@@ -0,0 +1,40 @@
1
+ import { type LucideIcon } from 'lucide-react';
2
+ import React from 'react';
3
+ import type { Mode } from '../providers/Theme/types.js';
4
+ import './ColorModeToggle.css';
5
+ export interface ColorModeToggleProps {
6
+ /** Extra class applied to the button (alongside `tm-color-mode-toggle`). */
7
+ className?: string;
8
+ style?: React.CSSProperties;
9
+ /** Icon size in px. Default 20. */
10
+ size?: number;
11
+ /**
12
+ * Cycle light → dark → auto on each click instead of a simple light/dark
13
+ * toggle. Default false.
14
+ */
15
+ includeAuto?: boolean;
16
+ /** Disable the View Transitions ripple (falls back to an instant switch). */
17
+ disableAnimation?: boolean;
18
+ /** Override the icons used for each mode. */
19
+ icons?: Partial<Record<Mode, LucideIcon>>;
20
+ 'aria-label'?: string;
21
+ }
22
+ /**
23
+ * Drop-in, accessible light/dark (optionally auto) toggle for the front-end.
24
+ *
25
+ * Renders a single icon button wired to the plugin's `ThemeProvider`. When the
26
+ * browser supports the View Transitions API (and the user hasn't requested
27
+ * reduced motion) the theme change animates as a circular "ripple" expanding
28
+ * from the click position.
29
+ *
30
+ * Returns `null` when the admin has disabled the color-mode toggle.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * import { ColorModeToggle } from '@kilivi/payloadcms-theme-management/components/ColorModeToggle'
35
+ * <ColorModeToggle />
36
+ * ```
37
+ */
38
+ export declare const ColorModeToggle: React.FC<ColorModeToggleProps>;
39
+ export default ColorModeToggle;
40
+ //# sourceMappingURL=ColorModeToggle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ColorModeToggle.d.ts","sourceRoot":"","sources":["../../src/components/ColorModeToggle.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAClE,OAAO,KAAsB,MAAM,OAAO,CAAA;AAE1C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AACvD,OAAO,uBAAuB,CAAA;AAE9B,MAAM,WAAW,oBAAoB;IACnC,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAA;IAC3B,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,6CAA6C;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAA;IACzC,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAUD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAqG1D,CAAA;AAED,eAAe,eAAe,CAAA"}
@@ -0,0 +1,111 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Monitor, Moon, Sun } from 'lucide-react';
4
+ import React, { useCallback } from 'react';
5
+ import { useTheme } from '../providers/Theme/index.js';
6
+ import './ColorModeToggle.css';
7
+ const DEFAULT_ICONS = {
8
+ light: Sun,
9
+ dark: Moon,
10
+ auto: Monitor
11
+ };
12
+ /**
13
+ * Drop-in, accessible light/dark (optionally auto) toggle for the front-end.
14
+ *
15
+ * Renders a single icon button wired to the plugin's `ThemeProvider`. When the
16
+ * browser supports the View Transitions API (and the user hasn't requested
17
+ * reduced motion) the theme change animates as a circular "ripple" expanding
18
+ * from the click position.
19
+ *
20
+ * Returns `null` when the admin has disabled the color-mode toggle.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * import { ColorModeToggle } from '@kilivi/payloadcms-theme-management/components/ColorModeToggle'
25
+ * <ColorModeToggle />
26
+ * ```
27
+ */ export const ColorModeToggle = ({ className, style, size = 20, includeAuto = false, disableAnimation = false, icons, 'aria-label': ariaLabel })=>{
28
+ const { mode, setMode, isColorModeToggleAllowed } = useTheme();
29
+ const resolvedCurrent = useCallback(()=>{
30
+ if (typeof document !== 'undefined') {
31
+ const attr = document.documentElement.getAttribute('data-theme-mode');
32
+ if (attr === 'dark' || attr === 'light') return attr;
33
+ }
34
+ return mode === 'dark' ? 'dark' : 'light';
35
+ }, [
36
+ mode
37
+ ]);
38
+ const getNextMode = useCallback(()=>{
39
+ if (includeAuto) {
40
+ if (mode === 'light') return 'dark';
41
+ if (mode === 'dark') return 'auto';
42
+ return 'light';
43
+ }
44
+ return resolvedCurrent() === 'dark' ? 'light' : 'dark';
45
+ }, [
46
+ includeAuto,
47
+ mode,
48
+ resolvedCurrent
49
+ ]);
50
+ const handleClick = useCallback((event)=>{
51
+ const next = getNextMode();
52
+ const prefersReduced = typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
53
+ const canAnimate = !disableAnimation && !prefersReduced && typeof document !== 'undefined' && 'startViewTransition' in document;
54
+ if (!canAnimate) {
55
+ setMode(next);
56
+ return;
57
+ }
58
+ const x = event.clientX;
59
+ const y = event.clientY;
60
+ const endRadius = Math.hypot(Math.max(x, window.innerWidth - x), Math.max(y, window.innerHeight - y));
61
+ const startViewTransition = document.startViewTransition;
62
+ const transition = startViewTransition(()=>{
63
+ setMode(next);
64
+ });
65
+ transition.ready.then(()=>{
66
+ document.documentElement.animate({
67
+ clipPath: [
68
+ `circle(0px at ${x}px ${y}px)`,
69
+ `circle(${endRadius}px at ${x}px ${y}px)`
70
+ ]
71
+ }, {
72
+ duration: 450,
73
+ easing: 'ease-in-out',
74
+ pseudoElement: '::view-transition-new(root)'
75
+ });
76
+ }).catch(()=>{
77
+ /* animation is best-effort; the mode change already applied */ });
78
+ }, [
79
+ disableAnimation,
80
+ getNextMode,
81
+ setMode
82
+ ]);
83
+ // Honour the admin "allow color mode toggle" switch.
84
+ if (isColorModeToggleAllowed === false) {
85
+ return null;
86
+ }
87
+ const iconSet = {
88
+ ...DEFAULT_ICONS,
89
+ ...icons
90
+ };
91
+ const activeMode = mode ?? 'light';
92
+ const Icon = iconSet[activeMode] ?? DEFAULT_ICONS.light;
93
+ return /*#__PURE__*/ _jsx("button", {
94
+ type: "button",
95
+ onClick: handleClick,
96
+ className: [
97
+ 'tm-color-mode-toggle',
98
+ className
99
+ ].filter(Boolean).join(' '),
100
+ style: style,
101
+ "aria-label": ariaLabel ?? `Switch color mode (current: ${activeMode})`,
102
+ title: ariaLabel ?? `Color mode: ${activeMode}`,
103
+ children: /*#__PURE__*/ _jsx(Icon, {
104
+ size: size,
105
+ "aria-hidden": true
106
+ })
107
+ });
108
+ };
109
+ export default ColorModeToggle;
110
+
111
+ //# sourceMappingURL=ColorModeToggle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/ColorModeToggle.tsx"],"sourcesContent":["'use client'\n\nimport { Monitor, Moon, Sun, type LucideIcon } from 'lucide-react'\nimport React, { useCallback } from 'react'\nimport { useTheme } from '../providers/Theme/index.js'\nimport type { Mode } from '../providers/Theme/types.js'\nimport './ColorModeToggle.css'\n\nexport interface ColorModeToggleProps {\n /** Extra class applied to the button (alongside `tm-color-mode-toggle`). */\n className?: string\n style?: React.CSSProperties\n /** Icon size in px. Default 20. */\n size?: number\n /**\n * Cycle light → dark → auto on each click instead of a simple light/dark\n * toggle. Default false.\n */\n includeAuto?: boolean\n /** Disable the View Transitions ripple (falls back to an instant switch). */\n disableAnimation?: boolean\n /** Override the icons used for each mode. */\n icons?: Partial<Record<Mode, LucideIcon>>\n 'aria-label'?: string\n}\n\nconst DEFAULT_ICONS: Record<Mode, LucideIcon> = {\n light: Sun,\n dark: Moon,\n auto: Monitor,\n}\n\ntype StartViewTransition = (callback: () => void) => { ready: Promise<void> }\n\n/**\n * Drop-in, accessible light/dark (optionally auto) toggle for the front-end.\n *\n * Renders a single icon button wired to the plugin's `ThemeProvider`. When the\n * browser supports the View Transitions API (and the user hasn't requested\n * reduced motion) the theme change animates as a circular \"ripple\" expanding\n * from the click position.\n *\n * Returns `null` when the admin has disabled the color-mode toggle.\n *\n * @example\n * ```tsx\n * import { ColorModeToggle } from '@kilivi/payloadcms-theme-management/components/ColorModeToggle'\n * <ColorModeToggle />\n * ```\n */\nexport const ColorModeToggle: React.FC<ColorModeToggleProps> = ({\n className,\n style,\n size = 20,\n includeAuto = false,\n disableAnimation = false,\n icons,\n 'aria-label': ariaLabel,\n}) => {\n const { mode, setMode, isColorModeToggleAllowed } = useTheme()\n\n const resolvedCurrent = useCallback((): 'light' | 'dark' => {\n if (typeof document !== 'undefined') {\n const attr = document.documentElement.getAttribute('data-theme-mode')\n if (attr === 'dark' || attr === 'light') return attr\n }\n return mode === 'dark' ? 'dark' : 'light'\n }, [mode])\n\n const getNextMode = useCallback((): Mode => {\n if (includeAuto) {\n if (mode === 'light') return 'dark'\n if (mode === 'dark') return 'auto'\n return 'light'\n }\n return resolvedCurrent() === 'dark' ? 'light' : 'dark'\n }, [includeAuto, mode, resolvedCurrent])\n\n const handleClick = useCallback(\n (event: React.MouseEvent<HTMLButtonElement>) => {\n const next = getNextMode()\n const prefersReduced =\n typeof window !== 'undefined' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches\n const canAnimate =\n !disableAnimation &&\n !prefersReduced &&\n typeof document !== 'undefined' &&\n 'startViewTransition' in document\n\n if (!canAnimate) {\n setMode(next)\n return\n }\n\n const x = event.clientX\n const y = event.clientY\n const endRadius = Math.hypot(\n Math.max(x, window.innerWidth - x),\n Math.max(y, window.innerHeight - y),\n )\n\n const startViewTransition = (\n document as unknown as { startViewTransition: StartViewTransition }\n ).startViewTransition\n\n const transition = startViewTransition(() => {\n setMode(next)\n })\n\n transition.ready\n .then(() => {\n document.documentElement.animate(\n {\n clipPath: [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`],\n },\n {\n duration: 450,\n easing: 'ease-in-out',\n pseudoElement: '::view-transition-new(root)',\n },\n )\n })\n .catch(() => {\n /* animation is best-effort; the mode change already applied */\n })\n },\n [disableAnimation, getNextMode, setMode],\n )\n\n // Honour the admin \"allow color mode toggle\" switch.\n if (isColorModeToggleAllowed === false) {\n return null\n }\n\n const iconSet = { ...DEFAULT_ICONS, ...icons }\n const activeMode: Mode = mode ?? 'light'\n const Icon = iconSet[activeMode] ?? DEFAULT_ICONS.light\n\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n className={['tm-color-mode-toggle', className].filter(Boolean).join(' ')}\n style={style}\n aria-label={ariaLabel ?? `Switch color mode (current: ${activeMode})`}\n title={ariaLabel ?? `Color mode: ${activeMode}`}\n >\n <Icon size={size} aria-hidden />\n </button>\n )\n}\n\nexport default ColorModeToggle\n"],"names":["Monitor","Moon","Sun","React","useCallback","useTheme","DEFAULT_ICONS","light","dark","auto","ColorModeToggle","className","style","size","includeAuto","disableAnimation","icons","ariaLabel","mode","setMode","isColorModeToggleAllowed","resolvedCurrent","document","attr","documentElement","getAttribute","getNextMode","handleClick","event","next","prefersReduced","window","matchMedia","matches","canAnimate","x","clientX","y","clientY","endRadius","Math","hypot","max","innerWidth","innerHeight","startViewTransition","transition","ready","then","animate","clipPath","duration","easing","pseudoElement","catch","iconSet","activeMode","Icon","button","type","onClick","filter","Boolean","join","aria-label","title","aria-hidden"],"mappings":"AAAA;;AAEA,SAASA,OAAO,EAAEC,IAAI,EAAEC,GAAG,QAAyB,eAAc;AAClE,OAAOC,SAASC,WAAW,QAAQ,QAAO;AAC1C,SAASC,QAAQ,QAAQ,8BAA6B;AAEtD,OAAO,wBAAuB;AAoB9B,MAAMC,gBAA0C;IAC9CC,OAAOL;IACPM,MAAMP;IACNQ,MAAMT;AACR;AAIA;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAMU,kBAAkD,CAAC,EAC9DC,SAAS,EACTC,KAAK,EACLC,OAAO,EAAE,EACTC,cAAc,KAAK,EACnBC,mBAAmB,KAAK,EACxBC,KAAK,EACL,cAAcC,SAAS,EACxB;IACC,MAAM,EAAEC,IAAI,EAAEC,OAAO,EAAEC,wBAAwB,EAAE,GAAGf;IAEpD,MAAMgB,kBAAkBjB,YAAY;QAClC,IAAI,OAAOkB,aAAa,aAAa;YACnC,MAAMC,OAAOD,SAASE,eAAe,CAACC,YAAY,CAAC;YACnD,IAAIF,SAAS,UAAUA,SAAS,SAAS,OAAOA;QAClD;QACA,OAAOL,SAAS,SAAS,SAAS;IACpC,GAAG;QAACA;KAAK;IAET,MAAMQ,cAActB,YAAY;QAC9B,IAAIU,aAAa;YACf,IAAII,SAAS,SAAS,OAAO;YAC7B,IAAIA,SAAS,QAAQ,OAAO;YAC5B,OAAO;QACT;QACA,OAAOG,sBAAsB,SAAS,UAAU;IAClD,GAAG;QAACP;QAAaI;QAAMG;KAAgB;IAEvC,MAAMM,cAAcvB,YAClB,CAACwB;QACC,MAAMC,OAAOH;QACb,MAAMI,iBACJ,OAAOC,WAAW,eAClBA,OAAOC,UAAU,CAAC,oCAAoCC,OAAO;QAC/D,MAAMC,aACJ,CAACnB,oBACD,CAACe,kBACD,OAAOR,aAAa,eACpB,yBAAyBA;QAE3B,IAAI,CAACY,YAAY;YACff,QAAQU;YACR;QACF;QAEA,MAAMM,IAAIP,MAAMQ,OAAO;QACvB,MAAMC,IAAIT,MAAMU,OAAO;QACvB,MAAMC,YAAYC,KAAKC,KAAK,CAC1BD,KAAKE,GAAG,CAACP,GAAGJ,OAAOY,UAAU,GAAGR,IAChCK,KAAKE,GAAG,CAACL,GAAGN,OAAOa,WAAW,GAAGP;QAGnC,MAAMQ,sBAAsB,AAC1BvB,SACAuB,mBAAmB;QAErB,MAAMC,aAAaD,oBAAoB;YACrC1B,QAAQU;QACV;QAEAiB,WAAWC,KAAK,CACbC,IAAI,CAAC;YACJ1B,SAASE,eAAe,CAACyB,OAAO,CAC9B;gBACEC,UAAU;oBAAC,CAAC,cAAc,EAAEf,EAAE,GAAG,EAAEE,EAAE,GAAG,CAAC;oBAAE,CAAC,OAAO,EAAEE,UAAU,MAAM,EAAEJ,EAAE,GAAG,EAAEE,EAAE,GAAG,CAAC;iBAAC;YACvF,GACA;gBACEc,UAAU;gBACVC,QAAQ;gBACRC,eAAe;YACjB;QAEJ,GACCC,KAAK,CAAC;QACL,6DAA6D,GAC/D;IACJ,GACA;QAACvC;QAAkBW;QAAaP;KAAQ;IAG1C,qDAAqD;IACrD,IAAIC,6BAA6B,OAAO;QACtC,OAAO;IACT;IAEA,MAAMmC,UAAU;QAAE,GAAGjD,aAAa;QAAE,GAAGU,KAAK;IAAC;IAC7C,MAAMwC,aAAmBtC,QAAQ;IACjC,MAAMuC,OAAOF,OAAO,CAACC,WAAW,IAAIlD,cAAcC,KAAK;IAEvD,qBACE,KAACmD;QACCC,MAAK;QACLC,SAASjC;QACThB,WAAW;YAAC;YAAwBA;SAAU,CAACkD,MAAM,CAACC,SAASC,IAAI,CAAC;QACpEnD,OAAOA;QACPoD,cAAY/C,aAAa,CAAC,4BAA4B,EAAEuC,WAAW,CAAC,CAAC;QACrES,OAAOhD,aAAa,CAAC,YAAY,EAAEuC,YAAY;kBAE/C,cAAA,KAACC;YAAK5C,MAAMA;YAAMqD,aAAW;;;AAGnC,EAAC;AAED,eAAexD,gBAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"ServerThemeInjector.d.ts","sourceRoot":"","sources":["../../src/components/ServerThemeInjector.tsx"],"names":[],"mappings":"AAUA,UAAU,wBAAwB;IAChC;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAMD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,EACxC,kBAAkB,GACnB,EAAE,QAAQ,CAAC,wBAAwB,CAAC,wCAsFpC"}
1
+ {"version":3,"file":"ServerThemeInjector.d.ts","sourceRoot":"","sources":["../../src/components/ServerThemeInjector.tsx"],"names":[],"mappings":"AAUA,UAAU,wBAAwB;IAChC;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAKD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,EACxC,kBAAkB,GACnB,EAAE,QAAQ,CAAC,wBAAwB,CAAC,wCAqHpC"}
@@ -9,10 +9,9 @@ import { InitTheme } from './InitTheme.js';
9
9
  * Server-side theme CSS injector to prevent FOUC
10
10
  * This component injects critical theme CSS directly into the HTML head during SSR
11
11
  */ export async function ServerThemeInjector({ themeConfiguration }) {
12
- // Accept unknown type to avoid type conflicts between app's payload-types and plugin's payload-types
13
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ // resolveThemeConfiguration accepts `unknown` — no cast needed
14
13
  const resolvedConfiguration = resolveThemeConfiguration(themeConfiguration);
15
- const { theme, borderRadius, customCSS, lightMode, darkMode, fontScale, spacing, animationLevel, allowColorModeToggle, colorMode, typography } = resolvedConfiguration;
14
+ const { theme, borderRadius, customCSS, lightMode, darkMode, fontScale, spacing, animationLevel, allowColorModeToggle, colorMode, typography, visualEffects, componentStyles } = resolvedConfiguration;
16
15
  // Use resolved configuration directly
17
16
  const normalizedThemeConfiguration = {
18
17
  theme,
@@ -25,20 +24,13 @@ import { InitTheme } from './InitTheme.js';
25
24
  customCSS: customCSS ?? null,
26
25
  lightMode,
27
26
  darkMode,
28
- typography: typography ?? undefined
27
+ typography: typography ?? undefined,
28
+ visualEffects: visualEffects ?? null,
29
+ componentStyles: componentStyles ?? null
29
30
  };
30
31
  let criticalCSS = await getThemeCriticalCSS(theme);
31
32
  criticalCSS ??= createFallbackCriticalCSS(theme);
32
- const borderRadiusMap = {
33
- none: 'none',
34
- small: 'small',
35
- medium: 'medium',
36
- large: 'large',
37
- xl: 'xl',
38
- full: 'xl'
39
- };
40
- const mappedBorderRadius = borderRadiusMap[borderRadius ?? 'medium'] ?? 'medium';
41
- const borderRadiusConfig = getBorderRadiusConfig(mappedBorderRadius);
33
+ const borderRadiusConfig = getBorderRadiusConfig(borderRadius ?? 'medium');
42
34
  const cssRecord = typeof borderRadiusConfig?.css === 'string' ? {
43
35
  '--radius-default': borderRadiusConfig.css
44
36
  } : borderRadiusConfig?.css ?? {};
@@ -53,16 +45,51 @@ ${borderRadiusCSS}
53
45
  darkMode
54
46
  });
55
47
  const customCSSBlock = typeof customCSS === 'string' ? customCSS.trim() : '';
48
+ // `color-scheme` makes native UI (form controls, scrollbars) match the theme and
49
+ // avoids a white flash on dark sites. Fixed mode → single scheme; toggle/auto → both.
50
+ const colorScheme = allowColorModeToggle === false ? colorMode === 'dark' ? 'dark' : 'light' : 'light dark';
51
+ const colorSchemeBlock = `:root {\n color-scheme: ${colorScheme};\n}`;
56
52
  const combinedCSS = [
57
53
  criticalCSS,
54
+ colorSchemeBlock,
58
55
  borderRadiusBlock,
59
56
  themeConfigurationCSS,
60
57
  colorModesCSS,
61
58
  customCSSBlock
62
59
  ].filter((block)=>Boolean(block?.trim?.().length)).join('\n\n');
60
+ // <meta name="theme-color"> colours the mobile browser chrome to match the page
61
+ // background. Emit per-scheme variants when the mode can change, otherwise a
62
+ // single tag matching the fixed mode.
63
+ const pick = (mode, key)=>{
64
+ const value = mode?.[key];
65
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
66
+ };
67
+ const lightBg = pick(lightMode, 'background');
68
+ const darkBg = pick(darkMode, 'background');
69
+ const emitBothSchemes = allowColorModeToggle !== false || colorMode === 'auto';
63
70
  return /*#__PURE__*/ _jsxs(_Fragment, {
64
71
  children: [
65
72
  /*#__PURE__*/ _jsx(InitTheme, {}),
73
+ emitBothSchemes ? /*#__PURE__*/ _jsxs(_Fragment, {
74
+ children: [
75
+ lightBg ? /*#__PURE__*/ _jsx("meta", {
76
+ name: "theme-color",
77
+ media: "(prefers-color-scheme: light)",
78
+ content: lightBg
79
+ }) : null,
80
+ darkBg ? /*#__PURE__*/ _jsx("meta", {
81
+ name: "theme-color",
82
+ media: "(prefers-color-scheme: dark)",
83
+ content: darkBg
84
+ }) : null
85
+ ]
86
+ }) : (()=>{
87
+ const fixed = colorMode === 'dark' ? darkBg : lightBg;
88
+ return fixed ? /*#__PURE__*/ _jsx("meta", {
89
+ name: "theme-color",
90
+ content: fixed
91
+ }) : null;
92
+ })(),
66
93
  /*#__PURE__*/ _jsx("style", {
67
94
  id: "theme-critical-css",
68
95
  dangerouslySetInnerHTML: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/ServerThemeInjector.tsx"],"sourcesContent":["import type { ThemeTypographyPreset } from '../presets.js'\nimport { getBorderRadiusConfig } from '../providers/Theme/themeConfig.js'\nimport type { BorderRadiusPreset, ThemeDefaults } from '../providers/Theme/types.js'\nimport { resolveThemeConfiguration } from '../utils/resolveThemeConfiguration.js'\nimport type { ResolvedThemeConfiguration } from '../utils/resolveThemeConfiguration.js'\nimport { createFallbackCriticalCSS, getThemeCriticalCSS } from '../utils/themeAssets.js'\nimport { generateThemeColorsCss } from '../utils/themeColors.js'\nimport { generateThemeCSS } from '../utils/themeUtils.js'\nimport { InitTheme } from './InitTheme.js'\n\ninterface ServerThemeInjectorProps {\n /**\n * Theme configuration object from your site settings.\n * Pass the themeConfiguration field directly, not the entire site settings object.\n *\n * @example\n * ```tsx\n * const siteSettings = await payload.findGlobal({ slug: 'site-settings' })\n * <ServerThemeInjector themeConfiguration={siteSettings.themeConfiguration} />\n * ```\n */\n themeConfiguration?: unknown\n}\n\ntype RuntimeThemeConfiguration = Omit<ResolvedThemeConfiguration, 'typography'> & {\n typography?: ThemeTypographyPreset | null\n}\n\n/**\n * Server-side theme CSS injector to prevent FOUC\n * This component injects critical theme CSS directly into the HTML head during SSR\n */\nexport async function ServerThemeInjector({\n themeConfiguration,\n}: Readonly<ServerThemeInjectorProps>) {\n // Accept unknown type to avoid type conflicts between app's payload-types and plugin's payload-types\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const resolvedConfiguration = resolveThemeConfiguration(themeConfiguration as any)\n const {\n theme,\n borderRadius,\n customCSS,\n lightMode,\n darkMode,\n fontScale,\n spacing,\n animationLevel,\n allowColorModeToggle,\n colorMode,\n typography,\n } = resolvedConfiguration\n\n // Use resolved configuration directly\n const normalizedThemeConfiguration: RuntimeThemeConfiguration = {\n theme,\n colorMode,\n allowColorModeToggle,\n borderRadius,\n fontScale,\n spacing,\n animationLevel,\n customCSS: customCSS ?? null,\n lightMode,\n darkMode,\n typography: typography ?? undefined,\n }\n\n let criticalCSS = await getThemeCriticalCSS(theme as ThemeDefaults)\n criticalCSS ??= createFallbackCriticalCSS(theme as ThemeDefaults)\n\n const borderRadiusMap: Record<string, BorderRadiusPreset> = {\n none: 'none',\n small: 'small',\n medium: 'medium',\n large: 'large',\n xl: 'xl',\n full: 'xl',\n }\n const mappedBorderRadius = borderRadiusMap[borderRadius ?? 'medium'] ?? 'medium'\n const borderRadiusConfig = getBorderRadiusConfig(mappedBorderRadius)\n const cssRecord =\n typeof borderRadiusConfig?.css === 'string'\n ? { '--radius-default': borderRadiusConfig.css }\n : (borderRadiusConfig?.css ?? {})\n\n const borderRadiusCSS = Object.entries(cssRecord)\n .map(([property, value]) => ` ${property}: ${value};`)\n .join('\\n')\n\n const borderRadiusBlock = borderRadiusCSS\n ? `:root {\n${borderRadiusCSS}\n}`\n : ''\n\n const themeConfigurationCSS = generateThemeCSS(normalizedThemeConfiguration)\n const colorModesCSS = generateThemeColorsCss({ themeName: theme, lightMode, darkMode })\n const customCSSBlock = typeof customCSS === 'string' ? customCSS.trim() : ''\n\n const combinedCSS = [\n criticalCSS,\n borderRadiusBlock,\n themeConfigurationCSS,\n colorModesCSS,\n customCSSBlock,\n ]\n .filter((block) => Boolean(block?.trim?.().length))\n .join('\\n\\n')\n\n return (\n <>\n <InitTheme />\n <style\n id=\"theme-critical-css\"\n dangerouslySetInnerHTML={{\n __html: combinedCSS,\n }}\n />\n </>\n )\n}\n"],"names":["getBorderRadiusConfig","resolveThemeConfiguration","createFallbackCriticalCSS","getThemeCriticalCSS","generateThemeColorsCss","generateThemeCSS","InitTheme","ServerThemeInjector","themeConfiguration","resolvedConfiguration","theme","borderRadius","customCSS","lightMode","darkMode","fontScale","spacing","animationLevel","allowColorModeToggle","colorMode","typography","normalizedThemeConfiguration","undefined","criticalCSS","borderRadiusMap","none","small","medium","large","xl","full","mappedBorderRadius","borderRadiusConfig","cssRecord","css","borderRadiusCSS","Object","entries","map","property","value","join","borderRadiusBlock","themeConfigurationCSS","colorModesCSS","themeName","customCSSBlock","trim","combinedCSS","filter","block","Boolean","length","style","id","dangerouslySetInnerHTML","__html"],"mappings":";AACA,SAASA,qBAAqB,QAAQ,oCAAmC;AAEzE,SAASC,yBAAyB,QAAQ,wCAAuC;AAEjF,SAASC,yBAAyB,EAAEC,mBAAmB,QAAQ,0BAAyB;AACxF,SAASC,sBAAsB,QAAQ,0BAAyB;AAChE,SAASC,gBAAgB,QAAQ,yBAAwB;AACzD,SAASC,SAAS,QAAQ,iBAAgB;AAoB1C;;;CAGC,GACD,OAAO,eAAeC,oBAAoB,EACxCC,kBAAkB,EACiB;IACnC,qGAAqG;IACrG,8DAA8D;IAC9D,MAAMC,wBAAwBR,0BAA0BO;IACxD,MAAM,EACJE,KAAK,EACLC,YAAY,EACZC,SAAS,EACTC,SAAS,EACTC,QAAQ,EACRC,SAAS,EACTC,OAAO,EACPC,cAAc,EACdC,oBAAoB,EACpBC,SAAS,EACTC,UAAU,EACX,GAAGX;IAEJ,sCAAsC;IACtC,MAAMY,+BAA0D;QAC9DX;QACAS;QACAD;QACAP;QACAI;QACAC;QACAC;QACAL,WAAWA,aAAa;QACxBC;QACAC;QACAM,YAAYA,cAAcE;IAC5B;IAEA,IAAIC,cAAc,MAAMpB,oBAAoBO;IAC5Ca,gBAAgBrB,0BAA0BQ;IAE1C,MAAMc,kBAAsD;QAC1DC,MAAM;QACNC,OAAO;QACPC,QAAQ;QACRC,OAAO;QACPC,IAAI;QACJC,MAAM;IACR;IACA,MAAMC,qBAAqBP,eAAe,CAACb,gBAAgB,SAAS,IAAI;IACxE,MAAMqB,qBAAqBhC,sBAAsB+B;IACjD,MAAME,YACJ,OAAOD,oBAAoBE,QAAQ,WAC/B;QAAE,oBAAoBF,mBAAmBE,GAAG;IAAC,IAC5CF,oBAAoBE,OAAO,CAAC;IAEnC,MAAMC,kBAAkBC,OAAOC,OAAO,CAACJ,WACpCK,GAAG,CAAC,CAAC,CAACC,UAAUC,MAAM,GAAK,CAAC,EAAE,EAAED,SAAS,EAAE,EAAEC,MAAM,CAAC,CAAC,EACrDC,IAAI,CAAC;IAER,MAAMC,oBAAoBP,kBACtB,CAAC;AACP,EAAEA,gBAAgB;CACjB,CAAC,GACI;IAEJ,MAAMQ,wBAAwBtC,iBAAiBgB;IAC/C,MAAMuB,gBAAgBxC,uBAAuB;QAAEyC,WAAWnC;QAAOG;QAAWC;IAAS;IACrF,MAAMgC,iBAAiB,OAAOlC,cAAc,WAAWA,UAAUmC,IAAI,KAAK;IAE1E,MAAMC,cAAc;QAClBzB;QACAmB;QACAC;QACAC;QACAE;KACD,CACEG,MAAM,CAAC,CAACC,QAAUC,QAAQD,OAAOH,SAASK,SAC1CX,IAAI,CAAC;IAER,qBACE;;0BACE,KAACnC;0BACD,KAAC+C;gBACCC,IAAG;gBACHC,yBAAyB;oBACvBC,QAAQR;gBACV;;;;AAIR"}
1
+ {"version":3,"sources":["../../src/components/ServerThemeInjector.tsx"],"sourcesContent":["import type { ThemeTypographyPreset } from '../presets.js'\nimport { getBorderRadiusConfig } from '../providers/Theme/themeConfig.js'\nimport type { ThemeDefaults } from '../providers/Theme/types.js'\nimport { resolveThemeConfiguration } from '../utils/resolveThemeConfiguration.js'\nimport type { ResolvedThemeConfiguration } from '../utils/resolveThemeConfiguration.js'\nimport { createFallbackCriticalCSS, getThemeCriticalCSS } from '../utils/themeAssets.js'\nimport { generateThemeColorsCss } from '../utils/themeColors.js'\nimport { generateThemeCSS } from '../utils/themeUtils.js'\nimport { InitTheme } from './InitTheme.js'\n\ninterface ServerThemeInjectorProps {\n /**\n * Theme configuration object from your site settings.\n * Pass the themeConfiguration field directly, not the entire site settings object.\n *\n * @example\n * ```tsx\n * const siteSettings = await payload.findGlobal({ slug: 'site-settings' })\n * <ServerThemeInjector themeConfiguration={siteSettings.themeConfiguration} />\n * ```\n */\n themeConfiguration?: unknown\n}\n\ntype RuntimeThemeConfiguration = Omit<ResolvedThemeConfiguration, 'typography'> & {\n typography?: ThemeTypographyPreset | null\n}\n/**\n * Server-side theme CSS injector to prevent FOUC\n * This component injects critical theme CSS directly into the HTML head during SSR\n */\nexport async function ServerThemeInjector({\n themeConfiguration,\n}: Readonly<ServerThemeInjectorProps>) {\n // resolveThemeConfiguration accepts `unknown` — no cast needed\n const resolvedConfiguration = resolveThemeConfiguration(themeConfiguration)\n const {\n theme,\n borderRadius,\n customCSS,\n lightMode,\n darkMode,\n fontScale,\n spacing,\n animationLevel,\n allowColorModeToggle,\n colorMode,\n typography,\n visualEffects,\n componentStyles,\n } = resolvedConfiguration\n\n // Use resolved configuration directly\n const normalizedThemeConfiguration: RuntimeThemeConfiguration = {\n theme,\n colorMode,\n allowColorModeToggle,\n borderRadius,\n fontScale,\n spacing,\n animationLevel,\n customCSS: customCSS ?? null,\n lightMode,\n darkMode,\n typography: typography ?? undefined,\n visualEffects: visualEffects ?? null,\n componentStyles: componentStyles ?? null,\n }\n\n let criticalCSS = await getThemeCriticalCSS(theme as ThemeDefaults)\n criticalCSS ??= createFallbackCriticalCSS(theme as ThemeDefaults)\n\n const borderRadiusConfig = getBorderRadiusConfig(borderRadius ?? 'medium')\n const cssRecord =\n typeof borderRadiusConfig?.css === 'string'\n ? { '--radius-default': borderRadiusConfig.css }\n : (borderRadiusConfig?.css ?? {})\n\n const borderRadiusCSS = Object.entries(cssRecord)\n .map(([property, value]) => ` ${property}: ${value};`)\n .join('\\n')\n\n const borderRadiusBlock = borderRadiusCSS\n ? `:root {\n${borderRadiusCSS}\n}`\n : ''\n\n const themeConfigurationCSS = generateThemeCSS(normalizedThemeConfiguration)\n const colorModesCSS = generateThemeColorsCss({ themeName: theme, lightMode, darkMode })\n const customCSSBlock = typeof customCSS === 'string' ? customCSS.trim() : ''\n\n // `color-scheme` makes native UI (form controls, scrollbars) match the theme and\n // avoids a white flash on dark sites. Fixed mode → single scheme; toggle/auto → both.\n const colorScheme =\n allowColorModeToggle === false\n ? colorMode === 'dark'\n ? 'dark'\n : 'light'\n : 'light dark'\n const colorSchemeBlock = `:root {\\n color-scheme: ${colorScheme};\\n}`\n\n const combinedCSS = [\n criticalCSS,\n colorSchemeBlock,\n borderRadiusBlock,\n themeConfigurationCSS,\n colorModesCSS,\n customCSSBlock,\n ]\n .filter((block) => Boolean(block?.trim?.().length))\n .join('\\n\\n')\n\n // <meta name=\"theme-color\"> colours the mobile browser chrome to match the page\n // background. Emit per-scheme variants when the mode can change, otherwise a\n // single tag matching the fixed mode.\n const pick = (mode: typeof lightMode, key: 'background'): string | undefined => {\n const value = (mode as Record<string, unknown> | null | undefined)?.[key]\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined\n }\n const lightBg = pick(lightMode, 'background')\n const darkBg = pick(darkMode, 'background')\n const emitBothSchemes = allowColorModeToggle !== false || colorMode === 'auto'\n\n return (\n <>\n <InitTheme />\n {emitBothSchemes ? (\n <>\n {lightBg ? (\n <meta name=\"theme-color\" media=\"(prefers-color-scheme: light)\" content={lightBg} />\n ) : null}\n {darkBg ? (\n <meta name=\"theme-color\" media=\"(prefers-color-scheme: dark)\" content={darkBg} />\n ) : null}\n </>\n ) : (\n (() => {\n const fixed = colorMode === 'dark' ? darkBg : lightBg\n return fixed ? <meta name=\"theme-color\" content={fixed} /> : null\n })()\n )}\n <style\n id=\"theme-critical-css\"\n dangerouslySetInnerHTML={{\n __html: combinedCSS,\n }}\n />\n </>\n )\n}\n"],"names":["getBorderRadiusConfig","resolveThemeConfiguration","createFallbackCriticalCSS","getThemeCriticalCSS","generateThemeColorsCss","generateThemeCSS","InitTheme","ServerThemeInjector","themeConfiguration","resolvedConfiguration","theme","borderRadius","customCSS","lightMode","darkMode","fontScale","spacing","animationLevel","allowColorModeToggle","colorMode","typography","visualEffects","componentStyles","normalizedThemeConfiguration","undefined","criticalCSS","borderRadiusConfig","cssRecord","css","borderRadiusCSS","Object","entries","map","property","value","join","borderRadiusBlock","themeConfigurationCSS","colorModesCSS","themeName","customCSSBlock","trim","colorScheme","colorSchemeBlock","combinedCSS","filter","block","Boolean","length","pick","mode","key","lightBg","darkBg","emitBothSchemes","meta","name","media","content","fixed","style","id","dangerouslySetInnerHTML","__html"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  interface ThemeLivePreviewSyncProps {
2
3
  initialData: Record<string, unknown>;
3
4
  serverURL: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeLivePreviewSync.d.ts","sourceRoot":"","sources":["../../src/components/ThemeLivePreviewSync.tsx"],"names":[],"mappings":"AASA,UAAU,yBAAyB;IACjC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CA4CpE,CAAA"}
1
+ {"version":3,"file":"ThemeLivePreviewSync.d.ts","sourceRoot":"","sources":["../../src/components/ThemeLivePreviewSync.tsx"],"names":[],"mappings":"AAGA,OAAO,KAA6B,MAAM,OAAO,CAAA;AAMjD,UAAU,yBAAyB;IACjC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CA4CpE,CAAA"}
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { useLivePreview } from '@payloadcms/live-preview-react';
3
- import { useEffect, useMemo } from 'react';
3
+ import React, { useEffect, useMemo } from 'react';
4
4
  import { buildLivePreviewThemeRuntime, extractThemeConfigurationFromLiveData } from '../utils/livePreviewThemeRuntime.js';
5
5
  export const ThemeLivePreviewSync = ({ initialData, serverURL, apiRoute, depth, themeConfigurationKey = 'themeConfiguration', styleElementId = 'theme-live-preview-css' })=>{
6
6
  const { data } = useLivePreview({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/ThemeLivePreviewSync.tsx"],"sourcesContent":["'use client'\n\nimport { useLivePreview } from '@payloadcms/live-preview-react'\nimport { useEffect, useMemo } from 'react'\nimport {\n buildLivePreviewThemeRuntime,\n extractThemeConfigurationFromLiveData,\n} from '../utils/livePreviewThemeRuntime.js'\n\ninterface ThemeLivePreviewSyncProps {\n initialData: Record<string, unknown>\n serverURL: string\n apiRoute?: string\n depth?: number\n themeConfigurationKey?: string\n styleElementId?: string\n}\n\nexport const ThemeLivePreviewSync: React.FC<ThemeLivePreviewSyncProps> = ({\n initialData,\n serverURL,\n apiRoute,\n depth,\n themeConfigurationKey = 'themeConfiguration',\n styleElementId = 'theme-live-preview-css',\n}) => {\n const { data } = useLivePreview<Record<string, unknown>>({\n apiRoute,\n depth,\n initialData,\n serverURL,\n })\n\n const themeConfiguration = useMemo(\n () => extractThemeConfigurationFromLiveData(data, themeConfigurationKey),\n [data, themeConfigurationKey],\n )\n\n useEffect(() => {\n if (typeof document === 'undefined' || !themeConfiguration) {\n return\n }\n\n const runtime = buildLivePreviewThemeRuntime(themeConfiguration)\n\n let styleElement = document.getElementById(styleElementId) as HTMLStyleElement | null\n if (!styleElement) {\n styleElement = document.createElement('style')\n styleElement.id = styleElementId\n document.head.appendChild(styleElement)\n }\n\n styleElement.innerHTML = runtime.css\n\n document.documentElement.dataset.theme = runtime.theme\n document.documentElement.dataset.colorMode = runtime.mode\n document.documentElement.dataset.themeMode = runtime.mode\n document.documentElement.classList.remove('light', 'dark', 'auto')\n document.documentElement.classList.add(runtime.mode)\n }, [themeConfiguration, styleElementId])\n\n return null\n}\n"],"names":["useLivePreview","useEffect","useMemo","buildLivePreviewThemeRuntime","extractThemeConfigurationFromLiveData","ThemeLivePreviewSync","initialData","serverURL","apiRoute","depth","themeConfigurationKey","styleElementId","data","themeConfiguration","document","runtime","styleElement","getElementById","createElement","id","head","appendChild","innerHTML","css","documentElement","dataset","theme","colorMode","mode","themeMode","classList","remove","add"],"mappings":"AAAA;AAEA,SAASA,cAAc,QAAQ,iCAAgC;AAC/D,SAASC,SAAS,EAAEC,OAAO,QAAQ,QAAO;AAC1C,SACEC,4BAA4B,EAC5BC,qCAAqC,QAChC,sCAAqC;AAW5C,OAAO,MAAMC,uBAA4D,CAAC,EACxEC,WAAW,EACXC,SAAS,EACTC,QAAQ,EACRC,KAAK,EACLC,wBAAwB,oBAAoB,EAC5CC,iBAAiB,wBAAwB,EAC1C;IACC,MAAM,EAAEC,IAAI,EAAE,GAAGZ,eAAwC;QACvDQ;QACAC;QACAH;QACAC;IACF;IAEA,MAAMM,qBAAqBX,QACzB,IAAME,sCAAsCQ,MAAMF,wBAClD;QAACE;QAAMF;KAAsB;IAG/BT,UAAU;QACR,IAAI,OAAOa,aAAa,eAAe,CAACD,oBAAoB;YAC1D;QACF;QAEA,MAAME,UAAUZ,6BAA6BU;QAE7C,IAAIG,eAAeF,SAASG,cAAc,CAACN;QAC3C,IAAI,CAACK,cAAc;YACjBA,eAAeF,SAASI,aAAa,CAAC;YACtCF,aAAaG,EAAE,GAAGR;YAClBG,SAASM,IAAI,CAACC,WAAW,CAACL;QAC5B;QAEAA,aAAaM,SAAS,GAAGP,QAAQQ,GAAG;QAEpCT,SAASU,eAAe,CAACC,OAAO,CAACC,KAAK,GAAGX,QAAQW,KAAK;QACtDZ,SAASU,eAAe,CAACC,OAAO,CAACE,SAAS,GAAGZ,QAAQa,IAAI;QACzDd,SAASU,eAAe,CAACC,OAAO,CAACI,SAAS,GAAGd,QAAQa,IAAI;QACzDd,SAASU,eAAe,CAACM,SAAS,CAACC,MAAM,CAAC,SAAS,QAAQ;QAC3DjB,SAASU,eAAe,CAACM,SAAS,CAACE,GAAG,CAACjB,QAAQa,IAAI;IACrD,GAAG;QAACf;QAAoBF;KAAe;IAEvC,OAAO;AACT,EAAC"}
1
+ {"version":3,"sources":["../../src/components/ThemeLivePreviewSync.tsx"],"sourcesContent":["'use client'\n\nimport { useLivePreview } from '@payloadcms/live-preview-react'\nimport React, { useEffect, useMemo } from 'react'\nimport {\n buildLivePreviewThemeRuntime,\n extractThemeConfigurationFromLiveData,\n} from '../utils/livePreviewThemeRuntime.js'\n\ninterface ThemeLivePreviewSyncProps {\n initialData: Record<string, unknown>\n serverURL: string\n apiRoute?: string\n depth?: number\n themeConfigurationKey?: string\n styleElementId?: string\n}\n\nexport const ThemeLivePreviewSync: React.FC<ThemeLivePreviewSyncProps> = ({\n initialData,\n serverURL,\n apiRoute,\n depth,\n themeConfigurationKey = 'themeConfiguration',\n styleElementId = 'theme-live-preview-css',\n}) => {\n const { data } = useLivePreview<Record<string, unknown>>({\n apiRoute,\n depth,\n initialData,\n serverURL,\n })\n\n const themeConfiguration = useMemo(\n () => extractThemeConfigurationFromLiveData(data, themeConfigurationKey),\n [data, themeConfigurationKey],\n )\n\n useEffect(() => {\n if (typeof document === 'undefined' || !themeConfiguration) {\n return\n }\n\n const runtime = buildLivePreviewThemeRuntime(themeConfiguration)\n\n let styleElement = document.getElementById(styleElementId) as HTMLStyleElement | null\n if (!styleElement) {\n styleElement = document.createElement('style')\n styleElement.id = styleElementId\n document.head.appendChild(styleElement)\n }\n\n styleElement.innerHTML = runtime.css\n\n document.documentElement.dataset.theme = runtime.theme\n document.documentElement.dataset.colorMode = runtime.mode\n document.documentElement.dataset.themeMode = runtime.mode\n document.documentElement.classList.remove('light', 'dark', 'auto')\n document.documentElement.classList.add(runtime.mode)\n }, [themeConfiguration, styleElementId])\n\n return null\n}\n"],"names":["useLivePreview","React","useEffect","useMemo","buildLivePreviewThemeRuntime","extractThemeConfigurationFromLiveData","ThemeLivePreviewSync","initialData","serverURL","apiRoute","depth","themeConfigurationKey","styleElementId","data","themeConfiguration","document","runtime","styleElement","getElementById","createElement","id","head","appendChild","innerHTML","css","documentElement","dataset","theme","colorMode","mode","themeMode","classList","remove","add"],"mappings":"AAAA;AAEA,SAASA,cAAc,QAAQ,iCAAgC;AAC/D,OAAOC,SAASC,SAAS,EAAEC,OAAO,QAAQ,QAAO;AACjD,SACEC,4BAA4B,EAC5BC,qCAAqC,QAChC,sCAAqC;AAW5C,OAAO,MAAMC,uBAA4D,CAAC,EACxEC,WAAW,EACXC,SAAS,EACTC,QAAQ,EACRC,KAAK,EACLC,wBAAwB,oBAAoB,EAC5CC,iBAAiB,wBAAwB,EAC1C;IACC,MAAM,EAAEC,IAAI,EAAE,GAAGb,eAAwC;QACvDS;QACAC;QACAH;QACAC;IACF;IAEA,MAAMM,qBAAqBX,QACzB,IAAME,sCAAsCQ,MAAMF,wBAClD;QAACE;QAAMF;KAAsB;IAG/BT,UAAU;QACR,IAAI,OAAOa,aAAa,eAAe,CAACD,oBAAoB;YAC1D;QACF;QAEA,MAAME,UAAUZ,6BAA6BU;QAE7C,IAAIG,eAAeF,SAASG,cAAc,CAACN;QAC3C,IAAI,CAACK,cAAc;YACjBA,eAAeF,SAASI,aAAa,CAAC;YACtCF,aAAaG,EAAE,GAAGR;YAClBG,SAASM,IAAI,CAACC,WAAW,CAACL;QAC5B;QAEAA,aAAaM,SAAS,GAAGP,QAAQQ,GAAG;QAEpCT,SAASU,eAAe,CAACC,OAAO,CAACC,KAAK,GAAGX,QAAQW,KAAK;QACtDZ,SAASU,eAAe,CAACC,OAAO,CAACE,SAAS,GAAGZ,QAAQa,IAAI;QACzDd,SAASU,eAAe,CAACC,OAAO,CAACI,SAAS,GAAGd,QAAQa,IAAI;QACzDd,SAASU,eAAe,CAACM,SAAS,CAACC,MAAM,CAAC,SAAS,QAAQ;QAC3DjB,SAASU,eAAe,CAACM,SAAS,CAACE,GAAG,CAACjB,QAAQa,IAAI;IACrD,GAAG;QAACf;QAAoBF;KAAe;IAEvC,OAAO;AACT,EAAC"}
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import { type ThemePreset } from '../presets.js';
3
+ export type SwitchablePreset = Pick<ThemePreset, 'name' | 'label' | 'lightMode' | 'darkMode'>;
4
+ export interface ThemeSwitcherProps {
5
+ /**
6
+ * Presets the visitor can switch between. Defaults to all bundled presets.
7
+ * Pass a curated subset for a focused switcher.
8
+ */
9
+ presets?: SwitchablePreset[];
10
+ className?: string;
11
+ style?: React.CSSProperties;
12
+ 'aria-label'?: string;
13
+ /** Persist the choice to localStorage and restore it on load. Default true. */
14
+ persist?: boolean;
15
+ /** Called after a preset is applied. */
16
+ onChange?: (presetName: string) => void;
17
+ }
18
+ /**
19
+ * Front-end theme-preset switcher. Lets visitors swap between bundled (or a
20
+ * curated subset of) theme presets at runtime by injecting the preset's colour
21
+ * CSS and updating `data-theme`. The choice is persisted to localStorage and
22
+ * restored on the next visit.
23
+ *
24
+ * Note: colour-mode (light/dark) is handled by {@link ColorModeToggle} and the
25
+ * SSR injector with no flash; preset switching re-applies on mount, so a brief
26
+ * flash to the SSR default can occur before the stored preset is restored.
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * import { ThemeSwitcher } from '@kilivi/payloadcms-theme-management/components/ThemeSwitcher'
31
+ * <ThemeSwitcher presets={[oceanPreset, forestPreset]} />
32
+ * ```
33
+ */
34
+ export declare const ThemeSwitcher: React.FC<ThemeSwitcherProps>;
35
+ export default ThemeSwitcher;
36
+ //# sourceMappingURL=ThemeSwitcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeSwitcher.d.ts","sourceRoot":"","sources":["../../src/components/ThemeSwitcher.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA2C,MAAM,OAAO,CAAA;AAC/D,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AAMjE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,GAAG,WAAW,GAAG,UAAU,CAAC,CAAA;AAE7F,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AAuBD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA6DtD,CAAA;AAED,eAAe,aAAa,CAAA"}