@secretstache/wordpress-gutenberg 0.3.0 → 0.3.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 +4 -0
- package/build/index.js +8 -2
- package/build/index.js.map +1 -1
- package/build/{index.css → styles.css} +2 -0
- package/package.json +5 -3
- package/src/components/ColorPaletteControl.js +24 -0
- package/src/components/DataQueryControls.js +51 -0
- package/src/components/DividersControl.js +74 -0
- package/src/components/IconPicker.js +73 -0
- package/src/components/ImageActions.js +59 -0
- package/src/components/LinkControl.js +30 -0
- package/src/components/MediaControl.js +192 -0
- package/src/components/MediaTypeControl.js +54 -0
- package/src/components/ResourcesWrapper.js +46 -0
- package/src/components/ResponsiveSpacingControl.js +74 -0
- package/src/components/SortableSelect.js +60 -0
- package/src/components/SpacingControl.js +119 -0
- package/src/components/index.js +12 -0
- package/src/hooks/index.js +11 -0
- package/src/hooks/useAccordionItem.js +51 -0
- package/src/hooks/useAllowedBlocks.js +25 -0
- package/src/hooks/useBlockTabsData.js +90 -0
- package/src/hooks/useChildBlockPosition.js +31 -0
- package/src/hooks/useColorChange.js +12 -0
- package/src/hooks/useDataQuery.js +45 -0
- package/src/hooks/useParentBlock.js +57 -0
- package/src/hooks/usePreviewToggle.js +32 -0
- package/src/hooks/useSlider.js +24 -0
- package/src/hooks/useThemeColors.js +19 -0
- package/src/hooks/useUpdateAttribute.js +4 -0
- package/src/index.js +6 -0
- package/src/styles/_animation-file-renderer.scss +11 -0
- package/src/styles/_editor-base.scss +56 -0
- package/src/styles/_icon-picker.scss +4 -0
- package/src/styles/_image-wrapper.scss +59 -0
- package/src/styles/_link-control.scss +6 -0
- package/src/styles/_media-picker.scss +20 -0
- package/src/styles/_new-child-btn.scss +15 -0
- package/src/styles/_responsive-spacing.scss +34 -0
- package/src/styles/_root-block-appender.scss +40 -0
- package/src/styles/_sortable-select.scss +5 -0
- package/src/styles/styles.scss +12 -0
- package/src/utils/attributes.js +224 -0
- package/src/utils/constants.js +17 -0
- package/src/utils/helpers.js +175 -0
- package/src/utils/index.js +6 -0
- package/src/utils/rootBlock/README.md +71 -0
- package/src/utils/rootBlock/hideRootBlockForInlineInserter.js +13 -0
- package/src/utils/rootBlock/hideRootBlockForOtherBlocks.js +32 -0
- package/src/utils/rootBlock/index.js +4 -0
- package/src/utils/rootBlock/initRootBlockAppender.js +45 -0
- package/src/utils/rootBlock/setRootBlock.js +32 -0
- package/src/utils/waitForContainer/README.md +40 -0
- package/src/utils/waitForContainer/index.js +25 -0
| @@ -128,6 +128,8 @@ | |
| 128 128 | 
             
                .block-editor__container .bc-spacing-range-control .components-range-control__root .components-range-control__wrapper .components-range-control__marks .components-range-control__mark-label {
         | 
| 129 129 | 
             
                  left: 8px !important;
         | 
| 130 130 | 
             
                  margin-top: 7px !important; }
         | 
| 131 | 
            +
              .block-editor__container .bc-spacing-range-control .components-range-control__root .components-base-control__help {
         | 
| 132 | 
            +
                margin-left: -11px; }
         | 
| 131 133 |  | 
| 132 134 | 
             
            .block-editor__container .bc-add-new-child-btn {
         | 
| 133 135 | 
             
              display: flex;
         | 
    
        package/package.json
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            {
         | 
| 2 2 | 
             
              "name": "@secretstache/wordpress-gutenberg",
         | 
| 3 | 
            -
              "version": "0.3. | 
| 3 | 
            +
              "version": "0.3.2",
         | 
| 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,192 @@ | |
| 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 ImageRenderer = ({
         | 
| 8 | 
            +
                imageId,
         | 
| 9 | 
            +
                imageUrl,
         | 
| 10 | 
            +
                onImageClick,
         | 
| 11 | 
            +
                onRemoveClick,
         | 
| 12 | 
            +
                onSelectClick,
         | 
| 13 | 
            +
                selectButtonLabel = "Select Image",
         | 
| 14 | 
            +
                removeButtonLabel = "Remove Image",
         | 
| 15 | 
            +
            }) => {
         | 
| 16 | 
            +
                return imageId && imageUrl ? (
         | 
| 17 | 
            +
                    <>
         | 
| 18 | 
            +
                        <div className="bc-selected-media-wrapper">
         | 
| 19 | 
            +
                            <img
         | 
| 20 | 
            +
                                src={imageUrl}
         | 
| 21 | 
            +
                                className="bc-selected-media bc-selected-media--image"
         | 
| 22 | 
            +
                                alt="Selected Image"
         | 
| 23 | 
            +
                                onClick={onImageClick}
         | 
| 24 | 
            +
                            />
         | 
| 25 | 
            +
                        </div>
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                        <Button
         | 
| 28 | 
            +
                            className="bc-remove-btn"
         | 
| 29 | 
            +
                            onClick={onRemoveClick}
         | 
| 30 | 
            +
                            isSecondary
         | 
| 31 | 
            +
                            isDestructive
         | 
| 32 | 
            +
                        >
         | 
| 33 | 
            +
                            {removeButtonLabel}
         | 
| 34 | 
            +
                        </Button>
         | 
| 35 | 
            +
                    </>
         | 
| 36 | 
            +
                ) : (
         | 
| 37 | 
            +
                    <Button
         | 
| 38 | 
            +
                        variant="secondary"
         | 
| 39 | 
            +
                        onClick={onSelectClick}
         | 
| 40 | 
            +
                        className="bc-select-btn"
         | 
| 41 | 
            +
                    >
         | 
| 42 | 
            +
                        {selectButtonLabel}
         | 
| 43 | 
            +
                    </Button>
         | 
| 44 | 
            +
                );
         | 
| 45 | 
            +
            };
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            export const VideoRenderer = ({
         | 
| 48 | 
            +
                videoId,
         | 
| 49 | 
            +
                videoUrl,
         | 
| 50 | 
            +
                onRemoveClick,
         | 
| 51 | 
            +
                onSelectClick,
         | 
| 52 | 
            +
                selectButtonLabel = "Select Video",
         | 
| 53 | 
            +
                removeButtonLabel = "Remove Video",
         | 
| 54 | 
            +
            }) => {
         | 
| 55 | 
            +
                return videoId && videoUrl ? (
         | 
| 56 | 
            +
                    <>
         | 
| 57 | 
            +
                        <div className="bc-selected-media-wrapper">
         | 
| 58 | 
            +
                            <video src={videoUrl} className="bc-selected-media bc-selected-media--video" controls />
         | 
| 59 | 
            +
                        </div>
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                        <Button
         | 
| 62 | 
            +
                            className="bc-remove-btn"
         | 
| 63 | 
            +
                            onClick={onRemoveClick}
         | 
| 64 | 
            +
                            isSecondary
         | 
| 65 | 
            +
                            isDestructive
         | 
| 66 | 
            +
                        >
         | 
| 67 | 
            +
                            {removeButtonLabel}
         | 
| 68 | 
            +
                        </Button>
         | 
| 69 | 
            +
                    </>
         | 
| 70 | 
            +
                ) : (
         | 
| 71 | 
            +
                    <Button
         | 
| 72 | 
            +
                        variant="secondary"
         | 
| 73 | 
            +
                        onClick={onSelectClick}
         | 
| 74 | 
            +
                        className="bc-select-btn"
         | 
| 75 | 
            +
                    >
         | 
| 76 | 
            +
                        {selectButtonLabel}
         | 
| 77 | 
            +
                    </Button>
         | 
| 78 | 
            +
                );
         | 
| 79 | 
            +
            };
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            export const AnimationRenderer = ({
         | 
| 82 | 
            +
                animationFileId,
         | 
| 83 | 
            +
                animationFileUrl,
         | 
| 84 | 
            +
                animationFileName,
         | 
| 85 | 
            +
                onSelectClick,
         | 
| 86 | 
            +
                onRemoveClick,
         | 
| 87 | 
            +
                selectButtonLabel = "Select File",
         | 
| 88 | 
            +
                removeButtonLabel = "Remove File",
         | 
| 89 | 
            +
            }) => {
         | 
| 90 | 
            +
                return animationFileId && animationFileUrl ? (
         | 
| 91 | 
            +
                    <>
         | 
| 92 | 
            +
                        <div className="bc-animation-block-json-file" onClick={onSelectClick}>
         | 
| 93 | 
            +
                            <WPIcon icon={pageIcon} size={36} />
         | 
| 94 | 
            +
                            <span>{animationFileName}</span>
         | 
| 95 | 
            +
                        </div>
         | 
| 96 | 
            +
                        <Button
         | 
| 97 | 
            +
                            variant="secondary"
         | 
| 98 | 
            +
                            isDestructive
         | 
| 99 | 
            +
                            className="bc-remove-btn"
         | 
| 100 | 
            +
                            onClick={onRemoveClick}
         | 
| 101 | 
            +
                        >
         | 
| 102 | 
            +
                            {removeButtonLabel}
         | 
| 103 | 
            +
                        </Button>
         | 
| 104 | 
            +
                    </>
         | 
| 105 | 
            +
                ) : (
         | 
| 106 | 
            +
                    <Button variant="secondary" onClick={onSelectClick}>
         | 
| 107 | 
            +
                        {selectButtonLabel}
         | 
| 108 | 
            +
                    </Button>
         | 
| 109 | 
            +
                )
         | 
| 110 | 
            +
            };
         | 
| 111 | 
            +
             | 
| 112 | 
            +
            export const MediaControl = ({
         | 
| 113 | 
            +
                mediaId,
         | 
| 114 | 
            +
                mediaUrl,
         | 
| 115 | 
            +
                mediaFileName = '',
         | 
| 116 | 
            +
                onSelect,
         | 
| 117 | 
            +
                onRemove,
         | 
| 118 | 
            +
                type = MEDIA_TYPES.IMAGE,
         | 
| 119 | 
            +
                selectButtonLabel,
         | 
| 120 | 
            +
                removeButtonLabel,
         | 
| 121 | 
            +
                ...other
         | 
| 122 | 
            +
            }) => {
         | 
| 123 | 
            +
                if (type === MEDIA_TYPES.IMAGE) {
         | 
| 124 | 
            +
                    return (
         | 
| 125 | 
            +
                        <MediaUploadCheck>
         | 
| 126 | 
            +
                            <MediaUpload
         | 
| 127 | 
            +
                                onSelect={onSelect}
         | 
| 128 | 
            +
                                allowedTypes={['image', 'image/svg+xml']}
         | 
| 129 | 
            +
                                accept="image/*"
         | 
| 130 | 
            +
                                value={mediaId}
         | 
| 131 | 
            +
                                render={({ open }) => (
         | 
| 132 | 
            +
                                    <ImageRenderer
         | 
| 133 | 
            +
                                        imageId={mediaId}
         | 
| 134 | 
            +
                                        imageUrl={mediaUrl}
         | 
| 135 | 
            +
                                        onImageClick={open}
         | 
| 136 | 
            +
                                        onSelectClick={open}
         | 
| 137 | 
            +
                                        onRemoveClick={onRemove}
         | 
| 138 | 
            +
                                        selectButtonLabel={selectButtonLabel}
         | 
| 139 | 
            +
                                        removeButtonLabel={removeButtonLabel}
         | 
| 140 | 
            +
                                    />
         | 
| 141 | 
            +
                                )}
         | 
| 142 | 
            +
                                {...other}
         | 
| 143 | 
            +
                            />
         | 
| 144 | 
            +
                        </MediaUploadCheck>
         | 
| 145 | 
            +
                    );
         | 
| 146 | 
            +
                } else if (type === MEDIA_TYPES.VIDEO) {
         | 
| 147 | 
            +
                    return (
         | 
| 148 | 
            +
                        <MediaUploadCheck>
         | 
| 149 | 
            +
                            <MediaUpload
         | 
| 150 | 
            +
                                onSelect={onSelect}
         | 
| 151 | 
            +
                                allowedTypes={['video']}
         | 
| 152 | 
            +
                                value={mediaId}
         | 
| 153 | 
            +
                                render={({ open }) => (
         | 
| 154 | 
            +
                                    <VideoRenderer
         | 
| 155 | 
            +
                                        videoId={mediaId}
         | 
| 156 | 
            +
                                        videoUrl={mediaUrl}
         | 
| 157 | 
            +
                                        onSelectClick={open}
         | 
| 158 | 
            +
                                        onRemoveClick={onRemove}
         | 
| 159 | 
            +
                                        selectButtonLabel={selectButtonLabel}
         | 
| 160 | 
            +
                                        removeButtonLabel={removeButtonLabel}
         | 
| 161 | 
            +
                                    />
         | 
| 162 | 
            +
                                )}
         | 
| 163 | 
            +
                                {...other}
         | 
| 164 | 
            +
                            />
         | 
| 165 | 
            +
                        </MediaUploadCheck>
         | 
| 166 | 
            +
                    );
         | 
| 167 | 
            +
                } else if (type === MEDIA_TYPES.ANIMATION) {
         | 
| 168 | 
            +
                    return (
         | 
| 169 | 
            +
                        <MediaUploadCheck>
         | 
| 170 | 
            +
                            <MediaUpload
         | 
| 171 | 
            +
                                onSelect={onSelect}
         | 
| 172 | 
            +
                                allowedTypes={['application/json', 'text/plain', 'application/lottie']}
         | 
| 173 | 
            +
                                value={mediaId}
         | 
| 174 | 
            +
                                render={({ open }) => (
         | 
| 175 | 
            +
                                    <AnimationRenderer
         | 
| 176 | 
            +
                                        animationFileId={mediaId}
         | 
| 177 | 
            +
                                        animationFileUrl={mediaUrl}
         | 
| 178 | 
            +
                                        animationFileName={mediaFileName}
         | 
| 179 | 
            +
                                        onSelectClick={open}
         | 
| 180 | 
            +
                                        onRemoveClick={onRemove}
         | 
| 181 | 
            +
                                        selectButtonLabel={selectButtonLabel}
         | 
| 182 | 
            +
                                        removeButtonLabel={removeButtonLabel}
         | 
| 183 | 
            +
                                    />
         | 
| 184 | 
            +
                                )}
         | 
| 185 | 
            +
                                {...other}
         | 
| 186 | 
            +
                            />
         | 
| 187 | 
            +
                        </MediaUploadCheck>
         | 
| 188 | 
            +
                    );
         | 
| 189 | 
            +
                } else {
         | 
| 190 | 
            +
                    throw new Error('Unrecognized media type.');
         | 
| 191 | 
            +
                }
         | 
| 192 | 
            +
            };
         | 
| @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            import { useState } from '@wordpress/element';
         | 
| 2 | 
            +
            import { SelectControl } from '@wordpress/components';
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            import { MediaControl } from './MediaControl.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 | 
            +
                                <MediaControl
         | 
| 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 | 
            +
            };
         |