From 43ac2af5c8655b885147485c919d012f789f874c Mon Sep 17 00:00:00 2001 From: Vicente Canales <1157901+vcanales@users.noreply.github.com> Date: Tue, 23 Jan 2024 14:38:47 -0500 Subject: [PATCH] Add effects/box shadow tools to block inspector (#57654) * wip * wip * Add effects panel to block inspector @todo: for some reason, the Effects Panel is rendering, but the Shadow controls are not. * add shadow classes for block settings in the editor * add shadow attribute to template from the UI * fix typo * define support key in the supports hook * remove internal dependencies block * remove effects panel from multi-selection inspector * read/write shadow from style attribute * make sure path and value computed correctly * fix shadow being applied to block stead of inner element for button * revert generation of class names for shadows * revert support for shadow atribute. * don't use useMemo * skip serialization for shadow when the support is enabled * add entry to components changelog --------- Co-authored-by: madhusudhand --- lib/block-supports/shadow.php | 5 +- .../src/components/block-inspector/index.js | 4 ++ .../components/global-styles/effects-panel.js | 17 +++++- .../inspector-controls-tabs/styles-tab.js | 1 + .../use-inspector-controls-tabs.js | 2 + .../components/inspector-controls/groups.js | 2 + packages/block-editor/src/hooks/effects.js | 59 +++++++++++++++++++ packages/block-editor/src/hooks/index.js | 1 + .../block-editor/src/hooks/index.native.js | 1 + packages/block-editor/src/hooks/style.js | 8 +++ packages/block-editor/src/hooks/supports.js | 2 + .../block-editor/src/hooks/test/effects.js | 39 ++++++++++++ .../src/hooks/use-shadow-props.js | 37 ++++++++++++ packages/block-editor/src/hooks/utils.js | 6 +- packages/block-editor/src/index.js | 2 + packages/block-library/src/button/edit.js | 3 + packages/block-library/src/button/save.js | 3 + packages/components/CHANGELOG.md | 1 + .../bubbles-virtually/use-slot-fills.ts | 2 +- schemas/json/block.json | 5 ++ 20 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 packages/block-editor/src/hooks/effects.js create mode 100644 packages/block-editor/src/hooks/test/effects.js create mode 100644 packages/block-editor/src/hooks/use-shadow-props.js diff --git a/lib/block-supports/shadow.php b/lib/block-supports/shadow.php index 4a28c98b7932..87258930faf1 100644 --- a/lib/block-supports/shadow.php +++ b/lib/block-supports/shadow.php @@ -53,9 +53,8 @@ function gutenberg_apply_shadow_support( $block_type, $block_attributes ) { $shadow_block_styles = array(); - $preset_shadow = array_key_exists( 'shadow', $block_attributes ) ? "var:preset|shadow|{$block_attributes['shadow']}" : null; - $custom_shadow = isset( $block_attributes['style']['shadow'] ) ? $block_attributes['style']['shadow'] : null; - $shadow_block_styles['shadow'] = $preset_shadow ? $preset_shadow : $custom_shadow; + $custom_shadow = $block_attributes['style']['shadow'] ?? null; + $shadow_block_styles['shadow'] = $custom_shadow; $attributes = array(); $styles = gutenberg_style_engine_get_styles( $shadow_block_styles ); diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index 9cb3b89a7bc2..afdda89b6739 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -307,6 +307,10 @@ const BlockInspectorSingleBlock = ( { clientId, blockName } ) => { label={ __( 'Background' ) } /> +
diff --git a/packages/block-editor/src/components/global-styles/effects-panel.js b/packages/block-editor/src/components/global-styles/effects-panel.js index 9a9fd8d1258e..94c1d119c354 100644 --- a/packages/block-editor/src/components/global-styles/effects-panel.js +++ b/packages/block-editor/src/components/global-styles/effects-panel.js @@ -26,6 +26,7 @@ import { shadow as shadowIcon, Icon, check } from '@wordpress/icons'; /** * Internal dependencies */ +import { mergeOrigins } from '../use-settings'; import { getValueFromVariable, TOOLSPANEL_DROPDOWNMENU_PROPS } from './utils'; import { setImmutably } from '../../utils/object'; @@ -81,8 +82,22 @@ export default function EffectsPanel( { // Shadow const hasShadowEnabled = useHasShadowControl( settings ); const shadow = decodeValue( inheritedValue?.shadow ); + const shadowPresets = settings?.shadow?.presets; + const mergedShadowPresets = shadowPresets + ? mergeOrigins( shadowPresets ) + : []; const setShadow = ( newValue ) => { - onChange( setImmutably( value, [ 'shadow' ], newValue ) ); + const slug = mergedShadowPresets?.find( + ( { shadow: shadowName } ) => shadowName === newValue + )?.slug; + + onChange( + setImmutably( + value, + [ 'shadow' ], + slug ? `var:preset|shadow|${ slug }` : newValue || undefined + ) + ); }; const hasShadow = () => !! value?.shadow; const resetShadow = () => setShadow( undefined ); diff --git a/packages/block-editor/src/components/inspector-controls-tabs/styles-tab.js b/packages/block-editor/src/components/inspector-controls-tabs/styles-tab.js index d42b3bffce4e..6f24051ea2cf 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/styles-tab.js +++ b/packages/block-editor/src/components/inspector-controls-tabs/styles-tab.js @@ -46,6 +46,7 @@ const StylesTab = ( { blockName, clientId, hasBlockStyles } ) => { label={ __( 'Dimensions' ) } /> + ); diff --git a/packages/block-editor/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js b/packages/block-editor/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js index 2a47ae5267ca..ff68be82a829 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js +++ b/packages/block-editor/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js @@ -40,6 +40,7 @@ export default function useInspectorControlsTabs( blockName ) { position: positionGroup, styles: stylesGroup, typography: typographyGroup, + effects: effectsGroup, } = InspectorControlsGroups; // List View Tab: If there are any fills for the list group add that tab. @@ -55,6 +56,7 @@ export default function useInspectorControlsTabs( blockName ) { ...( useSlotFills( dimensionsGroup.Slot.__unstableName ) || [] ), ...( useSlotFills( stylesGroup.Slot.__unstableName ) || [] ), ...( useSlotFills( typographyGroup.Slot.__unstableName ) || [] ), + ...( useSlotFills( effectsGroup.Slot.__unstableName ) || [] ), ]; const hasStyleFills = styleFills.length; diff --git a/packages/block-editor/src/components/inspector-controls/groups.js b/packages/block-editor/src/components/inspector-controls/groups.js index b4eada4b6b4b..9ca1a72b9918 100644 --- a/packages/block-editor/src/components/inspector-controls/groups.js +++ b/packages/block-editor/src/components/inspector-controls/groups.js @@ -20,6 +20,7 @@ const InspectorControlsTypography = createSlotFill( ); const InspectorControlsListView = createSlotFill( 'InspectorControlsListView' ); const InspectorControlsStyles = createSlotFill( 'InspectorControlsStyles' ); +const InspectorControlsEffects = createSlotFill( 'InspectorControlsEffects' ); const groups = { default: InspectorControlsDefault, @@ -28,6 +29,7 @@ const groups = { border: InspectorControlsBorder, color: InspectorControlsColor, dimensions: InspectorControlsDimensions, + effects: InspectorControlsEffects, filter: InspectorControlsFilter, list: InspectorControlsListView, position: InspectorControlsPosition, diff --git a/packages/block-editor/src/hooks/effects.js b/packages/block-editor/src/hooks/effects.js new file mode 100644 index 000000000000..92d9a2310f1c --- /dev/null +++ b/packages/block-editor/src/hooks/effects.js @@ -0,0 +1,59 @@ +/** + * WordPress dependencies + */ +import { hasBlockSupport } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; +/** + * Internal dependencies + */ +import StylesEffectsPanel, { + useHasEffectsPanel, +} from '../components/global-styles/effects-panel'; +import { InspectorControls } from '../components'; +import { store as blockEditorStore } from '../store'; + +export const SHADOW_SUPPORT_KEY = 'shadow'; +export const EFFECTS_SUPPORT_KEYS = [ SHADOW_SUPPORT_KEY ]; + +export function hasEffectsSupport( blockName ) { + return EFFECTS_SUPPORT_KEYS.some( ( key ) => + hasBlockSupport( blockName, key ) + ); +} + +function EffectsInspectorControl( { children, resetAllFilter } ) { + return ( + + { children } + + ); +} +export function EffectsPanel( { clientId, setAttributes, settings } ) { + const isEnabled = useHasEffectsPanel( settings ); + const blockAttributes = useSelect( + ( select ) => select( blockEditorStore ).getBlockAttributes( clientId ), + [ clientId ] + ); + const shadow = blockAttributes?.style?.shadow; + const value = { shadow }; + + const onChange = ( newValue ) => { + setAttributes( { + style: { ...blockAttributes.style, shadow: newValue.shadow }, + } ); + }; + + if ( ! isEnabled ) { + return null; + } + + return ( + + ); +} diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index f17c0a22166e..cb0ca4e2ff3e 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -68,6 +68,7 @@ createBlockSaveFilter( [ export { useCustomSides } from './dimensions'; export { useLayoutClasses, useLayoutStyles } from './layout'; export { getBorderClassesAndStyles, useBorderProps } from './use-border-props'; +export { getShadowClassesAndStyles, useShadowProps } from './use-shadow-props'; export { getColorClassesAndStyles, useColorProps } from './use-color-props'; export { getSpacingClassesAndStyles } from './use-spacing-props'; export { getTypographyClassesAndStyles } from './use-typography-props'; diff --git a/packages/block-editor/src/hooks/index.native.js b/packages/block-editor/src/hooks/index.native.js index 55ae7e19df70..6e4c1c6a17ba 100644 --- a/packages/block-editor/src/hooks/index.native.js +++ b/packages/block-editor/src/hooks/index.native.js @@ -28,6 +28,7 @@ createBlockSaveFilter( [ ] ); export { getBorderClassesAndStyles, useBorderProps } from './use-border-props'; +export { getShadowClassesAndStyles, useShadowProps } from './use-shadow-props'; export { getColorClassesAndStyles, useColorProps } from './use-color-props'; export { getSpacingClassesAndStyles } from './use-spacing-props'; export { useCachedTruthy } from './use-cached-truthy'; diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index 7221de63456c..dec046f888a4 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -27,6 +27,11 @@ import { SPACING_SUPPORT_KEY, DimensionsPanel, } from './dimensions'; +import { + EFFECTS_SUPPORT_KEYS, + SHADOW_SUPPORT_KEY, + EffectsPanel, +} from './effects'; import { shouldSkipSerialization, useStyleOverride, @@ -37,6 +42,7 @@ import { useBlockEditingMode } from '../components/block-editing-mode'; const styleSupportKeys = [ ...TYPOGRAPHY_SUPPORT_KEYS, + ...EFFECTS_SUPPORT_KEYS, BORDER_SUPPORT_KEY, COLOR_SUPPORT_KEY, DIMENSIONS_SUPPORT_KEY, @@ -110,6 +116,7 @@ const skipSerializationPathsEdit = { [ `${ SPACING_SUPPORT_KEY }.__experimentalSkipSerialization` ]: [ SPACING_SUPPORT_KEY, ], + [ `${ SHADOW_SUPPORT_KEY }` ]: [ SHADOW_SUPPORT_KEY ], }; /** @@ -336,6 +343,7 @@ function BlockStyleControls( { + ); } diff --git a/packages/block-editor/src/hooks/supports.js b/packages/block-editor/src/hooks/supports.js index 2cf08d46fa8f..4e116494029b 100644 --- a/packages/block-editor/src/hooks/supports.js +++ b/packages/block-editor/src/hooks/supports.js @@ -59,8 +59,10 @@ const TYPOGRAPHY_SUPPORT_KEYS = [ WRITING_MODE_SUPPORT_KEY, LETTER_SPACING_SUPPORT_KEY, ]; +const EFFECTS_SUPPORT_KEYS = [ 'shadow' ]; const SPACING_SUPPORT_KEY = 'spacing'; const styleSupportKeys = [ + ...EFFECTS_SUPPORT_KEYS, ...TYPOGRAPHY_SUPPORT_KEYS, BORDER_SUPPORT_KEY, COLOR_SUPPORT_KEY, diff --git a/packages/block-editor/src/hooks/test/effects.js b/packages/block-editor/src/hooks/test/effects.js new file mode 100644 index 000000000000..b4fe61745744 --- /dev/null +++ b/packages/block-editor/src/hooks/test/effects.js @@ -0,0 +1,39 @@ +/** + * Internal dependencies + */ +import { hasEffectsSupport } from '../effects'; + +describe( 'effects', () => { + describe( 'hasEffectsSupport', () => { + it( 'should return false if the block does not support effects', () => { + const settings = { + supports: { + shadow: false, + }, + }; + + expect( hasEffectsSupport( settings ) ).toBe( false ); + } ); + + it( 'should return true if the block supports effects', () => { + const settings = { + supports: { + shadow: true, + }, + }; + + expect( hasEffectsSupport( settings ) ).toBe( true ); + } ); + + it( 'should return true if the block supports effects and other features', () => { + const settings = { + supports: { + shadow: true, + align: true, + }, + }; + + expect( hasEffectsSupport( settings ) ).toBe( true ); + } ); + } ); +} ); diff --git a/packages/block-editor/src/hooks/use-shadow-props.js b/packages/block-editor/src/hooks/use-shadow-props.js new file mode 100644 index 000000000000..fdc601366245 --- /dev/null +++ b/packages/block-editor/src/hooks/use-shadow-props.js @@ -0,0 +1,37 @@ +/** + * Internal dependencies + */ +import { getInlineStyles } from './style'; + +// This utility is intended to assist where the serialization of the shadow +// block support is being skipped for a block but the shadow related CSS classes +// & styles still need to be generated so they can be applied to inner elements. + +/** + * Provides the CSS class names and inline styles for a block's shadow support + * attributes. + * + * @param {Object} attributes Block attributes. + * @return {Object} Shadow block support derived CSS classes & styles. + */ +export function getShadowClassesAndStyles( attributes ) { + const shadow = attributes.style?.shadow || ''; + + return { + className: undefined, + style: getInlineStyles( { shadow } ), + }; +} + +/** + * Derives the shadow related props for a block from its shadow block support + * attributes. + * + * @param {Object} attributes Block attributes. + * + * @return {Object} ClassName & style props from shadow block support. + */ +export function useShadowProps( attributes ) { + const shadowProps = getShadowClassesAndStyles( attributes ); + return shadowProps; +} diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index e63029e4e34e..ea31a516ac63 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -221,6 +221,7 @@ export function useBlockSettings( name, parentLayout ) { isTextEnabled, isHeadingEnabled, isButtonEnabled, + shadow, ] = useSettings( 'background.backgroundImage', 'background.backgroundSize', @@ -268,7 +269,8 @@ export function useBlockSettings( name, parentLayout ) { 'color.link', 'color.text', 'color.heading', - 'color.button' + 'color.button', + 'shadow' ); const rawSettings = useMemo( () => { @@ -345,6 +347,7 @@ export function useBlockSettings( name, parentLayout ) { }, layout, parentLayout, + shadow, }; }, [ backgroundImage, @@ -395,6 +398,7 @@ export function useBlockSettings( name, parentLayout ) { isTextEnabled, isHeadingEnabled, isButtonEnabled, + shadow, ] ); return useSettingsForBlockElement( rawSettings, name ); diff --git a/packages/block-editor/src/index.js b/packages/block-editor/src/index.js index 1dbc4501e921..83475b935872 100644 --- a/packages/block-editor/src/index.js +++ b/packages/block-editor/src/index.js @@ -11,6 +11,8 @@ export { useCustomSides as __experimentalUseCustomSides, getSpacingClassesAndStyles as __experimentalGetSpacingClassesAndStyles, getGapCSSValue as __experimentalGetGapCSSValue, + getShadowClassesAndStyles as __experimentalGetShadowClassesAndStyles, + useShadowProps as __experimentalUseShadowProps, useCachedTruthy, } from './hooks'; export * from './components'; diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index b46e145d760a..a0994ce3f84b 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -32,6 +32,7 @@ import { __experimentalUseBorderProps as useBorderProps, __experimentalUseColorProps as useColorProps, __experimentalGetSpacingClassesAndStyles as useSpacingProps, + __experimentalUseShadowProps as useShadowProps, __experimentalLinkControl as LinkControl, __experimentalGetElementClassName, store as blockEditorStore, @@ -184,6 +185,7 @@ function ButtonEdit( props ) { const borderProps = useBorderProps( attributes ); const colorProps = useColorProps( attributes ); const spacingProps = useSpacingProps( attributes ); + const shadowProps = useShadowProps( attributes ); const ref = useRef(); const richTextRef = useRef(); const blockProps = useBlockProps( { @@ -266,6 +268,7 @@ function ButtonEdit( props ) { ...borderProps.style, ...colorProps.style, ...spacingProps.style, + ...shadowProps.style, } } onSplit={ ( value ) => createBlock( 'core/button', { diff --git a/packages/block-library/src/button/save.js b/packages/block-library/src/button/save.js index e12936e8c924..ba0fbd45f083 100644 --- a/packages/block-library/src/button/save.js +++ b/packages/block-library/src/button/save.js @@ -12,6 +12,7 @@ import { __experimentalGetBorderClassesAndStyles as getBorderClassesAndStyles, __experimentalGetColorClassesAndStyles as getColorClassesAndStyles, __experimentalGetSpacingClassesAndStyles as getSpacingClassesAndStyles, + __experimentalGetShadowClassesAndStyles as getShadowClassesAndStyles, __experimentalGetElementClassName, } from '@wordpress/block-editor'; @@ -40,6 +41,7 @@ export default function save( { attributes, className } ) { const borderProps = getBorderClassesAndStyles( attributes ); const colorProps = getColorClassesAndStyles( attributes ); const spacingProps = getSpacingClassesAndStyles( attributes ); + const shadowProps = getShadowClassesAndStyles( attributes ); const buttonClasses = classnames( 'wp-block-button__link', colorProps.className, @@ -56,6 +58,7 @@ export default function save( { attributes, className } ) { ...borderProps.style, ...colorProps.style, ...spacingProps.style, + ...shadowProps.style, }; // The use of a `title` attribute here is soft-deprecated, but still applied diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index e546707a30f5..e732cbda994a 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -10,6 +10,7 @@ - `PaletteEdit` and `CircularOptionPicker`: improve unit tests ([#57809](https://github.com/WordPress/gutenberg/pull/57809)). - `Tooltip`: no-op when nested inside other `Tooltip` components ([#57202](https://github.com/WordPress/gutenberg/pull/57202)). - `Tooltip` and `Button`: tidy up unit tests ([#57975](https://github.com/WordPress/gutenberg/pull/57975)). +- `SlotFill`: fix typo in use-slot-fills return docs ([#57654](https://github.com/WordPress/gutenberg/pull/57654)) ### Bug Fix diff --git a/packages/components/src/slot-fill/bubbles-virtually/use-slot-fills.ts b/packages/components/src/slot-fill/bubbles-virtually/use-slot-fills.ts index 599f0bc56677..0131bbfc434e 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/use-slot-fills.ts +++ b/packages/components/src/slot-fill/bubbles-virtually/use-slot-fills.ts @@ -19,6 +19,6 @@ export default function useSlotFills( name: SlotKey ) { const fills = useSnapshot( registry.fills, { sync: true } ); // The important bit here is that this call ensures that the hook // only causes a re-render if the "fills" of a given slot name - // change change, not any fills. + // change, not any fills. return fills.get( name ); } diff --git a/schemas/json/block.json b/schemas/json/block.json index fd69ea1badb3..9cc7a83c2304 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -543,6 +543,11 @@ } } }, + "shadow": { + "type": "boolean", + "description": "Allow blocks to define a box shadow.", + "default": false + }, "typography": { "type": "object", "description": "This value signals that a block supports some of the CSS style properties related to typography. When it does, the block editor will show UI controls for the user to set their values if the theme declares support.\n\nWhen the block declares support for a specific typography property, its attributes definition is extended to include the style attribute.",