@wordpress/block-editor
Version:
336 lines (316 loc) • 8.99 kB
JavaScript
/**
* WordPress dependencies
*/
import {
__experimentalUseCustomUnits as useCustomUnits,
__experimentalUnitControl as UnitControl,
__experimentalToggleGroupControl as ToggleGroupControl,
__experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon,
__experimentalInputControlPrefixWrapper as InputControlPrefixWrapper,
__experimentalVStack as VStack,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import {
Icon,
alignNone,
stretchWide,
justifyLeft,
justifyCenter,
justifyRight,
} from '@wordpress/icons';
import { getCSSRules } from '@wordpress/style-engine';
/**
* Internal dependencies
*/
import { useSettings } from '../components/use-settings';
import { appendSelectors, getBlockGapCSS, getAlignmentsInfo } from './utils';
import { getGapCSSValue } from '../hooks/gap';
import { BlockControls, JustifyContentControl } from '../components';
import { shouldSkipSerialization } from '../hooks/utils';
import { LAYOUT_DEFINITIONS } from './definitions';
export default {
name: 'constrained',
label: __( 'Constrained' ),
inspectorControls: function DefaultLayoutInspectorControls( {
layout,
onChange,
layoutBlockSupport = {},
} ) {
const { wideSize, contentSize, justifyContent = 'center' } = layout;
const {
allowJustification = true,
allowCustomContentAndWideSize = true,
} = layoutBlockSupport;
const onJustificationChange = ( value ) => {
onChange( {
...layout,
justifyContent: value,
} );
};
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' ),
},
];
const [ availableUnits ] = useSettings( 'spacing.units' );
const units = useCustomUnits( {
availableUnits: availableUnits || [ '%', 'px', 'em', 'rem', 'vw' ],
} );
return (
<VStack
spacing={ 4 }
className="block-editor-hooks__layout-constrained"
>
{ allowCustomContentAndWideSize && (
<>
<UnitControl
__next40pxDefaultSize
label={ __( 'Content width' ) }
labelPosition="top"
value={ contentSize || wideSize || '' }
onChange={ ( nextWidth ) => {
nextWidth =
0 > parseFloat( nextWidth )
? '0'
: nextWidth;
onChange( {
...layout,
contentSize: nextWidth,
} );
} }
units={ units }
prefix={
<InputControlPrefixWrapper variant="icon">
<Icon icon={ alignNone } />
</InputControlPrefixWrapper>
}
/>
<UnitControl
__next40pxDefaultSize
label={ __( 'Wide width' ) }
labelPosition="top"
value={ wideSize || contentSize || '' }
onChange={ ( nextWidth ) => {
nextWidth =
0 > parseFloat( nextWidth )
? '0'
: nextWidth;
onChange( {
...layout,
wideSize: nextWidth,
} );
} }
units={ units }
prefix={
<InputControlPrefixWrapper variant="icon">
<Icon icon={ stretchWide } />
</InputControlPrefixWrapper>
}
/>
<p className="block-editor-hooks__layout-constrained-helptext">
{ __(
'Customize the width for all elements that are assigned to the center or wide columns.'
) }
</p>
</>
) }
{ allowJustification && (
<ToggleGroupControl
__next40pxDefaultSize
__nextHasNoMarginBottom
label={ __( 'Justification' ) }
value={ justifyContent }
onChange={ onJustificationChange }
>
{ justificationOptions.map(
( { value, icon, label } ) => {
return (
<ToggleGroupControlOptionIcon
key={ value }
value={ value }
icon={ icon }
label={ label }
/>
);
}
) }
</ToggleGroupControl>
) }
</VStack>
);
},
toolBarControls: function DefaultLayoutToolbarControls( {
layout = {},
onChange,
layoutBlockSupport,
} ) {
const { allowJustification = true } = layoutBlockSupport;
if ( ! allowJustification ) {
return null;
}
return (
<BlockControls group="block" __experimentalShareWithChildBlocks>
<DefaultLayoutJustifyContentControl
layout={ layout }
onChange={ onChange }
/>
</BlockControls>
);
},
getLayoutStyle: function getLayoutStyle( {
selector,
layout = {},
style,
blockName,
hasBlockGapSupport,
layoutDefinitions = LAYOUT_DEFINITIONS,
} ) {
const { contentSize, wideSize, justifyContent } = layout;
const blockGapStyleValue = getGapCSSValue( style?.spacing?.blockGap );
// If a block's block.json skips serialization for spacing or
// spacing.blockGap, don't apply the user-defined value to the styles.
let blockGapValue = '';
if ( ! shouldSkipSerialization( blockName, 'spacing', 'blockGap' ) ) {
// If an object is provided only use the 'top' value for this kind of gap.
if ( blockGapStyleValue?.top ) {
blockGapValue = getGapCSSValue( blockGapStyleValue?.top );
} else if ( typeof blockGapStyleValue === 'string' ) {
blockGapValue = getGapCSSValue( blockGapStyleValue );
}
}
const marginLeft =
justifyContent === 'left' ? '0 !important' : 'auto !important';
const marginRight =
justifyContent === 'right' ? '0 !important' : 'auto !important';
let output =
!! contentSize || !! wideSize
? `
${ appendSelectors(
selector,
'> :where(:not(.alignleft):not(.alignright):not(.alignfull))'
) } {
max-width: ${ contentSize ?? wideSize };
margin-left: ${ marginLeft };
margin-right: ${ marginRight };
}
${ appendSelectors( selector, '> .alignwide' ) } {
max-width: ${ wideSize ?? contentSize };
}
${ appendSelectors( selector, '> .alignfull' ) } {
max-width: none;
}
`
: '';
if ( justifyContent === 'left' ) {
output += `${ appendSelectors(
selector,
'> :where(:not(.alignleft):not(.alignright):not(.alignfull))'
) }
{ margin-left: ${ marginLeft }; }`;
} else if ( justifyContent === 'right' ) {
output += `${ appendSelectors(
selector,
'> :where(:not(.alignleft):not(.alignright):not(.alignfull))'
) }
{ margin-right: ${ marginRight }; }`;
}
// If there is custom padding, add negative margins for alignfull blocks.
if ( style?.spacing?.padding ) {
// The style object might be storing a preset so we need to make sure we get a usable value.
const paddingValues = getCSSRules( style );
paddingValues.forEach( ( rule ) => {
if ( rule.key === 'paddingRight' ) {
// Add unit if 0, to avoid calc(0 * -1) which is invalid.
const paddingRightValue =
rule.value === '0' ? '0px' : rule.value;
output += `
${ appendSelectors( selector, '> .alignfull' ) } {
margin-right: calc(${ paddingRightValue } * -1);
}
`;
} else if ( rule.key === 'paddingLeft' ) {
// Add unit if 0, to avoid calc(0 * -1) which is invalid.
const paddingLeftValue =
rule.value === '0' ? '0px' : rule.value;
output += `
${ appendSelectors( selector, '> .alignfull' ) } {
margin-left: calc(${ paddingLeftValue } * -1);
}
`;
}
} );
}
// Output blockGap styles based on rules contained in layout definitions in theme.json.
if ( hasBlockGapSupport && blockGapValue ) {
output += getBlockGapCSS(
selector,
layoutDefinitions,
'constrained',
blockGapValue
);
}
return output;
},
getOrientation() {
return 'vertical';
},
getAlignments( layout ) {
const alignmentInfo = getAlignmentsInfo( layout );
if ( layout.alignments !== undefined ) {
if ( ! layout.alignments.includes( 'none' ) ) {
layout.alignments.unshift( 'none' );
}
return layout.alignments.map( ( alignment ) => ( {
name: alignment,
info: alignmentInfo[ alignment ],
} ) );
}
const { contentSize, wideSize } = layout;
const alignments = [
{ name: 'left' },
{ name: 'center' },
{ name: 'right' },
];
if ( contentSize ) {
alignments.unshift( { name: 'full' } );
}
if ( wideSize ) {
alignments.unshift( { name: 'wide', info: alignmentInfo.wide } );
}
alignments.unshift( { name: 'none', info: alignmentInfo.none } );
return alignments;
},
};
const POPOVER_PROPS = {
placement: 'bottom-start',
};
function DefaultLayoutJustifyContentControl( { layout, onChange } ) {
const { justifyContent = 'center' } = layout;
const onJustificationChange = ( value ) => {
onChange( {
...layout,
justifyContent: value,
} );
};
const allowedControls = [ 'left', 'center', 'right' ];
return (
<JustifyContentControl
allowedControls={ allowedControls }
value={ justifyContent }
onChange={ onJustificationChange }
popoverProps={ POPOVER_PROPS }
/>
);
}