@kilivi/payloadcms-theme-management 0.2.2 → 0.2.3
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 +26 -4
- package/dist/extended-presets.d.ts +16 -1
- package/dist/extended-presets.d.ts.map +1 -1
- package/dist/extended-presets.js +8 -1
- package/dist/extended-presets.js.map +1 -1
- package/dist/fields/ThemeColorPickerField.css +350 -65
- package/dist/fields/ThemeColorPickerField.d.ts.map +1 -1
- package/dist/fields/ThemeColorPickerField.js +131 -13
- package/dist/fields/ThemeColorPickerField.js.map +1 -1
- package/dist/fields/ThemeLivePreview.css +190 -0
- package/dist/fields/ThemeLivePreview.d.ts +4 -0
- package/dist/fields/ThemeLivePreview.d.ts.map +1 -0
- package/dist/fields/ThemeLivePreview.js +198 -0
- package/dist/fields/ThemeLivePreview.js.map +1 -0
- package/dist/fields/extendedThemeFields.d.ts +121 -9
- package/dist/fields/extendedThemeFields.d.ts.map +1 -1
- package/dist/fields/extendedThemeFields.js +298 -103
- package/dist/fields/extendedThemeFields.js.map +1 -1
- package/dist/fields/themeConfigurationField.d.ts.map +1 -1
- package/dist/fields/themeConfigurationField.js +8 -6
- package/dist/fields/themeConfigurationField.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -2
- package/dist/index.js.map +1 -1
- package/dist/tweakcn-presets.d.ts +10 -0
- package/dist/tweakcn-presets.d.ts.map +1 -0
- package/dist/tweakcn-presets.js +714 -0
- package/dist/tweakcn-presets.js.map +1 -0
- package/dist/tweakcn-special-themes.d.ts +8 -0
- package/dist/tweakcn-special-themes.d.ts.map +1 -0
- package/dist/tweakcn-special-themes.js +614 -0
- package/dist/tweakcn-special-themes.js.map +1 -0
- package/dist/views/ThemePreviewView.d.ts +8 -0
- package/dist/views/ThemePreviewView.d.ts.map +1 -0
- package/dist/views/ThemePreviewView.js +44 -0
- package/dist/views/ThemePreviewView.js.map +1 -0
- package/dist/views/ThemePreviewViewClient.d.ts +7 -0
- package/dist/views/ThemePreviewViewClient.d.ts.map +1 -0
- package/dist/views/ThemePreviewViewClient.js +12 -0
- package/dist/views/ThemePreviewViewClient.js.map +1 -0
- package/package.json +6 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemeColorPickerField.d.ts","sourceRoot":"","sources":["../../src/fields/ThemeColorPickerField.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"ThemeColorPickerField.d.ts","sourceRoot":"","sources":["../../src/fields/ThemeColorPickerField.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAGvD,OAAO,6BAA6B,CAAA;AAyFpC,QAAA,MAAM,qBAAqB,EAAE,wBA8K5B,CAAA;AAED,eAAe,qBAAqB,CAAA"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useField, useFormFields } from '@payloadcms/ui';
|
|
4
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { useEffect, useMemo, useState, useCallback, useRef } from 'react';
|
|
5
|
+
import { HexColorPicker, HexColorInput } from 'react-colorful';
|
|
5
6
|
import './ThemeColorPickerField.css';
|
|
6
7
|
function resolveLocalizedValue(value, fallback) {
|
|
7
8
|
if (typeof value === 'string') {
|
|
@@ -16,19 +17,96 @@ function resolveLocalizedValue(value, fallback) {
|
|
|
16
17
|
}
|
|
17
18
|
return fallback;
|
|
18
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Convert various color formats to HEX for the color picker
|
|
22
|
+
*/ function toHex(color) {
|
|
23
|
+
if (!color) return '#000000';
|
|
24
|
+
// Already hex
|
|
25
|
+
if (color.startsWith('#')) return color;
|
|
26
|
+
// OKLCH format - extract hue and convert to approximate hex
|
|
27
|
+
if (color.startsWith('oklch')) {
|
|
28
|
+
// For now, return a default - proper conversion requires color-conversion library
|
|
29
|
+
return '#3b82f6' // blue-500 as default
|
|
30
|
+
;
|
|
31
|
+
}
|
|
32
|
+
// HSL format
|
|
33
|
+
if (color.startsWith('hsl')) {
|
|
34
|
+
const regex = /hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/;
|
|
35
|
+
const match = regex.exec(color);
|
|
36
|
+
if (match) {
|
|
37
|
+
const [, h, s, l] = match.map(Number);
|
|
38
|
+
return hslToHex(h, s, l);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return '#000000';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Convert HSL to HEX
|
|
45
|
+
*/ function hslToHex(h, s, l) {
|
|
46
|
+
s /= 100;
|
|
47
|
+
l /= 100;
|
|
48
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
49
|
+
const x = c * (1 - Math.abs(h / 60 % 2 - 1));
|
|
50
|
+
const m = l - c / 2;
|
|
51
|
+
let r = 0;
|
|
52
|
+
let g = 0;
|
|
53
|
+
let b = 0;
|
|
54
|
+
if (h >= 0 && h < 60) {
|
|
55
|
+
r = c;
|
|
56
|
+
g = x;
|
|
57
|
+
} else if (h >= 60 && h < 120) {
|
|
58
|
+
r = x;
|
|
59
|
+
g = c;
|
|
60
|
+
} else if (h >= 120 && h < 180) {
|
|
61
|
+
g = c;
|
|
62
|
+
b = x;
|
|
63
|
+
} else if (h >= 180 && h < 240) {
|
|
64
|
+
g = x;
|
|
65
|
+
b = c;
|
|
66
|
+
} else if (h >= 240 && h < 300) {
|
|
67
|
+
r = x;
|
|
68
|
+
b = c;
|
|
69
|
+
} else if (h >= 300 && h < 360) {
|
|
70
|
+
r = c;
|
|
71
|
+
b = x;
|
|
72
|
+
}
|
|
73
|
+
const toHex = (n)=>{
|
|
74
|
+
const hex = Math.round((n + m) * 255).toString(16);
|
|
75
|
+
return hex.length === 1 ? '0' + hex : hex;
|
|
76
|
+
};
|
|
77
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
78
|
+
}
|
|
19
79
|
const ThemeColorPickerField = ({ field, path })=>{
|
|
20
80
|
const { value, setValue } = useField({
|
|
21
81
|
path
|
|
22
82
|
});
|
|
23
83
|
const [localValue, setLocalValue] = useState(value || '');
|
|
84
|
+
const [showPicker, setShowPicker] = useState(false);
|
|
85
|
+
const [hexValue, setHexValue] = useState(toHex(value || ''));
|
|
86
|
+
const pickerRef = useRef(null);
|
|
24
87
|
const allFields = useFormFields(([fields])=>fields);
|
|
25
88
|
const mode = path.includes('lightMode') ? 'lightMode' : 'darkMode';
|
|
26
89
|
const modePrefix = `themeConfiguration.${mode}`;
|
|
27
90
|
useEffect(()=>{
|
|
28
91
|
setLocalValue(value || '');
|
|
92
|
+
setHexValue(toHex(value || ''));
|
|
29
93
|
}, [
|
|
30
94
|
value
|
|
31
95
|
]);
|
|
96
|
+
// Close picker when clicking outside
|
|
97
|
+
useEffect(()=>{
|
|
98
|
+
function handleClickOutside(event) {
|
|
99
|
+
if (pickerRef.current && !pickerRef.current.contains(event.target)) {
|
|
100
|
+
setShowPicker(false);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (showPicker) {
|
|
104
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
105
|
+
return ()=>document.removeEventListener('mousedown', handleClickOutside);
|
|
106
|
+
}
|
|
107
|
+
}, [
|
|
108
|
+
showPicker
|
|
109
|
+
]);
|
|
32
110
|
const colorValues = useMemo(()=>{
|
|
33
111
|
if (!allFields) {
|
|
34
112
|
return {};
|
|
@@ -64,10 +142,20 @@ const ThemeColorPickerField = ({ field, path })=>{
|
|
|
64
142
|
allFields,
|
|
65
143
|
modePrefix
|
|
66
144
|
]);
|
|
67
|
-
|
|
145
|
+
const handleHexChange = useCallback((hex)=>{
|
|
146
|
+
setHexValue(hex);
|
|
147
|
+
setLocalValue(hex);
|
|
148
|
+
setValue(hex);
|
|
149
|
+
}, [
|
|
150
|
+
setValue
|
|
151
|
+
]);
|
|
152
|
+
const handleTextChange = useCallback((nextValue)=>{
|
|
68
153
|
setLocalValue(nextValue);
|
|
69
154
|
setValue(nextValue);
|
|
70
|
-
|
|
155
|
+
setHexValue(toHex(nextValue));
|
|
156
|
+
}, [
|
|
157
|
+
setValue
|
|
158
|
+
]);
|
|
71
159
|
const fieldName = path.split('.').pop() || '';
|
|
72
160
|
const label = resolveLocalizedValue(field.label, fieldName);
|
|
73
161
|
const description = resolveLocalizedValue(field.admin?.description, '');
|
|
@@ -93,26 +181,56 @@ const ThemeColorPickerField = ({ field, path })=>{
|
|
|
93
181
|
/*#__PURE__*/ _jsxs("div", {
|
|
94
182
|
className: "color-input-wrapper",
|
|
95
183
|
children: [
|
|
96
|
-
/*#__PURE__*/ _jsx("
|
|
97
|
-
type: "
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
184
|
+
/*#__PURE__*/ _jsx("button", {
|
|
185
|
+
type: "button",
|
|
186
|
+
className: "color-swatch",
|
|
187
|
+
style: {
|
|
188
|
+
backgroundColor: hexValue
|
|
189
|
+
},
|
|
190
|
+
onClick: ()=>setShowPicker(!showPicker),
|
|
191
|
+
"aria-label": "Open color picker"
|
|
101
192
|
}),
|
|
102
193
|
/*#__PURE__*/ _jsx("input", {
|
|
103
194
|
type: "text",
|
|
104
195
|
value: localValue || '',
|
|
105
|
-
onChange: (event)=>
|
|
106
|
-
placeholder: "#
|
|
107
|
-
className: "text-input"
|
|
108
|
-
|
|
196
|
+
onChange: (event)=>handleTextChange(event.target.value),
|
|
197
|
+
placeholder: "oklch(...) or #hex",
|
|
198
|
+
className: "text-input"
|
|
199
|
+
})
|
|
200
|
+
]
|
|
201
|
+
}),
|
|
202
|
+
showPicker && /*#__PURE__*/ _jsxs("div", {
|
|
203
|
+
className: "color-picker-popover",
|
|
204
|
+
ref: pickerRef,
|
|
205
|
+
children: [
|
|
206
|
+
/*#__PURE__*/ _jsx(HexColorPicker, {
|
|
207
|
+
color: hexValue,
|
|
208
|
+
onChange: handleHexChange
|
|
209
|
+
}),
|
|
210
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
211
|
+
className: "picker-footer",
|
|
212
|
+
children: [
|
|
213
|
+
/*#__PURE__*/ _jsx(HexColorInput, {
|
|
214
|
+
color: hexValue,
|
|
215
|
+
onChange: handleHexChange,
|
|
216
|
+
prefixed: true,
|
|
217
|
+
placeholder: "#000000",
|
|
218
|
+
className: "hex-input"
|
|
219
|
+
}),
|
|
220
|
+
/*#__PURE__*/ _jsx("button", {
|
|
221
|
+
type: "button",
|
|
222
|
+
onClick: ()=>setShowPicker(false),
|
|
223
|
+
className: "close-button",
|
|
224
|
+
children: "Done"
|
|
225
|
+
})
|
|
226
|
+
]
|
|
109
227
|
})
|
|
110
228
|
]
|
|
111
229
|
}),
|
|
112
230
|
/*#__PURE__*/ _jsx("div", {
|
|
113
231
|
className: "color-preview",
|
|
114
232
|
style: {
|
|
115
|
-
backgroundColor:
|
|
233
|
+
backgroundColor: hexValue
|
|
116
234
|
},
|
|
117
235
|
children: /*#__PURE__*/ _jsx("span", {
|
|
118
236
|
className: "preview-label",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/fields/ThemeColorPickerField.tsx"],"sourcesContent":["'use client'\n\nimport { useField, useFormFields } from '@payloadcms/ui'\nimport type { TextFieldClientComponent } from 'payload'\nimport { useEffect, useMemo, useState } from 'react'\nimport './ThemeColorPickerField.css'\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\nconst ThemeColorPickerField: TextFieldClientComponent = ({ field, path }) => {\n const { value, setValue } = useField<string>({ path })\n const [localValue, setLocalValue] = useState(value || '')\n const allFields = useFormFields(([fields]) => fields)\n\n const mode = path.includes('lightMode') ? 'lightMode' : 'darkMode'\n const modePrefix = `themeConfiguration.${mode}`\n\n useEffect(() => {\n setLocalValue(value || '')\n }, [value])\n\n const colorValues = useMemo(() => {\n if (!allFields) {\n return {}\n }\n\n const names = [\n 'background',\n 'foreground',\n 'card',\n 'cardForeground',\n 'popover',\n 'popoverForeground',\n 'primary',\n 'primaryForeground',\n 'secondary',\n 'secondaryForeground',\n 'muted',\n 'mutedForeground',\n 'accent',\n 'accentForeground',\n 'destructive',\n 'destructiveForeground',\n 'border',\n 'input',\n 'ring',\n ]\n\n return names.reduce<Record<string, string>>((accumulator, name) => {\n const fieldState = allFields[`${modePrefix}.${name}`]\n const parsedValue = typeof fieldState?.value === 'string' ? fieldState.value : undefined\n accumulator[name] = parsedValue ?? ''\n return accumulator\n }, {})\n }, [allFields, modePrefix])\n\n function handleChange(nextValue: string) {\n setLocalValue(nextValue)\n setValue(nextValue)\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-wrapper\">\n <input\n type=\"color\"\n value={localValue || '#000000'}\n onChange={(event) => handleChange(event.target.value)}\n className=\"color-input\"\n />\n <input\n type=\"text\"\n value={localValue || ''}\n onChange={(event) => handleChange(event.target.value)}\n placeholder=\"#000000\"\n className=\"text-input\"\n pattern=\"^#[0-9A-Fa-f]{6}$\"\n />\n </div>\n\n <div className=\"color-preview\" style={{ backgroundColor: localValue || '#000000' }}>\n <span className=\"preview-label\">{localValue || 'Not set'}</span>\n </div>\n </div>\n\n {(fieldName === 'primary' || fieldName === 'background') && (\n <div\n className=\"mini-preview\"\n style={{\n backgroundColor: colorValues.background || '#ffffff',\n color: colorValues.foreground || '#000000',\n borderColor: colorValues.border || '#e2e8f0',\n }}\n >\n <div\n className=\"preview-card\"\n style={{\n backgroundColor: colorValues.card || '#ffffff',\n color: colorValues.cardForeground || '#000000',\n borderColor: colorValues.border || '#e2e8f0',\n }}\n >\n <button\n className=\"preview-button\"\n style={{\n backgroundColor: colorValues.primary || '#0070f3',\n color: colorValues.primaryForeground || '#ffffff',\n }}\n >\n Primary Button\n </button>\n <button\n className=\"preview-button secondary\"\n style={{\n backgroundColor: colorValues.secondary || '#f1f5f9',\n color: colorValues.secondaryForeground || '#0f172a',\n }}\n >\n Secondary\n </button>\n </div>\n </div>\n )}\n </div>\n )\n}\n\nexport default ThemeColorPickerField\n"],"names":["useField","useFormFields","useEffect","useMemo","useState","resolveLocalizedValue","value","fallback","localized","resolved","en","cs","ThemeColorPickerField","field","path","setValue","localValue","setLocalValue","allFields","fields","mode","includes","modePrefix","colorValues","names","reduce","accumulator","name","fieldState","parsedValue","undefined","handleChange","nextValue","fieldName","split","pop","label","description","admin","div","className","input","type","onChange","event","target","placeholder","pattern","style","backgroundColor","span","background","color","foreground","borderColor","border","card","cardForeground","button","primary","primaryForeground","secondary","secondaryForeground"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/fields/ThemeColorPickerField.tsx"],"sourcesContent":["'use client'\n\nimport { useField, useFormFields } from '@payloadcms/ui'\nimport type { TextFieldClientComponent } from 'payload'\nimport { useEffect, useMemo, useState, useCallback, useRef } from 'react'\nimport { HexColorPicker, HexColorInput } from 'react-colorful'\nimport './ThemeColorPickerField.css'\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 const allFields = useFormFields(([fields]) => fields)\n\n const mode = path.includes('lightMode') ? 'lightMode' : 'darkMode'\n const modePrefix = `themeConfiguration.${mode}`\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 colorValues = useMemo(() => {\n if (!allFields) {\n return {}\n }\n\n const names = [\n 'background',\n 'foreground',\n 'card',\n 'cardForeground',\n 'popover',\n 'popoverForeground',\n 'primary',\n 'primaryForeground',\n 'secondary',\n 'secondaryForeground',\n 'muted',\n 'mutedForeground',\n 'accent',\n 'accentForeground',\n 'destructive',\n 'destructiveForeground',\n 'border',\n 'input',\n 'ring',\n ]\n\n return names.reduce<Record<string, string>>((accumulator, name) => {\n const fieldState = allFields[`${modePrefix}.${name}`]\n const parsedValue = typeof fieldState?.value === 'string' ? fieldState.value : undefined\n accumulator[name] = parsedValue ?? ''\n return accumulator\n }, {})\n }, [allFields, modePrefix])\n\n const handleHexChange = useCallback((hex: string) => {\n setHexValue(hex)\n setLocalValue(hex)\n setValue(hex)\n }, [setValue])\n\n const handleTextChange = useCallback((nextValue: string) => {\n setLocalValue(nextValue)\n setValue(nextValue)\n setHexValue(toHex(nextValue))\n }, [setValue])\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-wrapper\">\n <button\n type=\"button\"\n className=\"color-swatch\"\n style={{ backgroundColor: hexValue }}\n onClick={() => setShowPicker(!showPicker)}\n aria-label=\"Open color picker\"\n />\n <input\n type=\"text\"\n value={localValue || ''}\n onChange={(event) => handleTextChange(event.target.value)}\n placeholder=\"oklch(...) or #hex\"\n className=\"text-input\"\n />\n </div>\n\n {/* Professional Color Picker Popover */}\n {showPicker && (\n <div className=\"color-picker-popover\" ref={pickerRef}>\n <HexColorPicker color={hexValue} onChange={handleHexChange} />\n <div className=\"picker-footer\">\n <HexColorInput\n color={hexValue}\n onChange={handleHexChange}\n prefixed\n placeholder=\"#000000\"\n className=\"hex-input\"\n />\n <button\n type=\"button\"\n onClick={() => setShowPicker(false)}\n className=\"close-button\"\n >\n Done\n </button>\n </div>\n </div>\n )}\n\n <div className=\"color-preview\" style={{ backgroundColor: hexValue }}>\n <span className=\"preview-label\">{localValue || 'Not set'}</span>\n </div>\n </div>\n\n {(fieldName === 'primary' || fieldName === 'background') && (\n <div\n className=\"mini-preview\"\n style={{\n backgroundColor: colorValues.background || '#ffffff',\n color: colorValues.foreground || '#000000',\n borderColor: colorValues.border || '#e2e8f0',\n }}\n >\n <div\n className=\"preview-card\"\n style={{\n backgroundColor: colorValues.card || '#ffffff',\n color: colorValues.cardForeground || '#000000',\n borderColor: colorValues.border || '#e2e8f0',\n }}\n >\n <button\n className=\"preview-button\"\n style={{\n backgroundColor: colorValues.primary || '#0070f3',\n color: colorValues.primaryForeground || '#ffffff',\n }}\n >\n Primary Button\n </button>\n <button\n className=\"preview-button secondary\"\n style={{\n backgroundColor: colorValues.secondary || '#f1f5f9',\n color: colorValues.secondaryForeground || '#0f172a',\n }}\n >\n Secondary\n </button>\n </div>\n </div>\n )}\n </div>\n )\n}\n\nexport default ThemeColorPickerField\n"],"names":["useField","useFormFields","useEffect","useMemo","useState","useCallback","useRef","HexColorPicker","HexColorInput","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","allFields","fields","mode","includes","modePrefix","handleClickOutside","event","current","contains","target","document","addEventListener","removeEventListener","colorValues","names","reduce","accumulator","name","fieldState","parsedValue","undefined","handleHexChange","handleTextChange","nextValue","fieldName","split","pop","label","description","admin","div","className","button","type","style","backgroundColor","onClick","aria-label","input","onChange","placeholder","ref","prefixed","span","background","foreground","borderColor","border","card","cardForeground","primary","primaryForeground","secondary","secondaryForeground"],"mappings":"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"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/* Theme Live Preview Styles */
|
|
2
|
+
.theme-live-preview-container {
|
|
3
|
+
position: sticky;
|
|
4
|
+
top: 0;
|
|
5
|
+
height: 100vh;
|
|
6
|
+
overflow-y: auto;
|
|
7
|
+
background: var(--theme-elevation-50);
|
|
8
|
+
border-left: 1px solid var(--theme-elevation-200);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.theme-live-preview-empty {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
height: 100%;
|
|
16
|
+
padding: 2rem;
|
|
17
|
+
color: var(--theme-elevation-400);
|
|
18
|
+
font-size: 0.875rem;
|
|
19
|
+
text-align: center;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.preview-header {
|
|
23
|
+
position: sticky;
|
|
24
|
+
top: 0;
|
|
25
|
+
z-index: 10;
|
|
26
|
+
background: var(--theme-elevation-0);
|
|
27
|
+
border-bottom: 1px solid var(--theme-elevation-200);
|
|
28
|
+
padding: 1rem;
|
|
29
|
+
display: flex;
|
|
30
|
+
justify-content: space-between;
|
|
31
|
+
align-items: center;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.preview-header h3 {
|
|
35
|
+
margin: 0;
|
|
36
|
+
font-size: 1rem;
|
|
37
|
+
font-weight: 600;
|
|
38
|
+
color: var(--theme-text);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.mode-toggle {
|
|
42
|
+
display: flex;
|
|
43
|
+
gap: 0.5rem;
|
|
44
|
+
background: var(--theme-elevation-100);
|
|
45
|
+
padding: 0.25rem;
|
|
46
|
+
border-radius: 8px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.mode-toggle button {
|
|
50
|
+
padding: 0.5rem 1rem;
|
|
51
|
+
border: none;
|
|
52
|
+
background: transparent;
|
|
53
|
+
color: var(--theme-text);
|
|
54
|
+
font-size: 0.875rem;
|
|
55
|
+
font-weight: 500;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
border-radius: 6px;
|
|
58
|
+
transition: all 0.2s;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.mode-toggle button.active {
|
|
62
|
+
background: white;
|
|
63
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.preview-content {
|
|
67
|
+
padding: 2rem;
|
|
68
|
+
min-height: calc(100vh - 80px);
|
|
69
|
+
transition: all 0.3s;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.preview-section {
|
|
73
|
+
margin-bottom: 2rem;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.preview-section h1 {
|
|
77
|
+
font-size: 2rem;
|
|
78
|
+
font-weight: 700;
|
|
79
|
+
margin: 0 0 0.5rem 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.preview-section p {
|
|
83
|
+
font-size: 1rem;
|
|
84
|
+
margin: 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.preview-cards {
|
|
88
|
+
display: grid;
|
|
89
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
90
|
+
gap: 1.5rem;
|
|
91
|
+
margin-bottom: 2rem;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.preview-card {
|
|
95
|
+
padding: 1.5rem;
|
|
96
|
+
border-radius: 12px;
|
|
97
|
+
border: 2px solid;
|
|
98
|
+
transition: all 0.2s;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.preview-card:hover {
|
|
102
|
+
transform: translateY(-2px);
|
|
103
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.preview-card h3 {
|
|
107
|
+
margin: 0 0 0.5rem 0;
|
|
108
|
+
font-size: 1.25rem;
|
|
109
|
+
font-weight: 600;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.preview-card p {
|
|
113
|
+
margin: 0 0 1rem 0;
|
|
114
|
+
font-size: 0.875rem;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.preview-btn-primary,
|
|
118
|
+
.preview-btn-secondary,
|
|
119
|
+
.preview-btn-destructive {
|
|
120
|
+
padding: 0.625rem 1.25rem;
|
|
121
|
+
border: none;
|
|
122
|
+
border-radius: 8px;
|
|
123
|
+
font-weight: 600;
|
|
124
|
+
font-size: 0.875rem;
|
|
125
|
+
cursor: pointer;
|
|
126
|
+
transition: all 0.2s;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.preview-btn-primary:hover,
|
|
130
|
+
.preview-btn-secondary:hover,
|
|
131
|
+
.preview-btn-destructive:hover {
|
|
132
|
+
opacity: 0.9;
|
|
133
|
+
transform: translateY(-1px);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.preview-ui-elements {
|
|
137
|
+
display: flex;
|
|
138
|
+
gap: 1rem;
|
|
139
|
+
align-items: center;
|
|
140
|
+
flex-wrap: wrap;
|
|
141
|
+
margin-bottom: 2rem;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.preview-input {
|
|
145
|
+
padding: 0.625rem 1rem;
|
|
146
|
+
border: 2px solid;
|
|
147
|
+
border-radius: 8px;
|
|
148
|
+
font-size: 0.875rem;
|
|
149
|
+
min-width: 200px;
|
|
150
|
+
transition: all 0.2s;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.preview-input:focus {
|
|
154
|
+
outline: none;
|
|
155
|
+
border-color: var(--preview-ring, #3b82f6);
|
|
156
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.preview-badge {
|
|
160
|
+
padding: 0.375rem 0.875rem;
|
|
161
|
+
border-radius: 6px;
|
|
162
|
+
font-size: 0.75rem;
|
|
163
|
+
font-weight: 600;
|
|
164
|
+
text-transform: uppercase;
|
|
165
|
+
letter-spacing: 0.05em;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.preview-muted {
|
|
169
|
+
padding: 1.5rem;
|
|
170
|
+
border-radius: 8px;
|
|
171
|
+
margin-bottom: 2rem;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.preview-muted p {
|
|
175
|
+
margin: 0;
|
|
176
|
+
font-size: 0.875rem;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Dark mode adjustments */
|
|
180
|
+
.theme-live-preview-container[data-mode="dark"] .preview-content {
|
|
181
|
+
background: #1a1a1a;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.theme-live-preview-container[data-mode="dark"] .preview-card {
|
|
185
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.theme-live-preview-container[data-mode="dark"] .preview-card:hover {
|
|
189
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
|
190
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThemeLivePreview.d.ts","sourceRoot":"","sources":["../../src/fields/ThemeLivePreview.tsx"],"names":[],"mappings":"AAUA,OAAO,wBAAwB,CAAA;AAE/B,eAAO,MAAM,gBAAgB,mCAsK5B,CAAA;AAED,eAAe,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Theme Live Preview Component
|
|
5
|
+
* Real-time preview of theme configuration
|
|
6
|
+
* Inspired by Payload CMS 3 Live Preview
|
|
7
|
+
*/ import { useFormFields } from '@payloadcms/ui';
|
|
8
|
+
import { useEffect, useState } from 'react';
|
|
9
|
+
import './ThemeLivePreview.css';
|
|
10
|
+
export const ThemeLivePreview = ()=>{
|
|
11
|
+
const allFields = useFormFields(([fields])=>fields);
|
|
12
|
+
const [previewMode, setPreviewMode] = useState('light');
|
|
13
|
+
const themeConfig = allFields?.themeConfiguration?.value;
|
|
14
|
+
// Extract colors from theme configuration
|
|
15
|
+
const lightMode = themeConfig?.lightMode;
|
|
16
|
+
const darkMode = themeConfig?.darkMode;
|
|
17
|
+
const activeMode = previewMode === 'light' ? lightMode : darkMode;
|
|
18
|
+
// Apply theme colors to preview
|
|
19
|
+
useEffect(()=>{
|
|
20
|
+
if (!activeMode) return;
|
|
21
|
+
const root = document.documentElement;
|
|
22
|
+
Object.entries(activeMode).forEach(([key, value])=>{
|
|
23
|
+
if (typeof value === 'string') {
|
|
24
|
+
root.style.setProperty(`--preview-${key}`, value);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}, [
|
|
28
|
+
activeMode
|
|
29
|
+
]);
|
|
30
|
+
if (!themeConfig) {
|
|
31
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
32
|
+
className: "theme-live-preview-empty",
|
|
33
|
+
children: /*#__PURE__*/ _jsx("p", {
|
|
34
|
+
children: "Configure your theme to see live preview"
|
|
35
|
+
})
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
39
|
+
className: "theme-live-preview-container",
|
|
40
|
+
"data-mode": previewMode,
|
|
41
|
+
children: [
|
|
42
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
43
|
+
className: "preview-header",
|
|
44
|
+
children: [
|
|
45
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
46
|
+
children: "Live Theme Preview"
|
|
47
|
+
}),
|
|
48
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
49
|
+
className: "mode-toggle",
|
|
50
|
+
children: [
|
|
51
|
+
/*#__PURE__*/ _jsx("button", {
|
|
52
|
+
type: "button",
|
|
53
|
+
className: previewMode === 'light' ? 'active' : '',
|
|
54
|
+
onClick: ()=>setPreviewMode('light'),
|
|
55
|
+
children: "☀️ Light"
|
|
56
|
+
}),
|
|
57
|
+
/*#__PURE__*/ _jsx("button", {
|
|
58
|
+
type: "button",
|
|
59
|
+
className: previewMode === 'dark' ? 'active' : '',
|
|
60
|
+
onClick: ()=>setPreviewMode('dark'),
|
|
61
|
+
children: "🌙 Dark"
|
|
62
|
+
})
|
|
63
|
+
]
|
|
64
|
+
})
|
|
65
|
+
]
|
|
66
|
+
}),
|
|
67
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
68
|
+
className: "preview-content",
|
|
69
|
+
style: {
|
|
70
|
+
backgroundColor: activeMode?.background || '#ffffff',
|
|
71
|
+
color: activeMode?.foreground || '#000000'
|
|
72
|
+
},
|
|
73
|
+
children: [
|
|
74
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
75
|
+
className: "preview-section",
|
|
76
|
+
children: [
|
|
77
|
+
/*#__PURE__*/ _jsx("h1", {
|
|
78
|
+
style: {
|
|
79
|
+
color: activeMode?.foreground
|
|
80
|
+
},
|
|
81
|
+
children: "Welcome to Your Site"
|
|
82
|
+
}),
|
|
83
|
+
/*#__PURE__*/ _jsx("p", {
|
|
84
|
+
style: {
|
|
85
|
+
color: activeMode?.mutedForeground
|
|
86
|
+
},
|
|
87
|
+
children: "This is a live preview of your theme configuration"
|
|
88
|
+
})
|
|
89
|
+
]
|
|
90
|
+
}),
|
|
91
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
92
|
+
className: "preview-cards",
|
|
93
|
+
children: [
|
|
94
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
95
|
+
className: "preview-card",
|
|
96
|
+
style: {
|
|
97
|
+
backgroundColor: activeMode?.card || '#ffffff',
|
|
98
|
+
color: activeMode?.cardForeground || '#000000',
|
|
99
|
+
borderColor: activeMode?.border || '#e2e8f0'
|
|
100
|
+
},
|
|
101
|
+
children: [
|
|
102
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
103
|
+
children: "Featured Content"
|
|
104
|
+
}),
|
|
105
|
+
/*#__PURE__*/ _jsx("p", {
|
|
106
|
+
style: {
|
|
107
|
+
color: activeMode?.mutedForeground
|
|
108
|
+
},
|
|
109
|
+
children: "Card component with theme colors"
|
|
110
|
+
}),
|
|
111
|
+
/*#__PURE__*/ _jsx("button", {
|
|
112
|
+
className: "preview-btn-primary",
|
|
113
|
+
style: {
|
|
114
|
+
backgroundColor: activeMode?.primary || '#0070f3',
|
|
115
|
+
color: activeMode?.primaryForeground || '#ffffff'
|
|
116
|
+
},
|
|
117
|
+
children: "Primary Action"
|
|
118
|
+
})
|
|
119
|
+
]
|
|
120
|
+
}),
|
|
121
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
122
|
+
className: "preview-card",
|
|
123
|
+
style: {
|
|
124
|
+
backgroundColor: activeMode?.card || '#ffffff',
|
|
125
|
+
color: activeMode?.cardForeground || '#000000',
|
|
126
|
+
borderColor: activeMode?.border || '#e2e8f0'
|
|
127
|
+
},
|
|
128
|
+
children: [
|
|
129
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
130
|
+
children: "Secondary Card"
|
|
131
|
+
}),
|
|
132
|
+
/*#__PURE__*/ _jsx("p", {
|
|
133
|
+
style: {
|
|
134
|
+
color: activeMode?.mutedForeground
|
|
135
|
+
},
|
|
136
|
+
children: "Another card to showcase your theme"
|
|
137
|
+
}),
|
|
138
|
+
/*#__PURE__*/ _jsx("button", {
|
|
139
|
+
className: "preview-btn-secondary",
|
|
140
|
+
style: {
|
|
141
|
+
backgroundColor: activeMode?.secondary || '#f1f5f9',
|
|
142
|
+
color: activeMode?.secondaryForeground || '#0f172a'
|
|
143
|
+
},
|
|
144
|
+
children: "Secondary"
|
|
145
|
+
})
|
|
146
|
+
]
|
|
147
|
+
})
|
|
148
|
+
]
|
|
149
|
+
}),
|
|
150
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
151
|
+
className: "preview-ui-elements",
|
|
152
|
+
children: [
|
|
153
|
+
/*#__PURE__*/ _jsx("input", {
|
|
154
|
+
type: "text",
|
|
155
|
+
placeholder: "Input field",
|
|
156
|
+
className: "preview-input",
|
|
157
|
+
style: {
|
|
158
|
+
backgroundColor: activeMode?.background,
|
|
159
|
+
color: activeMode?.foreground,
|
|
160
|
+
borderColor: activeMode?.input || activeMode?.border
|
|
161
|
+
}
|
|
162
|
+
}),
|
|
163
|
+
/*#__PURE__*/ _jsx("div", {
|
|
164
|
+
className: "preview-badge",
|
|
165
|
+
style: {
|
|
166
|
+
backgroundColor: activeMode?.accent || '#f1f5f9',
|
|
167
|
+
color: activeMode?.accentForeground || '#0f172a'
|
|
168
|
+
},
|
|
169
|
+
children: "Accent Badge"
|
|
170
|
+
}),
|
|
171
|
+
/*#__PURE__*/ _jsx("button", {
|
|
172
|
+
className: "preview-btn-destructive",
|
|
173
|
+
style: {
|
|
174
|
+
backgroundColor: activeMode?.destructive || '#ef4444',
|
|
175
|
+
color: activeMode?.destructiveForeground || '#ffffff'
|
|
176
|
+
},
|
|
177
|
+
children: "Destructive"
|
|
178
|
+
})
|
|
179
|
+
]
|
|
180
|
+
}),
|
|
181
|
+
/*#__PURE__*/ _jsx("div", {
|
|
182
|
+
className: "preview-muted",
|
|
183
|
+
style: {
|
|
184
|
+
backgroundColor: activeMode?.muted || '#f1f5f9',
|
|
185
|
+
color: activeMode?.mutedForeground || '#64748b'
|
|
186
|
+
},
|
|
187
|
+
children: /*#__PURE__*/ _jsx("p", {
|
|
188
|
+
children: "Muted background section for less prominent content"
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
]
|
|
192
|
+
})
|
|
193
|
+
]
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
export default ThemeLivePreview;
|
|
197
|
+
|
|
198
|
+
//# sourceMappingURL=ThemeLivePreview.js.map
|