@kilivi/payloadcms-theme-management 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +346 -845
- package/dist/components/AppearanceSessionControls.d.ts +15 -0
- package/dist/components/AppearanceSessionControls.d.ts.map +1 -0
- package/dist/components/AppearanceSessionControls.js +152 -0
- package/dist/components/AppearanceSessionControls.js.map +1 -0
- package/dist/components/ColorModeToggle.css +42 -0
- package/dist/components/ColorModeToggle.d.ts +40 -0
- package/dist/components/ColorModeToggle.d.ts.map +1 -0
- package/dist/components/ColorModeToggle.js +111 -0
- package/dist/components/ColorModeToggle.js.map +1 -0
- package/dist/components/ServerThemeInjector.d.ts +13 -1
- package/dist/components/ServerThemeInjector.d.ts.map +1 -1
- package/dist/components/ServerThemeInjector.js +65 -15
- package/dist/components/ServerThemeInjector.js.map +1 -1
- package/dist/components/ThemeLivePreviewSync.d.ts +4 -0
- package/dist/components/ThemeLivePreviewSync.d.ts.map +1 -1
- package/dist/components/ThemeLivePreviewSync.js +28 -4
- package/dist/components/ThemeLivePreviewSync.js.map +1 -1
- package/dist/components/ThemeSwitcher.d.ts +36 -0
- package/dist/components/ThemeSwitcher.d.ts.map +1 -0
- package/dist/components/ThemeSwitcher.js +89 -0
- package/dist/components/ThemeSwitcher.js.map +1 -0
- package/dist/components/typographyPreviewUtils.d.ts +5 -0
- package/dist/components/typographyPreviewUtils.d.ts.map +1 -1
- package/dist/components/typographyPreviewUtils.js +24 -11
- package/dist/components/typographyPreviewUtils.js.map +1 -1
- package/dist/constants/devicePreviews.d.ts +35 -0
- package/dist/constants/devicePreviews.d.ts.map +1 -0
- package/dist/constants/devicePreviews.js +43 -0
- package/dist/constants/devicePreviews.js.map +1 -0
- package/dist/constants/themeFonts.d.ts +94 -168
- package/dist/constants/themeFonts.d.ts.map +1 -1
- package/dist/constants/themeFonts.js +260 -140
- package/dist/constants/themeFonts.js.map +1 -1
- package/dist/endpoints/themeFonts.d.ts +5 -0
- package/dist/endpoints/themeFonts.d.ts.map +1 -0
- package/dist/endpoints/themeFonts.js +138 -0
- package/dist/endpoints/themeFonts.js.map +1 -0
- package/dist/extended-presets.d.ts +0 -4
- package/dist/extended-presets.d.ts.map +1 -1
- package/dist/extended-presets.js +1 -18
- package/dist/extended-presets.js.map +1 -1
- package/dist/extended-themes.d.ts.map +1 -1
- package/dist/extended-themes.js +428 -0
- package/dist/extended-themes.js.map +1 -1
- package/dist/fields/AccessibilityAuditField.d.ts +2 -0
- package/dist/fields/AccessibilityAuditField.d.ts.map +1 -0
- package/dist/fields/AccessibilityAuditField.js +303 -0
- package/dist/fields/AccessibilityAuditField.js.map +1 -0
- package/dist/fields/AppearancePreviewField.d.ts +2 -0
- package/dist/fields/AppearancePreviewField.d.ts.map +1 -0
- package/dist/fields/AppearancePreviewField.js +826 -0
- package/dist/fields/AppearancePreviewField.js.map +1 -0
- package/dist/fields/ColorPickerField.d.ts.map +1 -1
- package/dist/fields/ColorPickerField.js +89 -85
- package/dist/fields/ColorPickerField.js.map +1 -1
- package/dist/fields/FontSelectField.css +155 -0
- package/dist/fields/FontSelectField.d.ts +8 -0
- package/dist/fields/FontSelectField.d.ts.map +1 -1
- package/dist/fields/FontSelectField.js +223 -188
- package/dist/fields/FontSelectField.js.map +1 -1
- package/dist/fields/PaletteGeneratorField.d.ts +7 -0
- package/dist/fields/PaletteGeneratorField.d.ts.map +1 -0
- package/dist/fields/PaletteGeneratorField.js +327 -0
- package/dist/fields/PaletteGeneratorField.js.map +1 -0
- package/dist/fields/StylePresetField.d.ts +3 -0
- package/dist/fields/StylePresetField.d.ts.map +1 -0
- package/dist/fields/StylePresetField.js +463 -0
- package/dist/fields/StylePresetField.js.map +1 -0
- package/dist/fields/ThemeColorPickerField.css +115 -35
- package/dist/fields/ThemeColorPickerField.d.ts.map +1 -1
- package/dist/fields/ThemeColorPickerField.js +204 -70
- package/dist/fields/ThemeColorPickerField.js.map +1 -1
- package/dist/fields/ThemeExportField.d.ts +6 -0
- package/dist/fields/ThemeExportField.d.ts.map +1 -0
- package/dist/fields/ThemeExportField.js +202 -0
- package/dist/fields/ThemeExportField.js.map +1 -0
- package/dist/fields/ThemePresetImportField.d.ts.map +1 -1
- package/dist/fields/ThemePresetImportField.js +56 -25
- package/dist/fields/ThemePresetImportField.js.map +1 -1
- package/dist/fields/ThemePreviewField.css +444 -5
- package/dist/fields/ThemePreviewField.d.ts.map +1 -1
- package/dist/fields/ThemePreviewField.js +265 -170
- package/dist/fields/ThemePreviewField.js.map +1 -1
- package/dist/fields/ThemeTokenSelectField.d.ts.map +1 -1
- package/dist/fields/ThemeTokenSelectField.js +278 -80
- package/dist/fields/ThemeTokenSelectField.js.map +1 -1
- package/dist/fields/colorFieldHelpers.d.ts +90 -0
- package/dist/fields/colorFieldHelpers.d.ts.map +1 -0
- package/dist/fields/colorFieldHelpers.js +133 -0
- package/dist/fields/colorFieldHelpers.js.map +1 -0
- package/dist/fields/colorModeFields.d.ts +1 -1
- package/dist/fields/colorModeFields.d.ts.map +1 -1
- package/dist/fields/colorModeFields.js +49 -7
- package/dist/fields/colorModeFields.js.map +1 -1
- package/dist/fields/themeConfigurationField.d.ts +8 -1
- package/dist/fields/themeConfigurationField.d.ts.map +1 -1
- package/dist/fields/themeConfigurationField.js +1209 -228
- package/dist/fields/themeConfigurationField.js.map +1 -1
- package/dist/globals/ThemeSettings/FontPicker/FontPreview.d.ts.map +1 -1
- package/dist/globals/ThemeSettings/FontPicker/FontPreview.js +8 -4
- package/dist/globals/ThemeSettings/FontPicker/FontPreview.js.map +1 -1
- package/dist/globals/ThemeSettings/FontPicker/index.d.ts.map +1 -1
- package/dist/globals/ThemeSettings/FontPicker/index.js +81 -26
- package/dist/globals/ThemeSettings/FontPicker/index.js.map +1 -1
- package/dist/globals/ThemeSettings/FontPicker/styles.css +98 -41
- package/dist/globals/ThemeSettings/FontPicker/translations.d.ts +6 -0
- package/dist/globals/ThemeSettings/FontPicker/translations.d.ts.map +1 -1
- package/dist/globals/ThemeSettings/FontPicker/translations.js +14 -2
- package/dist/globals/ThemeSettings/FontPicker/translations.js.map +1 -1
- package/dist/globals/ThemeSettings/config.d.ts.map +1 -1
- package/dist/globals/ThemeSettings/config.js +76 -79
- package/dist/globals/ThemeSettings/config.js.map +1 -1
- package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.d.ts.map +1 -1
- package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js +63 -62
- package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js.map +1 -1
- package/dist/globals/ThemeSettings/fields/TypographyInfoField.d.ts.map +1 -1
- package/dist/globals/ThemeSettings/fields/TypographyInfoField.js +22 -25
- package/dist/globals/ThemeSettings/fields/TypographyInfoField.js.map +1 -1
- package/dist/hooks/useAppearanceSession.d.ts +50 -0
- package/dist/hooks/useAppearanceSession.d.ts.map +1 -0
- package/dist/hooks/useAppearanceSession.js +91 -0
- package/dist/hooks/useAppearanceSession.js.map +1 -0
- package/dist/hooks/useNativeLivePreview.d.ts +15 -0
- package/dist/hooks/useNativeLivePreview.d.ts.map +1 -0
- package/dist/hooks/useNativeLivePreview.js +18 -0
- package/dist/hooks/useNativeLivePreview.js.map +1 -0
- package/dist/hooks/useThemeFonts.d.ts +10 -0
- package/dist/hooks/useThemeFonts.d.ts.map +1 -0
- package/dist/hooks/useThemeFonts.js +36 -0
- package/dist/hooks/useThemeFonts.js.map +1 -0
- package/dist/hooks/useThemeTranslations.d.ts +13 -0
- package/dist/hooks/useThemeTranslations.d.ts.map +1 -0
- package/dist/hooks/useThemeTranslations.js +22 -0
- package/dist/hooks/useThemeTranslations.js.map +1 -0
- package/dist/i18n.d.ts +53 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +53 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +59 -29
- package/dist/index.js.map +1 -1
- package/dist/next-fonts.d.ts +3 -0
- package/dist/next-fonts.d.ts.map +1 -0
- package/dist/next-fonts.js +360 -0
- package/dist/next-fonts.js.map +1 -0
- package/dist/payload-types.d.ts +34 -6
- package/dist/payload-types.d.ts.map +1 -1
- package/dist/payload-types.js.map +1 -1
- package/dist/presets.d.ts +14 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js.map +1 -1
- package/dist/providers/Theme/index.d.ts.map +1 -1
- package/dist/providers/Theme/index.js +75 -11
- package/dist/providers/Theme/index.js.map +1 -1
- package/dist/providers/Theme/themeConfig.d.ts.map +1 -1
- package/dist/providers/Theme/themeConfig.js +104 -0
- package/dist/providers/Theme/themeConfig.js.map +1 -1
- package/dist/providers/Theme/types.d.ts +1 -1
- package/dist/providers/Theme/types.d.ts.map +1 -1
- package/dist/providers/Theme/types.js +10 -1
- package/dist/providers/Theme/types.js.map +1 -1
- package/dist/style-presets.d.ts +75 -0
- package/dist/style-presets.d.ts.map +1 -0
- package/dist/style-presets.js +738 -0
- package/dist/style-presets.js.map +1 -0
- package/dist/translations.d.ts +164 -1
- package/dist/translations.d.ts.map +1 -1
- package/dist/translations.js +320 -9
- package/dist/translations.js.map +1 -1
- package/dist/tweakcn-special-themes.d.ts.map +1 -1
- package/dist/tweakcn-special-themes.js +585 -9
- package/dist/tweakcn-special-themes.js.map +1 -1
- package/dist/types/css.d.js +3 -0
- package/dist/types/css.d.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utilities/getThemeSettings.d.ts.map +1 -1
- package/dist/utilities/getThemeSettings.js +2 -14
- package/dist/utilities/getThemeSettings.js.map +1 -1
- package/dist/utils/contrast.d.ts +63 -0
- package/dist/utils/contrast.d.ts.map +1 -0
- package/dist/utils/contrast.js +158 -0
- package/dist/utils/contrast.js.map +1 -0
- package/dist/utils/exportTokens.d.ts +30 -0
- package/dist/utils/exportTokens.d.ts.map +1 -0
- package/dist/utils/exportTokens.js +183 -0
- package/dist/utils/exportTokens.js.map +1 -0
- package/dist/utils/generatePalette.d.ts +46 -0
- package/dist/utils/generatePalette.d.ts.map +1 -0
- package/dist/utils/generatePalette.js +127 -0
- package/dist/utils/generatePalette.js.map +1 -0
- package/dist/utils/generateThemeCSS.d.ts.map +1 -1
- package/dist/utils/generateThemeCSS.js +2 -40
- package/dist/utils/generateThemeCSS.js.map +1 -1
- package/dist/utils/livePreviewThemeRuntime.d.ts +22 -3
- package/dist/utils/livePreviewThemeRuntime.d.ts.map +1 -1
- package/dist/utils/livePreviewThemeRuntime.js +48 -15
- package/dist/utils/livePreviewThemeRuntime.js.map +1 -1
- package/dist/utils/localizedText.d.ts +8 -0
- package/dist/utils/localizedText.d.ts.map +1 -0
- package/dist/utils/localizedText.js +21 -0
- package/dist/utils/localizedText.js.map +1 -0
- package/dist/utils/resolveThemeConfiguration.d.ts +60 -1
- package/dist/utils/resolveThemeConfiguration.d.ts.map +1 -1
- package/dist/utils/resolveThemeConfiguration.js +53 -13
- package/dist/utils/resolveThemeConfiguration.js.map +1 -1
- package/dist/utils/themeColors.d.ts +3 -0
- package/dist/utils/themeColors.d.ts.map +1 -1
- package/dist/utils/themeColors.js +30 -1
- package/dist/utils/themeColors.js.map +1 -1
- package/dist/utils/themeHtmlAttributes.d.ts +32 -12
- package/dist/utils/themeHtmlAttributes.d.ts.map +1 -1
- package/dist/utils/themeHtmlAttributes.js +38 -5
- package/dist/utils/themeHtmlAttributes.js.map +1 -1
- package/dist/utils/themeUtils.d.ts +3 -5
- package/dist/utils/themeUtils.d.ts.map +1 -1
- package/dist/utils/themeUtils.js +474 -6
- package/dist/utils/themeUtils.js.map +1 -1
- package/dist/utils/tweakcnConverter.d.ts +1 -3
- package/dist/utils/tweakcnConverter.d.ts.map +1 -1
- package/dist/utils/tweakcnConverter.js +1 -3
- package/dist/utils/tweakcnConverter.js.map +1 -1
- package/package.json +15 -5
- package/dist/components/ThemePreview.d.ts +0 -3
- package/dist/components/ThemePreview.d.ts.map +0 -1
- package/dist/components/ThemePreview.js +0 -321
- package/dist/components/ThemePreview.js.map +0 -1
- package/dist/plugin.d.ts +0 -2
- package/dist/plugin.d.ts.map +0 -1
- package/dist/plugin.js +0 -3
- package/dist/plugin.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
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 [isDragging, setIsDragging] = useState(false)\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 // Only update parent setValue when drag completes\n useEffect(() => {\n if (!isDragging) {\n setValue(localValue)\n }\n }, [isDragging, localValue, setValue])\n\n const handleHexChange = useCallback((hex: string) => {\n setHexValue(hex)\n setLocalValue(hex)\n }, [])\n\n const handleTextChange = useCallback((nextValue: string) => {\n setLocalValue(nextValue)\n setHexValue(toHex(nextValue))\n }, [])\n\n const handlePickerMouseDown = useCallback(() => {\n setIsDragging(true)\n }, [])\n\n const handlePickerMouseUp = useCallback(() => {\n setIsDragging(false)\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\n ref={pickerRef}\n className=\"color-picker-popover\"\n onMouseDown={handlePickerMouseDown}\n onMouseUp={handlePickerMouseUp}\n onMouseLeave={handlePickerMouseUp}\n >\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={() => {\n handleHexChange(color)\n setValue(color)\n }}\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","isDragging","setIsDragging","pickerRef","handleClickOutside","event","current","contains","target","document","addEventListener","removeEventListener","handleHexChange","handleTextChange","nextValue","handlePickerMouseDown","handlePickerMouseUp","fieldName","split","pop","label","description","admin","div","className","button","type","style","backgroundColor","onClick","aria-label","title","input","onChange","placeholder","ref","onMouseDown","onMouseUp","onMouseLeave","span","prefixed","map","toLowerCase"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/fields/ThemeColorPickerField.tsx"],"sourcesContent":["'use client'\n\nimport { useField, useFormFields } from '@payloadcms/ui'\nimport { X } from 'lucide-react'\nimport type { TextFieldClientComponent } from 'payload'\nimport { HexColorInput, HexColorPicker } from 'react-colorful'\nimport { useCallback, useEffect, useId, useRef, useState } from 'react'\nimport { useThemeLanguage, useThemeTranslations } from '../hooks/useThemeTranslations.js'\nimport { cssColorToHex as toHex, getContrastRatio } from '../utils/contrast.js'\nimport { resolveLocalizedText } from '../utils/localizedText.js'\n\n// Import CSS for styling\nif (typeof window !== 'undefined') {\n import('./ThemeColorPickerField.css').catch(() => {})\n}\n\n/**\n * Convert any CSS color string to hex for the picker, with a neutral fallback so\n * the swatch always renders. Delegates to the shared {@link toHex} util\n * (color package + canvas fallback for oklch/lch).\n */\nfunction cssColorToHex(cssColor: string): string {\n if (!cssColor) return '#000000'\n return toHex(cssColor) ?? '#888888'\n}\n\n/** WCAG contrast ratio between two CSS colours (1–21), or null if unparseable. */\nconst contrastRatio = getContrastRatio\n\ntype ContrastLevel = 'aa' | 'aaLarge' | 'low'\n\nfunction contrastLevel(ratio: number): ContrastLevel {\n if (ratio >= 4.5) return 'aa'\n if (ratio >= 3) return 'aaLarge'\n return 'low'\n}\n\nconst format = (template: string, values: Record<string, string>) =>\n template.replace(/\\{(\\w+)\\}/g, (match, key: string) => values[key] ?? match)\n\nconst SURFACE_TOKENS = ['card', 'popover', 'primary', 'secondary', 'muted', 'accent', 'destructive']\n\nconst isForegroundToken = (name: string) => name === 'foreground' || name.endsWith('Foreground')\n\n/** The token a colour is read against: surfaces ↔ their `…Foreground`, background ↔ foreground. */\nfunction contrastPartner(name: string): string | null {\n if (name === 'background') return 'foreground'\n if (name === 'foreground') return 'background'\n if (SURFACE_TOKENS.includes(name)) return `${name}Foreground`\n const base = name.endsWith('Foreground') ? name.slice(0, -'Foreground'.length) : ''\n return SURFACE_TOKENS.includes(base) ? base : null\n}\n\nconst QUICK_COLORS = [\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]\n\nconst ThemeColorPickerField: TextFieldClientComponent = ({ field, path }) => {\n const { value, setValue } = useField<string>({ path })\n const t = useThemeTranslations()\n const language = useThemeLanguage()\n const inputId = useId()\n const descriptionId = useId()\n const popoverId = useId()\n const [localValue, setLocalValue] = useState(value || '')\n const [showPicker, setShowPicker] = useState(false)\n const [hexValue, setHexValue] = useState(cssColorToHex(value || ''))\n const [isDragging, setIsDragging] = useState(false)\n const pickerRef = useRef<HTMLDivElement>(null)\n const swatchRef = useRef<HTMLButtonElement>(null)\n\n useEffect(() => {\n setLocalValue(value || '')\n setHexValue(cssColorToHex(value || ''))\n }, [value])\n\n const closePicker = useCallback((restoreFocus: boolean) => {\n setShowPicker(false)\n setIsDragging(false)\n if (restoreFocus) swatchRef.current?.focus()\n }, [])\n\n // Close on outside pointer, Escape; move focus into the popover when it opens.\n useEffect(() => {\n if (!showPicker) return\n\n function handlePointerOutside(event: PointerEvent) {\n const target = event.target as Node\n if (pickerRef.current?.contains(target) || swatchRef.current?.contains(target)) return\n closePicker(false)\n }\n\n function handleKeyDown(event: KeyboardEvent) {\n if (event.key === 'Escape') {\n event.stopPropagation()\n closePicker(true)\n }\n }\n\n document.addEventListener('pointerdown', handlePointerOutside)\n document.addEventListener('keydown', handleKeyDown)\n pickerRef.current?.querySelector<HTMLElement>('.react-colorful__interactive')?.focus()\n\n return () => {\n document.removeEventListener('pointerdown', handlePointerOutside)\n document.removeEventListener('keydown', handleKeyDown)\n }\n }, [showPicker, closePicker])\n\n // A drag ends wherever the pointer is released, when the browser cancels the\n // gesture (e.g. to scroll on touch), or when the window loses focus.\n useEffect(() => {\n if (!isDragging) return\n const endDrag = () => setIsDragging(false)\n window.addEventListener('pointerup', endDrag)\n window.addEventListener('pointercancel', endDrag)\n window.addEventListener('blur', endDrag)\n return () => {\n window.removeEventListener('pointerup', endDrag)\n window.removeEventListener('pointercancel', endDrag)\n window.removeEventListener('blur', endDrag)\n }\n }, [isDragging])\n\n // Only update parent setValue when drag completes\n useEffect(() => {\n if (!isDragging) {\n setValue(localValue)\n }\n }, [isDragging, localValue, setValue])\n\n const handleHexChange = useCallback((hex: string) => {\n setHexValue(hex)\n setLocalValue(hex)\n }, [])\n\n const handleTextChange = useCallback((nextValue: string) => {\n setLocalValue(nextValue)\n setHexValue(cssColorToHex(nextValue))\n }, [])\n\n const fieldName = path.split('.').pop() || ''\n const label = resolveLocalizedText(field.label, language, fieldName)\n const description = resolveLocalizedText(field.admin?.description, language)\n\n // Contrast is only meaningful against the token this colour is paired with\n // (e.g. primary ↔ primaryForeground), so measure that pair instead of white/black.\n const pairKey = contrastPartner(fieldName)\n const pairPath = pairKey ? [...path.split('.').slice(0, -1), pairKey].join('.') : null\n const pairValue = useFormFields(([fields]) =>\n pairPath ? (fields?.[pairPath]?.value as string | undefined) : undefined,\n )\n const ownValue = localValue || hexValue\n const pairSample =\n pairKey && typeof pairValue === 'string' && pairValue.trim()\n ? isForegroundToken(fieldName)\n ? {\n foreground: ownValue,\n background: pairValue,\n foregroundLabel: label,\n backgroundLabel: pairKey,\n }\n : {\n foreground: pairValue,\n background: ownValue,\n foregroundLabel: pairKey,\n backgroundLabel: label,\n }\n : null\n\n const levelLabel: Record<ContrastLevel, string> = {\n aa: t.colorPicker.levelAA,\n aaLarge: t.colorPicker.levelAALarge,\n low: t.colorPicker.levelLow,\n }\n\n return (\n <div className=\"theme-color-picker-field\">\n <div className=\"field-header\">\n <label className=\"field-label\" htmlFor={inputId}>\n {label}\n </label>\n {description && (\n <div className=\"field-description\" id={descriptionId}>\n {description}\n </div>\n )}\n </div>\n\n <div className=\"color-picker-container\">\n <div className=\"color-input-row\">\n <button\n ref={swatchRef}\n type=\"button\"\n className=\"color-swatch-btn\"\n style={{ backgroundColor: localValue || hexValue }}\n onClick={() => setShowPicker((open) => !open)}\n aria-label={`${t.colorPicker.openPicker}: ${label}`}\n aria-expanded={showPicker}\n aria-controls={showPicker ? popoverId : undefined}\n title={hexValue}\n />\n <input\n id={inputId}\n type=\"text\"\n value={localValue || ''}\n onChange={(event) => handleTextChange(event.target.value)}\n placeholder={t.colorPicker.placeholder}\n aria-describedby={description ? descriptionId : undefined}\n spellCheck={false}\n className=\"color-text-input\"\n />\n </div>\n\n {pairSample ? (\n <div className=\"color-contrast-row\">\n {(() => {\n const ratio = contrastRatio(pairSample.foreground, pairSample.background)\n if (ratio == null) return null\n const level = contrastLevel(ratio)\n const title = format(t.colorPicker.pairContrastTitle, {\n foreground: pairSample.foregroundLabel,\n background: pairSample.backgroundLabel,\n })\n return (\n <span className=\"contrast-chip\" title={`${title}: ${ratio.toFixed(2)}:1`}>\n <span\n className=\"contrast-sample\"\n style={{ background: pairSample.background, color: pairSample.foreground }}\n aria-hidden=\"true\"\n >\n Aa\n </span>\n <span className={`contrast-ratio contrast-ratio--${level}`}>\n <span className=\"sr-only\">{title}: </span>\n {t.colorPicker.pairContrast} {ratio.toFixed(1)} · {levelLabel[level]}\n </span>\n </span>\n )\n })()}\n </div>\n ) : null}\n\n {showPicker && (\n <div\n ref={pickerRef}\n id={popoverId}\n role=\"dialog\"\n aria-label={`${t.colorPicker.title}: ${label}`}\n className=\"color-picker-popover\"\n onPointerDown={() => setIsDragging(true)}\n >\n <div className=\"picker-header\">\n <span className=\"picker-title\">{t.colorPicker.title}</span>\n <button\n type=\"button\"\n onClick={() => closePicker(true)}\n className=\"picker-close-btn\"\n aria-label={t.colorPicker.close}\n >\n <X size={16} aria-hidden=\"true\" />\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 aria-label=\"HEX\"\n className=\"hex-input\"\n />\n </div>\n\n <div className=\"color-swatches\">\n <div className=\"swatches-label\">{t.colorPicker.quickColors}</div>\n <div className=\"swatches-grid\">\n {QUICK_COLORS.map((color) => {\n const selected = hexValue.toLowerCase() === color.toLowerCase()\n return (\n <button\n key={color}\n type=\"button\"\n onClick={() => {\n handleHexChange(color)\n setValue(color)\n }}\n className={`swatch-btn ${selected ? 'selected' : ''}`}\n style={{ backgroundColor: color }}\n aria-label={`${t.colorPicker.selectColor} ${color}`}\n aria-pressed={selected}\n title={color}\n />\n )\n })}\n </div>\n </div>\n </div>\n )}\n </div>\n </div>\n )\n}\n\nexport default ThemeColorPickerField\n"],"names":["useField","useFormFields","X","HexColorInput","HexColorPicker","useCallback","useEffect","useId","useRef","useState","useThemeLanguage","useThemeTranslations","cssColorToHex","toHex","getContrastRatio","resolveLocalizedText","window","catch","cssColor","contrastRatio","contrastLevel","ratio","format","template","values","replace","match","key","SURFACE_TOKENS","isForegroundToken","name","endsWith","contrastPartner","includes","base","slice","length","QUICK_COLORS","ThemeColorPickerField","field","path","value","setValue","t","language","inputId","descriptionId","popoverId","localValue","setLocalValue","showPicker","setShowPicker","hexValue","setHexValue","isDragging","setIsDragging","pickerRef","swatchRef","closePicker","restoreFocus","current","focus","handlePointerOutside","event","target","contains","handleKeyDown","stopPropagation","document","addEventListener","querySelector","removeEventListener","endDrag","handleHexChange","hex","handleTextChange","nextValue","fieldName","split","pop","label","description","admin","pairKey","pairPath","join","pairValue","fields","undefined","ownValue","pairSample","trim","foreground","background","foregroundLabel","backgroundLabel","levelLabel","aa","colorPicker","levelAA","aaLarge","levelAALarge","low","levelLow","div","className","htmlFor","id","button","ref","type","style","backgroundColor","onClick","open","aria-label","openPicker","aria-expanded","aria-controls","title","input","onChange","placeholder","aria-describedby","spellCheck","level","pairContrastTitle","span","toFixed","color","aria-hidden","pairContrast","role","onPointerDown","close","size","prefixed","quickColors","map","selected","toLowerCase","selectColor","aria-pressed"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin tool to export the current theme as portable design tokens (W3C JSON) or
|
|
3
|
+
* Tailwind theme config (v4 `@theme inline` / v3 `theme.extend`).
|
|
4
|
+
*/
|
|
5
|
+
export default function ThemeExportField(): import("react").JSX.Element;
|
|
6
|
+
//# sourceMappingURL=ThemeExportField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThemeExportField.d.ts","sourceRoot":"","sources":["../../src/fields/ThemeExportField.tsx"],"names":[],"mappings":"AAkCA;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,gCAsIvC"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useFormFields } from '@payloadcms/ui';
|
|
4
|
+
import { Braces, Copy, Download, FileCode } from 'lucide-react';
|
|
5
|
+
import { useCallback, useState } from 'react';
|
|
6
|
+
import { useThemeTranslations } from '../hooks/useThemeTranslations.js';
|
|
7
|
+
import { generateDesignTokensJson, generateTailwindV3Theme, generateTailwindV4Theme } from '../utils/exportTokens.js';
|
|
8
|
+
const COLOR_KEYS = [
|
|
9
|
+
'background',
|
|
10
|
+
'foreground',
|
|
11
|
+
'card',
|
|
12
|
+
'cardForeground',
|
|
13
|
+
'popover',
|
|
14
|
+
'popoverForeground',
|
|
15
|
+
'primary',
|
|
16
|
+
'primaryForeground',
|
|
17
|
+
'secondary',
|
|
18
|
+
'secondaryForeground',
|
|
19
|
+
'muted',
|
|
20
|
+
'mutedForeground',
|
|
21
|
+
'accent',
|
|
22
|
+
'accentForeground',
|
|
23
|
+
'destructive',
|
|
24
|
+
'destructiveForeground',
|
|
25
|
+
'border',
|
|
26
|
+
'input',
|
|
27
|
+
'ring'
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Admin tool to export the current theme as portable design tokens (W3C JSON) or
|
|
31
|
+
* Tailwind theme config (v4 `@theme inline` / v3 `theme.extend`).
|
|
32
|
+
*/ export default function ThemeExportField() {
|
|
33
|
+
const formFields = useFormFields(([state])=>state);
|
|
34
|
+
const t = useThemeTranslations().themeExport;
|
|
35
|
+
const [copied, setCopied] = useState(false);
|
|
36
|
+
const [copyFailed, setCopyFailed] = useState(false);
|
|
37
|
+
const buildConfig = useCallback(()=>{
|
|
38
|
+
const read = (path)=>{
|
|
39
|
+
const v = formFields?.[`themeConfiguration.${path}`]?.value;
|
|
40
|
+
return typeof v === 'string' ? v : undefined;
|
|
41
|
+
};
|
|
42
|
+
const mode = (modeName)=>{
|
|
43
|
+
const colors = {};
|
|
44
|
+
for (const key of COLOR_KEYS){
|
|
45
|
+
const value = read(`${modeName}.${key}`);
|
|
46
|
+
if (value) colors[key] = value;
|
|
47
|
+
}
|
|
48
|
+
return colors;
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
theme: read('theme'),
|
|
52
|
+
borderRadius: read('borderRadius'),
|
|
53
|
+
lightMode: mode('lightMode'),
|
|
54
|
+
darkMode: mode('darkMode')
|
|
55
|
+
};
|
|
56
|
+
}, [
|
|
57
|
+
formFields
|
|
58
|
+
]);
|
|
59
|
+
const download = useCallback((filename, content, type)=>{
|
|
60
|
+
if (typeof document === 'undefined') return;
|
|
61
|
+
const blob = new Blob([
|
|
62
|
+
content
|
|
63
|
+
], {
|
|
64
|
+
type
|
|
65
|
+
});
|
|
66
|
+
const url = URL.createObjectURL(blob);
|
|
67
|
+
const a = document.createElement('a');
|
|
68
|
+
a.href = url;
|
|
69
|
+
a.download = filename;
|
|
70
|
+
document.body.appendChild(a);
|
|
71
|
+
a.click();
|
|
72
|
+
a.remove();
|
|
73
|
+
URL.revokeObjectURL(url);
|
|
74
|
+
}, []);
|
|
75
|
+
const onCopyV4 = useCallback(async ()=>{
|
|
76
|
+
const css = generateTailwindV4Theme(buildConfig());
|
|
77
|
+
try {
|
|
78
|
+
await navigator.clipboard.writeText(css);
|
|
79
|
+
setCopyFailed(false);
|
|
80
|
+
setCopied(true);
|
|
81
|
+
setTimeout(()=>setCopied(false), 1500);
|
|
82
|
+
} catch {
|
|
83
|
+
// Clipboard access can be blocked (permissions, insecure context); say so.
|
|
84
|
+
setCopyFailed(true);
|
|
85
|
+
}
|
|
86
|
+
}, [
|
|
87
|
+
buildConfig
|
|
88
|
+
]);
|
|
89
|
+
const btnStyle = {
|
|
90
|
+
display: 'inline-flex',
|
|
91
|
+
alignItems: 'center',
|
|
92
|
+
gap: '6px',
|
|
93
|
+
padding: '8px 12px',
|
|
94
|
+
borderRadius: '8px',
|
|
95
|
+
border: '1px solid var(--theme-elevation-200)',
|
|
96
|
+
cursor: 'pointer',
|
|
97
|
+
background: 'var(--theme-elevation-50)',
|
|
98
|
+
color: 'var(--theme-elevation-700)',
|
|
99
|
+
fontWeight: 600,
|
|
100
|
+
fontSize: '12px'
|
|
101
|
+
};
|
|
102
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
103
|
+
style: {
|
|
104
|
+
marginBottom: '20px'
|
|
105
|
+
},
|
|
106
|
+
children: [
|
|
107
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
108
|
+
style: {
|
|
109
|
+
display: 'inline-flex',
|
|
110
|
+
alignItems: 'center',
|
|
111
|
+
gap: '6px',
|
|
112
|
+
fontSize: '13px',
|
|
113
|
+
fontWeight: 600,
|
|
114
|
+
color: 'var(--theme-elevation-800)'
|
|
115
|
+
},
|
|
116
|
+
children: [
|
|
117
|
+
/*#__PURE__*/ _jsx(FileCode, {
|
|
118
|
+
size: 14,
|
|
119
|
+
"aria-hidden": true
|
|
120
|
+
}),
|
|
121
|
+
t.title
|
|
122
|
+
]
|
|
123
|
+
}),
|
|
124
|
+
/*#__PURE__*/ _jsx("div", {
|
|
125
|
+
style: {
|
|
126
|
+
fontSize: '11px',
|
|
127
|
+
color: 'var(--theme-elevation-500)',
|
|
128
|
+
marginBottom: '12px'
|
|
129
|
+
},
|
|
130
|
+
children: t.subtitle
|
|
131
|
+
}),
|
|
132
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
133
|
+
style: {
|
|
134
|
+
display: 'flex',
|
|
135
|
+
flexWrap: 'wrap',
|
|
136
|
+
gap: '8px'
|
|
137
|
+
},
|
|
138
|
+
children: [
|
|
139
|
+
/*#__PURE__*/ _jsxs("button", {
|
|
140
|
+
type: "button",
|
|
141
|
+
style: btnStyle,
|
|
142
|
+
onClick: ()=>download('theme.tokens.json', generateDesignTokensJson(buildConfig()), 'application/json'),
|
|
143
|
+
children: [
|
|
144
|
+
/*#__PURE__*/ _jsx(Braces, {
|
|
145
|
+
size: 14,
|
|
146
|
+
"aria-hidden": true
|
|
147
|
+
}),
|
|
148
|
+
t.designTokens
|
|
149
|
+
]
|
|
150
|
+
}),
|
|
151
|
+
/*#__PURE__*/ _jsxs("button", {
|
|
152
|
+
type: "button",
|
|
153
|
+
style: btnStyle,
|
|
154
|
+
onClick: ()=>download('theme.tailwind.css', generateTailwindV4Theme(buildConfig()), 'text/css'),
|
|
155
|
+
children: [
|
|
156
|
+
/*#__PURE__*/ _jsx(Download, {
|
|
157
|
+
size: 14,
|
|
158
|
+
"aria-hidden": true
|
|
159
|
+
}),
|
|
160
|
+
t.tailwindV4
|
|
161
|
+
]
|
|
162
|
+
}),
|
|
163
|
+
/*#__PURE__*/ _jsxs("button", {
|
|
164
|
+
type: "button",
|
|
165
|
+
style: btnStyle,
|
|
166
|
+
onClick: onCopyV4,
|
|
167
|
+
children: [
|
|
168
|
+
/*#__PURE__*/ _jsx(Copy, {
|
|
169
|
+
size: 14,
|
|
170
|
+
"aria-hidden": true
|
|
171
|
+
}),
|
|
172
|
+
copied ? t.copied : t.copyV4
|
|
173
|
+
]
|
|
174
|
+
}),
|
|
175
|
+
/*#__PURE__*/ _jsxs("button", {
|
|
176
|
+
type: "button",
|
|
177
|
+
style: btnStyle,
|
|
178
|
+
onClick: ()=>download('tailwind.config.js', generateTailwindV3Theme(buildConfig()), 'text/javascript'),
|
|
179
|
+
children: [
|
|
180
|
+
/*#__PURE__*/ _jsx(Download, {
|
|
181
|
+
size: 14,
|
|
182
|
+
"aria-hidden": true
|
|
183
|
+
}),
|
|
184
|
+
t.tailwindV3
|
|
185
|
+
]
|
|
186
|
+
})
|
|
187
|
+
]
|
|
188
|
+
}),
|
|
189
|
+
/*#__PURE__*/ _jsx("div", {
|
|
190
|
+
role: "status",
|
|
191
|
+
style: {
|
|
192
|
+
marginTop: '8px',
|
|
193
|
+
fontSize: '12px',
|
|
194
|
+
color: 'var(--theme-error-600)'
|
|
195
|
+
},
|
|
196
|
+
children: copyFailed ? t.copyFailed : null
|
|
197
|
+
})
|
|
198
|
+
]
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
//# sourceMappingURL=ThemeExportField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/fields/ThemeExportField.tsx"],"sourcesContent":["'use client'\n\nimport { useFormFields } from '@payloadcms/ui'\nimport { Braces, Copy, Download, FileCode } from 'lucide-react'\nimport { useCallback, useState } from 'react'\nimport { useThemeTranslations } from '../hooks/useThemeTranslations.js'\nimport {\n generateDesignTokensJson,\n generateTailwindV3Theme,\n generateTailwindV4Theme,\n} from '../utils/exportTokens.js'\n\nconst COLOR_KEYS = [\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/**\n * Admin tool to export the current theme as portable design tokens (W3C JSON) or\n * Tailwind theme config (v4 `@theme inline` / v3 `theme.extend`).\n */\nexport default function ThemeExportField() {\n const formFields = useFormFields(([state]) => state) as Record<\n string,\n { value?: unknown } | undefined\n >\n const t = useThemeTranslations().themeExport\n const [copied, setCopied] = useState(false)\n const [copyFailed, setCopyFailed] = useState(false)\n\n const buildConfig = useCallback(() => {\n const read = (path: string) => {\n const v = formFields?.[`themeConfiguration.${path}`]?.value\n return typeof v === 'string' ? v : undefined\n }\n const mode = (modeName: 'lightMode' | 'darkMode') => {\n const colors: Record<string, string> = {}\n for (const key of COLOR_KEYS) {\n const value = read(`${modeName}.${key}`)\n if (value) colors[key] = value\n }\n return colors\n }\n return {\n theme: read('theme'),\n borderRadius: read('borderRadius'),\n lightMode: mode('lightMode'),\n darkMode: mode('darkMode'),\n }\n }, [formFields])\n\n const download = useCallback((filename: string, content: string, type: string) => {\n if (typeof document === 'undefined') return\n const blob = new Blob([content], { type })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n document.body.appendChild(a)\n a.click()\n a.remove()\n URL.revokeObjectURL(url)\n }, [])\n\n const onCopyV4 = useCallback(async () => {\n const css = generateTailwindV4Theme(buildConfig())\n try {\n await navigator.clipboard.writeText(css)\n setCopyFailed(false)\n setCopied(true)\n setTimeout(() => setCopied(false), 1500)\n } catch {\n // Clipboard access can be blocked (permissions, insecure context); say so.\n setCopyFailed(true)\n }\n }, [buildConfig])\n\n const btnStyle: React.CSSProperties = {\n display: 'inline-flex',\n alignItems: 'center',\n gap: '6px',\n padding: '8px 12px',\n borderRadius: '8px',\n border: '1px solid var(--theme-elevation-200)',\n cursor: 'pointer',\n background: 'var(--theme-elevation-50)',\n color: 'var(--theme-elevation-700)',\n fontWeight: 600,\n fontSize: '12px',\n }\n\n return (\n <div style={{ marginBottom: '20px' }}>\n <div\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: '6px',\n fontSize: '13px',\n fontWeight: 600,\n color: 'var(--theme-elevation-800)',\n }}\n >\n <FileCode size={14} aria-hidden />\n {t.title}\n </div>\n <div style={{ fontSize: '11px', color: 'var(--theme-elevation-500)', marginBottom: '12px' }}>\n {t.subtitle}\n </div>\n\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>\n <button\n type=\"button\"\n style={btnStyle}\n onClick={() =>\n download('theme.tokens.json', generateDesignTokensJson(buildConfig()), 'application/json')\n }\n >\n <Braces size={14} aria-hidden />\n {t.designTokens}\n </button>\n <button\n type=\"button\"\n style={btnStyle}\n onClick={() =>\n download('theme.tailwind.css', generateTailwindV4Theme(buildConfig()), 'text/css')\n }\n >\n <Download size={14} aria-hidden />\n {t.tailwindV4}\n </button>\n <button type=\"button\" style={btnStyle} onClick={onCopyV4}>\n <Copy size={14} aria-hidden />\n {copied ? t.copied : t.copyV4}\n </button>\n <button\n type=\"button\"\n style={btnStyle}\n onClick={() =>\n download(\n 'tailwind.config.js',\n generateTailwindV3Theme(buildConfig()),\n 'text/javascript',\n )\n }\n >\n <Download size={14} aria-hidden />\n {t.tailwindV3}\n </button>\n </div>\n <div role=\"status\" style={{ marginTop: '8px', fontSize: '12px', color: 'var(--theme-error-600)' }}>\n {copyFailed ? t.copyFailed : null}\n </div>\n </div>\n )\n}\n"],"names":["useFormFields","Braces","Copy","Download","FileCode","useCallback","useState","useThemeTranslations","generateDesignTokensJson","generateTailwindV3Theme","generateTailwindV4Theme","COLOR_KEYS","ThemeExportField","formFields","state","t","themeExport","copied","setCopied","copyFailed","setCopyFailed","buildConfig","read","path","v","value","undefined","mode","modeName","colors","key","theme","borderRadius","lightMode","darkMode","download","filename","content","type","document","blob","Blob","url","URL","createObjectURL","a","createElement","href","body","appendChild","click","remove","revokeObjectURL","onCopyV4","css","navigator","clipboard","writeText","setTimeout","btnStyle","display","alignItems","gap","padding","border","cursor","background","color","fontWeight","fontSize","div","style","marginBottom","size","aria-hidden","title","subtitle","flexWrap","button","onClick","designTokens","tailwindV4","copyV4","tailwindV3","role","marginTop"],"mappings":"AAAA;;AAEA,SAASA,aAAa,QAAQ,iBAAgB;AAC9C,SAASC,MAAM,EAAEC,IAAI,EAAEC,QAAQ,EAAEC,QAAQ,QAAQ,eAAc;AAC/D,SAASC,WAAW,EAAEC,QAAQ,QAAQ,QAAO;AAC7C,SAASC,oBAAoB,QAAQ,mCAAkC;AACvE,SACEC,wBAAwB,EACxBC,uBAAuB,EACvBC,uBAAuB,QAClB,2BAA0B;AAEjC,MAAMC,aAAa;IACjB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED;;;CAGC,GACD,eAAe,SAASC;IACtB,MAAMC,aAAab,cAAc,CAAC,CAACc,MAAM,GAAKA;IAI9C,MAAMC,IAAIR,uBAAuBS,WAAW;IAC5C,MAAM,CAACC,QAAQC,UAAU,GAAGZ,SAAS;IACrC,MAAM,CAACa,YAAYC,cAAc,GAAGd,SAAS;IAE7C,MAAMe,cAAchB,YAAY;QAC9B,MAAMiB,OAAO,CAACC;YACZ,MAAMC,IAAIX,YAAY,CAAC,CAAC,mBAAmB,EAAEU,MAAM,CAAC,EAAEE;YACtD,OAAO,OAAOD,MAAM,WAAWA,IAAIE;QACrC;QACA,MAAMC,OAAO,CAACC;YACZ,MAAMC,SAAiC,CAAC;YACxC,KAAK,MAAMC,OAAOnB,WAAY;gBAC5B,MAAMc,QAAQH,KAAK,GAAGM,SAAS,CAAC,EAAEE,KAAK;gBACvC,IAAIL,OAAOI,MAAM,CAACC,IAAI,GAAGL;YAC3B;YACA,OAAOI;QACT;QACA,OAAO;YACLE,OAAOT,KAAK;YACZU,cAAcV,KAAK;YACnBW,WAAWN,KAAK;YAChBO,UAAUP,KAAK;QACjB;IACF,GAAG;QAACd;KAAW;IAEf,MAAMsB,WAAW9B,YAAY,CAAC+B,UAAkBC,SAAiBC;QAC/D,IAAI,OAAOC,aAAa,aAAa;QACrC,MAAMC,OAAO,IAAIC,KAAK;YAACJ;SAAQ,EAAE;YAAEC;QAAK;QACxC,MAAMI,MAAMC,IAAIC,eAAe,CAACJ;QAChC,MAAMK,IAAIN,SAASO,aAAa,CAAC;QACjCD,EAAEE,IAAI,GAAGL;QACTG,EAAEV,QAAQ,GAAGC;QACbG,SAASS,IAAI,CAACC,WAAW,CAACJ;QAC1BA,EAAEK,KAAK;QACPL,EAAEM,MAAM;QACRR,IAAIS,eAAe,CAACV;IACtB,GAAG,EAAE;IAEL,MAAMW,WAAWhD,YAAY;QAC3B,MAAMiD,MAAM5C,wBAAwBW;QACpC,IAAI;YACF,MAAMkC,UAAUC,SAAS,CAACC,SAAS,CAACH;YACpClC,cAAc;YACdF,UAAU;YACVwC,WAAW,IAAMxC,UAAU,QAAQ;QACrC,EAAE,OAAM;YACN,2EAA2E;YAC3EE,cAAc;QAChB;IACF,GAAG;QAACC;KAAY;IAEhB,MAAMsC,WAAgC;QACpCC,SAAS;QACTC,YAAY;QACZC,KAAK;QACLC,SAAS;QACT/B,cAAc;QACdgC,QAAQ;QACRC,QAAQ;QACRC,YAAY;QACZC,OAAO;QACPC,YAAY;QACZC,UAAU;IACZ;IAEA,qBACE,MAACC;QAAIC,OAAO;YAAEC,cAAc;QAAO;;0BACjC,MAACF;gBACCC,OAAO;oBACLX,SAAS;oBACTC,YAAY;oBACZC,KAAK;oBACLO,UAAU;oBACVD,YAAY;oBACZD,OAAO;gBACT;;kCAEA,KAAC/D;wBAASqE,MAAM;wBAAIC,aAAW;;oBAC9B3D,EAAE4D,KAAK;;;0BAEV,KAACL;gBAAIC,OAAO;oBAAEF,UAAU;oBAAQF,OAAO;oBAA8BK,cAAc;gBAAO;0BACvFzD,EAAE6D,QAAQ;;0BAGb,MAACN;gBAAIC,OAAO;oBAAEX,SAAS;oBAAQiB,UAAU;oBAAQf,KAAK;gBAAM;;kCAC1D,MAACgB;wBACCxC,MAAK;wBACLiC,OAAOZ;wBACPoB,SAAS,IACP5C,SAAS,qBAAqB3B,yBAAyBa,gBAAgB;;0CAGzE,KAACpB;gCAAOwE,MAAM;gCAAIC,aAAW;;4BAC5B3D,EAAEiE,YAAY;;;kCAEjB,MAACF;wBACCxC,MAAK;wBACLiC,OAAOZ;wBACPoB,SAAS,IACP5C,SAAS,sBAAsBzB,wBAAwBW,gBAAgB;;0CAGzE,KAAClB;gCAASsE,MAAM;gCAAIC,aAAW;;4BAC9B3D,EAAEkE,UAAU;;;kCAEf,MAACH;wBAAOxC,MAAK;wBAASiC,OAAOZ;wBAAUoB,SAAS1B;;0CAC9C,KAACnD;gCAAKuE,MAAM;gCAAIC,aAAW;;4BAC1BzD,SAASF,EAAEE,MAAM,GAAGF,EAAEmE,MAAM;;;kCAE/B,MAACJ;wBACCxC,MAAK;wBACLiC,OAAOZ;wBACPoB,SAAS,IACP5C,SACE,sBACA1B,wBAAwBY,gBACxB;;0CAIJ,KAAClB;gCAASsE,MAAM;gCAAIC,aAAW;;4BAC9B3D,EAAEoE,UAAU;;;;;0BAGjB,KAACb;gBAAIc,MAAK;gBAASb,OAAO;oBAAEc,WAAW;oBAAOhB,UAAU;oBAAQF,OAAO;gBAAyB;0BAC7FhD,aAAaJ,EAAEI,UAAU,GAAG;;;;AAIrC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemePresetImportField.d.ts","sourceRoot":"","sources":["../../src/fields/ThemePresetImportField.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"ThemePresetImportField.d.ts","sourceRoot":"","sources":["../../src/fields/ThemePresetImportField.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAcnD,QAAA,MAAM,sBAAsB,GAAI,iBAAiB,oBAAoB,gCAiKpE,CAAA;AAED,eAAe,sBAAsB,CAAA"}
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { Button, Drawer, useField, useModal } from '@payloadcms/ui';
|
|
4
4
|
import { toast } from 'sonner';
|
|
5
|
-
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { useCallback, useId, useMemo, useRef, useState } from 'react';
|
|
6
|
+
import { useThemeLanguage, useThemeTranslations } from '../hooks/useThemeTranslations.js';
|
|
6
7
|
import { parseThemePresetInput } from '../utils/customThemePresets.js';
|
|
8
|
+
import { resolveLocalizedText } from '../utils/localizedText.js';
|
|
9
|
+
const format = (template, values)=>template.replace(/\{(\w+)\}/g, (match, key)=>String(values[key] ?? match));
|
|
7
10
|
const baseClass = 'theme-preset-import-field';
|
|
8
11
|
const drawerSlug = 'import-theme-presets';
|
|
9
12
|
const ThemePresetImportField = ({ field, path })=>{
|
|
@@ -13,34 +16,44 @@ const ThemePresetImportField = ({ field, path })=>{
|
|
|
13
16
|
const [textValue, setTextValue] = useState('');
|
|
14
17
|
const { closeModal, openModal } = useModal();
|
|
15
18
|
const fileInputRef = useRef(null);
|
|
19
|
+
const t = useThemeTranslations().presetImport;
|
|
20
|
+
const language = useThemeLanguage();
|
|
21
|
+
const textareaId = useId();
|
|
16
22
|
const presetSummary = useMemo(()=>{
|
|
17
23
|
if (!value || value.length === 0) {
|
|
18
|
-
return
|
|
24
|
+
return t.none;
|
|
19
25
|
}
|
|
20
|
-
const names = value.map((preset)=>preset.name).join(', ');
|
|
21
|
-
return
|
|
26
|
+
const names = value.map((preset)=>preset.label || preset.name).join(', ');
|
|
27
|
+
return format(t.summary, {
|
|
28
|
+
count: value.length,
|
|
29
|
+
names
|
|
30
|
+
});
|
|
22
31
|
}, [
|
|
23
|
-
value
|
|
32
|
+
value,
|
|
33
|
+
t
|
|
24
34
|
]);
|
|
25
35
|
const applyFromText = useCallback((raw)=>{
|
|
26
36
|
if (!raw.trim()) {
|
|
27
37
|
setValue(null);
|
|
28
38
|
closeModal(drawerSlug);
|
|
29
|
-
toast.success(
|
|
39
|
+
toast.success(t.cleared);
|
|
30
40
|
return;
|
|
31
41
|
}
|
|
32
42
|
const presets = parseThemePresetInput(raw);
|
|
33
43
|
if (presets.length === 0) {
|
|
34
|
-
toast.error(
|
|
44
|
+
toast.error(t.invalid);
|
|
35
45
|
return;
|
|
36
46
|
}
|
|
37
47
|
setValue(presets);
|
|
38
48
|
closeModal(drawerSlug);
|
|
39
|
-
toast.success(
|
|
49
|
+
toast.success(format(t.imported, {
|
|
50
|
+
count: presets.length
|
|
51
|
+
}));
|
|
40
52
|
setTextValue('');
|
|
41
53
|
}, [
|
|
42
54
|
setValue,
|
|
43
|
-
closeModal
|
|
55
|
+
closeModal,
|
|
56
|
+
t
|
|
44
57
|
]);
|
|
45
58
|
const handleApplyClick = useCallback(()=>{
|
|
46
59
|
applyFromText(textValue);
|
|
@@ -56,26 +69,30 @@ const ThemePresetImportField = ({ field, path })=>{
|
|
|
56
69
|
setTextValue(content);
|
|
57
70
|
applyFromText(content);
|
|
58
71
|
} catch (error) {
|
|
59
|
-
toast.error(
|
|
72
|
+
toast.error(format(t.readError, {
|
|
73
|
+
message: error.message
|
|
74
|
+
}));
|
|
60
75
|
} finally{
|
|
61
76
|
if (event.target) {
|
|
62
77
|
event.target.value = '';
|
|
63
78
|
}
|
|
64
79
|
}
|
|
65
80
|
}, [
|
|
66
|
-
applyFromText
|
|
81
|
+
applyFromText,
|
|
82
|
+
t
|
|
67
83
|
]);
|
|
68
84
|
const handleImportClick = useCallback(()=>{
|
|
69
85
|
fileInputRef.current?.click();
|
|
70
86
|
}, []);
|
|
71
87
|
const handleClear = useCallback(()=>{
|
|
72
88
|
setValue(null);
|
|
73
|
-
toast.success(
|
|
89
|
+
toast.success(t.cleared);
|
|
74
90
|
}, [
|
|
75
|
-
setValue
|
|
91
|
+
setValue,
|
|
92
|
+
t
|
|
76
93
|
]);
|
|
77
|
-
const label =
|
|
78
|
-
const description =
|
|
94
|
+
const label = resolveLocalizedText(field.label, language, t.drawerTitle);
|
|
95
|
+
const description = resolveLocalizedText(field.admin?.description, language);
|
|
79
96
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
80
97
|
children: [
|
|
81
98
|
/*#__PURE__*/ _jsxs("div", {
|
|
@@ -92,7 +109,7 @@ const ThemePresetImportField = ({ field, path })=>{
|
|
|
92
109
|
gap: '0.25rem'
|
|
93
110
|
},
|
|
94
111
|
children: [
|
|
95
|
-
/*#__PURE__*/ _jsx("
|
|
112
|
+
/*#__PURE__*/ _jsx("div", {
|
|
96
113
|
style: {
|
|
97
114
|
fontWeight: 600
|
|
98
115
|
},
|
|
@@ -101,7 +118,7 @@ const ThemePresetImportField = ({ field, path })=>{
|
|
|
101
118
|
description && /*#__PURE__*/ _jsx("p", {
|
|
102
119
|
style: {
|
|
103
120
|
margin: 0,
|
|
104
|
-
color: 'var(--theme-elevation-
|
|
121
|
+
color: 'var(--theme-elevation-600)',
|
|
105
122
|
fontSize: '0.9rem'
|
|
106
123
|
},
|
|
107
124
|
children: description
|
|
@@ -120,15 +137,16 @@ const ThemePresetImportField = ({ field, path })=>{
|
|
|
120
137
|
buttonStyle: "secondary",
|
|
121
138
|
onClick: ()=>openModal(drawerSlug),
|
|
122
139
|
size: "small",
|
|
123
|
-
children:
|
|
140
|
+
children: t.importButton
|
|
124
141
|
}),
|
|
125
142
|
value && value.length > 0 && /*#__PURE__*/ _jsx(Button, {
|
|
126
143
|
buttonStyle: "secondary",
|
|
127
144
|
onClick: handleClear,
|
|
128
145
|
size: "small",
|
|
129
|
-
children:
|
|
146
|
+
children: t.clearButton
|
|
130
147
|
}),
|
|
131
148
|
/*#__PURE__*/ _jsx("span", {
|
|
149
|
+
role: "status",
|
|
132
150
|
style: {
|
|
133
151
|
fontSize: '0.875rem',
|
|
134
152
|
color: 'var(--theme-elevation-600)'
|
|
@@ -141,7 +159,7 @@ const ThemePresetImportField = ({ field, path })=>{
|
|
|
141
159
|
}),
|
|
142
160
|
/*#__PURE__*/ _jsx(Drawer, {
|
|
143
161
|
slug: drawerSlug,
|
|
144
|
-
title:
|
|
162
|
+
title: t.drawerTitle,
|
|
145
163
|
children: /*#__PURE__*/ _jsxs("div", {
|
|
146
164
|
style: {
|
|
147
165
|
padding: '1.5rem',
|
|
@@ -154,9 +172,18 @@ const ThemePresetImportField = ({ field, path })=>{
|
|
|
154
172
|
margin: 0,
|
|
155
173
|
color: 'var(--theme-elevation-600)'
|
|
156
174
|
},
|
|
157
|
-
children:
|
|
175
|
+
children: t.drawerIntro
|
|
176
|
+
}),
|
|
177
|
+
/*#__PURE__*/ _jsx("label", {
|
|
178
|
+
htmlFor: textareaId,
|
|
179
|
+
style: {
|
|
180
|
+
fontWeight: 600,
|
|
181
|
+
fontSize: '0.875rem'
|
|
182
|
+
},
|
|
183
|
+
children: t.textareaLabel
|
|
158
184
|
}),
|
|
159
185
|
/*#__PURE__*/ _jsx("textarea", {
|
|
186
|
+
id: textareaId,
|
|
160
187
|
value: textValue,
|
|
161
188
|
onChange: (event)=>setTextValue(event.target.value),
|
|
162
189
|
rows: 15,
|
|
@@ -167,7 +194,9 @@ const ThemePresetImportField = ({ field, path })=>{
|
|
|
167
194
|
borderRadius: '8px',
|
|
168
195
|
border: '1px solid var(--theme-elevation-200)',
|
|
169
196
|
padding: '12px',
|
|
170
|
-
fontSize: '0.875rem'
|
|
197
|
+
fontSize: '0.875rem',
|
|
198
|
+
background: 'var(--theme-input-bg, var(--theme-elevation-0))',
|
|
199
|
+
color: 'var(--theme-text)'
|
|
171
200
|
},
|
|
172
201
|
placeholder: `[
|
|
173
202
|
{
|
|
@@ -189,19 +218,21 @@ const ThemePresetImportField = ({ field, path })=>{
|
|
|
189
218
|
/*#__PURE__*/ _jsx(Button, {
|
|
190
219
|
buttonStyle: "secondary",
|
|
191
220
|
onClick: handleImportClick,
|
|
192
|
-
children:
|
|
221
|
+
children: t.uploadButton
|
|
193
222
|
}),
|
|
194
223
|
/*#__PURE__*/ _jsx(Button, {
|
|
195
224
|
buttonStyle: "primary",
|
|
196
225
|
onClick: handleApplyClick,
|
|
197
226
|
disabled: !textValue.trim(),
|
|
198
|
-
children:
|
|
227
|
+
children: t.applyButton
|
|
199
228
|
})
|
|
200
229
|
]
|
|
201
230
|
}),
|
|
202
231
|
/*#__PURE__*/ _jsx("input", {
|
|
203
232
|
type: "file",
|
|
204
|
-
accept: "application/json",
|
|
233
|
+
accept: "application/json,.json",
|
|
234
|
+
"aria-hidden": "true",
|
|
235
|
+
tabIndex: -1,
|
|
205
236
|
ref: fileInputRef,
|
|
206
237
|
onChange: handleFileChange,
|
|
207
238
|
style: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/fields/ThemePresetImportField.tsx"],"sourcesContent":["'use client'\n\nimport { Button, Drawer, useField, useModal } from '@payloadcms/ui'\nimport type { JSONFieldClientProps } from 'payload'\nimport { toast } from 'sonner'\nimport { useCallback, useMemo, useRef, useState, type ChangeEvent } from 'react'\nimport type { ThemePreset } from '../presets.js'\nimport { parseThemePresetInput } from '../utils/customThemePresets.js'\n\nconst baseClass = 'theme-preset-import-field'\nconst drawerSlug = 'import-theme-presets'\n\nconst ThemePresetImportField = ({ field, path }: JSONFieldClientProps) => {\n const { value, setValue } = useField<ThemePreset[] | null>({ path })\n const [textValue, setTextValue] = useState('')\n const { closeModal, openModal } = useModal()\n const fileInputRef = useRef<HTMLInputElement | null>(null)\n\n const presetSummary = useMemo(() => {\n if (!value || value.length === 0) {\n return 'No custom presets imported'\n }\n const names = value.map((preset) => preset.name).join(', ')\n return `${value.length} preset${value.length === 1 ? '' : 's'}: ${names}`\n }, [value])\n\n const applyFromText = useCallback(\n (raw: string) => {\n if (!raw.trim()) {\n setValue(null)\n closeModal(drawerSlug)\n toast.success('Cleared custom presets')\n return\n }\n\n const presets = parseThemePresetInput(raw)\n if (presets.length === 0) {\n toast.error('No valid presets found. Ensure each entry has a name and label.')\n return\n }\n\n setValue(presets)\n closeModal(drawerSlug)\n toast.success(`Imported ${presets.length} preset${presets.length === 1 ? '' : 's'}`)\n setTextValue('')\n },\n [setValue, closeModal],\n )\n\n const handleApplyClick = useCallback(() => {\n applyFromText(textValue)\n }, [applyFromText, textValue])\n\n const handleFileChange = useCallback(\n async (event: ChangeEvent<HTMLInputElement>) => {\n const file = event.target?.files?.[0]\n if (!file) return\n try {\n const content = await file.text()\n setTextValue(content)\n applyFromText(content)\n } catch (error) {\n toast.error((error as Error).message)\n } finally {\n if (event.target) {\n event.target.value = ''\n }\n }\n },\n [applyFromText],\n )\n\n const handleImportClick = useCallback(() => {\n fileInputRef.current?.click()\n }, [])\n\n const handleClear = useCallback(() => {\n setValue(null)\n toast.success('Cleared custom presets')\n }, [setValue])\n\n const label =\n typeof field.label === 'string' ? field.label : (field.label?.en ?? 'Custom Presets')\n const description =\n typeof field.admin?.description === 'string'\n ? field.admin.description\n : (field.admin?.description as Record<string, string>)?.en\n\n return (\n <>\n <div className={baseClass} style={{ display: 'grid', gap: '0.75rem' }}>\n <div style={{ display: 'flex', flexDirection: 'column', gap: '0.25rem' }}>\n <label style={{ fontWeight: 600 }}>{label}</label>\n {description && (\n <p style={{ margin: 0, color: 'var(--theme-elevation-500)', fontSize: '0.9rem' }}>\n {description}\n </p>\n )}\n </div>\n\n <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', flexWrap: 'wrap' }}>\n <Button buttonStyle=\"secondary\" onClick={() => openModal(drawerSlug)} size=\"small\">\n Import Presets\n </Button>\n {value && value.length > 0 && (\n <Button buttonStyle=\"secondary\" onClick={handleClear} size=\"small\">\n Clear\n </Button>\n )}\n <span style={{ fontSize: '0.875rem', color: 'var(--theme-elevation-600)' }}>\n {presetSummary}\n </span>\n </div>\n </div>\n\n <Drawer slug={drawerSlug} title=\"Import Theme Presets\">\n <div style={{ padding: '1.5rem', display: 'grid', gap: '1rem' }}>\n <p style={{ margin: 0, color: 'var(--theme-elevation-600)' }}>\n Paste JSON or upload a .json file containing theme preset definitions.\n </p>\n\n <textarea\n value={textValue}\n onChange={(event) => setTextValue(event.target.value)}\n rows={15}\n spellCheck={false}\n style={{\n width: '100%',\n fontFamily: 'monospace',\n borderRadius: '8px',\n border: '1px solid var(--theme-elevation-200)',\n padding: '12px',\n fontSize: '0.875rem',\n }}\n placeholder={`[\n {\n \"name\": \"brand-dark\",\n \"label\": \"Brand Dark\",\n \"borderRadius\": \"medium\",\n \"lightMode\": { \"background\": \"#ffffff\", \"foreground\": \"#111\" },\n \"darkMode\": { \"background\": \"#0a0a0a\", \"foreground\": \"#fff\" }\n }\n]`}\n />\n\n <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>\n <Button buttonStyle=\"secondary\" onClick={handleImportClick}>\n Upload JSON File\n </Button>\n <Button buttonStyle=\"primary\" onClick={handleApplyClick} disabled={!textValue.trim()}>\n Apply\n </Button>\n </div>\n\n <input\n type=\"file\"\n accept=\"application/json\"\n ref={fileInputRef}\n onChange={handleFileChange}\n style={{ display: 'none' }}\n />\n </div>\n </Drawer>\n </>\n )\n}\n\nexport default ThemePresetImportField\n"],"names":["Button","Drawer","useField","useModal","toast","useCallback","useMemo","useRef","useState","parseThemePresetInput","baseClass","drawerSlug","ThemePresetImportField","field","path","value","setValue","textValue","setTextValue","closeModal","openModal","fileInputRef","presetSummary","length","names","map","preset","name","join","applyFromText","raw","trim","success","presets","error","handleApplyClick","handleFileChange","event","file","target","files","content","text","message","handleImportClick","current","click","handleClear","label","en","description","admin","div","className","style","display","gap","flexDirection","fontWeight","p","margin","color","fontSize","alignItems","flexWrap","buttonStyle","onClick","size","span","slug","title","padding","textarea","onChange","rows","spellCheck","width","fontFamily","borderRadius","border","placeholder","justifyContent","disabled","input","type","accept","ref"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/fields/ThemePresetImportField.tsx"],"sourcesContent":["'use client'\n\nimport { Button, Drawer, useField, useModal } from '@payloadcms/ui'\nimport type { JSONFieldClientProps } from 'payload'\nimport { toast } from 'sonner'\nimport { useCallback, useId, useMemo, useRef, useState, type ChangeEvent } from 'react'\nimport { useThemeLanguage, useThemeTranslations } from '../hooks/useThemeTranslations.js'\nimport type { ThemePreset } from '../presets.js'\nimport { parseThemePresetInput } from '../utils/customThemePresets.js'\nimport { resolveLocalizedText } from '../utils/localizedText.js'\n\nconst format = (template: string, values: Record<string, string | number>) =>\n template.replace(/\\{(\\w+)\\}/g, (match, key: string) => String(values[key] ?? match))\n\nconst baseClass = 'theme-preset-import-field'\nconst drawerSlug = 'import-theme-presets'\n\nconst ThemePresetImportField = ({ field, path }: JSONFieldClientProps) => {\n const { value, setValue } = useField<ThemePreset[] | null>({ path })\n const [textValue, setTextValue] = useState('')\n const { closeModal, openModal } = useModal()\n const fileInputRef = useRef<HTMLInputElement | null>(null)\n const t = useThemeTranslations().presetImport\n const language = useThemeLanguage()\n const textareaId = useId()\n\n const presetSummary = useMemo(() => {\n if (!value || value.length === 0) {\n return t.none\n }\n const names = value.map((preset) => preset.label || preset.name).join(', ')\n return format(t.summary, { count: value.length, names })\n }, [value, t])\n\n const applyFromText = useCallback(\n (raw: string) => {\n if (!raw.trim()) {\n setValue(null)\n closeModal(drawerSlug)\n toast.success(t.cleared)\n return\n }\n\n const presets = parseThemePresetInput(raw)\n if (presets.length === 0) {\n toast.error(t.invalid)\n return\n }\n\n setValue(presets)\n closeModal(drawerSlug)\n toast.success(format(t.imported, { count: presets.length }))\n setTextValue('')\n },\n [setValue, closeModal, t],\n )\n\n const handleApplyClick = useCallback(() => {\n applyFromText(textValue)\n }, [applyFromText, textValue])\n\n const handleFileChange = useCallback(\n async (event: ChangeEvent<HTMLInputElement>) => {\n const file = event.target?.files?.[0]\n if (!file) return\n try {\n const content = await file.text()\n setTextValue(content)\n applyFromText(content)\n } catch (error) {\n toast.error(format(t.readError, { message: (error as Error).message }))\n } finally {\n if (event.target) {\n event.target.value = ''\n }\n }\n },\n [applyFromText, t],\n )\n\n const handleImportClick = useCallback(() => {\n fileInputRef.current?.click()\n }, [])\n\n const handleClear = useCallback(() => {\n setValue(null)\n toast.success(t.cleared)\n }, [setValue, t])\n\n const label = resolveLocalizedText(field.label, language, t.drawerTitle)\n const description = resolveLocalizedText(field.admin?.description, language)\n\n return (\n <>\n <div className={baseClass} style={{ display: 'grid', gap: '0.75rem' }}>\n <div style={{ display: 'flex', flexDirection: 'column', gap: '0.25rem' }}>\n <div style={{ fontWeight: 600 }}>{label}</div>\n {description && (\n <p style={{ margin: 0, color: 'var(--theme-elevation-600)', fontSize: '0.9rem' }}>\n {description}\n </p>\n )}\n </div>\n\n <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', flexWrap: 'wrap' }}>\n <Button buttonStyle=\"secondary\" onClick={() => openModal(drawerSlug)} size=\"small\">\n {t.importButton}\n </Button>\n {value && value.length > 0 && (\n <Button buttonStyle=\"secondary\" onClick={handleClear} size=\"small\">\n {t.clearButton}\n </Button>\n )}\n <span\n role=\"status\"\n style={{ fontSize: '0.875rem', color: 'var(--theme-elevation-600)' }}\n >\n {presetSummary}\n </span>\n </div>\n </div>\n\n <Drawer slug={drawerSlug} title={t.drawerTitle}>\n <div style={{ padding: '1.5rem', display: 'grid', gap: '1rem' }}>\n <p style={{ margin: 0, color: 'var(--theme-elevation-600)' }}>{t.drawerIntro}</p>\n\n <label htmlFor={textareaId} style={{ fontWeight: 600, fontSize: '0.875rem' }}>\n {t.textareaLabel}\n </label>\n <textarea\n id={textareaId}\n value={textValue}\n onChange={(event) => setTextValue(event.target.value)}\n rows={15}\n spellCheck={false}\n style={{\n width: '100%',\n fontFamily: 'monospace',\n borderRadius: '8px',\n border: '1px solid var(--theme-elevation-200)',\n padding: '12px',\n fontSize: '0.875rem',\n background: 'var(--theme-input-bg, var(--theme-elevation-0))',\n color: 'var(--theme-text)',\n }}\n placeholder={`[\n {\n \"name\": \"brand-dark\",\n \"label\": \"Brand Dark\",\n \"borderRadius\": \"medium\",\n \"lightMode\": { \"background\": \"#ffffff\", \"foreground\": \"#111\" },\n \"darkMode\": { \"background\": \"#0a0a0a\", \"foreground\": \"#fff\" }\n }\n]`}\n />\n\n <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>\n <Button buttonStyle=\"secondary\" onClick={handleImportClick}>\n {t.uploadButton}\n </Button>\n <Button buttonStyle=\"primary\" onClick={handleApplyClick} disabled={!textValue.trim()}>\n {t.applyButton}\n </Button>\n </div>\n\n <input\n type=\"file\"\n accept=\"application/json,.json\"\n aria-hidden=\"true\"\n tabIndex={-1}\n ref={fileInputRef}\n onChange={handleFileChange}\n style={{ display: 'none' }}\n />\n </div>\n </Drawer>\n </>\n )\n}\n\nexport default ThemePresetImportField\n"],"names":["Button","Drawer","useField","useModal","toast","useCallback","useId","useMemo","useRef","useState","useThemeLanguage","useThemeTranslations","parseThemePresetInput","resolveLocalizedText","format","template","values","replace","match","key","String","baseClass","drawerSlug","ThemePresetImportField","field","path","value","setValue","textValue","setTextValue","closeModal","openModal","fileInputRef","t","presetImport","language","textareaId","presetSummary","length","none","names","map","preset","label","name","join","summary","count","applyFromText","raw","trim","success","cleared","presets","error","invalid","imported","handleApplyClick","handleFileChange","event","file","target","files","content","text","readError","message","handleImportClick","current","click","handleClear","drawerTitle","description","admin","div","className","style","display","gap","flexDirection","fontWeight","p","margin","color","fontSize","alignItems","flexWrap","buttonStyle","onClick","size","importButton","clearButton","span","role","slug","title","padding","drawerIntro","htmlFor","textareaLabel","textarea","id","onChange","rows","spellCheck","width","fontFamily","borderRadius","border","background","placeholder","justifyContent","uploadButton","disabled","applyButton","input","type","accept","aria-hidden","tabIndex","ref"],"mappings":"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"}
|