@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.
- package/README.COMPONENTS.md +232 -2
- package/README.md +133 -27
- package/dist/components/cms/cloudinary.image.js +1 -0
- package/dist/components/cms/wordpress.components.js +1 -0
- package/dist/components/general/sidepanel.js +1 -1
- package/dist/components/menu/menu-accordion.css +10 -0
- package/dist/components/menu/menu-accordion.js +2 -1
- package/dist/components/menu/menu-simple.css +0 -7
- package/dist/components/sitebuilder/config/CompoundFontSelector.css +25 -0
- package/dist/components/sitebuilder/config/CompoundFontSelector.js +41 -0
- package/dist/components/sitebuilder/config/ConfigBuilder.css +11 -0
- package/dist/components/sitebuilder/config/ConfigBuilder.js +188 -29
- package/dist/components/sitebuilder/config/ConfigEngine.js +99 -0
- package/dist/components/sitebuilder/config/FontSelector.css +82 -0
- package/dist/components/sitebuilder/config/FontSelector.js +86 -0
- package/dist/components/sitebuilder/config/fonts.js +51 -0
- package/dist/components/sitebuilder/config/google-fonts.js +112 -0
- package/dist/components/sitebuilder/form/formbuilder.js +1 -0
- package/dist/components/sitebuilder/form/formcomponents.js +28 -4
- package/dist/components/structured/markdown.js +1 -0
- package/dist/components/structured/recipe.js +1 -0
- package/dist/components/structured/timeline.js +1 -0
- package/dist/css/pixelated.global.css +0 -35
- package/dist/css/pixelated.grid.scss +4 -0
- package/dist/css/pixelated.visualdesign.scss +88 -0
- package/dist/data/routes.json +34 -3
- package/dist/data/visualdesignform.json +244 -0
- package/dist/index.js +5 -0
- package/dist/index.server.js +4 -3
- package/dist/types/components/cms/cloudinary.image.d.ts.map +1 -1
- package/dist/types/components/cms/wordpress.components.d.ts.map +1 -1
- package/dist/types/components/menu/menu-accordion.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/config/CompoundFontSelector.d.ts +23 -0
- package/dist/types/components/sitebuilder/config/CompoundFontSelector.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts +270 -2
- package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/config/ConfigEngine.d.ts +10 -0
- package/dist/types/components/sitebuilder/config/ConfigEngine.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/config/FontSelector.d.ts +27 -0
- package/dist/types/components/sitebuilder/config/FontSelector.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/config/fonts.d.ts +9 -0
- package/dist/types/components/sitebuilder/config/fonts.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/config/google-fonts.d.ts +41 -0
- package/dist/types/components/sitebuilder/config/google-fonts.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/form/formbuilder.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/form/formcomponents.d.ts +3 -1
- package/dist/types/components/sitebuilder/form/formcomponents.d.ts.map +1 -1
- package/dist/types/components/structured/markdown.d.ts.map +1 -1
- package/dist/types/components/structured/recipe.d.ts.map +1 -1
- package/dist/types/components/structured/timeline.d.ts.map +1 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.server.d.ts +4 -3
- package/dist/types/stories/sitebuilder/compoundfontselector.stories.d.ts +51 -0
- package/dist/types/stories/sitebuilder/compoundfontselector.stories.d.ts.map +1 -0
- package/dist/types/stories/sitebuilder/configbuilder.stories.d.ts +55 -0
- package/dist/types/stories/sitebuilder/configbuilder.stories.d.ts.map +1 -1
- package/dist/types/tests/compoundfontselector.test.d.ts +2 -0
- package/dist/types/tests/compoundfontselector.test.d.ts.map +1 -0
- package/dist/types/tests/configengine.test.d.ts +2 -0
- package/dist/types/tests/configengine.test.d.ts.map +1 -0
- package/dist/types/tests/fontselector.test.d.ts +2 -0
- package/dist/types/tests/fontselector.test.d.ts.map +1 -0
- 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(
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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: [
|
|
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
|
-
]
|
|
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
|
+
];
|