@kilivi/payloadcms-theme-management 1.0.15 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -40
- package/dist/fields/ThemeColorPickerField.d.ts.map +1 -1
- package/dist/fields/ThemeColorPickerField.js +2 -54
- package/dist/fields/ThemeColorPickerField.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
A comprehensive theme management plugin for Payload CMS v3 that provides powerful theming capabilities with SSR support, preventing FOUC (Flash of Unstyled Content).
|
|
4
4
|
|
|
5
|
-
## 🎉 Version 0.
|
|
5
|
+
## 🎉 Version 1.0.2 - Production Ready!
|
|
6
6
|
|
|
7
7
|
- **✅ Standalone Global Support** - Create separate appearance settings as a global
|
|
8
8
|
- **✅ Auto-Populate Light/Dark Colors** - Theme selection hook automatically fills color fields
|
|
9
|
-
- **✅ Multi-Tenant Ready** - Full support for multi-tenant applications
|
|
10
|
-
- **✅
|
|
9
|
+
- **✅ Multi-Tenant Ready** - Full support for multi-tenant applications with isolated themes
|
|
10
|
+
- **✅ Cache Invalidation** - Automatic cache invalidation after saving appearance settings
|
|
11
11
|
- **✅ Live Theme Preview** - Real-time theme preview in admin panel at `/admin/theme-preview`
|
|
12
12
|
- **✅ Professional Color Picker** - Enhanced color picker with react-colorful library
|
|
13
13
|
- **✅ shadcn/ui Compatible** - Works with https://ui.shadcn.com/themes
|
|
@@ -147,7 +147,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|
|
147
147
|
const payload = await getPayload({ config: configPromise })
|
|
148
148
|
|
|
149
149
|
// Fetch from standalone global
|
|
150
|
-
const appearanceSettings = await payload.
|
|
150
|
+
const appearanceSettings = await payload.findGlobal({
|
|
151
151
|
slug: 'appearance-settings', // Your standalone global slug
|
|
152
152
|
})
|
|
153
153
|
|
|
@@ -162,6 +162,62 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|
|
162
162
|
}
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
+
#### Cache Optimization with Next.js
|
|
166
|
+
|
|
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).
|
|
168
|
+
|
|
169
|
+
You can optimize your theme fetching by adding cache tags to your layout:
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
// app/layout.tsx
|
|
173
|
+
import { ServerThemeInjector } from '@kilivi/payloadcms-theme-management/server'
|
|
174
|
+
import configPromise from '@payload-config'
|
|
175
|
+
import { getPayload } from 'payload'
|
|
176
|
+
import { unstable_cache } from 'next/cache'
|
|
177
|
+
|
|
178
|
+
// Create cached theme fetcher with automatic invalidation
|
|
179
|
+
const getCachedTheme = unstable_cache(
|
|
180
|
+
async () => {
|
|
181
|
+
const payload = await getPayload({ config: configPromise })
|
|
182
|
+
const appearanceSettings = await payload.findGlobal({
|
|
183
|
+
slug: 'appearance-settings',
|
|
184
|
+
})
|
|
185
|
+
return appearanceSettings?.themeConfiguration
|
|
186
|
+
},
|
|
187
|
+
['appearance-settings-theme'],
|
|
188
|
+
{
|
|
189
|
+
tags: ['global_appearance-settings'], // Matches the auto-invalidation tag from plugin
|
|
190
|
+
revalidate: 3600, // Optional: revalidate every hour as fallback
|
|
191
|
+
},
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
195
|
+
const themeConfiguration = await getCachedTheme()
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<html lang="en">
|
|
199
|
+
<head>
|
|
200
|
+
<ServerThemeInjector themeConfiguration={themeConfiguration} />
|
|
201
|
+
</head>
|
|
202
|
+
<body>{children}</body>
|
|
203
|
+
</html>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Benefits:**
|
|
209
|
+
|
|
210
|
+
- ⚡ Theme settings are cached and reused across requests
|
|
211
|
+
- 🔄 Cache automatically invalidates when you save appearance settings in admin
|
|
212
|
+
- 🚀 Reduces database queries and improves performance
|
|
213
|
+
|
|
214
|
+
**Cache Tag Format:**
|
|
215
|
+
|
|
216
|
+
- Default: `global_appearance-settings`
|
|
217
|
+
- Custom slug: `global_{standaloneCollectionSlug}`
|
|
218
|
+
|
|
219
|
+
> **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.
|
|
220
|
+
|
|
165
221
|
### 3. Use Theme Variables in Your CSS
|
|
166
222
|
|
|
167
223
|
The plugin injects CSS custom properties you can use:
|
|
@@ -196,7 +252,9 @@ Or with Tailwind CSS:
|
|
|
196
252
|
| `includeCustomCSS` | `boolean` | `true` | Allow custom CSS injection |
|
|
197
253
|
| `includeBrandIdentity` | `boolean` | `false` | Show brand identity fields |
|
|
198
254
|
| `enableAdvancedFeatures` | `boolean` | `true` | Enable advanced customization |
|
|
199
|
-
| `enableLogging` | `boolean` | `false` | Log plugin actions to console
|
|
255
|
+
| `enableLogging` | `boolean` | `false` | Log plugin actions to console (includes cache invalidation logs) |
|
|
256
|
+
|
|
257
|
+
**Cache Invalidation Note:** When using `useStandaloneCollection: true`, the plugin automatically invalidates the cache tag `global_{standaloneCollectionSlug}` after saving appearance settings. Use `unstable_cache` with matching tags for optimal performance.
|
|
200
258
|
|
|
201
259
|
## Available Theme Presets
|
|
202
260
|
|
|
@@ -720,37 +778,52 @@ export default {
|
|
|
720
778
|
|
|
721
779
|
### Fetching Theme Configuration
|
|
722
780
|
|
|
781
|
+
The recommended way to fetch theme configuration is using Payload's native API with Next.js caching:
|
|
782
|
+
|
|
723
783
|
```typescript
|
|
724
|
-
import
|
|
784
|
+
import configPromise from '@payload-config'
|
|
785
|
+
import { getPayload } from 'payload'
|
|
786
|
+
import { unstable_cache } from 'next/cache'
|
|
787
|
+
|
|
788
|
+
// Cached theme fetcher with automatic invalidation
|
|
789
|
+
const getCachedTheme = unstable_cache(
|
|
790
|
+
async () => {
|
|
791
|
+
const payload = await getPayload({ config: configPromise })
|
|
792
|
+
const global = await payload.findGlobal({
|
|
793
|
+
slug: 'appearance-settings', // or 'your-global-slug'
|
|
794
|
+
})
|
|
795
|
+
return global?.themeConfiguration
|
|
796
|
+
},
|
|
797
|
+
['appearance-settings'],
|
|
798
|
+
{
|
|
799
|
+
tags: ['global_appearance-settings'],
|
|
800
|
+
revalidate: 3600, // Revalidate every hour
|
|
801
|
+
},
|
|
802
|
+
)
|
|
725
803
|
|
|
726
|
-
//
|
|
727
|
-
|
|
804
|
+
// Use in your layout
|
|
805
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
806
|
+
const themeConfiguration = await getCachedTheme()
|
|
807
|
+
// Use themeConfiguration...
|
|
808
|
+
}
|
|
809
|
+
```
|
|
728
810
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
useGlobal: true, // Set to true when using standalone global
|
|
733
|
-
depth: 2,
|
|
734
|
-
locale: 'en',
|
|
735
|
-
})
|
|
811
|
+
**Legacy Helper (still available):**
|
|
812
|
+
|
|
813
|
+
If you need the helper function, `fetchThemeConfiguration` is still exported:
|
|
736
814
|
|
|
737
|
-
|
|
815
|
+
```typescript
|
|
816
|
+
import { fetchThemeConfiguration } from '@kilivi/payloadcms-theme-management'
|
|
817
|
+
|
|
818
|
+
// Simple fetch without caching
|
|
738
819
|
const theme = await fetchThemeConfiguration({
|
|
739
|
-
tenantSlug: 'tenant-123', // Optional: tenant identifier
|
|
740
820
|
collectionSlug: 'appearance-settings',
|
|
741
821
|
useGlobal: true,
|
|
742
|
-
depth: 2,
|
|
743
|
-
})
|
|
744
|
-
|
|
745
|
-
// From collection (default behavior)
|
|
746
|
-
const theme = await fetchThemeConfiguration({
|
|
747
|
-
collectionSlug: 'site-settings', // Collection slug
|
|
748
|
-
useGlobal: false, // Use collection endpoint
|
|
749
|
-
depth: 2,
|
|
750
|
-
locale: 'en',
|
|
751
822
|
})
|
|
752
823
|
```
|
|
753
824
|
|
|
825
|
+
However, prefer the native Payload API + `unstable_cache` approach for better performance and automatic invalidation support.
|
|
826
|
+
|
|
754
827
|
### Custom Theme Preset
|
|
755
828
|
|
|
756
829
|
```typescript
|
|
@@ -906,7 +979,15 @@ Created for Payload CMS v3 applications.
|
|
|
906
979
|
|
|
907
980
|
## Changelog
|
|
908
981
|
|
|
909
|
-
###
|
|
982
|
+
### v1.0.2 (Latest)
|
|
983
|
+
|
|
984
|
+
- ✅ **Added:** Automatic cache invalidation for standalone globals
|
|
985
|
+
- ✅ **Fixed:** Correct Payload CMS v3 API usage (findGlobal instead of non-existent findBySlug)
|
|
986
|
+
- ✅ **Improved:** Multi-tenant documentation with proper examples
|
|
987
|
+
- ✅ **Improved:** Cache optimization examples with unstable_cache
|
|
988
|
+
- ✅ **Updated:** README and Multi-Tenant guide with accurate API methods
|
|
989
|
+
|
|
990
|
+
### v1.0.0
|
|
910
991
|
|
|
911
992
|
- ✅ **Added:** Standalone Global Support — create separate appearance settings as a global
|
|
912
993
|
- ✅ **Added:** Auto-populate Light/Dark colors when selecting a theme
|
|
@@ -916,20 +997,10 @@ Created for Payload CMS v3 applications.
|
|
|
916
997
|
- ✅ **Added:** Professional color picker (react-colorful)
|
|
917
998
|
- ✅ **Improved:** shadcn/ui and TweakCN compatibility
|
|
918
999
|
- ✅ **Improved:** SSR theme injection, zero FOUC, and performance optimizations
|
|
919
|
-
- ✅ **
|
|
920
|
-
|
|
921
|
-
### v0.1.9
|
|
922
|
-
|
|
923
|
-
- ✅ **Fixed:** Server/client component separation
|
|
1000
|
+
- ✅ **Added:** Server/client component separation
|
|
924
1001
|
- ✅ **Added:** `server-only` package to prevent bundling errors
|
|
925
1002
|
- ✅ **Changed:** `ServerThemeInjector` now exported from `/server` entry
|
|
926
|
-
- ✅ **Added:** Comprehensive documentation
|
|
927
|
-
|
|
928
|
-
### v0.1.7
|
|
929
|
-
|
|
930
|
-
- ✅ **Fixed:** ESM import resolution with `.js` extensions
|
|
931
|
-
- ✅ **Added:** SWC + TypeScript build pipeline
|
|
932
1003
|
|
|
933
|
-
### v0.
|
|
1004
|
+
### v0.6.0
|
|
934
1005
|
|
|
935
|
-
- Initial release with
|
|
1006
|
+
- Initial release with core theming functionality
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemeColorPickerField.d.ts","sourceRoot":"","sources":["../../src/fields/ThemeColorPickerField.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ThemeColorPickerField.d.ts","sourceRoot":"","sources":["../../src/fields/ThemeColorPickerField.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAmCvD,QAAA,MAAM,qBAAqB,EAAE,wBAsI5B,CAAA;AAED,eAAe,qBAAqB,CAAA"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useField } from '@payloadcms/ui';
|
|
4
|
+
import Color from 'color';
|
|
4
5
|
import { HexColorInput, HexColorPicker } from 'react-colorful';
|
|
5
6
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
6
7
|
// Import CSS for styling
|
|
@@ -25,60 +26,7 @@ function resolveLocalizedValue(value, fallback) {
|
|
|
25
26
|
* Convert various color formats to HEX for the color picker
|
|
26
27
|
*/ function toHex(color) {
|
|
27
28
|
if (!color) return '#000000';
|
|
28
|
-
|
|
29
|
-
if (color.startsWith('#')) return color;
|
|
30
|
-
// OKLCH format - extract hue and convert to approximate hex
|
|
31
|
-
if (color.startsWith('oklch')) {
|
|
32
|
-
// For now, return a default - proper conversion requires color-conversion library
|
|
33
|
-
return '#3b82f6' // blue-500 as default
|
|
34
|
-
;
|
|
35
|
-
}
|
|
36
|
-
// HSL format
|
|
37
|
-
if (color.startsWith('hsl')) {
|
|
38
|
-
const regex = /hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/;
|
|
39
|
-
const match = regex.exec(color);
|
|
40
|
-
if (match) {
|
|
41
|
-
const [, h, s, l] = match.map(Number);
|
|
42
|
-
return hslToHex(h, s, l);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return '#000000';
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Convert HSL to HEX
|
|
49
|
-
*/ function hslToHex(h, s, l) {
|
|
50
|
-
s /= 100;
|
|
51
|
-
l /= 100;
|
|
52
|
-
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
53
|
-
const x = c * (1 - Math.abs(h / 60 % 2 - 1));
|
|
54
|
-
const m = l - c / 2;
|
|
55
|
-
let r = 0;
|
|
56
|
-
let g = 0;
|
|
57
|
-
let b = 0;
|
|
58
|
-
if (h >= 0 && h < 60) {
|
|
59
|
-
r = c;
|
|
60
|
-
g = x;
|
|
61
|
-
} else if (h >= 60 && h < 120) {
|
|
62
|
-
r = x;
|
|
63
|
-
g = c;
|
|
64
|
-
} else if (h >= 120 && h < 180) {
|
|
65
|
-
g = c;
|
|
66
|
-
b = x;
|
|
67
|
-
} else if (h >= 180 && h < 240) {
|
|
68
|
-
g = x;
|
|
69
|
-
b = c;
|
|
70
|
-
} else if (h >= 240 && h < 300) {
|
|
71
|
-
r = x;
|
|
72
|
-
b = c;
|
|
73
|
-
} else if (h >= 300 && h < 360) {
|
|
74
|
-
r = c;
|
|
75
|
-
b = x;
|
|
76
|
-
}
|
|
77
|
-
const toHex = (n)=>{
|
|
78
|
-
const hex = Math.round((n + m) * 255).toString(16);
|
|
79
|
-
return hex.length === 1 ? '0' + hex : hex;
|
|
80
|
-
};
|
|
81
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
29
|
+
return Color(color).hex().toString();
|
|
82
30
|
}
|
|
83
31
|
const ThemeColorPickerField = ({ field, path })=>{
|
|
84
32
|
const { value, setValue } = useField({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/fields/ThemeColorPickerField.tsx"],"sourcesContent":["'use client'\n\nimport { useField } from '@payloadcms/ui'\nimport type { TextFieldClientComponent } from 'payload'\nimport { HexColorInput, HexColorPicker } from 'react-colorful'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\n// Import CSS for styling\nif (typeof window !== 'undefined') {\n // @ts-expect-error - Dynamic CSS import\n import('./ThemeColorPickerField.css').catch(() => {})\n}\n\nfunction resolveLocalizedValue(value: unknown, fallback: string) {\n if (typeof value === 'string') {\n return value\n }\n\n if (typeof value === 'object' && value !== null) {\n const localized = value as Record<string, unknown>\n const resolved = localized.en ?? localized.cs ?? localized['']\n if (typeof resolved === 'string') {\n return resolved\n }\n }\n\n return fallback\n}\n\n/**\n * Convert various color formats to HEX for the color picker\n */\nfunction toHex(color: string): string {\n if (!color) return '#000000'\n\n // Already hex\n if (color.startsWith('#')) return color\n\n // OKLCH format - extract hue and convert to approximate hex\n if (color.startsWith('oklch')) {\n // For now, return a default - proper conversion requires color-conversion library\n return '#3b82f6' // blue-500 as default\n }\n\n // HSL format\n if (color.startsWith('hsl')) {\n const regex = /hsl\\((\\d+),\\s*(\\d+)%,\\s*(\\d+)%\\)/\n const match = regex.exec(color)\n if (match) {\n const [, h, s, l] = match.map(Number)\n return hslToHex(h, s, l)\n }\n }\n\n return '#000000'\n}\n\n/**\n * Convert HSL to HEX\n */\nfunction hslToHex(h: number, s: number, l: number): string {\n s /= 100\n l /= 100\n\n const c = (1 - Math.abs(2 * l - 1)) * s\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1))\n const m = l - c / 2\n\n let r = 0\n let g = 0\n let b = 0\n\n if (h >= 0 && h < 60) {\n r = c\n g = x\n } else if (h >= 60 && h < 120) {\n r = x\n g = c\n } else if (h >= 120 && h < 180) {\n g = c\n b = x\n } else if (h >= 180 && h < 240) {\n g = x\n b = c\n } else if (h >= 240 && h < 300) {\n r = x\n b = c\n } else if (h >= 300 && h < 360) {\n r = c\n b = x\n }\n\n const toHex = (n: number) => {\n const hex = Math.round((n + m) * 255).toString(16)\n return hex.length === 1 ? '0' + hex : hex\n }\n\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`\n}\n\nconst ThemeColorPickerField: TextFieldClientComponent = ({ field, path }) => {\n const { value, setValue } = useField<string>({ path })\n const [localValue, setLocalValue] = useState(value || '')\n const [showPicker, setShowPicker] = useState(false)\n const [hexValue, setHexValue] = useState(toHex(value || ''))\n const pickerRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n setLocalValue(value || '')\n setHexValue(toHex(value || ''))\n }, [value])\n\n // Close picker when clicking outside\n useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n if (pickerRef.current && !pickerRef.current.contains(event.target as Node)) {\n setShowPicker(false)\n }\n }\n\n if (showPicker) {\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }\n }, [showPicker])\n\n const handleHexChange = useCallback(\n (hex: string) => {\n setHexValue(hex)\n setLocalValue(hex)\n setValue(hex)\n },\n [setValue],\n )\n\n const handleTextChange = useCallback(\n (nextValue: string) => {\n setLocalValue(nextValue)\n setValue(nextValue)\n setHexValue(toHex(nextValue))\n },\n [setValue],\n )\n\n const fieldName = path.split('.').pop() || ''\n const label = resolveLocalizedValue(field.label, fieldName)\n const description = resolveLocalizedValue(field.admin?.description, '')\n\n return (\n <div className=\"theme-color-picker-field\">\n <div className=\"field-header\">\n <label className=\"field-label\">{label}</label>\n {description && <div className=\"field-description\">{description}</div>}\n </div>\n\n <div className=\"color-picker-container\">\n <div className=\"color-input-row\">\n <button\n type=\"button\"\n className=\"color-swatch-btn\"\n style={{ backgroundColor: hexValue }}\n onClick={() => setShowPicker(!showPicker)}\n aria-label=\"Open color picker\"\n title={hexValue}\n />\n <input\n type=\"text\"\n value={localValue || ''}\n onChange={(event) => handleTextChange(event.target.value)}\n placeholder=\"oklch(...) or #hex\"\n className=\"color-text-input\"\n />\n </div>\n\n {showPicker && (\n <div ref={pickerRef} className=\"color-picker-popover\">\n <div className=\"picker-header\">\n <span className=\"picker-title\">Color Picker</span>\n <button\n type=\"button\"\n onClick={() => setShowPicker(false)}\n className=\"picker-close-btn\"\n aria-label=\"Close\"\n >\n ✕\n </button>\n </div>\n\n <HexColorPicker color={hexValue} onChange={handleHexChange} className=\"hex-picker\" />\n\n <div className=\"hex-input-row\">\n <HexColorInput\n color={hexValue}\n onChange={handleHexChange}\n prefixed\n placeholder=\"#000000\"\n className=\"hex-input\"\n />\n </div>\n\n <div className=\"color-swatches\">\n <div className=\"swatches-label\">Quick Colors</div>\n <div className=\"swatches-grid\">\n {[\n '#ef4444',\n '#f97316',\n '#f59e0b',\n '#84cc16',\n '#10b981',\n '#06b6d4',\n '#3b82f6',\n '#8b5cf6',\n '#ec4899',\n '#64748b',\n '#000000',\n '#ffffff',\n ].map((color) => (\n <button\n key={color}\n type=\"button\"\n onClick={() => handleHexChange(color)}\n className={`swatch-btn ${hexValue.toLowerCase() === color.toLowerCase() ? 'selected' : ''}`}\n style={{ backgroundColor: color }}\n aria-label={`Select ${color}`}\n title={color}\n />\n ))}\n </div>\n </div>\n </div>\n )}\n </div>\n </div>\n )\n}\n\nexport default ThemeColorPickerField\n"],"names":["useField","HexColorInput","HexColorPicker","useCallback","useEffect","useRef","useState","window","catch","resolveLocalizedValue","value","fallback","localized","resolved","en","cs","toHex","color","startsWith","regex","match","exec","h","s","l","map","Number","hslToHex","c","Math","abs","x","m","r","g","b","n","hex","round","toString","length","ThemeColorPickerField","field","path","setValue","localValue","setLocalValue","showPicker","setShowPicker","hexValue","setHexValue","pickerRef","handleClickOutside","event","current","contains","target","document","addEventListener","removeEventListener","handleHexChange","handleTextChange","nextValue","fieldName","split","pop","label","description","admin","div","className","button","type","style","backgroundColor","onClick","aria-label","title","input","onChange","placeholder","ref","span","prefixed","toLowerCase"],"mappings":"AAAA;;AAEA,SAASA,QAAQ,QAAQ,iBAAgB;AAEzC,SAASC,aAAa,EAAEC,cAAc,QAAQ,iBAAgB;AAC9D,SAASC,WAAW,EAAEC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AAEhE,yBAAyB;AACzB,IAAI,OAAOC,WAAW,aAAa;IACjC,wCAAwC;IACxC,MAAM,CAAC,+BAA+BC,KAAK,CAAC,KAAO;AACrD;AAEA,SAASC,sBAAsBC,KAAc,EAAEC,QAAgB;IAC7D,IAAI,OAAOD,UAAU,UAAU;QAC7B,OAAOA;IACT;IAEA,IAAI,OAAOA,UAAU,YAAYA,UAAU,MAAM;QAC/C,MAAME,YAAYF;QAClB,MAAMG,WAAWD,UAAUE,EAAE,IAAIF,UAAUG,EAAE,IAAIH,SAAS,CAAC,GAAG;QAC9D,IAAI,OAAOC,aAAa,UAAU;YAChC,OAAOA;QACT;IACF;IAEA,OAAOF;AACT;AAEA;;CAEC,GACD,SAASK,MAAMC,KAAa;IAC1B,IAAI,CAACA,OAAO,OAAO;IAEnB,cAAc;IACd,IAAIA,MAAMC,UAAU,CAAC,MAAM,OAAOD;IAElC,4DAA4D;IAC5D,IAAIA,MAAMC,UAAU,CAAC,UAAU;QAC7B,kFAAkF;QAClF,OAAO,UAAU,sBAAsB;;IACzC;IAEA,aAAa;IACb,IAAID,MAAMC,UAAU,CAAC,QAAQ;QAC3B,MAAMC,QAAQ;QACd,MAAMC,QAAQD,MAAME,IAAI,CAACJ;QACzB,IAAIG,OAAO;YACT,MAAM,GAAGE,GAAGC,GAAGC,EAAE,GAAGJ,MAAMK,GAAG,CAACC;YAC9B,OAAOC,SAASL,GAAGC,GAAGC;QACxB;IACF;IAEA,OAAO;AACT;AAEA;;CAEC,GACD,SAASG,SAASL,CAAS,EAAEC,CAAS,EAAEC,CAAS;IAC/CD,KAAK;IACLC,KAAK;IAEL,MAAMI,IAAI,AAAC,CAAA,IAAIC,KAAKC,GAAG,CAAC,IAAIN,IAAI,EAAC,IAAKD;IACtC,MAAMQ,IAAIH,IAAK,CAAA,IAAIC,KAAKC,GAAG,CAAC,AAAER,IAAI,KAAM,IAAK,EAAC;IAC9C,MAAMU,IAAIR,IAAII,IAAI;IAElB,IAAIK,IAAI;IACR,IAAIC,IAAI;IACR,IAAIC,IAAI;IAER,IAAIb,KAAK,KAAKA,IAAI,IAAI;QACpBW,IAAIL;QACJM,IAAIH;IACN,OAAO,IAAIT,KAAK,MAAMA,IAAI,KAAK;QAC7BW,IAAIF;QACJG,IAAIN;IACN,OAAO,IAAIN,KAAK,OAAOA,IAAI,KAAK;QAC9BY,IAAIN;QACJO,IAAIJ;IACN,OAAO,IAAIT,KAAK,OAAOA,IAAI,KAAK;QAC9BY,IAAIH;QACJI,IAAIP;IACN,OAAO,IAAIN,KAAK,OAAOA,IAAI,KAAK;QAC9BW,IAAIF;QACJI,IAAIP;IACN,OAAO,IAAIN,KAAK,OAAOA,IAAI,KAAK;QAC9BW,IAAIL;QACJO,IAAIJ;IACN;IAEA,MAAMf,QAAQ,CAACoB;QACb,MAAMC,MAAMR,KAAKS,KAAK,CAAC,AAACF,CAAAA,IAAIJ,CAAAA,IAAK,KAAKO,QAAQ,CAAC;QAC/C,OAAOF,IAAIG,MAAM,KAAK,IAAI,MAAMH,MAAMA;IACxC;IAEA,OAAO,CAAC,CAAC,EAAErB,MAAMiB,KAAKjB,MAAMkB,KAAKlB,MAAMmB,IAAI;AAC7C;AAEA,MAAMM,wBAAkD,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE;IACtE,MAAM,EAAEjC,KAAK,EAAEkC,QAAQ,EAAE,GAAG5C,SAAiB;QAAE2C;IAAK;IACpD,MAAM,CAACE,YAAYC,cAAc,GAAGxC,SAASI,SAAS;IACtD,MAAM,CAACqC,YAAYC,cAAc,GAAG1C,SAAS;IAC7C,MAAM,CAAC2C,UAAUC,YAAY,GAAG5C,SAASU,MAAMN,SAAS;IACxD,MAAMyC,YAAY9C,OAAuB;IAEzCD,UAAU;QACR0C,cAAcpC,SAAS;QACvBwC,YAAYlC,MAAMN,SAAS;IAC7B,GAAG;QAACA;KAAM;IAEV,qCAAqC;IACrCN,UAAU;QACR,SAASgD,mBAAmBC,KAAiB;YAC3C,IAAIF,UAAUG,OAAO,IAAI,CAACH,UAAUG,OAAO,CAACC,QAAQ,CAACF,MAAMG,MAAM,GAAW;gBAC1ER,cAAc;YAChB;QACF;QAEA,IAAID,YAAY;YACdU,SAASC,gBAAgB,CAAC,aAAaN;YACvC,OAAO,IAAMK,SAASE,mBAAmB,CAAC,aAAaP;QACzD;IACF,GAAG;QAACL;KAAW;IAEf,MAAMa,kBAAkBzD,YACtB,CAACkC;QACCa,YAAYb;QACZS,cAAcT;QACdO,SAASP;IACX,GACA;QAACO;KAAS;IAGZ,MAAMiB,mBAAmB1D,YACvB,CAAC2D;QACChB,cAAcgB;QACdlB,SAASkB;QACTZ,YAAYlC,MAAM8C;IACpB,GACA;QAAClB;KAAS;IAGZ,MAAMmB,YAAYpB,KAAKqB,KAAK,CAAC,KAAKC,GAAG,MAAM;IAC3C,MAAMC,QAAQzD,sBAAsBiC,MAAMwB,KAAK,EAAEH;IACjD,MAAMI,cAAc1D,sBAAsBiC,MAAM0B,KAAK,EAAED,aAAa;IAEpE,qBACE,MAACE;QAAIC,WAAU;;0BACb,MAACD;gBAAIC,WAAU;;kCACb,KAACJ;wBAAMI,WAAU;kCAAeJ;;oBAC/BC,6BAAe,KAACE;wBAAIC,WAAU;kCAAqBH;;;;0BAGtD,MAACE;gBAAIC,WAAU;;kCACb,MAACD;wBAAIC,WAAU;;0CACb,KAACC;gCACCC,MAAK;gCACLF,WAAU;gCACVG,OAAO;oCAAEC,iBAAiBzB;gCAAS;gCACnC0B,SAAS,IAAM3B,cAAc,CAACD;gCAC9B6B,cAAW;gCACXC,OAAO5B;;0CAET,KAAC6B;gCACCN,MAAK;gCACL9D,OAAOmC,cAAc;gCACrBkC,UAAU,CAAC1B,QAAUQ,iBAAiBR,MAAMG,MAAM,CAAC9C,KAAK;gCACxDsE,aAAY;gCACZV,WAAU;;;;oBAIbvB,4BACC,MAACsB;wBAAIY,KAAK9B;wBAAWmB,WAAU;;0CAC7B,MAACD;gCAAIC,WAAU;;kDACb,KAACY;wCAAKZ,WAAU;kDAAe;;kDAC/B,KAACC;wCACCC,MAAK;wCACLG,SAAS,IAAM3B,cAAc;wCAC7BsB,WAAU;wCACVM,cAAW;kDACZ;;;;0CAKH,KAAC1E;gCAAee,OAAOgC;gCAAU8B,UAAUnB;gCAAiBU,WAAU;;0CAEtE,KAACD;gCAAIC,WAAU;0CACb,cAAA,KAACrE;oCACCgB,OAAOgC;oCACP8B,UAAUnB;oCACVuB,QAAQ;oCACRH,aAAY;oCACZV,WAAU;;;0CAId,MAACD;gCAAIC,WAAU;;kDACb,KAACD;wCAAIC,WAAU;kDAAiB;;kDAChC,KAACD;wCAAIC,WAAU;kDACZ;4CACC;4CACA;4CACA;4CACA;4CACA;4CACA;4CACA;4CACA;4CACA;4CACA;4CACA;4CACA;yCACD,CAAC7C,GAAG,CAAC,CAACR,sBACL,KAACsD;gDAECC,MAAK;gDACLG,SAAS,IAAMf,gBAAgB3C;gDAC/BqD,WAAW,CAAC,WAAW,EAAErB,SAASmC,WAAW,OAAOnE,MAAMmE,WAAW,KAAK,aAAa,IAAI;gDAC3FX,OAAO;oDAAEC,iBAAiBzD;gDAAM;gDAChC2D,cAAY,CAAC,OAAO,EAAE3D,OAAO;gDAC7B4D,OAAO5D;+CANFA;;;;;;;;;;AAgBzB;AAEA,eAAewB,sBAAqB"}
|
|
1
|
+
{"version":3,"sources":["../../src/fields/ThemeColorPickerField.tsx"],"sourcesContent":["'use client'\n\nimport { useField } from '@payloadcms/ui'\nimport Color from 'color'\nimport type { TextFieldClientComponent } from 'payload'\nimport { HexColorInput, HexColorPicker } from 'react-colorful'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\n// Import CSS for styling\nif (typeof window !== 'undefined') {\n // @ts-expect-error - Dynamic CSS import\n import('./ThemeColorPickerField.css').catch(() => {})\n}\n\nfunction resolveLocalizedValue(value: unknown, fallback: string) {\n if (typeof value === 'string') {\n return value\n }\n\n if (typeof value === 'object' && value !== null) {\n const localized = value as Record<string, unknown>\n const resolved = localized.en ?? localized.cs ?? localized['']\n if (typeof resolved === 'string') {\n return resolved\n }\n }\n\n return fallback\n}\n\n/**\n * Convert various color formats to HEX for the color picker\n */\nfunction toHex(color: string): string {\n if (!color) return '#000000'\n\n return Color(color).hex().toString()\n}\n\nconst ThemeColorPickerField: TextFieldClientComponent = ({ field, path }) => {\n const { value, setValue } = useField<string>({ path })\n const [localValue, setLocalValue] = useState(value || '')\n const [showPicker, setShowPicker] = useState(false)\n const [hexValue, setHexValue] = useState(toHex(value || ''))\n const pickerRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n setLocalValue(value || '')\n setHexValue(toHex(value || ''))\n }, [value])\n\n // Close picker when clicking outside\n useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n if (pickerRef.current && !pickerRef.current.contains(event.target as Node)) {\n setShowPicker(false)\n }\n }\n\n if (showPicker) {\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }\n }, [showPicker])\n\n const handleHexChange = useCallback(\n (hex: string) => {\n setHexValue(hex)\n setLocalValue(hex)\n setValue(hex)\n },\n [setValue],\n )\n\n const handleTextChange = useCallback(\n (nextValue: string) => {\n setLocalValue(nextValue)\n setValue(nextValue)\n setHexValue(toHex(nextValue))\n },\n [setValue],\n )\n\n const fieldName = path.split('.').pop() || ''\n const label = resolveLocalizedValue(field.label, fieldName)\n const description = resolveLocalizedValue(field.admin?.description, '')\n\n return (\n <div className=\"theme-color-picker-field\">\n <div className=\"field-header\">\n <label className=\"field-label\">{label}</label>\n {description && <div className=\"field-description\">{description}</div>}\n </div>\n\n <div className=\"color-picker-container\">\n <div className=\"color-input-row\">\n <button\n type=\"button\"\n className=\"color-swatch-btn\"\n style={{ backgroundColor: hexValue }}\n onClick={() => setShowPicker(!showPicker)}\n aria-label=\"Open color picker\"\n title={hexValue}\n />\n <input\n type=\"text\"\n value={localValue || ''}\n onChange={(event) => handleTextChange(event.target.value)}\n placeholder=\"oklch(...) or #hex\"\n className=\"color-text-input\"\n />\n </div>\n\n {showPicker && (\n <div ref={pickerRef} className=\"color-picker-popover\">\n <div className=\"picker-header\">\n <span className=\"picker-title\">Color Picker</span>\n <button\n type=\"button\"\n onClick={() => setShowPicker(false)}\n className=\"picker-close-btn\"\n aria-label=\"Close\"\n >\n ✕\n </button>\n </div>\n\n <HexColorPicker color={hexValue} onChange={handleHexChange} className=\"hex-picker\" />\n\n <div className=\"hex-input-row\">\n <HexColorInput\n color={hexValue}\n onChange={handleHexChange}\n prefixed\n placeholder=\"#000000\"\n className=\"hex-input\"\n />\n </div>\n\n <div className=\"color-swatches\">\n <div className=\"swatches-label\">Quick Colors</div>\n <div className=\"swatches-grid\">\n {[\n '#ef4444',\n '#f97316',\n '#f59e0b',\n '#84cc16',\n '#10b981',\n '#06b6d4',\n '#3b82f6',\n '#8b5cf6',\n '#ec4899',\n '#64748b',\n '#000000',\n '#ffffff',\n ].map((color) => (\n <button\n key={color}\n type=\"button\"\n onClick={() => handleHexChange(color)}\n className={`swatch-btn ${hexValue.toLowerCase() === color.toLowerCase() ? 'selected' : ''}`}\n style={{ backgroundColor: color }}\n aria-label={`Select ${color}`}\n title={color}\n />\n ))}\n </div>\n </div>\n </div>\n )}\n </div>\n </div>\n )\n}\n\nexport default ThemeColorPickerField\n"],"names":["useField","Color","HexColorInput","HexColorPicker","useCallback","useEffect","useRef","useState","window","catch","resolveLocalizedValue","value","fallback","localized","resolved","en","cs","toHex","color","hex","toString","ThemeColorPickerField","field","path","setValue","localValue","setLocalValue","showPicker","setShowPicker","hexValue","setHexValue","pickerRef","handleClickOutside","event","current","contains","target","document","addEventListener","removeEventListener","handleHexChange","handleTextChange","nextValue","fieldName","split","pop","label","description","admin","div","className","button","type","style","backgroundColor","onClick","aria-label","title","input","onChange","placeholder","ref","span","prefixed","map","toLowerCase"],"mappings":"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"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiD,MAAM,EAAa,MAAM,SAAS,CAAA;AAG/F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAG/C,OAAO,KAAK,EAAE,8BAA8B,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAA;AAqG9F,eAAO,MAAM,qBAAqB,GAAI,UAAS,4BAAiC,KAAG,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiD,MAAM,EAAa,MAAM,SAAS,CAAA;AAG/F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAG/C,OAAO,KAAK,EAAE,8BAA8B,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAA;AAqG9F,eAAO,MAAM,qBAAqB,GAAI,UAAS,4BAAiC,KAAG,MAsMlF,CAAA;AAED,KAAK,iBAAiB,GAAG,MAAM,GAAG,8BAA8B,CAAA;AAEhE,eAAO,MAAM,uBAAuB,GAClC,UAAU,iBAAiB,KAC1B,OAAO,CAAC,sBAAsB,GAAG,IAAI,CA6EvC,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,WAAW,MAAM,KAAG,WAAW,GAAG,IAEhE,CAAA;AAED,eAAO,MAAM,wBAAwB,QAAO,WAAW,EAEtD,CAAA;AAED,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACnE,YAAY,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACtE,YAAY,EAAE,QAAQ,EAAE,MAAM,qCAAqC,CAAA;AACnE,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACnF,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,iCAAiC,CAAA;AACxC,YAAY,EAAE,4BAA4B,EAAE,8BAA8B,EAAE,MAAM,YAAY,CAAA;AAC9F,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,sCAAsC,CAAA;AAC7C,YAAY,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAA;AACtF,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACzE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACxE,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAA;AAGvE,OAAO,EACL,gBAAgB,IAAI,iBAAiB,EACrC,oBAAoB,GACrB,MAAM,6BAA6B,CAAA;AACpC,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAEhF,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAG1D,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,aAAa,EACb,eAAe,GAChB,MAAM,4BAA4B,CAAA;AAMnC,eAAO,MAAM,qBAAqB,aArVa,4BAA4B,KAAQ,MAqVzB,CAAA;AAC1D,oDAAoD;AACpD,eAAO,MAAM,wBAAwB,aAvVU,4BAA4B,KAAQ,MAuVtB,CAAA;AAG7D,OAAO,EACL,YAAY,EACZ,eAAe,EACf,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,mBAAmB,CAAA;AAE1B,eAAe,qBAAqB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -155,6 +155,27 @@ export const themeManagementPlugin = (options = {})=>{
|
|
|
155
155
|
hooks: {
|
|
156
156
|
beforeChange: [
|
|
157
157
|
beforeChangeHook
|
|
158
|
+
],
|
|
159
|
+
afterChange: [
|
|
160
|
+
async ({ doc, req })=>{
|
|
161
|
+
// Invalidate cache after appearance settings change
|
|
162
|
+
if (!req?.context?.disableRevalidate) {
|
|
163
|
+
try {
|
|
164
|
+
const cacheTag = `global_${standaloneCollectionSlug}`;
|
|
165
|
+
// Import revalidateTag dynamically to avoid issues in non-Next.js environments
|
|
166
|
+
const { revalidateTag } = await import('next/cache');
|
|
167
|
+
revalidateTag(cacheTag);
|
|
168
|
+
if (enableLogging) {
|
|
169
|
+
req.payload.logger.info(`🎨 Theme Management Plugin: cache invalidated for tag "${cacheTag}"`);
|
|
170
|
+
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (enableLogging) {
|
|
173
|
+
req.payload.logger.error('🎨 Theme Management Plugin: cache invalidation failed:', error);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return doc;
|
|
178
|
+
}
|
|
158
179
|
]
|
|
159
180
|
}
|
|
160
181
|
};
|
|
@@ -214,9 +235,11 @@ export const fetchThemeConfiguration = async (options)=>{
|
|
|
214
235
|
// Fetch from global endpoint
|
|
215
236
|
const params = new URLSearchParams();
|
|
216
237
|
if (typeof depth === 'number') params.set('depth', String(depth));
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
|
|
238
|
+
// For globals, keep tenant param for backwards compatibility, and also add where[...] filter
|
|
239
|
+
if (tenantSlug) {
|
|
240
|
+
params.set('tenant', tenantSlug);
|
|
241
|
+
params.set('where[tenant][equals]', String(tenantSlug));
|
|
242
|
+
}
|
|
220
243
|
Object.entries(queryParams).forEach(([key, value])=>{
|
|
221
244
|
if (value === undefined) return;
|
|
222
245
|
params.set(key, String(value));
|
|
@@ -235,7 +258,8 @@ export const fetchThemeConfiguration = async (options)=>{
|
|
|
235
258
|
limit: '1'
|
|
236
259
|
});
|
|
237
260
|
if (typeof depth === 'number') params.set('depth', String(depth));
|
|
238
|
-
|
|
261
|
+
// For collections, use a where[tenant][equals] filter so Payload's find endpoint applies tenant scoping
|
|
262
|
+
if (tenantSlug) params.set('where[tenant][equals]', String(tenantSlug));
|
|
239
263
|
if (locale) params.set('locale', locale);
|
|
240
264
|
if (draft) params.set('draft', 'true');
|
|
241
265
|
Object.entries(queryParams).forEach(([key, value])=>{
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { CollectionConfig, Config, Field, GlobalConfig, Plugin, TabsField } from 'payload'\nimport { createThemeConfigurationField } from './fields/themeConfigurationField.js'\nimport type { ThemeTab } from './fields/themeConfigurationField.js'\nimport type { SiteThemeConfiguration } from './payload-types.js'\nimport type { ThemePreset } from './presets.js'\nimport { allThemePresets } from './presets.js'\nimport { getTranslations, translations } from './translations.js'\nimport type { FetchThemeConfigurationOptions, ThemeManagementPluginOptions } from './types.js'\n\nconst THEME_FIELD_NAME = 'themeConfiguration'\n\n/**\n * Removes existing theme configuration from a group field (legacy)\n */\nconst removeExistingThemeField = (fields: Field[] = []): Field[] =>\n fields.filter((field) => {\n const candidate = field as { name?: unknown }\n return candidate?.name !== THEME_FIELD_NAME\n })\n\n/**\n * Finds the tabs field in the collection fields array\n */\nconst findTabsField = (fields: Field[] = []): TabsField | null => {\n for (const field of fields) {\n if (field.type === 'tabs') {\n return field as TabsField\n }\n }\n return null\n}\n\n/**\n * Removes existing Appearance Settings tab if it exists\n */\nconst removeExistingThemeTab = (\n tabs: NonNullable<TabsField['tabs']>,\n): NonNullable<TabsField['tabs']> => {\n const knownLabels = Object.values(translations).map((t) => t.tabLabel)\n return tabs.filter((tab) => {\n // Normalize label to string (support localized object or plain string)\n let labelValues: string[] = []\n if (typeof tab.label === 'object' && tab.label !== null) {\n labelValues = Object.values(tab.label as Record<string, string>)\n } else if (typeof tab.label === 'string') {\n labelValues = [tab.label]\n }\n\n // If any of the label values match a known plugin tab label, remove it\n const matchesKnown = labelValues.some((v) => knownLabels.includes(v))\n\n return !matchesKnown\n })\n}\n\n/**\n * Injects theme tab into existing tabs field\n */\nconst injectThemeTab = (\n fields: Field[] | undefined,\n themeTab: ThemeTab,\n enableLogging: boolean,\n): Field[] => {\n // First, remove any legacy theme field (group-based)\n const sanitizedFields = removeExistingThemeField(fields ?? [])\n\n // Find the tabs field\n const tabsField = findTabsField(sanitizedFields)\n\n if (!tabsField || !tabsField.tabs) {\n if (enableLogging) {\n console.warn(\n '🎨 Theme Management Plugin: No tabs field found. Creating a new tabs structure.',\n )\n }\n\n // If no tabs exist, create a basic tabs structure with just the theme tab\n const newTabsField: TabsField = {\n type: 'tabs',\n tabs: [themeTab],\n }\n\n return [...sanitizedFields, newTabsField]\n }\n\n // Remove existing theme tab if present\n const sanitizedTabs = removeExistingThemeTab(tabsField.tabs)\n\n // Add theme tab to the end\n const updatedTabs = [...sanitizedTabs, themeTab]\n\n if (enableLogging) {\n console.log('🎨 Theme Management Plugin: injecting Appearance Settings tab')\n }\n\n // Update the tabs field\n const updatedTabsField: TabsField = {\n ...tabsField,\n tabs: updatedTabs,\n }\n\n // Replace the old tabs field with the updated one\n return sanitizedFields.map((field) => (field.type === 'tabs' ? updatedTabsField : field))\n}\n\nconst ensureCollectionsArray = (collections: Config['collections']): CollectionConfig[] =>\n Array.isArray(collections) ? collections : []\n\nexport const themeManagementPlugin = (options: ThemeManagementPluginOptions = {}): Plugin => {\n return (config: Config): Config => {\n const {\n enabled = true,\n targetCollection = 'site-settings',\n useStandaloneCollection = false,\n standaloneCollectionSlug = 'appearance-settings',\n // Allow either string or localized object; default comes from plugin translations\n standaloneCollectionLabel = getTranslations().standaloneCollectionLabel,\n themePresets = allThemePresets,\n defaultTheme = 'cool',\n includeColorModeToggle = true,\n includeCustomCSS = true,\n includeBrandIdentity = false,\n enableAdvancedFeatures = true,\n enableLogging = false,\n } = options\n\n if (!enabled) {\n if (enableLogging) {\n console.log('🎨 Theme Management Plugin: disabled via options, skipping.')\n }\n return config\n }\n\n const themeTab = createThemeConfigurationField({\n themePresets,\n defaultTheme,\n includeColorModeToggle,\n includeCustomCSS,\n includeBrandIdentity: includeBrandIdentity ?? false,\n enableAdvancedFeatures,\n })\n\n // If using standalone collection, create a new global\n if (useStandaloneCollection) {\n if (enableLogging) {\n console.log(\n `🎨 Theme Management Plugin: creating standalone global \"${standaloneCollectionSlug}\"`,\n )\n }\n\n // Extract the fields from the tab directly (no wrapping in group)\n const tabFields = (themeTab.fields ?? []) as Field[]\n\n /**\n * Hook to auto-populate lightMode and darkMode colors when theme changes\n */\n type BeforeChangeArgs = {\n data?: {\n themeConfiguration?: {\n theme?: string\n lightMode?: Record<string, unknown>\n darkMode?: Record<string, unknown>\n }\n }\n }\n\n const beforeChangeHook = async (args: BeforeChangeArgs) => {\n const { data } = args\n\n // If theme is being changed, auto-populate color modes\n if (data?.themeConfiguration?.theme) {\n const themeName = data.themeConfiguration.theme\n const selectedPreset = themePresets.find((p) => p.name === themeName)\n\n if (selectedPreset) {\n // Initialize light and dark mode colors from preset if not already set\n if (!data.themeConfiguration.lightMode) {\n data.themeConfiguration.lightMode = {}\n }\n if (!data.themeConfiguration.darkMode) {\n data.themeConfiguration.darkMode = {}\n }\n\n // Only populate if not manually edited (first time or empty)\n const isLightModeEmpty = !data.themeConfiguration.lightMode?.background\n const isDarkModeEmpty = !data.themeConfiguration.darkMode?.background\n\n if (isLightModeEmpty && selectedPreset.lightMode) {\n data.themeConfiguration.lightMode = { ...selectedPreset.lightMode }\n }\n\n if (isDarkModeEmpty && selectedPreset.darkMode) {\n data.themeConfiguration.darkMode = { ...selectedPreset.darkMode }\n }\n }\n }\n\n return data\n }\n\n const standaloneGlobal: GlobalConfig = {\n slug: standaloneCollectionSlug,\n label: standaloneCollectionLabel,\n admin: {\n group: 'Settings',\n },\n // Provide a broad access object but cast to GlobalConfig['access'] to satisfy types\n access: ((): GlobalConfig['access'] => {\n const accessObj = {\n read: () => true,\n create: ({ req }: { req?: { user?: unknown } }) => !!req?.user,\n update: ({ req }: { req?: { user?: unknown } }) => !!req?.user,\n delete: ({ req }: { req?: { user?: unknown } }) => !!req?.user,\n } as unknown as GlobalConfig['access']\n return accessObj\n })(),\n fields: tabFields,\n hooks: {\n beforeChange: [beforeChangeHook],\n },\n }\n\n const globals = Array.isArray(config.globals) ? config.globals : []\n\n // Check if global already exists\n const existingIndex = globals.findIndex((g) => g.slug === standaloneCollectionSlug)\n\n if (existingIndex !== -1) {\n if (enableLogging) {\n console.warn(\n `🎨 Theme Management Plugin: global \"${standaloneCollectionSlug}\" already exists, skipping creation.`,\n )\n }\n return config\n }\n\n return {\n ...config,\n globals: [...globals, standaloneGlobal],\n }\n }\n\n // Otherwise, add as tab to existing collection\n let collectionTouched = false\n\n const collections = ensureCollectionsArray(config.collections).map((collection) => {\n if (collection.slug !== targetCollection) {\n return collection\n }\n\n collectionTouched = true\n\n if (enableLogging) {\n console.log(`🎨 Theme Management Plugin: enhancing collection \"${collection.slug}\"`)\n }\n\n const existingFields = Array.isArray(collection.fields) ? collection.fields : []\n\n const fields = injectThemeTab(existingFields, themeTab, enableLogging)\n\n return {\n ...collection,\n fields,\n }\n })\n\n if (!collectionTouched) {\n if (enableLogging) {\n console.warn(\n `🎨 Theme Management Plugin: collection \"${targetCollection}\" was not found, leaving config untouched.`,\n )\n }\n return config\n }\n\n return {\n ...config,\n collections,\n }\n }\n}\n\ntype FetchOptionsInput = string | FetchThemeConfigurationOptions\n\nexport const fetchThemeConfiguration = async (\n options?: FetchOptionsInput,\n): Promise<SiteThemeConfiguration | null> => {\n const normalizedOptions = typeof options === 'string' ? { tenantSlug: options } : (options ?? {})\n\n const {\n tenantSlug,\n collectionSlug = 'site-settings', // Can be overridden to 'appearance-settings' if using standalone\n depth,\n locale,\n draft,\n queryParams = {},\n useGlobal = false, // Set to true if using standalone global instead of collection\n } = normalizedOptions\n\n try {\n if (useGlobal) {\n // Fetch from global endpoint\n const params = new URLSearchParams()\n\n if (typeof depth === 'number') params.set('depth', String(depth))\n if (tenantSlug) params.set('tenant', tenantSlug)\n if (locale) params.set('locale', locale)\n if (draft) params.set('draft', 'true')\n\n Object.entries(queryParams).forEach(([key, value]) => {\n if (value === undefined) return\n params.set(key, String(value))\n })\n\n const queryString = params.toString()\n const url = `/api/globals/${collectionSlug}${queryString ? `?${queryString}` : ''}`\n\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error(`Failed to fetch theme global (status ${response.status})`)\n }\n\n const data = (await response.json()) as {\n themeConfiguration?: SiteThemeConfiguration\n }\n\n return data?.themeConfiguration ?? null\n } else {\n // Fetch from collection endpoint (legacy/default behavior)\n const params = new URLSearchParams({ limit: '1' })\n\n if (typeof depth === 'number') params.set('depth', String(depth))\n if (tenantSlug) params.set('tenant', tenantSlug)\n if (locale) params.set('locale', locale)\n if (draft) params.set('draft', 'true')\n\n Object.entries(queryParams).forEach(([key, value]) => {\n if (value === undefined) return\n params.set(key, String(value))\n })\n\n const url = `/api/${collectionSlug}?${params.toString()}`\n\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error(`Failed to fetch theme configuration (status ${response.status})`)\n }\n\n const data = (await response.json()) as {\n docs?: Array<{ themeConfiguration?: SiteThemeConfiguration }>\n }\n\n const doc = data.docs?.[0]\n\n return doc?.themeConfiguration ?? null\n }\n } catch (error) {\n console.warn('Failed to fetch theme configuration:', error)\n return null\n }\n}\n\nexport const getThemePreset = (themeName: string): ThemePreset | null => {\n return allThemePresets.find((preset) => preset.name === themeName) || null\n}\n\nexport const getAvailableThemePresets = (): ThemePreset[] => {\n return allThemePresets\n}\n\nexport { defaultThemePresets, allThemePresets } from './presets.js'\nexport type { ThemePreset, ThemeTypographyPreset } from './presets.js'\nexport type { ThemeTab } from './fields/themeConfigurationField.js'\nexport {\n extendedThemePresets,\n allExtendedThemePresets,\n extendedThemeToCSSVariables,\n} from './extended-presets.js'\nexport type { ExtendedThemePreset, ShadcnColorTokens } from './extended-presets.js'\nexport {\n applyExtendedTheme,\n generateExtendedThemeCSS,\n resetExtendedTheme,\n getExtendedThemeTokens,\n createUseExtendedTheme,\n getTailwindVarReferences,\n} from './utils/extendedThemeHelpers.js'\nexport type { ThemeManagementPluginOptions, FetchThemeConfigurationOptions } from './types.js'\nexport {\n DEFAULT_THEME_CONFIGURATION,\n resolveThemeConfiguration,\n} from './utils/resolveThemeConfiguration.js'\nexport type { ResolvedThemeConfiguration } from './utils/resolveThemeConfiguration.js'\nexport { generateThemeColorsCss, hexToHsl } from './utils/themeColors.js'\nexport { generateThemeCSS, getThemeStyles } from './utils/themeUtils.js'\nexport { getThemeHtmlAttributes } from './utils/themeHtmlAttributes.js'\n\n// Dynamic theme CSS generation\nexport {\n generateThemeCSS as generatePresetCSS,\n generateAllThemesCSS,\n} from './utils/generateThemeCSS.js'\nexport { getThemeDynamicCSS, getAllDynamicThemesCSS } from './utils/themeCSS.js'\n\nexport type { Mode, ThemeDefaults } from './providers/Theme/types.js'\nexport { ThemeProvider } from './providers/Theme/index.js'\n\n// Font loading utilities for Next.js integration\nexport {\n getFontLoaderCode,\n getThemeFontFamilies,\n ThemeFontProvider,\n useThemeFonts,\n FONT_IMPORT_MAP,\n} from './providers/font-loader.js'\n\n// Note: Do NOT re-export client components from the root entry to avoid\n// server-side evaluation during `payload generate:importmap`.\n// If needed, import directly from subpath: `@kilivi/payloadcms-theme-management/fields/ThemeLivePreview`\n\nexport const ThemeManagementPlugin = themeManagementPlugin\n/** @deprecated use {@link themeManagementPlugin} */\nexport const themeConfigurationPlugin = themeManagementPlugin\n\n// Export plugin translations for consumers to merge into their payload translations\nexport {\n translations,\n getTranslations,\n registerTranslations,\n availableLanguages,\n} from './translations.js'\n\nexport default themeManagementPlugin\n"],"names":["createThemeConfigurationField","allThemePresets","getTranslations","translations","THEME_FIELD_NAME","removeExistingThemeField","fields","filter","field","candidate","name","findTabsField","type","removeExistingThemeTab","tabs","knownLabels","Object","values","map","t","tabLabel","tab","labelValues","label","matchesKnown","some","v","includes","injectThemeTab","themeTab","enableLogging","sanitizedFields","tabsField","console","warn","newTabsField","sanitizedTabs","updatedTabs","log","updatedTabsField","ensureCollectionsArray","collections","Array","isArray","themeManagementPlugin","options","config","enabled","targetCollection","useStandaloneCollection","standaloneCollectionSlug","standaloneCollectionLabel","themePresets","defaultTheme","includeColorModeToggle","includeCustomCSS","includeBrandIdentity","enableAdvancedFeatures","tabFields","beforeChangeHook","args","data","themeConfiguration","theme","themeName","selectedPreset","find","p","lightMode","darkMode","isLightModeEmpty","background","isDarkModeEmpty","standaloneGlobal","slug","admin","group","access","accessObj","read","create","req","user","update","delete","hooks","beforeChange","globals","existingIndex","findIndex","g","collectionTouched","collection","existingFields","fetchThemeConfiguration","normalizedOptions","tenantSlug","collectionSlug","depth","locale","draft","queryParams","useGlobal","params","URLSearchParams","set","String","entries","forEach","key","value","undefined","queryString","toString","url","response","fetch","ok","Error","status","json","limit","doc","docs","error","getThemePreset","preset","getAvailableThemePresets","defaultThemePresets","extendedThemePresets","allExtendedThemePresets","extendedThemeToCSSVariables","applyExtendedTheme","generateExtendedThemeCSS","resetExtendedTheme","getExtendedThemeTokens","createUseExtendedTheme","getTailwindVarReferences","DEFAULT_THEME_CONFIGURATION","resolveThemeConfiguration","generateThemeColorsCss","hexToHsl","generateThemeCSS","getThemeStyles","getThemeHtmlAttributes","generatePresetCSS","generateAllThemesCSS","getThemeDynamicCSS","getAllDynamicThemesCSS","ThemeProvider","getFontLoaderCode","getThemeFontFamilies","ThemeFontProvider","useThemeFonts","FONT_IMPORT_MAP","ThemeManagementPlugin","themeConfigurationPlugin","registerTranslations","availableLanguages"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { CollectionConfig, Config, Field, GlobalConfig, Plugin, TabsField } from 'payload'\nimport { createThemeConfigurationField } from './fields/themeConfigurationField.js'\nimport type { ThemeTab } from './fields/themeConfigurationField.js'\nimport type { SiteThemeConfiguration } from './payload-types.js'\nimport type { ThemePreset } from './presets.js'\nimport { allThemePresets } from './presets.js'\nimport { getTranslations, translations } from './translations.js'\nimport type { FetchThemeConfigurationOptions, ThemeManagementPluginOptions } from './types.js'\n\nconst THEME_FIELD_NAME = 'themeConfiguration'\n\n/**\n * Removes existing theme configuration from a group field (legacy)\n */\nconst removeExistingThemeField = (fields: Field[] = []): Field[] =>\n fields.filter((field) => {\n const candidate = field as { name?: unknown }\n return candidate?.name !== THEME_FIELD_NAME\n })\n\n/**\n * Finds the tabs field in the collection fields array\n */\nconst findTabsField = (fields: Field[] = []): TabsField | null => {\n for (const field of fields) {\n if (field.type === 'tabs') {\n return field as TabsField\n }\n }\n return null\n}\n\n/**\n * Removes existing Appearance Settings tab if it exists\n */\nconst removeExistingThemeTab = (\n tabs: NonNullable<TabsField['tabs']>,\n): NonNullable<TabsField['tabs']> => {\n const knownLabels = Object.values(translations).map((t) => t.tabLabel)\n return tabs.filter((tab) => {\n // Normalize label to string (support localized object or plain string)\n let labelValues: string[] = []\n if (typeof tab.label === 'object' && tab.label !== null) {\n labelValues = Object.values(tab.label as Record<string, string>)\n } else if (typeof tab.label === 'string') {\n labelValues = [tab.label]\n }\n\n // If any of the label values match a known plugin tab label, remove it\n const matchesKnown = labelValues.some((v) => knownLabels.includes(v))\n\n return !matchesKnown\n })\n}\n\n/**\n * Injects theme tab into existing tabs field\n */\nconst injectThemeTab = (\n fields: Field[] | undefined,\n themeTab: ThemeTab,\n enableLogging: boolean,\n): Field[] => {\n // First, remove any legacy theme field (group-based)\n const sanitizedFields = removeExistingThemeField(fields ?? [])\n\n // Find the tabs field\n const tabsField = findTabsField(sanitizedFields)\n\n if (!tabsField || !tabsField.tabs) {\n if (enableLogging) {\n console.warn(\n '🎨 Theme Management Plugin: No tabs field found. Creating a new tabs structure.',\n )\n }\n\n // If no tabs exist, create a basic tabs structure with just the theme tab\n const newTabsField: TabsField = {\n type: 'tabs',\n tabs: [themeTab],\n }\n\n return [...sanitizedFields, newTabsField]\n }\n\n // Remove existing theme tab if present\n const sanitizedTabs = removeExistingThemeTab(tabsField.tabs)\n\n // Add theme tab to the end\n const updatedTabs = [...sanitizedTabs, themeTab]\n\n if (enableLogging) {\n console.log('🎨 Theme Management Plugin: injecting Appearance Settings tab')\n }\n\n // Update the tabs field\n const updatedTabsField: TabsField = {\n ...tabsField,\n tabs: updatedTabs,\n }\n\n // Replace the old tabs field with the updated one\n return sanitizedFields.map((field) => (field.type === 'tabs' ? updatedTabsField : field))\n}\n\nconst ensureCollectionsArray = (collections: Config['collections']): CollectionConfig[] =>\n Array.isArray(collections) ? collections : []\n\nexport const themeManagementPlugin = (options: ThemeManagementPluginOptions = {}): Plugin => {\n return (config: Config): Config => {\n const {\n enabled = true,\n targetCollection = 'site-settings',\n useStandaloneCollection = false,\n standaloneCollectionSlug = 'appearance-settings',\n // Allow either string or localized object; default comes from plugin translations\n standaloneCollectionLabel = getTranslations().standaloneCollectionLabel,\n themePresets = allThemePresets,\n defaultTheme = 'cool',\n includeColorModeToggle = true,\n includeCustomCSS = true,\n includeBrandIdentity = false,\n enableAdvancedFeatures = true,\n enableLogging = false,\n } = options\n\n if (!enabled) {\n if (enableLogging) {\n console.log('🎨 Theme Management Plugin: disabled via options, skipping.')\n }\n return config\n }\n\n const themeTab = createThemeConfigurationField({\n themePresets,\n defaultTheme,\n includeColorModeToggle,\n includeCustomCSS,\n includeBrandIdentity: includeBrandIdentity ?? false,\n enableAdvancedFeatures,\n })\n\n // If using standalone collection, create a new global\n if (useStandaloneCollection) {\n if (enableLogging) {\n console.log(\n `🎨 Theme Management Plugin: creating standalone global \"${standaloneCollectionSlug}\"`,\n )\n }\n\n // Extract the fields from the tab directly (no wrapping in group)\n const tabFields = (themeTab.fields ?? []) as Field[]\n\n /**\n * Hook to auto-populate lightMode and darkMode colors when theme changes\n */\n type BeforeChangeArgs = {\n data?: {\n themeConfiguration?: {\n theme?: string\n lightMode?: Record<string, unknown>\n darkMode?: Record<string, unknown>\n }\n }\n }\n\n const beforeChangeHook = async (args: BeforeChangeArgs) => {\n const { data } = args\n\n // If theme is being changed, auto-populate color modes\n if (data?.themeConfiguration?.theme) {\n const themeName = data.themeConfiguration.theme\n const selectedPreset = themePresets.find((p) => p.name === themeName)\n\n if (selectedPreset) {\n // Initialize light and dark mode colors from preset if not already set\n if (!data.themeConfiguration.lightMode) {\n data.themeConfiguration.lightMode = {}\n }\n if (!data.themeConfiguration.darkMode) {\n data.themeConfiguration.darkMode = {}\n }\n\n // Only populate if not manually edited (first time or empty)\n const isLightModeEmpty = !data.themeConfiguration.lightMode?.background\n const isDarkModeEmpty = !data.themeConfiguration.darkMode?.background\n\n if (isLightModeEmpty && selectedPreset.lightMode) {\n data.themeConfiguration.lightMode = { ...selectedPreset.lightMode }\n }\n\n if (isDarkModeEmpty && selectedPreset.darkMode) {\n data.themeConfiguration.darkMode = { ...selectedPreset.darkMode }\n }\n }\n }\n\n return data\n }\n\n const standaloneGlobal: GlobalConfig = {\n slug: standaloneCollectionSlug,\n label: standaloneCollectionLabel,\n admin: {\n group: 'Settings',\n },\n // Provide a broad access object but cast to GlobalConfig['access'] to satisfy types\n access: ((): GlobalConfig['access'] => {\n const accessObj = {\n read: () => true,\n create: ({ req }: { req?: { user?: unknown } }) => !!req?.user,\n update: ({ req }: { req?: { user?: unknown } }) => !!req?.user,\n delete: ({ req }: { req?: { user?: unknown } }) => !!req?.user,\n } as unknown as GlobalConfig['access']\n return accessObj\n })(),\n fields: tabFields,\n hooks: {\n beforeChange: [beforeChangeHook],\n afterChange: [\n async ({ doc, req }) => {\n // Invalidate cache after appearance settings change\n if (!req?.context?.disableRevalidate) {\n try {\n const cacheTag = `global_${standaloneCollectionSlug}`\n // Import revalidateTag dynamically to avoid issues in non-Next.js environments\n const { revalidateTag } = await import('next/cache')\n revalidateTag(cacheTag)\n if (enableLogging) {\n req.payload.logger.info(\n `🎨 Theme Management Plugin: cache invalidated for tag \"${cacheTag}\"`,\n )\n }\n } catch (error) {\n if (enableLogging) {\n req.payload.logger.error(\n '🎨 Theme Management Plugin: cache invalidation failed:',\n error,\n )\n }\n }\n }\n return doc\n },\n ],\n },\n }\n\n const globals = Array.isArray(config.globals) ? config.globals : []\n\n // Check if global already exists\n const existingIndex = globals.findIndex((g) => g.slug === standaloneCollectionSlug)\n\n if (existingIndex !== -1) {\n if (enableLogging) {\n console.warn(\n `🎨 Theme Management Plugin: global \"${standaloneCollectionSlug}\" already exists, skipping creation.`,\n )\n }\n return config\n }\n\n return {\n ...config,\n globals: [...globals, standaloneGlobal],\n }\n }\n\n // Otherwise, add as tab to existing collection\n let collectionTouched = false\n\n const collections = ensureCollectionsArray(config.collections).map((collection) => {\n if (collection.slug !== targetCollection) {\n return collection\n }\n\n collectionTouched = true\n\n if (enableLogging) {\n console.log(`🎨 Theme Management Plugin: enhancing collection \"${collection.slug}\"`)\n }\n\n const existingFields = Array.isArray(collection.fields) ? collection.fields : []\n\n const fields = injectThemeTab(existingFields, themeTab, enableLogging)\n\n return {\n ...collection,\n fields,\n }\n })\n\n if (!collectionTouched) {\n if (enableLogging) {\n console.warn(\n `🎨 Theme Management Plugin: collection \"${targetCollection}\" was not found, leaving config untouched.`,\n )\n }\n return config\n }\n\n return {\n ...config,\n collections,\n }\n }\n}\n\ntype FetchOptionsInput = string | FetchThemeConfigurationOptions\n\nexport const fetchThemeConfiguration = async (\n options?: FetchOptionsInput,\n): Promise<SiteThemeConfiguration | null> => {\n const normalizedOptions = typeof options === 'string' ? { tenantSlug: options } : (options ?? {})\n\n const {\n tenantSlug,\n collectionSlug = 'site-settings', // Can be overridden to 'appearance-settings' if using standalone\n depth,\n locale,\n draft,\n queryParams = {},\n useGlobal = false, // Set to true if using standalone global instead of collection\n } = normalizedOptions\n\n try {\n if (useGlobal) {\n // Fetch from global endpoint\n const params = new URLSearchParams()\n\n if (typeof depth === 'number') params.set('depth', String(depth))\n // For globals, keep tenant param for backwards compatibility, and also add where[...] filter\n if (tenantSlug) {\n params.set('tenant', tenantSlug)\n params.set('where[tenant][equals]', String(tenantSlug))\n }\n\n Object.entries(queryParams).forEach(([key, value]) => {\n if (value === undefined) return\n params.set(key, String(value))\n })\n\n const queryString = params.toString()\n const url = `/api/globals/${collectionSlug}${queryString ? `?${queryString}` : ''}`\n\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error(`Failed to fetch theme global (status ${response.status})`)\n }\n\n const data = (await response.json()) as {\n themeConfiguration?: SiteThemeConfiguration\n }\n\n return data?.themeConfiguration ?? null\n } else {\n // Fetch from collection endpoint (legacy/default behavior)\n const params = new URLSearchParams({ limit: '1' })\n\n if (typeof depth === 'number') params.set('depth', String(depth))\n // For collections, use a where[tenant][equals] filter so Payload's find endpoint applies tenant scoping\n if (tenantSlug) params.set('where[tenant][equals]', String(tenantSlug))\n if (locale) params.set('locale', locale)\n if (draft) params.set('draft', 'true')\n\n Object.entries(queryParams).forEach(([key, value]) => {\n if (value === undefined) return\n params.set(key, String(value))\n })\n\n const url = `/api/${collectionSlug}?${params.toString()}`\n\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error(`Failed to fetch theme configuration (status ${response.status})`)\n }\n\n const data = (await response.json()) as {\n docs?: Array<{ themeConfiguration?: SiteThemeConfiguration }>\n }\n\n const doc = data.docs?.[0]\n\n return doc?.themeConfiguration ?? null\n }\n } catch (error) {\n console.warn('Failed to fetch theme configuration:', error)\n return null\n }\n}\n\nexport const getThemePreset = (themeName: string): ThemePreset | null => {\n return allThemePresets.find((preset) => preset.name === themeName) || null\n}\n\nexport const getAvailableThemePresets = (): ThemePreset[] => {\n return allThemePresets\n}\n\nexport { defaultThemePresets, allThemePresets } from './presets.js'\nexport type { ThemePreset, ThemeTypographyPreset } from './presets.js'\nexport type { ThemeTab } from './fields/themeConfigurationField.js'\nexport {\n extendedThemePresets,\n allExtendedThemePresets,\n extendedThemeToCSSVariables,\n} from './extended-presets.js'\nexport type { ExtendedThemePreset, ShadcnColorTokens } from './extended-presets.js'\nexport {\n applyExtendedTheme,\n generateExtendedThemeCSS,\n resetExtendedTheme,\n getExtendedThemeTokens,\n createUseExtendedTheme,\n getTailwindVarReferences,\n} from './utils/extendedThemeHelpers.js'\nexport type { ThemeManagementPluginOptions, FetchThemeConfigurationOptions } from './types.js'\nexport {\n DEFAULT_THEME_CONFIGURATION,\n resolveThemeConfiguration,\n} from './utils/resolveThemeConfiguration.js'\nexport type { ResolvedThemeConfiguration } from './utils/resolveThemeConfiguration.js'\nexport { generateThemeColorsCss, hexToHsl } from './utils/themeColors.js'\nexport { generateThemeCSS, getThemeStyles } from './utils/themeUtils.js'\nexport { getThemeHtmlAttributes } from './utils/themeHtmlAttributes.js'\n\n// Dynamic theme CSS generation\nexport {\n generateThemeCSS as generatePresetCSS,\n generateAllThemesCSS,\n} from './utils/generateThemeCSS.js'\nexport { getThemeDynamicCSS, getAllDynamicThemesCSS } from './utils/themeCSS.js'\n\nexport type { Mode, ThemeDefaults } from './providers/Theme/types.js'\nexport { ThemeProvider } from './providers/Theme/index.js'\n\n// Font loading utilities for Next.js integration\nexport {\n getFontLoaderCode,\n getThemeFontFamilies,\n ThemeFontProvider,\n useThemeFonts,\n FONT_IMPORT_MAP,\n} from './providers/font-loader.js'\n\n// Note: Do NOT re-export client components from the root entry to avoid\n// server-side evaluation during `payload generate:importmap`.\n// If needed, import directly from subpath: `@kilivi/payloadcms-theme-management/fields/ThemeLivePreview`\n\nexport const ThemeManagementPlugin = themeManagementPlugin\n/** @deprecated use {@link themeManagementPlugin} */\nexport const themeConfigurationPlugin = themeManagementPlugin\n\n// Export plugin translations for consumers to merge into their payload translations\nexport {\n translations,\n getTranslations,\n registerTranslations,\n availableLanguages,\n} from './translations.js'\n\nexport default themeManagementPlugin\n"],"names":["createThemeConfigurationField","allThemePresets","getTranslations","translations","THEME_FIELD_NAME","removeExistingThemeField","fields","filter","field","candidate","name","findTabsField","type","removeExistingThemeTab","tabs","knownLabels","Object","values","map","t","tabLabel","tab","labelValues","label","matchesKnown","some","v","includes","injectThemeTab","themeTab","enableLogging","sanitizedFields","tabsField","console","warn","newTabsField","sanitizedTabs","updatedTabs","log","updatedTabsField","ensureCollectionsArray","collections","Array","isArray","themeManagementPlugin","options","config","enabled","targetCollection","useStandaloneCollection","standaloneCollectionSlug","standaloneCollectionLabel","themePresets","defaultTheme","includeColorModeToggle","includeCustomCSS","includeBrandIdentity","enableAdvancedFeatures","tabFields","beforeChangeHook","args","data","themeConfiguration","theme","themeName","selectedPreset","find","p","lightMode","darkMode","isLightModeEmpty","background","isDarkModeEmpty","standaloneGlobal","slug","admin","group","access","accessObj","read","create","req","user","update","delete","hooks","beforeChange","afterChange","doc","context","disableRevalidate","cacheTag","revalidateTag","payload","logger","info","error","globals","existingIndex","findIndex","g","collectionTouched","collection","existingFields","fetchThemeConfiguration","normalizedOptions","tenantSlug","collectionSlug","depth","locale","draft","queryParams","useGlobal","params","URLSearchParams","set","String","entries","forEach","key","value","undefined","queryString","toString","url","response","fetch","ok","Error","status","json","limit","docs","getThemePreset","preset","getAvailableThemePresets","defaultThemePresets","extendedThemePresets","allExtendedThemePresets","extendedThemeToCSSVariables","applyExtendedTheme","generateExtendedThemeCSS","resetExtendedTheme","getExtendedThemeTokens","createUseExtendedTheme","getTailwindVarReferences","DEFAULT_THEME_CONFIGURATION","resolveThemeConfiguration","generateThemeColorsCss","hexToHsl","generateThemeCSS","getThemeStyles","getThemeHtmlAttributes","generatePresetCSS","generateAllThemesCSS","getThemeDynamicCSS","getAllDynamicThemesCSS","ThemeProvider","getFontLoaderCode","getThemeFontFamilies","ThemeFontProvider","useThemeFonts","FONT_IMPORT_MAP","ThemeManagementPlugin","themeConfigurationPlugin","registerTranslations","availableLanguages"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kilivi/payloadcms-theme-management",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Theme configuration plugin for Payload CMS v3 with 60+ professional themes and standalone collection support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -112,6 +112,7 @@
|
|
|
112
112
|
"access": "public"
|
|
113
113
|
},
|
|
114
114
|
"dependencies": {
|
|
115
|
+
"color": "^5.0.3",
|
|
115
116
|
"react-colorful": "^5.6.1",
|
|
116
117
|
"server-only": "^0.0.1"
|
|
117
118
|
},
|