@pixelated-tech/components 3.2.14 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.COMPONENTS.md +289 -30
  2. package/README.md +36 -28
  3. package/dist/components/general/tab.css +105 -0
  4. package/dist/components/general/tab.js +26 -0
  5. package/dist/components/seo/metadata.components.js +0 -19
  6. package/dist/components/seo/metadata.functions.js +111 -0
  7. package/dist/components/seo/schema-blogposting.functions.js +42 -0
  8. package/dist/components/seo/schema-blogposting.js +0 -46
  9. package/dist/components/seo/sitemap.js +1 -1
  10. package/dist/components/shoppingcart/shoppingcart.components.js +4 -4
  11. package/dist/components/sitebuilder/config/ConfigBuilder.css +266 -0
  12. package/dist/components/sitebuilder/config/ConfigBuilder.js +221 -0
  13. package/dist/components/{pagebuilder → sitebuilder}/form/form.css +55 -34
  14. package/dist/components/sitebuilder/form/formbuilder.js +106 -0
  15. package/dist/components/sitebuilder/form/formcomponents.js +356 -0
  16. package/dist/components/sitebuilder/form/formengine.js +82 -0
  17. package/dist/components/{pagebuilder/form/form.js → sitebuilder/form/formextractor.js} +10 -211
  18. package/dist/components/sitebuilder/form/formutils.js +206 -0
  19. package/dist/components/sitebuilder/form/formvalidator.js +123 -0
  20. package/dist/components/{pagebuilder → sitebuilder/page}/components/ComponentPropertiesForm.js +1 -1
  21. package/dist/components/{pagebuilder → sitebuilder/page}/components/PageBuilderUI.js +2 -2
  22. package/dist/components/{pagebuilder → sitebuilder/page}/components/PageEngine.js +1 -1
  23. package/dist/components/sitebuilder/page/documentation/api-examples/save-route-example.js +37 -0
  24. package/dist/components/{pagebuilder → sitebuilder/page}/lib/componentMap.js +3 -3
  25. package/dist/components/{pagebuilder → sitebuilder/page}/lib/componentMetadata.js +2 -2
  26. package/dist/components/{pagebuilder → sitebuilder/page}/lib/pageStorageContentful.js +2 -2
  27. package/dist/components/sitebuilder/page/lib/pageStorageTypes.js +1 -0
  28. package/dist/data/form.json +18 -18
  29. package/dist/data/shipping.to.json +9 -9
  30. package/dist/data/siteinfo-form.json +200 -0
  31. package/dist/index.js +29 -21
  32. package/dist/index.server.js +24 -17
  33. package/dist/types/components/general/semantic.d.ts +3 -3
  34. package/dist/types/components/general/tab.d.ts +18 -0
  35. package/dist/types/components/general/tab.d.ts.map +1 -0
  36. package/dist/types/components/seo/metadata.components.d.ts +0 -17
  37. package/dist/types/components/seo/metadata.components.d.ts.map +1 -1
  38. package/dist/types/components/seo/{metadata.d.ts → metadata.functions.d.ts} +15 -1
  39. package/dist/types/components/seo/metadata.functions.d.ts.map +1 -0
  40. package/dist/types/components/seo/schema-blogposting.d.ts +1 -25
  41. package/dist/types/components/seo/schema-blogposting.d.ts.map +1 -1
  42. package/dist/types/components/seo/schema-blogposting.functions.d.ts +26 -0
  43. package/dist/types/components/seo/schema-blogposting.functions.d.ts.map +1 -0
  44. package/dist/types/components/seo/sitemap.d.ts.map +1 -1
  45. package/dist/types/components/shoppingcart/shoppingcart.components.d.ts +1 -1
  46. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts +86 -0
  47. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts.map +1 -0
  48. package/dist/types/components/sitebuilder/form/formbuilder.d.ts +11 -0
  49. package/dist/types/components/sitebuilder/form/formbuilder.d.ts.map +1 -0
  50. package/dist/types/components/{pagebuilder → sitebuilder}/form/formcomponents.d.ts +12 -16
  51. package/dist/types/components/sitebuilder/form/formcomponents.d.ts.map +1 -0
  52. package/dist/types/components/{pagebuilder/form/form.submit.d.ts → sitebuilder/form/formemailer.d.ts} +1 -1
  53. package/dist/types/components/sitebuilder/form/formemailer.d.ts.map +1 -0
  54. package/dist/types/components/sitebuilder/form/formengine.d.ts +14 -0
  55. package/dist/types/components/sitebuilder/form/formengine.d.ts.map +1 -0
  56. package/dist/types/components/sitebuilder/form/formextractor.d.ts +25 -0
  57. package/dist/types/components/sitebuilder/form/formextractor.d.ts.map +1 -0
  58. package/dist/types/components/{pagebuilder/form/formvalidations.d.ts → sitebuilder/form/formfieldvalidations.d.ts} +1 -1
  59. package/dist/types/components/sitebuilder/form/formfieldvalidations.d.ts.map +1 -0
  60. package/dist/types/components/sitebuilder/form/formtypes.d.ts +66 -0
  61. package/dist/types/components/sitebuilder/form/formtypes.d.ts.map +1 -0
  62. package/dist/types/components/sitebuilder/form/formutils.d.ts +20 -0
  63. package/dist/types/components/sitebuilder/form/formutils.d.ts.map +1 -0
  64. package/dist/types/components/sitebuilder/form/formvalidator.d.ts +20 -0
  65. package/dist/types/components/sitebuilder/form/formvalidator.d.ts.map +1 -0
  66. package/dist/types/components/sitebuilder/page/components/ComponentPropertiesForm.d.ts.map +1 -0
  67. package/dist/types/components/sitebuilder/page/components/ComponentSelector.d.ts.map +1 -0
  68. package/dist/types/components/sitebuilder/page/components/ComponentTree.d.ts.map +1 -0
  69. package/dist/types/components/{pagebuilder → sitebuilder/page}/components/PageBuilderUI.d.ts +1 -1
  70. package/dist/types/components/sitebuilder/page/components/PageBuilderUI.d.ts.map +1 -0
  71. package/dist/types/components/sitebuilder/page/components/PageEngine.d.ts.map +1 -0
  72. package/dist/types/components/sitebuilder/page/components/SaveLoadSection.d.ts.map +1 -0
  73. package/dist/types/components/sitebuilder/page/documentation/api-examples/save-route-example.d.ts +6 -0
  74. package/dist/types/components/sitebuilder/page/documentation/api-examples/save-route-example.d.ts.map +1 -0
  75. package/dist/types/components/sitebuilder/page/lib/componentGeneration.d.ts.map +1 -0
  76. package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/componentMap.d.ts +3 -3
  77. package/dist/types/components/sitebuilder/page/lib/componentMap.d.ts.map +1 -0
  78. package/dist/types/components/sitebuilder/page/lib/componentMetadata.d.ts.map +1 -0
  79. package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/pageStorageContentful.d.ts +1 -1
  80. package/dist/types/components/sitebuilder/page/lib/pageStorageContentful.d.ts.map +1 -0
  81. package/dist/types/components/sitebuilder/page/lib/pageStorageLocal.d.ts.map +1 -0
  82. package/dist/types/components/sitebuilder/page/lib/pageStorageTypes.d.ts.map +1 -0
  83. package/dist/types/components/sitebuilder/page/lib/propTypeIntrospection.d.ts.map +1 -0
  84. package/dist/types/components/sitebuilder/page/lib/types.d.ts.map +1 -0
  85. package/dist/types/components/sitebuilder/page/lib/usePageBuilder.d.ts.map +1 -0
  86. package/dist/types/index.d.ts +29 -20
  87. package/dist/types/index.server.d.ts +23 -16
  88. package/dist/types/stories/general/tab.stories.d.ts +45 -0
  89. package/dist/types/stories/general/tab.stories.d.ts.map +1 -0
  90. package/dist/types/stories/seo/seo.metadata.stories.d.ts +1 -1
  91. package/dist/types/stories/seo/seo.metadata.stories.d.ts.map +1 -1
  92. package/dist/types/stories/sitebuilder/configbuilder.stories.d.ts +48 -0
  93. package/dist/types/stories/sitebuilder/configbuilder.stories.d.ts.map +1 -0
  94. package/dist/types/stories/{pagebuilder → sitebuilder}/form-builder.stories.d.ts +1 -1
  95. package/dist/types/stories/sitebuilder/form-builder.stories.d.ts.map +1 -0
  96. package/dist/types/stories/{pagebuilder → sitebuilder}/form-engine.stories.d.ts +1 -1
  97. package/dist/types/stories/sitebuilder/form-engine.stories.d.ts.map +1 -0
  98. package/dist/types/stories/{pagebuilder → sitebuilder}/form-extractor.stories.d.ts +1 -1
  99. package/dist/types/stories/sitebuilder/form-extractor.stories.d.ts.map +1 -0
  100. package/dist/types/stories/{pagebuilder → sitebuilder}/pagebuilder.stories.d.ts +1 -1
  101. package/dist/types/stories/{pagebuilder → sitebuilder}/pagebuilder.stories.d.ts.map +1 -1
  102. package/dist/types/stories/{pagebuilder → sitebuilder}/pagebuilder.usageguide.stories.d.ts +1 -1
  103. package/dist/types/stories/{pagebuilder → sitebuilder}/pagebuilder.usageguide.stories.d.ts.map +1 -1
  104. package/dist/types/stories/{pagebuilder → sitebuilder}/pageengine.stories.d.ts +1 -1
  105. package/dist/types/stories/{pagebuilder → sitebuilder}/pageengine.stories.d.ts.map +1 -1
  106. package/dist/types/tests/configbuilder.test.d.ts +2 -0
  107. package/dist/types/tests/configbuilder.test.d.ts.map +1 -0
  108. package/dist/types/tests/tab.test.d.ts +2 -0
  109. package/dist/types/tests/tab.test.d.ts.map +1 -0
  110. package/package.json +5 -4
  111. package/dist/components/pagebuilder/form/formcomponents.js +0 -359
  112. package/dist/components/seo/metadata.js +0 -108
  113. package/dist/types/components/pagebuilder/components/ComponentPropertiesForm.d.ts.map +0 -1
  114. package/dist/types/components/pagebuilder/components/ComponentSelector.d.ts.map +0 -1
  115. package/dist/types/components/pagebuilder/components/ComponentTree.d.ts.map +0 -1
  116. package/dist/types/components/pagebuilder/components/PageBuilderUI.d.ts.map +0 -1
  117. package/dist/types/components/pagebuilder/components/PageEngine.d.ts.map +0 -1
  118. package/dist/types/components/pagebuilder/components/SaveLoadSection.d.ts.map +0 -1
  119. package/dist/types/components/pagebuilder/form/form.d.ts +0 -46
  120. package/dist/types/components/pagebuilder/form/form.d.ts.map +0 -1
  121. package/dist/types/components/pagebuilder/form/form.submit.d.ts.map +0 -1
  122. package/dist/types/components/pagebuilder/form/formcomponents.d.ts.map +0 -1
  123. package/dist/types/components/pagebuilder/form/formvalidations.d.ts.map +0 -1
  124. package/dist/types/components/pagebuilder/lib/componentGeneration.d.ts.map +0 -1
  125. package/dist/types/components/pagebuilder/lib/componentMap.d.ts.map +0 -1
  126. package/dist/types/components/pagebuilder/lib/componentMetadata.d.ts.map +0 -1
  127. package/dist/types/components/pagebuilder/lib/pageStorageContentful.d.ts.map +0 -1
  128. package/dist/types/components/pagebuilder/lib/pageStorageLocal.d.ts.map +0 -1
  129. package/dist/types/components/pagebuilder/lib/pageStorageTypes.d.ts.map +0 -1
  130. package/dist/types/components/pagebuilder/lib/propTypeIntrospection.d.ts.map +0 -1
  131. package/dist/types/components/pagebuilder/lib/types.d.ts.map +0 -1
  132. package/dist/types/components/pagebuilder/lib/usePageBuilder.d.ts.map +0 -1
  133. package/dist/types/components/seo/metadata.d.ts.map +0 -1
  134. package/dist/types/stories/pagebuilder/form-builder.stories.d.ts.map +0 -1
  135. package/dist/types/stories/pagebuilder/form-engine.stories.d.ts.map +0 -1
  136. package/dist/types/stories/pagebuilder/form-extractor.stories.d.ts.map +0 -1
  137. /package/dist/components/{pagebuilder/form/form.submit.js → sitebuilder/form/formemailer.js} +0 -0
  138. /package/dist/components/{pagebuilder/form/formvalidations.js → sitebuilder/form/formfieldvalidations.js} +0 -0
  139. /package/dist/components/{pagebuilder/lib/pageStorageTypes.js → sitebuilder/form/formtypes.js} +0 -0
  140. /package/dist/components/{pagebuilder → sitebuilder/page}/components/ComponentSelector.js +0 -0
  141. /package/dist/components/{pagebuilder → sitebuilder/page}/components/ComponentTree.js +0 -0
  142. /package/dist/components/{pagebuilder → sitebuilder/page}/components/SaveLoadSection.js +0 -0
  143. /package/dist/components/{pagebuilder → sitebuilder/page}/components/pagebuilder.scss +0 -0
  144. /package/dist/components/{pagebuilder → sitebuilder/page}/lib/componentGeneration.js +0 -0
  145. /package/dist/components/{pagebuilder → sitebuilder/page}/lib/pageStorageLocal.js +0 -0
  146. /package/dist/components/{pagebuilder → sitebuilder/page}/lib/propTypeIntrospection.js +0 -0
  147. /package/dist/components/{pagebuilder → sitebuilder/page}/lib/types.js +0 -0
  148. /package/dist/components/{pagebuilder → sitebuilder/page}/lib/usePageBuilder.js +0 -0
  149. /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/ComponentPropertiesForm.d.ts +0 -0
  150. /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/ComponentSelector.d.ts +0 -0
  151. /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/ComponentTree.d.ts +0 -0
  152. /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/PageEngine.d.ts +0 -0
  153. /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/SaveLoadSection.d.ts +0 -0
  154. /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/componentGeneration.d.ts +0 -0
  155. /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/componentMetadata.d.ts +0 -0
  156. /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/pageStorageLocal.d.ts +0 -0
  157. /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/pageStorageTypes.d.ts +0 -0
  158. /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/propTypeIntrospection.d.ts +0 -0
  159. /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/types.d.ts +0 -0
  160. /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/usePageBuilder.d.ts +0 -0
@@ -11,6 +11,7 @@ This guide provides detailed API documentation and usage examples for all Pixela
11
11
  - [Modal](#modal)
12
12
  - [SidePanel](#sidepanel)
13
13
  - [SmartImage](#smartimage)
14
+ - [Tab](#tab)
14
15
  - [Table](#table)
15
16
 
16
17
  ### CMS Integration
@@ -34,6 +35,7 @@ This guide provides detailed API documentation and usage examples for all Pixela
34
35
  - [ComponentPropertiesForm](#componentpropertiesform)
35
36
  - [ComponentSelector](#componentselector)
36
37
  - [ComponentTree](#componenttree)
38
+ - [ConfigBuilder](#configbuilder)
37
39
  - [PageBuilderUI](#pagebuilderui)
38
40
  - [PageEngine](#pageengine)
39
41
  - [SaveLoadSection](#saveloadsection)
@@ -554,7 +556,7 @@ const reviews = [
554
556
 
555
557
  ### Forms
556
558
 
557
- Dynamic form builder that generates forms from JSON configuration.
559
+ Dynamic form builder that generates forms from JSON configuration with comprehensive validation and event handling.
558
560
 
559
561
  #### FormEngine
560
562
 
@@ -566,21 +568,39 @@ import { FormEngine } from '@pixelated-tech/components';
566
568
  const formConfig = {
567
569
  fields: [
568
570
  {
569
- type: 'text',
571
+ component: 'FormInput',
570
572
  props: {
573
+ type: 'text',
574
+ id: 'email',
571
575
  name: 'email',
572
576
  label: 'Email Address',
573
577
  placeholder: 'Enter your email',
574
- required: true
578
+ required: 'required',
579
+ validate: 'email'
575
580
  }
576
581
  },
577
582
  {
578
- type: 'textarea',
583
+ component: 'FormTextarea',
579
584
  props: {
585
+ id: 'message',
580
586
  name: 'message',
581
587
  label: 'Message',
582
588
  placeholder: 'Enter your message',
583
- rows: 4
589
+ rows: '4',
590
+ required: 'required'
591
+ }
592
+ },
593
+ {
594
+ component: 'FormSelect',
595
+ props: {
596
+ id: 'category',
597
+ name: 'category',
598
+ label: 'Category',
599
+ options: [
600
+ { text: 'General', value: 'general' },
601
+ { text: 'Support', value: 'support' },
602
+ { text: 'Sales', value: 'sales' }
603
+ ]
584
604
  }
585
605
  }
586
606
  ]
@@ -595,36 +615,171 @@ const formConfig = {
595
615
 
596
616
  #### Form Components
597
617
 
598
- Individual form field components for custom form building.
618
+ Individual form field components with unified event handling and validation.
619
+
620
+ ##### FormInput
621
+ Text input field with validation and accessibility features.
599
622
 
600
623
  ```tsx
601
- import * as FC from '@pixelated-tech/components';
624
+ import { FormInput } from '@pixelated-tech/components';
625
+
626
+ <FormInput
627
+ type="email"
628
+ id="email"
629
+ name="email"
630
+ label="Email Address"
631
+ placeholder="Enter your email"
632
+ required="required"
633
+ validate="email"
634
+ display="vertical"
635
+ />
636
+ ```
602
637
 
603
- function CustomForm() {
604
- return (
605
- <form>
606
- <FC.TextInput
607
- name="username"
608
- label="Username"
609
- required={true}
610
- placeholder="Enter username"
611
- />
612
- <FC.EmailInput
613
- name="email"
614
- label="Email"
615
- required={true}
616
- />
617
- <FC.TextArea
618
- name="bio"
619
- label="Bio"
620
- rows={4}
621
- placeholder="Tell us about yourself"
622
- />
623
- </form>
624
- );
625
- }
638
+ **Props:**
639
+ - `type`: Input type (text, email, password, etc.)
640
+ - `id`: Unique identifier
641
+ - `name`: Form field name
642
+ - `label`: Display label
643
+ - `placeholder`: Input placeholder text
644
+ - `required`: Makes field required
645
+ - `validate`: Validation rule name
646
+ - `display`: Layout mode (vertical/horizontal)
647
+
648
+ ##### FormTextarea
649
+ Multi-line text input with validation.
650
+
651
+ ```tsx
652
+ import { FormTextarea } from '@pixelated-tech/components';
653
+
654
+ <FormTextarea
655
+ id="description"
656
+ name="description"
657
+ label="Description"
658
+ placeholder="Enter description"
659
+ rows="4"
660
+ required="required"
661
+ display="vertical"
662
+ />
663
+ ```
664
+
665
+ ##### FormSelect
666
+ Dropdown selection with option validation.
667
+
668
+ ```tsx
669
+ import { FormSelect } from '@pixelated-tech/components';
670
+
671
+ <FormSelect
672
+ id="category"
673
+ name="category"
674
+ label="Category"
675
+ options={[
676
+ { text: 'Option 1', value: 'opt1' },
677
+ { text: 'Option 2', value: 'opt2' }
678
+ ]}
679
+ required="required"
680
+ />
626
681
  ```
627
682
 
683
+ ##### FormRadio
684
+ Radio button group with validation.
685
+
686
+ ```tsx
687
+ import { FormRadio } from '@pixelated-tech/components';
688
+
689
+ <FormRadio
690
+ id="choice"
691
+ name="choice"
692
+ label="Choose an option"
693
+ options={[
694
+ { text: 'Option A', value: 'a' },
695
+ { text: 'Option B', value: 'b' }
696
+ ]}
697
+ required="required"
698
+ />
699
+ ```
700
+
701
+ ##### FormCheckbox
702
+ Checkbox group with validation.
703
+
704
+ ```tsx
705
+ import { FormCheckbox } from '@pixelated-tech/components';
706
+
707
+ <FormCheckbox
708
+ id="preferences"
709
+ name="preferences"
710
+ label="Preferences"
711
+ options={[
712
+ { text: 'Email updates', value: 'email' },
713
+ { text: 'SMS updates', value: 'sms' }
714
+ ]}
715
+ />
716
+ ```
717
+
718
+ ##### FormButton
719
+ Action button for form submission.
720
+
721
+ ```tsx
722
+ import { FormButton } from '@pixelated-tech/components';
723
+
724
+ <FormButton
725
+ type="submit"
726
+ id="submit-btn"
727
+ text="Submit Form"
728
+ onClick={handleSubmit}
729
+ />
730
+ ```
731
+
732
+ ##### FormTooltip
733
+ Unified tooltip and validation message component with mouseover behavior and conditional styling.
734
+
735
+ ```tsx
736
+ import { FormTooltip } from '@pixelated-tech/components';
737
+
738
+ // Tooltip mode - displays information with black ⓘ icon
739
+ <FormTooltip
740
+ mode="tooltip"
741
+ text={['This field is required', 'Please enter a valid email address']}
742
+ />
743
+
744
+ // Validation mode - displays errors with red ❌ icon
745
+ <FormTooltip
746
+ mode="validate"
747
+ text={['Email format is invalid', 'Please check your input']}
748
+ />
749
+ ```
750
+
751
+ **Props:**
752
+ - `mode`: Display mode ('tooltip' for info, 'validate' for errors)
753
+ - `text`: Array of strings to display (always use array format)
754
+
755
+ **Features:**
756
+ - **Conditional Icons**: ⓘ (black) for tooltips, ❌ (red) for validation errors
757
+ - **Mouseover Behavior**: Shows/hides content on hover for both modes
758
+ - **Unified Styling**: Consistent appearance with mode-based color variations
759
+ - **Array-Based Text**: Always accepts text as string array for consistency
760
+
761
+ #### Form Validation
762
+
763
+ Built-in validation rules available via the `validate` prop:
764
+
765
+ - `email`: Email format validation
766
+ - `url`: URL format validation
767
+ - `phone`: Phone number format
768
+ - `zipcode`: US zip code validation
769
+ - `required`: Required field validation
770
+ - Custom validation functions can be added to `formvalidations.tsx`
771
+
772
+ #### Recent Improvements
773
+
774
+ **Version 3.2.14+** includes major refactoring for better performance and maintainability:
775
+
776
+ - **FormTooltip Unification**: Merged FormTooltip and FormValidate into single component with mode-based rendering
777
+ - **Unified Event Handling**: All form components now use consistent `onChange` and `onInput` handlers for better test compatibility
778
+ - **Performance Optimization**: Replaced inefficient `JSON.parse/stringify` with direct object destructuring
779
+ - **Code Deduplication**: Custom `useFormComponent` hook eliminates repetitive validation logic
780
+ - **Circular Reference Prevention**: Fixed memory leaks in option generation for radio/checkbox components
781
+ - **Enhanced Test Coverage**: 2,244 tests passing across 67 test files with comprehensive form component coverage
782
+
628
783
  ### Menu
629
784
 
630
785
  Navigation components with multiple interaction patterns.
@@ -691,6 +846,29 @@ const menuItems = [
691
846
  <MenuExpando menuItems={menuItems} />
692
847
  ```
693
848
 
849
+ ### Tab
850
+
851
+ Configurable tab component with multiple orientations and content areas.
852
+
853
+ ```tsx
854
+ import { Tab } from '@pixelated-tech/components';
855
+
856
+ const tabs = [
857
+ { id: 'tab1', label: 'Tab 1', content: <div>Content for Tab 1</div> },
858
+ { id: 'tab2', label: 'Tab 2', content: <div>Content for Tab 2</div> },
859
+ { id: 'tab3', label: 'Tab 3', content: <div>Content for Tab 3</div> },
860
+ ];
861
+
862
+ <Tab tabs={tabs} orientation="top" defaultActiveTab="tab1" />
863
+ ```
864
+
865
+ #### Props
866
+
867
+ - `tabs`: Array of tab objects with `id`, `label`, and `content`
868
+ - `orientation`: 'top' | 'bottom' | 'left' | 'right' (default: 'top')
869
+ - `defaultActiveTab`: ID of the initially active tab
870
+ - `onTabChange`: Callback function called when tab changes
871
+
694
872
  ### Tables
695
873
 
696
874
  Data display component with sorting and image support.
@@ -874,6 +1052,87 @@ import { SaveLoadSection } from '@pixelated-tech/components';
874
1052
  | `onLoad` | `function` | - | Load handler |
875
1053
  | `onNew` | `function` | - | New page handler |
876
1054
 
1055
+ ### ConfigBuilder
1056
+
1057
+ Tabbed interface for managing site metadata and routes configuration.
1058
+
1059
+ ```tsx
1060
+ import { ConfigBuilder } from '@pixelated-tech/components';
1061
+
1062
+ <ConfigBuilder
1063
+ initialConfig={{
1064
+ siteInfo: { name: 'My Site', description: 'Site description' },
1065
+ routes: [
1066
+ { path: '/home', component: 'Home', title: 'Home Page' }
1067
+ ]
1068
+ }}
1069
+ onSave={(config) => console.log('Config saved:', config)}
1070
+ />
1071
+ ```
1072
+
1073
+ #### Props
1074
+ | Prop | Type | Default | Description |
1075
+ |------|------|---------|-------------|
1076
+ | `initialConfig` | `SiteConfig` | - | Initial site configuration |
1077
+ | `onSave` | `function` | - | Configuration save handler |
1078
+
1079
+ #### SiteConfig Type
1080
+ ```tsx
1081
+ interface SiteConfig {
1082
+ siteInfo: {
1083
+ name: string;
1084
+ author: string;
1085
+ description: string;
1086
+ url: string;
1087
+ email: string;
1088
+ favicon: string;
1089
+ favicon_sizes: string;
1090
+ favicon_type: string;
1091
+ theme_color: string;
1092
+ background_color: string;
1093
+ default_locale: string;
1094
+ display: string;
1095
+ image?: string;
1096
+ image_height?: string;
1097
+ image_width?: string;
1098
+ telephone?: string;
1099
+ address?: {
1100
+ streetAddress: string;
1101
+ addressLocality: string;
1102
+ addressRegion: string;
1103
+ postalCode: string;
1104
+ addressCountry: string;
1105
+ };
1106
+ priceRange?: string;
1107
+ sameAs?: string[];
1108
+ keywords?: string;
1109
+ };
1110
+ routes: Array<{
1111
+ path: string;
1112
+ component: string;
1113
+ title?: string;
1114
+ description?: string;
1115
+ }>;
1116
+ }
1117
+ ```
1118
+
1119
+ #### Features
1120
+ - **Tabbed Interface**: Organized into "Site Info" and "Routes" tabs
1121
+ - **Comprehensive Site Info Management**: Edit all standard site metadata fields including PWA settings, contact info, and address
1122
+ - **Route Management**: Add, edit, and remove page routes with component mapping
1123
+ - **Real-time Preview**: JSON preview of current configuration
1124
+ - **Save Functionality**: Callback-based configuration persistence with form validation enforcement
1125
+ - **Form Validation**: Required field validation for essential site information with visual feedback
1126
+
1127
+ #### Validation Behavior
1128
+
1129
+ The "Save Config" button enforces form validation before allowing configuration saves:
1130
+
1131
+ - **Required Fields**: Site name, author, description, URL, and email are mandatory
1132
+ - **Visual Feedback**: Invalid fields show validation errors with ❌ indicators
1133
+ - **Save Prevention**: Save action is blocked until all required validations pass
1134
+ - **Real-time Validation**: Form validates as you type with immediate feedback
1135
+
877
1136
  ---
878
1137
 
879
1138
  ## SEO & Schema
package/README.md CHANGED
@@ -270,16 +270,16 @@ Project Link: [https://github.com/brianwhaley/pixelated-components](https://gith
270
270
 
271
271
  ### Overview
272
272
 
273
- **Current Status**: ✅ 2,210 tests passing across 65 test files
273
+ **Current Status**: ✅ 2,244 tests passing across 67 test files
274
274
 
275
275
  | Metric | Value |
276
276
  |--------|-------|
277
- | Test Files | 65 |
278
- | Total Tests | 2,210 |
279
- | Coverage (Statements) | 77.92% |
280
- | Coverage (Lines) | 81.09% |
281
- | Coverage (Functions) | 81.27% |
282
- | Coverage (Branches) | 67.31% |
277
+ | Test Files | 67 |
278
+ | Total Tests | 2,244 |
279
+ | Coverage (Statements) | 76.59% |
280
+ | Coverage (Lines) | 79.45% |
281
+ | Coverage (Functions) | 78.33% |
282
+ | Coverage (Branches) | 66.79% |
283
283
  | Test Framework | Vitest 4.x |
284
284
  | Testing Library | @testing-library/react + jsdom |
285
285
 
@@ -297,52 +297,60 @@ npm run test:run # Single run (for CI)
297
297
  **Component Coverage Summary**
298
298
 
299
299
  #### Component Coverage (Sorted by Statement Coverage)
300
+ - **tiles.tsx**: 100% statements
301
+ - **google.reviews.functions.ts**: 100% statements
300
302
  - **config.server.tsx**: 100% statements
301
- - **modal.tsx**: 100% statements
302
303
  - **accordion.tsx**: 100% statements
303
- - **tiles.tsx**: 100% statements
304
- - **googlesearch.tsx**: 100% statements
305
- - **formvalidations.tsx**: 100% statements
306
- - **buzzwordbingo.tsx**: 100% statements
307
- - **timeline.tsx**: 100% statements
308
- - **markdown.tsx**: 100% statements
304
+ - **modal.tsx**: 100% statements
305
+ - **tab.tsx**: 100% statements
309
306
  - **ComponentPropertiesForm.tsx**: 100% statements
310
307
  - **ComponentSelector.tsx**: 100% statements
311
308
  - **ComponentTree.tsx**: 100% statements
309
+ - **formvalidations.tsx**: 100% statements
310
+ - **componentMetadata.tsx**: 100% statements
311
+ - **googlesearch.tsx**: 100% statements
312
312
  - **schema-localbusiness.tsx**: 100% statements
313
313
  - **schema-recipe.tsx**: 100% statements
314
314
  - **schema-services.tsx**: 100% statements
315
315
  - **schema-website.tsx**: 100% statements
316
- - **google.reviews.functions.ts**: 100% statements
316
+ - **buzzwordbingo.tsx**: 100% statements
317
+ - **markdown.tsx**: 100% statements
318
+ - **timeline.tsx**: 100% statements
317
319
  - **sidepanel.tsx**: 97.5% statements
318
320
  - **config.ts**: 96.55% statements
319
321
  - **google.reviews.components.tsx**: 95.83% statements
320
- - **schema-blogposting.tsx**: 95.23% statements
322
+ - **schema-blogposting.tsx**: 95.24% statements
321
323
  - **recipe.tsx**: 94.59% statements
322
324
  - **resume.tsx**: 94.38% statements
323
325
  - **contentful.delivery.ts**: 92.5% statements
324
- - **css.tsx**: 91.42% statements
325
- - **functions.ts**: 90.9% statements
326
- - **config.client.tsx**: 90% statements
326
+ - **css.tsx**: 91.43% statements
327
+ - **functions.ts**: 90.91% statements
327
328
  - **menu-expando.tsx**: 90.12% statements
329
+ - **config.client.tsx**: 90% statements
330
+ - **loading.tsx**: 85.71% statements
331
+ - **SaveLoadSection.tsx**: 84.85% statements
332
+ - **table.tsx**: 84.48% statements
333
+ - **ConfigBuilder.tsx**: 83.52% statements
328
334
  - **cloudinary.ts**: 83.33% statements
335
+ - **formcomponents.tsx**: 83.33% statements
329
336
  - **form.tsx**: 83.2% statements
330
- - **shoppingcart.functions.ts**: 81.69% statements
337
+ - **shoppingcart.functions.ts**: 81.7% statements
331
338
  - **callout.tsx**: 80% statements
332
339
  - **microinteractions.tsx**: 80% statements
333
- - **sitemap.ts**: 76.05% statements
340
+ - **cloudinary.image.tsx**: 78.57% statements
341
+ - **sitemap.ts**: 76.06% statements
334
342
  - **manifest.tsx**: 75% statements
335
- - **carousel.tsx**: 71.69% statements
343
+ - **carousel.tsx**: 71.7% statements
336
344
  - **nerdjoke.tsx**: 69.44% statements
337
345
  - **menu-accordion.tsx**: 68.13% statements
338
346
  - **semantic.tsx**: 63.51% statements
339
- - **flickr.ts**: 51.42% statements
347
+ - **componentMap.tsx**: 60% statements
348
+ - **propTypeIntrospection.tsx**: 60% statements
349
+ - **wordpress.functions.ts**: 51.43% statements
340
350
  - **PageEngine.tsx**: 48% statements
341
- - **SaveLoadSection.tsx**: 84.84% statements
342
- - **table.tsx**: 84.48% statements
343
- - **loading.tsx**: 85.71% statements
344
- - **socialcard.tsx**: 29.5% statements
345
- - **PageBuilderUI.tsx**: 26.66% statements
351
+ - **componentGeneration.tsx**: 38.89% statements
352
+ - **socialcard.tsx**: 29.51% statements
353
+ - **PageBuilderUI.tsx**: 26.67% statements
346
354
 
347
355
  ### Testing Next Steps
348
356
 
@@ -0,0 +1,105 @@
1
+ /* Tab Component Styles */
2
+
3
+ .tab-container {
4
+ display: flex;
5
+ border: 1px solid #ddd;
6
+ border-radius: 4px;
7
+ overflow: hidden;
8
+ height: 100%;
9
+ }
10
+
11
+ .tab-top {
12
+ flex-direction: column;
13
+ }
14
+
15
+ .tab-bottom {
16
+ flex-direction: column-reverse;
17
+ }
18
+
19
+ .tab-left {
20
+ flex-direction: row;
21
+ }
22
+
23
+ .tab-right {
24
+ flex-direction: row-reverse;
25
+ }
26
+
27
+ .tab-headers {
28
+ display: flex;
29
+ background-color: #f5f5f5;
30
+ }
31
+
32
+ .tab-top .tab-headers,
33
+ .tab-bottom .tab-headers {
34
+ flex-direction: row;
35
+ width: 100%;
36
+ }
37
+
38
+ .tab-left .tab-headers,
39
+ .tab-right .tab-headers {
40
+ flex-direction: column;
41
+ height: 100%;
42
+ }
43
+
44
+ .tab-header {
45
+ padding: 10px 20px;
46
+ border: none;
47
+ background: none;
48
+ cursor: pointer;
49
+ transition: background-color 0.3s;
50
+ white-space: nowrap;
51
+ flex-shrink: 0;
52
+ }
53
+
54
+ .tab-top .tab-header,
55
+ .tab-bottom .tab-header {
56
+ border-right: 1px solid #ddd;
57
+ border-bottom: none;
58
+ }
59
+
60
+ .tab-left .tab-header,
61
+ .tab-right .tab-header {
62
+ border-right: none;
63
+ border-bottom: 1px solid #ddd;
64
+ height: auto;
65
+ padding: 20px 10px;
66
+ }
67
+
68
+ .tab-left .tab-header {
69
+ transform: rotate(-90deg);
70
+ }
71
+
72
+ .tab-right .tab-header {
73
+ transform: rotate(90deg);
74
+ }
75
+
76
+ .tab-header:last-child {
77
+ border-right: none;
78
+ }
79
+
80
+ .tab-top .tab-header:last-child,
81
+ .tab-bottom .tab-header:last-child {
82
+ border-right: none;
83
+ }
84
+
85
+ .tab-left .tab-header:last-child,
86
+ .tab-right .tab-header:last-child {
87
+ border-bottom: none;
88
+ }
89
+
90
+ .tab-header:hover {
91
+ background-color: #e0e0e0;
92
+ }
93
+
94
+ .tab-header.active {
95
+ background-color: #fff;
96
+ font-weight: bold;
97
+ }
98
+
99
+ .tab-content {
100
+ flex: 1;
101
+ padding: 20px;
102
+ background-color: #fff;
103
+ min-height: 200px;
104
+ overflow: auto;
105
+ }
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import PropTypes from 'prop-types';
4
+ import './tab.css';
5
+ const TabItemPropTypes = {
6
+ id: PropTypes.string.isRequired,
7
+ label: PropTypes.string.isRequired, //
8
+ content: PropTypes.node.isRequired,
9
+ };
10
+ // type TabItemType = InferProps<typeof TabItemPropTypes>;
11
+ Tab.propTypes = {
12
+ tabs: PropTypes.arrayOf(PropTypes.shape(TabItemPropTypes).isRequired).isRequired,
13
+ orientation: PropTypes.oneOf(['top', 'bottom', 'left', 'right']),
14
+ defaultActiveTab: PropTypes.string,
15
+ onTabChange: PropTypes.func,
16
+ };
17
+ export function Tab({ tabs, orientation = 'top', defaultActiveTab, onTabChange }) {
18
+ const [activeTab, setActiveTab] = useState(defaultActiveTab || tabs[0]?.id || '');
19
+ const handleTabClick = (tabId) => {
20
+ setActiveTab(tabId);
21
+ onTabChange?.(tabId);
22
+ };
23
+ const activeContent = tabs.find(tab => tab.id === activeTab)?.content;
24
+ const tabClass = `tab-container tab-${orientation}`;
25
+ return (_jsxs("div", { className: tabClass, children: [_jsx("div", { className: "tab-headers", children: tabs.map(tab => (_jsx("button", { className: `tab-header ${activeTab === tab.id ? 'active' : ''}`, onClick: () => handleTabClick(tab.id), children: tab.label }, tab.id))) }), _jsx("div", { className: "tab-content", children: activeContent })] }));
26
+ }
@@ -1,23 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
1
  import PropTypes from "prop-types";
3
- // https://gist.github.com/whitingx/3840905
4
- generateMetaTags.propTypes = {
5
- title: PropTypes.string.isRequired,
6
- description: PropTypes.string.isRequired,
7
- keywords: PropTypes.string.isRequired,
8
- site_name: PropTypes.string.isRequired,
9
- email: PropTypes.string.isRequired,
10
- origin: PropTypes.string.isRequired,
11
- url: PropTypes.string.isRequired,
12
- image: PropTypes.string.isRequired,
13
- image_height: PropTypes.string.isRequired,
14
- image_width: PropTypes.string.isRequired,
15
- favicon: PropTypes.string.isRequired,
16
- };
17
- export function generateMetaTags(props) {
18
- const { title, description, keywords, site_name, email, origin, url, image, image_height, image_width, favicon } = props;
19
- return (_jsxs(_Fragment, { children: [_jsx("title", { children: title }), _jsx("meta", { charSet: "UTF-8" }), _jsx("meta", { httpEquiv: "content-type", content: "text/html; charset=UTF-8" }), _jsx("meta", { httpEquiv: 'Expires', content: '0' }), _jsx("meta", { httpEquiv: 'Pragma', content: 'no-cache' }), _jsx("meta", { httpEquiv: 'Cache-Control', content: 'no-cache' }), _jsx("meta", { name: "application-name", content: site_name }), _jsx("meta", { name: "author", content: site_name + ", " + email }), _jsx("meta", { name: 'copyright', content: site_name }), _jsx("meta", { name: "creator", content: site_name }), _jsx("meta", { name: "description", content: description }), _jsx("meta", { name: "keywords", content: keywords }), _jsx("meta", { name: 'language', content: 'EN' }), _jsx("meta", { name: 'owner', content: site_name }), _jsx("meta", { name: "publisher", content: site_name }), _jsx("meta", { name: 'rating', content: 'General' }), _jsx("meta", { name: 'reply-to', content: email }), _jsx("meta", { name: "robots", content: "index, follow" }), _jsx("meta", { name: 'url', content: url }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0, shrink-to-fit=no" }), _jsx("meta", { property: "og:description", content: description }), _jsx("meta", { property: 'og:email', content: email }), _jsx("meta", { property: "og:image", content: image }), _jsx("meta", { property: "og:image:height", content: image_height }), _jsx("meta", { property: "og:image:width", content: image_width }), _jsx("meta", { property: "og:locale", content: "en_US" }), _jsx("meta", { property: "og:site_name", content: site_name }), _jsx("meta", { property: "og:title", content: title }), _jsx("meta", { property: "og:type", content: "website" }), _jsx("meta", { property: "og:url", content: url }), _jsx("meta", { itemProp: "name", content: site_name }), _jsx("meta", { itemProp: "url", content: url }), _jsx("meta", { itemProp: "description", content: description }), _jsx("meta", { itemProp: "thumbnailUrl", content: image }), _jsx("meta", { property: "twitter:domain", content: new URL(origin).hostname }), _jsx("meta", { property: "twitter:url", content: url }), _jsx("meta", { name: "twitter:card", content: "summary_large_image" }), _jsx("meta", { name: "twitter:creator", content: site_name }), _jsx("meta", { name: "twitter:description", content: description }), _jsx("meta", { name: "twitter:image", content: image }), _jsx("meta", { name: "twitter:image:height", content: image_height }), _jsx("meta", { name: "twitter:image:width", content: image_width }), _jsx("meta", { name: "twitter:title", content: title }), _jsx("link", { rel: "author", href: origin }), _jsx("link", { rel: "canonical", href: url }), _jsx("link", { rel: "icon", type: "image/x-icon", href: favicon }), _jsx("link", { rel: "shortcut icon", type: "image/x-icon", href: favicon }), _jsx("link", { rel: "manifest", href: "/manifest.webmanifest" }), _jsx("link", { rel: "preconnect", href: "https://images.ctfassets.net/" }), _jsx("link", { rel: "preconnect", href: "https://res.cloudinary.com/" }), _jsx("link", { rel: "preconnect", href: "https://farm2.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm6.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm8.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm66.static.flickr.com" })] }));
20
- }
21
2
  setClientMetadata.propTypes = {
22
3
  title: PropTypes.string.isRequired,
23
4
  description: PropTypes.string.isRequired,