Skip to content

Commit

Permalink
Merge pull request #1571 from tomusborne/feature/add-alignment-toolbar
Browse files Browse the repository at this point in the history
Feature: Add alignment toolbar
  • Loading branch information
tomusborne authored Dec 7, 2024
2 parents 69bb0fa + 8fd66b5 commit f0b7abb
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 14 deletions.
4 changes: 4 additions & 0 deletions src/blocks/element/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
"htmlAttributes": {
"type": "object",
"default": {}
},
"align": {
"type": "string",
"default": ""
}
},
"supports": {
Expand Down
16 changes: 15 additions & 1 deletion src/blocks/element/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import RootElement from '../../components/root-element/index.js';
import { BlockSettings } from './components/BlockSettings';
import { selectorShortcuts } from '@utils/selectorShortcuts.js';
import { withStyles } from '@hoc/withStyles';
import { BlockStylesBuilder, StylesOnboarder } from '@components/index.js';
import { AlignmentToolbar, BlockStylesBuilder, StylesOnboarder } from '@components/index.js';
import { withHtmlAttributes } from '@hoc/withHtmlAttributes.js';
import { getBlockClasses } from '@utils/getBlockClasses.js';
import { BlockAppender } from '@components';
Expand All @@ -19,13 +19,15 @@ function EditBlock( props ) {
clientId,
isSelected,
name,
getStyleValue,
onStyleChange,
editorHtmlAttributes,
styles,
} = props;

const {
tagName,
align,
} = attributes;

const classNames = getBlockClasses(
Expand Down Expand Up @@ -95,6 +97,17 @@ function EditBlock( props ) {
<>
<InspectorControls>
<StylesOnboarder />

<AlignmentToolbar
withTextAlign
withBlockWidth
getStyleValue={ getStyleValue }
onStyleChange={ onStyleChange }
align={ align }
setAttributes={ setAttributes }
clientId={ clientId }
/>

<BlockStyles
settingsTab={ (
<BlockSettings
Expand All @@ -115,6 +128,7 @@ function EditBlock( props ) {
<RootElement
name={ name }
clientId={ clientId }
align={ align }
>
<TagName { ...innerBlocksProps } />
</RootElement>
Expand Down
2 changes: 1 addition & 1 deletion src/blocks/media/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
},
"editorScript": "file:./index.js",
"editorStyle": [
"file:./editor.scss"
"file:./index.css"
],
"usesContext": [
"postId",
Expand Down
1 change: 1 addition & 0 deletions src/blocks/media/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ function EditBlock( props ) {
<>
<InspectorControls>
<StylesOnboarder />

<BlockStyles
settingsTab={ (
<BlockSettings
Expand Down
11 changes: 0 additions & 11 deletions src/blocks/media/editor.scss
Original file line number Diff line number Diff line change
@@ -1,11 +0,0 @@
.wp-block-generateblocks-media {
display: block;

&:not(.is-selected) {
display: none;
}

&.is-selected + [class*="gb-media"] {
display: none;
}
}
11 changes: 10 additions & 1 deletion src/blocks/text/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { BlockSettings } from './components/BlockSettings';
import { selectorShortcuts } from '@utils/selectorShortcuts';
import { withStyles } from '@hoc/withStyles';
import { BlockStylesBuilder } from '@components/block-styles-builder/BlockStylesBuilder';
import { LinkBlockToolbar, StylesOnboarder, TagNameToolbar } from '@components/index';
import { AlignmentToolbar, LinkBlockToolbar, StylesOnboarder, TagNameToolbar } from '@components/index';
import { withHtmlAttributes } from '@hoc/withHtmlAttributes';
import { getBlockClasses } from '@utils/getBlockClasses';
import { DynamicTagBlockToolbar } from '../../dynamic-tags';
Expand All @@ -29,6 +29,7 @@ function EditBlock( props ) {
name,
clientId,
onStyleChange,
getStyleValue,
editorHtmlAttributes,
isSelected,
styles,
Expand Down Expand Up @@ -178,6 +179,14 @@ function EditBlock( props ) {
context={ context }
/>

<AlignmentToolbar
withTextAlign
getStyleValue={ getStyleValue }
onStyleChange={ onStyleChange }
setAttributes={ setAttributes }
clientId={ clientId }
/>

{ ! iconOnly && (
<DynamicTagBlockToolbar
value={ content }
Expand Down
119 changes: 119 additions & 0 deletions src/components/alignment-toolbar/AlignmentToolbar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { __ } from '@wordpress/i18n';
import { ToolbarButton, Dropdown, MenuGroup, MenuItem } from '@wordpress/components';
import { BlockControls } from '@wordpress/block-editor';
import { alignLeft, alignCenter, alignRight, alignJustify } from '@wordpress/icons';
import { useBlockStyles } from '@hooks/useBlockStyles';
import { BlockWidth } from './BlockWidth';

const POPOVER_PROPS = {
position: 'bottom right',
};

export function AlignmentToolbar( {
getStyleValue,
onStyleChange,
align,
setAttributes,
clientId,
withTextAlign = false,
withBlockWidth = false,
} ) {
const {
currentAtRule,
} = useBlockStyles();

function getAlignmentIcon() {
if ( withTextAlign ) {
const alignment = getStyleValue( 'textAlign', currentAtRule );

switch ( alignment ) {
case 'center':
return alignCenter;
case 'right':
return alignRight;
case 'justify':
return alignJustify;
case 'left':
default:
return alignLeft;
}
}
}

const textAlignments = [
{
icon: alignLeft,
value: 'left',
label: __( 'Left', 'generateblocks' ),
},
{
icon: alignCenter,
value: 'center',
label: __( 'Center', 'generateblocks' ),
},
{
icon: alignRight,
value: 'right',
label: __( 'Right', 'generateblocks' ),
},
{
icon: alignJustify,
value: 'justify',
label: __( 'Justify', 'generateblocks' ),
},
];

if ( ! withTextAlign && ! withBlockWidth ) {
return null;
}

return (
<BlockControls group="inline">
<Dropdown
popoverProps={ POPOVER_PROPS }
renderToggle={ ( { isOpen, onToggle } ) => (
<ToolbarButton
icon={ getAlignmentIcon() }
label={ __( 'Alignment', 'generateblocks' ) }
onClick={ onToggle }
aria-expanded={ isOpen }
isPressed={ !! isOpen }
/>
) }
renderContent={ () => (
<>
{ !! withTextAlign && (
<MenuGroup label={ __( 'Text Alignment', 'generateblocks' ) }>
{ textAlignments.map( ( { icon, value, label } ) => (
<MenuItem
key={ value }
icon={ icon }
isPressed={ value === getStyleValue( 'textAlign', currentAtRule ) }
onClick={ () => {
if ( value === getStyleValue( 'textAlign', currentAtRule ) ) {
onStyleChange( 'textAlign', '', currentAtRule );
return;
}

onStyleChange( 'textAlign', value, currentAtRule );
} }
>
{ label }
</MenuItem>
) ) }
</MenuGroup>
) }

{ !! withBlockWidth && '' === currentAtRule && undefined !== align && (
<BlockWidth
align={ align }
onChange={ ( value ) => setAttributes( { align: value } ) }
clientId={ clientId }
/>
) }
</>
) }
/>
</BlockControls>
);
}
73 changes: 73 additions & 0 deletions src/components/alignment-toolbar/BlockWidth.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { __ } from '@wordpress/i18n';
import { MenuGroup, MenuItem } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { store as blockEditorStore, useSetting } from '@wordpress/block-editor';
import { alignNone, stretchWide, stretchFullWidth } from '@wordpress/icons';
import { applyFilters } from '@wordpress/hooks';

export function BlockWidth( { align, onChange, clientId } ) {
const hasWideSize = useSetting( 'layout.wideSize' );
const { themeSupportsAlignWide } = useSelect(
( select ) => {
const { getSettings } = select( blockEditorStore );
return {
themeSupportsAlignWide: getSettings()?.alignWide,
};
},
[]
);
const {
getBlockRootClientId,
} = useSelect( ( select ) => select( blockEditorStore ), [] );

const removeBlockWidthOptions = applyFilters(
'generateblocks.editor.removeBlockWidthOptions',
false,
{ clientId }
);

if ( ( ! themeSupportsAlignWide && ! hasWideSize ) || removeBlockWidthOptions ) {
return null;
}

const parentBlockId = getBlockRootClientId( clientId );

if ( parentBlockId ) {
return null;
}

const options = [
{
label: __( 'Default', 'generateblocks' ),
value: '',
icon: alignNone,
},
{
label: __( 'Wide', 'generateblocks' ),
value: 'wide',
icon: stretchWide,
},
{
label: __( 'Full', 'generateblocks' ),
value: 'full',
icon: stretchFullWidth,
},
];

return (
<MenuGroup label={ __( 'Block Width', 'generateblocks' ) }>
{ options.map( ( option ) => {
return (
<MenuItem
key={ option.value }
isPressed={ align === option.value }
onClick={ () => onChange( option.value ) }
icon={ option.icon }
>
{ option.label }
</MenuItem>
);
} ) }
</MenuGroup>
);
}
2 changes: 2 additions & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { NoticePanel } from './notice-panel/NoticePanel';
import { OnboardNotice } from './onboard-notice/OnboardNotice';
import { TagNameToolbar } from './TagNameToolbar/TagNameToolbar';
import { BlockAppender } from './block-appender/BlockAppender';
import { AlignmentToolbar } from './alignment-toolbar/AlignmentToolbar';

export {
AdvancedSelect,
Expand Down Expand Up @@ -92,4 +93,5 @@ export {
UnitControl,
UnitPicker,
URLControls,
AlignmentToolbar,
};
19 changes: 19 additions & 0 deletions src/hoc/withHtmlAttributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { useEffect, useMemo, useState } from '@wordpress/element';
import { InspectorAdvancedControls } from '@wordpress/block-editor';
import { TextControl } from '@wordpress/components';

import { useUpdateEffect } from 'react-use';

import { convertInlineStyleStringToObject } from '@utils/convertInlineStyleStringToObject';
import { replaceTags } from '../dynamic-tags/utils';

Expand Down Expand Up @@ -68,6 +70,8 @@ export function withHtmlAttributes( WrappedComponent ) {
const {
htmlAttributes = {},
uniqueId,
className,
align,
} = attributes;

const [ styleWithReplacements, setStyleWithReplacements ] = useState( '' );
Expand Down Expand Up @@ -102,6 +106,20 @@ export function withHtmlAttributes( WrappedComponent ) {
getReplacements();
}, [ style, context ] );

useUpdateEffect( () => {
const layoutClasses = [ 'alignwide', 'alignfull' ];
const existingClasses = className?.split( ' ' ) || [];
const newClasses = existingClasses.filter(
( existingClass ) => ! layoutClasses.includes( existingClass )
);

if ( align ) {
newClasses.push( 'align' + align );
}

setAttributes( { className: newClasses.join( ' ' ) } );
}, [ align ] );

const inlineStyleObject = typeof styleWithReplacements === 'string'
? convertInlineStyleStringToObject( styleWithReplacements )
: '';
Expand All @@ -110,6 +128,7 @@ export function withHtmlAttributes( WrappedComponent ) {
style: inlineStyleObject,
'data-gb-id': uniqueId,
'data-context-post-id': context?.postId ?? context?.[ 'generateblocks/loopIndex' ] ?? 0,
'data-align': align ? align : undefined,
};

const frontendHtmlAttributes = useMemo( () => {
Expand Down

0 comments on commit f0b7abb

Please sign in to comment.