WordPress / gutenberg

The Block Editor project for WordPress and beyond. Plugin is available from the official repository.
https://wordpress.org/gutenberg/
Other
10.5k stars 4.2k forks source link

Extending AlignmentToolbar Options #27629

Open augustuswm opened 3 years ago

augustuswm commented 3 years ago

Is your feature request related to a problem? Please describe.

Short: We would like to be able to extend BLOCK_ALIGNMENTS_CONTROLS at runtime.

My apologies in advance if this is already configurable somewhere (or if this is not in the scope of Gutenberg) as I am still getting a handle on the Gutenberg source.

Longer: We currently develop our in house CMS on top of WordPress, and we were looking to extend the default alignment options. From what I can tell we can currently define custom alignment keys in our block registrations (supports.align) or filter existing block registrations to add additional alignment keys. While they are technically functional, these alignments do not have icons or titles in the AlignmentToolbar as they are not present in the BLOCK_ALIGNMENTS_CONTROLS object that is defined in https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/block-alignment-toolbar/index.js

Describe the solution you'd like

A filter we could hook with a function that would return the extended BLOCK_ALIGNMENTS_CONTROLS. This filter would then be invoked by the AlignmentToolbar instead of the referencing the BLOCK_ALIGNMENTS_CONTROLS object.

Describe alternatives you've considered

I think this functionality could also be handled by a registration model with a store, but that seems like a lot of extra work. I'm sure there are other options, but I'm not familiar enough with Gutenberg internals to suggest them.

Alternatively we can certainly solve this with a self-built version of Gutenberg, but we prefer to stay with the version distributed with WordPress releases if possible.

talldan commented 3 years ago

@augustuswm What are the additional alignment options you're looking to add?

augustuswm commented 3 years ago

It depends on the frontend that we are implementing, but for a current project we are working on we would be looking to add additional left / right floating alignments for images and multiple "Wide" alignments.

For images we have a standard left floated treatment that aligns with the left hand edge of the text as well as a left floated treatment that extends out past the left hand edge. (With the same options for right floating images).

The wide alignment case is simpler, essentially adding (for lack of better names) a slight wide and extra wide option. Alignment widths that are narrower and wider than the Wide alignment.

landwire commented 3 years ago

@augustuswm: were you able to add any more alignment options? If so, would you care to share how you achieved this?

augustuswm commented 3 years ago

@landwire - We have not. That work was put off as getting something implemented looked like it would be too much effort for what we were going to use it for. We may revisit in the next few months depending on what priorities look like.

Taking a quick 2 minute look at the master branch, I think the same core issue exists as it doesn't look like there is an api to modify BLOCK_ALIGNMENTS_CONTROLS. Certainly possible I've missed a change though.

WORX-Developer commented 3 years ago

This is similar to something we would like to achieve. Most of the time we have a layout with a base content width, which is used nearly throughout all pages. But there are elements that need to be narrower. So what we would need instead of "no alignment (default), wide, full" is something like "narrow, no alignment (default), wide (maybe), full". I know this could be done by using the default width on every element that needs to be narrow and wide on every other element. But that is not very intuitiv when setting up a page.

Have you any thoughts on this?

michaelbourne commented 3 years ago

This would be beneficial on so many projects. Right now we just use custom classes to alter alignment, but that's not the smoothest use of Gutenberg. And since users can't select multiple block styles, we're lacking any sort of UI based design features for alignment outside of the box.

A filter for BLOCK_ALIGNMENTS_CONTROLS would work great, as then my parent theme can supply additional alignments, and take advantage of wp.hooks.addFilter to enable them on specific blocks for each project.

mertafor commented 2 years ago

I'm trying to achieve the same as well. Most websites are using wide container on desktop, usually 1200px and higher. A Narrow option or even Custom Width is vital to limit certain sections on desktop. It can be useful for almost all block types such as single paragraph, images or even columns block. In my opinion, asking customers to add a "align-narrow" CSS class through Additional CSS Classes field is "asking too much" :)

fatesallow commented 2 years ago

I would really like this too. I'd love to be able to use a filter to add to the alignment options.

Right now I make a filter that adds an option for "extra wide" in the sidebar, but it's hard to explain to clients why they choose an alignment option on the block toolbar, but for "extra wide" they need to use the sidebar.

augustuswm commented 2 years ago

This is not at all PR ready, but internally at my company we are using the following if it is helpful to sketch out the idea. It is by no means extensively tested, and is really targeted to work for our use case.

Replace direct use of BLOCK_ALIGNMENTS_CONTROLS with filtered value in

packages/block-editor/src/components/block-alignment-control/ui.js

const blockAlignmentControls = applyFilters('layout.alignments.controls', BLOCK_ALIGNMENTS_CONTROLS);

Replace default use of DEFAULT_CONTROLS and direct use of WIDE_CONTROLS in

packages/block-editor/src/components/block-alignment-control/use-available-alignments.js

controls = applyFilters('layout.alignments.availableControls', controls);
const { alignments: availableAlignments = applyFilters('layout.alignments.availableControls', DEFAULT_CONTROLS) } = layout;
const wideControls = applyFilters('layout.alignments.availableWideControls', WIDE_CONTROLS);

Filter alignments returned from getAlignments for flow layout in

packages/block-editor/src/layouts/flow.js

return applyFilters('layout.alignments', alignments);

Filter default controls used by useAvailableAlignments in

packages/block-editor/src/components/block-alignment-control/use-available-alignments.js

export default function useAvailableAlignments(controls = applyFilters('layout.alignments.availableControls', DEFAULT_CONTROLS)) {

Filter direct use of ALL_ALIGNMENTS and WIDE_ALIGNMENTS in

packages/block-editor/src/hooks/align.js

let allAlignments = applyFilters('layout.alignments.allValid', ALL_ALIGNMENTS);
return without(validAlignments, ...applyFilters('layout.alignments.allWide', WIDE_ALIGNMENTS));

There is likely more to do to make this really generic and work everywhere, but we use these filters in to extend the list of alignments to ['left', 'center', 'right', 'narrow', 'wide', 'extrawide', 'full']

Levdbas commented 2 years ago

I would like to add the option of small alignment myself. Sometimes you just want an element to end up a tiny bit smaller in width then other blocks. To be able to extend the allignments would be a great addition.

CreativeDive commented 1 year ago

I'm a bit new to block development and not familiar with react. But I was playing around with native javascript here. Unfortunately, there is no way to extend the core block alignment toolbar. But it is possible to create our own. Here's my solution with a cool advantage: you can extend your custom alignments like narrowWidth.

Another advantage is that you can use "wide" and "full" even if a block is nested as innerBlock.

Bildschirm­foto 2023-04-07 um 19 12 08

Apart from the fact that the control element does not appear as active, it already works very well. I think I need to use other components here and work with React UseState. Anyone interested is welcome to help.

var {
    __,
} = wp.i18n;

var {
    BlockControls,
    useBlockProps,
} = wp.blockEditor;

var {
    ToolbarDropdownMenu,
    ToolbarGroup,
    DropdownMenu,
    MenuGroup,
    MenuItem,
} = wp.components;

var {
    createHigherOrderComponent,
} = wp.compose;

var {
    getBlockSupport,
} = wp.blocks;

var {
    Fragment,
    createElement,
} = wp.element;

var el = createElement;

/*
* Disable default "align" to hide the core block alignment toolbar.
* Clone the block specific "align" supports values and store these in "alignCustom" instead.
*/

function modifyBlockAlignmentSupport( settings, name ) {

    if ( ! name.includes('acf/') ) {
        // Only modify our acf blocks
        return settings;
    }

    const hasAlignSupport = getBlockSupport( name, 'align', false );        

    if( ! hasAlignSupport ) {
        return settings;
    }   

    // Get the original align values from the block supports settings
    let originalAlignSupport = settings.supports.align;

    // Modify the default block supports settings
    newSettings = {
        ...settings,
        supports: {
            ...settings.supports,
            align: false, // <-- This will disable the default align to hide the core block alignment toolbar
            alignCustom: originalAlignSupport, // <-- We clone the default align and store it here instead
        }
    };

    return newSettings;

}

wp.hooks.addFilter( 'blocks.registerBlockType', 'plugin-name/modify-block-align-support', modifyBlockAlignmentSupport );

/*
* Function to handle the block alignment icons
*/

function blockAlignIcon( name ) {

    var icons = {
        'none': 'M5 15h14V9H5v6zm0 4.8h14v-1.5H5v1.5zM5 4.2v1.5h14V4.2H5z',
        'full': 'M5 4v11h14V4H5zm3 15.8h8v-1.5H8v1.5z',
        'wide': 'M5 9v6h14V9H5zm11-4.8H8v1.5h8V4.2zM8 19.8h8v-1.5H8v1.5z',
        'narrow': 'M5 9v6h14V9H5zm11-4.8H8v1.5h8V4.2zM8 19.8h8v-1.5H8v1.5z',
        'left': 'M4 9v6h14V9H4zm8-4.8H4v1.5h8V4.2zM4 19.8h8v-1.5H4v1.5z',
        'center': 'M7 9v6h10V9H7zM5 19.8h14v-1.5H5v1.5zM5 4.3v1.5h14V4.3H5z',
        'right': 'M6 15h14V9H6v6zm6-10.8v1.5h8V4.2h-8zm0 15.6h8v-1.5h-8v1.5z',
    }

    //var path = icons.name ? icons.name : icons.none;
    var path = icons[name];

    return el('svg', { width: 24, height: 24 },
        el('path', { d: path } )
    );

}

/*
* Filter to add custom block alignment toolbar controls
*/

var customBlockAlignmentControls = createHigherOrderComponent( function( BlockEdit ) {

    return function( props ) {

        const blockName = props.name;
        const currentAlign = props.attributes.align;
        const originalEdit = el( BlockEdit, props );        

        // Check for block align support        
        const blockAlignSupport = getBlockSupport( blockName, 'alignCustom', false );            

        // We add this custom controls to our acf blocks only
        if( ! blockName.includes('acf/') ) {
            // Return unmodified block edit
            return originalEdit;
        }

        // Do not add this custom controls if the block type has no align support
        if( ! blockAlignSupport ) {
            // Return unmodified block edit
            return originalEdit;
        }   

        // Get the current ToolbarDropdownMenu icon, depending on the selected align
        let currentIcon = currentAlign ? currentAlign : 'none';     
        currentIcon = blockAlignIcon( currentIcon );

        /*
        * Define a function to set the "align" attribute, after selecting a specific block alignment
        */

        function onChangeAlignment( newAlignment ) {

            let iconName = newAlignment;

            if( newAlignment === 'none' ) {
                // Because we don't want a "alignnone" classname in our block
                newAlignment = false;
            }

            // Change the block align attribute
            props.setAttributes( {
                align: newAlignment === undefined ? 'none' : newAlignment,
            } );

            // Change the current ToolbarDropdownMenu icon if the block align has been changed
            const alignToolbarButton = document.querySelector('[aria-label="Align"]');
            if( alignToolbarButton ) {
                let iconPath = blockAlignIcon( iconName );
                iconPath = iconPath.props.children.props.d;
                alignToolbarButton.innerHTML = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="' + iconPath + '"/></svg>';
            }

        }        

        /*
        * List all possible block alignments
        */

        const alignControls = {
            none: __('None', 'textdomain'),
            full: __('Full width', 'textdomain'),
            wide: __('Wide width', 'textdomain'),
            narrow: __('Narrow width', 'textdomain'),
            left: __('Align left', 'textdomain'),
            center: __('Align center', 'textdomain'),
            right: __('Align right', 'textdomain'),
        }

        /*
        * Build the toolbar block alignment controls depening on the align support of the block type
        */

        const allowedAlignControls = [];

        for( let key in alignControls ) {

            if( ! blockAlignSupport.includes( key ) && key !== 'none' ) {
                // [None] should be there all the time to reset other selected alignments
                // Only add the current align control if it's supported
                continue;
            }

            let controlTitle = alignControls[key];

            let newControl = { 
                title: controlTitle,
                icon: blockAlignIcon( key ),
                onClick: () => onChangeAlignment( key ),
            };

            allowedAlignControls.push(newControl);

        }

        /*
        * Re-Build the block toolbar and edit
        */

        return el(
            Fragment,
            {},
            el( BlockControls, { 
                    key: 'controls',
                    group: 'default',
                },
                el( ToolbarGroup, null,
                    el( ToolbarDropdownMenu, {
                        label: 'Align',
                        icon: currentIcon,
                        controls: allowedAlignControls,
                    } ),
                ),
            ), el( BlockEdit, props )
        ); 

    };

}, 'withInspectorControls' );

wp.hooks.addFilter( 'editor.BlockEdit', 'plugin-name/with-inspector-controls', customBlockAlignmentControls );
landwire commented 1 year ago

Would be great if this could be done just by adding some layout settings in theme.json. That would be so helpful!

corentin-gautier commented 11 months ago

I've been looking for something like this since using gutenberg, pretty much all the designs I have to implement have more than wide and full alignement (it's often normal/wide/wider/fullscreen) but a flexible way to achieve this would be great. There's currently two ways of enabling wide and full alignment:

A more generalized system with add_theme_support('alignments', true) and a configuration in theme.json like this:

"layout": {
  "default": "700px",
  "wide": "880px"
  "wider": "1000px"
  "even-wider": "1200px"
  "full": "100vw"
}

or something like this:

{
  "layout": {
    "default": {
      "name": "Default",
      "size": "700px",
      "icon": "<svg></svg>"
    },
    "wide": {
      "name": "Wide",
      "size": "800px",
      "icon": "<svg></svg>"
    },
    "wider": {
      "name": "Wider",
      "size": "1000px",
      "icon": "<svg></svg>"
    },
    "even-wider": {
      "name": "Even wider",
      "size": "1200px",
      "icon": "<svg></svg>"
    },
    "full": {
      "name": "Full",
      "size": "100vw",
      "icon": "<svg></svg>"
    }
  }
}

@markhowellsmead @talldan @skorasaurus what do you think ?

markhowellsmead commented 11 months ago

I've been waiting for this for five years. It looks as though no-one cares about this.

corentin-gautier commented 11 months ago

@mtias could we add this to the ongoing work over at #41241 or #33447 ?

As I said, pretty much all the websites I did over the last few years have been needing more than 3 alignments options, I'm guessing this is the case for a lot of people

eric-michel commented 7 months ago

This has been a constant source of friction for us when using Gutenberg, and is something I've mentioned in other threads (and I know many others have as well).

I do not understand why max-width options are not handled in theme.json like almost every other value - with an array of options defined within the theme. Just like we are not limited to 3 color options, 3 font sizes, or 3 spacing values, we shouldn't be limited to 3 width options. Let theme and site developers decide what is best for their stakeholders.

As it works now, we are assigning our two most common widths to default and wide options, then adding custom classes to our child themes to take care of what should be narrow or wider options, for instance. This is very cumbersome to our clients who expect to be able to create their content in fundamental ways without resorting to utility classes.

landwire commented 7 months ago

@eric-michel This is so true. I cannot believe that it would be much code involved to allow more custom alignments like narrow and extrawide in theme.json. @augustuswm showed a good way to start I assume, but I have not checked the code in detail. It would be so helpful for developers as my clients also find it very annoying to use utility classes.

eric-michel commented 7 months ago

@landwire Agreed. I'm not sure how to bring more visibility to this issue given how long it's been waiting in the wings.

One workaround that does work is altering the content max-widths via the Layout setting in Group blocks: image

But this solution is just as cumbersome as using utility classes - maybe moreso. If I want to regularly use a "narrow" alignment of 500px max width, I don't want to have to create a Group block where I manually enter 500px into the "Content" field every single time. That's the antithesis of DRY.

markhowellsmead commented 7 months ago

Pinging @WordPress/outreach to get some more visibility and feedback on this, please. 🙏