@wordpress/block-editor
Version:
386 lines (360 loc) • 9.05 kB
JavaScript
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import {
justifyLeft,
justifyCenter,
justifyRight,
justifySpaceBetween,
justifyStretch,
arrowRight,
arrowDown,
} from '@wordpress/icons';
import {
ToggleControl,
Flex,
FlexItem,
__experimentalToggleGroupControl as ToggleGroupControl,
__experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon,
} from '@wordpress/components';
/**
* Internal dependencies
*/
import { appendSelectors, getBlockGapCSS } from './utils';
import { getGapCSSValue } from '../hooks/gap';
import {
BlockControls,
JustifyContentControl,
BlockVerticalAlignmentControl,
} from '../components';
import { shouldSkipSerialization } from '../hooks/utils';
import { LAYOUT_DEFINITIONS } from './definitions';
// Used with the default, horizontal flex orientation.
const justifyContentMap = {
left: 'flex-start',
right: 'flex-end',
center: 'center',
'space-between': 'space-between',
};
// Used with the vertical (column) flex orientation.
const alignItemsMap = {
left: 'flex-start',
right: 'flex-end',
center: 'center',
stretch: 'stretch',
};
const verticalAlignmentMap = {
top: 'flex-start',
center: 'center',
bottom: 'flex-end',
stretch: 'stretch',
'space-between': 'space-between',
};
const flexWrapOptions = [ 'wrap', 'nowrap' ];
export default {
name: 'flex',
label: __( 'Flex' ),
inspectorControls: function FlexLayoutInspectorControls( {
layout = {},
onChange,
layoutBlockSupport = {},
} ) {
const { allowOrientation = true, allowJustification = true } =
layoutBlockSupport;
return (
<>
<Flex>
{ allowJustification && (
<FlexItem>
<FlexLayoutJustifyContentControl
layout={ layout }
onChange={ onChange }
/>
</FlexItem>
) }
{ allowOrientation && (
<FlexItem>
<OrientationControl
layout={ layout }
onChange={ onChange }
/>
</FlexItem>
) }
</Flex>
<FlexWrapControl layout={ layout } onChange={ onChange } />
</>
);
},
toolBarControls: function FlexLayoutToolbarControls( {
layout = {},
onChange,
layoutBlockSupport,
} ) {
const { allowVerticalAlignment = true, allowJustification = true } =
layoutBlockSupport;
if ( ! allowJustification && ! allowVerticalAlignment ) {
return null;
}
return (
<BlockControls group="block" __experimentalShareWithChildBlocks>
{ allowJustification && (
<FlexLayoutJustifyContentControl
layout={ layout }
onChange={ onChange }
isToolbar
/>
) }
{ allowVerticalAlignment && (
<FlexLayoutVerticalAlignmentControl
layout={ layout }
onChange={ onChange }
/>
) }
</BlockControls>
);
},
getLayoutStyle: function getLayoutStyle( {
selector,
layout,
style,
blockName,
hasBlockGapSupport,
layoutDefinitions = LAYOUT_DEFINITIONS,
} ) {
const { orientation = 'horizontal' } = layout;
// If a block's block.json skips serialization for spacing or spacing.blockGap,
// don't apply the user-defined value to the styles.
const blockGapValue =
style?.spacing?.blockGap &&
! shouldSkipSerialization( blockName, 'spacing', 'blockGap' )
? getGapCSSValue( style?.spacing?.blockGap, '0.5em' )
: undefined;
const justifyContent = justifyContentMap[ layout.justifyContent ];
const flexWrap = flexWrapOptions.includes( layout.flexWrap )
? layout.flexWrap
: 'wrap';
const verticalAlignment =
verticalAlignmentMap[ layout.verticalAlignment ];
const alignItems =
alignItemsMap[ layout.justifyContent ] || alignItemsMap.left;
let output = '';
const rules = [];
if ( flexWrap && flexWrap !== 'wrap' ) {
rules.push( `flex-wrap: ${ flexWrap }` );
}
if ( orientation === 'horizontal' ) {
if ( verticalAlignment ) {
rules.push( `align-items: ${ verticalAlignment }` );
}
if ( justifyContent ) {
rules.push( `justify-content: ${ justifyContent }` );
}
} else {
if ( verticalAlignment ) {
rules.push( `justify-content: ${ verticalAlignment }` );
}
rules.push( 'flex-direction: column' );
rules.push( `align-items: ${ alignItems }` );
}
if ( rules.length ) {
output = `${ appendSelectors( selector ) } {
${ rules.join( '; ' ) };
}`;
}
// Output blockGap styles based on rules contained in layout definitions in theme.json.
if ( hasBlockGapSupport && blockGapValue ) {
output += getBlockGapCSS(
selector,
layoutDefinitions,
'flex',
blockGapValue
);
}
return output;
},
getOrientation( layout ) {
const { orientation = 'horizontal' } = layout;
return orientation;
},
getAlignments() {
return [];
},
};
function FlexLayoutVerticalAlignmentControl( { layout, onChange } ) {
const { orientation = 'horizontal' } = layout;
const defaultVerticalAlignment =
orientation === 'horizontal'
? verticalAlignmentMap.center
: verticalAlignmentMap.top;
const { verticalAlignment = defaultVerticalAlignment } = layout;
const onVerticalAlignmentChange = ( value ) => {
onChange( {
...layout,
verticalAlignment: value,
} );
};
return (
<BlockVerticalAlignmentControl
onChange={ onVerticalAlignmentChange }
value={ verticalAlignment }
controls={
orientation === 'horizontal'
? [ 'top', 'center', 'bottom', 'stretch' ]
: [ 'top', 'center', 'bottom', 'space-between' ]
}
/>
);
}
const POPOVER_PROPS = {
placement: 'bottom-start',
};
function FlexLayoutJustifyContentControl( {
layout,
onChange,
isToolbar = false,
} ) {
const { justifyContent = 'left', orientation = 'horizontal' } = layout;
const onJustificationChange = ( value ) => {
onChange( {
...layout,
justifyContent: value,
} );
};
const allowedControls = [ 'left', 'center', 'right' ];
if ( orientation === 'horizontal' ) {
allowedControls.push( 'space-between' );
} else {
allowedControls.push( 'stretch' );
}
if ( isToolbar ) {
return (
<JustifyContentControl
allowedControls={ allowedControls }
value={ justifyContent }
onChange={ onJustificationChange }
popoverProps={ POPOVER_PROPS }
/>
);
}
const justificationOptions = [
{
value: 'left',
icon: justifyLeft,
label: __( 'Justify items left' ),
},
{
value: 'center',
icon: justifyCenter,
label: __( 'Justify items center' ),
},
{
value: 'right',
icon: justifyRight,
label: __( 'Justify items right' ),
},
];
if ( orientation === 'horizontal' ) {
justificationOptions.push( {
value: 'space-between',
icon: justifySpaceBetween,
label: __( 'Space between items' ),
} );
} else {
justificationOptions.push( {
value: 'stretch',
icon: justifyStretch,
label: __( 'Stretch items' ),
} );
}
return (
<ToggleGroupControl
__next40pxDefaultSize
__nextHasNoMarginBottom
label={ __( 'Justification' ) }
value={ justifyContent }
onChange={ onJustificationChange }
className="block-editor-hooks__flex-layout-justification-controls"
>
{ justificationOptions.map( ( { value, icon, label } ) => {
return (
<ToggleGroupControlOptionIcon
key={ value }
value={ value }
icon={ icon }
label={ label }
/>
);
} ) }
</ToggleGroupControl>
);
}
function FlexWrapControl( { layout, onChange } ) {
const { flexWrap = 'wrap' } = layout;
return (
<ToggleControl
__nextHasNoMarginBottom
label={ __( 'Allow to wrap to multiple lines' ) }
onChange={ ( value ) => {
onChange( {
...layout,
flexWrap: value ? 'wrap' : 'nowrap',
} );
} }
checked={ flexWrap === 'wrap' }
/>
);
}
function OrientationControl( { layout, onChange } ) {
const {
orientation = 'horizontal',
verticalAlignment,
justifyContent,
} = layout;
return (
<ToggleGroupControl
__next40pxDefaultSize
__nextHasNoMarginBottom
className="block-editor-hooks__flex-layout-orientation-controls"
label={ __( 'Orientation' ) }
value={ orientation }
onChange={ ( value ) => {
// Make sure the vertical alignment and justification are compatible with the new orientation.
let newVerticalAlignment = verticalAlignment;
let newJustification = justifyContent;
if ( value === 'horizontal' ) {
if ( verticalAlignment === 'space-between' ) {
newVerticalAlignment = 'center';
}
if ( justifyContent === 'stretch' ) {
newJustification = 'left';
}
} else {
if ( verticalAlignment === 'stretch' ) {
newVerticalAlignment = 'top';
}
if ( justifyContent === 'space-between' ) {
newJustification = 'left';
}
}
return onChange( {
...layout,
orientation: value,
verticalAlignment: newVerticalAlignment,
justifyContent: newJustification,
} );
} }
>
<ToggleGroupControlOptionIcon
icon={ arrowRight }
value="horizontal"
label={ __( 'Horizontal' ) }
/>
<ToggleGroupControlOptionIcon
icon={ arrowDown }
value="vertical"
label={ __( 'Vertical' ) }
/>
</ToggleGroupControl>
);
}