@pixelated-tech/components 3.3.0 → 3.3.2

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 (63) hide show
  1. package/README.COMPONENTS.md +232 -2
  2. package/README.md +133 -27
  3. package/dist/components/cms/cloudinary.image.js +1 -0
  4. package/dist/components/cms/wordpress.components.js +1 -0
  5. package/dist/components/general/sidepanel.js +1 -1
  6. package/dist/components/menu/menu-accordion.css +10 -0
  7. package/dist/components/menu/menu-accordion.js +2 -1
  8. package/dist/components/menu/menu-simple.css +0 -7
  9. package/dist/components/sitebuilder/config/CompoundFontSelector.css +25 -0
  10. package/dist/components/sitebuilder/config/CompoundFontSelector.js +41 -0
  11. package/dist/components/sitebuilder/config/ConfigBuilder.css +11 -0
  12. package/dist/components/sitebuilder/config/ConfigBuilder.js +188 -29
  13. package/dist/components/sitebuilder/config/ConfigEngine.js +99 -0
  14. package/dist/components/sitebuilder/config/FontSelector.css +82 -0
  15. package/dist/components/sitebuilder/config/FontSelector.js +86 -0
  16. package/dist/components/sitebuilder/config/fonts.js +51 -0
  17. package/dist/components/sitebuilder/config/google-fonts.js +112 -0
  18. package/dist/components/sitebuilder/form/formbuilder.js +1 -0
  19. package/dist/components/sitebuilder/form/formcomponents.js +28 -4
  20. package/dist/components/structured/markdown.js +1 -0
  21. package/dist/components/structured/recipe.js +1 -0
  22. package/dist/components/structured/timeline.js +1 -0
  23. package/dist/css/pixelated.global.css +0 -35
  24. package/dist/css/pixelated.grid.scss +4 -0
  25. package/dist/css/pixelated.visualdesign.scss +88 -0
  26. package/dist/data/routes.json +34 -3
  27. package/dist/data/visualdesignform.json +244 -0
  28. package/dist/index.js +5 -0
  29. package/dist/index.server.js +4 -3
  30. package/dist/types/components/cms/cloudinary.image.d.ts.map +1 -1
  31. package/dist/types/components/cms/wordpress.components.d.ts.map +1 -1
  32. package/dist/types/components/menu/menu-accordion.d.ts.map +1 -1
  33. package/dist/types/components/sitebuilder/config/CompoundFontSelector.d.ts +23 -0
  34. package/dist/types/components/sitebuilder/config/CompoundFontSelector.d.ts.map +1 -0
  35. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts +270 -2
  36. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts.map +1 -1
  37. package/dist/types/components/sitebuilder/config/ConfigEngine.d.ts +10 -0
  38. package/dist/types/components/sitebuilder/config/ConfigEngine.d.ts.map +1 -0
  39. package/dist/types/components/sitebuilder/config/FontSelector.d.ts +27 -0
  40. package/dist/types/components/sitebuilder/config/FontSelector.d.ts.map +1 -0
  41. package/dist/types/components/sitebuilder/config/fonts.d.ts +9 -0
  42. package/dist/types/components/sitebuilder/config/fonts.d.ts.map +1 -0
  43. package/dist/types/components/sitebuilder/config/google-fonts.d.ts +41 -0
  44. package/dist/types/components/sitebuilder/config/google-fonts.d.ts.map +1 -0
  45. package/dist/types/components/sitebuilder/form/formbuilder.d.ts.map +1 -1
  46. package/dist/types/components/sitebuilder/form/formcomponents.d.ts +3 -1
  47. package/dist/types/components/sitebuilder/form/formcomponents.d.ts.map +1 -1
  48. package/dist/types/components/structured/markdown.d.ts.map +1 -1
  49. package/dist/types/components/structured/recipe.d.ts.map +1 -1
  50. package/dist/types/components/structured/timeline.d.ts.map +1 -1
  51. package/dist/types/index.d.ts +5 -0
  52. package/dist/types/index.server.d.ts +4 -3
  53. package/dist/types/stories/sitebuilder/compoundfontselector.stories.d.ts +51 -0
  54. package/dist/types/stories/sitebuilder/compoundfontselector.stories.d.ts.map +1 -0
  55. package/dist/types/stories/sitebuilder/configbuilder.stories.d.ts +55 -0
  56. package/dist/types/stories/sitebuilder/configbuilder.stories.d.ts.map +1 -1
  57. package/dist/types/tests/compoundfontselector.test.d.ts +2 -0
  58. package/dist/types/tests/compoundfontselector.test.d.ts.map +1 -0
  59. package/dist/types/tests/configengine.test.d.ts +2 -0
  60. package/dist/types/tests/configengine.test.d.ts.map +1 -0
  61. package/dist/types/tests/fontselector.test.d.ts +2 -0
  62. package/dist/types/tests/fontselector.test.d.ts.map +1 -0
  63. package/package.json +11 -9
@@ -150,6 +150,17 @@
150
150
  margin-bottom: 15px;
151
151
  }
152
152
 
153
+ .route-headers {
154
+ display: grid;
155
+ grid-template-columns: 2fr 2fr 2fr 2fr auto;
156
+ gap: 10px;
157
+ align-items: center;
158
+ padding: 10px;
159
+ font-weight: bold;
160
+ color: #333;
161
+ margin-bottom: 10px;
162
+ }
163
+
153
164
  .route-item {
154
165
  display: grid;
155
166
  grid-template-columns: 2fr 2fr 2fr 2fr auto;
@@ -1,9 +1,13 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useState, useEffect } from 'react';
3
4
  import PropTypes from 'prop-types';
4
5
  import { Tab } from '../../general/tab';
6
+ import { Accordion } from '../../general/accordion';
5
7
  import { FormEngine } from '../form/formengine';
6
8
  import siteInfoForm from '../../../data/siteinfo-form.json';
9
+ import visualDesignForm from '../../../data/visualdesignform.json';
10
+ import defaultConfigData from '../../../data/routes.json';
7
11
  import './ConfigBuilder.css';
8
12
  const SiteInfoPropTypes = {
9
13
  name: PropTypes.string.isRequired,
@@ -39,38 +43,162 @@ const RoutePropTypes = {
39
43
  title: PropTypes.string,
40
44
  description: PropTypes.string,
41
45
  };
46
+ const VisualDesignPropTypes = {
47
+ 'primary-color': PropTypes.shape({
48
+ value: PropTypes.string.isRequired,
49
+ type: PropTypes.string.isRequired,
50
+ group: PropTypes.string.isRequired,
51
+ label: PropTypes.string.isRequired
52
+ }).isRequired,
53
+ 'secondary-color': PropTypes.shape({
54
+ value: PropTypes.string.isRequired,
55
+ type: PropTypes.string.isRequired,
56
+ group: PropTypes.string.isRequired,
57
+ label: PropTypes.string.isRequired
58
+ }).isRequired,
59
+ 'accent1-color': PropTypes.shape({
60
+ value: PropTypes.string.isRequired,
61
+ type: PropTypes.string.isRequired,
62
+ group: PropTypes.string.isRequired,
63
+ label: PropTypes.string.isRequired
64
+ }).isRequired,
65
+ 'accent2-color': PropTypes.shape({
66
+ value: PropTypes.string.isRequired,
67
+ type: PropTypes.string.isRequired,
68
+ group: PropTypes.string.isRequired,
69
+ label: PropTypes.string.isRequired
70
+ }).isRequired,
71
+ 'bg-color': PropTypes.shape({
72
+ value: PropTypes.string.isRequired,
73
+ type: PropTypes.string.isRequired,
74
+ group: PropTypes.string.isRequired,
75
+ label: PropTypes.string.isRequired
76
+ }).isRequired,
77
+ 'text-color': PropTypes.shape({
78
+ value: PropTypes.string.isRequired,
79
+ type: PropTypes.string.isRequired,
80
+ group: PropTypes.string.isRequired,
81
+ label: PropTypes.string.isRequired
82
+ }).isRequired,
83
+ 'header-font': PropTypes.shape({
84
+ value: PropTypes.string.isRequired,
85
+ type: PropTypes.string.isRequired,
86
+ group: PropTypes.string.isRequired,
87
+ label: PropTypes.string.isRequired
88
+ }).isRequired,
89
+ 'body-font': PropTypes.shape({
90
+ value: PropTypes.string.isRequired,
91
+ type: PropTypes.string.isRequired,
92
+ group: PropTypes.string.isRequired,
93
+ label: PropTypes.string.isRequired
94
+ }).isRequired,
95
+ 'font-size1-min': PropTypes.shape({
96
+ value: PropTypes.string.isRequired,
97
+ type: PropTypes.string.isRequired,
98
+ group: PropTypes.string.isRequired,
99
+ label: PropTypes.string.isRequired
100
+ }).isRequired,
101
+ 'font-size1-max': PropTypes.shape({
102
+ value: PropTypes.string.isRequired,
103
+ type: PropTypes.string.isRequired,
104
+ group: PropTypes.string.isRequired,
105
+ label: PropTypes.string.isRequired
106
+ }).isRequired,
107
+ 'font-size2-min': PropTypes.shape({
108
+ value: PropTypes.string.isRequired,
109
+ type: PropTypes.string.isRequired,
110
+ group: PropTypes.string.isRequired,
111
+ label: PropTypes.string.isRequired
112
+ }).isRequired,
113
+ 'font-size2-max': PropTypes.shape({
114
+ value: PropTypes.string.isRequired,
115
+ type: PropTypes.string.isRequired,
116
+ group: PropTypes.string.isRequired,
117
+ label: PropTypes.string.isRequired
118
+ }).isRequired,
119
+ 'font-size3-min': PropTypes.shape({
120
+ value: PropTypes.string.isRequired,
121
+ type: PropTypes.string.isRequired,
122
+ group: PropTypes.string.isRequired,
123
+ label: PropTypes.string.isRequired
124
+ }).isRequired,
125
+ 'font-size3-max': PropTypes.shape({
126
+ value: PropTypes.string.isRequired,
127
+ type: PropTypes.string.isRequired,
128
+ group: PropTypes.string.isRequired,
129
+ label: PropTypes.string.isRequired
130
+ }).isRequired,
131
+ 'font-size4-min': PropTypes.shape({
132
+ value: PropTypes.string.isRequired,
133
+ type: PropTypes.string.isRequired,
134
+ group: PropTypes.string.isRequired,
135
+ label: PropTypes.string.isRequired
136
+ }).isRequired,
137
+ 'font-size4-max': PropTypes.shape({
138
+ value: PropTypes.string.isRequired,
139
+ type: PropTypes.string.isRequired,
140
+ group: PropTypes.string.isRequired,
141
+ label: PropTypes.string.isRequired
142
+ }).isRequired,
143
+ 'font-size5-min': PropTypes.shape({
144
+ value: PropTypes.string.isRequired,
145
+ type: PropTypes.string.isRequired,
146
+ group: PropTypes.string.isRequired,
147
+ label: PropTypes.string.isRequired
148
+ }).isRequired,
149
+ 'font-size5-max': PropTypes.shape({
150
+ value: PropTypes.string.isRequired,
151
+ type: PropTypes.string.isRequired,
152
+ group: PropTypes.string.isRequired,
153
+ label: PropTypes.string.isRequired
154
+ }).isRequired,
155
+ 'font-size6-min': PropTypes.shape({
156
+ value: PropTypes.string.isRequired,
157
+ type: PropTypes.string.isRequired,
158
+ group: PropTypes.string.isRequired,
159
+ label: PropTypes.string.isRequired
160
+ }).isRequired,
161
+ 'font-size6-max': PropTypes.shape({
162
+ value: PropTypes.string.isRequired,
163
+ type: PropTypes.string.isRequired,
164
+ group: PropTypes.string.isRequired,
165
+ label: PropTypes.string.isRequired
166
+ }).isRequired,
167
+ 'font-min-screen': PropTypes.shape({
168
+ value: PropTypes.string.isRequired,
169
+ type: PropTypes.string.isRequired,
170
+ group: PropTypes.string.isRequired,
171
+ label: PropTypes.string.isRequired
172
+ }).isRequired,
173
+ 'font-max-screen': PropTypes.shape({
174
+ value: PropTypes.string.isRequired,
175
+ type: PropTypes.string.isRequired,
176
+ group: PropTypes.string.isRequired,
177
+ label: PropTypes.string.isRequired
178
+ }).isRequired,
179
+ };
42
180
  const SiteConfigPropTypes = {
43
181
  siteInfo: PropTypes.shape(SiteInfoPropTypes).isRequired,
44
182
  routes: PropTypes.arrayOf(PropTypes.shape(RoutePropTypes).isRequired).isRequired,
183
+ visualdesign: PropTypes.shape(VisualDesignPropTypes),
45
184
  };
46
185
  const ConfigBuilderPropTypes = {
47
186
  initialConfig: PropTypes.shape(SiteConfigPropTypes),
48
187
  onSave: PropTypes.func,
49
188
  };
50
- export function ConfigBuilder({ initialConfig, onSave }) {
51
- const [config, setConfig] = useState(initialConfig || {
52
- siteInfo: {
53
- name: '',
54
- author: '',
55
- description: '',
56
- url: '',
57
- email: '',
58
- favicon: '/favicon.ico',
59
- favicon_sizes: '64x64 32x32 24x24 16x16',
60
- favicon_type: 'image/x-icon',
61
- theme_color: '#ffffff',
62
- background_color: '#ffffff',
63
- default_locale: 'en',
64
- display: 'standalone',
65
- address: {
66
- streetAddress: '',
67
- addressLocality: '',
68
- addressRegion: '',
69
- postalCode: '',
70
- addressCountry: ''
71
- }
72
- },
73
- routes: []
189
+ export function ConfigBuilder(props) {
190
+ const { initialConfig, onSave } = props;
191
+ const defaultConfig = {
192
+ siteInfo: defaultConfigData.siteInfo,
193
+ routes: [], // Start with empty routes, the JSON structure is different
194
+ visualdesign: defaultConfigData.visualdesign
195
+ };
196
+ const [config, setConfig] = useState({
197
+ ...defaultConfig,
198
+ ...initialConfig,
199
+ siteInfo: { ...defaultConfig.siteInfo, ...initialConfig?.siteInfo },
200
+ routes: initialConfig?.routes || [],
201
+ visualdesign: { ...defaultConfig.visualdesign, ...initialConfig?.visualdesign }
74
202
  });
75
203
  const [socialLinks, setSocialLinks] = useState(initialConfig?.siteInfo?.sameAs || ['']);
76
204
  const [isFormValid, setIsFormValid] = useState(false);
@@ -114,9 +242,10 @@ export function ConfigBuilder({ initialConfig, onSave }) {
114
242
  };
115
243
  useEffect(() => {
116
244
  if (initialConfig) {
117
- setConfig(prev => ({
245
+ setConfig((prev) => ({
118
246
  siteInfo: { ...prev.siteInfo, ...initialConfig.siteInfo },
119
- routes: initialConfig.routes || []
247
+ routes: initialConfig.routes || [],
248
+ visualdesign: initialConfig.visualdesign || prev.visualdesign || {}
120
249
  }));
121
250
  setSocialLinks(initialConfig.siteInfo?.sameAs || ['']);
122
251
  }
@@ -130,7 +259,7 @@ export function ConfigBuilder({ initialConfig, onSave }) {
130
259
  value: config.siteInfo[field.props.name] || '',
131
260
  defaultValue: config.siteInfo[field.props.name] || field.props.defaultValue || '',
132
261
  onChange: (value) => {
133
- setConfig(prev => ({
262
+ setConfig((prev) => ({
134
263
  ...prev,
135
264
  siteInfo: {
136
265
  ...prev.siteInfo,
@@ -141,6 +270,26 @@ export function ConfigBuilder({ initialConfig, onSave }) {
141
270
  }
142
271
  }))
143
272
  };
273
+ // Visual Design form data
274
+ const visualFormData = {
275
+ fields: (visualDesignForm.fields || []).map((field) => ({
276
+ ...field,
277
+ props: {
278
+ ...field.props,
279
+ value: (config.visualdesign && config.visualdesign[field.props.name]) ? (config.visualdesign[field.props.name].value ?? config.visualdesign[field.props.name]) : '',
280
+ defaultValue: (config.visualdesign && config.visualdesign[field.props.name]) ? (config.visualdesign[field.props.name].value ?? config.visualdesign[field.props.name]) : field.props.defaultValue || '',
281
+ onChange: (value) => {
282
+ setConfig((prev) => ({
283
+ ...prev,
284
+ visualdesign: {
285
+ ...(prev.visualdesign || {}),
286
+ [field.props.name]: { value }
287
+ }
288
+ }));
289
+ }
290
+ }
291
+ }))
292
+ };
144
293
  const handleFormSubmit = (event) => {
145
294
  event.preventDefault();
146
295
  const formData = new FormData(event.target);
@@ -214,8 +363,18 @@ export function ConfigBuilder({ initialConfig, onSave }) {
214
363
  {
215
364
  id: 'routes',
216
365
  label: 'Routes',
217
- content: (_jsxs("div", { className: "routes-section", children: [_jsx("div", { className: "routes-list", children: config.routes.map((route, index) => (_jsxs("div", { className: "route-item", children: [_jsx("input", { type: "text", placeholder: "Path", value: route.path, onChange: (e) => updateRoute(index, 'path', e.target.value) }), _jsx("input", { type: "text", placeholder: "Component", value: route.component, onChange: (e) => updateRoute(index, 'component', e.target.value) }), _jsx("input", { type: "text", placeholder: "Title", value: route.title || '', onChange: (e) => updateRoute(index, 'title', e.target.value) }), _jsx("input", { type: "text", placeholder: "Description", value: route.description || '', onChange: (e) => updateRoute(index, 'description', e.target.value) }), _jsx("button", { onClick: () => removeRoute(index), children: "Remove" })] }, index))) }), _jsx("button", { onClick: addRoute, children: "Add Route" })] }))
366
+ content: (_jsxs("div", { className: "routes-section", children: [_jsxs("div", { className: "routes-list", children: [_jsxs("div", { className: "route-headers", children: [_jsx("span", { children: "Path" }), _jsx("span", { children: "Component" }), _jsx("span", { children: "Title" }), _jsx("span", { children: "Description" }), _jsx("span", { children: "Actions" })] }), config.routes.map((route, index) => (_jsxs("div", { className: "route-item", children: [_jsx("input", { type: "text", placeholder: "Path", value: route.path, onChange: (e) => updateRoute(index, 'path', e.target.value) }), _jsx("input", { type: "text", placeholder: "Component", value: route.component, onChange: (e) => updateRoute(index, 'component', e.target.value) }), _jsx("input", { type: "text", placeholder: "Title", value: route.title || '', onChange: (e) => updateRoute(index, 'title', e.target.value) }), _jsx("input", { type: "text", placeholder: "Description", value: route.description || '', onChange: (e) => updateRoute(index, 'description', e.target.value) }), _jsx("button", { onClick: () => removeRoute(index), children: "Remove" })] }, index)))] }), _jsx("button", { onClick: addRoute, children: "Add Route" })] }))
367
+ },
368
+ {
369
+ id: 'visualdesign',
370
+ label: 'Visual Design',
371
+ content: (_jsx("div", { className: "visual-design-section", children: _jsx(FormEngine, { formData: visualFormData, name: "visualdesign", id: "visualdesign" }) }))
372
+ }
373
+ ], orientation: "top" }), _jsx("button", { onClick: handleSave, disabled: !isFormValid, className: isFormValid ? 'save-button-valid' : 'save-button-invalid', children: "Save Config" }), !isFormValid && (_jsx("div", { className: "validation-message", children: "Please fill in all required fields (marked with *) before saving." })), _jsx(Accordion, { items: [
374
+ {
375
+ title: 'Configuration Preview',
376
+ content: _jsx("pre", { children: JSON.stringify(config, null, 2) })
218
377
  }
219
- ], orientation: "top" }), _jsx("button", { onClick: handleSave, disabled: !isFormValid, className: isFormValid ? 'save-button-valid' : 'save-button-invalid', children: "Save Config" }), !isFormValid && (_jsx("div", { className: "validation-message", children: "Please fill in all required fields (marked with *) before saving." })), _jsx("pre", { children: JSON.stringify(config, null, 2) })] }));
378
+ ] })] }));
220
379
  }
221
380
  ConfigBuilder.propTypes = ConfigBuilderPropTypes;
@@ -0,0 +1,99 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { generateGoogleFontsUrl } from './google-fonts';
3
+ import { ALL_WEBSAFE_FONTS } from './fonts';
4
+ export function VisualDesignStyles({ visualdesign }) {
5
+ const tokens = visualdesign || {};
6
+ const resolveValue = (v) => (v && typeof v === 'object' && 'value' in v) ? v.value : v;
7
+ // Check if Google Fonts are being used
8
+ const hasGoogleFonts = () => {
9
+ for (const [key, val] of Object.entries(tokens)) {
10
+ const value = resolveValue(val);
11
+ if ((key === 'header-font' || key === 'body-font') && typeof value === 'string') {
12
+ // Check if the font stack contains non-web-safe fonts
13
+ const fonts = value.split(',').map((f) => f.trim().replace(/["']/g, ''));
14
+ for (const font of fonts) {
15
+ if (!ALL_WEBSAFE_FONTS.some(f => f.value === font)) {
16
+ return true;
17
+ }
18
+ }
19
+ }
20
+ }
21
+ return false;
22
+ };
23
+ const varLines = [];
24
+ // Always include base font sizing first (from pixelated.visualdesign.scss)
25
+ varLines.push('--font-size1-min: 2.00rem;', '--font-size1-max: 3.00rem;', '--font-size2-min: 1.35rem;', '--font-size2-max: 1.75rem;', '--font-size3-min: 1.17rem;', '--font-size3-max: 1.35rem;', '--font-size4-min: 1.00rem;', '--font-size4-max: 1.25rem;', '--font-size5-min: 0.83rem;', '--font-size5-max: 1.00rem;', '--font-size6-min: 0.67rem;', '--font-size6-max: 0.85rem;', '--font-min-screen: 375px;', '--font-max-screen: 1440px;');
26
+ for (const [key, val] of Object.entries(tokens)) {
27
+ const value = resolveValue(val);
28
+ if (value === undefined || value === null)
29
+ continue;
30
+ // Handle font family construction from 3-field structure (legacy)
31
+ if (key.endsWith('-primary')) {
32
+ const baseKey = key.replace('-primary', '');
33
+ const primary = value;
34
+ const fallback = resolveValue(tokens[`${baseKey}-fallback`]);
35
+ const generic = resolveValue(tokens[`${baseKey}-generic`]);
36
+ // Build font stack: "Primary Font", Fallback, generic
37
+ const fontStack = [primary, fallback, generic].filter(Boolean).map(f => `"${f}"`).join(', ');
38
+ varLines.push(`--${baseKey}-family: ${fontStack};`);
39
+ }
40
+ else if (key === 'header-font' || key === 'body-font') {
41
+ // Handle compound font values (new format)
42
+ varLines.push(`--${key}: ${value};`);
43
+ }
44
+ else if (!key.endsWith('-fallback') && !key.endsWith('-generic')) {
45
+ // Skip fallback and generic fields as they're handled above
46
+ varLines.push(`--${key}: ${value};`);
47
+ }
48
+ }
49
+ const fontLines = [];
50
+ // Generate clamp calculations for font sizes
51
+ for (let i = 1; i <= 6; i++) {
52
+ varLines.push(`--font-size${i}: clamp(var(--font-size${i}-min), calc(var(--font-size${i}-min) + ((var(--font-size${i}-max) - var(--font-size${i}-min)) * ((100vw - var(--font-min-screen)) / (var(--font-max-screen) - var(--font-min-screen))))), var(--font-size${i}-max));`);
53
+ }
54
+ // Generate h1-h6 font-size rules
55
+ for (let i = 1; i <= 6; i++) {
56
+ fontLines.push(`h${i} { font-size: var(--font-size${i}); }`);
57
+ }
58
+ const css = [
59
+ ':root {',
60
+ ...varLines.map(l => ` ${l}`),
61
+ '}',
62
+ '',
63
+ '/* Base visual design styles */',
64
+ 'body {',
65
+ ' background-color: var(--bg-color) !important;',
66
+ ' color: var(--text-color);',
67
+ ' font-family: var(--body-font);',
68
+ '}',
69
+ 'h1, h2, h3, h4, h5, h6 {',
70
+ ' line-height: 1.1;',
71
+ ' font-family: var(--header-font);',
72
+ '}',
73
+ 'html { font-size: 1.0rem; /* 16px; */ }',
74
+ '',
75
+ ...fontLines
76
+ ].join('\n');
77
+ const googleFontsUsed = hasGoogleFonts();
78
+ return (_jsxs(_Fragment, { children: [googleFontsUsed && (_jsxs(_Fragment, { children: [_jsx("link", { rel: "preconnect", href: "https://fonts.googleapis.com" }), _jsx("link", { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" })] })), _jsx("style", { dangerouslySetInnerHTML: { __html: css } })] }));
79
+ }
80
+ /**
81
+ * Component to handle Google Fonts imports - should be used in the document head
82
+ */
83
+ export function GoogleFontsImports({ visualdesign }) {
84
+ const tokens = visualdesign || {};
85
+ const fonts = [];
86
+ // Extract Google font names from the new 3-field font structure
87
+ for (const [key, val] of Object.entries(tokens)) {
88
+ if (key.endsWith('-primary') && typeof val === 'string' && val.trim()) {
89
+ // Only include fonts that are not web-safe (web-safe fonts don't need Google Fonts import)
90
+ if (!ALL_WEBSAFE_FONTS.some(f => f.value === val.trim())) {
91
+ fonts.push(val.trim());
92
+ }
93
+ }
94
+ }
95
+ const googleFontsUrl = generateGoogleFontsUrl(fonts);
96
+ if (!googleFontsUrl)
97
+ return null;
98
+ return (_jsxs(_Fragment, { children: [_jsx("link", { rel: "preconnect", href: "https://fonts.googleapis.com" }), _jsx("link", { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" }), _jsx("link", { href: googleFontsUrl, rel: "stylesheet" })] }));
99
+ }
@@ -0,0 +1,82 @@
1
+ .font-selector-container {
2
+ margin-bottom: 1rem;
3
+ }
4
+
5
+ .font-selector-label {
6
+ display: block;
7
+ margin-bottom: 0.5rem;
8
+ font-weight: 500;
9
+ }
10
+
11
+ .font-selector-required {
12
+ color: #e53e3e;
13
+ margin-left: 0.25rem;
14
+ }
15
+
16
+ .font-selector-tooltip {
17
+ margin-left: 0.5rem;
18
+ cursor: help;
19
+ opacity: 0.7;
20
+ }
21
+
22
+ .font-selector-input-container {
23
+ position: relative;
24
+ }
25
+
26
+ .font-selector-input {
27
+ width: 100%;
28
+ padding: 0.5rem;
29
+ border: 1px solid #d1d5db;
30
+ border-radius: 0.375rem;
31
+ font-size: 0.875rem;
32
+ }
33
+
34
+ .font-selector-input:focus {
35
+ outline: none;
36
+ border-color: #3b82f6;
37
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
38
+ }
39
+
40
+ .font-selector-dropdown {
41
+ position: absolute;
42
+ top: 100%;
43
+ left: 0;
44
+ right: 0;
45
+ background: white;
46
+ border: 1px solid #d1d5db;
47
+ border-radius: 0.375rem;
48
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
49
+ max-height: 200px;
50
+ overflow-y: auto;
51
+ z-index: 1000;
52
+ }
53
+
54
+ .font-selector-option {
55
+ padding: 0.5rem;
56
+ cursor: pointer;
57
+ border-bottom: 1px solid #f3f4f6;
58
+ }
59
+
60
+ .font-selector-option:hover {
61
+ background-color: #f9fafb;
62
+ }
63
+
64
+ .font-selector-option:last-child {
65
+ border-bottom: none;
66
+ }
67
+
68
+ .font-selector-loading {
69
+ padding: 0.5rem;
70
+ color: #6b7280;
71
+ font-style: italic;
72
+ }
73
+
74
+ .font-selector-font-name {
75
+ font-weight: 500;
76
+ }
77
+
78
+ .font-selector-font-category {
79
+ color: #6b7280;
80
+ font-size: 0.875rem;
81
+ margin-left: 0.5rem;
82
+ }
@@ -0,0 +1,86 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useMemo } from 'react';
3
+ import PropTypes from 'prop-types';
4
+ import { getFontOptions } from './google-fonts';
5
+ import { WEB_SAFE_FONTS, GENERIC_FAMILIES } from './fonts';
6
+ import './FontSelector.css';
7
+ export function FontSelector({ id, name, label, fontType, required = false, placeholder, value = '', onChange }) {
8
+ const [inputValue, setInputValue] = useState(value);
9
+ const [googleFonts, setGoogleFonts] = useState([]);
10
+ const [isLoading, setIsLoading] = useState(false);
11
+ const [showDropdown, setShowDropdown] = useState(false);
12
+ // Load Google Fonts for autocomplete
13
+ useEffect(() => {
14
+ if (fontType === 'google') {
15
+ setIsLoading(true);
16
+ getFontOptions().then(options => {
17
+ setGoogleFonts(options);
18
+ setIsLoading(false);
19
+ }).catch(() => {
20
+ setIsLoading(false);
21
+ });
22
+ }
23
+ }, [fontType]);
24
+ // Get options based on font type
25
+ const options = useMemo(() => {
26
+ switch (fontType) {
27
+ case 'google':
28
+ return googleFonts;
29
+ case 'websafe':
30
+ return WEB_SAFE_FONTS;
31
+ case 'generic':
32
+ return GENERIC_FAMILIES;
33
+ default:
34
+ return [];
35
+ }
36
+ }, [fontType, googleFonts]);
37
+ // Filter options based on input
38
+ const filteredOptions = useMemo(() => {
39
+ if (!inputValue)
40
+ return options.slice(0, 10); // Show first 10 when no input
41
+ return options.filter(option => option.label.toLowerCase().includes(inputValue.toLowerCase()) ||
42
+ option.value.toLowerCase().includes(inputValue.toLowerCase())).slice(0, 10); // Limit to 10 results
43
+ }, [options, inputValue]);
44
+ const handleInputChange = (e) => {
45
+ const newValue = e.target.value;
46
+ setInputValue(newValue);
47
+ setShowDropdown(true);
48
+ onChange?.(newValue);
49
+ };
50
+ const handleOptionSelect = (option) => {
51
+ setInputValue(option.value);
52
+ setShowDropdown(false);
53
+ onChange?.(option.value);
54
+ };
55
+ const handleFocus = () => {
56
+ setShowDropdown(true);
57
+ };
58
+ const handleBlur = () => {
59
+ // Delay hiding dropdown to allow clicks on options
60
+ setTimeout(() => setShowDropdown(false), 200);
61
+ };
62
+ // Generate tooltip with Google Fonts link for Google fonts
63
+ const getTooltip = () => {
64
+ if (fontType === 'google' && inputValue) {
65
+ const fontName = inputValue.replace(/\s+/g, '+');
66
+ return `👁️ [Preview ${inputValue} on Google Fonts](https://fonts.google.com/specimen/${fontName})`;
67
+ }
68
+ return null;
69
+ };
70
+ return (_jsxs("div", { className: "font-selector-container", children: [_jsxs("label", { htmlFor: id, className: "font-selector-label", children: [label, required && _jsx("span", { className: "font-selector-required", children: "*" }), getTooltip() && (_jsx("span", { className: "font-selector-tooltip", title: getTooltip().replace(/\[([^\]]+)\]\([^)]+\)/, '$1'), children: "\uD83D\uDC41\uFE0F" }))] }), _jsxs("div", { className: "font-selector-input-container", children: [_jsx("input", { type: "text", id: id, name: name, value: inputValue, onChange: handleInputChange, onFocus: handleFocus, onBlur: handleBlur, placeholder: placeholder, required: required, autoComplete: "off", className: "font-selector-input" }), showDropdown && filteredOptions.length > 0 && (_jsx("div", { className: "font-selector-dropdown", children: isLoading ? (_jsx("div", { className: "font-selector-loading", children: "Loading fonts..." })) : (filteredOptions.map((option) => (_jsxs("div", { className: "font-selector-option", onClick: () => handleOptionSelect(option), onKeyDown: (e) => {
71
+ if (e.key === 'Enter' || e.key === ' ') {
72
+ e.preventDefault();
73
+ handleOptionSelect(option);
74
+ }
75
+ }, tabIndex: 0, role: "option", "aria-selected": inputValue === option.value, children: [_jsx("span", { className: "font-selector-font-name", children: option.label }), option.category && (_jsxs("span", { className: "font-selector-font-category", children: ["(", option.category, ")"] }))] }, option.value)))) }))] })] }));
76
+ }
77
+ FontSelector.propTypes = {
78
+ id: PropTypes.string.isRequired,
79
+ name: PropTypes.string.isRequired,
80
+ label: PropTypes.string.isRequired,
81
+ fontType: PropTypes.oneOf(['google', 'websafe', 'generic']).isRequired,
82
+ required: PropTypes.bool,
83
+ placeholder: PropTypes.string,
84
+ value: PropTypes.string,
85
+ onChange: PropTypes.func,
86
+ };
@@ -0,0 +1,51 @@
1
+ // Web-safe fonts available on most operating systems
2
+ export const WEB_SAFE_FONTS = [
3
+ { value: 'Arial', label: 'Arial' },
4
+ { value: 'Bookman', label: 'Bookman' },
5
+ { value: 'Comic Sans MS', label: 'Comic Sans MS' },
6
+ { value: 'Courier', label: 'Courier' },
7
+ { value: 'Courier New', label: 'Courier New' },
8
+ { value: 'Garamond', label: 'Garamond' },
9
+ { value: 'Geneva', label: 'Geneva' },
10
+ { value: 'Georgia', label: 'Georgia' },
11
+ { value: 'Helvetica', label: 'Helvetica' },
12
+ { value: 'Helvetica Neue', label: 'Helvetica Neue' },
13
+ { value: 'Impact', label: 'Impact' },
14
+ { value: 'Lucida Console', label: 'Lucida Console' },
15
+ { value: 'Monaco', label: 'Monaco' },
16
+ { value: 'Palatino', label: 'Palatino' },
17
+ { value: 'Roboto', label: 'Roboto' },
18
+ { value: 'Segoe UI', label: 'Segoe UI' },
19
+ { value: 'System', label: 'System' },
20
+ { value: 'Apple System', label: 'Apple System' },
21
+ { value: 'BlinkMacSystemFont', label: 'BlinkMacSystemFont' },
22
+ { value: 'Tahoma', label: 'Tahoma' },
23
+ { value: 'Times', label: 'Times' },
24
+ { value: 'Times New Roman', label: 'Times New Roman' },
25
+ { value: 'Trebuchet MS', label: 'Trebuchet MS' },
26
+ { value: 'Verdana', label: 'Verdana' },
27
+ { value: 'New York', label: 'New York' },
28
+ { value: 'Oxygen', label: 'Oxygen' },
29
+ { value: 'Ubuntu', label: 'Ubuntu' },
30
+ { value: 'Cantarell', label: 'Cantarell' },
31
+ { value: 'Fira Sans', label: 'Fira Sans' },
32
+ { value: 'Droid Sans', label: 'Droid Sans' },
33
+ ];
34
+ // Generic font families that work across all systems
35
+ export const GENERIC_FAMILIES = [
36
+ { value: 'cursive', label: 'cursive' },
37
+ { value: 'fantasy', label: 'fantasy' },
38
+ { value: 'monospace', label: 'monospace' },
39
+ { value: 'sans-serif', label: 'sans-serif' },
40
+ { value: 'serif', label: 'serif' },
41
+ { value: 'system-ui', label: 'system-ui' },
42
+ { value: 'ui-monospace', label: 'ui-monospace' },
43
+ { value: 'ui-rounded', label: 'ui-rounded' },
44
+ { value: 'ui-sans-serif', label: 'ui-sans-serif' },
45
+ { value: 'ui-serif', label: 'ui-serif' },
46
+ ];
47
+ // Combined list of all web-safe fonts and generic families
48
+ export const ALL_WEBSAFE_FONTS = [
49
+ ...WEB_SAFE_FONTS,
50
+ ...GENERIC_FAMILIES
51
+ ];