@kilivi/payloadcms-theme-management 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +318 -847
  2. package/dist/components/AppearanceSessionControls.d.ts +15 -0
  3. package/dist/components/AppearanceSessionControls.d.ts.map +1 -0
  4. package/dist/components/AppearanceSessionControls.js +152 -0
  5. package/dist/components/AppearanceSessionControls.js.map +1 -0
  6. package/dist/components/ColorModeToggle.css +42 -0
  7. package/dist/components/ColorModeToggle.d.ts +40 -0
  8. package/dist/components/ColorModeToggle.d.ts.map +1 -0
  9. package/dist/components/ColorModeToggle.js +111 -0
  10. package/dist/components/ColorModeToggle.js.map +1 -0
  11. package/dist/components/ServerThemeInjector.d.ts.map +1 -1
  12. package/dist/components/ServerThemeInjector.js +41 -14
  13. package/dist/components/ServerThemeInjector.js.map +1 -1
  14. package/dist/components/ThemeLivePreviewSync.d.ts +1 -0
  15. package/dist/components/ThemeLivePreviewSync.d.ts.map +1 -1
  16. package/dist/components/ThemeLivePreviewSync.js +1 -1
  17. package/dist/components/ThemeLivePreviewSync.js.map +1 -1
  18. package/dist/components/ThemeSwitcher.d.ts +36 -0
  19. package/dist/components/ThemeSwitcher.d.ts.map +1 -0
  20. package/dist/components/ThemeSwitcher.js +89 -0
  21. package/dist/components/ThemeSwitcher.js.map +1 -0
  22. package/dist/components/typographyPreviewUtils.d.ts.map +1 -1
  23. package/dist/components/typographyPreviewUtils.js +8 -1
  24. package/dist/components/typographyPreviewUtils.js.map +1 -1
  25. package/dist/constants/devicePreviews.d.ts +35 -0
  26. package/dist/constants/devicePreviews.d.ts.map +1 -0
  27. package/dist/constants/devicePreviews.js +43 -0
  28. package/dist/constants/devicePreviews.js.map +1 -0
  29. package/dist/constants/themeFonts.js +9 -9
  30. package/dist/constants/themeFonts.js.map +1 -1
  31. package/dist/extended-presets.d.ts +0 -4
  32. package/dist/extended-presets.d.ts.map +1 -1
  33. package/dist/extended-presets.js +1 -18
  34. package/dist/extended-presets.js.map +1 -1
  35. package/dist/extended-themes.d.ts.map +1 -1
  36. package/dist/extended-themes.js +428 -0
  37. package/dist/extended-themes.js.map +1 -1
  38. package/dist/fields/AccessibilityAuditField.d.ts +2 -0
  39. package/dist/fields/AccessibilityAuditField.d.ts.map +1 -0
  40. package/dist/fields/AccessibilityAuditField.js +303 -0
  41. package/dist/fields/AccessibilityAuditField.js.map +1 -0
  42. package/dist/fields/AppearancePreviewField.d.ts +2 -0
  43. package/dist/fields/AppearancePreviewField.d.ts.map +1 -0
  44. package/dist/fields/AppearancePreviewField.js +763 -0
  45. package/dist/fields/AppearancePreviewField.js.map +1 -0
  46. package/dist/fields/ColorPickerField.d.ts.map +1 -1
  47. package/dist/fields/ColorPickerField.js +89 -85
  48. package/dist/fields/ColorPickerField.js.map +1 -1
  49. package/dist/fields/FontSelectField.d.ts +6 -0
  50. package/dist/fields/FontSelectField.d.ts.map +1 -1
  51. package/dist/fields/FontSelectField.js +195 -122
  52. package/dist/fields/FontSelectField.js.map +1 -1
  53. package/dist/fields/PaletteGeneratorField.d.ts +7 -0
  54. package/dist/fields/PaletteGeneratorField.d.ts.map +1 -0
  55. package/dist/fields/PaletteGeneratorField.js +327 -0
  56. package/dist/fields/PaletteGeneratorField.js.map +1 -0
  57. package/dist/fields/StylePresetField.d.ts +3 -0
  58. package/dist/fields/StylePresetField.d.ts.map +1 -0
  59. package/dist/fields/StylePresetField.js +462 -0
  60. package/dist/fields/StylePresetField.js.map +1 -0
  61. package/dist/fields/ThemeColorPickerField.css +115 -35
  62. package/dist/fields/ThemeColorPickerField.d.ts.map +1 -1
  63. package/dist/fields/ThemeColorPickerField.js +204 -70
  64. package/dist/fields/ThemeColorPickerField.js.map +1 -1
  65. package/dist/fields/ThemeExportField.d.ts +6 -0
  66. package/dist/fields/ThemeExportField.d.ts.map +1 -0
  67. package/dist/fields/ThemeExportField.js +202 -0
  68. package/dist/fields/ThemeExportField.js.map +1 -0
  69. package/dist/fields/ThemePresetImportField.d.ts.map +1 -1
  70. package/dist/fields/ThemePresetImportField.js +56 -25
  71. package/dist/fields/ThemePresetImportField.js.map +1 -1
  72. package/dist/fields/ThemePreviewField.css +382 -5
  73. package/dist/fields/ThemePreviewField.d.ts.map +1 -1
  74. package/dist/fields/ThemePreviewField.js +268 -170
  75. package/dist/fields/ThemePreviewField.js.map +1 -1
  76. package/dist/fields/ThemeTokenSelectField.d.ts.map +1 -1
  77. package/dist/fields/ThemeTokenSelectField.js +278 -80
  78. package/dist/fields/ThemeTokenSelectField.js.map +1 -1
  79. package/dist/fields/colorFieldHelpers.d.ts +90 -0
  80. package/dist/fields/colorFieldHelpers.d.ts.map +1 -0
  81. package/dist/fields/colorFieldHelpers.js +133 -0
  82. package/dist/fields/colorFieldHelpers.js.map +1 -0
  83. package/dist/fields/colorModeFields.d.ts +1 -1
  84. package/dist/fields/colorModeFields.d.ts.map +1 -1
  85. package/dist/fields/colorModeFields.js +49 -7
  86. package/dist/fields/colorModeFields.js.map +1 -1
  87. package/dist/fields/themeConfigurationField.d.ts +8 -1
  88. package/dist/fields/themeConfigurationField.d.ts.map +1 -1
  89. package/dist/fields/themeConfigurationField.js +961 -29
  90. package/dist/fields/themeConfigurationField.js.map +1 -1
  91. package/dist/globals/ThemeSettings/FontPicker/FontPreview.d.ts.map +1 -1
  92. package/dist/globals/ThemeSettings/FontPicker/FontPreview.js +8 -4
  93. package/dist/globals/ThemeSettings/FontPicker/FontPreview.js.map +1 -1
  94. package/dist/globals/ThemeSettings/FontPicker/index.d.ts.map +1 -1
  95. package/dist/globals/ThemeSettings/FontPicker/index.js +81 -26
  96. package/dist/globals/ThemeSettings/FontPicker/index.js.map +1 -1
  97. package/dist/globals/ThemeSettings/FontPicker/styles.css +98 -41
  98. package/dist/globals/ThemeSettings/FontPicker/translations.d.ts +6 -0
  99. package/dist/globals/ThemeSettings/FontPicker/translations.d.ts.map +1 -1
  100. package/dist/globals/ThemeSettings/FontPicker/translations.js +14 -2
  101. package/dist/globals/ThemeSettings/FontPicker/translations.js.map +1 -1
  102. package/dist/globals/ThemeSettings/config.d.ts.map +1 -1
  103. package/dist/globals/ThemeSettings/config.js +76 -79
  104. package/dist/globals/ThemeSettings/config.js.map +1 -1
  105. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.d.ts.map +1 -1
  106. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js +63 -62
  107. package/dist/globals/ThemeSettings/fields/FontLoadingGuideField.js.map +1 -1
  108. package/dist/globals/ThemeSettings/fields/TypographyInfoField.d.ts.map +1 -1
  109. package/dist/globals/ThemeSettings/fields/TypographyInfoField.js +22 -25
  110. package/dist/globals/ThemeSettings/fields/TypographyInfoField.js.map +1 -1
  111. package/dist/hooks/useAppearanceSession.d.ts +50 -0
  112. package/dist/hooks/useAppearanceSession.d.ts.map +1 -0
  113. package/dist/hooks/useAppearanceSession.js +91 -0
  114. package/dist/hooks/useAppearanceSession.js.map +1 -0
  115. package/dist/hooks/useThemeTranslations.d.ts +13 -0
  116. package/dist/hooks/useThemeTranslations.d.ts.map +1 -0
  117. package/dist/hooks/useThemeTranslations.js +22 -0
  118. package/dist/hooks/useThemeTranslations.js.map +1 -0
  119. package/dist/i18n.d.ts +53 -0
  120. package/dist/i18n.d.ts.map +1 -0
  121. package/dist/i18n.js +53 -0
  122. package/dist/i18n.js.map +1 -0
  123. package/dist/index.d.ts +10 -4
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +50 -29
  126. package/dist/index.js.map +1 -1
  127. package/dist/payload-types.d.ts +24 -0
  128. package/dist/payload-types.d.ts.map +1 -1
  129. package/dist/payload-types.js.map +1 -1
  130. package/dist/presets.d.ts +8 -0
  131. package/dist/presets.d.ts.map +1 -1
  132. package/dist/presets.js.map +1 -1
  133. package/dist/providers/Theme/index.d.ts.map +1 -1
  134. package/dist/providers/Theme/index.js +75 -11
  135. package/dist/providers/Theme/index.js.map +1 -1
  136. package/dist/providers/Theme/themeConfig.d.ts.map +1 -1
  137. package/dist/providers/Theme/themeConfig.js +104 -0
  138. package/dist/providers/Theme/themeConfig.js.map +1 -1
  139. package/dist/providers/Theme/types.d.ts +1 -1
  140. package/dist/providers/Theme/types.d.ts.map +1 -1
  141. package/dist/providers/Theme/types.js +10 -1
  142. package/dist/providers/Theme/types.js.map +1 -1
  143. package/dist/style-presets.d.ts +66 -0
  144. package/dist/style-presets.d.ts.map +1 -0
  145. package/dist/style-presets.js +556 -0
  146. package/dist/style-presets.js.map +1 -0
  147. package/dist/translations.d.ts +159 -1
  148. package/dist/translations.d.ts.map +1 -1
  149. package/dist/translations.js +310 -9
  150. package/dist/translations.js.map +1 -1
  151. package/dist/tweakcn-special-themes.d.ts.map +1 -1
  152. package/dist/tweakcn-special-themes.js +585 -9
  153. package/dist/tweakcn-special-themes.js.map +1 -1
  154. package/dist/types/css.d.js +3 -0
  155. package/dist/types/css.d.js.map +1 -0
  156. package/dist/types.d.ts +26 -0
  157. package/dist/types.d.ts.map +1 -1
  158. package/dist/types.js +1 -1
  159. package/dist/types.js.map +1 -1
  160. package/dist/utilities/getThemeSettings.d.ts.map +1 -1
  161. package/dist/utilities/getThemeSettings.js +2 -14
  162. package/dist/utilities/getThemeSettings.js.map +1 -1
  163. package/dist/utils/contrast.d.ts +63 -0
  164. package/dist/utils/contrast.d.ts.map +1 -0
  165. package/dist/utils/contrast.js +158 -0
  166. package/dist/utils/contrast.js.map +1 -0
  167. package/dist/utils/exportTokens.d.ts +30 -0
  168. package/dist/utils/exportTokens.d.ts.map +1 -0
  169. package/dist/utils/exportTokens.js +183 -0
  170. package/dist/utils/exportTokens.js.map +1 -0
  171. package/dist/utils/generatePalette.d.ts +46 -0
  172. package/dist/utils/generatePalette.d.ts.map +1 -0
  173. package/dist/utils/generatePalette.js +127 -0
  174. package/dist/utils/generatePalette.js.map +1 -0
  175. package/dist/utils/generateThemeCSS.d.ts.map +1 -1
  176. package/dist/utils/generateThemeCSS.js +2 -40
  177. package/dist/utils/generateThemeCSS.js.map +1 -1
  178. package/dist/utils/livePreviewThemeRuntime.d.ts +16 -2
  179. package/dist/utils/livePreviewThemeRuntime.d.ts.map +1 -1
  180. package/dist/utils/livePreviewThemeRuntime.js +37 -14
  181. package/dist/utils/livePreviewThemeRuntime.js.map +1 -1
  182. package/dist/utils/localizedText.d.ts +8 -0
  183. package/dist/utils/localizedText.d.ts.map +1 -0
  184. package/dist/utils/localizedText.js +21 -0
  185. package/dist/utils/localizedText.js.map +1 -0
  186. package/dist/utils/resolveThemeConfiguration.d.ts +45 -1
  187. package/dist/utils/resolveThemeConfiguration.d.ts.map +1 -1
  188. package/dist/utils/resolveThemeConfiguration.js +12 -5
  189. package/dist/utils/resolveThemeConfiguration.js.map +1 -1
  190. package/dist/utils/themeColors.d.ts +3 -0
  191. package/dist/utils/themeColors.d.ts.map +1 -1
  192. package/dist/utils/themeColors.js +30 -1
  193. package/dist/utils/themeColors.js.map +1 -1
  194. package/dist/utils/themeHtmlAttributes.d.ts +32 -12
  195. package/dist/utils/themeHtmlAttributes.d.ts.map +1 -1
  196. package/dist/utils/themeHtmlAttributes.js +38 -5
  197. package/dist/utils/themeHtmlAttributes.js.map +1 -1
  198. package/dist/utils/themeUtils.d.ts +3 -5
  199. package/dist/utils/themeUtils.d.ts.map +1 -1
  200. package/dist/utils/themeUtils.js +416 -6
  201. package/dist/utils/themeUtils.js.map +1 -1
  202. package/dist/utils/tweakcnConverter.d.ts +1 -3
  203. package/dist/utils/tweakcnConverter.d.ts.map +1 -1
  204. package/dist/utils/tweakcnConverter.js +1 -3
  205. package/dist/utils/tweakcnConverter.js.map +1 -1
  206. package/package.json +11 -5
  207. package/dist/components/ThemePreview.d.ts +0 -3
  208. package/dist/components/ThemePreview.d.ts.map +0 -1
  209. package/dist/components/ThemePreview.js +0 -321
  210. package/dist/components/ThemePreview.js.map +0 -1
  211. package/dist/plugin.d.ts +0 -2
  212. package/dist/plugin.d.ts.map +0 -1
  213. package/dist/plugin.js +0 -3
  214. package/dist/plugin.js.map +0 -1
@@ -1,74 +1,172 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useField } from '@payloadcms/ui';
4
- import { useEffect, useState } from 'react';
5
- import { getTranslations } from '../translations.js';
6
- import { getAdminLanguage } from '../utils/getAdminLanguage.js';
7
- const FontSelectField = ({ field, path })=>{
4
+ import { Check, ChevronDown } from 'lucide-react';
5
+ import { useCallback, useEffect, useId, useRef, useState } from 'react';
6
+ import { useThemeLanguage, useThemeTranslations } from '../hooks/useThemeTranslations.js';
7
+ import { resolveLocalizedText } from '../utils/localizedText.js';
8
+ const NON_GOOGLE_VALUES = [
9
+ 'preset',
10
+ 'custom',
11
+ 'system-ui'
12
+ ];
13
+ function previewFamily(option) {
14
+ if (option.fontFamily) return option.fontFamily;
15
+ if (option.category === 'sans-serif') return 'system-ui, sans-serif';
16
+ if (option.category === 'serif') return 'Georgia, serif';
17
+ if (option.category === 'monospace') return 'monospace';
18
+ return 'inherit';
19
+ }
20
+ /**
21
+ * Font select with live font previews. Implements the WAI-ARIA "select-only
22
+ * combobox" pattern: a button trigger plus a listbox navigated with the arrow
23
+ * keys, Home/End, Enter/Space and Escape. Preview fonts load for whichever option
24
+ * is active, so keyboard users get the same previews as mouse users.
25
+ */ const FontSelectField = ({ field, path })=>{
8
26
  const { value, setValue } = useField({
9
27
  path
10
28
  });
11
29
  const [isOpen, setIsOpen] = useState(false);
30
+ const [activeIndex, setActiveIndex] = useState(-1);
31
+ const lang = useThemeLanguage();
32
+ const t = useThemeTranslations();
33
+ const baseId = useId();
34
+ const labelId = `${baseId}-label`;
35
+ const listboxId = `${baseId}-listbox`;
36
+ const containerRef = useRef(null);
37
+ const triggerRef = useRef(null);
38
+ const listboxRef = useRef(null);
39
+ const loadedFonts = useRef(new Set());
12
40
  const options = field.options || [];
13
- const selectedOption = options.find((opt)=>opt.value === value);
14
- const [loadedFonts, setLoadedFonts] = useState(new Set());
15
- const getOptionLabel = (opt)=>{
16
- if (typeof opt.label === 'string') return opt.label;
17
- const lang = getAdminLanguage();
18
- return opt.label[lang] || opt.label.en || opt.label.cs || 'Font';
19
- };
20
- const ensureFontLoaded = (fontValue)=>{
21
- if (!fontValue || [
22
- 'preset',
23
- 'custom',
24
- 'system-ui'
25
- ].includes(fontValue)) return;
26
- // Avoid duplicate loads
27
- if (loadedFonts.has(fontValue)) return;
28
- if (typeof document === 'undefined') return;
41
+ const selectedIndex = options.findIndex((opt)=>opt.value === value);
42
+ const selectedOption = selectedIndex >= 0 ? options[selectedIndex] : undefined;
43
+ const getOptionLabel = (opt)=>resolveLocalizedText(opt.label, lang, 'Font');
44
+ const label = resolveLocalizedText(field.label, lang, 'Font');
45
+ const optionId = (index)=>`${baseId}-option-${index}`;
46
+ const ensureFontLoaded = useCallback((fontValue)=>{
47
+ if (!fontValue || NON_GOOGLE_VALUES.includes(fontValue)) return;
48
+ if (loadedFonts.current.has(fontValue) || typeof document === 'undefined') return;
49
+ loadedFonts.current.add(fontValue);
29
50
  const linkId = `font-${fontValue.replace(/\s+/g, '-')}`;
30
- if (document.getElementById(linkId)) {
31
- setLoadedFonts((prev)=>new Set(prev).add(fontValue));
32
- return;
33
- }
34
- const weights = '400;700' // reasonable default weights
35
- ;
36
- const fontUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(fontValue)}:wght@${weights}&display=swap`;
51
+ if (document.getElementById(linkId)) return;
37
52
  const link = document.createElement('link');
38
53
  link.id = linkId;
39
54
  link.rel = 'stylesheet';
40
- link.href = fontUrl;
41
- link.onload = ()=>setLoadedFonts((prev)=>new Set(prev).add(fontValue));
55
+ link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(fontValue)}:wght@400;700&display=swap`;
42
56
  document.head.appendChild(link);
43
- };
44
- const handleSelect = (optionValue)=>{
45
- setValue(optionValue);
57
+ }, []);
58
+ const open = useCallback((index = selectedIndex >= 0 ? selectedIndex : 0)=>{
59
+ setActiveIndex(index);
60
+ setIsOpen(true);
61
+ }, [
62
+ selectedIndex
63
+ ]);
64
+ const close = useCallback((restoreFocus)=>{
46
65
  setIsOpen(false);
66
+ if (restoreFocus) triggerRef.current?.focus();
67
+ }, []);
68
+ const handleSelect = (index)=>{
69
+ const option = options[index];
70
+ if (!option) return;
71
+ setValue(option.value);
72
+ close(true);
47
73
  };
74
+ // Load the preview font for the active option and keep it scrolled into view.
48
75
  useEffect(()=>{
49
- const handleClickOutside = (e)=>{
50
- const target = e.target;
51
- if (!target.closest('.font-select-container')) {
52
- setIsOpen(false);
53
- }
54
- };
55
- if (isOpen) {
56
- document.addEventListener('mousedown', handleClickOutside);
57
- return ()=>document.removeEventListener('mousedown', handleClickOutside);
58
- }
76
+ if (!isOpen || activeIndex < 0) return;
77
+ ensureFontLoaded(options[activeIndex]?.value);
78
+ listboxRef.current?.querySelector(`#${CSS.escape(optionId(activeIndex))}`)?.scrollIntoView({
79
+ block: 'nearest'
80
+ });
81
+ }, [
82
+ isOpen,
83
+ activeIndex
84
+ ]);
85
+ // Focus the listbox when it opens so keyboard navigation works immediately.
86
+ useEffect(()=>{
87
+ if (isOpen) listboxRef.current?.focus();
59
88
  }, [
60
89
  isOpen
61
90
  ]);
62
- const t = getTranslations(getAdminLanguage());
63
- const label = typeof field.label === 'string' ? field.label : typeof field.label === 'object' ? field.label?.en || field.label?.cs : field.label || 'Font';
91
+ useEffect(()=>{
92
+ if (!isOpen) return;
93
+ const handlePointerOutside = (e)=>{
94
+ if (!containerRef.current?.contains(e.target)) close(false);
95
+ };
96
+ document.addEventListener('pointerdown', handlePointerOutside);
97
+ return ()=>document.removeEventListener('pointerdown', handlePointerOutside);
98
+ }, [
99
+ isOpen,
100
+ close
101
+ ]);
102
+ const handleTriggerKeyDown = (event)=>{
103
+ if ([
104
+ 'ArrowDown',
105
+ 'ArrowUp',
106
+ 'Enter',
107
+ ' '
108
+ ].includes(event.key)) {
109
+ event.preventDefault();
110
+ open(event.key === 'ArrowUp' && selectedIndex < 0 ? options.length - 1 : undefined);
111
+ }
112
+ };
113
+ const handleListboxKeyDown = (event)=>{
114
+ const last = options.length - 1;
115
+ switch(event.key){
116
+ case 'ArrowDown':
117
+ event.preventDefault();
118
+ setActiveIndex((i)=>Math.min(last, i + 1));
119
+ break;
120
+ case 'ArrowUp':
121
+ event.preventDefault();
122
+ setActiveIndex((i)=>Math.max(0, i - 1));
123
+ break;
124
+ case 'Home':
125
+ event.preventDefault();
126
+ setActiveIndex(0);
127
+ break;
128
+ case 'End':
129
+ event.preventDefault();
130
+ setActiveIndex(last);
131
+ break;
132
+ case 'Enter':
133
+ case ' ':
134
+ event.preventDefault();
135
+ handleSelect(activeIndex);
136
+ break;
137
+ case 'Escape':
138
+ event.preventDefault();
139
+ event.stopPropagation();
140
+ close(true);
141
+ break;
142
+ case 'Tab':
143
+ close(false);
144
+ break;
145
+ default:
146
+ // Type-ahead: jump to the first option starting with the typed letter.
147
+ if (event.key.length === 1 && /\S/.test(event.key)) {
148
+ const letter = event.key.toLowerCase();
149
+ const start = activeIndex + 1;
150
+ const ordered = [
151
+ ...options.slice(start),
152
+ ...options.slice(0, start)
153
+ ];
154
+ const match = ordered.find((opt)=>getOptionLabel(opt).toLowerCase().startsWith(letter));
155
+ if (match) setActiveIndex(options.indexOf(match));
156
+ }
157
+ }
158
+ };
64
159
  return /*#__PURE__*/ _jsxs("div", {
160
+ ref: containerRef,
65
161
  className: "font-select-container",
66
162
  style: {
67
163
  position: 'relative',
68
- marginBottom: '12px'
164
+ marginBottom: '12px',
165
+ maxWidth: '360px'
69
166
  },
70
167
  children: [
71
- /*#__PURE__*/ _jsx("label", {
168
+ /*#__PURE__*/ _jsx("div", {
169
+ id: labelId,
72
170
  style: {
73
171
  display: 'block',
74
172
  marginBottom: '6px',
@@ -79,10 +177,18 @@ const FontSelectField = ({ field, path })=>{
79
177
  children: label
80
178
  }),
81
179
  /*#__PURE__*/ _jsxs("button", {
180
+ ref: triggerRef,
82
181
  type: "button",
83
- onClick: ()=>setIsOpen(!isOpen),
182
+ className: "font-select-trigger",
183
+ role: "combobox",
184
+ "aria-haspopup": "listbox",
185
+ "aria-expanded": isOpen,
186
+ "aria-controls": isOpen ? listboxId : undefined,
187
+ "aria-labelledby": `${labelId} ${baseId}-value`,
188
+ onClick: ()=>isOpen ? close(false) : open(),
189
+ onKeyDown: handleTriggerKeyDown,
84
190
  style: {
85
- width: '300px',
191
+ width: '100%',
86
192
  padding: '10px 14px',
87
193
  borderRadius: '6px',
88
194
  border: '1px solid var(--theme-elevation-200)',
@@ -93,44 +199,38 @@ const FontSelectField = ({ field, path })=>{
93
199
  display: 'flex',
94
200
  justifyContent: 'space-between',
95
201
  alignItems: 'center',
202
+ gap: '8px',
96
203
  fontSize: '14px',
97
- fontFamily: selectedOption?.fontFamily || 'inherit',
98
- transition: 'border-color 0.2s ease'
99
- },
100
- onMouseEnter: (e)=>{
101
- e.currentTarget.style.borderColor = 'var(--theme-elevation-400)';
102
- },
103
- onMouseLeave: (e)=>{
104
- e.currentTarget.style.borderColor = 'var(--theme-elevation-200)';
204
+ fontFamily: selectedOption?.fontFamily || 'inherit'
105
205
  },
106
206
  children: [
107
207
  /*#__PURE__*/ _jsx("span", {
208
+ id: `${baseId}-value`,
108
209
  style: {
109
- flex: 1
210
+ flex: 1,
211
+ minWidth: 0
110
212
  },
111
213
  children: selectedOption ? getOptionLabel(selectedOption) : t.ui.selectFontPlaceholder
112
214
  }),
113
- ' ',
114
- /*#__PURE__*/ _jsx("svg", {
115
- width: "12",
116
- height: "12",
117
- viewBox: "0 0 12 12",
118
- fill: "none",
215
+ /*#__PURE__*/ _jsx(ChevronDown, {
216
+ size: 14,
217
+ "aria-hidden": true,
119
218
  style: {
120
- transition: 'transform 0.2s ease',
121
- transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)'
122
- },
123
- children: /*#__PURE__*/ _jsx("path", {
124
- d: "M3 4.5L6 7.5L9 4.5",
125
- stroke: "currentColor",
126
- strokeWidth: "1.5",
127
- strokeLinecap: "round",
128
- strokeLinejoin: "round"
129
- })
219
+ flexShrink: 0,
220
+ transform: isOpen ? 'rotate(180deg)' : undefined
221
+ }
130
222
  })
131
223
  ]
132
224
  }),
133
225
  isOpen && /*#__PURE__*/ _jsx("div", {
226
+ ref: listboxRef,
227
+ id: listboxId,
228
+ role: "listbox",
229
+ tabIndex: -1,
230
+ "aria-labelledby": labelId,
231
+ "aria-activedescendant": activeIndex >= 0 ? optionId(activeIndex) : undefined,
232
+ onKeyDown: handleListboxKeyDown,
233
+ className: "font-select-listbox",
134
234
  style: {
135
235
  position: 'absolute',
136
236
  top: '100%',
@@ -145,47 +245,31 @@ const FontSelectField = ({ field, path })=>{
145
245
  boxShadow: '0 8px 24px rgba(15, 23, 42, 0.15)',
146
246
  zIndex: 1000
147
247
  },
148
- children: options.map((option)=>{
248
+ children: options.map((option, index)=>{
149
249
  const isSelected = option.value === value;
150
- let fontFamily = option.fontFamily;
151
- if (!fontFamily) {
152
- if (option.category === 'sans-serif') fontFamily = 'system-ui, sans-serif';
153
- else if (option.category === 'serif') fontFamily = 'Georgia, serif';
154
- else if (option.category === 'monospace') fontFamily = 'monospace';
155
- else fontFamily = 'inherit';
156
- }
157
- return /*#__PURE__*/ _jsxs("button", {
158
- type: "button",
159
- onClick: ()=>handleSelect(option.value),
250
+ const isActive = index === activeIndex;
251
+ return /*#__PURE__*/ _jsxs("div", {
252
+ id: optionId(index),
253
+ role: "option",
254
+ "aria-selected": isSelected,
255
+ onClick: ()=>handleSelect(index),
256
+ onPointerMove: ()=>setActiveIndex(index),
160
257
  style: {
161
- width: '250px',
162
258
  padding: '12px 14px',
163
- border: 'none',
164
- backgroundColor: isSelected ? 'var(--theme-elevation-100)' : 'transparent',
259
+ backgroundColor: isActive ? 'var(--theme-elevation-100)' : isSelected ? 'var(--theme-elevation-50)' : 'transparent',
260
+ // The active option gets an outline so keyboard focus never relies on a subtle fill alone.
261
+ outline: isActive ? '2px solid var(--theme-elevation-800)' : undefined,
262
+ outlineOffset: '-2px',
165
263
  color: 'var(--theme-elevation-800)',
166
- textAlign: 'left',
167
264
  cursor: 'pointer',
168
265
  fontSize: '14px',
169
- fontFamily,
266
+ fontFamily: previewFamily(option),
170
267
  display: 'flex',
171
268
  flexDirection: 'column',
172
- gap: '4px',
173
- transition: 'background-color 0.15s ease'
174
- },
175
- onMouseEnter: (e)=>{
176
- if (!isSelected) {
177
- e.currentTarget.style.backgroundColor = 'var(--theme-elevation-50)';
178
- }
179
- // Lazy-load a preview font when hovering
180
- ensureFontLoaded(option.value);
181
- },
182
- onMouseLeave: (e)=>{
183
- if (!isSelected) {
184
- e.currentTarget.style.backgroundColor = 'transparent';
185
- }
269
+ gap: '4px'
186
270
  },
187
271
  children: [
188
- /*#__PURE__*/ _jsxs("div", {
272
+ /*#__PURE__*/ _jsxs("span", {
189
273
  style: {
190
274
  fontWeight: isSelected ? 600 : 500,
191
275
  display: 'flex',
@@ -193,34 +277,23 @@ const FontSelectField = ({ field, path })=>{
193
277
  gap: '8px'
194
278
  },
195
279
  children: [
196
- isSelected && /*#__PURE__*/ _jsx("svg", {
197
- width: "14",
198
- height: "14",
199
- viewBox: "0 0 14 14",
200
- fill: "none",
201
- children: /*#__PURE__*/ _jsx("path", {
202
- d: "M11.6667 3.5L5.25 9.91667L2.33334 7",
203
- stroke: "var(--theme-elevation-500)",
204
- strokeWidth: "2",
205
- strokeLinecap: "round",
206
- strokeLinejoin: "round"
207
- })
280
+ isSelected && /*#__PURE__*/ _jsx(Check, {
281
+ size: 14,
282
+ color: "var(--theme-elevation-600)",
283
+ "aria-hidden": true
208
284
  }),
209
285
  getOptionLabel(option)
210
286
  ]
211
287
  }),
212
- /*#__PURE__*/ _jsx("div", {
288
+ /*#__PURE__*/ _jsx("span", {
289
+ "aria-hidden": true,
213
290
  style: {
214
291
  fontSize: '16px',
215
292
  color: 'var(--theme-elevation-600)',
216
293
  fontStyle: option.value === 'preset' || option.value === 'custom' ? 'italic' : 'normal',
217
294
  fontFamily: option.fontFamily || undefined
218
295
  },
219
- children: (()=>{
220
- if (option.value === 'preset') return t.ui.usePreset;
221
- if (option.value === 'custom') return t.ui.specifyCustom;
222
- return t.ui.sampleSentence;
223
- })()
296
+ children: option.value === 'preset' ? t.ui.usePreset : option.value === 'custom' ? t.ui.specifyCustom : t.ui.sampleSentence
224
297
  })
225
298
  ]
226
299
  }, option.value);
@@ -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 { useField } from '@payloadcms/ui'\nimport { Check, ChevronDown } from 'lucide-react'\nimport type { SelectFieldClientComponent } from 'payload'\nimport { useCallback, useEffect, useId, useRef, useState, type KeyboardEvent } from 'react'\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?: 'sans-serif' | 'serif' | 'monospace' | 'display'\n}\n\nconst NON_GOOGLE_VALUES = ['preset', 'custom', 'system-ui']\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. Preview fonts load for whichever option\n * is active, so keyboard users get the same previews as mouse users.\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 const loadedFonts = useRef<Set<string>>(new Set())\n\n const options = (field.options as FontOption[]) || []\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 const ensureFontLoaded = useCallback((fontValue: string | undefined) => {\n if (!fontValue || NON_GOOGLE_VALUES.includes(fontValue)) return\n if (loadedFonts.current.has(fontValue) || typeof document === 'undefined') return\n loadedFonts.current.add(fontValue)\n\n const linkId = `font-${fontValue.replace(/\\s+/g, '-')}`\n if (document.getElementById(linkId)) return\n\n const link = document.createElement('link')\n link.id = linkId\n link.rel = 'stylesheet'\n link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(fontValue)}:wght@400;700&display=swap`\n document.head.appendChild(link)\n }, [])\n\n const open = useCallback(\n (index = selectedIndex >= 0 ? selectedIndex : 0) => {\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 // Load the preview font for the active option and keep it scrolled into view.\n useEffect(() => {\n if (!isOpen || activeIndex < 0) return\n ensureFontLoaded(options[activeIndex]?.value)\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 return (\n <div\n ref={containerRef}\n className=\"font-select-container\"\n style={{ position: 'relative', marginBottom: '12px', maxWidth: '360px' }}\n >\n <div\n id={labelId}\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 </div>\n\n <button\n ref={triggerRef}\n type=\"button\"\n className=\"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 style={{\n width: '100%',\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 gap: '8px',\n fontSize: '14px',\n fontFamily: selectedOption?.fontFamily || 'inherit',\n }}\n >\n <span id={`${baseId}-value`} style={{ flex: 1, minWidth: 0 }}>\n {selectedOption ? getOptionLabel(selectedOption) : t.ui.selectFontPlaceholder}\n </span>\n <ChevronDown\n size={14}\n aria-hidden\n style={{ flexShrink: 0, transform: isOpen ? 'rotate(180deg)' : undefined }}\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=\"font-select-listbox\"\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, index) => {\n const isSelected = option.value === value\n const isActive = index === activeIndex\n\n return (\n <div\n key={option.value}\n id={optionId(index)}\n role=\"option\"\n aria-selected={isSelected}\n onClick={() => handleSelect(index)}\n onPointerMove={() => setActiveIndex(index)}\n style={{\n padding: '12px 14px',\n backgroundColor: isActive\n ? 'var(--theme-elevation-100)'\n : isSelected\n ? 'var(--theme-elevation-50)'\n : 'transparent',\n // The active option gets an outline so keyboard focus never relies on a subtle fill alone.\n outline: isActive ? '2px solid var(--theme-elevation-800)' : undefined,\n outlineOffset: '-2px',\n color: 'var(--theme-elevation-800)',\n cursor: 'pointer',\n fontSize: '14px',\n fontFamily: previewFamily(option),\n display: 'flex',\n flexDirection: 'column',\n gap: '4px',\n }}\n >\n <span\n style={{\n fontWeight: isSelected ? 600 : 500,\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n }}\n >\n {isSelected && <Check size={14} color=\"var(--theme-elevation-600)\" aria-hidden />}\n {getOptionLabel(option)}\n </span>\n <span\n aria-hidden\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 {option.value === 'preset'\n ? t.ui.usePreset\n : option.value === 'custom'\n ? t.ui.specifyCustom\n : t.ui.sampleSentence}\n </span>\n </div>\n )\n })}\n </div>\n )}\n </div>\n )\n}\n\nexport default FontSelectField\n"],"names":["useField","Check","ChevronDown","useCallback","useEffect","useId","useRef","useState","useThemeLanguage","useThemeTranslations","resolveLocalizedText","NON_GOOGLE_VALUES","previewFamily","option","fontFamily","category","FontSelectField","field","path","value","setValue","isOpen","setIsOpen","activeIndex","setActiveIndex","lang","t","baseId","labelId","listboxId","containerRef","triggerRef","listboxRef","loadedFonts","Set","options","selectedIndex","findIndex","opt","selectedOption","undefined","getOptionLabel","label","optionId","index","ensureFontLoaded","fontValue","includes","current","has","document","add","linkId","replace","getElementById","link","createElement","id","rel","href","encodeURIComponent","head","appendChild","open","close","restoreFocus","focus","handleSelect","querySelector","CSS","escape","scrollIntoView","block","handlePointerOutside","e","contains","target","addEventListener","removeEventListener","handleTriggerKeyDown","event","key","preventDefault","length","handleListboxKeyDown","last","i","Math","min","max","stopPropagation","test","letter","toLowerCase","start","ordered","slice","match","find","startsWith","indexOf","div","ref","className","style","position","marginBottom","maxWidth","display","fontSize","fontWeight","color","button","type","role","aria-haspopup","aria-expanded","aria-controls","aria-labelledby","onClick","onKeyDown","width","padding","borderRadius","border","backgroundColor","textAlign","cursor","justifyContent","alignItems","gap","span","flex","minWidth","ui","selectFontPlaceholder","size","aria-hidden","flexShrink","transform","tabIndex","aria-activedescendant","top","left","right","marginTop","maxHeight","overflowY","boxShadow","zIndex","map","isSelected","isActive","aria-selected","onPointerMove","outline","outlineOffset","flexDirection","fontStyle","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"}