@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.
Files changed (235) hide show
  1. package/README.md +346 -845
  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 +13 -1
  12. package/dist/components/ServerThemeInjector.d.ts.map +1 -1
  13. package/dist/components/ServerThemeInjector.js +65 -15
  14. package/dist/components/ServerThemeInjector.js.map +1 -1
  15. package/dist/components/ThemeLivePreviewSync.d.ts +4 -0
  16. package/dist/components/ThemeLivePreviewSync.d.ts.map +1 -1
  17. package/dist/components/ThemeLivePreviewSync.js +28 -4
  18. package/dist/components/ThemeLivePreviewSync.js.map +1 -1
  19. package/dist/components/ThemeSwitcher.d.ts +36 -0
  20. package/dist/components/ThemeSwitcher.d.ts.map +1 -0
  21. package/dist/components/ThemeSwitcher.js +89 -0
  22. package/dist/components/ThemeSwitcher.js.map +1 -0
  23. package/dist/components/typographyPreviewUtils.d.ts +5 -0
  24. package/dist/components/typographyPreviewUtils.d.ts.map +1 -1
  25. package/dist/components/typographyPreviewUtils.js +24 -11
  26. package/dist/components/typographyPreviewUtils.js.map +1 -1
  27. package/dist/constants/devicePreviews.d.ts +35 -0
  28. package/dist/constants/devicePreviews.d.ts.map +1 -0
  29. package/dist/constants/devicePreviews.js +43 -0
  30. package/dist/constants/devicePreviews.js.map +1 -0
  31. package/dist/constants/themeFonts.d.ts +94 -168
  32. package/dist/constants/themeFonts.d.ts.map +1 -1
  33. package/dist/constants/themeFonts.js +260 -140
  34. package/dist/constants/themeFonts.js.map +1 -1
  35. package/dist/endpoints/themeFonts.d.ts +5 -0
  36. package/dist/endpoints/themeFonts.d.ts.map +1 -0
  37. package/dist/endpoints/themeFonts.js +138 -0
  38. package/dist/endpoints/themeFonts.js.map +1 -0
  39. package/dist/extended-presets.d.ts +0 -4
  40. package/dist/extended-presets.d.ts.map +1 -1
  41. package/dist/extended-presets.js +1 -18
  42. package/dist/extended-presets.js.map +1 -1
  43. package/dist/extended-themes.d.ts.map +1 -1
  44. package/dist/extended-themes.js +428 -0
  45. package/dist/extended-themes.js.map +1 -1
  46. package/dist/fields/AccessibilityAuditField.d.ts +2 -0
  47. package/dist/fields/AccessibilityAuditField.d.ts.map +1 -0
  48. package/dist/fields/AccessibilityAuditField.js +303 -0
  49. package/dist/fields/AccessibilityAuditField.js.map +1 -0
  50. package/dist/fields/AppearancePreviewField.d.ts +2 -0
  51. package/dist/fields/AppearancePreviewField.d.ts.map +1 -0
  52. package/dist/fields/AppearancePreviewField.js +826 -0
  53. package/dist/fields/AppearancePreviewField.js.map +1 -0
  54. package/dist/fields/ColorPickerField.d.ts.map +1 -1
  55. package/dist/fields/ColorPickerField.js +89 -85
  56. package/dist/fields/ColorPickerField.js.map +1 -1
  57. package/dist/fields/FontSelectField.css +155 -0
  58. package/dist/fields/FontSelectField.d.ts +8 -0
  59. package/dist/fields/FontSelectField.d.ts.map +1 -1
  60. package/dist/fields/FontSelectField.js +223 -188
  61. package/dist/fields/FontSelectField.js.map +1 -1
  62. package/dist/fields/PaletteGeneratorField.d.ts +7 -0
  63. package/dist/fields/PaletteGeneratorField.d.ts.map +1 -0
  64. package/dist/fields/PaletteGeneratorField.js +327 -0
  65. package/dist/fields/PaletteGeneratorField.js.map +1 -0
  66. package/dist/fields/StylePresetField.d.ts +3 -0
  67. package/dist/fields/StylePresetField.d.ts.map +1 -0
  68. package/dist/fields/StylePresetField.js +463 -0
  69. package/dist/fields/StylePresetField.js.map +1 -0
  70. package/dist/fields/ThemeColorPickerField.css +115 -35
  71. package/dist/fields/ThemeColorPickerField.d.ts.map +1 -1
  72. package/dist/fields/ThemeColorPickerField.js +204 -70
  73. package/dist/fields/ThemeColorPickerField.js.map +1 -1
  74. package/dist/fields/ThemeExportField.d.ts +6 -0
  75. package/dist/fields/ThemeExportField.d.ts.map +1 -0
  76. package/dist/fields/ThemeExportField.js +202 -0
  77. package/dist/fields/ThemeExportField.js.map +1 -0
  78. package/dist/fields/ThemePresetImportField.d.ts.map +1 -1
  79. package/dist/fields/ThemePresetImportField.js +56 -25
  80. package/dist/fields/ThemePresetImportField.js.map +1 -1
  81. package/dist/fields/ThemePreviewField.css +444 -5
  82. package/dist/fields/ThemePreviewField.d.ts.map +1 -1
  83. package/dist/fields/ThemePreviewField.js +265 -170
  84. package/dist/fields/ThemePreviewField.js.map +1 -1
  85. package/dist/fields/ThemeTokenSelectField.d.ts.map +1 -1
  86. package/dist/fields/ThemeTokenSelectField.js +278 -80
  87. package/dist/fields/ThemeTokenSelectField.js.map +1 -1
  88. package/dist/fields/colorFieldHelpers.d.ts +90 -0
  89. package/dist/fields/colorFieldHelpers.d.ts.map +1 -0
  90. package/dist/fields/colorFieldHelpers.js +133 -0
  91. package/dist/fields/colorFieldHelpers.js.map +1 -0
  92. package/dist/fields/colorModeFields.d.ts +1 -1
  93. package/dist/fields/colorModeFields.d.ts.map +1 -1
  94. package/dist/fields/colorModeFields.js +49 -7
  95. package/dist/fields/colorModeFields.js.map +1 -1
  96. package/dist/fields/themeConfigurationField.d.ts +8 -1
  97. package/dist/fields/themeConfigurationField.d.ts.map +1 -1
  98. package/dist/fields/themeConfigurationField.js +1209 -228
  99. package/dist/fields/themeConfigurationField.js.map +1 -1
  100. package/dist/globals/ThemeSettings/FontPicker/FontPreview.d.ts.map +1 -1
  101. package/dist/globals/ThemeSettings/FontPicker/FontPreview.js +8 -4
  102. package/dist/globals/ThemeSettings/FontPicker/FontPreview.js.map +1 -1
  103. package/dist/globals/ThemeSettings/FontPicker/index.d.ts.map +1 -1
  104. package/dist/globals/ThemeSettings/FontPicker/index.js +81 -26
  105. package/dist/globals/ThemeSettings/FontPicker/index.js.map +1 -1
  106. package/dist/globals/ThemeSettings/FontPicker/styles.css +98 -41
  107. package/dist/globals/ThemeSettings/FontPicker/translations.d.ts +6 -0
  108. package/dist/globals/ThemeSettings/FontPicker/translations.d.ts.map +1 -1
  109. package/dist/globals/ThemeSettings/FontPicker/translations.js +14 -2
  110. package/dist/globals/ThemeSettings/FontPicker/translations.js.map +1 -1
  111. package/dist/globals/ThemeSettings/config.d.ts.map +1 -1
  112. package/dist/globals/ThemeSettings/config.js +76 -79
  113. package/dist/globals/ThemeSettings/config.js.map +1 -1
  114. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.d.ts.map +1 -1
  115. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js +63 -62
  116. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js.map +1 -1
  117. package/dist/globals/ThemeSettings/fields/TypographyInfoField.d.ts.map +1 -1
  118. package/dist/globals/ThemeSettings/fields/TypographyInfoField.js +22 -25
  119. package/dist/globals/ThemeSettings/fields/TypographyInfoField.js.map +1 -1
  120. package/dist/hooks/useAppearanceSession.d.ts +50 -0
  121. package/dist/hooks/useAppearanceSession.d.ts.map +1 -0
  122. package/dist/hooks/useAppearanceSession.js +91 -0
  123. package/dist/hooks/useAppearanceSession.js.map +1 -0
  124. package/dist/hooks/useNativeLivePreview.d.ts +15 -0
  125. package/dist/hooks/useNativeLivePreview.d.ts.map +1 -0
  126. package/dist/hooks/useNativeLivePreview.js +18 -0
  127. package/dist/hooks/useNativeLivePreview.js.map +1 -0
  128. package/dist/hooks/useThemeFonts.d.ts +10 -0
  129. package/dist/hooks/useThemeFonts.d.ts.map +1 -0
  130. package/dist/hooks/useThemeFonts.js +36 -0
  131. package/dist/hooks/useThemeFonts.js.map +1 -0
  132. package/dist/hooks/useThemeTranslations.d.ts +13 -0
  133. package/dist/hooks/useThemeTranslations.d.ts.map +1 -0
  134. package/dist/hooks/useThemeTranslations.js +22 -0
  135. package/dist/hooks/useThemeTranslations.js.map +1 -0
  136. package/dist/i18n.d.ts +53 -0
  137. package/dist/i18n.d.ts.map +1 -0
  138. package/dist/i18n.js +53 -0
  139. package/dist/i18n.js.map +1 -0
  140. package/dist/index.d.ts +13 -4
  141. package/dist/index.d.ts.map +1 -1
  142. package/dist/index.js +59 -29
  143. package/dist/index.js.map +1 -1
  144. package/dist/next-fonts.d.ts +3 -0
  145. package/dist/next-fonts.d.ts.map +1 -0
  146. package/dist/next-fonts.js +360 -0
  147. package/dist/next-fonts.js.map +1 -0
  148. package/dist/payload-types.d.ts +34 -6
  149. package/dist/payload-types.d.ts.map +1 -1
  150. package/dist/payload-types.js.map +1 -1
  151. package/dist/presets.d.ts +14 -0
  152. package/dist/presets.d.ts.map +1 -1
  153. package/dist/presets.js.map +1 -1
  154. package/dist/providers/Theme/index.d.ts.map +1 -1
  155. package/dist/providers/Theme/index.js +75 -11
  156. package/dist/providers/Theme/index.js.map +1 -1
  157. package/dist/providers/Theme/themeConfig.d.ts.map +1 -1
  158. package/dist/providers/Theme/themeConfig.js +104 -0
  159. package/dist/providers/Theme/themeConfig.js.map +1 -1
  160. package/dist/providers/Theme/types.d.ts +1 -1
  161. package/dist/providers/Theme/types.d.ts.map +1 -1
  162. package/dist/providers/Theme/types.js +10 -1
  163. package/dist/providers/Theme/types.js.map +1 -1
  164. package/dist/style-presets.d.ts +75 -0
  165. package/dist/style-presets.d.ts.map +1 -0
  166. package/dist/style-presets.js +738 -0
  167. package/dist/style-presets.js.map +1 -0
  168. package/dist/translations.d.ts +164 -1
  169. package/dist/translations.d.ts.map +1 -1
  170. package/dist/translations.js +320 -9
  171. package/dist/translations.js.map +1 -1
  172. package/dist/tweakcn-special-themes.d.ts.map +1 -1
  173. package/dist/tweakcn-special-themes.js +585 -9
  174. package/dist/tweakcn-special-themes.js.map +1 -1
  175. package/dist/types/css.d.js +3 -0
  176. package/dist/types/css.d.js.map +1 -0
  177. package/dist/types.d.ts +26 -0
  178. package/dist/types.d.ts.map +1 -1
  179. package/dist/types.js +1 -1
  180. package/dist/types.js.map +1 -1
  181. package/dist/utilities/getThemeSettings.d.ts.map +1 -1
  182. package/dist/utilities/getThemeSettings.js +2 -14
  183. package/dist/utilities/getThemeSettings.js.map +1 -1
  184. package/dist/utils/contrast.d.ts +63 -0
  185. package/dist/utils/contrast.d.ts.map +1 -0
  186. package/dist/utils/contrast.js +158 -0
  187. package/dist/utils/contrast.js.map +1 -0
  188. package/dist/utils/exportTokens.d.ts +30 -0
  189. package/dist/utils/exportTokens.d.ts.map +1 -0
  190. package/dist/utils/exportTokens.js +183 -0
  191. package/dist/utils/exportTokens.js.map +1 -0
  192. package/dist/utils/generatePalette.d.ts +46 -0
  193. package/dist/utils/generatePalette.d.ts.map +1 -0
  194. package/dist/utils/generatePalette.js +127 -0
  195. package/dist/utils/generatePalette.js.map +1 -0
  196. package/dist/utils/generateThemeCSS.d.ts.map +1 -1
  197. package/dist/utils/generateThemeCSS.js +2 -40
  198. package/dist/utils/generateThemeCSS.js.map +1 -1
  199. package/dist/utils/livePreviewThemeRuntime.d.ts +22 -3
  200. package/dist/utils/livePreviewThemeRuntime.d.ts.map +1 -1
  201. package/dist/utils/livePreviewThemeRuntime.js +48 -15
  202. package/dist/utils/livePreviewThemeRuntime.js.map +1 -1
  203. package/dist/utils/localizedText.d.ts +8 -0
  204. package/dist/utils/localizedText.d.ts.map +1 -0
  205. package/dist/utils/localizedText.js +21 -0
  206. package/dist/utils/localizedText.js.map +1 -0
  207. package/dist/utils/resolveThemeConfiguration.d.ts +60 -1
  208. package/dist/utils/resolveThemeConfiguration.d.ts.map +1 -1
  209. package/dist/utils/resolveThemeConfiguration.js +53 -13
  210. package/dist/utils/resolveThemeConfiguration.js.map +1 -1
  211. package/dist/utils/themeColors.d.ts +3 -0
  212. package/dist/utils/themeColors.d.ts.map +1 -1
  213. package/dist/utils/themeColors.js +30 -1
  214. package/dist/utils/themeColors.js.map +1 -1
  215. package/dist/utils/themeHtmlAttributes.d.ts +32 -12
  216. package/dist/utils/themeHtmlAttributes.d.ts.map +1 -1
  217. package/dist/utils/themeHtmlAttributes.js +38 -5
  218. package/dist/utils/themeHtmlAttributes.js.map +1 -1
  219. package/dist/utils/themeUtils.d.ts +3 -5
  220. package/dist/utils/themeUtils.d.ts.map +1 -1
  221. package/dist/utils/themeUtils.js +474 -6
  222. package/dist/utils/themeUtils.js.map +1 -1
  223. package/dist/utils/tweakcnConverter.d.ts +1 -3
  224. package/dist/utils/tweakcnConverter.d.ts.map +1 -1
  225. package/dist/utils/tweakcnConverter.js +1 -3
  226. package/dist/utils/tweakcnConverter.js.map +1 -1
  227. package/package.json +15 -5
  228. package/dist/components/ThemePreview.d.ts +0 -3
  229. package/dist/components/ThemePreview.d.ts.map +0 -1
  230. package/dist/components/ThemePreview.js +0 -321
  231. package/dist/components/ThemePreview.js.map +0 -1
  232. package/dist/plugin.d.ts +0 -2
  233. package/dist/plugin.d.ts.map +0 -1
  234. package/dist/plugin.js +0 -3
  235. 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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"}