@dotcms/uve 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1143 -0
- package/index.cjs.js +621 -2
- package/index.esm.js +624 -2
- package/internal.cjs.js +0 -4
- package/internal.esm.js +1 -1
- package/package.json +4 -4
- package/public.cjs.js +10 -78
- package/public.esm.js +11 -75
- package/src/index.d.ts +2 -0
- package/src/internal/constants.d.ts +0 -2
- package/src/lib/core/core.utils.d.ts +0 -48
- package/src/lib/dom/dom.utils.d.ts +1 -10
- package/src/lib/style-editor/internal.d.ts +71 -0
- package/src/lib/style-editor/public.d.ts +372 -0
- package/src/lib/style-editor/types.d.ts +702 -0
- package/index.esm.d.ts +0 -1
- package/internal.esm.d.ts +0 -1
- /package/{index.cjs.d.ts → index.d.ts} +0 -0
- /package/{internal.cjs.d.ts → internal.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -30,6 +30,20 @@ With `@dotcms/uve`, framework SDKs are able to:
|
|
|
30
30
|
- [`updateNavigation()`](#updatenavigationpathname)
|
|
31
31
|
- [`reorderMenu()`](#reordermenuconfig)
|
|
32
32
|
- [`sendMessageToUVE()`](#sendmessagetouvemessage)
|
|
33
|
+
- [Style Editor](#style-editor)
|
|
34
|
+
- [What is the Style Editor?](#what-is-the-style-editor)
|
|
35
|
+
- [Quick Start](#quick-start)
|
|
36
|
+
- [`defineStyleEditorSchema()`](#definestyleeditorschemaform)
|
|
37
|
+
- [Field Types](#field-types)
|
|
38
|
+
- [`styleEditorField.input()`](#styleeditorfieldinputconfig)
|
|
39
|
+
- [`styleEditorField.dropdown()`](#styleeditorfielddropdownconfig)
|
|
40
|
+
- [`styleEditorField.radio()`](#styleditorfieldradioconfig)
|
|
41
|
+
- [`styleEditorField.checkboxGroup()`](#styleeditorfieldcheckboxgroupconfig)
|
|
42
|
+
- [`registerStyleEditorSchemas()`](#registerstyleeditorschemasschemas)
|
|
43
|
+
- [`useStyleEditorSchemas()` (React Hook)](#usestyleeditorschemasschemas-react-hook)
|
|
44
|
+
- [Accessing Style Values](#accessing-style-values)
|
|
45
|
+
- [Best Practices](#best-practices)
|
|
46
|
+
- [Complete Example](#complete-example)
|
|
33
47
|
- [Troubleshooting](#troubleshooting)
|
|
34
48
|
- [Common Issues & Solutions](#common-issues--solutions)
|
|
35
49
|
- [Debugging Tips](#debugging-tips)
|
|
@@ -473,6 +487,1135 @@ sendMessageToUVE({
|
|
|
473
487
|
| `CLIENT_READY` | --- |
|
|
474
488
|
| `EDIT_CONTENTLET` | `DotCMSBasicContentlet` |
|
|
475
489
|
|
|
490
|
+
## Style Editor
|
|
491
|
+
|
|
492
|
+
### What is the Style Editor?
|
|
493
|
+
|
|
494
|
+
The Style Editor is a powerful feature that enables content authors and developers to define dynamic, real-time editable properties for contentlets within the Universal Visual Editor (UVE). This allows for live customization of component appearance, layout, typography, colors, and any other configurable aspects without requiring code changes or page reloads.
|
|
495
|
+
|
|
496
|
+
**Key Benefits:**
|
|
497
|
+
|
|
498
|
+
- **Real-Time Visual Editing**: Modify component styles and see changes instantly in the editor
|
|
499
|
+
- **Content-Specific Customization**: Different content types can have unique style schemas
|
|
500
|
+
- **Developer-Controlled**: Developers define which properties are editable and how they're presented
|
|
501
|
+
- **Flexible Configuration**: Support for text inputs, dropdowns, radio buttons, and checkbox groups
|
|
502
|
+
- **Type-Safe**: Full TypeScript support with type inference for option values
|
|
503
|
+
|
|
504
|
+
**Use Cases:**
|
|
505
|
+
|
|
506
|
+
- Adjust typography (font size, family, weight)
|
|
507
|
+
- Configure layouts (grid columns, alignment, spacing)
|
|
508
|
+
- Customize colors and themes
|
|
509
|
+
- Toggle component features (borders, shadows, decorations)
|
|
510
|
+
- Control responsive behavior
|
|
511
|
+
- Modify animation settings
|
|
512
|
+
|
|
513
|
+
### Quick Start
|
|
514
|
+
|
|
515
|
+
**1. Install the required packages:**
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
npm install @dotcms/uve@latest
|
|
519
|
+
npm install @dotcms/types@latest --save-dev
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
**2. Define a style editor schema:**
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
import { defineStyleEditorSchema, styleEditorField } from '@dotcms/uve';
|
|
526
|
+
|
|
527
|
+
const mySchema = defineStyleEditorSchema({
|
|
528
|
+
contentType: 'BlogPost',
|
|
529
|
+
sections: [
|
|
530
|
+
{
|
|
531
|
+
title: 'Typography',
|
|
532
|
+
fields: [
|
|
533
|
+
styleEditorField.dropdown({
|
|
534
|
+
id: 'font-size',
|
|
535
|
+
label: 'Font Size',
|
|
536
|
+
options: [
|
|
537
|
+
{ label: 'Small (14px)', value: '14px' },
|
|
538
|
+
{ label: 'Medium (16px)', value: '16px' },
|
|
539
|
+
{ label: 'Large (18px)', value: '18px' }
|
|
540
|
+
]
|
|
541
|
+
})
|
|
542
|
+
]
|
|
543
|
+
}
|
|
544
|
+
]
|
|
545
|
+
});
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
**3. Register the schema:**
|
|
549
|
+
|
|
550
|
+
**Using React:**
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
import { useStyleEditorSchemas } from '@dotcms/react';
|
|
554
|
+
|
|
555
|
+
function MyComponent() {
|
|
556
|
+
useStyleEditorSchemas([mySchema]);
|
|
557
|
+
|
|
558
|
+
return <div>Your component content</div>;
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
**Using vanilla JavaScript:**
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
import { registerStyleEditorSchemas } from '@dotcms/uve';
|
|
566
|
+
|
|
567
|
+
registerStyleEditorSchemas([mySchema]);
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### `defineStyleEditorSchema(form)`
|
|
571
|
+
|
|
572
|
+
`defineStyleEditorSchema` creates a normalized style editor schema that UVE can process. It validates your form definition and converts it into the format expected by the Universal Visual Editor.
|
|
573
|
+
|
|
574
|
+
| Input | Type | Required | Description |
|
|
575
|
+
| ------ | ------------------ | -------- | ------------------------------------------------------ |
|
|
576
|
+
| `form` | `StyleEditorForm` | ✅ | The form definition with content type, sections, and fields |
|
|
577
|
+
|
|
578
|
+
**Returns:** `StyleEditorFormSchema` - A normalized schema ready for registration with UVE
|
|
579
|
+
|
|
580
|
+
#### StyleEditorForm Structure
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
interface StyleEditorForm {
|
|
584
|
+
contentType: string; // The content type identifier
|
|
585
|
+
sections: StyleEditorSection[]; // Array of form sections
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
interface StyleEditorSection {
|
|
589
|
+
title: string; // Section heading displayed in the editor
|
|
590
|
+
fields: StyleEditorField[]; // Array of field definitions
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
#### Usage
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
import { defineStyleEditorSchema, styleEditorField } from '@dotcms/uve';
|
|
598
|
+
|
|
599
|
+
const schema = defineStyleEditorSchema({
|
|
600
|
+
contentType: 'Activity',
|
|
601
|
+
sections: [
|
|
602
|
+
{
|
|
603
|
+
title: 'Typography',
|
|
604
|
+
fields: [
|
|
605
|
+
styleEditorField.input({
|
|
606
|
+
id: 'heading-size',
|
|
607
|
+
label: 'Heading Size',
|
|
608
|
+
inputType: 'number',
|
|
609
|
+
placeholder: '24'
|
|
610
|
+
}),
|
|
611
|
+
styleEditorField.dropdown({
|
|
612
|
+
id: 'font-family',
|
|
613
|
+
label: 'Font Family',
|
|
614
|
+
options: ['Arial', 'Helvetica', 'Georgia']
|
|
615
|
+
})
|
|
616
|
+
]
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
title: 'Layout',
|
|
620
|
+
fields: [
|
|
621
|
+
styleEditorField.radio({
|
|
622
|
+
id: 'alignment',
|
|
623
|
+
label: 'Text Alignment',
|
|
624
|
+
options: ['Left', 'Center', 'Right']
|
|
625
|
+
})
|
|
626
|
+
]
|
|
627
|
+
}
|
|
628
|
+
]
|
|
629
|
+
});
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
**⚠️ Important Notes:**
|
|
633
|
+
|
|
634
|
+
- Each field must have a unique `id` within the schema
|
|
635
|
+
- The `contentType` must match the content type in your dotCMS instance
|
|
636
|
+
- Schemas are only processed when UVE is in EDIT mode
|
|
637
|
+
|
|
638
|
+
### Field Types
|
|
639
|
+
|
|
640
|
+
The Style Editor supports four field types, each designed for specific use cases. Use the `styleEditorField` factory functions to create type-safe field definitions.
|
|
641
|
+
|
|
642
|
+
#### `styleEditorField.input(config)`
|
|
643
|
+
|
|
644
|
+
Creates a text or number input field for free-form entry.
|
|
645
|
+
|
|
646
|
+
**Configuration:**
|
|
647
|
+
|
|
648
|
+
```typescript
|
|
649
|
+
interface StyleEditorInputFieldConfig {
|
|
650
|
+
id: string; // Unique identifier
|
|
651
|
+
label: string; // Display label
|
|
652
|
+
inputType: StyleEditorFieldInputType; // Input type
|
|
653
|
+
placeholder?: string; // Optional placeholder text
|
|
654
|
+
}
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
**Use Cases:**
|
|
658
|
+
|
|
659
|
+
- Custom values (e.g., font sizes, margins, colors)
|
|
660
|
+
- Numeric settings (e.g., animation duration, opacity)
|
|
661
|
+
- Text values (e.g., CSS class names, custom IDs)
|
|
662
|
+
|
|
663
|
+
**Examples:**
|
|
664
|
+
|
|
665
|
+
```typescript
|
|
666
|
+
// Number input for pixel values
|
|
667
|
+
styleEditorField.input({
|
|
668
|
+
id: 'padding-top',
|
|
669
|
+
label: 'Top Padding (px)',
|
|
670
|
+
inputType: 'number',
|
|
671
|
+
placeholder: '16'
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
// Text input for custom CSS
|
|
675
|
+
styleEditorField.input({
|
|
676
|
+
id: 'custom-class',
|
|
677
|
+
label: 'Custom CSS Class',
|
|
678
|
+
inputType: 'text',
|
|
679
|
+
placeholder: 'my-custom-style'
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// Number input with decimal values
|
|
683
|
+
styleEditorField.input({
|
|
684
|
+
id: 'opacity',
|
|
685
|
+
label: 'Opacity',
|
|
686
|
+
inputType: 'number',
|
|
687
|
+
placeholder: '1.0'
|
|
688
|
+
});
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
#### `styleEditorField.dropdown(config)`
|
|
692
|
+
|
|
693
|
+
Creates a dropdown (select) field with predefined options. Users can select one value from the list.
|
|
694
|
+
|
|
695
|
+
**Configuration:**
|
|
696
|
+
|
|
697
|
+
```typescript
|
|
698
|
+
interface StyleEditorDropdownField {
|
|
699
|
+
id: string; // Unique identifier
|
|
700
|
+
label: string; // Display label
|
|
701
|
+
options: StyleEditorOption[]; // Array of options
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
type StyleEditorOption = { label: string; value: string };
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
**Use Cases:**
|
|
708
|
+
|
|
709
|
+
- Predefined sizes (e.g., small, medium, large)
|
|
710
|
+
- Font families or style presets
|
|
711
|
+
- Color themes
|
|
712
|
+
- Any single-choice selection from a list
|
|
713
|
+
|
|
714
|
+
**Examples:**
|
|
715
|
+
|
|
716
|
+
```typescript
|
|
717
|
+
// Font size options
|
|
718
|
+
const FONT_SIZES = [
|
|
719
|
+
{ label: 'Extra Small (12px)', value: '12px' },
|
|
720
|
+
{ label: 'Small (14px)', value: '14px' },
|
|
721
|
+
{ label: 'Medium (16px)', value: '16px' },
|
|
722
|
+
{ label: 'Large (18px)', value: '18px' },
|
|
723
|
+
{ label: 'Extra Large (24px)', value: '24px' }
|
|
724
|
+
];
|
|
725
|
+
|
|
726
|
+
styleEditorField.dropdown({
|
|
727
|
+
id: 'font-size',
|
|
728
|
+
label: 'Font Size',
|
|
729
|
+
options: FONT_SIZES
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
// Theme selection
|
|
733
|
+
styleEditorField.dropdown({
|
|
734
|
+
id: 'theme',
|
|
735
|
+
label: 'Color Theme',
|
|
736
|
+
options: [
|
|
737
|
+
{ label: 'Light Theme', value: 'light' },
|
|
738
|
+
{ label: 'Dark Theme', value: 'dark' },
|
|
739
|
+
{ label: 'High Contrast', value: 'high-contrast' }
|
|
740
|
+
]
|
|
741
|
+
});
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
#### `styleEditorField.radio(config)`
|
|
746
|
+
|
|
747
|
+
Creates a radio button group for single-choice selection. Optionally supports images for visual selection.
|
|
748
|
+
|
|
749
|
+
**Configuration:**
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
interface StyleEditorRadioField {
|
|
753
|
+
id: string; // Unique identifier
|
|
754
|
+
label: string; // Display label
|
|
755
|
+
options: StyleEditorRadioOption[]; // Array of options
|
|
756
|
+
columns?: 1 | 2; // Layout: 1 or 2 columns (default: 1)
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
type StyleEditorRadioOption = {
|
|
760
|
+
label: string;
|
|
761
|
+
value: string;
|
|
762
|
+
imageURL?: string; // Optional preview image
|
|
763
|
+
};
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
**Use Cases:**
|
|
767
|
+
|
|
768
|
+
- Layout selection with visual previews
|
|
769
|
+
- Alignment options (left, center, right)
|
|
770
|
+
- Style variants with images
|
|
771
|
+
- Any single-choice where visual feedback is helpful
|
|
772
|
+
|
|
773
|
+
**Examples:**
|
|
774
|
+
|
|
775
|
+
```typescript
|
|
776
|
+
// Simple text options
|
|
777
|
+
styleEditorField.radio({
|
|
778
|
+
id: 'text-align',
|
|
779
|
+
label: 'Text Alignment',
|
|
780
|
+
options: [
|
|
781
|
+
{ label: 'Left', value: 'left' },
|
|
782
|
+
{ label: 'Center', value: 'center' },
|
|
783
|
+
{ label: 'Right', value: 'right' },
|
|
784
|
+
{ label: 'Justify', value: 'justify' }
|
|
785
|
+
]
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
// Two-column layout with images
|
|
789
|
+
const LAYOUT_OPTIONS = [
|
|
790
|
+
{
|
|
791
|
+
label: 'Left Sidebar',
|
|
792
|
+
value: 'left',
|
|
793
|
+
imageURL: 'https://example.com/layouts/left-sidebar.png'
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
label: 'Right Sidebar',
|
|
797
|
+
value: 'right',
|
|
798
|
+
imageURL: 'https://example.com/layouts/right-sidebar.png'
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
label: 'Full Width',
|
|
802
|
+
value: 'full',
|
|
803
|
+
imageURL: 'https://example.com/layouts/full-width.png'
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
label: 'Split View',
|
|
807
|
+
value: 'split',
|
|
808
|
+
imageURL: 'https://example.com/layouts/split-view.png'
|
|
809
|
+
}
|
|
810
|
+
];
|
|
811
|
+
|
|
812
|
+
styleEditorField.radio({
|
|
813
|
+
id: 'page-layout',
|
|
814
|
+
label: 'Page Layout',
|
|
815
|
+
columns: 2, // Display in 2-column grid
|
|
816
|
+
options: LAYOUT_OPTIONS
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
// Font weight selection
|
|
820
|
+
styleEditorField.radio({
|
|
821
|
+
id: 'font-weight',
|
|
822
|
+
label: 'Font Weight',
|
|
823
|
+
options: [
|
|
824
|
+
{ label: 'Normal', value: '400' },
|
|
825
|
+
{ label: 'Medium', value: '500' },
|
|
826
|
+
{ label: 'Semi-Bold', value: '600' },
|
|
827
|
+
{ label: 'Bold', value: '700' }
|
|
828
|
+
]
|
|
829
|
+
});
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
**💡 Image Guidelines:**
|
|
833
|
+
|
|
834
|
+
- Use clear, recognizable preview images
|
|
835
|
+
- Recommended size: 200x150px or similar aspect ratio
|
|
836
|
+
- Use consistent image dimensions within a radio group
|
|
837
|
+
- Images should clearly differentiate between options
|
|
838
|
+
|
|
839
|
+
#### `styleEditorField.checkboxGroup(config)`
|
|
840
|
+
|
|
841
|
+
Creates a group of checkboxes for multi-selection. Each checkbox returns a boolean value (checked/unchecked).
|
|
842
|
+
|
|
843
|
+
**Configuration:**
|
|
844
|
+
|
|
845
|
+
```typescript
|
|
846
|
+
interface StyleEditorCheckboxGroupField {
|
|
847
|
+
id: string; // Unique identifier for the group
|
|
848
|
+
label: string; // Display label for the group
|
|
849
|
+
options: StyleEditorCheckboxOption[]; // Array of checkbox options
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
interface StyleEditorCheckboxOption {
|
|
853
|
+
label: string; // Display text for the checkbox
|
|
854
|
+
key: string; // Unique identifier (NOT 'value')
|
|
855
|
+
}
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
**⚠️ Important:** Checkbox options use `key` instead of `value` because the actual value is boolean (true/false).
|
|
859
|
+
|
|
860
|
+
**Use Cases:**
|
|
861
|
+
|
|
862
|
+
- Text decorations (bold, italic, underline)
|
|
863
|
+
- Feature toggles (enable shadows, borders, animations)
|
|
864
|
+
- Multiple style attributes
|
|
865
|
+
- Any multi-select boolean options
|
|
866
|
+
|
|
867
|
+
**Examples:**
|
|
868
|
+
|
|
869
|
+
```typescript
|
|
870
|
+
// Typography settings
|
|
871
|
+
styleEditorField.checkboxGroup({
|
|
872
|
+
id: 'text-style',
|
|
873
|
+
label: 'Text Style',
|
|
874
|
+
options: [
|
|
875
|
+
{ label: 'Bold', key: 'bold' },
|
|
876
|
+
{ label: 'Italic', key: 'italic' },
|
|
877
|
+
{ label: 'Underline', key: 'underline' },
|
|
878
|
+
{ label: 'Strikethrough', key: 'strikethrough' }
|
|
879
|
+
]
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
// Component features
|
|
883
|
+
styleEditorField.checkboxGroup({
|
|
884
|
+
id: 'component-features',
|
|
885
|
+
label: 'Component Features',
|
|
886
|
+
options: [
|
|
887
|
+
{ label: 'Show Shadow', key: 'shadow' },
|
|
888
|
+
{ label: 'Show Border', key: 'border' },
|
|
889
|
+
{ label: 'Enable Animation', key: 'animate' },
|
|
890
|
+
{ label: 'Rounded Corners', key: 'rounded' }
|
|
891
|
+
]
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
// Responsive behavior
|
|
895
|
+
styleEditorField.checkboxGroup({
|
|
896
|
+
id: 'responsive',
|
|
897
|
+
label: 'Responsive Options',
|
|
898
|
+
options: [
|
|
899
|
+
{ label: 'Hide on Mobile', key: 'hide-mobile' },
|
|
900
|
+
{ label: 'Hide on Tablet', key: 'hide-tablet' },
|
|
901
|
+
{ label: 'Full Width on Mobile', key: 'full-width-mobile' }
|
|
902
|
+
]
|
|
903
|
+
});
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
**Return Value Structure:**
|
|
907
|
+
|
|
908
|
+
```typescript
|
|
909
|
+
// Example return value when checkboxes are checked
|
|
910
|
+
{
|
|
911
|
+
"bold": true,
|
|
912
|
+
"italic": false,
|
|
913
|
+
"underline": true,
|
|
914
|
+
"strikethrough": false
|
|
915
|
+
}
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
### `registerStyleEditorSchemas(schemas)`
|
|
919
|
+
|
|
920
|
+
`registerStyleEditorSchemas` registers one or more style editor schemas with UVE. This function should be called during your component initialization to make the schemas available in the editor.
|
|
921
|
+
|
|
922
|
+
| Input | Type | Required | Description |
|
|
923
|
+
| --------- | --------------------------- | -------- | ------------------------------------------------ |
|
|
924
|
+
| `schemas` | `StyleEditorFormSchema[]` | ✅ | Array of normalized schemas from `defineStyleEditorSchema` |
|
|
925
|
+
|
|
926
|
+
**Returns:** `void`
|
|
927
|
+
|
|
928
|
+
**Behavior:**
|
|
929
|
+
|
|
930
|
+
- Only registers schemas when UVE is in **EDIT** mode
|
|
931
|
+
- Silently returns if UVE is not in EDIT mode
|
|
932
|
+
- Validates that each schema has a `contentType` property
|
|
933
|
+
- Logs a warning and skips schemas without `contentType`
|
|
934
|
+
- Sends validated schemas to UVE via internal messaging
|
|
935
|
+
|
|
936
|
+
#### Usage
|
|
937
|
+
|
|
938
|
+
```typescript
|
|
939
|
+
import { defineStyleEditorSchema, styleEditorField, registerStyleEditorSchemas } from '@dotcms/uve';
|
|
940
|
+
|
|
941
|
+
// Create schemas
|
|
942
|
+
const blogSchema = defineStyleEditorSchema({
|
|
943
|
+
contentType: 'BlogPost',
|
|
944
|
+
sections: [
|
|
945
|
+
{
|
|
946
|
+
title: 'Typography',
|
|
947
|
+
fields: [
|
|
948
|
+
styleEditorField.dropdown({
|
|
949
|
+
id: 'font-size',
|
|
950
|
+
label: 'Font Size',
|
|
951
|
+
options: ['14px', '16px', '18px']
|
|
952
|
+
})
|
|
953
|
+
]
|
|
954
|
+
}
|
|
955
|
+
]
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
const activitySchema = defineStyleEditorSchema({
|
|
959
|
+
contentType: 'Activity',
|
|
960
|
+
sections: [
|
|
961
|
+
{
|
|
962
|
+
title: 'Layout',
|
|
963
|
+
fields: [
|
|
964
|
+
styleEditorField.radio({
|
|
965
|
+
id: 'layout',
|
|
966
|
+
label: 'Layout',
|
|
967
|
+
options: ['Left', 'Right', 'Center']
|
|
968
|
+
})
|
|
969
|
+
]
|
|
970
|
+
}
|
|
971
|
+
]
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
// Register multiple schemas at once
|
|
975
|
+
registerStyleEditorSchemas([blogSchema, activitySchema]);
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
**⚠️ Important Notes:**
|
|
979
|
+
|
|
980
|
+
- Call this function after UVE initialization (`initUVE`)
|
|
981
|
+
- Schemas are only processed in EDIT mode
|
|
982
|
+
- Missing `contentType` will cause the schema to be skipped
|
|
983
|
+
- You can register multiple schemas for different content types
|
|
984
|
+
|
|
985
|
+
### `useStyleEditorSchemas(schemas)` (React Hook)
|
|
986
|
+
|
|
987
|
+
**Available in:** `@dotcms/react` package
|
|
988
|
+
|
|
989
|
+
`useStyleEditorSchemas` is a React hook that simplifies schema registration by automatically handling the component lifecycle. It registers schemas when the component mounts and re-registers if the schemas array reference changes.
|
|
990
|
+
|
|
991
|
+
| Input | Type | Required | Description |
|
|
992
|
+
| -------- | --------------------------- | -------- | ------------------------------- |
|
|
993
|
+
| `schemas` | `StyleEditorFormSchema[]` | ✅ | Array of normalized form schemas |
|
|
994
|
+
|
|
995
|
+
**Returns:** `void`
|
|
996
|
+
|
|
997
|
+
**Behavior:**
|
|
998
|
+
|
|
999
|
+
- Registers schemas on component mount
|
|
1000
|
+
- Re-registers when the `schemas` array reference changes
|
|
1001
|
+
- Internally calls `registerStyleEditorSchemas()`
|
|
1002
|
+
- Safe to call in multiple components
|
|
1003
|
+
|
|
1004
|
+
#### Usage
|
|
1005
|
+
|
|
1006
|
+
```typescript
|
|
1007
|
+
import { useStyleEditorSchemas } from '@dotcms/react';
|
|
1008
|
+
import { defineStyleEditorSchema, styleEditorField } from '@dotcms/uve';
|
|
1009
|
+
|
|
1010
|
+
function BlogPostEditor() {
|
|
1011
|
+
// Define schemas
|
|
1012
|
+
const schemas = [
|
|
1013
|
+
defineStyleEditorSchema({
|
|
1014
|
+
contentType: 'BlogPost',
|
|
1015
|
+
sections: [
|
|
1016
|
+
{
|
|
1017
|
+
title: 'Typography',
|
|
1018
|
+
fields: [
|
|
1019
|
+
styleEditorField.dropdown({
|
|
1020
|
+
id: 'font-size',
|
|
1021
|
+
label: 'Font Size',
|
|
1022
|
+
options: [
|
|
1023
|
+
{ label: '14px', value: '14px' },
|
|
1024
|
+
{ label: '16px', value: '16px' },
|
|
1025
|
+
{ label: '18px', value: '18px' },
|
|
1026
|
+
{ label: '24px', value: '24px' }
|
|
1027
|
+
]
|
|
1028
|
+
}),
|
|
1029
|
+
styleEditorField.radio({
|
|
1030
|
+
id: 'font-weight',
|
|
1031
|
+
label: 'Font Weight',
|
|
1032
|
+
options: [
|
|
1033
|
+
{ label: 'Normal', value: 'normal' },
|
|
1034
|
+
{ label: 'Bold', value: 'bold' }
|
|
1035
|
+
]
|
|
1036
|
+
})
|
|
1037
|
+
]
|
|
1038
|
+
}
|
|
1039
|
+
]
|
|
1040
|
+
})
|
|
1041
|
+
];
|
|
1042
|
+
|
|
1043
|
+
// Register schemas automatically
|
|
1044
|
+
useStyleEditorSchemas(schemas);
|
|
1045
|
+
|
|
1046
|
+
return (
|
|
1047
|
+
<div>
|
|
1048
|
+
<h1>Blog Post Editor</h1>
|
|
1049
|
+
{/* Your component content */}
|
|
1050
|
+
</div>
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
```
|
|
1054
|
+
|
|
1055
|
+
**💡 Performance Tip:** For better performance in components that re-render frequently, you can optionally use `useMemo` to prevent re-creating the schema on every render:
|
|
1056
|
+
|
|
1057
|
+
```typescript
|
|
1058
|
+
import { useMemo } from 'react';
|
|
1059
|
+
|
|
1060
|
+
function BlogPostEditor() {
|
|
1061
|
+
const schemas = useMemo(
|
|
1062
|
+
() => [
|
|
1063
|
+
defineStyleEditorSchema({
|
|
1064
|
+
/* schema definition */
|
|
1065
|
+
})
|
|
1066
|
+
],
|
|
1067
|
+
[] // Empty deps = create once
|
|
1068
|
+
);
|
|
1069
|
+
|
|
1070
|
+
useStyleEditorSchemas(schemas);
|
|
1071
|
+
return <div>Content</div>;
|
|
1072
|
+
}
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
### Accessing Style Values
|
|
1076
|
+
|
|
1077
|
+
Style Editor values are managed internally by UVE and passed to your components through the `styleProperties` attribute. This attribute is available in your contentlet component props.
|
|
1078
|
+
|
|
1079
|
+
#### In React Components
|
|
1080
|
+
|
|
1081
|
+
When rendering contentlets, style properties are accessed through the `styleProperties` prop:
|
|
1082
|
+
|
|
1083
|
+
```typescript
|
|
1084
|
+
import { DotCMSContentlet } from '@dotcms/types';
|
|
1085
|
+
|
|
1086
|
+
interface ActivityProps {
|
|
1087
|
+
contentlet: DotCMSContentlet;
|
|
1088
|
+
styleProperties?: Record<string, any>;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
function Activity(props: ActivityProps) {
|
|
1092
|
+
const { title, description, styleProperties } = props; // Contentlet information
|
|
1093
|
+
|
|
1094
|
+
// Access style values using dot notation or bracket notation
|
|
1095
|
+
const fontSize = styleProperties?.['font-size'];
|
|
1096
|
+
const textAlign = styleProperties?.text;
|
|
1097
|
+
const layout = styleProperties?.layout;
|
|
1098
|
+
|
|
1099
|
+
return (
|
|
1100
|
+
<div style={{ fontSize, textAlign }}>
|
|
1101
|
+
<h1>{title}</h1>
|
|
1102
|
+
<p>{description}</p>
|
|
1103
|
+
</div>
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
#### Value Types by Field Type
|
|
1109
|
+
|
|
1110
|
+
**Input Field:**
|
|
1111
|
+
|
|
1112
|
+
```typescript
|
|
1113
|
+
// Returns: string (text) or number (number input)
|
|
1114
|
+
const fontSize: string = '16px';
|
|
1115
|
+
const padding: number = 24;
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
**Dropdown Field:**
|
|
1119
|
+
|
|
1120
|
+
```typescript
|
|
1121
|
+
// Returns: string (the selected value)
|
|
1122
|
+
const theme: string = 'light';
|
|
1123
|
+
const fontFamily: string = 'Arial';
|
|
1124
|
+
```
|
|
1125
|
+
|
|
1126
|
+
**Radio Field:**
|
|
1127
|
+
|
|
1128
|
+
```typescript
|
|
1129
|
+
// Returns: string (the selected value)
|
|
1130
|
+
const layout: string = 'left';
|
|
1131
|
+
const alignment: string = 'center';
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
**Checkbox Group:**
|
|
1135
|
+
|
|
1136
|
+
```typescript
|
|
1137
|
+
// Returns: Record<string, boolean> (object with key-value pairs)
|
|
1138
|
+
const textStyles: Record<string, boolean> = {
|
|
1139
|
+
bold: true,
|
|
1140
|
+
italic: false,
|
|
1141
|
+
underline: true,
|
|
1142
|
+
strikethrough: false
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
// Access individual values
|
|
1146
|
+
if (textStyles.bold) {
|
|
1147
|
+
// Apply bold styling
|
|
1148
|
+
}
|
|
1149
|
+
```
|
|
1150
|
+
|
|
1151
|
+
#### Applying Style Values
|
|
1152
|
+
|
|
1153
|
+
Use the style values to conditionally render styles, classes, or component variants:
|
|
1154
|
+
|
|
1155
|
+
```typescript
|
|
1156
|
+
function BlogPost(props) {
|
|
1157
|
+
const { title, body, styleProperties } = props;
|
|
1158
|
+
|
|
1159
|
+
// Example: Apply dynamic font size
|
|
1160
|
+
const fontSize = styleProperties?.['font-size'] || '16px';
|
|
1161
|
+
|
|
1162
|
+
// Example: Apply layout classes
|
|
1163
|
+
const layout = styleProperties?.layout || 'default';
|
|
1164
|
+
const layoutClass = `layout-${layout}`;
|
|
1165
|
+
|
|
1166
|
+
// Example: Apply checkbox group values
|
|
1167
|
+
const textStyles = styleProperties?.['text-style'] || {};
|
|
1168
|
+
const textStyleClasses = [
|
|
1169
|
+
textStyles.bold ? 'font-bold' : '',
|
|
1170
|
+
textStyles.italic ? 'font-italic' : '',
|
|
1171
|
+
textStyles.underline ? 'text-underline' : ''
|
|
1172
|
+
]
|
|
1173
|
+
.filter(Boolean)
|
|
1174
|
+
.join(' ');
|
|
1175
|
+
|
|
1176
|
+
return (
|
|
1177
|
+
<div className={`${layoutClass} ${textStyleClasses}`} style={{ fontSize }}>
|
|
1178
|
+
<h1>{title}</h1>
|
|
1179
|
+
<p>{body}</p>
|
|
1180
|
+
</div>
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
```
|
|
1184
|
+
|
|
1185
|
+
**💡 Note:** The `styleProperties` prop is automatically passed to your contentlet components by the framework SDK when UVE is active and style schemas are registered.
|
|
1186
|
+
|
|
1187
|
+
### Best Practices
|
|
1188
|
+
|
|
1189
|
+
#### 1. Use Meaningful IDs and Labels
|
|
1190
|
+
|
|
1191
|
+
```typescript
|
|
1192
|
+
// ✅ Good: Clear, descriptive IDs and labels
|
|
1193
|
+
styleEditorField.dropdown({
|
|
1194
|
+
id: 'heading-font-size',
|
|
1195
|
+
label: 'Heading Font Size',
|
|
1196
|
+
options: [
|
|
1197
|
+
{ label: 'Small (18px)', value: '18px' },
|
|
1198
|
+
{ label: 'Medium (24px)', value: '24px' },
|
|
1199
|
+
{ label: 'Large (32px)', value: '32px' }
|
|
1200
|
+
]
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
// ❌ Bad: Vague IDs and labels
|
|
1204
|
+
styleEditorField.dropdown({
|
|
1205
|
+
id: 'size',
|
|
1206
|
+
label: 'Size',
|
|
1207
|
+
options: ['18px', '24px', '32px']
|
|
1208
|
+
});
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
#### 2. Group Related Fields in Sections
|
|
1212
|
+
|
|
1213
|
+
```typescript
|
|
1214
|
+
// ✅ Good: Logical grouping by functionality
|
|
1215
|
+
defineStyleEditorSchema({
|
|
1216
|
+
contentType: 'BlogPost',
|
|
1217
|
+
sections: [
|
|
1218
|
+
{
|
|
1219
|
+
title: 'Typography',
|
|
1220
|
+
fields: [
|
|
1221
|
+
/* font-related fields */
|
|
1222
|
+
]
|
|
1223
|
+
},
|
|
1224
|
+
{
|
|
1225
|
+
title: 'Layout',
|
|
1226
|
+
fields: [
|
|
1227
|
+
/* layout-related fields */
|
|
1228
|
+
]
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
title: 'Colors',
|
|
1232
|
+
fields: [
|
|
1233
|
+
/* color-related fields */
|
|
1234
|
+
]
|
|
1235
|
+
}
|
|
1236
|
+
]
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
// ❌ Bad: All fields in one section
|
|
1240
|
+
defineStyleEditorSchema({
|
|
1241
|
+
contentType: 'BlogPost',
|
|
1242
|
+
sections: [
|
|
1243
|
+
{
|
|
1244
|
+
title: 'Settings',
|
|
1245
|
+
fields: [
|
|
1246
|
+
/* all fields mixed together */
|
|
1247
|
+
]
|
|
1248
|
+
}
|
|
1249
|
+
]
|
|
1250
|
+
});
|
|
1251
|
+
```
|
|
1252
|
+
|
|
1253
|
+
#### 3. Provide Clear Option Labels
|
|
1254
|
+
|
|
1255
|
+
```typescript
|
|
1256
|
+
// ✅ Good: Descriptive labels with context
|
|
1257
|
+
styleEditorField.dropdown({
|
|
1258
|
+
id: 'font-size',
|
|
1259
|
+
label: 'Font Size',
|
|
1260
|
+
options: [
|
|
1261
|
+
{ label: 'Extra Small (12px)', value: '12px' },
|
|
1262
|
+
{ label: 'Small (14px)', value: '14px' },
|
|
1263
|
+
{ label: 'Medium (16px)', value: '16px' },
|
|
1264
|
+
{ label: 'Large (18px)', value: '18px' }
|
|
1265
|
+
]
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
// ❌ Bad: Unclear labels
|
|
1269
|
+
styleEditorField.dropdown({
|
|
1270
|
+
id: 'font-size',
|
|
1271
|
+
label: 'Font Size',
|
|
1272
|
+
options: ['XS', 'S', 'M', 'L']
|
|
1273
|
+
});
|
|
1274
|
+
```
|
|
1275
|
+
|
|
1276
|
+
#### 4. Use Appropriate Field Types
|
|
1277
|
+
|
|
1278
|
+
```typescript
|
|
1279
|
+
// ✅ Good: Radio with images for visual layouts
|
|
1280
|
+
styleEditorField.radio({
|
|
1281
|
+
id: 'page-layout',
|
|
1282
|
+
label: 'Layout',
|
|
1283
|
+
columns: 2,
|
|
1284
|
+
options: [
|
|
1285
|
+
{ label: 'Left', value: 'left', imageURL: '...' },
|
|
1286
|
+
{ label: 'Right', value: 'right', imageURL: '...' }
|
|
1287
|
+
]
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
// ✅ Good: Dropdown for text-only options
|
|
1291
|
+
styleEditorField.dropdown({
|
|
1292
|
+
id: 'font-family',
|
|
1293
|
+
label: 'Font',
|
|
1294
|
+
options: ['Arial', 'Georgia', 'Verdana']
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
// ✅ Good: Checkbox group for boolean flags
|
|
1298
|
+
styleEditorField.checkboxGroup({
|
|
1299
|
+
id: 'text-decorations',
|
|
1300
|
+
label: 'Text Decorations',
|
|
1301
|
+
options: [
|
|
1302
|
+
{ label: 'Bold', key: 'bold' },
|
|
1303
|
+
{ label: 'Italic', key: 'italic' }
|
|
1304
|
+
]
|
|
1305
|
+
});
|
|
1306
|
+
```
|
|
1307
|
+
|
|
1308
|
+
#### 5. Validate Content Type Matching
|
|
1309
|
+
|
|
1310
|
+
```typescript
|
|
1311
|
+
// ✅ Good: Content type matches your dotCMS content type
|
|
1312
|
+
defineStyleEditorSchema({
|
|
1313
|
+
contentType: 'BlogPost', // Matches content type in dotCMS
|
|
1314
|
+
sections: [
|
|
1315
|
+
/* ... */
|
|
1316
|
+
]
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
// ❌ Bad: Typo or mismatch
|
|
1320
|
+
defineStyleEditorSchema({
|
|
1321
|
+
contentType: 'blog-post', // Won't match 'BlogPost' in dotCMS
|
|
1322
|
+
sections: [
|
|
1323
|
+
/* ... */
|
|
1324
|
+
]
|
|
1325
|
+
});
|
|
1326
|
+
```
|
|
1327
|
+
|
|
1328
|
+
#### 6. Provide Sensible Defaults
|
|
1329
|
+
|
|
1330
|
+
When using style properties, always provide fallback defaults:
|
|
1331
|
+
|
|
1332
|
+
```typescript
|
|
1333
|
+
// ✅ Good: Fallback values prevent errors
|
|
1334
|
+
const fontSize = styleProperties?.['font-size'] || '16px';
|
|
1335
|
+
const layout = styleProperties?.layout || 'default';
|
|
1336
|
+
const textStyles = styleProperties?.['text-style'] || {};
|
|
1337
|
+
|
|
1338
|
+
// ❌ Bad: No fallbacks (could cause errors)
|
|
1339
|
+
const fontSize = styleProperties?.['font-size'];
|
|
1340
|
+
const layout = styleProperties?.layout;
|
|
1341
|
+
```
|
|
1342
|
+
|
|
1343
|
+
### Complete Example
|
|
1344
|
+
|
|
1345
|
+
Here's a comprehensive example demonstrating all Style Editor features:
|
|
1346
|
+
|
|
1347
|
+
```typescript
|
|
1348
|
+
import { useStyleEditorSchemas } from '@dotcms/react';
|
|
1349
|
+
import { defineStyleEditorSchema, styleEditorField } from '@dotcms/uve';
|
|
1350
|
+
|
|
1351
|
+
export function BlogPostStyleEditor() {
|
|
1352
|
+
// Define option constants
|
|
1353
|
+
const FONT_SIZES = [
|
|
1354
|
+
{ label: 'Extra Small (12px)', value: '12px' },
|
|
1355
|
+
{ label: 'Small (14px)', value: '14px' },
|
|
1356
|
+
{ label: 'Medium (16px)', value: '16px' },
|
|
1357
|
+
{ label: 'Large (18px)', value: '18px' },
|
|
1358
|
+
{ label: 'Extra Large (24px)', value: '24px' },
|
|
1359
|
+
{ label: 'Huge (32px)', value: '32px' }
|
|
1360
|
+
];
|
|
1361
|
+
|
|
1362
|
+
const FONT_FAMILIES = [
|
|
1363
|
+
{ label: 'Arial', value: 'arial' },
|
|
1364
|
+
{ label: 'Georgia', value: 'georgia' },
|
|
1365
|
+
{ label: 'Helvetica', value: 'helvetica' },
|
|
1366
|
+
{ label: 'Times New Roman', value: 'times' },
|
|
1367
|
+
{ label: 'Verdana', value: 'verdana' },
|
|
1368
|
+
{ label: 'Courier New', value: 'courier' }
|
|
1369
|
+
];
|
|
1370
|
+
|
|
1371
|
+
const LAYOUT_OPTIONS = [
|
|
1372
|
+
{
|
|
1373
|
+
label: 'Left Sidebar',
|
|
1374
|
+
value: 'sidebar-left',
|
|
1375
|
+
imageURL: 'https://example.com/layouts/sidebar-left.png'
|
|
1376
|
+
},
|
|
1377
|
+
{
|
|
1378
|
+
label: 'Right Sidebar',
|
|
1379
|
+
value: 'sidebar-right',
|
|
1380
|
+
imageURL: 'https://example.com/layouts/sidebar-right.png'
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
label: 'Full Width',
|
|
1384
|
+
value: 'full-width',
|
|
1385
|
+
imageURL: 'https://example.com/layouts/full-width.png'
|
|
1386
|
+
},
|
|
1387
|
+
{
|
|
1388
|
+
label: 'Centered',
|
|
1389
|
+
value: 'centered',
|
|
1390
|
+
imageURL: 'https://example.com/layouts/centered.png'
|
|
1391
|
+
}
|
|
1392
|
+
];
|
|
1393
|
+
|
|
1394
|
+
const COLOR_THEMES = [
|
|
1395
|
+
{ label: 'Light Theme', value: 'light' },
|
|
1396
|
+
{ label: 'Dark Theme', value: 'dark' },
|
|
1397
|
+
{ label: 'High Contrast', value: 'high-contrast' },
|
|
1398
|
+
{ label: 'Sepia', value: 'sepia' }
|
|
1399
|
+
];
|
|
1400
|
+
|
|
1401
|
+
// Define schema (optionally use useMemo to prevent re-creation on every render)
|
|
1402
|
+
const schemas = [
|
|
1403
|
+
defineStyleEditorSchema({
|
|
1404
|
+
contentType: 'BlogPost',
|
|
1405
|
+
sections: [
|
|
1406
|
+
{
|
|
1407
|
+
title: 'Typography',
|
|
1408
|
+
fields: [
|
|
1409
|
+
styleEditorField.dropdown({
|
|
1410
|
+
id: 'heading-font-size',
|
|
1411
|
+
label: 'Heading Font Size',
|
|
1412
|
+
options: FONT_SIZES
|
|
1413
|
+
}),
|
|
1414
|
+
styleEditorField.dropdown({
|
|
1415
|
+
id: 'body-font-size',
|
|
1416
|
+
label: 'Body Font Size',
|
|
1417
|
+
options: FONT_SIZES.slice(0, 4) // Only smaller sizes
|
|
1418
|
+
}),
|
|
1419
|
+
styleEditorField.dropdown({
|
|
1420
|
+
id: 'font-family',
|
|
1421
|
+
label: 'Font Family',
|
|
1422
|
+
options: FONT_FAMILIES
|
|
1423
|
+
}),
|
|
1424
|
+
styleEditorField.input({
|
|
1425
|
+
id: 'line-height',
|
|
1426
|
+
label: 'Line Height',
|
|
1427
|
+
inputType: 'number',
|
|
1428
|
+
placeholder: '1.5'
|
|
1429
|
+
}),
|
|
1430
|
+
styleEditorField.checkboxGroup({
|
|
1431
|
+
id: 'text-style',
|
|
1432
|
+
label: 'Text Style',
|
|
1433
|
+
options: [
|
|
1434
|
+
{ label: 'Bold Headings', key: 'bold-headings' },
|
|
1435
|
+
{ label: 'Italic Quotes', key: 'italic-quotes' },
|
|
1436
|
+
{ label: 'Underline Links', key: 'underline-links' }
|
|
1437
|
+
]
|
|
1438
|
+
})
|
|
1439
|
+
]
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
title: 'Layout',
|
|
1443
|
+
fields: [
|
|
1444
|
+
styleEditorField.radio({
|
|
1445
|
+
id: 'page-layout',
|
|
1446
|
+
label: 'Page Layout',
|
|
1447
|
+
columns: 2,
|
|
1448
|
+
options: LAYOUT_OPTIONS
|
|
1449
|
+
}),
|
|
1450
|
+
styleEditorField.radio({
|
|
1451
|
+
id: 'content-width',
|
|
1452
|
+
label: 'Content Width',
|
|
1453
|
+
options: [
|
|
1454
|
+
{ label: 'Narrow (800px)', value: '800px' },
|
|
1455
|
+
{ label: 'Medium (1000px)', value: '1000px' },
|
|
1456
|
+
{ label: 'Wide (1200px)', value: '1200px' },
|
|
1457
|
+
{ label: 'Extra Wide (1400px)', value: '1400px' }
|
|
1458
|
+
]
|
|
1459
|
+
}),
|
|
1460
|
+
styleEditorField.input({
|
|
1461
|
+
id: 'section-spacing',
|
|
1462
|
+
label: 'Section Spacing (px)',
|
|
1463
|
+
inputType: 'number',
|
|
1464
|
+
placeholder: '40'
|
|
1465
|
+
})
|
|
1466
|
+
]
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
title: 'Colors & Theme',
|
|
1470
|
+
fields: [
|
|
1471
|
+
styleEditorField.dropdown({
|
|
1472
|
+
id: 'color-theme',
|
|
1473
|
+
label: 'Color Theme',
|
|
1474
|
+
options: COLOR_THEMES
|
|
1475
|
+
}),
|
|
1476
|
+
styleEditorField.input({
|
|
1477
|
+
id: 'primary-color',
|
|
1478
|
+
label: 'Primary Color',
|
|
1479
|
+
inputType: 'text',
|
|
1480
|
+
placeholder: '#007bff'
|
|
1481
|
+
}),
|
|
1482
|
+
styleEditorField.input({
|
|
1483
|
+
id: 'secondary-color',
|
|
1484
|
+
label: 'Secondary Color',
|
|
1485
|
+
inputType: 'text',
|
|
1486
|
+
placeholder: '#6c757d'
|
|
1487
|
+
}),
|
|
1488
|
+
styleEditorField.input({
|
|
1489
|
+
id: 'background-color',
|
|
1490
|
+
label: 'Background Color',
|
|
1491
|
+
inputType: 'text',
|
|
1492
|
+
placeholder: '#ffffff'
|
|
1493
|
+
})
|
|
1494
|
+
]
|
|
1495
|
+
},
|
|
1496
|
+
{
|
|
1497
|
+
title: 'Component Features',
|
|
1498
|
+
fields: [
|
|
1499
|
+
styleEditorField.checkboxGroup({
|
|
1500
|
+
id: 'features',
|
|
1501
|
+
label: 'Enable Features',
|
|
1502
|
+
options: [
|
|
1503
|
+
{ label: 'Drop Shadow', key: 'shadow' },
|
|
1504
|
+
{ label: 'Border', key: 'border' },
|
|
1505
|
+
{ label: 'Rounded Corners', key: 'rounded' },
|
|
1506
|
+
{ label: 'Smooth Animations', key: 'animate' },
|
|
1507
|
+
{ label: 'Hover Effects', key: 'hover-effects' }
|
|
1508
|
+
]
|
|
1509
|
+
}),
|
|
1510
|
+
styleEditorField.checkboxGroup({
|
|
1511
|
+
id: 'responsive',
|
|
1512
|
+
label: 'Responsive Options',
|
|
1513
|
+
options: [
|
|
1514
|
+
{ label: 'Hide on Mobile', key: 'hide-mobile' },
|
|
1515
|
+
{ label: 'Stack on Tablet', key: 'stack-tablet' },
|
|
1516
|
+
{
|
|
1517
|
+
label: 'Full Width on Mobile',
|
|
1518
|
+
key: 'full-width-mobile'
|
|
1519
|
+
}
|
|
1520
|
+
]
|
|
1521
|
+
})
|
|
1522
|
+
]
|
|
1523
|
+
}
|
|
1524
|
+
]
|
|
1525
|
+
})
|
|
1526
|
+
];
|
|
1527
|
+
|
|
1528
|
+
// Register schemas with UVE
|
|
1529
|
+
useStyleEditorSchemas(schemas);
|
|
1530
|
+
|
|
1531
|
+
return (
|
|
1532
|
+
<div>
|
|
1533
|
+
<h1>Blog Post Style Editor</h1>
|
|
1534
|
+
<p>Style editor schema is registered and available in UVE edit mode.</p>
|
|
1535
|
+
</div>
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// Example: Using style properties in a component
|
|
1540
|
+
export function BlogPostRenderer(props) {
|
|
1541
|
+
const { title, body, styleProperties } = props;
|
|
1542
|
+
|
|
1543
|
+
// Extract style values with defaults
|
|
1544
|
+
const headingSize = styleProperties?.['heading-font-size'] || '24px';
|
|
1545
|
+
const bodySize = styleProperties?.['body-font-size'] || '16px';
|
|
1546
|
+
const fontFamily = styleProperties?.['font-family'] || 'arial';
|
|
1547
|
+
const lineHeight = styleProperties?.['line-height'] || '1.5';
|
|
1548
|
+
const layout = styleProperties?.['page-layout'] || 'full-width';
|
|
1549
|
+
const contentWidth = styleProperties?.['content-width'] || '1000px';
|
|
1550
|
+
const sectionSpacing = styleProperties?.['section-spacing'] || 40;
|
|
1551
|
+
const theme = styleProperties?.['color-theme'] || 'light';
|
|
1552
|
+
const primaryColor = styleProperties?.['primary-color'] || '#007bff';
|
|
1553
|
+
const backgroundColor = styleProperties?.['background-color'] || '#ffffff';
|
|
1554
|
+
|
|
1555
|
+
// Extract checkbox group values
|
|
1556
|
+
const textStyle = styleProperties?.['text-style'] || {};
|
|
1557
|
+
const features = styleProperties?.features || {};
|
|
1558
|
+
const responsive = styleProperties?.responsive || {};
|
|
1559
|
+
|
|
1560
|
+
// Build CSS classes based on values
|
|
1561
|
+
const containerClasses = [
|
|
1562
|
+
`layout-${layout}`,
|
|
1563
|
+
`theme-${theme}`,
|
|
1564
|
+
features.shadow ? 'has-shadow' : '',
|
|
1565
|
+
features.border ? 'has-border' : '',
|
|
1566
|
+
features.rounded ? 'has-rounded' : '',
|
|
1567
|
+
features.animate ? 'has-animations' : '',
|
|
1568
|
+
responsive['hide-mobile'] ? 'hide-mobile' : '',
|
|
1569
|
+
responsive['stack-tablet'] ? 'stack-tablet' : ''
|
|
1570
|
+
]
|
|
1571
|
+
.filter(Boolean)
|
|
1572
|
+
.join(' ');
|
|
1573
|
+
|
|
1574
|
+
return (
|
|
1575
|
+
<div
|
|
1576
|
+
className={containerClasses}
|
|
1577
|
+
style={{
|
|
1578
|
+
fontFamily,
|
|
1579
|
+
lineHeight,
|
|
1580
|
+
backgroundColor,
|
|
1581
|
+
maxWidth: contentWidth,
|
|
1582
|
+
paddingTop: `${sectionSpacing}px`,
|
|
1583
|
+
paddingBottom: `${sectionSpacing}px`
|
|
1584
|
+
}}
|
|
1585
|
+
>
|
|
1586
|
+
<h1
|
|
1587
|
+
style={{
|
|
1588
|
+
fontSize: headingSize,
|
|
1589
|
+
fontWeight: textStyle['bold-headings'] ? 'bold' : 'normal',
|
|
1590
|
+
color: primaryColor
|
|
1591
|
+
}}
|
|
1592
|
+
>
|
|
1593
|
+
{title}
|
|
1594
|
+
</h1>
|
|
1595
|
+
|
|
1596
|
+
<div
|
|
1597
|
+
style={{
|
|
1598
|
+
fontSize: bodySize
|
|
1599
|
+
}}
|
|
1600
|
+
>
|
|
1601
|
+
{body}
|
|
1602
|
+
</div>
|
|
1603
|
+
</div>
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
```
|
|
1607
|
+
|
|
1608
|
+
**This example demonstrates:**
|
|
1609
|
+
|
|
1610
|
+
- ✅ Organized option constants
|
|
1611
|
+
- ✅ Logical section grouping (Typography, Layout, Colors, Features)
|
|
1612
|
+
- ✅ All four field types (input, dropdown, radio, checkboxGroup)
|
|
1613
|
+
- ✅ Visual layout selection with images
|
|
1614
|
+
- ✅ Checkbox groups for boolean flags
|
|
1615
|
+
- ✅ Clear, descriptive labels
|
|
1616
|
+
- ✅ Safe value extraction with defaults using `styleProperties`
|
|
1617
|
+
- ✅ Dynamic styling based on style values
|
|
1618
|
+
|
|
476
1619
|
## Troubleshooting
|
|
477
1620
|
|
|
478
1621
|
### Common Issues & Solutions
|