@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.
Files changed (43) hide show
  1. package/dist/components/admin/site-health/site-health-on-site-seo.integration.js +15 -0
  2. package/dist/components/general/callout.js +4 -3
  3. package/dist/components/general/intersection-observer.js +117 -0
  4. package/dist/components/general/microinteractions.js +39 -50
  5. package/dist/components/general/schema-services.js +17 -4
  6. package/dist/components/general/schema-website.js +100 -7
  7. package/dist/components/general/splitscroll.css +125 -0
  8. package/dist/components/general/splitscroll.js +89 -0
  9. package/dist/components/sitebuilder/config/ConfigBuilder.js +142 -25
  10. package/dist/components/sitebuilder/config/services-form.json +51 -0
  11. package/dist/components/sitebuilder/config/siteinfo-form.json +68 -0
  12. package/dist/components/sitebuilder/form/form.css +81 -0
  13. package/dist/components/sitebuilder/form/formcomponents.js +86 -0
  14. package/dist/data/form.json +12 -0
  15. package/dist/index.js +2 -0
  16. package/dist/scripts/setup-remotes.sh +69 -0
  17. package/dist/types/components/admin/site-health/site-health-on-site-seo.integration.d.ts.map +1 -1
  18. package/dist/types/components/config/config.types.d.ts +8 -1
  19. package/dist/types/components/config/config.types.d.ts.map +1 -1
  20. package/dist/types/components/general/callout.d.ts +3 -2
  21. package/dist/types/components/general/callout.d.ts.map +1 -1
  22. package/dist/types/components/general/intersection-observer.d.ts +73 -0
  23. package/dist/types/components/general/intersection-observer.d.ts.map +1 -0
  24. package/dist/types/components/general/microinteractions.d.ts +1 -1
  25. package/dist/types/components/general/microinteractions.d.ts.map +1 -1
  26. package/dist/types/components/general/schema-services.d.ts +25 -6
  27. package/dist/types/components/general/schema-services.d.ts.map +1 -1
  28. package/dist/types/components/general/schema-website.d.ts +60 -5
  29. package/dist/types/components/general/schema-website.d.ts.map +1 -1
  30. package/dist/types/components/general/splitscroll.d.ts +51 -0
  31. package/dist/types/components/general/splitscroll.d.ts.map +1 -0
  32. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts +30 -0
  33. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts.map +1 -1
  34. package/dist/types/components/sitebuilder/form/formcomponents.d.ts +21 -0
  35. package/dist/types/components/sitebuilder/form/formcomponents.d.ts.map +1 -1
  36. package/dist/types/index.d.ts +2 -0
  37. package/dist/types/stories/callout/callout.stories.d.ts +7 -0
  38. package/dist/types/stories/callout/callout.stories.d.ts.map +1 -1
  39. package/dist/types/stories/general/splitscroll.stories.d.ts +19 -0
  40. package/dist/types/stories/general/splitscroll.stories.d.ts.map +1 -0
  41. package/dist/types/tests/splitscroll.test.d.ts +2 -0
  42. package/dist/types/tests/splitscroll.test.d.ts.map +1 -0
  43. 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: defaultConfigData.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[field.props.name] || '',
204
- defaultValue: config.siteInfo[field.props.name] || field.props.defaultValue || '',
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
- siteInfo: {
216
- ...prev.siteInfo,
217
- [field.props.name]: actualValue
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[key] = value;
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]: processedValue } : route)
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
- if (field.props.type === 'checkbox') {
405
- value = e.target.checked;
406
- }
407
- else if (field.props.name === 'keywords') {
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 = e.target.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, {}));
@@ -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"