diff --git a/src/editor/components/Blocks.js b/src/editor/components/Blocks.js new file mode 100644 index 0000000..36db903 --- /dev/null +++ b/src/editor/components/Blocks.js @@ -0,0 +1,48 @@ +import { __ } from '@wordpress/i18n'; +import { useState, useContext } from '@wordpress/element'; + +import Search from './Search'; +import BlocksItem from './BlocksItem'; +import EditorContext from '../context/EditorContext'; +import { getCoreBlocks } from '../../utils/block-helpers'; + +/** + * Blocks tab menu component + */ +const Blocks = () => { + const { themeConfig, schema } = useContext( EditorContext ); + const [ searchValue, setSearchValue ] = useState(); + + return ( +
+

{ __( 'Blocks', 'themer' ) }

+

+ { __( + 'Customise the appearance of specific blocks for the whole site.', + 'themer' + ) } +

+ + { getCoreBlocks( undefined, themeConfig, schema )?.map( + ( block ) => { + if ( + searchValue?.length > 0 && + ! block.toLowerCase().includes( searchValue ) + ) { + return false; + } + + return ( + + ); + } + ) } +
+ ); +}; + +export default Blocks; diff --git a/src/editor/components/BlocksItem.js b/src/editor/components/BlocksItem.js new file mode 100644 index 0000000..8ad9156 --- /dev/null +++ b/src/editor/components/BlocksItem.js @@ -0,0 +1,36 @@ +import Border from './StylesBorder'; +import getThemeOption from '../../utils/get-theme-option'; + +/** + * Individual block item + * + * @param {Object} props Component props + * @param {string} props.block Block name + * @param {Object} props.themeConfig Theme JSON + */ +const BlocksItem = ( { block, themeConfig } ) => { + if ( ! block ) { + return; + } + + const blockSelector = [ 'styles', 'blocks', block ]; + const hasBorderStyles = getThemeOption( + [ ...blockSelector, 'border' ].join( '.' ), + themeConfig + ); + + return ( +
+ { block } +
+ { hasBorderStyles && ( + + ) } +
+
+ ); +}; + +export default BlocksItem; diff --git a/src/editor/components/Colours.js b/src/editor/components/Colours.js new file mode 100644 index 0000000..c7ba6ff --- /dev/null +++ b/src/editor/components/Colours.js @@ -0,0 +1,6 @@ +/** + * Colours tab menu component + */ +const Colours = () =>

Colours Tab

; + +export default Colours; diff --git a/src/editor/components/ComponentWrapper.js b/src/editor/components/ComponentWrapper.js index be82459..31c5891 100644 --- a/src/editor/components/ComponentWrapper.js +++ b/src/editor/components/ComponentWrapper.js @@ -1,4 +1,4 @@ -import ThemerComponent from './fields/ThemerComponent'; +import ThemerComponent from './ThemerComponent'; /** * Wrapper for app diff --git a/src/editor/components/CustomBlocks.js b/src/editor/components/CustomBlocks.js new file mode 100644 index 0000000..b1d5c21 --- /dev/null +++ b/src/editor/components/CustomBlocks.js @@ -0,0 +1,6 @@ +/** + * Custom Blocks tab menu component + */ +const CustomBlock = () =>

Custom Block Tab

; + +export default CustomBlock; diff --git a/src/editor/components/Layout.js b/src/editor/components/Layout.js new file mode 100644 index 0000000..796dccb --- /dev/null +++ b/src/editor/components/Layout.js @@ -0,0 +1,6 @@ +/** + * Layout tab menu component + */ +const Layout = () =>

Layout Tab

; + +export default Layout; diff --git a/src/editor/components/fields/Preview.js b/src/editor/components/Preview.js similarity index 100% rename from src/editor/components/fields/Preview.js rename to src/editor/components/Preview.js diff --git a/src/editor/components/fields/ResponsiveButton.js b/src/editor/components/ResponsiveButton.js similarity index 100% rename from src/editor/components/fields/ResponsiveButton.js rename to src/editor/components/ResponsiveButton.js diff --git a/src/editor/components/Search.js b/src/editor/components/Search.js new file mode 100644 index 0000000..0725cff --- /dev/null +++ b/src/editor/components/Search.js @@ -0,0 +1,24 @@ +/** + * Search component + * + * @param {Object} props Component props + * @param {Function} props.setValue Input on change function + * @param {string} props.placeholder Placeholder attribute value + */ +const Search = ( { setValue, placeholder = 'Search' } ) => { + const handleSearch = ( event ) => { + setValue( event?.target?.value?.toLowerCase().trim() ); + }; + + return ( +

+ handleSearch( event ) } + placeholder={ placeholder } + /> +

+ ); +}; + +export default Search; diff --git a/src/editor/components/StylesBorder.js b/src/editor/components/StylesBorder.js new file mode 100644 index 0000000..85526e9 --- /dev/null +++ b/src/editor/components/StylesBorder.js @@ -0,0 +1,43 @@ +/* eslint-disable @wordpress/no-unsafe-wp-apis */ + +import { set } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { useContext } from '@wordpress/element'; +import { __experimentalBorderBoxControl as BorderBoxControl } from '@wordpress/components'; + +import getThemeOption from '../../utils/get-theme-option'; +import EditorContext from '../context/EditorContext'; +import StylesContext from '../context/StylesContext'; + +/** + * Reusable border control style component + * + * @param {Object} props Component props + * @param {string} props.selector Property target selector + */ +const Border = ( { selector } ) => { + const { themeConfig } = useContext( EditorContext ); + const { setUserConfig } = useContext( StylesContext ); + const value = getThemeOption( selector, themeConfig ); + const colors = getThemeOption( + 'settings.color.palette.theme', + themeConfig + ); + + const onChange = ( newValue ) => { + let config = structuredClone( themeConfig ); + config = set( config, selector, newValue ); + setUserConfig( config ); + }; + + return ( + + ); +}; + +export default Border; diff --git a/src/editor/components/StylesColour.js b/src/editor/components/StylesColour.js new file mode 100644 index 0000000..9ffe3db --- /dev/null +++ b/src/editor/components/StylesColour.js @@ -0,0 +1,8 @@ +/** + * Reusable colour control style component + */ +const Colour = () => { + return

Colour Component

; +}; + +export default Colour; diff --git a/src/editor/components/StylesDimensions.js b/src/editor/components/StylesDimensions.js new file mode 100644 index 0000000..e26f6a2 --- /dev/null +++ b/src/editor/components/StylesDimensions.js @@ -0,0 +1,8 @@ +/** + * Reusable dimensions control style component + */ +const Dimensions = () => { + return

Dimensions Component

; +}; + +export default Dimensions; diff --git a/src/editor/components/StylesFilter.js b/src/editor/components/StylesFilter.js new file mode 100644 index 0000000..db61844 --- /dev/null +++ b/src/editor/components/StylesFilter.js @@ -0,0 +1,8 @@ +/** + * Reusable filter control style component + */ +const Filter = () => { + return

Filter Component

; +}; + +export default Filter; diff --git a/src/editor/components/StylesOutline.js b/src/editor/components/StylesOutline.js new file mode 100644 index 0000000..044826b --- /dev/null +++ b/src/editor/components/StylesOutline.js @@ -0,0 +1,8 @@ +/** + * Reusable outline control style component + */ +const Outline = () => { + return

Outline Component

; +}; + +export default Outline; diff --git a/src/editor/components/StylesShadow.js b/src/editor/components/StylesShadow.js new file mode 100644 index 0000000..1dd4b69 --- /dev/null +++ b/src/editor/components/StylesShadow.js @@ -0,0 +1,8 @@ +/** + * Reusable shadow control style component + */ +const Shadow = () => { + return

Shadow Component

; +}; + +export default Shadow; diff --git a/src/editor/components/StylesSpacing.js b/src/editor/components/StylesSpacing.js new file mode 100644 index 0000000..4e91f73 --- /dev/null +++ b/src/editor/components/StylesSpacing.js @@ -0,0 +1,8 @@ +/** + * Reusable spacing control style component + */ +const Spacing = () => { + return

Spacing Component

; +}; + +export default Spacing; diff --git a/src/editor/components/StylesTypography.js b/src/editor/components/StylesTypography.js new file mode 100644 index 0000000..84ba5fa --- /dev/null +++ b/src/editor/components/StylesTypography.js @@ -0,0 +1,8 @@ +/** + * Reusable typography control style component + */ +const Typography = () => { + return

Typography Component

; +}; + +export default Typography; diff --git a/src/editor/components/ThemerComponent.js b/src/editor/components/ThemerComponent.js new file mode 100644 index 0000000..d75b9e6 --- /dev/null +++ b/src/editor/components/ThemerComponent.js @@ -0,0 +1,226 @@ +import { mergeWith, isEmpty } from 'lodash'; +import { Button, Spinner, TabPanel } from '@wordpress/components'; +import { useSelect, dispatch } from '@wordpress/data'; +import { useEffect, useState, useMemo } from '@wordpress/element'; +import apiFetch from '@wordpress/api-fetch'; + +import Blocks from './Blocks'; +import Layout from './Layout'; +import Colours from './Colours'; +import Preview from './Preview'; +import Typography from './Typography'; +import CustomBlocks from './CustomBlocks'; +import ButtonExport from './ButtonExport'; +import ResponsiveButton from './ResponsiveButton'; +import EditorContext from '../context/EditorContext'; +import StylesContext from '../context/StylesContext'; +import fetchSchema from '../../utils/schema-helpers'; + +/** + * main component + */ +const ThemerComponent = () => { + const [ previewCss, setPreviewCss ] = useState( '' ); + const [ previewSize, setPreviewSize ] = useState(); + const [ schema, setSchema ] = useState( {} ); + + const setUserConfig = ( config ) => { + dispatch( 'core' ).editEntityRecord( + 'root', + 'globalStyles', + globalStylesId, + config + ); + }; + + const { globalStylesId, baseConfig, userConfig } = useSelect( + ( select ) => { + const { + __experimentalGetCurrentGlobalStylesId, + __experimentalGetCurrentThemeBaseGlobalStyles, + getEditedEntityRecord, + } = select( 'core' ); + + const currentGlobalStylesId = + __experimentalGetCurrentGlobalStylesId(); + + return { + globalStylesId: currentGlobalStylesId, // eslint-disable no-underscore-dangle -- require underscore dangle for experimental functions + baseConfig: __experimentalGetCurrentThemeBaseGlobalStyles(), // eslint-disable no-underscore-dangle -- require underscore dangle for experimental functions + userConfig: getEditedEntityRecord( + 'root', + 'globalStyles', + currentGlobalStylesId + ), + }; + } + ); + + /** + * Returns merged base and user configs + */ + const themeConfig = useMemo( () => { + if ( isEmpty( userConfig ) ) { + return baseConfig; + } + const merged = mergeWith( {}, baseConfig, userConfig ); + return merged; + }, [ userConfig, baseConfig ] ); + + /** + * Fetch new preview CSS whenever config is changed + */ + useEffect( () => { + const updatePreviewCss = async () => { + const res = await apiFetch( { + path: '/themer/v1/styles', + method: 'POST', + data: themeConfig, + } ); + if ( res ) { + setPreviewCss( res ); + } + }; + if ( themeConfig ) { + updatePreviewCss(); + } + }, [ themeConfig, setPreviewCss ] ); + + /** + * TODO: For demo purpose only, this should be refactored and + * implemented into the processing of the schema file task + */ + useEffect( () => { + ( async () => { + const schemaJson = await fetchSchema(); + setSchema( schemaJson ); + } )(); + }, [] ); + + /** + * saves edited entity data + */ + const save = async () => { + try { + await dispatch( 'core' ).saveEditedEntityRecord( + 'root', + 'globalStyles', + globalStylesId + ); + } catch ( err ) { + // eslint-disable-next-line no-console + console.log( err ); + } + }; + + /** + * resets updated theme db data back to original theme.json + */ + const reset = () => { + dispatch( 'core' ).editEntityRecord( + 'root', + 'globalStyles', + globalStylesId, + baseConfig + ); + }; + + if ( ! themeConfig || ! previewCss ) { + return ( + <> + + + ); + } + + return ( + <> + + +
+
+
+
+ + { ( tab ) => { + switch ( tab?.name ) { + case 'colours': + return ; + case 'layout': + return ; + case 'blocks': + return ; + case 'custom-blocks': + return ; + case 'typography': + default: + return ; + } + } } + +
+
+ + + +
+
+
+
+ + ); +}; + +export default ThemerComponent; diff --git a/src/editor/components/Typography.js b/src/editor/components/Typography.js new file mode 100644 index 0000000..89a1137 --- /dev/null +++ b/src/editor/components/Typography.js @@ -0,0 +1,6 @@ +/** + * Typography tab menu component + */ +const Typography = () =>

Typography Tab

; + +export default Typography; diff --git a/src/editor/components/fields/ComponentMap.js b/src/editor/components/fields/ComponentMap.js deleted file mode 100644 index f896b95..0000000 --- a/src/editor/components/fields/ComponentMap.js +++ /dev/null @@ -1,72 +0,0 @@ -import { TextControl, ColorPicker } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; - -import FontPicker from './Components/FontPicker'; -import SpacingControl from './Components/SpacingControl'; - -/** - * Returns appropriate component depending on field type - * - * @param {Object} props - * @param {string} props.label - * @param {string} props.value - * @param {Function} props.onChange - */ -const ComponentMap = ( { label, value, onChange } ) => { - const { currentThemeBaseGlobalStyles } = useSelect( ( select ) => { - return { - currentThemeBaseGlobalStyles: - select( - 'core' - ).__experimentalGetCurrentThemeBaseGlobalStyles(), - }; - } ); - - const colorPickerArray = [ 'background', 'text' ]; - const fontPickerArray = [ - 'fontFamily', - 'fontSize', - 'lineHeight', - 'textDecoration', - ]; - const blockGapArray = [ 'blockGap', 'top', 'right', 'bottom', 'left' ]; - - switch ( true ) { - case colorPickerArray.includes( label ): - return ( - onChange( val ) } - /> - ); - case fontPickerArray.includes( label ): - return ( -
- onChange( val ) } - base={ currentThemeBaseGlobalStyles } - /> -
- ); - case blockGapArray.includes( label ): - return ( - onChange( val ) } - base={ currentThemeBaseGlobalStyles } - /> - ); - default: - return ( - onChange( val ) } - /> - ); - } -}; - -export default ComponentMap; diff --git a/src/editor/components/fields/Components/FontPicker.js b/src/editor/components/fields/Components/FontPicker.js deleted file mode 100644 index ac4c4ac..0000000 --- a/src/editor/components/fields/Components/FontPicker.js +++ /dev/null @@ -1,136 +0,0 @@ -/** - * This component requires use of experimental apis - */ - -/* eslint-disable @wordpress/no-unsafe-wp-apis */ - -import { - FontSizePicker, - SelectControl, - Button, - __experimentalInputControl as InputControl, - __experimentalToggleGroupControl as ToggleGroup, - __experimentalToggleGroupControlOptionIcon as ToggleIcon, -} from '@wordpress/components'; - -/** - * returns component for font options - * - * @param {Object} props - * @param {Object} props.base - * @param {string} props.id - * @param {string|string[]} props.value - * @param {Function} props.onChange - */ -const FontPicker = ( { base, id, value, onChange } ) => { - /** - * returns preset font sizes from theme.json - */ - const getFontSizes = () => { - const sizes = base?.settings?.typography?.fontSizes?.theme; - return sizes; - }; - - /** - * returns preset font families from theme.json - */ - const getFontFamilies = () => { - const fonts = base?.settings?.typography?.fontFamilies?.theme; - const result = []; - fonts.forEach( ( item ) => { - // eslint-disable-next-line no-param-reassign -- remove '-' from slug to use as title - item.slug = item.slug.replace( /\s+/g, '-' ); - result.push( { - value: item.slug, - label: item.name, - } ); - } ); - return result; - }; - - /** - * handles line height incremental input - * - * @param {number} val - * @param {string} dir - */ - const getLineHeight = ( val, dir ) => { - let increment; - if ( dir === 'minus' ) { - increment = -0.1; - } else increment = 0.1; - const number = parseFloat( val ); - const result = parseFloat( number + increment ) - .toFixed( 1 ) - .toString(); - return result; - }; - - switch ( id ) { - case 'fontSize': - return ( - onChange( val ) } - /> - ); - case 'fontFamily': - return ( - onChange( val ) } - /> - ); - case 'lineHeight': - return ( - onChange( val ) } - suffix={ - <> -