diff --git a/packages/block-editor/src/components/inspector-controls/block-support-slot-container.js b/packages/block-editor/src/components/inspector-controls/block-support-slot-container.js index fb53e47afbd82..be2f01b5ed168 100644 --- a/packages/block-editor/src/components/inspector-controls/block-support-slot-container.js +++ b/packages/block-editor/src/components/inspector-controls/block-support-slot-container.js @@ -2,11 +2,27 @@ * WordPress dependencies */ import { __experimentalToolsPanelContext as ToolsPanelContext } from '@wordpress/components'; -import { useContext } from '@wordpress/element'; +import { useContext, useMemo } from '@wordpress/element'; -export default function BlockSupportSlotContainer( { Slot, ...props } ) { +export default function BlockSupportSlotContainer( { + Slot, + fillProps, + ...props +} ) { + // Add the toolspanel context provider and value to existing fill props const toolsPanelContext = useContext( ToolsPanelContext ); + const computedFillProps = useMemo( + () => ( { + ...( fillProps ?? {} ), + forwardedContext: [ + ...( fillProps?.forwardedContext ?? [] ), + [ ToolsPanelContext.Provider, { value: toolsPanelContext } ], + ], + } ), + [ toolsPanelContext, fillProps ] + ); + return ( - + ); } diff --git a/packages/block-editor/src/components/inspector-controls/fill.js b/packages/block-editor/src/components/inspector-controls/fill.js index 2db809a46b21e..f0640a9d31ddc 100644 --- a/packages/block-editor/src/components/inspector-controls/fill.js +++ b/packages/block-editor/src/components/inspector-controls/fill.js @@ -7,7 +7,7 @@ import { } from '@wordpress/components'; import warning from '@wordpress/warning'; import deprecated from '@wordpress/deprecated'; -import { useEffect } from '@wordpress/element'; +import { useEffect, useContext } from '@wordpress/element'; /** * Internal dependencies @@ -60,28 +60,42 @@ export default function InspectorControlsFill( { ); } -function ToolsPanelInspectorControl( { children, resetAllFilter, fillProps } ) { - const { registerResetAllFilter, deregisterResetAllFilter } = fillProps; +function RegisterResetAll( { resetAllFilter, children } ) { + const { registerResetAllFilter, deregisterResetAllFilter } = + useContext( ToolsPanelContext ); useEffect( () => { - if ( resetAllFilter && registerResetAllFilter ) { + if ( + resetAllFilter && + registerResetAllFilter && + deregisterResetAllFilter + ) { registerResetAllFilter( resetAllFilter ); - } - return () => { - if ( resetAllFilter && deregisterResetAllFilter ) { + return () => { deregisterResetAllFilter( resetAllFilter ); - } - }; + }; + } }, [ resetAllFilter, registerResetAllFilter, deregisterResetAllFilter ] ); + return children; +} + +function ToolsPanelInspectorControl( { children, resetAllFilter, fillProps } ) { + // `fillProps.forwardedContext` is an array of context provider entries, provided by slot, + // that should wrap the fill markup. + const { forwardedContext = [] } = fillProps; // Children passed to InspectorControlsFill will not have // access to any React Context whose Provider is part of // the InspectorControlsSlot tree. So we re-create the // Provider in this subtree. - const value = - fillProps && Object.keys( fillProps ).length > 0 ? fillProps : null; - return ( - + const innerMarkup = ( + { children } - + + ); + return forwardedContext.reduce( + ( inner, [ Provider, props ] ) => ( + { inner } + ), + innerMarkup ); } diff --git a/packages/block-editor/src/components/inspector-controls/slot.js b/packages/block-editor/src/components/inspector-controls/slot.js index 3687644b21b4d..cc32b1c88480e 100644 --- a/packages/block-editor/src/components/inspector-controls/slot.js +++ b/packages/block-editor/src/components/inspector-controls/slot.js @@ -1,7 +1,11 @@ /** * WordPress dependencies */ -import { __experimentalUseSlotFills as useSlotFills } from '@wordpress/components'; +import { + __experimentalUseSlotFills as useSlotFills, + __unstableMotionContext as MotionContext, +} from '@wordpress/components'; +import { useContext, useMemo } from '@wordpress/element'; import warning from '@wordpress/warning'; import deprecated from '@wordpress/deprecated'; @@ -16,6 +20,7 @@ export default function InspectorControlsSlot( { __experimentalGroup, group = 'default', label, + fillProps, ...props } ) { if ( __experimentalGroup ) { @@ -31,6 +36,20 @@ export default function InspectorControlsSlot( { } const Slot = groups[ group ]?.Slot; const fills = useSlotFills( Slot?.__unstableName ); + + const motionContextValue = useContext( MotionContext ); + + const computedFillProps = useMemo( + () => ( { + ...( fillProps ?? {} ), + forwardedContext: [ + ...( fillProps?.forwardedContext ?? [] ), + [ MotionContext.Provider, { value: motionContextValue } ], + ], + } ), + [ motionContextValue, fillProps ] + ); + if ( ! Slot ) { warning( `Unknown InspectorControls group "${ group }" provided.` ); return null; @@ -43,10 +62,16 @@ export default function InspectorControlsSlot( { if ( label ) { return ( - + ); } - return ; + return ( + + ); } diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 362e2d26a3d7b..4a044d8e79ec8 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -9,6 +9,7 @@ ### Enhancements - Making Circular Option Picker a `listbox`. Note that while this changes some public API, new props are optional, and currently have default values; this will change in another patch ([#52255](https://github.com/WordPress/gutenberg/pull/52255)). +- `ToggleGroupControl`: Rewrite backdrop animation using framer motion shared layout animations, add better support for controlled and uncontrolled modes ([#50278](https://github.com/WordPress/gutenberg/pull/50278)). - `Popover`: Add the `is-positioned` CSS class only after the popover has finished animating ([#54178](https://github.com/WordPress/gutenberg/pull/54178)). ### Bug Fix diff --git a/packages/components/src/animation/index.tsx b/packages/components/src/animation/index.tsx index ceeb89da7f329..39507803d2f40 100644 --- a/packages/components/src/animation/index.tsx +++ b/packages/components/src/animation/index.tsx @@ -10,4 +10,5 @@ export { motion as __unstableMotion, AnimatePresence as __unstableAnimatePresence, + MotionContext as __unstableMotionContext, } from 'framer-motion'; diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index b17d82a81bee6..a824162cb2412 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -17,7 +17,11 @@ export { default as Animate, getAnimateClassName as __unstableGetAnimateClassName, } from './animate'; -export { __unstableMotion, __unstableAnimatePresence } from './animation'; +export { + __unstableMotion, + __unstableAnimatePresence, + __unstableMotionContext, +} from './animation'; export { default as AnglePickerControl } from './angle-picker-control'; export { default as Autocomplete, diff --git a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap index e5ea6c14f5ef6..296e9483b9f70 100644 --- a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ToggleGroupControl should render correctly with icons 1`] = ` +exports[`ToggleGroupControl controlled should render correctly with icons 1`] = ` .emotion-0 { font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',sans-serif; font-size: 13px; @@ -49,17 +49,332 @@ exports[`ToggleGroupControl should render correctly with icons 1`] = ` min-width: 0; padding: 2px; position: relative; - -webkit-transition: -webkit-transform 100ms linear; - transition: transform 100ms linear; min-height: 36px; } +.emotion-8:hover { + border-color: #757575; +} + +.emotion-8:focus-within { + border-color: var(--wp-components-color-accent-darker-10, var(--wp-admin-theme-color-darker-10, #2145e6)); + box-shadow: 0 0 0 0.5px var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9)); + z-index: 1; + outline: 2px solid transparent; + outline-offset: -2px; +} + +.emotion-10 { + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + max-width: 100%; + min-width: 0; + position: relative; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.emotion-12 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + appearance: none; + background: transparent; + border: none; + border-radius: 2px; + color: #757575; + fill: currentColor; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + font-family: inherit; + height: 100%; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; + line-height: 100%; + outline: none; + padding: 0 12px; + position: relative; + text-align: center; + -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; + transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + width: 100%; + z-index: 2; + color: #1e1e1e; + width: 30px; + padding-left: 0; + padding-right: 0; + color: #fff; +} + +@media ( prefers-reduced-motion: reduce ) { + .emotion-12 { + transition-duration: 0ms; + } +} + +.emotion-12::-moz-focus-inner { + border: 0; +} + +.emotion-12:active { + background: #fff; +} + +.emotion-12:active { + background: transparent; +} + +.emotion-13 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + font-size: 13px; + line-height: 1; +} + +.emotion-15 { + background: #1e1e1e; + border-radius: 2px; + position: absolute; + inset: 0; + z-index: 1; + outline: 2px solid transparent; + outline-offset: -3px; +} + +.emotion-18 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + appearance: none; + background: transparent; + border: none; + border-radius: 2px; + color: #757575; + fill: currentColor; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + font-family: inherit; + height: 100%; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; + line-height: 100%; + outline: none; + padding: 0 12px; + position: relative; + text-align: center; + -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; + transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + width: 100%; + z-index: 2; + color: #1e1e1e; + width: 30px; + padding-left: 0; + padding-right: 0; +} + @media ( prefers-reduced-motion: reduce ) { - .emotion-8 { + .emotion-18 { transition-duration: 0ms; } } +.emotion-18::-moz-focus-inner { + border: 0; +} + +.emotion-18:active { + background: #fff; +} + +
+
+
+
+ + Test Toggle Group Control + +
+
+
+ + +
+ +
+
+
+
+ +
+`; + +exports[`ToggleGroupControl controlled should render correctly with text options 1`] = ` +.emotion-0 { + font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',sans-serif; + font-size: 13px; + box-sizing: border-box; +} + +.emotion-0 *, +.emotion-0 *::before, +.emotion-0 *::after { + box-sizing: inherit; +} + +.emotion-2 { + margin-bottom: calc(4px * 2); +} + +.components-panel__row .emotion-2 { + margin-bottom: inherit; +} + +.emotion-4 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.emotion-6 { + font-size: 11px; + font-weight: 500; + line-height: 1.4; + text-transform: uppercase; + display: inline-block; + margin-bottom: calc(4px * 2); + padding: 0; +} + +.emotion-8 { + background: #fff; + border: 1px solid transparent; + border-radius: 2px; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + min-width: 0; + padding: 2px; + position: relative; + min-height: 36px; +} + .emotion-8:hover { border-color: #757575; } @@ -73,26 +388,224 @@ exports[`ToggleGroupControl should render correctly with icons 1`] = ` } .emotion-10 { - background: #1e1e1e; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + max-width: 100%; + min-width: 0; + position: relative; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.emotion-12 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + appearance: none; + background: transparent; + border: none; border-radius: 2px; - left: 0; - position: absolute; - top: 2px; - bottom: 2px; - -webkit-transition: -webkit-transform 160ms ease; - transition: transform 160ms ease; - z-index: 1; - outline: 2px solid transparent; - outline-offset: -3px; + color: #757575; + fill: currentColor; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + font-family: inherit; + height: 100%; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; + line-height: 100%; + outline: none; + padding: 0 12px; + position: relative; + text-align: center; + -webkit-transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; + transition: background 160ms linear,color 160ms linear,font-weight 60ms linear; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + width: 100%; + z-index: 2; } @media ( prefers-reduced-motion: reduce ) { - .emotion-10 { + .emotion-12 { transition-duration: 0ms; } } -.emotion-12 { +.emotion-12::-moz-focus-inner { + border: 0; +} + +.emotion-12:active { + background: #fff; +} + +.emotion-13 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + font-size: 13px; + line-height: 1; +} + +
+
+
+
+ + Test Toggle Group Control + +
+
+
+ +
+
+ +
+
+
+
+ +
+`; + +exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] = ` +.emotion-0 { + font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',sans-serif; + font-size: 13px; + box-sizing: border-box; +} + +.emotion-0 *, +.emotion-0 *::before, +.emotion-0 *::after { + box-sizing: inherit; +} + +.emotion-2 { + margin-bottom: calc(4px * 2); +} + +.components-panel__row .emotion-2 { + margin-bottom: inherit; +} + +.emotion-4 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.emotion-6 { + font-size: 11px; + font-weight: 500; + line-height: 1.4; + text-transform: uppercase; + display: inline-block; + margin-bottom: calc(4px * 2); + padding: 0; +} + +.emotion-8 { + background: #fff; + border: 1px solid transparent; + border-radius: 2px; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + min-width: 0; + padding: 2px; + position: relative; + min-height: 36px; +} + +.emotion-8:hover { + border-color: #757575; +} + +.emotion-8:focus-within { + border-color: var(--wp-components-color-accent-darker-10, var(--wp-admin-theme-color-darker-10, #2145e6)); + box-shadow: 0 0 0 0.5px var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9)); + z-index: 1; + outline: 2px solid transparent; + outline-offset: -2px; +} + +.emotion-10 { display: -webkit-inline-box; display: -webkit-inline-flex; display: -ms-inline-flexbox; @@ -105,7 +618,7 @@ exports[`ToggleGroupControl should render correctly with icons 1`] = ` flex: 1; } -.emotion-14 { +.emotion-12 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -151,24 +664,24 @@ exports[`ToggleGroupControl should render correctly with icons 1`] = ` } @media ( prefers-reduced-motion: reduce ) { - .emotion-14 { + .emotion-12 { transition-duration: 0ms; } } -.emotion-14::-moz-focus-inner { +.emotion-12::-moz-focus-inner { border: 0; } -.emotion-14:active { +.emotion-12:active { background: #fff; } -.emotion-14:active { +.emotion-12:active { background: transparent; } -.emotion-15 { +.emotion-13 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -177,7 +690,17 @@ exports[`ToggleGroupControl should render correctly with icons 1`] = ` line-height: 1; } -.emotion-19 { +.emotion-15 { + background: #1e1e1e; + border-radius: 2px; + position: absolute; + inset: 0; + z-index: 1; + outline: 2px solid transparent; + outline-offset: -3px; +} + +.emotion-18 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -222,16 +745,16 @@ exports[`ToggleGroupControl should render correctly with icons 1`] = ` } @media ( prefers-reduced-motion: reduce ) { - .emotion-19 { + .emotion-18 { transition-duration: 0ms; } } -.emotion-19::-moz-focus-inner { +.emotion-18::-moz-focus-inner { border: 0; } -.emotion-19:active { +.emotion-18:active { background: #fff; } @@ -259,31 +782,23 @@ exports[`ToggleGroupControl should render correctly with icons 1`] = ` id="toggle-group-control-as-radio-group-1" role="radiogroup" > -