@webikon/webentor-core 0.9.12

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 (132) hide show
  1. package/.husky/pre-commit +40 -0
  2. package/.prettierrc.js +5 -0
  3. package/CHANGELOG.md +88 -0
  4. package/LICENCE.md +7 -0
  5. package/README.md +26 -0
  6. package/core-js/_alpine.ts +20 -0
  7. package/core-js/_slider.ts +232 -0
  8. package/core-js/_utils.ts +126 -0
  9. package/core-js/blocks-components/block-appender.tsx +36 -0
  10. package/core-js/blocks-components/button.tsx +424 -0
  11. package/core-js/blocks-components/custom-image-sizes-panel.tsx +197 -0
  12. package/core-js/blocks-components/index.ts +4 -0
  13. package/core-js/blocks-components/typography-picker-select.tsx +31 -0
  14. package/core-js/blocks-filters/_filter-core-typography.tsx +108 -0
  15. package/core-js/blocks-filters/_slider-settings.tsx +283 -0
  16. package/core-js/blocks-filters/index.ts +3 -0
  17. package/core-js/blocks-filters/responsive-settings/components/DisabledSliderInfo.tsx +10 -0
  18. package/core-js/blocks-filters/responsive-settings/constants.ts +11 -0
  19. package/core-js/blocks-filters/responsive-settings/index.tsx +196 -0
  20. package/core-js/blocks-filters/responsive-settings/settings/block-link/index.ts +1 -0
  21. package/core-js/blocks-filters/responsive-settings/settings/block-link/panel.tsx +47 -0
  22. package/core-js/blocks-filters/responsive-settings/settings/border/border/index.tsx +1 -0
  23. package/core-js/blocks-filters/responsive-settings/settings/border/border/properties.ts +27 -0
  24. package/core-js/blocks-filters/responsive-settings/settings/border/border/settings.tsx +310 -0
  25. package/core-js/blocks-filters/responsive-settings/settings/border/border-radius/index.tsx +1 -0
  26. package/core-js/blocks-filters/responsive-settings/settings/border/border-radius/properties.ts +31 -0
  27. package/core-js/blocks-filters/responsive-settings/settings/border/border-radius/settings.tsx +211 -0
  28. package/core-js/blocks-filters/responsive-settings/settings/border/index.ts +1 -0
  29. package/core-js/blocks-filters/responsive-settings/settings/border/panel.tsx +54 -0
  30. package/core-js/blocks-filters/responsive-settings/settings/container/display/index.ts +2 -0
  31. package/core-js/blocks-filters/responsive-settings/settings/container/display/properties.ts +167 -0
  32. package/core-js/blocks-filters/responsive-settings/settings/container/display/settings.tsx +73 -0
  33. package/core-js/blocks-filters/responsive-settings/settings/container/flexbox/index.ts +2 -0
  34. package/core-js/blocks-filters/responsive-settings/settings/container/flexbox/properties.ts +187 -0
  35. package/core-js/blocks-filters/responsive-settings/settings/container/flexbox/settings.tsx +131 -0
  36. package/core-js/blocks-filters/responsive-settings/settings/container/grid/index.ts +2 -0
  37. package/core-js/blocks-filters/responsive-settings/settings/container/grid/properties.ts +187 -0
  38. package/core-js/blocks-filters/responsive-settings/settings/container/grid/settings.tsx +132 -0
  39. package/core-js/blocks-filters/responsive-settings/settings/container/index.ts +4 -0
  40. package/core-js/blocks-filters/responsive-settings/settings/container/panel.tsx +92 -0
  41. package/core-js/blocks-filters/responsive-settings/settings/spacing/index.ts +3 -0
  42. package/core-js/blocks-filters/responsive-settings/settings/spacing/panel.tsx +45 -0
  43. package/core-js/blocks-filters/responsive-settings/settings/spacing/properties.ts +74 -0
  44. package/core-js/blocks-filters/responsive-settings/settings/spacing/settings.tsx +85 -0
  45. package/core-js/blocks-filters/responsive-settings/types/index.ts +68 -0
  46. package/core-js/blocks-filters/responsive-settings/utils.ts +321 -0
  47. package/core-js/blocks-utils/_use-block-parent.ts +27 -0
  48. package/core-js/blocks-utils/_use-post-types.ts +43 -0
  49. package/core-js/blocks-utils/_use-taxonomies.ts +29 -0
  50. package/core-js/blocks-utils/index.ts +3 -0
  51. package/core-js/config/webentor-config.ts +718 -0
  52. package/core-js/index.ts +14 -0
  53. package/core-js/types/_block-components.ts +7 -0
  54. package/core-js/types/_webentor-config.ts +182 -0
  55. package/package.json +98 -0
  56. package/resources/blocks/e-accordion/block.json +34 -0
  57. package/resources/blocks/e-accordion/e-accordion.block.tsx +125 -0
  58. package/resources/blocks/e-accordion/script.ts +1 -0
  59. package/resources/blocks/e-accordion/style.css +8 -0
  60. package/resources/blocks/e-accordion-group/block.json +56 -0
  61. package/resources/blocks/e-accordion-group/e-accordion-group.block.tsx +99 -0
  62. package/resources/blocks/e-breadcrumbs/block.json +41 -0
  63. package/resources/blocks/e-breadcrumbs/e-breadcrumbs.block.tsx +53 -0
  64. package/resources/blocks/e-button/block.json +32 -0
  65. package/resources/blocks/e-button/e-button.block.tsx +55 -0
  66. package/resources/blocks/e-gallery/block.json +90 -0
  67. package/resources/blocks/e-gallery/e-gallery.block.tsx +316 -0
  68. package/resources/blocks/e-icon-picker/block.json +37 -0
  69. package/resources/blocks/e-icon-picker/e-icon-picker.block.tsx +230 -0
  70. package/resources/blocks/e-icon-picker/style.css +17 -0
  71. package/resources/blocks/e-image/block.json +78 -0
  72. package/resources/blocks/e-image/e-image.block.tsx +331 -0
  73. package/resources/blocks/e-picker-query-loop/block.json +25 -0
  74. package/resources/blocks/e-picker-query-loop/e-picker-query-loop.block.tsx +189 -0
  75. package/resources/blocks/e-post-template/block.json +25 -0
  76. package/resources/blocks/e-post-template/e-post-template.block.tsx +100 -0
  77. package/resources/blocks/e-query-loop/block.json +36 -0
  78. package/resources/blocks/e-query-loop/constants.tsx +8 -0
  79. package/resources/blocks/e-query-loop/e-query-loop.block.tsx +270 -0
  80. package/resources/blocks/e-query-loop/taxonomy-controls.tsx +184 -0
  81. package/resources/blocks/e-slider/block.json +42 -0
  82. package/resources/blocks/e-slider/e-slider.block.tsx +100 -0
  83. package/resources/blocks/e-svg/block.json +37 -0
  84. package/resources/blocks/e-svg/e-svg.block.tsx +156 -0
  85. package/resources/blocks/e-tab-container/block.json +49 -0
  86. package/resources/blocks/e-tab-container/e-tab-container.block.tsx +123 -0
  87. package/resources/blocks/e-table/block.json +30 -0
  88. package/resources/blocks/e-table/e-table.block.tsx +120 -0
  89. package/resources/blocks/e-table/script.ts +48 -0
  90. package/resources/blocks/e-table-cell/block.json +40 -0
  91. package/resources/blocks/e-table-cell/e-table-cell.block.tsx +180 -0
  92. package/resources/blocks/e-table-row/block.json +28 -0
  93. package/resources/blocks/e-table-row/e-table-row.block.tsx +118 -0
  94. package/resources/blocks/e-tabs/block.json +27 -0
  95. package/resources/blocks/e-tabs/e-tabs.block.tsx +90 -0
  96. package/resources/blocks/l-404/block.json +51 -0
  97. package/resources/blocks/l-404/l-404.block.tsx +75 -0
  98. package/resources/blocks/l-flexible-container/block.json +34 -0
  99. package/resources/blocks/l-flexible-container/l-flexible-container.block.tsx +97 -0
  100. package/resources/blocks/l-footer/block.json +23 -0
  101. package/resources/blocks/l-footer/l-footer.block.tsx +51 -0
  102. package/resources/blocks/l-formatted-content/block.json +28 -0
  103. package/resources/blocks/l-formatted-content/l-formatted-content.block.tsx +97 -0
  104. package/resources/blocks/l-header/block.json +26 -0
  105. package/resources/blocks/l-header/l-header.block.tsx +100 -0
  106. package/resources/blocks/l-mobile-nav/block.json +15 -0
  107. package/resources/blocks/l-mobile-nav/l-mobile-nav.block.tsx +56 -0
  108. package/resources/blocks/l-mobile-nav/style.css +54 -0
  109. package/resources/blocks/l-nav-menu/block.json +27 -0
  110. package/resources/blocks/l-nav-menu/l-nav-menu.block.tsx +109 -0
  111. package/resources/blocks/l-nav-menu/style.css +134 -0
  112. package/resources/blocks/l-post-card/block.json +13 -0
  113. package/resources/blocks/l-post-card/l-post-card.block.tsx +52 -0
  114. package/resources/blocks/l-section/block.json +89 -0
  115. package/resources/blocks/l-section/l-section.block.tsx +316 -0
  116. package/resources/blocks/l-site-logo/block.json +15 -0
  117. package/resources/blocks/l-site-logo/l-site-logo.block.tsx +54 -0
  118. package/resources/core-components/slider/slider.script.ts +11 -0
  119. package/resources/core-components/slider/slider.style.css +134 -0
  120. package/resources/scripts/editor.ts +29 -0
  121. package/resources/styles/app.css +21 -0
  122. package/resources/styles/common/_editor.css +86 -0
  123. package/resources/styles/common/_form.css +83 -0
  124. package/resources/styles/common/_global.css +73 -0
  125. package/resources/styles/common/_theme.css +75 -0
  126. package/resources/styles/common/_utilities.css +33 -0
  127. package/resources/styles/common/_wordpress.css +110 -0
  128. package/resources/styles/components/_table.css +102 -0
  129. package/resources/styles/editor.css +16 -0
  130. package/resources/styles/partials/.gitkeep +0 -0
  131. package/resources/styles/partials/_header.css +21 -0
  132. package/resources/styles/partials/_pagination.css +35 -0
@@ -0,0 +1,184 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { FormTokenField } from '@wordpress/components';
3
+ import { useDebounce } from '@wordpress/compose';
4
+ import { useSelect } from '@wordpress/data';
5
+ import { decodeEntities } from '@wordpress/html-entities';
6
+
7
+ import { useTaxonomies } from '@webentorCore/blocks-utils/_use-taxonomies';
8
+
9
+ const EMPTY_ARRAY = [];
10
+ const BASE_QUERY = {
11
+ order: 'asc',
12
+ _fields: 'id,name',
13
+ context: 'view',
14
+ };
15
+
16
+ // Helper function to get the term id based on user input in terms `FormTokenField`.
17
+ const getTermIdByTermValue = (terms, termValue) => {
18
+ // First we check for exact match by `term.id` or case sensitive `term.name` match.
19
+ const termId =
20
+ termValue?.id || terms?.find((term) => term.name === termValue)?.id;
21
+ if (termId) {
22
+ return termId;
23
+ }
24
+
25
+ /**
26
+ * Here we make an extra check for entered terms in a non case sensitive way,
27
+ * to match user expectations, due to `FormTokenField` behaviour that shows
28
+ * suggestions which are case insensitive.
29
+ *
30
+ * Although WP tries to discourage users to add terms with the same name (case insensitive),
31
+ * it's still possible if you manually change the name, as long as the terms have different slugs.
32
+ * In this edge case we always apply the first match from the terms list.
33
+ */
34
+ const termValueLower = termValue.toLocaleLowerCase();
35
+ return terms?.find((term) => term.name.toLocaleLowerCase() === termValueLower)
36
+ ?.id;
37
+ };
38
+
39
+ export function TaxonomyControls({ onChange, query }) {
40
+ const { postType, taxQuery } = query;
41
+
42
+ const taxonomies = useTaxonomies(postType);
43
+ if (!taxonomies || taxonomies.length === 0) {
44
+ return null;
45
+ }
46
+
47
+ return (
48
+ <>
49
+ {taxonomies.map((taxonomy) => {
50
+ const termIds = taxQuery?.[taxonomy.slug] || [];
51
+ const handleChange = (newTermIds) =>
52
+ onChange({
53
+ taxQuery: {
54
+ ...taxQuery,
55
+ [taxonomy.slug]: newTermIds,
56
+ },
57
+ });
58
+
59
+ return (
60
+ <TaxonomyItem
61
+ key={taxonomy.slug}
62
+ taxonomy={taxonomy}
63
+ termIds={termIds}
64
+ onChange={handleChange}
65
+ />
66
+ );
67
+ })}
68
+ </>
69
+ );
70
+ }
71
+
72
+ /**
73
+ * Renders a `FormTokenField` for a given taxonomy.
74
+ *
75
+ * @param {Object} props The props for the component.
76
+ * @param {Object} props.taxonomy The taxonomy object.
77
+ * @param {number[]} props.termIds An array with the block's term ids for the given taxonomy.
78
+ * @param {Function} props.onChange Callback `onChange` function.
79
+ * @return {JSX.Element} The rendered component.
80
+ */
81
+ function TaxonomyItem({ taxonomy, termIds, onChange }) {
82
+ const [search, setSearch] = useState('');
83
+ const [value, setValue] = useState(EMPTY_ARRAY);
84
+ const [suggestions, setSuggestions] = useState(EMPTY_ARRAY);
85
+ const debouncedSearch = useDebounce(setSearch, 250);
86
+ const { searchResults, searchHasResolved } = useSelect(
87
+ (select) => {
88
+ if (!search) {
89
+ return { searchResults: EMPTY_ARRAY, searchHasResolved: true };
90
+ }
91
+ const { getEntityRecords, hasFinishedResolution } = select('core');
92
+ const selectorArgs = [
93
+ 'taxonomy',
94
+ taxonomy.slug,
95
+ {
96
+ ...BASE_QUERY,
97
+ search,
98
+ orderby: 'name',
99
+ exclude: termIds,
100
+ per_page: 20,
101
+ },
102
+ ];
103
+ return {
104
+ searchResults: getEntityRecords(...selectorArgs),
105
+ searchHasResolved: hasFinishedResolution(
106
+ 'getEntityRecords',
107
+ selectorArgs,
108
+ ),
109
+ };
110
+ },
111
+ [search, termIds],
112
+ );
113
+ // `existingTerms` are the ones fetched from the API and their type is `{ id: number; name: string }`.
114
+ // They are used to extract the terms' names to populate the `FormTokenField` properly
115
+ // and to sanitize the provided `termIds`, by setting only the ones that exist.
116
+ const existingTerms = useSelect(
117
+ (select) => {
118
+ if (!termIds?.length) {
119
+ return EMPTY_ARRAY;
120
+ }
121
+ const { getEntityRecords } = select('core');
122
+ return getEntityRecords('taxonomy', taxonomy.slug, {
123
+ ...BASE_QUERY,
124
+ include: termIds,
125
+ per_page: termIds.length,
126
+ });
127
+ },
128
+ [termIds],
129
+ );
130
+ // Update the `value` state only after the selectors are resolved
131
+ // to avoid emptying the input when we're changing terms.
132
+ useEffect(() => {
133
+ if (!termIds?.length) {
134
+ setValue(EMPTY_ARRAY);
135
+ }
136
+ if (!existingTerms?.length) {
137
+ return;
138
+ }
139
+ // Returns only the existing entity ids. This prevents the component
140
+ // from crashing in the editor, when non existing ids are provided.
141
+ const sanitizedValue = termIds.reduce((accumulator, id) => {
142
+ const entity = existingTerms.find((term) => term.id === id);
143
+ if (entity) {
144
+ accumulator.push({
145
+ id,
146
+ value: entity.name,
147
+ });
148
+ }
149
+ return accumulator;
150
+ }, []);
151
+ setValue(sanitizedValue);
152
+ }, [termIds, existingTerms]);
153
+ // Update suggestions only when the query has resolved.
154
+ useEffect(() => {
155
+ if (!searchHasResolved) {
156
+ return;
157
+ }
158
+ setSuggestions(searchResults.map((result) => result.name));
159
+ }, [searchResults, searchHasResolved]);
160
+ const onTermsChange = (newTermValues) => {
161
+ const newTermIds = new Set();
162
+ for (const termValue of newTermValues) {
163
+ const termId = getTermIdByTermValue(searchResults, termValue);
164
+ if (termId) {
165
+ newTermIds.add(termId);
166
+ }
167
+ }
168
+ setSuggestions(EMPTY_ARRAY);
169
+ onChange(Array.from(newTermIds));
170
+ };
171
+ return (
172
+ <div className="block-library-query-inspector__taxonomy-control">
173
+ <FormTokenField
174
+ label={taxonomy.name}
175
+ value={value}
176
+ onInputChange={debouncedSearch}
177
+ suggestions={suggestions}
178
+ displayTransform={decodeEntities}
179
+ onChange={onTermsChange}
180
+ __experimentalShowHowTo={false}
181
+ />
182
+ </div>
183
+ );
184
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "$schema": "../../../schemas/webentor-block.json",
3
+ "apiVersion": 3,
4
+ "name": "webentor/e-slider",
5
+ "title": "Slider",
6
+ "description": "Slider block.",
7
+ "category": "webentor-elements",
8
+ "icon": "slides",
9
+ "keywords": ["slider", "carousel"],
10
+ "example": {
11
+ "attributes": {
12
+ "mode": "preview",
13
+ "data": {
14
+ "preview_image_help": true
15
+ }
16
+ }
17
+ },
18
+ "attributes": {
19
+ "slider": {
20
+ "type": "object",
21
+ "default": {}
22
+ },
23
+ "template": {
24
+ "type": "array",
25
+ "default": null
26
+ }
27
+ },
28
+ "supports": {
29
+ "color": {
30
+ "background": false,
31
+ "text": true
32
+ },
33
+ "webentor": {
34
+ "spacing": true,
35
+ "display": true,
36
+ "grid": true,
37
+ "gridItem": true,
38
+ "flexbox": true,
39
+ "flexboxItem": true
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,100 @@
1
+ import {
2
+ InnerBlocks,
3
+ useBlockProps,
4
+ useInnerBlocksProps,
5
+ } from '@wordpress/block-editor';
6
+ import {
7
+ BlockEditProps,
8
+ registerBlockType,
9
+ TemplateArray,
10
+ } from '@wordpress/blocks';
11
+ import { applyFilters } from '@wordpress/hooks';
12
+ import { __ } from '@wordpress/i18n';
13
+
14
+ import { useBlockParent } from '@webentorCore/blocks-utils/_use-block-parent';
15
+
16
+ import block from './block.json';
17
+
18
+ /**
19
+ * Edit component.
20
+ * See https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#edit
21
+ *
22
+ * @param {object} props The block props.
23
+ * @returns {Function} Render the edit screen
24
+ */
25
+
26
+ // TODO extend with slider/responsive settings
27
+ type AttributesType = {
28
+ coverImage: string;
29
+ template?: TemplateArray;
30
+ };
31
+
32
+ const BlockEdit: React.FC<BlockEditProps<AttributesType>> = (props) => {
33
+ const { attributes } = props;
34
+
35
+ const blockProps = useBlockProps();
36
+ const parentBlockProps = useBlockParent();
37
+
38
+ /**
39
+ * Filter allowed blocks used in webentor/e-slider inner block
40
+ */
41
+ const allowedBlocks: string[] = applyFilters(
42
+ 'webentor.core.e-slider.allowedBlocks',
43
+ [
44
+ 'webentor/l-flexible-container',
45
+ 'webentor/e-query-loop',
46
+ 'webentor/e-picker-query-loop',
47
+ ],
48
+ blockProps,
49
+ parentBlockProps,
50
+ );
51
+
52
+ /**
53
+ * Filter template used in webentor/e-slider inner block
54
+ */
55
+ const defaultTemplate: TemplateArray = attributes?.template;
56
+ const template: TemplateArray = applyFilters(
57
+ 'webentor.core.e-slider.template',
58
+ defaultTemplate,
59
+ blockProps,
60
+ parentBlockProps,
61
+ );
62
+
63
+ const { children, ...innerBlocksProps } = useInnerBlocksProps(blockProps, {
64
+ allowedBlocks,
65
+ template,
66
+ });
67
+
68
+ // Preview image for block inserter
69
+ if (attributes.coverImage) {
70
+ return <img src={attributes.coverImage} width="468" />;
71
+ }
72
+
73
+ return (
74
+ <div
75
+ {...innerBlocksProps}
76
+ className={`${innerBlocksProps.className} slider-content wbtr:relative wbtr:border wbtr:border-editor-border wbtr:p-2 wbtr:pt-4`}
77
+ >
78
+ <div className="wbtr:absolute wbtr:top-[2px] wbtr:left-2 wbtr:mb-1 wbtr:text-10 wbtr:opacity-50">
79
+ {__('Slider', 'webentor')}
80
+ </div>
81
+
82
+ {children}
83
+ </div>
84
+ );
85
+ };
86
+
87
+ /**
88
+ * See https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#save
89
+ *
90
+ * @return {null} Dynamic blocks do not save the HTML.
91
+ */
92
+ const BlockSave = () => <InnerBlocks.Content />;
93
+
94
+ /**
95
+ * Register block.
96
+ */
97
+ registerBlockType(block, {
98
+ edit: BlockEdit,
99
+ save: BlockSave,
100
+ });
@@ -0,0 +1,37 @@
1
+ {
2
+ "$schema": "../../../schemas/webentor-block.json",
3
+ "apiVersion": 3,
4
+ "name": "webentor/e-svg",
5
+ "title": "Svg",
6
+ "description": "Svg block.",
7
+ "category": "webentor-elements",
8
+ "icon": "art",
9
+ "keywords": ["svg"],
10
+ "example": {
11
+ "attributes": {
12
+ "mode": "preview",
13
+ "data": {
14
+ "preview_image_help": true
15
+ }
16
+ }
17
+ },
18
+ "attributes": {
19
+ "imgId": {
20
+ "type": "number",
21
+ "default": 0
22
+ },
23
+ "width": {
24
+ "type": "string",
25
+ "default": "0"
26
+ },
27
+ "height": {
28
+ "type": "string",
29
+ "default": "0"
30
+ },
31
+ "link": {
32
+ "type": "object",
33
+ "default": {}
34
+ }
35
+ },
36
+ "supports": {}
37
+ }
@@ -0,0 +1,156 @@
1
+ import { Image, Link, MediaToolbar } from '@10up/block-components';
2
+ import {
3
+ BlockControls,
4
+ InspectorControls,
5
+ useBlockProps,
6
+ } from '@wordpress/block-editor';
7
+ import { BlockEditProps, registerBlockType } from '@wordpress/blocks';
8
+ import {
9
+ __experimentalNumberControl as NumberControl,
10
+ PanelBody,
11
+ PanelRow,
12
+ } from '@wordpress/components';
13
+ import { __ } from '@wordpress/i18n';
14
+
15
+ import block from './block.json';
16
+
17
+ /**
18
+ * Edit component.
19
+ * See https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#edit
20
+ *
21
+ * @param {object} props The block props.
22
+ * @returns {Function} Render the edit screen
23
+ */
24
+
25
+ type AttributesType = {
26
+ coverImage: string;
27
+ link: {
28
+ title: string;
29
+ url: string;
30
+ opensInNewTab: boolean;
31
+ };
32
+ imgId: number;
33
+ width: string;
34
+ height: string;
35
+ imageSize: string;
36
+ };
37
+
38
+ const BlockEdit: React.FC<BlockEditProps<AttributesType>> = (props) => {
39
+ const { attributes, setAttributes } = props;
40
+
41
+ const blockProps = useBlockProps();
42
+
43
+ const imageWidth = 40;
44
+ const imageHeight = 40;
45
+
46
+ // Preview image for block inserter
47
+ if (attributes.coverImage) {
48
+ return <img src={attributes.coverImage} width="468" />;
49
+ }
50
+
51
+ const handleLinkTextChange = (value) =>
52
+ setAttributes({
53
+ link: {
54
+ ...attributes.link,
55
+ title: value,
56
+ },
57
+ });
58
+
59
+ const handleLinkChange = (value) =>
60
+ setAttributes({
61
+ link: {
62
+ url: value?.url,
63
+ opensInNewTab: value?.opensInNewTab,
64
+ title: value?.title ?? attributes?.link?.title,
65
+ },
66
+ });
67
+
68
+ const handleLinkRemove = () => {
69
+ setAttributes({ link: null });
70
+ };
71
+
72
+ const handleImageSelect = (image) => {
73
+ setAttributes({ imgId: image.id });
74
+ };
75
+ const handleImageRemove = () => {
76
+ setAttributes({ imgId: null });
77
+ };
78
+
79
+ return (
80
+ <>
81
+ <InspectorControls>
82
+ <PanelBody title="Block Settings" initialOpen={true}>
83
+ <PanelRow>
84
+ {/* External link */}
85
+ <Link
86
+ value={attributes?.link?.title}
87
+ url={attributes?.link?.url}
88
+ opensInNewTab={attributes?.link?.opensInNewTab}
89
+ onTextChange={handleLinkTextChange}
90
+ onLinkChange={handleLinkChange}
91
+ onLinkRemove={handleLinkRemove}
92
+ placeholder="Enter Link Text here..."
93
+ />
94
+ </PanelRow>
95
+ <PanelRow>
96
+ {/* Custom image size with with, heigh and crop */}
97
+ <div className="wbtr:flex wbtr:gap-2">
98
+ <NumberControl
99
+ label={__('Width', 'webentor')}
100
+ value={attributes?.width || ''}
101
+ onChange={(width) => setAttributes({ width })}
102
+ required
103
+ />
104
+
105
+ <NumberControl
106
+ label={__('Height', 'webentor')}
107
+ value={attributes?.height || ''}
108
+ onChange={(height) => setAttributes({ height })}
109
+ required
110
+ />
111
+ </div>
112
+ </PanelRow>
113
+ </PanelBody>
114
+ </InspectorControls>
115
+
116
+ <BlockControls>
117
+ <MediaToolbar
118
+ isOptional
119
+ id={attributes.imgId}
120
+ onSelect={handleImageSelect}
121
+ onRemove={handleImageRemove}
122
+ />
123
+ </BlockControls>
124
+
125
+ <div {...blockProps} className={blockProps.className}>
126
+ <Image
127
+ id={attributes.imgId}
128
+ size={attributes.imageSize}
129
+ onSelect={handleImageSelect}
130
+ allowedTypes={['image/svg+xml']}
131
+ labels={{
132
+ title: 'Select Image',
133
+ instructions: 'Upload or pick one from your media library.',
134
+ }}
135
+ width={attributes?.width || imageWidth}
136
+ height={attributes?.height || imageHeight}
137
+ />
138
+ </div>
139
+ </>
140
+ );
141
+ };
142
+
143
+ /**
144
+ * See https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#save
145
+ *
146
+ * @return {null} Dynamic blocks do not save the HTML.
147
+ */
148
+ const BlockSave = () => null;
149
+
150
+ /**
151
+ * Register block.
152
+ */
153
+ registerBlockType(block, {
154
+ edit: BlockEdit,
155
+ save: BlockSave,
156
+ });
@@ -0,0 +1,49 @@
1
+ {
2
+ "$schema": "../../../schemas/webentor-block.json",
3
+ "apiVersion": 3,
4
+ "name": "webentor/e-tab-container",
5
+ "title": "Single Tab",
6
+ "description": "Tab used in Tabs block.",
7
+ "category": "webentor-elements",
8
+ "icon": "table-row-after",
9
+ "keywords": [
10
+ "tabs",
11
+ "container",
12
+ "elements"
13
+ ],
14
+ "attributes": {
15
+ "title": {
16
+ "type": "string",
17
+ "default": ""
18
+ },
19
+ "hideTitle": {
20
+ "type": "boolean",
21
+ "default": false
22
+ },
23
+ "template": {
24
+ "type": "array",
25
+ "default": null
26
+ },
27
+ "spacing": {
28
+ "type": "object",
29
+ "default": {
30
+ "padding-top": {
31
+ "value": {
32
+ "basic": "pt-5",
33
+ "md": "pt-8"
34
+ }
35
+ },
36
+ "padding-bottom": {
37
+ "value": {
38
+ "basic": "pb-4"
39
+ }
40
+ }
41
+ }
42
+ }
43
+ },
44
+ "supports": {
45
+ "webentor": {
46
+ "display": true
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,123 @@
1
+ import {
2
+ InnerBlocks,
3
+ InspectorControls,
4
+ RichText,
5
+ useBlockProps,
6
+ useInnerBlocksProps,
7
+ } from '@wordpress/block-editor';
8
+ import {
9
+ BlockEditProps,
10
+ registerBlockType,
11
+ TemplateArray,
12
+ } from '@wordpress/blocks';
13
+ import { PanelBody, PanelRow, ToggleControl } from '@wordpress/components';
14
+ import { applyFilters } from '@wordpress/hooks';
15
+ import { __ } from '@wordpress/i18n';
16
+
17
+ import { useBlockParent } from '@webentorCore/blocks-utils/_use-block-parent';
18
+
19
+ import block from './block.json';
20
+
21
+ /**
22
+ * Edit component.
23
+ * See https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#edit
24
+ *
25
+ * @param {object} props The block props.
26
+ * @returns {Function} Render the edit screen
27
+ */
28
+
29
+ type AttributesType = {
30
+ coverImage: string;
31
+ hideTitle: boolean;
32
+ title: string;
33
+ template?: TemplateArray;
34
+ };
35
+
36
+ const BlockEdit: React.FC<BlockEditProps<AttributesType>> = (props) => {
37
+ const { attributes, setAttributes } = props;
38
+
39
+ const blockProps = useBlockProps();
40
+ const parentBlockProps = useBlockParent();
41
+
42
+ /**
43
+ * Filter allowed blocks used in webentor/e-tab-container inner block
44
+ */
45
+ const allowedBlocks: string[] = applyFilters(
46
+ 'webentor.core.e-tab-container.allowedBlocks',
47
+ null,
48
+ blockProps,
49
+ parentBlockProps,
50
+ );
51
+
52
+ /**
53
+ * Filter template used in webentor/e-tab-container inner block
54
+ */
55
+ const defaultTemplate: TemplateArray = attributes?.template;
56
+ const template: TemplateArray = applyFilters(
57
+ 'webentor.core.e-tab-container.template',
58
+ defaultTemplate,
59
+ blockProps,
60
+ parentBlockProps,
61
+ );
62
+
63
+ const innerBlocksProps = useInnerBlocksProps(blockProps, {
64
+ allowedBlocks,
65
+ template,
66
+ });
67
+
68
+ // Preview image for block inserter
69
+ if (attributes.coverImage) {
70
+ return <img src={attributes.coverImage} width="468" />;
71
+ }
72
+
73
+ return (
74
+ <>
75
+ <InspectorControls>
76
+ <PanelBody title="Block Settings" initialOpen={true}>
77
+ <PanelRow>
78
+ <ToggleControl
79
+ label={__('Hide title', 'webentor')}
80
+ help={__(
81
+ 'Title would be used only in Tabs navigation, but hidden in the content area.',
82
+ 'webentor',
83
+ )}
84
+ checked={attributes.hideTitle}
85
+ onChange={(hideTitle) => setAttributes({ hideTitle })}
86
+ />
87
+ </PanelRow>
88
+ </PanelBody>
89
+ </InspectorControls>
90
+
91
+ <div {...blockProps} className={`${blockProps.className}`}>
92
+ <div className="md:wbtr:pt-8 wbtr:flex wbtr:w-full wbtr:flex-col wbtr:border wbtr:border-editor-border wbtr:px-4 wbtr:pt-5 wbtr:pb-4">
93
+ <RichText
94
+ tagName="h2"
95
+ placeholder={__('Tab Title (required)', 'webentor')}
96
+ value={attributes.title}
97
+ onChange={(title) => setAttributes({ title })}
98
+ />
99
+
100
+ <div
101
+ {...innerBlocksProps}
102
+ className={`${innerBlocksProps.className} wbtr:border wbtr:border-editor-border wbtr:p-4`}
103
+ />
104
+ </div>
105
+ </div>
106
+ </>
107
+ );
108
+ };
109
+
110
+ /**
111
+ * See https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#save
112
+ *
113
+ * @return {null} Dynamic blocks do not save the HTML.
114
+ */
115
+ const BlockSave = () => <InnerBlocks.Content />;
116
+
117
+ /**
118
+ * Register block.
119
+ */
120
+ registerBlockType(block, {
121
+ edit: BlockEdit,
122
+ save: BlockSave,
123
+ });