-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Try adding a grid layout type #49018
Changes from all commits
7c3ec88
439499e
afa4124
99a52f5
b338b73
7bb10f9
52bfcc7
04778c8
f85a8b5
470fcf3
099db1e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1342,7 +1342,7 @@ protected function get_layout_styles( $block_metadata ) { | |
|
||
if ( | ||
! empty( $class_name ) && | ||
! empty( $base_style_rules ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like this change is to allow the block of code to be reachable if |
||
is_array( $base_style_rules ) | ||
) { | ||
// Output display mode. This requires special handling as `display` is not exposed in `safe_style_css_filter`. | ||
if ( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -87,3 +87,22 @@ function allow_filter_in_styles( $allow_css, $css_test_string ) { | |
} | ||
|
||
add_filter( 'safecss_filter_attr_allow_css', 'allow_filter_in_styles', 10, 2 ); | ||
|
||
/** | ||
* Mark CSS safe if it contains grid functions | ||
* | ||
* This function should not be backported to core. | ||
* | ||
* @param bool $allow_css Whether the CSS is allowed. | ||
* @param string $css_test_string The CSS to test. | ||
*/ | ||
function allow_grid_functions_in_styles( $allow_css, $css_test_string ) { | ||
if ( preg_match( | ||
'/^grid-template-columns:\s*repeat\([0-9,a-z-\s\(\)]*\)$/', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks good to me for now, but just wondering what the fix in core will be for it? I suppose we don't need to figure that out right now, but I was curious if it'll just be a matter of adding There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Judging by the previous update it should be pretty straightforward. |
||
$css_test_string | ||
) ) { | ||
return true; | ||
} | ||
return $allow_css; | ||
} | ||
add_filter( 'safecss_filter_attr_allow_css', 'allow_grid_functions_in_styles', 10, 2 ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -352,6 +352,28 @@ | |
} | ||
} | ||
] | ||
}, | ||
"grid": { | ||
"name": "grid", | ||
"slug": "grid", | ||
"className": "is-layout-grid", | ||
"displayMode": "grid", | ||
"baseStyles": [ | ||
{ | ||
"selector": " > *", | ||
"rules": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems there are more nuances about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I added that margin override due to this comment pointing out that default block margins can interfere with the grid gap. It's the same we have for flex blocks; do you think this might be more of an issue with grid than with flex layouts? There's also a whole discussion around margins set in global styles being overridden by layout rules, that we're trying to address in #47858. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Probably the same :). Thanks for linking the discussions/issues. |
||
"margin": "0" | ||
} | ||
} | ||
], | ||
"spacingStyles": [ | ||
{ | ||
"selector": "", | ||
"rules": { | ||
"gap": null | ||
} | ||
} | ||
] | ||
} | ||
} | ||
}, | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,172 @@ | ||||||||||||||||
/** | ||||||||||||||||
* WordPress dependencies | ||||||||||||||||
*/ | ||||||||||||||||
import { __ } from '@wordpress/i18n'; | ||||||||||||||||
|
||||||||||||||||
import { | ||||||||||||||||
BaseControl, | ||||||||||||||||
Flex, | ||||||||||||||||
FlexItem, | ||||||||||||||||
RangeControl, | ||||||||||||||||
__experimentalUnitControl as UnitControl, | ||||||||||||||||
__experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue, | ||||||||||||||||
} from '@wordpress/components'; | ||||||||||||||||
|
||||||||||||||||
/** | ||||||||||||||||
* Internal dependencies | ||||||||||||||||
*/ | ||||||||||||||||
import { appendSelectors, getBlockGapCSS } from './utils'; | ||||||||||||||||
import { getGapCSSValue } from '../hooks/gap'; | ||||||||||||||||
import { shouldSkipSerialization } from '../hooks/utils'; | ||||||||||||||||
|
||||||||||||||||
const RANGE_CONTROL_MAX_VALUES = { | ||||||||||||||||
px: 600, | ||||||||||||||||
'%': 100, | ||||||||||||||||
vw: 100, | ||||||||||||||||
vh: 100, | ||||||||||||||||
em: 38, | ||||||||||||||||
rem: 38, | ||||||||||||||||
}; | ||||||||||||||||
|
||||||||||||||||
export default { | ||||||||||||||||
name: 'grid', | ||||||||||||||||
label: __( 'Grid' ), | ||||||||||||||||
inspectorControls: function GridLayoutInspectorControls( { | ||||||||||||||||
layout = {}, | ||||||||||||||||
onChange, | ||||||||||||||||
} ) { | ||||||||||||||||
return ( | ||||||||||||||||
<GridLayoutMinimumWidthControl | ||||||||||||||||
layout={ layout } | ||||||||||||||||
onChange={ onChange } | ||||||||||||||||
/> | ||||||||||||||||
); | ||||||||||||||||
}, | ||||||||||||||||
toolBarControls: function DefaultLayoutToolbarControls() { | ||||||||||||||||
return null; | ||||||||||||||||
}, | ||||||||||||||||
getLayoutStyle: function getLayoutStyle( { | ||||||||||||||||
selector, | ||||||||||||||||
layout, | ||||||||||||||||
style, | ||||||||||||||||
blockName, | ||||||||||||||||
hasBlockGapSupport, | ||||||||||||||||
layoutDefinitions, | ||||||||||||||||
} ) { | ||||||||||||||||
const { minimumColumnWidth = '12rem' } = 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; | ||||||||||||||||
|
||||||||||||||||
let output = ''; | ||||||||||||||||
const rules = []; | ||||||||||||||||
|
||||||||||||||||
if ( minimumColumnWidth ) { | ||||||||||||||||
rules.push( | ||||||||||||||||
`grid-template-columns: repeat(auto-fill, minmax(min(${ minimumColumnWidth }, 100%), 1fr))` | ||||||||||||||||
); | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
if ( rules.length ) { | ||||||||||||||||
// Reason to disable: the extra line breaks added by prettier mess with the unit tests. | ||||||||||||||||
// eslint-disable-next-line prettier/prettier | ||||||||||||||||
output = `${ appendSelectors( selector ) } { ${ rules.join( | ||||||||||||||||
'; ' | ||||||||||||||||
) }; }`; | ||||||||||||||||
Comment on lines
+76
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another option (totally feel free to ignore this!), if we don't want to disable prettier on this line could be to perform the output over two lines. So, something like:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't want to dig too deeply into it in this PR, but it feels a bit absurd to have prettier formatting multi-line strings at all, because sometimes the spacing is there for a specific reason. Something to look at later perhaps? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Totally, and not a blocker at all. This was just in case you didn't want the extra disable in there. I doubt we can really configure prettier here, though, as far as I know there's not a lot of configurability. |
||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
// Output blockGap styles based on rules contained in layout definitions in theme.json. | ||||||||||||||||
if ( hasBlockGapSupport && blockGapValue ) { | ||||||||||||||||
output += getBlockGapCSS( | ||||||||||||||||
selector, | ||||||||||||||||
layoutDefinitions, | ||||||||||||||||
'grid', | ||||||||||||||||
blockGapValue | ||||||||||||||||
); | ||||||||||||||||
} | ||||||||||||||||
return output; | ||||||||||||||||
}, | ||||||||||||||||
getOrientation() { | ||||||||||||||||
return 'horizontal'; | ||||||||||||||||
}, | ||||||||||||||||
getAlignments() { | ||||||||||||||||
return []; | ||||||||||||||||
}, | ||||||||||||||||
}; | ||||||||||||||||
|
||||||||||||||||
// Enables setting minimum width of grid items. | ||||||||||||||||
function GridLayoutMinimumWidthControl( { layout, onChange } ) { | ||||||||||||||||
const { minimumColumnWidth: value = '12rem' } = layout; | ||||||||||||||||
const [ quantity, unit ] = parseQuantityAndUnitFromRawValue( value ); | ||||||||||||||||
|
||||||||||||||||
const handleSliderChange = ( next ) => { | ||||||||||||||||
onChange( { | ||||||||||||||||
...layout, | ||||||||||||||||
minimumColumnWidth: [ next, unit ].join( '' ), | ||||||||||||||||
} ); | ||||||||||||||||
}; | ||||||||||||||||
|
||||||||||||||||
// Mostly copied from HeightControl. | ||||||||||||||||
const handleUnitChange = ( newUnit ) => { | ||||||||||||||||
// Attempt to smooth over differences between currentUnit and newUnit. | ||||||||||||||||
// This should slightly improve the experience of switching between unit types. | ||||||||||||||||
let newValue; | ||||||||||||||||
|
||||||||||||||||
if ( [ 'em', 'rem' ].includes( newUnit ) && unit === 'px' ) { | ||||||||||||||||
// Convert pixel value to an approximate of the new unit, assuming a root size of 16px. | ||||||||||||||||
newValue = ( quantity / 16 ).toFixed( 2 ) + newUnit; | ||||||||||||||||
} else if ( [ 'em', 'rem' ].includes( unit ) && newUnit === 'px' ) { | ||||||||||||||||
// Convert to pixel value assuming a root size of 16px. | ||||||||||||||||
newValue = Math.round( quantity * 16 ) + newUnit; | ||||||||||||||||
} else if ( | ||||||||||||||||
[ 'vh', 'vw', '%' ].includes( newUnit ) && | ||||||||||||||||
quantity > 100 | ||||||||||||||||
) { | ||||||||||||||||
// When converting to `vh`, `vw`, or `%` units, cap the new value at 100. | ||||||||||||||||
newValue = 100 + newUnit; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
onChange( { | ||||||||||||||||
...layout, | ||||||||||||||||
minimumColumnWidth: newValue, | ||||||||||||||||
} ); | ||||||||||||||||
}; | ||||||||||||||||
|
||||||||||||||||
return ( | ||||||||||||||||
<fieldset> | ||||||||||||||||
<BaseControl.VisualLabel as="legend"> | ||||||||||||||||
{ __( 'Minimum column width' ) } | ||||||||||||||||
</BaseControl.VisualLabel> | ||||||||||||||||
<Flex gap={ 4 }> | ||||||||||||||||
<FlexItem isBlock> | ||||||||||||||||
<UnitControl | ||||||||||||||||
size={ '__unstable-large' } | ||||||||||||||||
onChange={ ( newValue ) => { | ||||||||||||||||
onChange( { | ||||||||||||||||
...layout, | ||||||||||||||||
minimumColumnWidth: newValue, | ||||||||||||||||
} ); | ||||||||||||||||
} } | ||||||||||||||||
onUnitChange={ handleUnitChange } | ||||||||||||||||
value={ value } | ||||||||||||||||
min={ 0 } | ||||||||||||||||
/> | ||||||||||||||||
</FlexItem> | ||||||||||||||||
<FlexItem isBlock> | ||||||||||||||||
<RangeControl | ||||||||||||||||
andrewserong marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
onChange={ handleSliderChange } | ||||||||||||||||
value={ quantity } | ||||||||||||||||
min={ 0 } | ||||||||||||||||
max={ RANGE_CONTROL_MAX_VALUES[ unit ] || 600 } | ||||||||||||||||
withInputField={ false } | ||||||||||||||||
/> | ||||||||||||||||
</FlexItem> | ||||||||||||||||
</Flex> | ||||||||||||||||
</fieldset> | ||||||||||||||||
); | ||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import grid from '../grid'; | ||
|
||
describe( 'getLayoutStyle', () => { | ||
it( 'should return a single `grid-template-columns` property if no non-default params are provided', () => { | ||
const expected = `.editor-styles-wrapper .my-container { grid-template-columns: repeat(auto-fill, minmax(min(12rem, 100%), 1fr)); }`; | ||
|
||
const result = grid.getLayoutStyle( { | ||
selector: '.my-container', | ||
layout: {}, | ||
style: {}, | ||
blockName: 'test-block', | ||
hasBlockGapSupport: false, | ||
layoutDefinitions: undefined, | ||
} ); | ||
|
||
expect( result ).toBe( expected ); | ||
} ); | ||
} ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,6 +47,17 @@ const getGroupPlaceholderIcons = ( name = 'group' ) => { | |
<Path d="M42 0H2C.9 0 0 .9 0 2v12.5c0 .6.4 1 1 1h42c.6 0 1-.4 1-1V2c0-1.1-.9-2-2-2zm1 16.5H1c-.6 0-1 .4-1 1V30c0 1.1.9 2 2 2h40c1.1 0 2-.9 2-2V17.5c0-.6-.4-1-1-1z" /> | ||
</SVG> | ||
), | ||
'group-grid': ( | ||
<SVG | ||
xmlns="http://www.w3.org/2000/svg" | ||
width="44" | ||
height="32" | ||
viewBox="0 0 44 32" | ||
> | ||
<Path d="m20.30137,-0.00025l-18.9728,0c-0.86524,0.07234 -1.41711,0.79149 -1.41711,1.89149l0,12.64468c0,0.6 0.73401,0.96383 1.0304,0.96383l19.67469,0.03617c0.29639,0 1.0304,-0.4 1.0304,-1l-0.03576,-12.7532c0,-1.1 -0.76644,-1.78297 -1.30983,-1.78297zm0.52975,16.60851l-19.99654,-0.03617c-0.29639,0 -0.92312,0.36383 -0.92312,0.96383l-0.03576,12.68085c0,1.1 0.8022,1.81915 1.34559,1.81915l19.00857,0c0.54339,0 1.45287,-0.71915 1.45287,-1.81915l0,-12.53617c0,-0.6 -0.5552,-1.07234 -0.8516,-1.07234z" /> | ||
<Path d="m42.73056,-0.03617l-18.59217,0c-0.84788,0.07234 -1.38868,0.79149 -1.38868,1.89149l0,12.64468c0,0.6 0.71928,0.96383 1.00973,0.96383l19.27997,0.03617c0.29045,0 1.00973,-0.4 1.00973,-1l-0.03504,-12.7532c0,-1.1 -0.75106,-1.78297 -1.28355,-1.78297zm0.51912,16.60851l-19.59537,-0.03617c-0.29045,0 -0.9046,0.36383 -0.9046,0.96383l-0.03504,12.68085c0,1.1 0.78611,1.81915 1.31859,1.81915l18.62721,0c0.53249,0 1.42372,-0.71915 1.42372,-1.81915l0,-12.53617c0,-0.6 -0.54407,-1.07234 -0.83451,-1.07234z" /> | ||
</SVG> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I improvised a grid icon here but we'll probably want a proper one at some point 😄 |
||
), | ||
}; | ||
return icons?.[ name ]; | ||
}; | ||
|
@@ -85,7 +96,8 @@ export function useShouldShowPlaceHolder( { | |
! fontSize && | ||
! textColor && | ||
! style && | ||
usedLayoutType !== 'flex' | ||
usedLayoutType !== 'flex' && | ||
usedLayoutType !== 'grid' | ||
); | ||
|
||
useEffect( () => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a future proofing question: if
minimumColumnWidth
isn't set, this assumes a default of12rem
. If we want to enable the control over individual columns again in follow-ups, how will we tell that we shouldn't be outputting12rem
?By which I mean, what are the trade-offs between using
12rem
as an inferred value, versus explicitly defining12rem
within the Grid variation? For example, what if other blocks wished to use a different default minimum column width?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be pretty easy to introduce something like
isResponsive
again, or any other property that indicates that the grid type is not based on column width (which would always be the default). We'll need a check before outputting the value because the whole template-columns declaration will be different for a static grid, and even if aminimumColumnWidth
is set (e.g. if we don't reset it on switching to another grid type) we will only use the value if the grid is of the responsive type (or whatever we want to call it).I chose 12rem arbitrarily, it could be any other value but we do need to have some default value here or the
auto-fill
won't work.If other blocks want to use a different value they can set it when they define the layout type, e.g.
layout: { type: 'grid', minimumColumnWidth: '9rem' }
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So (just to make sure I'm following), we're essentially saying, a grid layout with absolutely no other values will default to
12rem
minimum column width. For future changes, we're adding additional attributes that will be set, so it'll be easy to determine when to switch off the current logic. And for changing the12rem
default on a block-by-block basis, we provide the default type type value as you mention.Sounds solid to me! 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the idea is to have a sensible default so you can just add a grid and it'll look ok for the majority of cases? Then progressively add configurability.