@secretstache/wordpress-gutenberg 0.3.0 → 0.3.1

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 (54) hide show
  1. package/README.md +4 -0
  2. package/build/index.js +8 -2
  3. package/build/index.js.map +1 -1
  4. package/package.json +5 -3
  5. package/src/components/ColorPaletteControl.js +24 -0
  6. package/src/components/DataQueryControls.js +51 -0
  7. package/src/components/DividersControl.js +74 -0
  8. package/src/components/IconPicker.js +73 -0
  9. package/src/components/ImageActions.js +59 -0
  10. package/src/components/LinkControl.js +30 -0
  11. package/src/components/MediaPicker.js +179 -0
  12. package/src/components/MediaTypeControl.js +54 -0
  13. package/src/components/ResourcesWrapper.js +46 -0
  14. package/src/components/ResponsiveSpacingControl.js +74 -0
  15. package/src/components/SortableSelect.js +60 -0
  16. package/src/components/SpacingControl.js +119 -0
  17. package/src/components/index.js +12 -0
  18. package/src/hooks/index.js +11 -0
  19. package/src/hooks/useAccordionItem.js +51 -0
  20. package/src/hooks/useAllowedBlocks.js +25 -0
  21. package/src/hooks/useBlockTabsData.js +90 -0
  22. package/src/hooks/useChildBlockPosition.js +31 -0
  23. package/src/hooks/useColorChange.js +12 -0
  24. package/src/hooks/useDataQuery.js +45 -0
  25. package/src/hooks/useParentBlock.js +57 -0
  26. package/src/hooks/usePreviewToggle.js +32 -0
  27. package/src/hooks/useSlider.js +24 -0
  28. package/src/hooks/useThemeColors.js +19 -0
  29. package/src/hooks/useUpdateAttribute.js +4 -0
  30. package/src/index.js +6 -0
  31. package/src/styles/_animation-file-renderer.scss +11 -0
  32. package/src/styles/_editor-base.scss +56 -0
  33. package/src/styles/_icon-picker.scss +4 -0
  34. package/src/styles/_image-wrapper.scss +59 -0
  35. package/src/styles/_link-control.scss +6 -0
  36. package/src/styles/_media-picker.scss +20 -0
  37. package/src/styles/_new-child-btn.scss +15 -0
  38. package/src/styles/_responsive-spacing.scss +30 -0
  39. package/src/styles/_root-block-appender.scss +40 -0
  40. package/src/styles/_sortable-select.scss +5 -0
  41. package/src/styles/styles.scss +12 -0
  42. package/src/utils/attributes.js +224 -0
  43. package/src/utils/constants.js +17 -0
  44. package/src/utils/helpers.js +175 -0
  45. package/src/utils/index.js +6 -0
  46. package/src/utils/rootBlock/README.md +71 -0
  47. package/src/utils/rootBlock/hideRootBlockForInlineInserter.js +13 -0
  48. package/src/utils/rootBlock/hideRootBlockForOtherBlocks.js +32 -0
  49. package/src/utils/rootBlock/index.js +4 -0
  50. package/src/utils/rootBlock/initRootBlockAppender.js +45 -0
  51. package/src/utils/rootBlock/setRootBlock.js +32 -0
  52. package/src/utils/waitForContainer/README.md +40 -0
  53. package/src/utils/waitForContainer/index.js +25 -0
  54. /package/build/{index.css → styles.css} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secretstache/wordpress-gutenberg",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "",
5
5
  "author": "Secret Stache",
6
6
  "license": "GPL-2.0-or-later",
@@ -14,7 +14,8 @@
14
14
  "main": "build/index.js",
15
15
  "module": "build/index.es.js",
16
16
  "files": [
17
- "build"
17
+ "build",
18
+ "src"
18
19
  ],
19
20
  "type": "module",
20
21
  "scripts": {
@@ -50,18 +51,19 @@
50
51
  "rollup-plugin-postcss": "^4.0.2"
51
52
  },
52
53
  "dependencies": {
54
+ "classnames": "^2.5.1",
53
55
  "es-toolkit": "^1.12.0",
54
56
  "react-select": "5.7.5",
55
57
  "react-sortable-hoc": "2.0.0",
56
58
  "slugify": "^1.6.6"
57
59
  },
58
60
  "peerDependencies": {
59
- "@wordpress/dom-ready": "^4.3.0",
60
61
  "@wordpress/api-fetch": "^6.52.0",
61
62
  "@wordpress/block-editor": "^12.22.0",
62
63
  "@wordpress/blocks": "^12.34.0",
63
64
  "@wordpress/components": "^27.2.0",
64
65
  "@wordpress/data": "^9.24.0",
66
+ "@wordpress/dom-ready": "^4.3.0",
65
67
  "@wordpress/element": "^5.32.0",
66
68
  "@wordpress/hooks": "^3.57.0",
67
69
  "@wordpress/icons": "^9.45.0",
@@ -0,0 +1,24 @@
1
+ import { BaseControl, ColorPalette } from '@wordpress/components';
2
+ import { useThemeColors, useColorChange } from '../hooks';
3
+
4
+ export const ColorPaletteControl = ({
5
+ label = 'Color',
6
+ value,
7
+ attributeName,
8
+ setAttributes,
9
+ allowedColors,
10
+ }) => {
11
+ const colors = useThemeColors(allowedColors);
12
+ const onColorChange = useColorChange(colors, setAttributes);
13
+
14
+ return (
15
+ <BaseControl label={label}>
16
+ <ColorPalette
17
+ colors={colors}
18
+ value={value}
19
+ disableCustomColors={true}
20
+ onChange={(colorValue) => onColorChange(colorValue, attributeName)}
21
+ />
22
+ </BaseControl>
23
+ );
24
+ };
@@ -0,0 +1,51 @@
1
+ import { RadioControl } from '@wordpress/components';
2
+
3
+ // TODO: add support of curated posts, categories; consider merging with the useDataQuery hook
4
+ export const DataQueryControls = (props) => {
5
+ const {
6
+ dataSourceLabel = 'Data Source',
7
+ dataSource,
8
+ onDataSourceChange,
9
+
10
+ queryTypeLabel = 'Query',
11
+ queryType,
12
+ onQueryTypeChange,
13
+
14
+ settings,
15
+ } = props;
16
+
17
+ const sourcesList = settings
18
+ .filter((source) => source?.value && source?.label)
19
+ .map((source) => ({ label: source.label, value: source.value }));
20
+
21
+ const queriesList = settings.find((source) => source.value === dataSource)?.queries || [];
22
+
23
+ const hasSources = sourcesList && sourcesList?.length > 0;
24
+ const hasQueries = queriesList && queriesList?.length > 0;
25
+
26
+ return (
27
+ <>
28
+ {
29
+ hasSources && (
30
+ <RadioControl
31
+ label={dataSourceLabel}
32
+ selected={dataSource}
33
+ options={sourcesList}
34
+ onChange={onDataSourceChange}
35
+ />
36
+ )
37
+ }
38
+
39
+ {
40
+ hasQueries && (
41
+ <RadioControl
42
+ label={queryTypeLabel}
43
+ selected={queryType}
44
+ options={queriesList}
45
+ onChange={onQueryTypeChange}
46
+ />
47
+ )
48
+ }
49
+ </>
50
+ )
51
+ }
@@ -0,0 +1,74 @@
1
+ import { SelectControl, ToggleControl } from '@wordpress/components';
2
+ import { useEffect, useState } from '@wordpress/element';
3
+
4
+ export const DividersControl = ({
5
+ topDividers = [],
6
+ bottomDividers = [],
7
+ value = { topDivider: '', bottomDivider: '', isIncludeLine: false },
8
+ hasLine = true,
9
+ onChange,
10
+ }) => {
11
+ const hasTop = topDividers && topDividers?.length > 0;
12
+ const hasBottom = bottomDividers && bottomDividers?.length > 0;
13
+
14
+ const [ topDivider, setTopDivider ] = useState(value?.topDivider || '');
15
+ const [ bottomDivider, setBottomDivider ] = useState(value?.bottomDivider || '');
16
+ const [ isIncludeLine, setIsIncludeLine ] = useState(value?.isIncludeLine || false);
17
+
18
+ useEffect(() => {
19
+ onChange({
20
+ topDivider,
21
+ bottomDivider,
22
+ isIncludeLine,
23
+ });
24
+ }, [ topDivider, bottomDivider, isIncludeLine ]);
25
+
26
+ return (
27
+ <>
28
+ {
29
+ hasTop && (
30
+ <SelectControl
31
+ label="Top Divider"
32
+ value={topDivider}
33
+ onChange={(topDivider) => setTopDivider(topDivider)}
34
+ options={[
35
+ {
36
+ label: 'None',
37
+ value: ''
38
+ },
39
+ ...topDividers,
40
+ ]}
41
+
42
+ />
43
+ )
44
+ }
45
+
46
+ {
47
+ hasBottom && (
48
+ <SelectControl
49
+ label="Bottom Divider"
50
+ value={bottomDivider}
51
+ onChange={(bottomDivider) => setBottomDivider(bottomDivider)}
52
+ options={[
53
+ {
54
+ label: 'None',
55
+ value: ''
56
+ },
57
+ ...bottomDividers,
58
+ ]}
59
+ />
60
+ )
61
+ }
62
+
63
+ {
64
+ hasLine && (
65
+ <ToggleControl
66
+ label="Include Vertical Line leading from this block to the next"
67
+ checked={isIncludeLine}
68
+ onChange={() => setIsIncludeLine((prevState) => !prevState)}
69
+ />
70
+ )
71
+ }
72
+ </>
73
+ );
74
+ }
@@ -0,0 +1,73 @@
1
+ import {
2
+ MediaPlaceholder,
3
+ MediaUpload,
4
+ MediaUploadCheck,
5
+ } from '@wordpress/block-editor';
6
+ import { Button, Icon } from '@wordpress/components';
7
+ import { edit as editIcon, trash as trashIcon } from '@wordpress/icons';
8
+
9
+ export const IconPicker = ({ imageId, imageUrl, imageAlt, svgCode, onSelect, onRemove }) => {
10
+ const hasImage = imageId && imageUrl;
11
+ const isSvg = hasImage && svgCode;
12
+
13
+ return (
14
+ <MediaUploadCheck>
15
+ <MediaUpload
16
+ onSelect={onSelect}
17
+ allowedTypes={['image']}
18
+ value={imageId}
19
+ render={({ open }) => {
20
+ return hasImage ? (
21
+ <div className="bc-image-wrapper">
22
+ {hasImage && (
23
+ isSvg ? (
24
+ <div
25
+ className="svg-container"
26
+ dangerouslySetInnerHTML={{ __html: svgCode }}
27
+ />
28
+ ) : (
29
+ <img src={imageUrl} alt={imageAlt || 'icon'} />
30
+ )
31
+ )}
32
+
33
+ <div className="bc-image-wrapper__actions">
34
+ <Button
35
+ className="bc-image-wrapper__btn bc-image-wrapper__replace-btn"
36
+ type="button"
37
+ onClick={open}
38
+ >
39
+ <Icon
40
+ icon={editIcon}
41
+ size={20}
42
+ className="bc-image-wrapper__btn-icon"
43
+ />
44
+ </Button>
45
+
46
+ <Button
47
+ className="bc-image-wrapper__btn bc-image-wrapper__remove-btn"
48
+ type="button"
49
+ onClick={onRemove}
50
+ >
51
+ <Icon
52
+ icon={trashIcon}
53
+ size={20}
54
+ className="bc-image-wrapper__btn-icon"
55
+ />
56
+ </Button>
57
+ </div>
58
+
59
+ <div className="bc-image-wrapper__overlay" />
60
+ </div>
61
+ ) : (
62
+ <MediaPlaceholder
63
+ icon="format-image"
64
+ onSelect={onSelect}
65
+ allowedTypes={['image', 'image/svg+xml']}
66
+ labels={{ title: 'Icon Image' }}
67
+ />
68
+ );
69
+ }}
70
+ />
71
+ </MediaUploadCheck>
72
+ );
73
+ };
@@ -0,0 +1,59 @@
1
+ import { MediaUpload, MediaUploadCheck, MediaPlaceholder } from '@wordpress/block-editor';
2
+ import { Button } from '@wordpress/components';
3
+
4
+ export const ImageActions = ({
5
+ imageId,
6
+ imageUrl,
7
+ imageAlt,
8
+ placeholder = false,
9
+ onSelectImage,
10
+ onRemoveImage,
11
+ className = '',
12
+ }) => {
13
+ const hasImage = imageId && imageUrl;
14
+
15
+ return (
16
+ <MediaUploadCheck>
17
+ <MediaUpload
18
+ onSelect={onSelectImage}
19
+ allowedTypes={['image']}
20
+ value={imageId}
21
+ render={({ open }) => {
22
+ return hasImage ? (
23
+ <div className={`bc-image-wrapper ${className}`}>
24
+ <img src={imageUrl} alt={imageAlt} onClick={open} />
25
+ <div className="bc-image-wrapper__actions">
26
+ <Button
27
+ className="bc-image-wrapper__btn bc-image-wrapper__replace-btn"
28
+ type="button"
29
+ onClick={open}
30
+ >
31
+ Replace
32
+ </Button>
33
+ <Button
34
+ className="bc-image-wrapper__btn bc-image-wrapper__remove-btn"
35
+ type="button"
36
+ onClick={onRemoveImage}
37
+ >
38
+ Remove
39
+ </Button>
40
+ </div>
41
+ <div className="bc-image-wrapper__overlay" />
42
+ </div>
43
+ ) : placeholder ? (
44
+ <MediaPlaceholder
45
+ className="media-placeholder"
46
+ icon="format-image"
47
+ onSelect={onSelectImage}
48
+ allowedTypes={['image']}
49
+ labels={{
50
+ title: 'Image',
51
+ instructions: 'Upload an image file or pick one from your media library.',
52
+ }}
53
+ />
54
+ ) : null;
55
+ }}
56
+ />
57
+ </MediaUploadCheck>
58
+ );
59
+ };
@@ -0,0 +1,30 @@
1
+ import { BaseControl, CheckboxControl } from '@wordpress/components';
2
+ import { URLInput } from '@wordpress/block-editor';
3
+ import { useUpdateAttribute } from '../hooks';
4
+
5
+ export const LinkControl = ({
6
+ url = { value: '#', attrName: 'linkSource' },
7
+ isOpenInNewTab = { value: false, attrName: 'linkIsOpenInNewTab'},
8
+ setAttributes,
9
+ label = 'Source',
10
+ }) => {
11
+ const updateAttribute = useUpdateAttribute(setAttributes);
12
+
13
+ return (
14
+ <>
15
+ <BaseControl label={label}>
16
+ <URLInput
17
+ className="bc-url-input"
18
+ value={url.value}
19
+ onChange={(newUrl) => setAttributes({ [url.attrName]: newUrl })}
20
+ />
21
+ </BaseControl>
22
+
23
+ <CheckboxControl
24
+ checked={isOpenInNewTab.value}
25
+ label="Open in a new tab"
26
+ onChange={(newIsOpenInNewTab) => setAttributes({ [isOpenInNewTab.attrName]: newIsOpenInNewTab })}
27
+ />
28
+ </>
29
+ );
30
+ };
@@ -0,0 +1,179 @@
1
+ import { Button, Icon as WPIcon } from '@wordpress/components';
2
+ import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor';
3
+ import { page as pageIcon } from '@wordpress/icons';
4
+
5
+ import { MEDIA_TYPES } from '../utils/index.js';
6
+
7
+ export const BCImageRenderer = ({
8
+ imageId,
9
+ imageUrl,
10
+ onImageClick,
11
+ onRemoveClick,
12
+ onSelectClick,
13
+ }) => {
14
+ return imageId && imageUrl ? (
15
+ <>
16
+ <div className="bc-selected-media-wrapper">
17
+ <img
18
+ src={imageUrl}
19
+ className="bc-selected-media bc-selected-media--image"
20
+ alt="Selected Image"
21
+ onClick={onImageClick}
22
+ />
23
+ </div>
24
+
25
+ <Button
26
+ className="bc-remove-btn"
27
+ onClick={onRemoveClick}
28
+ isSecondary
29
+ isDestructive
30
+ >
31
+ Remove Image
32
+ </Button>
33
+ </>
34
+ ) : (
35
+ <Button
36
+ variant="secondary"
37
+ onClick={onSelectClick}
38
+ className="bc-select-btn"
39
+ >
40
+ Select Image
41
+ </Button>
42
+ );
43
+ };
44
+
45
+ export const BCVideoRenderer = ({
46
+ videoId,
47
+ videoUrl,
48
+ onRemoveClick,
49
+ onSelectClick,
50
+ }) => {
51
+ return videoId && videoUrl ? (
52
+ <>
53
+ <div className="bc-selected-media-wrapper">
54
+ <video src={videoUrl} className="bc-selected-media bc-selected-media--video" controls />
55
+ </div>
56
+
57
+ <Button
58
+ className="bc-remove-btn"
59
+ onClick={onRemoveClick}
60
+ isSecondary
61
+ isDestructive
62
+ >
63
+ Remove Video
64
+ </Button>
65
+ </>
66
+ ) : (
67
+ <Button
68
+ variant="secondary"
69
+ onClick={onSelectClick}
70
+ className="bc-select-btn"
71
+ >
72
+ Select Video
73
+ </Button>
74
+ );
75
+ };
76
+
77
+ export const BCAnimationRenderer = ({
78
+ animationFileId,
79
+ animationFileUrl,
80
+ animationFileName,
81
+ onSelectClick,
82
+ onRemoveClick,
83
+ }) => {
84
+ return animationFileId && animationFileUrl ? (
85
+ <>
86
+ <div className="bc-animation-block-json-file" onClick={onSelectClick}>
87
+ <WPIcon icon={pageIcon} size={36}/>
88
+ <span>{animationFileName}</span>
89
+ </div>
90
+ <Button
91
+ variant="secondary"
92
+ isDestructive
93
+ className="bc-remove-btn"
94
+ onClick={onRemoveClick}
95
+ >
96
+ Remove File
97
+ </Button>
98
+ </>
99
+ ) : (
100
+ <Button variant="secondary" onClick={onSelectClick}>
101
+ Select File
102
+ </Button>
103
+ )
104
+ };
105
+
106
+ // TODO: find better name
107
+ export const BCMediaPicker = ({
108
+ mediaId,
109
+ mediaUrl,
110
+ mediaFileName = '',
111
+ onSelect,
112
+ onRemove,
113
+ type = MEDIA_TYPES.IMAGE,
114
+ ...other
115
+ }) => {
116
+ if (type === MEDIA_TYPES.IMAGE) {
117
+ return (
118
+ <MediaUploadCheck>
119
+ <MediaUpload
120
+ onSelect={onSelect}
121
+ allowedTypes={['image', 'image/svg+xml']}
122
+ accept="image/*"
123
+ value={mediaId}
124
+ render={({ open }) => (
125
+ <BCImageRenderer
126
+ imageId={mediaId}
127
+ imageUrl={mediaUrl}
128
+ onImageClick={open}
129
+ onSelectClick={open}
130
+ onRemoveClick={onRemove}
131
+ />
132
+ )}
133
+ { ...other }
134
+ />
135
+ </MediaUploadCheck>
136
+ );
137
+ } else if (type === MEDIA_TYPES.VIDEO) {
138
+ return (
139
+ <MediaUploadCheck>
140
+ <MediaUpload
141
+ onSelect={onSelect}
142
+ allowedTypes={['video']}
143
+ value={mediaId}
144
+ render={({ open }) => (
145
+ <BCVideoRenderer
146
+ videoId={mediaId}
147
+ videoUrl={mediaUrl}
148
+ onSelectClick={open}
149
+ onRemoveClick={onRemove}
150
+ />
151
+ )}
152
+ { ...other }
153
+ />
154
+ </MediaUploadCheck>
155
+ );
156
+ } else if (type === MEDIA_TYPES.ANIMATION) {
157
+ return (
158
+ <MediaUploadCheck>
159
+ <MediaUpload
160
+ onSelect={onSelect}
161
+ allowedTypes={['application/json', 'text/plain', 'application/lottie']}
162
+ value={mediaId}
163
+ render={({ open }) => (
164
+ <BCAnimationRenderer
165
+ animationFileId={mediaId}
166
+ animationFileUrl={mediaUrl}
167
+ animationFileName={mediaFileName}
168
+ onSelectClick={open}
169
+ onRemoveClick={onRemove}
170
+ />
171
+ )}
172
+ {...other}
173
+ />
174
+ </MediaUploadCheck>
175
+ );
176
+ } else {
177
+ throw new Error('Unrecognized media type.');
178
+ }
179
+ };
@@ -0,0 +1,54 @@
1
+ import { useState } from '@wordpress/element';
2
+ import { SelectControl } from '@wordpress/components';
3
+
4
+ import { BCMediaPicker } from './MediaPicker.js';
5
+ import { MEDIA_TYPE_LABELS} from '../utils/index.js';
6
+
7
+ export const MediaTypeControl = (props) => {
8
+ const {
9
+ mediaTypes = [],
10
+ mediaId,
11
+ mediaUrl,
12
+ mediaFileName = '',
13
+ mediaOnSelect,
14
+ mediaOnRemove,
15
+ } = props;
16
+
17
+ const [ selectedMediaType, setSelectedMediaType ] = useState(mediaTypes?.[0]);
18
+
19
+ const mediaTypesOptions = mediaTypes
20
+ ?.filter((type) => MEDIA_TYPE_LABELS[type]) // Ensure it's an allowed type
21
+ ?.map((type) => ({
22
+ label: MEDIA_TYPE_LABELS[type],
23
+ value: type,
24
+ }));
25
+
26
+ return (
27
+ <>
28
+ {
29
+ // TODO: add custom label
30
+ mediaTypes && (
31
+ <SelectControl
32
+ label="Media Type"
33
+ value={selectedMediaType}
34
+ onChange={(mediaType) => setSelectedMediaType(mediaType)}
35
+ options={mediaTypesOptions}
36
+ />
37
+ )
38
+ }
39
+
40
+ {
41
+ selectedMediaType && (
42
+ <BCMediaPicker
43
+ mediaId={mediaId}
44
+ mediaUrl={mediaUrl}
45
+ mediaFileName={mediaFileName}
46
+ type={selectedMediaType}
47
+ onSelect={mediaOnSelect}
48
+ onRemove={mediaOnRemove}
49
+ />
50
+ )
51
+ }
52
+ </>
53
+ );
54
+ }
@@ -0,0 +1,46 @@
1
+ import { Notice, Placeholder, Spinner } from '@wordpress/components';
2
+
3
+ const EmptyNotice = ({
4
+ message = 'No resources were found matching your criteria. Please try to adjust the query.'
5
+ }) => (
6
+ <Notice status="info" isDismissible={false}>
7
+ <p>{message}</p>
8
+ </Notice>
9
+ );
10
+
11
+ const LoadingSpinner = () => <Spinner className="bc-spinner" />;
12
+
13
+ const PlaceholderContent = ({
14
+ icon = 'info-outline',
15
+ instructions = 'Please configure the block in the sidebar.',
16
+ ...restProps
17
+ }) => (
18
+ <Placeholder
19
+ icon={icon}
20
+ instructions={instructions}
21
+ {...restProps}
22
+ />
23
+ );
24
+
25
+ export const ResourcesWrapper = ({
26
+ isLoading,
27
+ isEmpty,
28
+ isPlaceholder,
29
+ emptyMessage,
30
+ placeholderProps = {},
31
+ children,
32
+ }) => {
33
+ if (isLoading) {
34
+ return <LoadingSpinner />;
35
+ }
36
+
37
+ if (isEmpty) {
38
+ return <EmptyNotice message={emptyMessage} />;
39
+ }
40
+
41
+ if (isPlaceholder) {
42
+ return <PlaceholderContent {...placeholderProps} />;
43
+ }
44
+
45
+ return children;
46
+ };