@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/FontSelectField.tsx"],"sourcesContent":["'use client'\n\nimport { useField } from '@payloadcms/ui'\nimport type { SelectFieldClientComponent } from 'payload'\nimport { useEffect, useState } from 'react'\nimport { getTranslations } from '../translations.js'\nimport { getAdminLanguage } from '../utils/getAdminLanguage.js'\n\ntype LocalizedLabel = Record<string, string>\n\ninterface FontOption {\n label: string | LocalizedLabel\n value: string\n fontFamily?: string\n category?: 'sans-serif' | 'serif' | 'monospace' | 'display'\n}\n\nconst FontSelectField: SelectFieldClientComponent = ({ field, path }) => {\n const { value, setValue } = useField<string>({ path })\n const [isOpen, setIsOpen] = useState(false)\n\n const options = (field.options as FontOption[]) || []\n const selectedOption = options.find((opt) => opt.value === value)\n const [loadedFonts, setLoadedFonts] = useState<Set<string>>(new Set())\n\n const getOptionLabel = (opt: FontOption) => {\n if (typeof opt.label === 'string') return opt.label\n const lang = getAdminLanguage()\n return opt.label[lang] || opt.label.en || opt.label.cs || 'Font'\n }\n\n const ensureFontLoaded = (fontValue: string | undefined) => {\n if (!fontValue || ['preset', 'custom', 'system-ui'].includes(fontValue)) return\n // Avoid duplicate loads\n if (loadedFonts.has(fontValue)) return\n\n if (typeof document === 'undefined') return\n\n const linkId = `font-${fontValue.replace(/\\s+/g, '-')}`\n if (document.getElementById(linkId)) {\n setLoadedFonts((prev) => new Set(prev).add(fontValue))\n return\n }\n\n const weights = '400;700' // reasonable default weights\n const fontUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(\n fontValue,\n )}:wght@${weights}&display=swap`\n const link = document.createElement('link')\n link.id = linkId\n link.rel = 'stylesheet'\n link.href = fontUrl\n link.onload = () => setLoadedFonts((prev) => new Set(prev).add(fontValue))\n document.head.appendChild(link)\n }\n\n const handleSelect = (optionValue: string) => {\n setValue(optionValue)\n setIsOpen(false)\n }\n\n useEffect(() => {\n const handleClickOutside = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n if (!target.closest('.font-select-container')) {\n setIsOpen(false)\n }\n }\n\n if (isOpen) {\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }\n }, [isOpen])\n\n const t = getTranslations(getAdminLanguage())\n const label =\n typeof field.label === 'string'\n ? field.label\n : typeof field.label === 'object'\n ? field.label?.en || field.label?.cs\n : field.label || 'Font'\n\n return (\n <div className=\"font-select-container\" style={{ position: 'relative', marginBottom: '12px' }}>\n <label\n style={{\n display: 'block',\n marginBottom: '6px',\n fontSize: '13px',\n fontWeight: 600,\n color: 'var(--theme-elevation-800)',\n }}\n >\n {label}\n </label>\n\n <button\n type=\"button\"\n onClick={() => setIsOpen(!isOpen)}\n style={{\n width: '300px',\n padding: '10px 14px',\n borderRadius: '6px',\n border: '1px solid var(--theme-elevation-200)',\n backgroundColor: 'var(--theme-input-bg)',\n color: 'var(--theme-elevation-800)',\n textAlign: 'left',\n cursor: 'pointer',\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n fontSize: '14px',\n fontFamily: selectedOption?.fontFamily || 'inherit',\n transition: 'border-color 0.2s ease',\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.borderColor = 'var(--theme-elevation-400)'\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.borderColor = 'var(--theme-elevation-200)'\n }}\n >\n <span style={{ flex: 1 }}>\n {selectedOption ? getOptionLabel(selectedOption) : t.ui.selectFontPlaceholder}\n </span>{' '}\n <svg\n width=\"12\"\n height=\"12\"\n viewBox=\"0 0 12 12\"\n fill=\"none\"\n style={{\n transition: 'transform 0.2s ease',\n transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)',\n }}\n >\n <path\n d=\"M3 4.5L6 7.5L9 4.5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n\n {isOpen && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 0,\n right: 0,\n marginTop: '4px',\n maxHeight: '280px',\n overflowY: 'auto',\n backgroundColor: 'var(--theme-elevation-0)',\n border: '1px solid var(--theme-elevation-200)',\n borderRadius: '8px',\n boxShadow: '0 8px 24px rgba(15, 23, 42, 0.15)',\n zIndex: 1000,\n }}\n >\n {options.map((option) => {\n const isSelected = option.value === value\n\n let fontFamily = option.fontFamily\n if (!fontFamily) {\n if (option.category === 'sans-serif') fontFamily = 'system-ui, sans-serif'\n else if (option.category === 'serif') fontFamily = 'Georgia, serif'\n else if (option.category === 'monospace') fontFamily = 'monospace'\n else fontFamily = 'inherit'\n }\n\n return (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => handleSelect(option.value)}\n style={{\n width: '250px',\n padding: '12px 14px',\n border: 'none',\n backgroundColor: isSelected ? 'var(--theme-elevation-100)' : 'transparent',\n color: 'var(--theme-elevation-800)',\n textAlign: 'left',\n cursor: 'pointer',\n fontSize: '14px',\n fontFamily,\n display: 'flex',\n flexDirection: 'column',\n gap: '4px',\n transition: 'background-color 0.15s ease',\n }}\n onMouseEnter={(e) => {\n if (!isSelected) {\n e.currentTarget.style.backgroundColor = 'var(--theme-elevation-50)'\n }\n // Lazy-load a preview font when hovering\n ensureFontLoaded(option.value)\n }}\n onMouseLeave={(e) => {\n if (!isSelected) {\n e.currentTarget.style.backgroundColor = 'transparent'\n }\n }}\n >\n <div\n style={{\n fontWeight: isSelected ? 600 : 500,\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n }}\n >\n {isSelected && (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\">\n <path\n d=\"M11.6667 3.5L5.25 9.91667L2.33334 7\"\n stroke=\"var(--theme-elevation-500)\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n )}\n {getOptionLabel(option)}\n </div>\n <div\n style={{\n fontSize: '16px',\n color: 'var(--theme-elevation-600)',\n fontStyle:\n option.value === 'preset' || option.value === 'custom' ? 'italic' : 'normal',\n fontFamily: option.fontFamily || undefined,\n }}\n >\n {(() => {\n if (option.value === 'preset') return t.ui.usePreset\n if (option.value === 'custom') return t.ui.specifyCustom\n return t.ui.sampleSentence\n })()}\n </div>\n </button>\n )\n })}\n </div>\n )}\n </div>\n )\n}\n\nexport default FontSelectField\n"],"names":["useField","useEffect","useState","getTranslations","getAdminLanguage","FontSelectField","field","path","value","setValue","isOpen","setIsOpen","options","selectedOption","find","opt","loadedFonts","setLoadedFonts","Set","getOptionLabel","label","lang","en","cs","ensureFontLoaded","fontValue","includes","has","document","linkId","replace","getElementById","prev","add","weights","fontUrl","encodeURIComponent","link","createElement","id","rel","href","onload","head","appendChild","handleSelect","optionValue","handleClickOutside","e","target","closest","addEventListener","removeEventListener","t","div","className","style","position","marginBottom","display","fontSize","fontWeight","color","button","type","onClick","width","padding","borderRadius","border","backgroundColor","textAlign","cursor","justifyContent","alignItems","fontFamily","transition","onMouseEnter","currentTarget","borderColor","onMouseLeave","span","flex","ui","selectFontPlaceholder","svg","height","viewBox","fill","transform","d","stroke","strokeWidth","strokeLinecap","strokeLinejoin","top","left","right","marginTop","maxHeight","overflowY","boxShadow","zIndex","map","option","isSelected","category","flexDirection","gap","fontStyle","undefined","usePreset","specifyCustom","sampleSentence"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/fields/FontSelectField.tsx"],"sourcesContent":["'use client'\n\nimport './FontSelectField.css'\nimport { useField } from '@payloadcms/ui'\nimport { mergeFieldStyles } from '@payloadcms/ui/shared'\nimport { Check, ChevronDown } from 'lucide-react'\nimport type { SelectFieldClientComponent } from 'payload'\nimport React, { useCallback, useEffect, useId, useRef, useState, type KeyboardEvent } from 'react'\nimport {\n FONT_CATEGORY_LABELS,\n getFontCatalogEntry,\n type FontCategory,\n} from '../constants/themeFonts.js'\nimport { useThemeFonts } from '../hooks/useThemeFonts.js'\nimport { useThemeLanguage, useThemeTranslations } from '../hooks/useThemeTranslations.js'\nimport { resolveLocalizedText } from '../utils/localizedText.js'\n\ntype LocalizedLabel = Record<string, string>\n\ninterface FontOption {\n label: string | LocalizedLabel\n value: string\n fontFamily?: string\n category?: FontCategory\n}\n\nfunction previewFamily(option: FontOption): string {\n if (option.fontFamily) return option.fontFamily\n if (option.category === 'sans-serif') return 'system-ui, sans-serif'\n if (option.category === 'serif') return 'Georgia, serif'\n if (option.category === 'monospace') return 'monospace'\n return 'inherit'\n}\n\n/**\n * Font select with live font previews. Implements the WAI-ARIA \"select-only\n * combobox\" pattern: a button trigger plus a listbox navigated with the arrow\n * keys, Home/End, Enter/Space and Escape. Options are grouped by font category.\n * Preview fonts come from the plugin's self-hosted font endpoint: the selected\n * face always, every face while the list is open.\n */\nconst FontSelectField: SelectFieldClientComponent = ({ field, path }) => {\n const { value, setValue } = useField<string>({ path })\n const [isOpen, setIsOpen] = useState(false)\n const [activeIndex, setActiveIndex] = useState(-1)\n const lang = useThemeLanguage()\n const t = useThemeTranslations()\n const baseId = useId()\n const labelId = `${baseId}-label`\n const listboxId = `${baseId}-listbox`\n const containerRef = useRef<HTMLDivElement>(null)\n const triggerRef = useRef<HTMLButtonElement>(null)\n const listboxRef = useRef<HTMLDivElement>(null)\n\n // Payload strips custom option props on the way to the client, so the face and\n // category come from the font catalog by value.\n const options = ((field.options as FontOption[]) || []).map((option) => {\n const entry = getFontCatalogEntry(option.value)\n return entry ? { ...option, fontFamily: entry.fontFamily, category: entry.category } : option\n })\n const [alignEnd, setAlignEnd] = useState(false)\n const selectedIndex = options.findIndex((opt) => opt.value === value)\n const selectedOption = selectedIndex >= 0 ? options[selectedIndex] : undefined\n\n const getOptionLabel = (opt: FontOption) => resolveLocalizedText(opt.label, lang, 'Font')\n const label = resolveLocalizedText(field.label, lang, 'Font')\n const optionId = (index: number) => `${baseId}-option-${index}`\n\n useThemeFonts(isOpen ? options.map((option) => option.value) : [value])\n\n const open = useCallback(\n (index = selectedIndex >= 0 ? selectedIndex : 0) => {\n // Open towards the side with room, so the list never spills past the form column.\n const trigger = triggerRef.current?.getBoundingClientRect()\n const column = containerRef.current\n ?.closest('.render-fields, .group-field__wrap')\n ?.getBoundingClientRect()\n setAlignEnd(Boolean(trigger && column && trigger.left + 300 > column.right))\n setActiveIndex(index)\n setIsOpen(true)\n },\n [selectedIndex],\n )\n\n const close = useCallback((restoreFocus: boolean) => {\n setIsOpen(false)\n if (restoreFocus) triggerRef.current?.focus()\n }, [])\n\n const handleSelect = (index: number) => {\n const option = options[index]\n if (!option) return\n setValue(option.value)\n close(true)\n }\n\n // Keep the active option scrolled into view.\n useEffect(() => {\n if (!isOpen || activeIndex < 0) return\n listboxRef.current\n ?.querySelector<HTMLElement>(`#${CSS.escape(optionId(activeIndex))}`)\n ?.scrollIntoView({ block: 'nearest' })\n }, [isOpen, activeIndex])\n\n // Focus the listbox when it opens so keyboard navigation works immediately.\n useEffect(() => {\n if (isOpen) listboxRef.current?.focus()\n }, [isOpen])\n\n useEffect(() => {\n if (!isOpen) return\n const handlePointerOutside = (e: PointerEvent) => {\n if (!containerRef.current?.contains(e.target as Node)) close(false)\n }\n document.addEventListener('pointerdown', handlePointerOutside)\n return () => document.removeEventListener('pointerdown', handlePointerOutside)\n }, [isOpen, close])\n\n const handleTriggerKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {\n if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(event.key)) {\n event.preventDefault()\n open(event.key === 'ArrowUp' && selectedIndex < 0 ? options.length - 1 : undefined)\n }\n }\n\n const handleListboxKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n const last = options.length - 1\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault()\n setActiveIndex((i) => Math.min(last, i + 1))\n break\n case 'ArrowUp':\n event.preventDefault()\n setActiveIndex((i) => Math.max(0, i - 1))\n break\n case 'Home':\n event.preventDefault()\n setActiveIndex(0)\n break\n case 'End':\n event.preventDefault()\n setActiveIndex(last)\n break\n case 'Enter':\n case ' ':\n event.preventDefault()\n handleSelect(activeIndex)\n break\n case 'Escape':\n event.preventDefault()\n event.stopPropagation()\n close(true)\n break\n case 'Tab':\n close(false)\n break\n default:\n // Type-ahead: jump to the first option starting with the typed letter.\n if (event.key.length === 1 && /\\S/.test(event.key)) {\n const letter = event.key.toLowerCase()\n const start = activeIndex + 1\n const ordered = [...options.slice(start), ...options.slice(0, start)]\n const match = ordered.find((opt) => getOptionLabel(opt).toLowerCase().startsWith(letter))\n if (match) setActiveIndex(options.indexOf(match))\n }\n }\n }\n\n const isSpecial = (option: FontOption) => option.value === 'preset' || option.value === 'custom'\n\n return (\n <div\n ref={containerRef}\n // Payload rows size each field through --field-width on the field root.\n className=\"field-type tm-font-select\"\n style={mergeFieldStyles(field) as React.CSSProperties}\n >\n <div id={labelId} className=\"field-label tm-font-select__label\">\n {label}\n </div>\n\n <button\n ref={triggerRef}\n type=\"button\"\n className=\"tm-font-select__trigger\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? listboxId : undefined}\n aria-labelledby={`${labelId} ${baseId}-value`}\n onClick={() => (isOpen ? close(false) : open())}\n onKeyDown={handleTriggerKeyDown}\n >\n <span\n id={`${baseId}-value`}\n className=\"tm-font-select__value\"\n style={{\n fontFamily:\n selectedOption && !isSpecial(selectedOption) ? previewFamily(selectedOption) : undefined,\n }}\n >\n {selectedOption ? getOptionLabel(selectedOption) : t.ui.selectFontPlaceholder}\n </span>\n <ChevronDown\n size={16}\n aria-hidden\n className={`tm-font-select__chevron${isOpen ? ' is-open' : ''}`}\n />\n </button>\n\n {isOpen && (\n <div\n ref={listboxRef}\n id={listboxId}\n role=\"listbox\"\n tabIndex={-1}\n aria-labelledby={labelId}\n aria-activedescendant={activeIndex >= 0 ? optionId(activeIndex) : undefined}\n onKeyDown={handleListboxKeyDown}\n className={`tm-font-select__listbox${alignEnd ? ' is-align-end' : ''}`}\n >\n {options.map((option, index) => {\n const isSelected = option.value === value\n const isActive = index === activeIndex\n const startsGroup =\n option.category !== undefined && option.category !== options[index - 1]?.category\n const special = isSpecial(option)\n const family = special ? undefined : previewFamily(option)\n\n return (\n <React.Fragment key={option.value}>\n {startsGroup && option.category ? (\n <div role=\"presentation\" className=\"tm-font-select__group\">\n {resolveLocalizedText(FONT_CATEGORY_LABELS[option.category], lang, '')}\n </div>\n ) : null}\n <div\n id={optionId(index)}\n role=\"option\"\n aria-selected={isSelected}\n onClick={() => handleSelect(index)}\n onPointerMove={() => setActiveIndex(index)}\n className={`tm-font-select__option${isActive ? ' is-active' : ''}${\n isSelected ? ' is-selected' : ''\n }`}\n >\n <span className=\"tm-font-select__check\" aria-hidden>\n {isSelected ? <Check size={14} /> : null}\n </span>\n <span className=\"tm-font-select__text\">\n <span className=\"tm-font-select__name\" style={{ fontFamily: family }}>\n {getOptionLabel(option)}\n </span>\n <span\n aria-hidden\n className={`tm-font-select__sample${special ? ' is-hint' : ''}`}\n style={{ fontFamily: family }}\n >\n {option.value === 'preset'\n ? t.ui.usePreset\n : option.value === 'custom'\n ? t.ui.specifyCustom\n : t.ui.sampleSentence}\n </span>\n </span>\n </div>\n </React.Fragment>\n )\n })}\n </div>\n )}\n </div>\n )\n}\n\nexport default FontSelectField\n"],"names":["useField","mergeFieldStyles","Check","ChevronDown","React","useCallback","useEffect","useId","useRef","useState","FONT_CATEGORY_LABELS","getFontCatalogEntry","useThemeFonts","useThemeLanguage","useThemeTranslations","resolveLocalizedText","previewFamily","option","fontFamily","category","FontSelectField","field","path","value","setValue","isOpen","setIsOpen","activeIndex","setActiveIndex","lang","t","baseId","labelId","listboxId","containerRef","triggerRef","listboxRef","options","map","entry","alignEnd","setAlignEnd","selectedIndex","findIndex","opt","selectedOption","undefined","getOptionLabel","label","optionId","index","open","trigger","current","getBoundingClientRect","column","closest","Boolean","left","right","close","restoreFocus","focus","handleSelect","querySelector","CSS","escape","scrollIntoView","block","handlePointerOutside","e","contains","target","document","addEventListener","removeEventListener","handleTriggerKeyDown","event","includes","key","preventDefault","length","handleListboxKeyDown","last","i","Math","min","max","stopPropagation","test","letter","toLowerCase","start","ordered","slice","match","find","startsWith","indexOf","isSpecial","div","ref","className","style","id","button","type","role","aria-haspopup","aria-expanded","aria-controls","aria-labelledby","onClick","onKeyDown","span","ui","selectFontPlaceholder","size","aria-hidden","tabIndex","aria-activedescendant","isSelected","isActive","startsGroup","special","family","Fragment","aria-selected","onPointerMove","usePreset","specifyCustom","sampleSentence"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Admin tool that generates a full light + dark palette from a single brand
3
+ * colour (or a colour extracted from an uploaded logo/image) and writes it
4
+ * straight into the colour-mode fields.
5
+ */
6
+ export default function PaletteGeneratorField(): import("react").JSX.Element;
7
+ //# sourceMappingURL=PaletteGeneratorField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PaletteGeneratorField.d.ts","sourceRoot":"","sources":["../../src/fields/PaletteGeneratorField.tsx"],"names":[],"mappings":"AAkBA;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,qBAAqB,gCA2P5C"}
@@ -0,0 +1,327 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useForm, useFormFields } from '@payloadcms/ui';
4
+ import { ImageUp, Sparkles, Wand2 } from 'lucide-react';
5
+ import { useCallback, useEffect, useId, useRef, useState } from 'react';
6
+ import { HexColorInput, HexColorPicker } from 'react-colorful';
7
+ import { createBulkWriter, getAppearanceSession, useAppearanceSession } from '../hooks/useAppearanceSession.js';
8
+ import { useThemeTranslations } from '../hooks/useThemeTranslations.js';
9
+ import { extractDominantColors, generatePaletteFromColor } from '../utils/generatePalette.js';
10
+ /**
11
+ * Admin tool that generates a full light + dark palette from a single brand
12
+ * colour (or a colour extracted from an uploaded logo/image) and writes it
13
+ * straight into the colour-mode fields.
14
+ */ export default function PaletteGeneratorField() {
15
+ const formFields = useFormFields(([state])=>state);
16
+ const { dispatchFields, setModified } = useForm();
17
+ const t = useThemeTranslations().paletteGenerator;
18
+ const fileInputRef = useRef(null);
19
+ const currentPrimary = (()=>{
20
+ const v = formFields?.['themeConfiguration.lightMode.primary']?.value;
21
+ return typeof v === 'string' && v.trim() ? v : '#3b82f6';
22
+ })();
23
+ const [seed, setSeed] = useState(currentPrimary);
24
+ const [swatches, setSwatches] = useState([]);
25
+ const [open, setOpen] = useState(false);
26
+ const [imageError, setImageError] = useState(false);
27
+ const seedTouchedRef = useRef(false);
28
+ // Follow the form's primary colour (e.g. after a preset is applied) until the
29
+ // editor picks a seed of their own.
30
+ useEffect(()=>{
31
+ if (!seedTouchedRef.current) setSeed(currentPrimary);
32
+ }, [
33
+ currentPrimary
34
+ ]);
35
+ const chooseSeed = useCallback((color)=>{
36
+ seedTouchedRef.current = true;
37
+ setSeed(color);
38
+ }, []);
39
+ const { locks } = useAppearanceSession();
40
+ const appearanceT = useThemeTranslations().appearance;
41
+ const colorsLocked = locks.colors;
42
+ const lockNoteId = useId();
43
+ const applyPalette = useCallback(()=>{
44
+ if (getAppearanceSession().locks.colors) return;
45
+ const palette = generatePaletteFromColor(seed);
46
+ const writer = createBulkWriter(formFields, dispatchFields);
47
+ [
48
+ 'lightMode',
49
+ 'darkMode'
50
+ ].forEach((mode)=>{
51
+ const colors = palette[mode];
52
+ Object.keys(colors).forEach((key)=>{
53
+ writer.write(`themeConfiguration.${mode}.${key}`, colors[key]);
54
+ });
55
+ });
56
+ // `dispatchFields` alone does not flip the form's dirty flag, so Payload
57
+ // would keep the Save button disabled after generating a palette.
58
+ setModified(true);
59
+ writer.commit('palette', `${appearanceT.generatedPalette} ${seed}`);
60
+ }, [
61
+ seed,
62
+ formFields,
63
+ dispatchFields,
64
+ setModified,
65
+ appearanceT.generatedPalette
66
+ ]);
67
+ const handleFile = useCallback((event)=>{
68
+ const file = event.target.files?.[0];
69
+ if (!file) return;
70
+ setImageError(false);
71
+ const url = URL.createObjectURL(file);
72
+ const img = new Image();
73
+ img.onload = ()=>{
74
+ const colors = extractDominantColors(img, 6);
75
+ setSwatches(colors);
76
+ if (colors[0]) chooseSeed(colors[0]);
77
+ URL.revokeObjectURL(url);
78
+ };
79
+ img.onerror = ()=>{
80
+ setImageError(true);
81
+ URL.revokeObjectURL(url);
82
+ };
83
+ img.src = url;
84
+ // Allow re-selecting the same file after an error.
85
+ event.target.value = '';
86
+ }, [
87
+ chooseSeed
88
+ ]);
89
+ return /*#__PURE__*/ _jsxs("div", {
90
+ style: {
91
+ padding: '16px',
92
+ borderRadius: '12px',
93
+ border: '1px solid var(--theme-elevation-200)',
94
+ backgroundColor: 'var(--theme-elevation-25)'
95
+ },
96
+ children: [
97
+ /*#__PURE__*/ _jsxs("div", {
98
+ style: {
99
+ display: 'inline-flex',
100
+ alignItems: 'center',
101
+ gap: '6px',
102
+ fontSize: '13px',
103
+ fontWeight: 600,
104
+ color: 'var(--theme-elevation-800)'
105
+ },
106
+ children: [
107
+ /*#__PURE__*/ _jsx(Sparkles, {
108
+ size: 14,
109
+ "aria-hidden": true
110
+ }),
111
+ t.title
112
+ ]
113
+ }),
114
+ /*#__PURE__*/ _jsx("div", {
115
+ style: {
116
+ fontSize: '12px',
117
+ color: 'var(--theme-elevation-600)',
118
+ marginBottom: '12px'
119
+ },
120
+ children: t.subtitle
121
+ }),
122
+ /*#__PURE__*/ _jsxs("div", {
123
+ style: {
124
+ display: 'flex',
125
+ flexWrap: 'wrap',
126
+ alignItems: 'flex-start',
127
+ gap: '16px'
128
+ },
129
+ children: [
130
+ /*#__PURE__*/ _jsxs("div", {
131
+ style: {
132
+ display: 'grid',
133
+ gap: '8px'
134
+ },
135
+ children: [
136
+ /*#__PURE__*/ _jsxs("button", {
137
+ type: "button",
138
+ onClick: ()=>setOpen((o)=>!o),
139
+ "aria-label": `${t.seedLabel}: ${seed}`,
140
+ "aria-expanded": open,
141
+ style: {
142
+ display: 'flex',
143
+ alignItems: 'center',
144
+ gap: '8px',
145
+ padding: '4px 8px 4px 4px',
146
+ borderRadius: '8px',
147
+ border: '1px solid var(--theme-elevation-200)',
148
+ background: 'var(--theme-elevation-50)',
149
+ cursor: 'pointer'
150
+ },
151
+ children: [
152
+ /*#__PURE__*/ _jsx("span", {
153
+ style: {
154
+ width: '28px',
155
+ height: '28px',
156
+ borderRadius: '6px',
157
+ background: seed,
158
+ border: '1px solid rgba(0,0,0,0.1)'
159
+ }
160
+ }),
161
+ /*#__PURE__*/ _jsx("span", {
162
+ style: {
163
+ fontFamily: 'monospace',
164
+ fontSize: '12px',
165
+ color: 'var(--theme-elevation-700)'
166
+ },
167
+ children: seed
168
+ })
169
+ ]
170
+ }),
171
+ open ? /*#__PURE__*/ _jsxs("div", {
172
+ style: {
173
+ display: 'grid',
174
+ gap: '8px'
175
+ },
176
+ children: [
177
+ /*#__PURE__*/ _jsx(HexColorPicker, {
178
+ color: seed,
179
+ onChange: chooseSeed
180
+ }),
181
+ /*#__PURE__*/ _jsx(HexColorInput, {
182
+ color: seed,
183
+ onChange: chooseSeed,
184
+ prefixed: true,
185
+ style: {
186
+ width: '160px',
187
+ padding: '6px 8px',
188
+ borderRadius: '6px',
189
+ border: '1px solid var(--theme-elevation-200)',
190
+ fontFamily: 'monospace',
191
+ fontSize: '12px'
192
+ }
193
+ })
194
+ ]
195
+ }) : null
196
+ ]
197
+ }),
198
+ /*#__PURE__*/ _jsxs("div", {
199
+ style: {
200
+ display: 'grid',
201
+ gap: '8px'
202
+ },
203
+ children: [
204
+ /*#__PURE__*/ _jsxs("button", {
205
+ type: "button",
206
+ onClick: applyPalette,
207
+ disabled: colorsLocked,
208
+ "aria-describedby": colorsLocked ? lockNoteId : undefined,
209
+ style: {
210
+ display: 'inline-flex',
211
+ alignItems: 'center',
212
+ gap: '6px',
213
+ padding: '8px 14px',
214
+ borderRadius: '8px',
215
+ border: 'none',
216
+ cursor: colorsLocked ? 'not-allowed' : 'pointer',
217
+ background: 'var(--theme-elevation-800)',
218
+ color: 'var(--theme-elevation-0)',
219
+ opacity: colorsLocked ? 0.5 : 1,
220
+ fontWeight: 600,
221
+ fontSize: '12px'
222
+ },
223
+ children: [
224
+ /*#__PURE__*/ _jsx(Wand2, {
225
+ size: 14,
226
+ "aria-hidden": true
227
+ }),
228
+ t.generate
229
+ ]
230
+ }),
231
+ /*#__PURE__*/ _jsxs("button", {
232
+ type: "button",
233
+ onClick: ()=>fileInputRef.current?.click(),
234
+ style: {
235
+ display: 'inline-flex',
236
+ alignItems: 'center',
237
+ gap: '6px',
238
+ padding: '8px 14px',
239
+ borderRadius: '8px',
240
+ border: '1px solid var(--theme-elevation-200)',
241
+ cursor: 'pointer',
242
+ background: 'transparent',
243
+ color: 'var(--theme-elevation-700)',
244
+ fontWeight: 600,
245
+ fontSize: '12px'
246
+ },
247
+ children: [
248
+ /*#__PURE__*/ _jsx(ImageUp, {
249
+ size: 14,
250
+ "aria-hidden": true
251
+ }),
252
+ t.fromImage
253
+ ]
254
+ }),
255
+ /*#__PURE__*/ _jsx("input", {
256
+ ref: fileInputRef,
257
+ type: "file",
258
+ accept: "image/*",
259
+ onChange: handleFile,
260
+ style: {
261
+ display: 'none'
262
+ }
263
+ }),
264
+ /*#__PURE__*/ _jsx("div", {
265
+ id: lockNoteId,
266
+ style: {
267
+ fontSize: '12px',
268
+ color: 'var(--theme-elevation-600)',
269
+ maxWidth: '200px'
270
+ },
271
+ children: colorsLocked ? appearanceT.colorsLocked : t.hint
272
+ })
273
+ ]
274
+ })
275
+ ]
276
+ }),
277
+ imageError ? /*#__PURE__*/ _jsx("div", {
278
+ role: "alert",
279
+ style: {
280
+ marginTop: '12px',
281
+ fontSize: '12px',
282
+ color: 'var(--theme-error-600)'
283
+ },
284
+ children: t.imageError
285
+ }) : null,
286
+ swatches.length > 0 ? /*#__PURE__*/ _jsxs("div", {
287
+ style: {
288
+ marginTop: '12px'
289
+ },
290
+ children: [
291
+ /*#__PURE__*/ _jsx("div", {
292
+ style: {
293
+ fontSize: '12px',
294
+ color: 'var(--theme-elevation-600)',
295
+ marginBottom: '6px'
296
+ },
297
+ children: t.pick
298
+ }),
299
+ /*#__PURE__*/ _jsx("div", {
300
+ style: {
301
+ display: 'flex',
302
+ gap: '6px',
303
+ flexWrap: 'wrap'
304
+ },
305
+ children: swatches.map((color)=>/*#__PURE__*/ _jsx("button", {
306
+ type: "button",
307
+ onClick: ()=>chooseSeed(color),
308
+ title: color,
309
+ "aria-label": `${t.useColor} ${color}`,
310
+ "aria-pressed": color === seed,
311
+ style: {
312
+ width: '30px',
313
+ height: '30px',
314
+ borderRadius: '6px',
315
+ background: color,
316
+ cursor: 'pointer',
317
+ border: color === seed ? '2px solid var(--theme-elevation-800)' : '1px solid rgba(0,0,0,0.12)'
318
+ }
319
+ }, color))
320
+ })
321
+ ]
322
+ }) : null
323
+ ]
324
+ });
325
+ }
326
+
327
+ //# sourceMappingURL=PaletteGeneratorField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/fields/PaletteGeneratorField.tsx"],"sourcesContent":["'use client'\n\nimport { useForm, useFormFields } from '@payloadcms/ui'\nimport { ImageUp, Sparkles, Wand2 } from 'lucide-react'\nimport { useCallback, useEffect, useId, useRef, useState } from 'react'\nimport { HexColorInput, HexColorPicker } from 'react-colorful'\nimport {\n createBulkWriter,\n getAppearanceSession,\n useAppearanceSession,\n} from '../hooks/useAppearanceSession.js'\nimport { useThemeTranslations } from '../hooks/useThemeTranslations.js'\nimport {\n extractDominantColors,\n generatePaletteFromColor,\n type GeneratedColorMode,\n} from '../utils/generatePalette.js'\n\n/**\n * Admin tool that generates a full light + dark palette from a single brand\n * colour (or a colour extracted from an uploaded logo/image) and writes it\n * straight into the colour-mode fields.\n */\nexport default function PaletteGeneratorField() {\n const formFields = useFormFields(([state]) => state) as Record<\n string,\n { value?: unknown } | undefined\n >\n const { dispatchFields, setModified } = useForm()\n const t = useThemeTranslations().paletteGenerator\n const fileInputRef = useRef<HTMLInputElement | null>(null)\n\n const currentPrimary = (() => {\n const v = formFields?.['themeConfiguration.lightMode.primary']?.value\n return typeof v === 'string' && v.trim() ? v : '#3b82f6'\n })()\n\n const [seed, setSeed] = useState<string>(currentPrimary)\n const [swatches, setSwatches] = useState<string[]>([])\n const [open, setOpen] = useState(false)\n const [imageError, setImageError] = useState(false)\n const seedTouchedRef = useRef(false)\n\n // Follow the form's primary colour (e.g. after a preset is applied) until the\n // editor picks a seed of their own.\n useEffect(() => {\n if (!seedTouchedRef.current) setSeed(currentPrimary)\n }, [currentPrimary])\n\n const chooseSeed = useCallback((color: string) => {\n seedTouchedRef.current = true\n setSeed(color)\n }, [])\n\n const { locks } = useAppearanceSession()\n const appearanceT = useThemeTranslations().appearance\n const colorsLocked = locks.colors\n const lockNoteId = useId()\n\n const applyPalette = useCallback(() => {\n if (getAppearanceSession().locks.colors) return\n const palette = generatePaletteFromColor(seed)\n const writer = createBulkWriter(formFields, dispatchFields)\n ;(['lightMode', 'darkMode'] as const).forEach((mode) => {\n const colors = palette[mode]\n ;(Object.keys(colors) as Array<keyof GeneratedColorMode>).forEach((key) => {\n writer.write(`themeConfiguration.${mode}.${key}`, colors[key])\n })\n })\n // `dispatchFields` alone does not flip the form's dirty flag, so Payload\n // would keep the Save button disabled after generating a palette.\n setModified(true)\n writer.commit('palette', `${appearanceT.generatedPalette} ${seed}`)\n }, [seed, formFields, dispatchFields, setModified, appearanceT.generatedPalette])\n\n const handleFile = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0]\n if (!file) return\n setImageError(false)\n const url = URL.createObjectURL(file)\n const img = new Image()\n img.onload = () => {\n const colors = extractDominantColors(img, 6)\n setSwatches(colors)\n if (colors[0]) chooseSeed(colors[0])\n URL.revokeObjectURL(url)\n }\n img.onerror = () => {\n setImageError(true)\n URL.revokeObjectURL(url)\n }\n img.src = url\n // Allow re-selecting the same file after an error.\n event.target.value = ''\n }, [chooseSeed])\n\n return (\n <div\n style={{\n padding: '16px',\n borderRadius: '12px',\n border: '1px solid var(--theme-elevation-200)',\n backgroundColor: 'var(--theme-elevation-25)',\n }}\n >\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 <Sparkles size={14} aria-hidden />\n {t.title}\n </div>\n <div style={{ fontSize: '12px', color: 'var(--theme-elevation-600)', marginBottom: '12px' }}>\n {t.subtitle}\n </div>\n\n <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'flex-start', gap: '16px' }}>\n {/* Seed colour */}\n <div style={{ display: 'grid', gap: '8px' }}>\n <button\n type=\"button\"\n onClick={() => setOpen((o) => !o)}\n aria-label={`${t.seedLabel}: ${seed}`}\n aria-expanded={open}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n padding: '4px 8px 4px 4px',\n borderRadius: '8px',\n border: '1px solid var(--theme-elevation-200)',\n background: 'var(--theme-elevation-50)',\n cursor: 'pointer',\n }}\n >\n <span\n style={{\n width: '28px',\n height: '28px',\n borderRadius: '6px',\n background: seed,\n border: '1px solid rgba(0,0,0,0.1)',\n }}\n />\n <span style={{ fontFamily: 'monospace', fontSize: '12px', color: 'var(--theme-elevation-700)' }}>\n {seed}\n </span>\n </button>\n {open ? (\n <div style={{ display: 'grid', gap: '8px' }}>\n <HexColorPicker color={seed} onChange={chooseSeed} />\n <HexColorInput\n color={seed}\n onChange={chooseSeed}\n prefixed\n style={{\n width: '160px',\n padding: '6px 8px',\n borderRadius: '6px',\n border: '1px solid var(--theme-elevation-200)',\n fontFamily: 'monospace',\n fontSize: '12px',\n }}\n />\n </div>\n ) : null}\n </div>\n\n {/* Actions */}\n <div style={{ display: 'grid', gap: '8px' }}>\n <button\n type=\"button\"\n onClick={applyPalette}\n disabled={colorsLocked}\n aria-describedby={colorsLocked ? lockNoteId : undefined}\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: '6px',\n padding: '8px 14px',\n borderRadius: '8px',\n border: 'none',\n cursor: colorsLocked ? 'not-allowed' : 'pointer',\n background: 'var(--theme-elevation-800)',\n color: 'var(--theme-elevation-0)',\n opacity: colorsLocked ? 0.5 : 1,\n fontWeight: 600,\n fontSize: '12px',\n }}\n >\n <Wand2 size={14} aria-hidden />\n {t.generate}\n </button>\n <button\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: '6px',\n padding: '8px 14px',\n borderRadius: '8px',\n border: '1px solid var(--theme-elevation-200)',\n cursor: 'pointer',\n background: 'transparent',\n color: 'var(--theme-elevation-700)',\n fontWeight: 600,\n fontSize: '12px',\n }}\n >\n <ImageUp size={14} aria-hidden />\n {t.fromImage}\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n onChange={handleFile}\n style={{ display: 'none' }}\n />\n <div\n id={lockNoteId}\n style={{ fontSize: '12px', color: 'var(--theme-elevation-600)', maxWidth: '200px' }}\n >\n {colorsLocked ? appearanceT.colorsLocked : t.hint}\n </div>\n </div>\n </div>\n\n {imageError ? (\n <div role=\"alert\" style={{ marginTop: '12px', fontSize: '12px', color: 'var(--theme-error-600)' }}>\n {t.imageError}\n </div>\n ) : null}\n\n {/* Extracted swatches */}\n {swatches.length > 0 ? (\n <div style={{ marginTop: '12px' }}>\n <div style={{ fontSize: '12px', color: 'var(--theme-elevation-600)', marginBottom: '6px' }}>\n {t.pick}\n </div>\n <div style={{ display: 'flex', gap: '6px', flexWrap: 'wrap' }}>\n {swatches.map((color) => (\n <button\n key={color}\n type=\"button\"\n onClick={() => chooseSeed(color)}\n title={color}\n aria-label={`${t.useColor} ${color}`}\n aria-pressed={color === seed}\n style={{\n width: '30px',\n height: '30px',\n borderRadius: '6px',\n background: color,\n cursor: 'pointer',\n border:\n color === seed\n ? '2px solid var(--theme-elevation-800)'\n : '1px solid rgba(0,0,0,0.12)',\n }}\n />\n ))}\n </div>\n </div>\n ) : null}\n </div>\n )\n}\n"],"names":["useForm","useFormFields","ImageUp","Sparkles","Wand2","useCallback","useEffect","useId","useRef","useState","HexColorInput","HexColorPicker","createBulkWriter","getAppearanceSession","useAppearanceSession","useThemeTranslations","extractDominantColors","generatePaletteFromColor","PaletteGeneratorField","formFields","state","dispatchFields","setModified","t","paletteGenerator","fileInputRef","currentPrimary","v","value","trim","seed","setSeed","swatches","setSwatches","open","setOpen","imageError","setImageError","seedTouchedRef","current","chooseSeed","color","locks","appearanceT","appearance","colorsLocked","colors","lockNoteId","applyPalette","palette","writer","forEach","mode","Object","keys","key","write","commit","generatedPalette","handleFile","event","file","target","files","url","URL","createObjectURL","img","Image","onload","revokeObjectURL","onerror","src","div","style","padding","borderRadius","border","backgroundColor","display","alignItems","gap","fontSize","fontWeight","size","aria-hidden","title","marginBottom","subtitle","flexWrap","button","type","onClick","o","aria-label","seedLabel","aria-expanded","background","cursor","span","width","height","fontFamily","onChange","prefixed","disabled","aria-describedby","undefined","opacity","generate","click","fromImage","input","ref","accept","id","maxWidth","hint","role","marginTop","length","pick","map","useColor","aria-pressed"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { TextFieldClientProps } from 'payload';
2
+ export default function StylePresetField(props: TextFieldClientProps): import("react").JSX.Element;
3
+ //# sourceMappingURL=StylePresetField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StylePresetField.d.ts","sourceRoot":"","sources":["../../src/fields/StylePresetField.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAuJnD,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,KAAK,EAAE,oBAAoB,+BAgYnE"}