@kilivi/payloadcms-theme-management 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +318 -847
  2. package/dist/components/AppearanceSessionControls.d.ts +15 -0
  3. package/dist/components/AppearanceSessionControls.d.ts.map +1 -0
  4. package/dist/components/AppearanceSessionControls.js +152 -0
  5. package/dist/components/AppearanceSessionControls.js.map +1 -0
  6. package/dist/components/ColorModeToggle.css +42 -0
  7. package/dist/components/ColorModeToggle.d.ts +40 -0
  8. package/dist/components/ColorModeToggle.d.ts.map +1 -0
  9. package/dist/components/ColorModeToggle.js +111 -0
  10. package/dist/components/ColorModeToggle.js.map +1 -0
  11. package/dist/components/ServerThemeInjector.d.ts.map +1 -1
  12. package/dist/components/ServerThemeInjector.js +41 -14
  13. package/dist/components/ServerThemeInjector.js.map +1 -1
  14. package/dist/components/ThemeLivePreviewSync.d.ts +1 -0
  15. package/dist/components/ThemeLivePreviewSync.d.ts.map +1 -1
  16. package/dist/components/ThemeLivePreviewSync.js +1 -1
  17. package/dist/components/ThemeLivePreviewSync.js.map +1 -1
  18. package/dist/components/ThemeSwitcher.d.ts +36 -0
  19. package/dist/components/ThemeSwitcher.d.ts.map +1 -0
  20. package/dist/components/ThemeSwitcher.js +89 -0
  21. package/dist/components/ThemeSwitcher.js.map +1 -0
  22. package/dist/components/typographyPreviewUtils.d.ts.map +1 -1
  23. package/dist/components/typographyPreviewUtils.js +8 -1
  24. package/dist/components/typographyPreviewUtils.js.map +1 -1
  25. package/dist/constants/devicePreviews.d.ts +35 -0
  26. package/dist/constants/devicePreviews.d.ts.map +1 -0
  27. package/dist/constants/devicePreviews.js +43 -0
  28. package/dist/constants/devicePreviews.js.map +1 -0
  29. package/dist/constants/themeFonts.js +9 -9
  30. package/dist/constants/themeFonts.js.map +1 -1
  31. package/dist/extended-presets.d.ts +0 -4
  32. package/dist/extended-presets.d.ts.map +1 -1
  33. package/dist/extended-presets.js +1 -18
  34. package/dist/extended-presets.js.map +1 -1
  35. package/dist/extended-themes.d.ts.map +1 -1
  36. package/dist/extended-themes.js +428 -0
  37. package/dist/extended-themes.js.map +1 -1
  38. package/dist/fields/AccessibilityAuditField.d.ts +2 -0
  39. package/dist/fields/AccessibilityAuditField.d.ts.map +1 -0
  40. package/dist/fields/AccessibilityAuditField.js +303 -0
  41. package/dist/fields/AccessibilityAuditField.js.map +1 -0
  42. package/dist/fields/AppearancePreviewField.d.ts +2 -0
  43. package/dist/fields/AppearancePreviewField.d.ts.map +1 -0
  44. package/dist/fields/AppearancePreviewField.js +763 -0
  45. package/dist/fields/AppearancePreviewField.js.map +1 -0
  46. package/dist/fields/ColorPickerField.d.ts.map +1 -1
  47. package/dist/fields/ColorPickerField.js +89 -85
  48. package/dist/fields/ColorPickerField.js.map +1 -1
  49. package/dist/fields/FontSelectField.d.ts +6 -0
  50. package/dist/fields/FontSelectField.d.ts.map +1 -1
  51. package/dist/fields/FontSelectField.js +195 -122
  52. package/dist/fields/FontSelectField.js.map +1 -1
  53. package/dist/fields/PaletteGeneratorField.d.ts +7 -0
  54. package/dist/fields/PaletteGeneratorField.d.ts.map +1 -0
  55. package/dist/fields/PaletteGeneratorField.js +327 -0
  56. package/dist/fields/PaletteGeneratorField.js.map +1 -0
  57. package/dist/fields/StylePresetField.d.ts +3 -0
  58. package/dist/fields/StylePresetField.d.ts.map +1 -0
  59. package/dist/fields/StylePresetField.js +462 -0
  60. package/dist/fields/StylePresetField.js.map +1 -0
  61. package/dist/fields/ThemeColorPickerField.css +115 -35
  62. package/dist/fields/ThemeColorPickerField.d.ts.map +1 -1
  63. package/dist/fields/ThemeColorPickerField.js +204 -70
  64. package/dist/fields/ThemeColorPickerField.js.map +1 -1
  65. package/dist/fields/ThemeExportField.d.ts +6 -0
  66. package/dist/fields/ThemeExportField.d.ts.map +1 -0
  67. package/dist/fields/ThemeExportField.js +202 -0
  68. package/dist/fields/ThemeExportField.js.map +1 -0
  69. package/dist/fields/ThemePresetImportField.d.ts.map +1 -1
  70. package/dist/fields/ThemePresetImportField.js +56 -25
  71. package/dist/fields/ThemePresetImportField.js.map +1 -1
  72. package/dist/fields/ThemePreviewField.css +382 -5
  73. package/dist/fields/ThemePreviewField.d.ts.map +1 -1
  74. package/dist/fields/ThemePreviewField.js +268 -170
  75. package/dist/fields/ThemePreviewField.js.map +1 -1
  76. package/dist/fields/ThemeTokenSelectField.d.ts.map +1 -1
  77. package/dist/fields/ThemeTokenSelectField.js +278 -80
  78. package/dist/fields/ThemeTokenSelectField.js.map +1 -1
  79. package/dist/fields/colorFieldHelpers.d.ts +90 -0
  80. package/dist/fields/colorFieldHelpers.d.ts.map +1 -0
  81. package/dist/fields/colorFieldHelpers.js +133 -0
  82. package/dist/fields/colorFieldHelpers.js.map +1 -0
  83. package/dist/fields/colorModeFields.d.ts +1 -1
  84. package/dist/fields/colorModeFields.d.ts.map +1 -1
  85. package/dist/fields/colorModeFields.js +49 -7
  86. package/dist/fields/colorModeFields.js.map +1 -1
  87. package/dist/fields/themeConfigurationField.d.ts +8 -1
  88. package/dist/fields/themeConfigurationField.d.ts.map +1 -1
  89. package/dist/fields/themeConfigurationField.js +961 -29
  90. package/dist/fields/themeConfigurationField.js.map +1 -1
  91. package/dist/globals/ThemeSettings/FontPicker/FontPreview.d.ts.map +1 -1
  92. package/dist/globals/ThemeSettings/FontPicker/FontPreview.js +8 -4
  93. package/dist/globals/ThemeSettings/FontPicker/FontPreview.js.map +1 -1
  94. package/dist/globals/ThemeSettings/FontPicker/index.d.ts.map +1 -1
  95. package/dist/globals/ThemeSettings/FontPicker/index.js +81 -26
  96. package/dist/globals/ThemeSettings/FontPicker/index.js.map +1 -1
  97. package/dist/globals/ThemeSettings/FontPicker/styles.css +98 -41
  98. package/dist/globals/ThemeSettings/FontPicker/translations.d.ts +6 -0
  99. package/dist/globals/ThemeSettings/FontPicker/translations.d.ts.map +1 -1
  100. package/dist/globals/ThemeSettings/FontPicker/translations.js +14 -2
  101. package/dist/globals/ThemeSettings/FontPicker/translations.js.map +1 -1
  102. package/dist/globals/ThemeSettings/config.d.ts.map +1 -1
  103. package/dist/globals/ThemeSettings/config.js +76 -79
  104. package/dist/globals/ThemeSettings/config.js.map +1 -1
  105. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.d.ts.map +1 -1
  106. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js +63 -62
  107. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js.map +1 -1
  108. package/dist/globals/ThemeSettings/fields/TypographyInfoField.d.ts.map +1 -1
  109. package/dist/globals/ThemeSettings/fields/TypographyInfoField.js +22 -25
  110. package/dist/globals/ThemeSettings/fields/TypographyInfoField.js.map +1 -1
  111. package/dist/hooks/useAppearanceSession.d.ts +50 -0
  112. package/dist/hooks/useAppearanceSession.d.ts.map +1 -0
  113. package/dist/hooks/useAppearanceSession.js +91 -0
  114. package/dist/hooks/useAppearanceSession.js.map +1 -0
  115. package/dist/hooks/useThemeTranslations.d.ts +13 -0
  116. package/dist/hooks/useThemeTranslations.d.ts.map +1 -0
  117. package/dist/hooks/useThemeTranslations.js +22 -0
  118. package/dist/hooks/useThemeTranslations.js.map +1 -0
  119. package/dist/i18n.d.ts +53 -0
  120. package/dist/i18n.d.ts.map +1 -0
  121. package/dist/i18n.js +53 -0
  122. package/dist/i18n.js.map +1 -0
  123. package/dist/index.d.ts +10 -4
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +50 -29
  126. package/dist/index.js.map +1 -1
  127. package/dist/payload-types.d.ts +24 -0
  128. package/dist/payload-types.d.ts.map +1 -1
  129. package/dist/payload-types.js.map +1 -1
  130. package/dist/presets.d.ts +8 -0
  131. package/dist/presets.d.ts.map +1 -1
  132. package/dist/presets.js.map +1 -1
  133. package/dist/providers/Theme/index.d.ts.map +1 -1
  134. package/dist/providers/Theme/index.js +75 -11
  135. package/dist/providers/Theme/index.js.map +1 -1
  136. package/dist/providers/Theme/themeConfig.d.ts.map +1 -1
  137. package/dist/providers/Theme/themeConfig.js +104 -0
  138. package/dist/providers/Theme/themeConfig.js.map +1 -1
  139. package/dist/providers/Theme/types.d.ts +1 -1
  140. package/dist/providers/Theme/types.d.ts.map +1 -1
  141. package/dist/providers/Theme/types.js +10 -1
  142. package/dist/providers/Theme/types.js.map +1 -1
  143. package/dist/style-presets.d.ts +66 -0
  144. package/dist/style-presets.d.ts.map +1 -0
  145. package/dist/style-presets.js +556 -0
  146. package/dist/style-presets.js.map +1 -0
  147. package/dist/translations.d.ts +159 -1
  148. package/dist/translations.d.ts.map +1 -1
  149. package/dist/translations.js +310 -9
  150. package/dist/translations.js.map +1 -1
  151. package/dist/tweakcn-special-themes.d.ts.map +1 -1
  152. package/dist/tweakcn-special-themes.js +585 -9
  153. package/dist/tweakcn-special-themes.js.map +1 -1
  154. package/dist/types/css.d.js +3 -0
  155. package/dist/types/css.d.js.map +1 -0
  156. package/dist/types.d.ts +26 -0
  157. package/dist/types.d.ts.map +1 -1
  158. package/dist/types.js +1 -1
  159. package/dist/types.js.map +1 -1
  160. package/dist/utilities/getThemeSettings.d.ts.map +1 -1
  161. package/dist/utilities/getThemeSettings.js +2 -14
  162. package/dist/utilities/getThemeSettings.js.map +1 -1
  163. package/dist/utils/contrast.d.ts +63 -0
  164. package/dist/utils/contrast.d.ts.map +1 -0
  165. package/dist/utils/contrast.js +158 -0
  166. package/dist/utils/contrast.js.map +1 -0
  167. package/dist/utils/exportTokens.d.ts +30 -0
  168. package/dist/utils/exportTokens.d.ts.map +1 -0
  169. package/dist/utils/exportTokens.js +183 -0
  170. package/dist/utils/exportTokens.js.map +1 -0
  171. package/dist/utils/generatePalette.d.ts +46 -0
  172. package/dist/utils/generatePalette.d.ts.map +1 -0
  173. package/dist/utils/generatePalette.js +127 -0
  174. package/dist/utils/generatePalette.js.map +1 -0
  175. package/dist/utils/generateThemeCSS.d.ts.map +1 -1
  176. package/dist/utils/generateThemeCSS.js +2 -40
  177. package/dist/utils/generateThemeCSS.js.map +1 -1
  178. package/dist/utils/livePreviewThemeRuntime.d.ts +16 -2
  179. package/dist/utils/livePreviewThemeRuntime.d.ts.map +1 -1
  180. package/dist/utils/livePreviewThemeRuntime.js +37 -14
  181. package/dist/utils/livePreviewThemeRuntime.js.map +1 -1
  182. package/dist/utils/localizedText.d.ts +8 -0
  183. package/dist/utils/localizedText.d.ts.map +1 -0
  184. package/dist/utils/localizedText.js +21 -0
  185. package/dist/utils/localizedText.js.map +1 -0
  186. package/dist/utils/resolveThemeConfiguration.d.ts +45 -1
  187. package/dist/utils/resolveThemeConfiguration.d.ts.map +1 -1
  188. package/dist/utils/resolveThemeConfiguration.js +12 -5
  189. package/dist/utils/resolveThemeConfiguration.js.map +1 -1
  190. package/dist/utils/themeColors.d.ts +3 -0
  191. package/dist/utils/themeColors.d.ts.map +1 -1
  192. package/dist/utils/themeColors.js +30 -1
  193. package/dist/utils/themeColors.js.map +1 -1
  194. package/dist/utils/themeHtmlAttributes.d.ts +32 -12
  195. package/dist/utils/themeHtmlAttributes.d.ts.map +1 -1
  196. package/dist/utils/themeHtmlAttributes.js +38 -5
  197. package/dist/utils/themeHtmlAttributes.js.map +1 -1
  198. package/dist/utils/themeUtils.d.ts +3 -5
  199. package/dist/utils/themeUtils.d.ts.map +1 -1
  200. package/dist/utils/themeUtils.js +416 -6
  201. package/dist/utils/themeUtils.js.map +1 -1
  202. package/dist/utils/tweakcnConverter.d.ts +1 -3
  203. package/dist/utils/tweakcnConverter.d.ts.map +1 -1
  204. package/dist/utils/tweakcnConverter.js +1 -3
  205. package/dist/utils/tweakcnConverter.js.map +1 -1
  206. package/package.json +11 -5
  207. package/dist/components/ThemePreview.d.ts +0 -3
  208. package/dist/components/ThemePreview.d.ts.map +0 -1
  209. package/dist/components/ThemePreview.js +0 -321
  210. package/dist/components/ThemePreview.js.map +0 -1
  211. package/dist/plugin.d.ts +0 -2
  212. package/dist/plugin.d.ts.map +0 -1
  213. package/dist/plugin.js +0 -3
  214. package/dist/plugin.js.map +0 -1
@@ -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":"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"}
@@ -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;AASnD,QAAA,MAAM,sBAAsB,GAAI,iBAAiB,oBAAoB,gCAyJpE,CAAA;AAED,eAAe,sBAAsB,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 'No custom presets imported';
24
+ return t.none;
19
25
  }
20
- const names = value.map((preset)=>preset.name).join(', ');
21
- return `${value.length} preset${value.length === 1 ? '' : 's'}: ${names}`;
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('Cleared custom presets');
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('No valid presets found. Ensure each entry has a name and label.');
44
+ toast.error(t.invalid);
35
45
  return;
36
46
  }
37
47
  setValue(presets);
38
48
  closeModal(drawerSlug);
39
- toast.success(`Imported ${presets.length} preset${presets.length === 1 ? '' : 's'}`);
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(error.message);
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('Cleared custom presets');
89
+ toast.success(t.cleared);
74
90
  }, [
75
- setValue
91
+ setValue,
92
+ t
76
93
  ]);
77
- const label = typeof field.label === 'string' ? field.label : field.label?.en ?? 'Custom Presets';
78
- const description = typeof field.admin?.description === 'string' ? field.admin.description : field.admin?.description?.en;
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("label", {
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-500)',
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: "Import Presets"
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: "Clear"
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: "Import Theme Presets",
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: "Paste JSON or upload a .json file containing theme preset definitions."
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: "Upload JSON File"
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: "Apply"
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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"}