@pixelated-tech/components 3.7.3 → 3.7.5
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/dist/components/admin/site-health/site-health-on-site-seo.integration.js +15 -0
- package/dist/components/general/callout.js +4 -3
- package/dist/components/general/intersection-observer.js +117 -0
- package/dist/components/general/microinteractions.js +39 -50
- package/dist/components/general/schema-services.js +17 -4
- package/dist/components/general/schema-website.js +100 -7
- package/dist/components/general/splitscroll.css +125 -0
- package/dist/components/general/splitscroll.js +89 -0
- package/dist/components/sitebuilder/config/ConfigBuilder.js +142 -25
- package/dist/components/sitebuilder/config/services-form.json +51 -0
- package/dist/components/sitebuilder/config/siteinfo-form.json +68 -0
- package/dist/components/sitebuilder/form/form.css +81 -0
- package/dist/components/sitebuilder/form/formcomponents.js +86 -0
- package/dist/data/form.json +12 -0
- package/dist/index.js +2 -0
- package/dist/scripts/setup-remotes.sh +69 -0
- package/dist/types/components/admin/site-health/site-health-on-site-seo.integration.d.ts.map +1 -1
- package/dist/types/components/config/config.types.d.ts +8 -1
- package/dist/types/components/config/config.types.d.ts.map +1 -1
- package/dist/types/components/general/callout.d.ts +3 -2
- package/dist/types/components/general/callout.d.ts.map +1 -1
- package/dist/types/components/general/intersection-observer.d.ts +73 -0
- package/dist/types/components/general/intersection-observer.d.ts.map +1 -0
- package/dist/types/components/general/microinteractions.d.ts +1 -1
- package/dist/types/components/general/microinteractions.d.ts.map +1 -1
- package/dist/types/components/general/schema-services.d.ts +25 -6
- package/dist/types/components/general/schema-services.d.ts.map +1 -1
- package/dist/types/components/general/schema-website.d.ts +60 -5
- package/dist/types/components/general/schema-website.d.ts.map +1 -1
- package/dist/types/components/general/splitscroll.d.ts +51 -0
- package/dist/types/components/general/splitscroll.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts +30 -0
- package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/form/formcomponents.d.ts +21 -0
- package/dist/types/components/sitebuilder/form/formcomponents.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/stories/callout/callout.stories.d.ts +7 -0
- package/dist/types/stories/callout/callout.stories.d.ts.map +1 -1
- package/dist/types/stories/general/splitscroll.stories.d.ts +19 -0
- package/dist/types/stories/general/splitscroll.stories.d.ts.map +1 -0
- package/dist/types/tests/splitscroll.test.d.ts +2 -0
- package/dist/types/tests/splitscroll.test.d.ts.map +1 -0
- package/package.json +5 -5
|
@@ -12,6 +12,7 @@ import * as FC from '../form/formcomponents';
|
|
|
12
12
|
import siteInfoForm from './siteinfo-form.json';
|
|
13
13
|
import visualDesignForm from './visualdesignform.json';
|
|
14
14
|
import routesForm from './routes-form.json';
|
|
15
|
+
import servicesForm from './services-form.json';
|
|
15
16
|
import defaultConfigData from '../../../data/routes.json';
|
|
16
17
|
import './ConfigBuilder.css';
|
|
17
18
|
const RoutePropTypes = {
|
|
@@ -49,6 +50,21 @@ const SiteInfoPropTypes = {
|
|
|
49
50
|
priceRange: PropTypes.string,
|
|
50
51
|
sameAs: PropTypes.arrayOf(PropTypes.string.isRequired),
|
|
51
52
|
keywords: PropTypes.string,
|
|
53
|
+
openingHours: PropTypes.string,
|
|
54
|
+
publisherType: PropTypes.string,
|
|
55
|
+
copyrightYear: PropTypes.number,
|
|
56
|
+
potentialAction: PropTypes.shape({
|
|
57
|
+
'@type': PropTypes.string,
|
|
58
|
+
target: PropTypes.string.isRequired,
|
|
59
|
+
'query-input': PropTypes.string,
|
|
60
|
+
queryInput: PropTypes.string,
|
|
61
|
+
}),
|
|
62
|
+
services: PropTypes.arrayOf(PropTypes.shape({
|
|
63
|
+
name: PropTypes.string.isRequired,
|
|
64
|
+
description: PropTypes.string.isRequired,
|
|
65
|
+
url: PropTypes.string,
|
|
66
|
+
areaServed: PropTypes.arrayOf(PropTypes.string.isRequired),
|
|
67
|
+
})),
|
|
52
68
|
};
|
|
53
69
|
const VisualDesignVariable = {
|
|
54
70
|
value: PropTypes.string.isRequired,
|
|
@@ -92,7 +108,10 @@ ConfigBuilder.propTypes = {
|
|
|
92
108
|
export function ConfigBuilder(props) {
|
|
93
109
|
const { initialConfig, onSave } = props;
|
|
94
110
|
const defaultConfig = {
|
|
95
|
-
siteInfo:
|
|
111
|
+
siteInfo: {
|
|
112
|
+
...defaultConfigData.siteInfo,
|
|
113
|
+
services: defaultConfigData.siteInfo.services || []
|
|
114
|
+
},
|
|
96
115
|
routes: [], // Start with empty routes, the JSON structure is different
|
|
97
116
|
visualdesign: defaultConfigData.visualdesign
|
|
98
117
|
};
|
|
@@ -157,8 +176,21 @@ export function ConfigBuilder(props) {
|
|
|
157
176
|
? route.keywords.split(',').map((k) => k.trim()).filter((k) => k.length > 0)
|
|
158
177
|
: [])
|
|
159
178
|
}));
|
|
179
|
+
// Ensure services keywords/arrays are valid
|
|
180
|
+
const normalizedServices = (parsedConfig.siteInfo.services || []).map((service) => ({
|
|
181
|
+
...service,
|
|
182
|
+
areaServed: Array.isArray(service.areaServed)
|
|
183
|
+
? service.areaServed
|
|
184
|
+
: (typeof service.areaServed === 'string'
|
|
185
|
+
? service.areaServed.split(',').map((s) => s.trim()).filter((s) => s.length > 0)
|
|
186
|
+
: [])
|
|
187
|
+
}));
|
|
160
188
|
setConfig({
|
|
161
189
|
...parsedConfig,
|
|
190
|
+
siteInfo: {
|
|
191
|
+
...parsedConfig.siteInfo,
|
|
192
|
+
services: normalizedServices
|
|
193
|
+
},
|
|
162
194
|
routes: normalizedRoutes
|
|
163
195
|
});
|
|
164
196
|
setSocialLinks(parsedConfig.siteInfo.sameAs || ['']);
|
|
@@ -195,13 +227,26 @@ export function ConfigBuilder(props) {
|
|
|
195
227
|
}
|
|
196
228
|
}, [initialConfig]);
|
|
197
229
|
// Prepare form data for FormEngine with current values
|
|
230
|
+
const getNestedValue = (obj, path) => {
|
|
231
|
+
return path.split('.').reduce((current, key) => current?.[key], obj);
|
|
232
|
+
};
|
|
233
|
+
const setNestedValue = (obj, path, value) => {
|
|
234
|
+
const keys = path.split('.');
|
|
235
|
+
const lastKey = keys.pop();
|
|
236
|
+
const target = keys.reduce((current, key) => {
|
|
237
|
+
if (!current[key])
|
|
238
|
+
current[key] = {};
|
|
239
|
+
return current[key];
|
|
240
|
+
}, obj);
|
|
241
|
+
target[lastKey] = value;
|
|
242
|
+
};
|
|
198
243
|
const formData = {
|
|
199
244
|
fields: siteInfoForm.fields.map(field => ({
|
|
200
245
|
...field,
|
|
201
246
|
props: {
|
|
202
247
|
...field.props,
|
|
203
|
-
value: config.siteInfo
|
|
204
|
-
defaultValue: config.siteInfo
|
|
248
|
+
value: getNestedValue(config.siteInfo, field.props.name) || '',
|
|
249
|
+
defaultValue: getNestedValue(config.siteInfo, field.props.name) || field.props.defaultValue || '',
|
|
205
250
|
onChange: (value) => {
|
|
206
251
|
// Handle both direct values and event objects
|
|
207
252
|
let actualValue = value;
|
|
@@ -210,13 +255,14 @@ export function ConfigBuilder(props) {
|
|
|
210
255
|
const target = value.target;
|
|
211
256
|
actualValue = target.type === 'checkbox' ? (target.checked ? target.value : '') : target.value;
|
|
212
257
|
}
|
|
213
|
-
setConfig((prev) =>
|
|
214
|
-
...prev
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
258
|
+
setConfig((prev) => {
|
|
259
|
+
const newSiteInfo = { ...prev.siteInfo };
|
|
260
|
+
setNestedValue(newSiteInfo, field.props.name, actualValue);
|
|
261
|
+
return {
|
|
262
|
+
...prev,
|
|
263
|
+
siteInfo: newSiteInfo
|
|
264
|
+
};
|
|
265
|
+
});
|
|
220
266
|
}
|
|
221
267
|
}
|
|
222
268
|
}))
|
|
@@ -257,7 +303,7 @@ export function ConfigBuilder(props) {
|
|
|
257
303
|
const siteInfoData = {};
|
|
258
304
|
// Extract form data
|
|
259
305
|
for (const [key, value] of formData.entries()) {
|
|
260
|
-
siteInfoData
|
|
306
|
+
setNestedValue(siteInfoData, key, value);
|
|
261
307
|
}
|
|
262
308
|
// Update config with form data
|
|
263
309
|
setConfig(prev => ({
|
|
@@ -297,14 +343,9 @@ export function ConfigBuilder(props) {
|
|
|
297
343
|
}));
|
|
298
344
|
};
|
|
299
345
|
const updateRoute = (index, field, value) => {
|
|
300
|
-
// Special handling for keywords field - convert comma-separated string to array
|
|
301
|
-
let processedValue = value;
|
|
302
|
-
if (field === 'keywords' && typeof value === 'string') {
|
|
303
|
-
processedValue = value.split(',').map((k) => k.trim()).filter((k) => k.length > 0);
|
|
304
|
-
}
|
|
305
346
|
setConfig(prev => ({
|
|
306
347
|
...prev,
|
|
307
|
-
routes: prev.routes.map((route, i) => i === index ? { ...route, [field]:
|
|
348
|
+
routes: prev.routes.map((route, i) => i === index ? { ...route, [field]: value } : route)
|
|
308
349
|
}));
|
|
309
350
|
};
|
|
310
351
|
const removeRoute = (index) => {
|
|
@@ -313,12 +354,63 @@ export function ConfigBuilder(props) {
|
|
|
313
354
|
routes: prev.routes.filter((_, i) => i !== index)
|
|
314
355
|
}));
|
|
315
356
|
};
|
|
357
|
+
const addService = () => {
|
|
358
|
+
setConfig(prev => ({
|
|
359
|
+
...prev,
|
|
360
|
+
siteInfo: {
|
|
361
|
+
...prev.siteInfo,
|
|
362
|
+
services: [...(prev.siteInfo.services || []), { name: '', description: '', url: '', areaServed: '' }]
|
|
363
|
+
}
|
|
364
|
+
}));
|
|
365
|
+
};
|
|
366
|
+
const updateService = (index, field, value) => {
|
|
367
|
+
setConfig(prev => ({
|
|
368
|
+
...prev,
|
|
369
|
+
siteInfo: {
|
|
370
|
+
...prev.siteInfo,
|
|
371
|
+
services: (prev.siteInfo.services || []).map((service, i) => i === index ? { ...service, [field]: value } : service)
|
|
372
|
+
}
|
|
373
|
+
}));
|
|
374
|
+
};
|
|
375
|
+
const removeService = (index) => {
|
|
376
|
+
setConfig(prev => ({
|
|
377
|
+
...prev,
|
|
378
|
+
siteInfo: {
|
|
379
|
+
...prev.siteInfo,
|
|
380
|
+
services: (prev.siteInfo.services || []).filter((_, i) => i !== index)
|
|
381
|
+
}
|
|
382
|
+
}));
|
|
383
|
+
};
|
|
384
|
+
// Helper to convert comma-separated strings back to arrays for output
|
|
385
|
+
const getProcessedConfig = (rawConfig) => {
|
|
386
|
+
// Use a simple spread/map to avoid full deep clone where unnecessary,
|
|
387
|
+
// but enough to safely modify for output
|
|
388
|
+
const processed = {
|
|
389
|
+
...rawConfig,
|
|
390
|
+
siteInfo: {
|
|
391
|
+
...rawConfig.siteInfo,
|
|
392
|
+
services: (rawConfig.siteInfo.services || []).map((service) => ({
|
|
393
|
+
...service,
|
|
394
|
+
areaServed: typeof service.areaServed === 'string'
|
|
395
|
+
? service.areaServed.split(',').map((s) => s.trim()).filter((s) => s.length > 0)
|
|
396
|
+
: service.areaServed
|
|
397
|
+
}))
|
|
398
|
+
},
|
|
399
|
+
routes: (rawConfig.routes || []).map((route) => ({
|
|
400
|
+
...route,
|
|
401
|
+
keywords: typeof route.keywords === 'string'
|
|
402
|
+
? route.keywords.split(',').map((k) => k.trim()).filter((k) => k.length > 0)
|
|
403
|
+
: route.keywords
|
|
404
|
+
}))
|
|
405
|
+
};
|
|
406
|
+
return processed;
|
|
407
|
+
};
|
|
316
408
|
const handleSave = () => {
|
|
317
409
|
if (!isFormValid) {
|
|
318
410
|
alert('Please fill in all required fields correctly before saving.');
|
|
319
411
|
return;
|
|
320
412
|
}
|
|
321
|
-
onSave?.(config);
|
|
413
|
+
onSave?.(getProcessedConfig(config));
|
|
322
414
|
};
|
|
323
415
|
const handleAiRecommendations = async (routeIndex) => {
|
|
324
416
|
console.log('handleAiRecommendations called with routeIndex:', routeIndex);
|
|
@@ -401,14 +493,13 @@ export function ConfigBuilder(props) {
|
|
|
401
493
|
: { value: fieldValue || '' }),
|
|
402
494
|
onChange: (e) => {
|
|
403
495
|
let value;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
value = e.target.value.split(',').map((s) => s.trim()).filter((s) => s);
|
|
496
|
+
// Handle both direct values and event objects
|
|
497
|
+
const actualValue = (e && typeof e === 'object' && e.target) ? (e.target.type === 'checkbox' ? e.target.checked : e.target.value) : e;
|
|
498
|
+
if (field.props.name === 'keywords' && typeof actualValue === 'string') {
|
|
499
|
+
value = actualValue.split(',').map((s) => s.trim()).filter((s) => s);
|
|
409
500
|
}
|
|
410
501
|
else {
|
|
411
|
-
value =
|
|
502
|
+
value = actualValue;
|
|
412
503
|
}
|
|
413
504
|
updateRoute(index, field.props.name, value);
|
|
414
505
|
}
|
|
@@ -419,6 +510,32 @@ export function ConfigBuilder(props) {
|
|
|
419
510
|
handleAiRecommendations(index);
|
|
420
511
|
}, className: "route-button ai-recommend", children: [_jsx("span", { className: "ai-icon", children: "\u2728" }), " Recommend"] }), _jsx("button", { onClick: () => removeRoute(index), className: "route-button remove", children: "Remove" })] })] }, index))) }), _jsx("button", { onClick: addRoute, children: "Add Route" })] }) }))
|
|
421
512
|
},
|
|
513
|
+
{
|
|
514
|
+
id: 'services',
|
|
515
|
+
label: 'Services',
|
|
516
|
+
content: (_jsx(FormValidationProvider, { children: _jsxs("div", { className: "routes-section", children: [_jsx("div", { className: "routes-list", children: (config.siteInfo.services || []).map((service, index) => (_jsxs("div", { className: "route-item", children: [servicesForm.fields.map((field) => {
|
|
517
|
+
const Component = FC[field.component];
|
|
518
|
+
if (!Component)
|
|
519
|
+
return null;
|
|
520
|
+
let fieldValue = service[field.props.name];
|
|
521
|
+
if (field.props.name === 'areaServed' && Array.isArray(fieldValue)) {
|
|
522
|
+
fieldValue = fieldValue.join(', ');
|
|
523
|
+
}
|
|
524
|
+
const fieldProps = {
|
|
525
|
+
...field.props,
|
|
526
|
+
id: `${field.props.id}-${index}`,
|
|
527
|
+
...(field.component === 'FormTextarea'
|
|
528
|
+
? { defaultValue: fieldValue || '' }
|
|
529
|
+
: { value: fieldValue || '' }),
|
|
530
|
+
onChange: (e) => {
|
|
531
|
+
// Handle both direct values and event objects
|
|
532
|
+
const actualValue = (e && typeof e === 'object' && e.target) ? e.target.value : e;
|
|
533
|
+
updateService(index, field.props.name, actualValue);
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
return _jsx(Component, { ...fieldProps }, fieldProps.id);
|
|
537
|
+
}), _jsx("div", { className: "route-buttons", children: _jsx("button", { onClick: () => removeService(index), className: "route-button remove", children: "Remove" }) })] }, index))) }), _jsx("button", { onClick: addService, children: "Add Service" })] }) }))
|
|
538
|
+
},
|
|
422
539
|
{
|
|
423
540
|
id: 'visualdesign',
|
|
424
541
|
label: 'Visual Design',
|
|
@@ -429,7 +546,7 @@ export function ConfigBuilder(props) {
|
|
|
429
546
|
title: 'Configuration Preview',
|
|
430
547
|
content: _jsx("pre", { children: (() => {
|
|
431
548
|
try {
|
|
432
|
-
return JSON.stringify(config, null, 2);
|
|
549
|
+
return JSON.stringify(getProcessedConfig(config), null, 2);
|
|
433
550
|
}
|
|
434
551
|
catch (e) {
|
|
435
552
|
// Simple fallback that doesn't try to analyze the object deeply
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"fields": [
|
|
3
|
+
{
|
|
4
|
+
"component": "FormInput",
|
|
5
|
+
"props": {
|
|
6
|
+
"type": "text",
|
|
7
|
+
"id": "name",
|
|
8
|
+
"name": "name",
|
|
9
|
+
"label": "Service Name",
|
|
10
|
+
"required": true,
|
|
11
|
+
"placeholder": "e.g. Web Development",
|
|
12
|
+
"size": "40"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"component": "FormTextarea",
|
|
17
|
+
"props": {
|
|
18
|
+
"id": "description",
|
|
19
|
+
"name": "description",
|
|
20
|
+
"label": "Description",
|
|
21
|
+
"required": true,
|
|
22
|
+
"placeholder": "Detailed description of the service...",
|
|
23
|
+
"rows": "3"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"component": "FormInput",
|
|
28
|
+
"props": {
|
|
29
|
+
"type": "url",
|
|
30
|
+
"id": "url",
|
|
31
|
+
"name": "url",
|
|
32
|
+
"label": "Service URL",
|
|
33
|
+
"required": false,
|
|
34
|
+
"placeholder": "https://pixelated.tech/services/web-dev",
|
|
35
|
+
"size": "40"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"component": "FormInput",
|
|
40
|
+
"props": {
|
|
41
|
+
"type": "text",
|
|
42
|
+
"id": "areaServed",
|
|
43
|
+
"name": "areaServed",
|
|
44
|
+
"label": "Area Served",
|
|
45
|
+
"required": false,
|
|
46
|
+
"placeholder": "New Jersey, South Carolina",
|
|
47
|
+
"size": "40"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
@@ -188,6 +188,17 @@
|
|
|
188
188
|
"size": "40"
|
|
189
189
|
}
|
|
190
190
|
},
|
|
191
|
+
{
|
|
192
|
+
"component": "FormInput",
|
|
193
|
+
"props": {
|
|
194
|
+
"type": "text",
|
|
195
|
+
"id": "openingHours",
|
|
196
|
+
"name": "openingHours",
|
|
197
|
+
"label": "Opening Hours",
|
|
198
|
+
"placeholder": "Mo-Fr 09:00-17:00",
|
|
199
|
+
"size": "40"
|
|
200
|
+
}
|
|
201
|
+
},
|
|
191
202
|
{
|
|
192
203
|
"component": "FormInput",
|
|
193
204
|
"props": {
|
|
@@ -209,6 +220,63 @@
|
|
|
209
220
|
"placeholder": "web development, design, services",
|
|
210
221
|
"size": "40"
|
|
211
222
|
}
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"component": "FormSelect",
|
|
226
|
+
"props": {
|
|
227
|
+
"id": "publisherType",
|
|
228
|
+
"name": "publisherType",
|
|
229
|
+
"label": "Publisher Type",
|
|
230
|
+
"options": [
|
|
231
|
+
{ "value": "Organization", "label": "Organization" },
|
|
232
|
+
{ "value": "LocalBusiness", "label": "Local Business" },
|
|
233
|
+
{ "value": "Person", "label": "Person" }
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"component": "FormInput",
|
|
239
|
+
"props": {
|
|
240
|
+
"type": "number",
|
|
241
|
+
"id": "copyrightYear",
|
|
242
|
+
"name": "copyrightYear",
|
|
243
|
+
"label": "Copyright Year",
|
|
244
|
+
"placeholder": "2024",
|
|
245
|
+
"size": "40"
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"component": "FormInput",
|
|
250
|
+
"props": {
|
|
251
|
+
"type": "text",
|
|
252
|
+
"id": "potentialAction.@type",
|
|
253
|
+
"name": "potentialAction.@type",
|
|
254
|
+
"label": "Search Action Type",
|
|
255
|
+
"placeholder": "SearchAction",
|
|
256
|
+
"size": "40"
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"component": "FormInput",
|
|
261
|
+
"props": {
|
|
262
|
+
"type": "text",
|
|
263
|
+
"id": "potentialAction.target",
|
|
264
|
+
"name": "potentialAction.target",
|
|
265
|
+
"label": "Search Target URL",
|
|
266
|
+
"placeholder": "https://example.com/search?q={search_term_string}",
|
|
267
|
+
"size": "40"
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"component": "FormInput",
|
|
272
|
+
"props": {
|
|
273
|
+
"type": "text",
|
|
274
|
+
"id": "potentialAction.query-input",
|
|
275
|
+
"name": "potentialAction.query-input",
|
|
276
|
+
"label": "Query Input",
|
|
277
|
+
"placeholder": "required name=search_term_string",
|
|
278
|
+
"size": "40"
|
|
279
|
+
}
|
|
212
280
|
}
|
|
213
281
|
]
|
|
214
282
|
}
|
|
@@ -123,4 +123,85 @@ form span {
|
|
|
123
123
|
|
|
124
124
|
.tooltip-text-item:last-child {
|
|
125
125
|
margin-bottom: 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* ========================================
|
|
129
|
+
============= TAG INPUT ============
|
|
130
|
+
======================================== */
|
|
131
|
+
|
|
132
|
+
.form-tag-input {
|
|
133
|
+
margin: 5px 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.tag-container {
|
|
137
|
+
display: flex;
|
|
138
|
+
flex-wrap: wrap;
|
|
139
|
+
align-items: center;
|
|
140
|
+
min-height: 40px;
|
|
141
|
+
padding: 5px;
|
|
142
|
+
border: 1px solid #CCC;
|
|
143
|
+
border-radius: 5px;
|
|
144
|
+
background-color: #FFF;
|
|
145
|
+
gap: 5px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.tag-container:focus-within {
|
|
149
|
+
border-color: #0C0;
|
|
150
|
+
box-shadow: 0 0 5px 2px #CCC;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.tag-chip {
|
|
154
|
+
display: inline-flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
background-color: #eee;
|
|
157
|
+
padding: 4px 8px;
|
|
158
|
+
border-radius: 10px;
|
|
159
|
+
border: 1px solid #bbb;
|
|
160
|
+
white-space: nowrap;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.tag-remove {
|
|
164
|
+
background: none;
|
|
165
|
+
border: none;
|
|
166
|
+
color: #1565c0;
|
|
167
|
+
cursor: pointer;
|
|
168
|
+
font-size: 1.2em;
|
|
169
|
+
line-height: 1;
|
|
170
|
+
padding: 0;
|
|
171
|
+
margin-left: 4px;
|
|
172
|
+
border-radius: 50%;
|
|
173
|
+
width: 16px;
|
|
174
|
+
height: 16px;
|
|
175
|
+
display: flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
justify-content: center;
|
|
178
|
+
transition: background-color 0.2s;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.tag-remove:hover {
|
|
182
|
+
background-color: rgba(21, 101, 192, 0.1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.tag-remove:disabled {
|
|
186
|
+
cursor: not-allowed;
|
|
187
|
+
opacity: 0.5;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.tag-input {
|
|
191
|
+
flex: 1;
|
|
192
|
+
min-width: 120px;
|
|
193
|
+
border: none;
|
|
194
|
+
outline: none;
|
|
195
|
+
padding: 4px 8px;
|
|
196
|
+
font-size: 1.1em;
|
|
197
|
+
font-family: Verdana, Geneva, sans-serif;
|
|
198
|
+
background: transparent;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.tag-input:focus {
|
|
202
|
+
outline: none;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.tag-input::placeholder {
|
|
206
|
+
color: #999;
|
|
126
207
|
}
|
|
@@ -372,6 +372,92 @@ export function FormDataList(props) {
|
|
|
372
372
|
}
|
|
373
373
|
return (_jsx("datalist", { id: props.id, children: options }));
|
|
374
374
|
}
|
|
375
|
+
FormTagInput.propTypes = {
|
|
376
|
+
id: PropTypes.string.isRequired,
|
|
377
|
+
name: PropTypes.string,
|
|
378
|
+
defaultValue: PropTypes.arrayOf(PropTypes.string),
|
|
379
|
+
value: PropTypes.arrayOf(PropTypes.string),
|
|
380
|
+
placeholder: PropTypes.string,
|
|
381
|
+
autoComplete: PropTypes.string,
|
|
382
|
+
// flag attributes
|
|
383
|
+
disabled: PropTypes.string,
|
|
384
|
+
readOnly: PropTypes.string,
|
|
385
|
+
required: PropTypes.string,
|
|
386
|
+
// ----- for calculations
|
|
387
|
+
display: PropTypes.string,
|
|
388
|
+
label: PropTypes.string,
|
|
389
|
+
tooltip: PropTypes.string,
|
|
390
|
+
className: PropTypes.string,
|
|
391
|
+
validate: PropTypes.string,
|
|
392
|
+
onChange: PropTypes.func,
|
|
393
|
+
};
|
|
394
|
+
export function FormTagInput(props) {
|
|
395
|
+
const [inputValue, setInputValue] = useState('');
|
|
396
|
+
const [internalTags, setInternalTags] = useState(Array.isArray(props.defaultValue) ? props.defaultValue.filter((tag) => tag != null) : []);
|
|
397
|
+
const { formValidate, handleBlur } = useFormComponent(props);
|
|
398
|
+
const { validateField } = useFormValidation();
|
|
399
|
+
// Determine if component is controlled or uncontrolled
|
|
400
|
+
const isControlled = props.value !== undefined;
|
|
401
|
+
// Get current tags array - use props.value if controlled, otherwise internal state
|
|
402
|
+
const currentTags = isControlled
|
|
403
|
+
? (Array.isArray(props.value) ? props.value.filter((tag) => tag != null) : [])
|
|
404
|
+
: internalTags;
|
|
405
|
+
// Handle adding a new tag
|
|
406
|
+
const addTag = (tag) => {
|
|
407
|
+
const trimmedTag = tag.trim();
|
|
408
|
+
if (trimmedTag && !currentTags.includes(trimmedTag)) {
|
|
409
|
+
const newTags = [...currentTags, trimmedTag];
|
|
410
|
+
// Always call onChange if provided (for external updates)
|
|
411
|
+
if (props.onChange) {
|
|
412
|
+
props.onChange(newTags);
|
|
413
|
+
}
|
|
414
|
+
if (!isControlled) {
|
|
415
|
+
// Uncontrolled mode: update internal state
|
|
416
|
+
setInternalTags(newTags);
|
|
417
|
+
}
|
|
418
|
+
// Trigger form validation
|
|
419
|
+
if (props.id) {
|
|
420
|
+
validateField(props.id, true, []);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
setInputValue('');
|
|
424
|
+
};
|
|
425
|
+
// Handle removing a tag
|
|
426
|
+
const removeTag = (tagToRemove) => {
|
|
427
|
+
const newTags = currentTags.filter(tag => tag !== tagToRemove);
|
|
428
|
+
// Always call onChange if provided (for external updates)
|
|
429
|
+
if (props.onChange) {
|
|
430
|
+
props.onChange(newTags);
|
|
431
|
+
}
|
|
432
|
+
if (!isControlled) {
|
|
433
|
+
// Uncontrolled mode: update internal state
|
|
434
|
+
setInternalTags(newTags);
|
|
435
|
+
}
|
|
436
|
+
// Trigger form validation
|
|
437
|
+
if (props.id) {
|
|
438
|
+
validateField(props.id, true, []);
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
// Handle input key events
|
|
442
|
+
const handleKeyDown = (event) => {
|
|
443
|
+
if (event.key === 'Enter' || event.key === ',') {
|
|
444
|
+
event.preventDefault();
|
|
445
|
+
addTag(inputValue);
|
|
446
|
+
}
|
|
447
|
+
else if (event.key === 'Backspace' && inputValue === '' && currentTags.length > 0) {
|
|
448
|
+
// Remove last tag on backspace when input is empty
|
|
449
|
+
const lastTag = currentTags[currentTags.length - 1];
|
|
450
|
+
if (lastTag) {
|
|
451
|
+
removeTag(lastTag);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
// Handle input change
|
|
456
|
+
const handleInputChange = (event) => {
|
|
457
|
+
setInputValue(event.target.value);
|
|
458
|
+
};
|
|
459
|
+
return (_jsxs("div", { className: `form-tag-input ${props.className || ''}`, children: [_jsx(FormLabel, { id: props.id, label: props.label }, "label-" + props.id), props.tooltip ? _jsx(FormTooltip, { id: props.id, text: [props.tooltip] }) : "", props.display === "vertical" ? formValidate : "", _jsxs("div", { className: "tag-container", children: [currentTags.map((tag, index) => (_jsxs("span", { className: "tag-chip", children: [tag, _jsx("button", { type: "button", className: "tag-remove", onClick: () => removeTag(tag), "aria-label": `Remove ${tag}`, disabled: props.disabled === 'disabled', children: "\u00D7" })] }, index))), _jsx("input", { type: "text", id: props.id, name: props.name || undefined, value: inputValue, onChange: handleInputChange, onKeyDown: handleKeyDown, placeholder: props.placeholder || "Add tag...", autoComplete: props.autoComplete || undefined, disabled: props.disabled === 'disabled', readOnly: props.readOnly === 'readOnly', required: props.required === 'required', className: "tag-input", "aria-label": "Add new tag" })] }), props.display !== "vertical" ? formValidate : ""] }));
|
|
460
|
+
}
|
|
375
461
|
FormFieldset.propTypes = {};
|
|
376
462
|
export function FormFieldset(props) {
|
|
377
463
|
return (_jsx(_Fragment, {}));
|
package/dist/data/form.json
CHANGED
|
@@ -358,6 +358,18 @@
|
|
|
358
358
|
"tooltip": "Please enter your comments"
|
|
359
359
|
}
|
|
360
360
|
},
|
|
361
|
+
{
|
|
362
|
+
"component" : "FormTagInput",
|
|
363
|
+
"props" : {
|
|
364
|
+
"id" : "tags",
|
|
365
|
+
"name" : "tags",
|
|
366
|
+
"defaultValue" : ["react", "javascript"],
|
|
367
|
+
"display" : "horizontal",
|
|
368
|
+
"label" : "Tags : ",
|
|
369
|
+
"placeholder" : "Add tags...",
|
|
370
|
+
"tooltip" : "Add tags by typing and pressing Enter or comma"
|
|
371
|
+
}
|
|
372
|
+
},
|
|
361
373
|
{
|
|
362
374
|
"component" : "FormButton",
|
|
363
375
|
"props" : {
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,9 @@ export * from './components/general/hubspot.components';
|
|
|
28
28
|
export * from './components/general/image';
|
|
29
29
|
export * from './components/general/instagram.components';
|
|
30
30
|
export * from './components/general/instagram.functions';
|
|
31
|
+
export * from './components/general/intersection-observer';
|
|
31
32
|
export * from './components/general/loading';
|
|
33
|
+
export * from './components/general/splitscroll';
|
|
32
34
|
export * from './components/general/manifest';
|
|
33
35
|
export * from './components/general/markdown';
|
|
34
36
|
export * from './components/general/menu-accordion';
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Setup remotes for all Pixelated projects
|
|
4
|
+
# This script configures consistent Git remotes across all repositories
|
|
5
|
+
|
|
6
|
+
set -e # Exit on any error
|
|
7
|
+
|
|
8
|
+
# Base directory (parent of all repos)
|
|
9
|
+
BASE_DIR="$(pwd)"
|
|
10
|
+
echo "Base directory: $BASE_DIR"
|
|
11
|
+
|
|
12
|
+
# List of repositories to configure
|
|
13
|
+
REPOS=(
|
|
14
|
+
"brianwhaley"
|
|
15
|
+
"informationfocus"
|
|
16
|
+
"oaktreelandscaping"
|
|
17
|
+
"palmetto-epoxy"
|
|
18
|
+
"pixelated"
|
|
19
|
+
"pixelated-admin"
|
|
20
|
+
"pixelated-components"
|
|
21
|
+
"pixelated-template"
|
|
22
|
+
"pixelvivid"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# GitHub username/organization
|
|
26
|
+
GITHUB_USER="brianwhaley"
|
|
27
|
+
|
|
28
|
+
echo "Setting up Git remotes for Pixelated projects..."
|
|
29
|
+
echo "================================================="
|
|
30
|
+
|
|
31
|
+
# First, clean up any global remotes that might interfere
|
|
32
|
+
echo "Cleaning up global remotes..."
|
|
33
|
+
for repo in "${REPOS[@]}"; do
|
|
34
|
+
git config --global --unset-all remote."$repo".url 2>/dev/null || true
|
|
35
|
+
git config --global --unset remote."$repo".fetch 2>/dev/null || true
|
|
36
|
+
done
|
|
37
|
+
echo "✓ Global remotes cleaned up"
|
|
38
|
+
|
|
39
|
+
for repo in "${REPOS[@]}"; do
|
|
40
|
+
repo_path="$BASE_DIR/$repo"
|
|
41
|
+
|
|
42
|
+
if [ -d "$repo_path/.git" ]; then
|
|
43
|
+
echo "Configuring remotes for: $repo"
|
|
44
|
+
cd "$repo_path"
|
|
45
|
+
|
|
46
|
+
# Add remotes for all repositories
|
|
47
|
+
for target_repo in "${REPOS[@]}"; do
|
|
48
|
+
echo " - Setting up $target_repo remote"
|
|
49
|
+
remote_url="https://github.com/$GITHUB_USER/$target_repo.git"
|
|
50
|
+
# Remove remote if it exists, then add fresh
|
|
51
|
+
git remote remove "$target_repo" 2>/dev/null || true
|
|
52
|
+
git remote add "$target_repo" "$remote_url"
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
echo " ✓ All remotes configured for $repo"
|
|
56
|
+
else
|
|
57
|
+
echo "⚠️ Skipping $repo (not a Git repository or doesn't exist)"
|
|
58
|
+
fi
|
|
59
|
+
echo ""
|
|
60
|
+
done
|
|
61
|
+
|
|
62
|
+
echo "================================================="
|
|
63
|
+
echo "Remote setup complete!"
|
|
64
|
+
echo ""
|
|
65
|
+
echo "Each repository now has remotes named after all Pixelated projects."
|
|
66
|
+
echo "Example usage:"
|
|
67
|
+
echo " git remote -v # List all remotes"
|
|
68
|
+
echo " git fetch pixelated-components # Fetch from pixelated-components"
|
|
69
|
+
echo " git push pixelated-components main # Push to pixelated-components"
|