diff --git a/src/editor/components/CodeView.js b/src/editor/components/CodeView.js index ff76a7f9..dafb40c2 100644 --- a/src/editor/components/CodeView.js +++ b/src/editor/components/CodeView.js @@ -28,6 +28,14 @@ const CodeView = ( { themeConfig } ) => { return rest; } + /** + * If the current location is the site settings path, return the entire + * settings object. + */ + if ( location.path === '/settings' ) { + const { blocks, elements, ...rest } = themeConfig.settings; + return rest; + } /** * Remove the leading slash from the path so we don't end up with * an empty string as the first part of the pathParts array. diff --git a/src/editor/components/ComponentSettings/Settings.js b/src/editor/components/ComponentSettings/Settings.js new file mode 100644 index 00000000..d3539461 --- /dev/null +++ b/src/editor/components/ComponentSettings/Settings.js @@ -0,0 +1,177 @@ +import { Panel, PanelBody } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useContext } from '@wordpress/element'; + +import EditorContext from '../../context/EditorContext'; + +import SettingsAppearanceTools from './SettingsAppearanceTools'; +import SettingsBorder from './SettingsBorder'; +import SettingsColor from './SettingsColor'; +import SettingsCustomCSS from './SettingsCustomCSS'; +import SettingsLayout from './SettingsLayout'; +import SettingsSpacing from './SettingsSpacing'; +import TypographySettings from './TypographySettings'; +import SettingsDimensions from './SettingsDimensions'; +import SettingsShadow from './SettingsShadow'; +import SettingsPosition from './SettingsPosition'; +import SettingsBackground from './SettingsBackground'; + +/** + * Settings component + * + * This component will render the settings components for the given selector. + * + * + * @param {Object} props Component props + * @param {string} props.selector Selector for settings object within theme config + */ +const Settings = ( { selector } ) => { + const { schema } = useContext( EditorContext ); + + if ( ! selector ) { + return; + } + + const definitions = schema?.definitions; + + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +}; + +export default Settings; diff --git a/src/editor/components/ComponentSettings/SettingsAppearanceTools.js b/src/editor/components/ComponentSettings/SettingsAppearanceTools.js new file mode 100644 index 00000000..94da38c2 --- /dev/null +++ b/src/editor/components/ComponentSettings/SettingsAppearanceTools.js @@ -0,0 +1,53 @@ +import { set } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext } from '@wordpress/element'; +import { ToggleControl } from '@wordpress/components'; + +import getThemeOption from '../../../utils/get-theme-option'; +import EditorContext from '../../context/EditorContext'; +import StylesContext from '../../context/StylesContext'; + +/** + * Component for site appearance settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + * @param {string} props.description Property description + */ +const SettingsAppearanceTools = ( { selector, description } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ) || {}; + + const handleNewValue = ( newValue, key ) => { + let config = structuredClone( userConfig ); + config = set( config, [ selector, key ].join( '.' ), newValue ); + setUserConfig( config ); + }; + + return ( + <> + { description && ( +

+ { description } +

+ ) } + + handleNewValue( newValue, 'appearanceTools' ) + } + /> + + handleNewValue( newValue, 'useRootPaddingAwareAlignments' ) + } + /> + + ); +}; + +export default SettingsAppearanceTools; diff --git a/src/editor/components/ComponentSettings/SettingsBackground.js b/src/editor/components/ComponentSettings/SettingsBackground.js new file mode 100644 index 00000000..023f7824 --- /dev/null +++ b/src/editor/components/ComponentSettings/SettingsBackground.js @@ -0,0 +1,53 @@ +import { set } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext } from '@wordpress/element'; +import { ToggleControl } from '@wordpress/components'; + +import getThemeOption from '../../../utils/get-theme-option'; +import EditorContext from '../../context/EditorContext'; +import StylesContext from '../../context/StylesContext'; + +/** + * Component for background settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + * @param {string} props.description Property description + */ +const BackgroundSettings = ( { selector, description } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ) || {}; + + const handleNewValue = ( newValue, key ) => { + let config = structuredClone( userConfig ); + config = set( config, [ selector, key ].join( '.' ), newValue ); + setUserConfig( config ); + }; + + return ( + <> + { description && ( +

+ { description } +

+ ) } + + handleNewValue( newValue, 'backgroundImage' ) + } + /> + + handleNewValue( newValue, 'backgroundSize' ) + } + /> + + ); +}; + +export default BackgroundSettings; diff --git a/src/editor/components/ComponentSettings/SettingsBorder.js b/src/editor/components/ComponentSettings/SettingsBorder.js new file mode 100644 index 00000000..c4f26be2 --- /dev/null +++ b/src/editor/components/ComponentSettings/SettingsBorder.js @@ -0,0 +1,61 @@ +import { set } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext } from '@wordpress/element'; +import { ToggleControl } from '@wordpress/components'; + +import getThemeOption from '../../../utils/get-theme-option'; +import EditorContext from '../../context/EditorContext'; +import StylesContext from '../../context/StylesContext'; + +/** + * Component for border settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + * @param {string} props.description Property description + */ +const SettingsBorder = ( { selector, description } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ) || {}; + + const handleNewValue = ( newValue, key ) => { + let config = structuredClone( userConfig ); + config = set( config, [ selector, key ].join( '.' ), newValue ); + setUserConfig( config ); + }; + + return ( + <> + { description && ( +

+ { description } +

+ ) } + handleNewValue( newValue, 'color' ) } + /> + + handleNewValue( newValue, 'radius' ) + } + /> + handleNewValue( newValue, 'style' ) } + /> + handleNewValue( newValue, 'width' ) } + /> + + ); +}; + +export default SettingsBorder; diff --git a/src/editor/components/ComponentSettings/SettingsColor.js b/src/editor/components/ComponentSettings/SettingsColor.js new file mode 100644 index 00000000..f408d82e --- /dev/null +++ b/src/editor/components/ComponentSettings/SettingsColor.js @@ -0,0 +1,131 @@ +import { set } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext } from '@wordpress/element'; +import { ToggleControl } from '@wordpress/components'; + +import getThemeOption from '../../../utils/get-theme-option'; +import EditorContext from '../../context/EditorContext'; +import StylesContext from '../../context/StylesContext'; + +import SettingsDuotoneComponent from './SettingsDuotoneComponent'; +import SettingsGradientsComponent from './SettingsGradientsComponent'; +import SettingsPaletteComponent from './SettingsPaletteComponent'; + +/** + * Component for color settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + * @param {string} props.description Property description + */ +const SettingsBorder = ( { selector, description } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ) || {}; + + const handleNewValue = ( newValue, key ) => { + let config = structuredClone( userConfig ); + config = set( config, [ selector, key ].join( '.' ), newValue ); + setUserConfig( config ); + }; + + return ( + <> + { description && ( +

+ { description } +

+ ) } + + handleNewValue( newValue, 'background' ) + } + /> + + handleNewValue( newValue, 'button' ) + } + /> + + handleNewValue( newValue, 'caption' ) + } + /> + + handleNewValue( newValue, 'custom' ) + } + /> + + handleNewValue( newValue, 'customDuotone' ) + } + /> + + handleNewValue( newValue, 'customGradient' ) + } + /> + + handleNewValue( newValue, 'defaultDuotone' ) + } + /> + + handleNewValue( newValue, 'defaultGradients' ) + } + /> + + handleNewValue( newValue, 'defaultPalette' ) + } + /> + + + + handleNewValue( newValue, 'heading' ) + } + /> + handleNewValue( newValue, 'link' ) } + /> + + handleNewValue( newValue, 'text' ) } + /> + + ); +}; + +export default SettingsBorder; diff --git a/src/editor/components/ComponentSettings/SettingsCustomCSS.js b/src/editor/components/ComponentSettings/SettingsCustomCSS.js new file mode 100644 index 00000000..cfd07fb9 --- /dev/null +++ b/src/editor/components/ComponentSettings/SettingsCustomCSS.js @@ -0,0 +1,95 @@ +import { set } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext, useState, useEffect } from '@wordpress/element'; +import { TextareaControl } from '@wordpress/components'; + +import getThemeOption from '../../../utils/get-theme-option'; +import EditorContext from '../../context/EditorContext'; +import StylesContext from '../../context/StylesContext'; + +/** + * Component for custom settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + * @param {string} props.description Property description + */ +const SettingsCustom = ( { selector, description } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ); + + const [ isValidObject, setIsValidObject ] = useState( '' ); + const [ custom, setCustom ] = useState( '' ); + + const saveButton = document.querySelector( '.themer-save-button' ); + + useEffect( () => { + setCustom( JSON.stringify( value ) ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [] ); + + function isJSONString( str ) { + if ( ! str ) { + return; + } + try { + JSON.parse( str ); + } catch ( e ) { + saveButton.setAttribute( 'disabled', '' ); + return false; + } + saveButton.removeAttribute( 'disabled' ); + handleNewValue( JSON.parse( str ) ); + return true; + } + + useEffect( () => { + if ( ! custom ) { + return setIsValidObject( '' ); + } + // eslint-disable-next-line no-unused-expressions + isJSONString( custom ) + ? setIsValidObject( 'Valid object' ) + : setIsValidObject( 'Invalid object' ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ custom ] ); + + const handleNewValue = ( newValue ) => { + let config = structuredClone( userConfig ); + config = set( config, selector, newValue ); + setUserConfig( config ); + }; + + return ( + <> + { description && ( +

+ { description } +

+ ) } + + + + { __( 'CSS syntax help', 'themer' ) } + +

+ } + value={ custom } + onChange={ ( val ) => setCustom( val ) } + /> + { isValidObject } +
+ + ); +}; + +export default SettingsCustom; diff --git a/src/editor/components/ComponentSettings/SettingsDimensions.js b/src/editor/components/ComponentSettings/SettingsDimensions.js new file mode 100644 index 00000000..ba2b49bb --- /dev/null +++ b/src/editor/components/ComponentSettings/SettingsDimensions.js @@ -0,0 +1,60 @@ +import { set } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext } from '@wordpress/element'; +import { ToggleControl } from '@wordpress/components'; + +import getThemeOption from '../../../utils/get-theme-option'; +import EditorContext from '../../context/EditorContext'; +import StylesContext from '../../context/StylesContext'; + +/** + * Component for site Dimension settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + * @param {string} props.description Property description + */ +const SettingsDimensions = ( { selector, description } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ) || []; + + const handleNewValue = ( newValue, key ) => { + let config = structuredClone( userConfig ); + config = set( config, [ selector, key ].join( '.' ), newValue ); + setUserConfig( config ); + }; + + return ( + <> + { description && ( +

+ { description } +

+ ) } + { + handleNewValue( val, 'defaultAspectRatios' ); + } } + /> + { + handleNewValue( val, 'aspectRatio' ); + } } + /> + { + handleNewValue( val, 'minHeight' ); + } } + /> + + ); +}; + +export default SettingsDimensions; diff --git a/src/editor/components/ComponentSettings/SettingsDuotoneComponent.js b/src/editor/components/ComponentSettings/SettingsDuotoneComponent.js new file mode 100644 index 00000000..139743fe --- /dev/null +++ b/src/editor/components/ComponentSettings/SettingsDuotoneComponent.js @@ -0,0 +1,210 @@ +import { set, get, isEmpty } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext, useState } from '@wordpress/element'; +import { + TextControl, + DuotonePicker, + Button, + Modal, + ColorIndicator, +} from '@wordpress/components'; +import { plus, swatch } from '@wordpress/icons'; + +import getThemeOption from '../../../utils/get-theme-option'; +import EditorContext from '../../context/EditorContext'; +import StylesContext from '../../context/StylesContext'; +import { + getGradientFromCSSColors, + formatSlug, +} from '../../../utils/style-helpers'; + +/** + * Component for Duotone settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + */ +const SettingsDuotoneComponent = ( { selector } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig )?.theme || []; + + const [ newDuotone, setNewDuotone ] = useState( { + colors: [], + name: '', + slug: '', + } ); + const [ currentDuotone, setCurrentDuotone ] = useState( { + colors: [], + name: '', + slug: '', + key: '', + } ); + + const [ isOpen, setIsOpen ] = useState( false ); + + const onChange = ( newValue, field ) => { + setCurrentDuotone( { ...currentDuotone, [ field ]: newValue } ); + let config = structuredClone( userConfig ); + config = set( + config, + `${ selector }.theme[${ currentDuotone.key }].${ field }`, + newValue + ); + setUserConfig( config ); + }; + + const handleDeleteDuotone = ( key ) => { + let config = structuredClone( userConfig ); + const obj = get( config, `${ selector }.theme` ); + + obj.splice( key, 1 ); + + config = set( config, `${ selector }.theme`, obj ); + setCurrentDuotone( { value: '', key: '' } ); + setUserConfig( config ); + }; + + const handleNewDuotone = () => { + let config = structuredClone( userConfig ); + const obj = get( config, `${ selector }.theme` ) || []; + obj.push( { ...newDuotone } ); + config = set( config, `${ selector }.theme`, obj ); + setIsOpen( false ); + setNewDuotone( { colors: [], name: '', slug: '' } ); + setUserConfig( config ); + }; + + const renderModal = ( isNew ) => { + return ( + { + setCurrentDuotone( { + colors: [], + name: '', + slug: '', + key: '', + } ); + setIsOpen( false ); + } } + > + { + return isNew + ? setNewDuotone( { ...newDuotone, name } ) + : onChange( name, 'name' ); + } } + /> + { + slug = formatSlug( slug ); + return isNew + ? setNewDuotone( { ...newDuotone, slug } ) + : onChange( slug, 'slug' ); + } } + /> + { + return isNew + ? setNewDuotone( { + ...newDuotone, + colors: newValue, + } ) + : onChange( newValue, 'colors' ); + } } + /> + + + + ); + }; + + return ( +
+ + { __( 'Duotone Settings', 'themer' ) } + + + { value.map( ( duotone, index ) => { + const color = getGradientFromCSSColors( duotone.colors ); + return ( +
+
+ ); + } ) } +
+ ); +}; + +export default SettingsDuotoneComponent; diff --git a/src/editor/components/ComponentSettings/SettingsGradientsComponent.js b/src/editor/components/ComponentSettings/SettingsGradientsComponent.js new file mode 100644 index 00000000..a8ea1053 --- /dev/null +++ b/src/editor/components/ComponentSettings/SettingsGradientsComponent.js @@ -0,0 +1,199 @@ +import { set, get, isEmpty } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext, useState } from '@wordpress/element'; +import { + TextControl, + GradientPicker, + Button, + Modal, + ColorIndicator, +} from '@wordpress/components'; +import { plus, swatch } from '@wordpress/icons'; + +import getThemeOption from '../../../utils/get-theme-option'; +import EditorContext from '../../context/EditorContext'; +import StylesContext from '../../context/StylesContext'; +import { formatSlug } from '../../../utils/style-helpers'; + +/** + * Component for Gradient settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + */ +const SettingsGradientComponent = ( { selector } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig )?.theme || []; + + const [ newGradient, setNewGradient ] = useState( { + gradient: null, + name: '', + slug: '', + } ); + const [ currentGradient, setCurrentGradient ] = useState( { + gradient: null, + name: '', + slug: '', + key: '', + } ); + const [ isOpen, setIsOpen ] = useState( false ); + const onChange = ( newValue, field ) => { + setCurrentGradient( { ...currentGradient, [ field ]: newValue } ); + let config = structuredClone( userConfig ); + config = set( + config, + `${ selector }.theme[${ currentGradient.key }].${ field }`, + newValue + ); + setUserConfig( config ); + }; + + const handleDeleteGradient = ( key ) => { + let config = structuredClone( userConfig ); + const obj = get( config, `${ selector }.theme` ); + + obj.splice( key, 1 ); + + config = set( config, `${ selector }.theme`, obj ); + setCurrentGradient( { gradient: '', name: '', slug: '', key: '' } ); + setUserConfig( config ); + }; + + const handleNewGradient = () => { + let config = structuredClone( userConfig ); + const obj = get( config, `${ selector }.theme` ) || []; + obj.push( { ...newGradient } ); + config = set( config, `${ selector }.theme`, obj ); + setIsOpen( false ); + setNewGradient( { gradient: '', name: '', slug: '' } ); + setUserConfig( config ); + }; + + const renderModal = ( isNew ) => { + return ( + { + setCurrentGradient( { + gradient: '', + name: '', + slug: '', + key: '', + } ); + setIsOpen( false ); + } } + > + { + return isNew + ? setNewGradient( { ...newGradient, name } ) + : onChange( name, 'name' ); + } } + /> + { + slug = formatSlug( slug ); + return isNew + ? setNewGradient( { ...newGradient, slug } ) + : onChange( slug, 'slug' ); + } } + /> + { + return isNew + ? setNewGradient( { + ...newGradient, + gradient, + } ) + : onChange( gradient, 'gradient' ); + } } + /> + + + + ); + }; + + return ( +
+ + { __( 'Gradient Settings', 'themer' ) } + + + { value.map( ( val, index ) => { + return ( +
+
+ ); + } ) } +
+ ); +}; + +export default SettingsGradientComponent; diff --git a/src/editor/components/ComponentSettings/SettingsLayout.js b/src/editor/components/ComponentSettings/SettingsLayout.js new file mode 100644 index 00000000..af722a42 --- /dev/null +++ b/src/editor/components/ComponentSettings/SettingsLayout.js @@ -0,0 +1,53 @@ +import { set } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext } from '@wordpress/element'; +import { __experimentalUnitControl as UnitControl } from '@wordpress/components'; + +import getThemeOption from '../../../utils/get-theme-option'; +import EditorContext from '../../context/EditorContext'; +import StylesContext from '../../context/StylesContext'; + +/** + * Component for layout settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + * @param {string} props.description Property description + */ +const SettingsLayout = ( { selector, description } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ) || {}; + + const handleNewValue = ( newValue, key ) => { + let config = structuredClone( userConfig ); + config = set( config, [ selector, key ].join( '.' ), newValue ); + setUserConfig( config ); + }; + + return ( + <> + { description && ( +

+ { description } +

+ ) } + + handleNewValue( newValue, 'contentSize' ) + } + /> + + handleNewValue( newValue, 'wideSize' ) + } + /> + + ); +}; + +export default SettingsLayout; diff --git a/src/editor/components/ComponentSettings/SettingsPaletteComponent.js b/src/editor/components/ComponentSettings/SettingsPaletteComponent.js new file mode 100644 index 00000000..6f07b0b5 --- /dev/null +++ b/src/editor/components/ComponentSettings/SettingsPaletteComponent.js @@ -0,0 +1,185 @@ +import { set, get } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext, useState } from '@wordpress/element'; +import { + TextControl, + ColorPicker, + Button, + Modal, + ColorIndicator, +} from '@wordpress/components'; +import { plus, swatch } from '@wordpress/icons'; + +import getThemeOption from '../../../utils/get-theme-option'; +import EditorContext from '../../context/EditorContext'; +import StylesContext from '../../context/StylesContext'; +import { formatSlug } from '../../../utils/style-helpers'; + +/** + * Component for Palette settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + * @param {string} props.label Property label + */ +const SettingsPaletteComponent = ( { selector, label } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig )?.theme || []; + + const [ newColor, setNewColor ] = useState( { + color: '', + name: '', + slug: '', + } ); + const [ currentColor, setCurrentColor ] = useState( { + value: '', + name: '', + slug: '', + key: '', + } ); + const [ isOpen, setIsOpen ] = useState( false ); + + const onChange = ( newValue, field ) => { + setCurrentColor( { ...currentColor, [ field ]: newValue } ); + + let config = structuredClone( userConfig ); + config = set( + config, + `${ selector }.theme[${ currentColor.key }].${ field }`, + newValue + ); + setUserConfig( config ); + }; + + const handleDeleteColor = ( key ) => { + let config = structuredClone( userConfig ); + const obj = get( config, `${ selector }.theme` ); + + obj.splice( key, 1 ); + + config = set( config, `${ selector }.theme`, obj ); + setCurrentColor( { value: '', key: '' } ); + setUserConfig( config ); + }; + + const handleNewColor = () => { + let config = structuredClone( userConfig ); + const obj = get( config, `${ selector }.theme` ) || []; + + obj.push( { ...newColor } ); + + config = set( config, `${ selector }.theme`, obj ); + setIsOpen( false ); + setNewColor( { color: '', name: '', slug: '' } ); + setUserConfig( config ); + }; + + const renderModal = ( isNew ) => { + return ( + { + setCurrentColor( { value: '', key: '' } ); + setIsOpen( false ); + } } + > + { + return isNew + ? setNewColor( { ...newColor, name } ) + : onChange( name, 'name' ); + } } + /> + { + slug = formatSlug( slug ); + return isNew + ? setNewColor( { ...newColor, slug } ) + : onChange( slug, 'slug' ); + } } + /> + + isNew + ? setNewColor( { ...newColor, color: newValue } ) + : onChange( newValue, 'color' ) + } + /> + + + + ); + }; + + return ( +
+ { label } + + { value.map( ( val, index ) => { + return ( +
+
+ ); + } ) } +
+ ); +}; + +export default SettingsPaletteComponent; diff --git a/src/editor/components/ComponentSettings/SettingsPosition.js b/src/editor/components/ComponentSettings/SettingsPosition.js new file mode 100644 index 00000000..f28516f9 --- /dev/null +++ b/src/editor/components/ComponentSettings/SettingsPosition.js @@ -0,0 +1,46 @@ +import { set } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext } from '@wordpress/element'; +import { ToggleControl } from '@wordpress/components'; + +import getThemeOption from '../../../utils/get-theme-option'; +import EditorContext from '../../context/EditorContext'; +import StylesContext from '../../context/StylesContext'; + +/** + * Component for Position settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + * @param {string} props.description Property description + */ +const SettingsPosition = ( { selector, description } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ) || []; + + const handleNewValue = ( newValue, key ) => { + let config = structuredClone( userConfig ); + config = set( config, [ selector, key ].join( '.' ), newValue ); + setUserConfig( config ); + }; + + return ( + <> + { description && ( +

+ { description } +

+ ) } + + handleNewValue( newValue, 'sticky' ) + } + /> + + ); +}; + +export default SettingsPosition; diff --git a/src/editor/components/ComponentSettings/SettingsShadow.js b/src/editor/components/ComponentSettings/SettingsShadow.js new file mode 100644 index 00000000..9c0d6e54 --- /dev/null +++ b/src/editor/components/ComponentSettings/SettingsShadow.js @@ -0,0 +1,217 @@ +import { set, get } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext, useState } from '@wordpress/element'; +import { + ToggleControl, + TextControl, + Modal, + Button, +} from '@wordpress/components'; +import { plus } from '@wordpress/icons'; + +import getThemeOption from '../../../utils/get-theme-option'; +import EditorContext from '../../context/EditorContext'; +import StylesContext from '../../context/StylesContext'; + +/** + * Component for Shadow settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + * @param {string} props.description Property description + */ +const SettingsShadow = ( { selector, description } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ) || []; + const customPresets = value?.presets?.theme || []; + + const [ show, setShow ] = useState( false ); + const [ newPreset, setNewPreset ] = useState( { + name: '', + slug: '', + shadow: '', + } ); + const [ currentPreset, setCurrentPreset ] = useState( { + name: '', + slug: '', + shadow: '', + index: '', + } ); + + const handleNewValue = ( newValue, key ) => { + let config = structuredClone( userConfig ); + config = set( config, [ selector, key ].join( '.' ), newValue ); + setUserConfig( config ); + }; + + const handleNewPreset = ( index ) => { + let config = structuredClone( userConfig ); + let obj = structuredClone( + get( config, `${ selector }.presets.theme[${ index }]` ) + ); + + obj = { + ...obj, + name: currentPreset?.name, + slug: currentPreset?.slug, + shadow: currentPreset?.shadow, + }; + config = set( config, `${ selector }.presets.theme[${ index }]`, obj ); + setUserConfig( config ); + + setCurrentPreset( { + name: '', + slug: '', + shadow: '', + index: '', + } ); + }; + + const pushNewPreset = () => { + let config = structuredClone( userConfig ); + const obj = get( config, `${ selector }.presets.theme` ) || []; + obj.push( { ...newPreset } ); + config = set( config, `${ selector }.presets.theme`, obj ); + setUserConfig( config ); + setShow( false ); + }; + + const handleDeletePreset = ( index ) => { + let config = structuredClone( userConfig ); + const obj = get( config, `${ selector }.presets.theme` ); + obj.splice( index, 1 ); + config = set( config, `${ selector }.presets.theme`, obj ); + setUserConfig( config ); + setCurrentPreset( { + name: '', + slug: '', + shadow: '', + index: '', + } ); + }; + + return ( + <> + { description && ( +

+ { description } +

+ ) } + { + handleNewValue( val, 'defaultPresets' ); + } } + /> +
+
+ { __( 'Custom Presets', 'themer' ) } +
+ { customPresets.map( ( preset, index ) => { + return ( + + ); + } ) } +
+ { + + + { show && ( + setShow( false ) }> + + setNewPreset( { ...newPreset, name } ) + } + /> + + setNewPreset( { ...newPreset, slug } ) + } + /> + + setNewPreset( { ...newPreset, shadow } ) + } + /> + + + ) } + + } + { currentPreset.index !== '' && ( + + setCurrentPreset( { + name: '', + slug: '', + shadow: '', + index: '', + } ) + } + > + + setCurrentPreset( { ...currentPreset, name } ) + } + /> + + setCurrentPreset( { ...currentPreset, slug } ) + } + /> + + setCurrentPreset( { ...currentPreset, shadow } ) + } + /> + + + + ) } + + ); +}; + +export default SettingsShadow; diff --git a/src/editor/components/ComponentSettings/SettingsSpacing.js b/src/editor/components/ComponentSettings/SettingsSpacing.js new file mode 100644 index 00000000..58b325ef --- /dev/null +++ b/src/editor/components/ComponentSettings/SettingsSpacing.js @@ -0,0 +1,117 @@ +import { set } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext, useState, useEffect } from '@wordpress/element'; +import { ToggleControl, CheckboxControl } from '@wordpress/components'; + +import getThemeOption from '../../../utils/get-theme-option'; +import EditorContext from '../../context/EditorContext'; +import StylesContext from '../../context/StylesContext'; + +/** + * Component for Spacing settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + * @param {string} props.description Property description + */ +const SettingsSpacing = ( { selector, description } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ) || []; + const userValue = getThemeOption( selector, userConfig ) || []; + + const [ units, setUnits ] = useState( userValue?.units || [] ); + + const onChange = ( newValue, key ) => { + let config = structuredClone( userConfig ); + config = set( config, [ selector, key ].join( '.' ), newValue ); + setUserConfig( config ); + }; + + const handleUnitChange = ( val ) => { + if ( units.includes( val ) ) { + const index = units.indexOf( val ); + const newUnits = [ ...units ]; + newUnits.splice( index, 1 ); + setUnits( newUnits ); + } else if ( ! units.includes( val ) ) { + const newUnits = [ ...units, val ]; + setUnits( newUnits ); + } + }; + + useEffect( () => { + let config = structuredClone( userConfig ); + config = set( config, `${ selector }.units`, units ); + setUserConfig( config ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ units ] ); + + return ( + <> + { description && ( +

+ { description } +

+ ) } + { + onChange( val, 'blockGap' ); + } } + /> + { + onChange( val, 'margin' ); + } } + /> + { + onChange( val, 'padding' ); + } } + /> + + { __( 'Units', 'themer' ) } + +
+ handleUnitChange( 'px' ) } + /> + handleUnitChange( 'em' ) } + /> + handleUnitChange( 'rem' ) } + /> + handleUnitChange( 'vh' ) } + /> + handleUnitChange( 'vw' ) } + /> + handleUnitChange( '%' ) } + /> +
+ + ); +}; + +export default SettingsSpacing; diff --git a/src/editor/components/ComponentSettings/SiteSettings.js b/src/editor/components/ComponentSettings/SiteSettings.js new file mode 100644 index 00000000..4a7754b2 --- /dev/null +++ b/src/editor/components/ComponentSettings/SiteSettings.js @@ -0,0 +1,23 @@ +import { __experimentalHeading as Heading } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +import Settings from './Settings'; + +/** + * Site tab menu component + */ +const Site = () => { + return ( +
+
+ { __( 'Site', 'themer' ) } +

+ { __( 'Customise the appearance of the site.', 'themer' ) } +

+
+ +
+ ); +}; + +export default Site; diff --git a/src/editor/components/ComponentSettings/TypographySettings.js b/src/editor/components/ComponentSettings/TypographySettings.js new file mode 100644 index 00000000..1764b3d6 --- /dev/null +++ b/src/editor/components/ComponentSettings/TypographySettings.js @@ -0,0 +1,121 @@ +import { set } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext } from '@wordpress/element'; +import { ToggleControl } from '@wordpress/components'; + +import getThemeOption from '../../../utils/get-theme-option'; +import EditorContext from '../../context/EditorContext'; +import StylesContext from '../../context/StylesContext'; +import FontFamilies from './TypographySettings/FontFamilies'; +import FontSizes from './TypographySettings/FontSizes'; + +/** + * Component for Typography settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + * @param {string} props.description Property description + */ +const TypographySettings = ( { selector, description } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ) || []; + + const handleNewValue = ( newValue, key ) => { + let config = structuredClone( userConfig ); + config = set( config, [ selector, key ].join( '.' ), newValue ); + setUserConfig( config ); + }; + return ( + <> + { description && ( +

+ { description } +

+ ) } +
+ { + handleNewValue( val, 'customFontSize' ); + } } + /> + { + handleNewValue( val, 'defaultFontSizes' ); + } } + /> + { + handleNewValue( val, 'dropCap' ); + } } + /> + + + { + handleNewValue( val, 'fontStyle' ); + } } + /> + { + handleNewValue( val, 'fontWeight' ); + } } + /> + { + handleNewValue( val, 'letterSpacing' ); + } } + /> + { + handleNewValue( val, 'lineHeight' ); + } } + /> + { + handleNewValue( val, 'textDecoration' ); + } } + /> + { + handleNewValue( val, 'textTransform' ); + } } + /> + { + handleNewValue( val, 'writingMode' ); + } } + /> + { + handleNewValue( val, 'fluid' ); + } } + /> +
+ + ); +}; + +export default TypographySettings; diff --git a/src/editor/components/ComponentSettings/TypographySettings/FontFace.js b/src/editor/components/ComponentSettings/TypographySettings/FontFace.js new file mode 100644 index 00000000..6c8cc503 --- /dev/null +++ b/src/editor/components/ComponentSettings/TypographySettings/FontFace.js @@ -0,0 +1,223 @@ +import { set, get } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext, useState } from '@wordpress/element'; +import { Button, TextControl, Modal } from '@wordpress/components'; +import { plus } from '@wordpress/icons'; + +import getThemeOption from '../../../../utils/get-theme-option'; +import EditorContext from '../../../context/EditorContext'; +import StylesContext from '../../../context/StylesContext'; + +import FontFaceSrc from './FontFaceSrc'; + +/** + * Component for Font Face settings within Typography settings + * + * @param {Object} props Component props + * @param {number} props.familyIndex Index of the font family + * @param {string} props.selector Property target selector + */ +const FontFace = ( { familyIndex, selector } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ) || []; + const fontFaces = value[ familyIndex ]?.fontFace || []; + + const [ show, setShow ] = useState( false ); + const [ newFontFace, setNewFontFace ] = useState( { + fontFamily: '', + fontStretch: '', + fontStyle: '', + fontWeight: '', + tempSrc: '', + src: [], + } ); + const [ currentFontFace, setCurrentFontFace ] = useState( { + fontFamily: '', + fontStretch: '', + fontStyle: '', + fontWeight: '', + src: [], + } ); + + const handleNewValue = ( newValue, key ) => { + setCurrentFontFace( { ...currentFontFace, [ key ]: newValue } ); + let config = structuredClone( userConfig ); + config = set( + config, + `${ selector }[${ familyIndex }.fontFace[${ currentFontFace.index }].${ key }`, + newValue + ); + setUserConfig( config ); + }; + + const pushNewFontFace = () => { + let config = structuredClone( userConfig ); + const obj = + get( config, `${ selector }[${ familyIndex }].fontFace` ) || []; + obj.push( { ...newFontFace } ); + config = set( config, `${ selector }[${ familyIndex }].fontFace`, obj ); + setUserConfig( config ); + setShow( false ); + }; + + const handleDeleteFontFace = ( index ) => { + let config = structuredClone( userConfig ); + const obj = get( config, `${ selector }[${ familyIndex }].fontFace` ); + + obj.splice( index, 1 ); + + config = set( config, `${ selector }[${ familyIndex }].fontFace`, obj ); + setUserConfig( config ); + setCurrentFontFace( { + fontFamily: '', + fontStretch: '', + fontStyle: '', + fontWeight: '', + } ); + }; + + return ( +
+ + { __( 'Font Face', 'themer' ) } + + + ); + } ) } + { show && ( + setShow( false ) }> + { + setNewFontFace( { ...newFontFace, fontFamily } ); + } } + /> + { + setNewFontFace( { ...newFontFace, fontStretch } ); + } } + /> + { + setNewFontFace( { ...newFontFace, fontStyle } ); + } } + /> + { + setNewFontFace( { ...newFontFace, fontWeight } ); + } } + /> + + { /* Needs functionality for new font face src to be added here */ } + + + ) } + { currentFontFace.fontFamily && ( + + setCurrentFontFace( { + fontFamily: '', + fontStretch: '', + fontStyle: '', + fontWeight: '', + } ) + } + > + { + handleNewValue( fontFamily, 'fontFamily' ); + } } + /> + { + handleNewValue( fontStretch, 'fontStretch' ); + } } + /> + { + handleNewValue( fontStyle, 'fontStyle' ); + } } + /> + { + handleNewValue( fontWeight, 'fontWeight' ); + } } + /> + + + + + ) } +
+ ); +}; + +export default FontFace; diff --git a/src/editor/components/ComponentSettings/TypographySettings/FontFaceSrc.js b/src/editor/components/ComponentSettings/TypographySettings/FontFaceSrc.js new file mode 100644 index 00000000..cd6fba20 --- /dev/null +++ b/src/editor/components/ComponentSettings/TypographySettings/FontFaceSrc.js @@ -0,0 +1,81 @@ +import { set, get } from 'lodash'; +import { useContext, useState } from '@wordpress/element'; +import { Button, TextControl } from '@wordpress/components'; +import { plus } from '@wordpress/icons'; + +import getThemeOption from '../../../../utils/get-theme-option'; +import EditorContext from '../../../context/EditorContext'; +import StylesContext from '../../../context/StylesContext'; + +/** + * Component for Font Face source settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + * @param {number} props.familyIndex Index of the font family + * @param {number} props.fontFaceIndex Index of the font face + * + */ +const FontFaceSrc = ( { selector, familyIndex, fontFaceIndex } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ) || []; + + const [ newSrc, setNewSrc ] = useState( '' ); + + let srcOptions = []; + + if ( fontFaceIndex !== '' ) { + srcOptions = value[ familyIndex ].fontFace[ fontFaceIndex ]?.src || []; + } + + const handleNewValue = ( newValue, index ) => { + let config = structuredClone( userConfig ); + config = set( + config, + `${ selector }[${ familyIndex }].fontFace[${ fontFaceIndex }].src[${ index }]`, + newValue + ); + setUserConfig( config ); + }; + + const pushNewSrc = () => { + let config = structuredClone( userConfig ); + const obj = + get( + config, + `${ selector }[${ familyIndex }].fontFace[${ fontFaceIndex }].src` + ) || []; + obj.push( newSrc ); + config = set( + config, + `${ selector }[${ familyIndex }].fontFace[${ fontFaceIndex }].src`, + obj + ); + setUserConfig( config ); + setNewSrc( '' ); + }; + + return ( +
+ { srcOptions.map( ( srcOption, index ) => ( + handleNewValue( val, index ) } + /> + ) ) } + setNewSrc( val ) } + /> + +
+ ); +}; + +export default FontFaceSrc; diff --git a/src/editor/components/ComponentSettings/TypographySettings/FontFamilies.js b/src/editor/components/ComponentSettings/TypographySettings/FontFamilies.js new file mode 100644 index 00000000..8d9712ab --- /dev/null +++ b/src/editor/components/ComponentSettings/TypographySettings/FontFamilies.js @@ -0,0 +1,201 @@ +import { set, get } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext, useState } from '@wordpress/element'; +import { Button, TextControl, Modal } from '@wordpress/components'; +import { plus } from '@wordpress/icons'; + +import FontFace from './FontFace'; +import getThemeOption from '../../../../utils/get-theme-option'; +import EditorContext from '../../../context/EditorContext'; +import StylesContext from '../../../context/StylesContext'; + +/** + * Component for site appearance settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + */ +const FontFamilies = ( { selector } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ) || []; + + const [ currentFont, setCurrentFont ] = useState( { + fontFamily: '', + name: '', + slug: '', + index: '', + } ); + const [ newFont, setNewFont ] = useState( { + fontFamily: '', + name: '', + slug: '', + } ); + + const [ isOpen, setIsOpen ] = useState( false ); + + const handleUpdateValue = ( index ) => { + let config = structuredClone( userConfig ); + let obj = structuredClone( get( config, `${ selector }[${ index }]` ) ); + + obj = { + ...obj, + ...currentFont, + }; + config = set( config, `${ selector }[${ index }]`, obj ); + setUserConfig( config ); + }; + + const handleFontFamilyChange = () => { + let config = structuredClone( userConfig ); + const obj = get( config, `${ selector }` ) || []; + obj.push( { + fontFamily: newFont.fontFamily, + name: newFont.name, + slug: newFont.slug, + } ); + + config = set( config, `${ selector }`, obj ); + setUserConfig( config ); + setNewFont( { + fontFamily: '', + name: '', + slug: '', + } ); + setIsOpen( false ); + }; + + const handleDeleteFontFamily = ( index ) => { + let config = structuredClone( userConfig ); + const obj = get( config, `${ selector }` ); + + obj.splice( index, 1 ); + + config = set( config, `${ selector }`, obj ); + setUserConfig( config ); + setCurrentFont( { + fontFamily: '', + name: '', + slug: '', + index: '', + } ); + }; + + return ( +
+
+ { __( 'Font Families', 'themer' ) } +
+ { value.map( ( font, index ) => { + return ( + + ); + } ) } + { currentFont.index !== '' && ( + + setCurrentFont( { + fontFamily: '', + name: '', + slug: '', + index: '', + } ) + } + > + { + setCurrentFont( { ...currentFont, fontFamily } ); + } } + /> + { + setCurrentFont( { ...currentFont, name } ); + } } + /> + { + setCurrentFont( { ...currentFont, slug } ); + } } + /> + + + + + ) } + { isOpen && ( + setIsOpen( ! isOpen ) }> + { + setNewFont( { ...newFont, fontFamily } ); + } } + /> + { + setNewFont( { ...newFont, name } ); + } } + /> + { + setNewFont( { ...newFont, slug } ); + } } + /> + + + ) } +
+ ); +}; + +export default FontFamilies; diff --git a/src/editor/components/ComponentSettings/TypographySettings/FontSizes.js b/src/editor/components/ComponentSettings/TypographySettings/FontSizes.js new file mode 100644 index 00000000..27e13647 --- /dev/null +++ b/src/editor/components/ComponentSettings/TypographySettings/FontSizes.js @@ -0,0 +1,190 @@ +import { set, get } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext, useState } from '@wordpress/element'; +import { Button, TextControl, Modal } from '@wordpress/components'; +import { plus } from '@wordpress/icons'; + +import getThemeOption from '../../../../utils/get-theme-option'; +import EditorContext from '../../../context/EditorContext'; +import StylesContext from '../../../context/StylesContext'; + +/** + * Component for site Font Face settings + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + */ +const FontFaces = ( { selector } ) => { + const { userConfig, themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ) || []; + + const [ currentSize, setCurrentSize ] = useState( {} ); + const [ newSize, setNewSize ] = useState( {} ); + const [ isOpen, setIsOpen ] = useState( false ); + + const handleNewValue = ( newValue, key, index ) => { + setCurrentSize( { ...currentSize, [ key ]: newValue } ); + let config = structuredClone( userConfig ); + config = set( config, `${ selector }[${ index }].${ key }`, newValue ); + setUserConfig( config ); + }; + + const handleNewFontSize = () => { + let config = structuredClone( userConfig ); + const obj = get( config, selector ) || []; + obj.push( newSize ); + config = set( config, selector, obj ); + setUserConfig( config ); + setNewSize( {} ); + setIsOpen( false ); + }; + + const handleDelete = ( index ) => { + let config = structuredClone( userConfig ); + const obj = get( config, selector ); + obj.splice( index, 1 ); + config = set( config, selector, obj ); + setUserConfig( config ); + setCurrentSize( {} ); + }; + + return ( +
+
+ { __( 'Font Sizes', 'themer' ) } +
+ { value.map( ( size, index ) => { + return ( + + ); + } ) } + + + ) } + { currentSize?.name && ( + setCurrentSize( {} ) }> + + handleNewValue( val, 'name', currentSize.index ) + } + /> + + handleNewValue( val, 'slug', currentSize.index ) + } + /> + + handleNewValue( val, 'size', currentSize.index ) + } + /> + + handleNewValue( + val, + 'fluid.min', + currentSize.index + ) + } + /> + + handleNewValue( + val, + 'fluid.max', + currentSize.index + ) + } + /> + + + + ) } +
+ ); +}; + +export default FontFaces; diff --git a/src/editor/components/Nav.js b/src/editor/components/Nav.js index 72a237b7..241c09fb 100644 --- a/src/editor/components/Nav.js +++ b/src/editor/components/Nav.js @@ -1,5 +1,5 @@ import { __ } from '@wordpress/i18n'; -import { globe, blockDefault, html } from '@wordpress/icons'; +import { globe, blockDefault, html, settings } from '@wordpress/icons'; import NavListItem from './NavListItem'; import NavBlockList from './NavBlockList'; @@ -13,12 +13,20 @@ import NavElementList from './NavElementList'; const Nav = () => { return (
    - +
  • + + +
  • { const elementsSelector = `blocks.${ block.name }.elements`; return ( - - - + hasStyles={ hasBlockStyles } + > + + + ); } ) }
diff --git a/src/editor/components/NavListItem.js b/src/editor/components/NavListItem.js index 71caaa35..4bc28b0d 100644 --- a/src/editor/components/NavListItem.js +++ b/src/editor/components/NavListItem.js @@ -9,27 +9,40 @@ import { useState } from '@wordpress/element'; /** * Nav List Item * - * @param {Object} props Component props - * @param {JSX.Element} props.children Child elements - * @param {string} props.icon Item icon - * @param {Object} props.label Item label - * @param {string} props.route Navigation route this item should link to - * @param {boolean} props.hasStyles Whether or not this item has styles + * @param {Object} props Component props + * @param {JSX.Element} props.children Child elements + * @param {string} props.icon Item icon + * @param {Object} props.label Item label + * @param {string} props.route Navigation route this item should link to + * @param {boolean} props.hasStyles Whether or not this item has styles + * @param {boolean} props.deselectable Whether or not this item can be deselected */ -const NavListItem = ( { children, icon, label, route, hasStyles } ) => { +const NavListItem = ( { + children, + icon, + label, + route, + hasStyles, + deselectable, +} ) => { const [ isOpen, setIsOpen ] = useState( false ); + const [ prevPath, setPrevPath ] = useState( null ); const { goTo, location } = useNavigator(); - const handleClick = () => { - if ( route ) goTo( route ); - }; + const isActive = location.path === route; const handleExpandClick = ( event ) => { event.stopPropagation(); setIsOpen( ( prev ) => ! prev ); }; - const isActive = location.path === route; + const handleClick = () => { + setPrevPath( location.path ); + if ( route ) goTo( route ); + if ( deselectable && isActive ) { + goTo( prevPath ); + } + }; const renderIcon = () => { if ( ! children ) { diff --git a/src/editor/components/StylesPanel.js b/src/editor/components/StylesPanel.js index 9efa7d53..5794f5e0 100644 --- a/src/editor/components/StylesPanel.js +++ b/src/editor/components/StylesPanel.js @@ -8,6 +8,7 @@ import Site from './Site'; import BlockItem from './BlockItem'; import ElementItem from './ElementItem'; import PseudoItem from './PseudoItem'; +import SiteSettings from './ComponentSettings/SiteSettings'; /** * Styles Panel @@ -27,6 +28,11 @@ const StylesPanel = () => { + { /* site settings screen */ } + + + + { /* block screen */ } { >