From 8b1492131355aa6d42272faba25b841f0cd4509d Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 18 May 2022 08:38:10 +0100 Subject: [PATCH] Refactor the Popover component to use the floatingUI library (#40740) Co-authored-by: ntsekouras Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> --- docs/manifest.json | 6 - package-lock.json | 26 +- .../block-alignment-matrix-control/index.js | 6 +- .../block-alignment-matrix-control/style.scss | 10 - .../src/components/block-popover/inbetween.js | 9 +- .../src/components/block-popover/index.js | 17 +- .../src/components/block-popover/style.scss | 1 + .../src/components/block-switcher/style.scss | 41 +- .../components/colors-gradients/dropdown.js | 13 +- .../components/colors-gradients/style.scss | 20 +- .../src/components/duotone-control/style.scss | 8 +- .../src/components/inserter/style.scss | 3 +- .../components/list-view/drop-indicator.js | 1 - .../src/components/list-view/style.scss | 3 +- .../src/components/navigable-toolbar/index.js | 14 +- .../src/components/preview-options/style.scss | 4 - .../rich-text/format-toolbar-container.js | 1 - .../src/components/rich-text/index.js | 2 +- .../src/components/rich-text/style.scss | 10 +- .../src/components/url-input/index.js | 2 +- packages/block-editor/src/hooks/border.js | 13 +- packages/block-editor/src/hooks/border.scss | 48 -- packages/block-editor/src/style.scss | 1 - packages/block-library/src/image/editor.scss | 2 +- packages/block-library/src/video/editor.scss | 9 +- packages/components/package.json | 1 + .../components/src/autocomplete/style.scss | 2 +- .../component.tsx | 24 +- .../border-box-control/README.md | 24 +- .../border-box-control/component.tsx | 21 +- .../src/border-box-control/types.ts | 26 +- .../border-control-dropdown/component.tsx | 6 +- .../border-control-dropdown/hook.ts | 5 +- .../border-control/border-control/README.md | 7 - .../border-control/component.tsx | 4 +- .../components/src/border-control/styles.ts | 3 +- .../components/src/border-control/types.ts | 10 +- .../components/src/color-palette/index.js | 19 +- .../components/src/color-palette/style.scss | 21 +- packages/components/src/dropdown/index.js | 13 +- packages/components/src/dropdown/style.scss | 2 +- packages/components/src/flyout/context.js | 10 - .../src/flyout/flyout-content/component.js | 53 -- .../src/flyout/flyout-content/index.js | 1 - .../components/src/flyout/flyout/README.md | 98 --- .../components/src/flyout/flyout/component.js | 111 ---- packages/components/src/flyout/flyout/hook.js | 45 -- .../components/src/flyout/flyout/index.js | 2 - packages/components/src/flyout/index.js | 1 - .../components/src/flyout/stories/index.js | 24 - packages/components/src/flyout/styles.ts | 41 -- .../flyout/test/__snapshots__/index.js.snap | 183 ------ packages/components/src/flyout/test/index.js | 103 --- packages/components/src/flyout/types.ts | 84 --- packages/components/src/flyout/utils.js | 21 - packages/components/src/index.js | 1 - .../components/src/navigator/stories/index.js | 20 +- packages/components/src/palette-edit/index.js | 3 +- .../components/src/palette-edit/style.scss | 7 - packages/components/src/popover/README.md | 16 +- packages/components/src/popover/index.js | 613 +++++++----------- packages/components/src/popover/style.scss | 210 +----- .../popover/test/__snapshots__/index.js.snap | 32 +- packages/components/src/popover/test/utils.js | 304 --------- packages/components/src/popover/utils.js | 396 ----------- packages/components/src/tooltip/index.js | 10 +- packages/components/src/tooltip/style.scss | 6 +- .../src/hooks/use-focus-on-mount/index.js | 7 +- .../blocks/__snapshots__/heading.test.js.snap | 6 - .../specs/editor/blocks/heading.test.js | 12 +- .../src/components/header/style.scss | 6 +- .../components/sidebar/post-schedule/index.js | 2 +- .../sidebar/post-schedule/style.scss | 4 +- .../components/global-styles/border-panel.js | 14 +- .../header/document-actions/style.scss | 2 +- .../src/components/sidebar/style.scss | 49 -- .../components/table-of-contents/style.scss | 4 +- .../components/more-menu-dropdown/style.scss | 1 - .../src/components/import-dropdown/style.scss | 2 +- packages/nux/src/components/dot-tip/index.js | 1 - .../nux/src/components/dot-tip/style.scss | 5 +- .../dot-tip/test/__snapshots__/index.js.snap | 1 - .../src/blocks/legacy-widget/edit/form.js | 2 +- 83 files changed, 487 insertions(+), 2484 deletions(-) delete mode 100644 packages/block-editor/src/components/block-alignment-matrix-control/style.scss delete mode 100644 packages/components/src/flyout/context.js delete mode 100644 packages/components/src/flyout/flyout-content/component.js delete mode 100644 packages/components/src/flyout/flyout-content/index.js delete mode 100644 packages/components/src/flyout/flyout/README.md delete mode 100644 packages/components/src/flyout/flyout/component.js delete mode 100644 packages/components/src/flyout/flyout/hook.js delete mode 100644 packages/components/src/flyout/flyout/index.js delete mode 100644 packages/components/src/flyout/index.js delete mode 100644 packages/components/src/flyout/stories/index.js delete mode 100644 packages/components/src/flyout/styles.ts delete mode 100644 packages/components/src/flyout/test/__snapshots__/index.js.snap delete mode 100644 packages/components/src/flyout/test/index.js delete mode 100644 packages/components/src/flyout/types.ts delete mode 100644 packages/components/src/flyout/utils.js delete mode 100644 packages/components/src/popover/test/utils.js delete mode 100644 packages/components/src/popover/utils.js diff --git a/docs/manifest.json b/docs/manifest.json index 7ae12e9743f00..693f4bd420e52 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -815,12 +815,6 @@ "markdown_source": "../packages/components/src/flex/flex/README.md", "parent": "components" }, - { - "title": "Flyout", - "slug": "flyout", - "markdown_source": "../packages/components/src/flyout/flyout/README.md", - "parent": "components" - }, { "title": "FocalPointPicker", "slug": "focal-point-picker", diff --git a/package-lock.json b/package-lock.json index 61a7c8a683198..6d77b8c2bdd18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2724,6 +2724,28 @@ } } }, + "@floating-ui/core": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.6.2.tgz", + "integrity": "sha512-jktYRmZwmau63adUG3GKOAVCofBXkk55S/zQ94XOorAHhwqFIOFAy1rSp2N0Wp6/tGbe9V3u/ExlGZypyY17rg==" + }, + "@floating-ui/dom": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.4.5.tgz", + "integrity": "sha512-b+prvQgJt8pieaKYMSJBXHxX/DYwdLsAWxKYqnO5dO2V4oo/TYBZJAUQCVNjTWWsrs6o4VDrNcP9+E70HAhJdw==", + "requires": { + "@floating-ui/core": "^0.6.2" + } + }, + "@floating-ui/react-dom": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.6.3.tgz", + "integrity": "sha512-hC+pS5D6AgS2wWjbmSQ6UR6Kpy+drvWGJIri6e1EDGADTPsCaa4KzCgmCczHrQeInx9tqs81EyDmbKJYY2swKg==", + "requires": { + "@floating-ui/dom": "^0.4.5", + "use-isomorphic-layout-effect": "^1.1.1" + } + }, "@gar/promisify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz", @@ -17266,6 +17288,7 @@ "@emotion/serialize": "^1.0.2", "@emotion/styled": "^11.6.0", "@emotion/utils": "1.0.0", + "@floating-ui/react-dom": "0.6.3", "@use-gesture/react": "^10.2.6", "@wordpress/a11y": "file:packages/a11y", "@wordpress/compose": "file:packages/compose", @@ -57866,8 +57889,7 @@ "use-isomorphic-layout-effect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz", - "integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==", - "dev": true + "integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==" }, "use-latest": { "version": "1.2.0", diff --git a/packages/block-editor/src/components/block-alignment-matrix-control/index.js b/packages/block-editor/src/components/block-alignment-matrix-control/index.js index 6c3f34137d903..1a923fe2e057f 100644 --- a/packages/block-editor/src/components/block-alignment-matrix-control/index.js +++ b/packages/block-editor/src/components/block-alignment-matrix-control/index.js @@ -22,15 +22,11 @@ function BlockAlignmentMatrixControl( props ) { } = props; const icon = ; - const className = 'block-editor-block-alignment-matrix-control'; - const popoverClassName = `${ className }__popover`; - const isAlternate = true; return ( { const openOnArrowDown = ( event ) => { if ( ! isOpen && event.keyCode === DOWN ) { diff --git a/packages/block-editor/src/components/block-alignment-matrix-control/style.scss b/packages/block-editor/src/components/block-alignment-matrix-control/style.scss deleted file mode 100644 index bc2fa4837bece..0000000000000 --- a/packages/block-editor/src/components/block-alignment-matrix-control/style.scss +++ /dev/null @@ -1,10 +0,0 @@ -.block-editor-block-alignment-matrix-control__popover { - .components-popover__content { - min-width: 0; - width: auto; - - > div { - padding: $grid-unit; - } - } -} diff --git a/packages/block-editor/src/components/block-popover/inbetween.js b/packages/block-editor/src/components/block-popover/inbetween.js index 68fdc8955ee8e..de0fb77ea66bc 100644 --- a/packages/block-editor/src/components/block-popover/inbetween.js +++ b/packages/block-editor/src/components/block-popover/inbetween.js @@ -107,6 +107,8 @@ function BlockPopoverInbetween( { left: previousRect ? previousRect.right : nextRect.right, right: previousRect ? previousRect.left : nextRect.left, bottom: nextRect ? nextRect.top : previousRect.bottom, + height: 0, + width: 0, ownerDocument, }; } @@ -116,6 +118,8 @@ function BlockPopoverInbetween( { left: previousRect ? previousRect.left : nextRect.left, right: previousRect ? previousRect.right : nextRect.right, bottom: nextRect ? nextRect.top : previousRect.bottom, + height: 0, + width: 0, ownerDocument, }; } @@ -126,6 +130,8 @@ function BlockPopoverInbetween( { left: previousRect ? previousRect.left : nextRect.right, right: nextRect ? nextRect.right : previousRect.left, bottom: previousRect ? previousRect.bottom : nextRect.bottom, + height: 0, + width: 0, ownerDocument, }; } @@ -135,6 +141,8 @@ function BlockPopoverInbetween( { left: previousRect ? previousRect.right : nextRect.left, right: nextRect ? nextRect.left : previousRect.right, bottom: previousRect ? previousRect.bottom : nextRect.bottom, + height: 0, + width: 0, ownerDocument, }; }, [ previousElement, nextElement ] ); @@ -155,7 +163,6 @@ function BlockPopoverInbetween( { return ( div { - min-width: auto; - display: flex; - background: $white; - padding: 0; - - .components-menu-group { - margin: 0; - } -} - -.block-editor-block-switcher__popover .components-popover__content { - - .block-editor-block-styles { - margin: 0 -3px; // Remove the panel body padding while keeping it for the title. - } - - // Hide the bottom border on the last panel so it stacks with the popover. - .components-panel__body { - border: 0; - - // Elevate this so the hover style is visible. - position: relative; - z-index: 1; - } - - .components-panel__body + .components-panel__body { - border-top: $border-width solid $gray-200; - } -} - .block-editor-block-switcher__popover__preview__parent { .block-editor-block-switcher__popover__preview__container { position: absolute; top: -$grid-unit-15; - left: calc(100% + #{$grid-unit-40}); + left: calc(100% + #{$grid-unit-20}); } } @@ -138,7 +101,6 @@ // Position correctly. Needs specificity. &.components-popover { - margin-left: $grid-unit-05; margin-top: $grid-unit-15 - $border-width; } @@ -151,6 +113,7 @@ border: $border-width solid $gray-900; background: $white; border-radius: $radius-block-ui; + outline: none; } .block-editor-block-switcher__preview { diff --git a/packages/block-editor/src/components/colors-gradients/dropdown.js b/packages/block-editor/src/components/colors-gradients/dropdown.js index 5a04d65b6b5d8..299995d3eb6cc 100644 --- a/packages/block-editor/src/components/colors-gradients/dropdown.js +++ b/packages/block-editor/src/components/colors-gradients/dropdown.js @@ -121,13 +121,16 @@ export default function ColorGradientSettingsDropdown( { __experimentalIsRenderedInSidebar, ...props } ) { - const dropdownPosition = __experimentalIsRenderedInSidebar - ? 'bottom left' - : undefined; - const dropdownClassName = __experimentalIsItemGroup ? 'block-editor-panel-color-gradient-settings__dropdown' : 'block-editor-tools-panel-color-gradient-settings__dropdown'; + let popoverProps; + if ( __experimentalIsRenderedInSidebar ) { + popoverProps = { + placement: 'left-start', + offset: 36, + }; + } return ( // Only wrap with `ItemGroup` if these controls are being rendered @@ -170,7 +173,7 @@ export default function ColorGradientSettingsDropdown( { { ...props } > div { - width: $sidebar-width; - } -} - -@include break-medium() { - .block-editor-panel-color-gradient-settings__dropdown-content { - .components-popover__content { - margin-right: #{ math.div($sidebar-width, 2) + $grid-unit-20 } !important; - } - - &.is-from-top .components-popover__content { - margin-top: #{ -($grid-unit-60 + $grid-unit-15) } !important; - } - - &.is-from-bottom .components-popover__content { - margin-bottom: #{ -($grid-unit-60 + $grid-unit-15) } !important; - } - } + width: $sidebar-width; } .block-editor-panel-color-gradient-settings__dropdown:last-child > div { diff --git a/packages/block-editor/src/components/duotone-control/style.scss b/packages/block-editor/src/components/duotone-control/style.scss index f7bf47ee66c1d..d5b8c24bcdb9d 100644 --- a/packages/block-editor/src/components/duotone-control/style.scss +++ b/packages/block-editor/src/components/duotone-control/style.scss @@ -9,7 +9,7 @@ $popover-padding: $grid-unit-20; $swatch-columns: math.floor(math.div($popover-width + $swatch-gap - 2 * $popover-padding, $swatch-size + $swatch-gap)); .block-editor-duotone-control__popover { - > .components-popover__content > div { + > .components-popover__content { padding: $popover-padding; width: $popover-width; } @@ -34,9 +34,3 @@ $swatch-columns: math.floor(math.div($popover-width + $swatch-gap - 2 * $popover margin: $grid-unit-20 0; font-size: $helptext-font-size; } - -// Better align the popover under the swatch. -// @todo: when the positioning for popovers gets refactored, this can presumably be removed. -.block-editor-duotone-control__popover:not([data-y-axis="middle"][data-x-axis="right"]) > .components-popover__content { - margin-left: -14px; -} diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index da16f256bec2a..802718847648d 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -23,6 +23,7 @@ $block-inserter-tabs-height: 44px; .block-editor-inserter__popover.is-quick { .components-popover__content { border: none; + outline: none; .block-editor-inserter__quick-inserter > * { border-left: $border-width solid $gray-400; @@ -310,7 +311,7 @@ $block-inserter-tabs-height: 44px; border-top: $border-width solid $gray-300; } -.block-editor-inserter__popover.is-quick > .components-popover__content > div { +.block-editor-inserter__popover.is-quick > .components-popover__content { padding: 0; } diff --git a/packages/block-editor/src/components/list-view/drop-indicator.js b/packages/block-editor/src/components/list-view/drop-indicator.js index 15f5d68212a8a..b242ae62ca86f 100644 --- a/packages/block-editor/src/components/list-view/drop-indicator.js +++ b/packages/block-editor/src/components/list-view/drop-indicator.js @@ -110,7 +110,6 @@ export default function ListViewDropIndicator( { return ( .components-popover__content { +.block-editor-list-view-drop-indicator > .components-popover__content { margin-left: 0; border: none; box-shadow: none; + outline: none; } .block-editor-list-view-placeholder { diff --git a/packages/block-editor/src/components/navigable-toolbar/index.js b/packages/block-editor/src/components/navigable-toolbar/index.js index 34ffa790f4ce3..4025468f4abbb 100644 --- a/packages/block-editor/src/components/navigable-toolbar/index.js +++ b/packages/block-editor/src/components/navigable-toolbar/index.js @@ -29,7 +29,12 @@ function hasFocusWithin( container ) { function focusFirstTabbableIn( container ) { const [ firstTabbable ] = focus.tabbable.find( container ); if ( firstTabbable ) { - firstTabbable.focus(); + firstTabbable.focus( { + // When focusing newly mounted toolbars, + // the position of the popover is often not right on the first render + // This prevents the layout shifts when focusing the dialogs. + preventScroll: true, + } ); } } @@ -119,7 +124,12 @@ function useToolbarFocus( const items = getAllToolbarItemsIn( ref.current ); const index = initialIndex || 0; if ( items[ index ] && hasFocusWithin( ref.current ) ) { - items[ index ].focus(); + items[ index ].focus( { + // When focusing newly mounted toolbars, + // the position of the popover is often not right on the first render + // This prevents the layout shifts when focusing the dialogs. + preventScroll: true, + } ); } } ); } diff --git a/packages/block-editor/src/components/preview-options/style.scss b/packages/block-editor/src/components/preview-options/style.scss index 166764b35498f..b25578c1d2051 100644 --- a/packages/block-editor/src/components/preview-options/style.scss +++ b/packages/block-editor/src/components/preview-options/style.scss @@ -11,10 +11,6 @@ } .block-editor-post-preview__dropdown-content { - .components-popover__content { - overflow-y: visible; - } - &.edit-post-post-preview-dropdown { .components-menu-group { &:first-child { diff --git a/packages/block-editor/src/components/rich-text/format-toolbar-container.js b/packages/block-editor/src/components/rich-text/format-toolbar-container.js index 7d32b911d9de0..08ea1e7e66fdf 100644 --- a/packages/block-editor/src/components/rich-text/format-toolbar-container.js +++ b/packages/block-editor/src/components/rich-text/format-toolbar-container.js @@ -14,7 +14,6 @@ const FormatToolbarContainer = ( { inline, anchorRef } ) => { // Render in popover. return ( ) } +
.components-popover__content { - width: 360px; -} - .block-library-video-tracks-editor__track-list-track { display: flex; place-content: space-between; @@ -75,9 +71,11 @@ display: block; } -.block-library-video-tracks-editor > .components-popover__content > div { +.block-library-video-tracks-editor > .components-popover__content { + width: 360px; padding: 0; } + .block-library-video-tracks-editor__track-list, .block-library-video-tracks-editor__add-tracks-container { .components-menu-group__label { @@ -85,7 +83,6 @@ } } - .block-library-video-tracks-editor__single-track-editor, .block-library-video-tracks-editor__track-list, .block-library-video-tracks-editor__add-tracks-container { diff --git a/packages/components/package.json b/packages/components/package.json index 15ea54baa7d6b..1e791128d2937 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -36,6 +36,7 @@ "@emotion/serialize": "^1.0.2", "@emotion/styled": "^11.6.0", "@emotion/utils": "1.0.0", + "@floating-ui/react-dom": "0.6.3", "@use-gesture/react": "^10.2.6", "@wordpress/a11y": "file:../a11y", "@wordpress/compose": "file:../compose", diff --git a/packages/components/src/autocomplete/style.scss b/packages/components/src/autocomplete/style.scss index b7060e4a46918..4b8434caacf45 100644 --- a/packages/components/src/autocomplete/style.scss +++ b/packages/components/src/autocomplete/style.scss @@ -1,4 +1,4 @@ -.components-autocomplete__popover .components-popover__content > div { +.components-autocomplete__popover .components-popover__content { padding: $grid-unit-20; min-width: 220px; } diff --git a/packages/components/src/border-box-control/border-box-control-split-controls/component.tsx b/packages/components/src/border-box-control/border-box-control-split-controls/component.tsx index add245b0d5e94..af6249f7b4ca9 100644 --- a/packages/components/src/border-box-control/border-box-control-split-controls/component.tsx +++ b/packages/components/src/border-box-control/border-box-control-split-controls/component.tsx @@ -2,6 +2,8 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { useRef } from '@wordpress/element'; +import { useMergeRefs } from '@wordpress/compose'; /** * Internal dependencies @@ -25,13 +27,23 @@ const BorderBoxControlSplitControls = ( enableAlpha, enableStyle, onChange, - popoverClassNames, + popoverPlacement, + popoverOffset, value, __experimentalHasMultipleOrigins, __experimentalIsRenderedInSidebar, __next36pxDefaultSize, ...otherProps } = useBorderBoxControlSplitControls( props ); + const containerRef = useRef(); + const mergedRef = useMergeRefs( [ containerRef, forwardedRef ] ); + const popoverProps = popoverPlacement + ? { + placement: popoverPlacement, + offset: popoverOffset, + anchorRef: containerRef, + } + : undefined; const sharedBorderControlProps = { colors, @@ -45,7 +57,7 @@ const BorderBoxControlSplitControls = ( }; return ( - + onChange( newBorder, 'top' ) } - popoverContentClassName={ popoverClassNames?.top } + __unstablePopoverProps={ popoverProps } value={ value?.top } { ...sharedBorderControlProps } /> @@ -63,7 +75,7 @@ const BorderBoxControlSplitControls = ( hideLabelFromVision={ true } label={ __( 'Left border' ) } onChange={ ( newBorder ) => onChange( newBorder, 'left' ) } - popoverContentClassName={ popoverClassNames?.left } + __unstablePopoverProps={ popoverProps } value={ value?.left } { ...sharedBorderControlProps } /> @@ -71,7 +83,7 @@ const BorderBoxControlSplitControls = ( hideLabelFromVision={ true } label={ __( 'Right border' ) } onChange={ ( newBorder ) => onChange( newBorder, 'right' ) } - popoverContentClassName={ popoverClassNames?.right } + __unstablePopoverProps={ popoverProps } value={ value?.right } { ...sharedBorderControlProps } /> @@ -80,7 +92,7 @@ const BorderBoxControlSplitControls = ( hideLabelFromVision={ true } label={ __( 'Bottom border' ) } onChange={ ( newBorder ) => onChange( newBorder, 'bottom' ) } - popoverContentClassName={ popoverClassNames?.bottom } + __unstablePopoverProps={ popoverProps } value={ value?.bottom } { ...sharedBorderControlProps } /> diff --git a/packages/components/src/border-box-control/border-box-control/README.md b/packages/components/src/border-box-control/border-box-control/README.md index 67a9426823f0f..c895c1b853de1 100644 --- a/packages/components/src/border-box-control/border-box-control/README.md +++ b/packages/components/src/border-box-control/border-box-control/README.md @@ -122,23 +122,19 @@ _Note: The will be `undefined` if a user clears all borders._ - Required: Yes -### `popoverClassNames`: `Object` +### `popoverPlacement`: `string` -An object defining CSS classnames for all the inner `BorderControl` popover -content. +The position of the color popover relative to the control wrapper. -Example: -```js -{ - linked: 'linked-border-popover-content', - top: 'top-border-popover-content', - right: 'right-border-popover-content', - bottom: 'bottom-border-popover-content', - left: 'left-border-popover-content', -} -``` +By default, popovers are displayed relative to the button that initiated the popover. By supplying a popover placement, you force the popover to display in a specific location. + +The available base placements are 'top', 'right', 'bottom', 'left'. Each of these base placements has an alignment in the form -start and -end. For example, 'right-start', or 'bottom-end'. These allow you to align the tooltip to the edges of the button, rather than centering it. + +- Required: No + +### `popoverOffset`: `number` -By default, popovers are displayed relative to the button that initiated the popover. By supplying classnames for each individual popover, it is possible to add styling rules to align the popover positions to an unrelated design element, for example, the sidebar inspector in the block editor. +Works in conjunctions with `popoverPlacement` and allows leaving a space between the color popover and the control wrapper. - Required: No diff --git a/packages/components/src/border-box-control/border-box-control/component.tsx b/packages/components/src/border-box-control/border-box-control/component.tsx index c51de26a1dfdd..654bd0ec4ce1f 100644 --- a/packages/components/src/border-box-control/border-box-control/component.tsx +++ b/packages/components/src/border-box-control/border-box-control/component.tsx @@ -2,6 +2,8 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { useRef } from '@wordpress/element'; +import { useMergeRefs } from '@wordpress/compose'; /** * Internal dependencies @@ -51,7 +53,8 @@ const BorderBoxControl = ( linkedValue, onLinkedChange, onSplitChange, - popoverClassNames, + popoverPlacement, + popoverOffset, splitValue, toggleLinked, __experimentalHasMultipleOrigins, @@ -59,9 +62,18 @@ const BorderBoxControl = ( __next36pxDefaultSize = false, ...otherProps } = useBorderBoxControl( props ); + const containerRef = useRef(); + const mergedRef = useMergeRefs( [ containerRef, forwardedRef ] ); + const popoverProps = popoverPlacement + ? { + placement: popoverPlacement, + offset: popoverOffset, + anchorRef: containerRef, + } + : undefined; return ( - + void; /** - * An object defining CSS classnames for all the inner `BorderControl` - * popover content. + * The position of the color popovers compared to the control wrapper. + */ + popoverPlacement?: string; + /** + * The space between the popover and the control wrapper. */ - popoverClassNames?: PopoverClassNames; + popoverOffset?: number; /** * An object representing the current border configuration. * @@ -106,10 +101,13 @@ export type SplitControlsProps = ColorProps & { */ onChange: ( value: Border | undefined, side: BorderSide ) => void; /** - * An object defining CSS classnames for the split side `BorderControl`s' - * popover content. + * The position of the color popovers compared to the control wrapper. + */ + popoverPlacement?: string; + /** + * The space between the popover and the control wrapper. */ - popoverClassNames?: PopoverClassNames; + popoverOffset?: number; /** * An object representing the current border configuration. It contains * properties for each side, with each side an object reflecting the border diff --git a/packages/components/src/border-control/border-control-dropdown/component.tsx b/packages/components/src/border-control/border-control-dropdown/component.tsx index 80940164afcd0..6d8ee795497dc 100644 --- a/packages/components/src/border-control/border-control-dropdown/component.tsx +++ b/packages/components/src/border-control/border-control-dropdown/component.tsx @@ -147,6 +147,7 @@ const BorderControlDropdown = ( resetButtonClassName, showDropdownHeader, enableStyle = true, + __unstablePopoverProps, ...otherProps } = useBorderControlDropdown( props ); @@ -240,7 +241,10 @@ const BorderControlDropdown = ( diff --git a/packages/components/src/border-control/border-control-dropdown/hook.ts b/packages/components/src/border-control/border-control-dropdown/hook.ts index 25d8652148468..aab058874ae96 100644 --- a/packages/components/src/border-control/border-control-dropdown/hook.ts +++ b/packages/components/src/border-control/border-control-dropdown/hook.ts @@ -20,7 +20,6 @@ export function useBorderControlDropdown( border, className, colors, - contentClassName, onChange, previousStyleSelection, __next36pxDefaultSize, @@ -68,8 +67,8 @@ export function useBorderControlDropdown( }, [ border, cx, __next36pxDefaultSize ] ); const popoverClassName = useMemo( () => { - return cx( styles.borderControlPopover, contentClassName ); - }, [ cx, contentClassName ] ); + return cx( styles.borderControlPopover ); + }, [ cx ] ); const popoverControlsClassName = useMemo( () => { return cx( styles.borderControlPopoverControls ); diff --git a/packages/components/src/border-control/border-control/README.md b/packages/components/src/border-control/border-control/README.md index aa8b439d8326b..a4d0f2017f2d6 100644 --- a/packages/components/src/border-control/border-control/README.md +++ b/packages/components/src/border-control/border-control/README.md @@ -113,13 +113,6 @@ _Note: the value may be `undefined` if a user clears all border properties._ - Required: Yes -### `popoverContentClassName`: `string` - -A custom CSS class name to be assigned to the `BorderControl`'s dropdown -popover content. - -- Required: No - ### `shouldSanitizeBorder`: `boolean` If opted into, sanitizing the border means that if no width or color have been diff --git a/packages/components/src/border-control/border-control/component.tsx b/packages/components/src/border-control/border-control/component.tsx index 59e6743e9e5a1..942e8b00104da 100644 --- a/packages/components/src/border-control/border-control/component.tsx +++ b/packages/components/src/border-control/border-control/component.tsx @@ -43,7 +43,7 @@ const BorderControl = ( onSliderChange, onWidthChange, placeholder, - popoverContentClassName, + __unstablePopoverProps, previousStyleSelection, showDropdownHeader, sliderClassName, @@ -69,7 +69,7 @@ const BorderControl = ( div > div { + && .components-popover__content { padding: 0; + width: 264px; } `; diff --git a/packages/components/src/border-control/types.ts b/packages/components/src/border-control/types.ts index 049c278a5e71f..0605b2e40fe41 100644 --- a/packages/components/src/border-control/types.ts +++ b/packages/components/src/border-control/types.ts @@ -81,10 +81,9 @@ export type BorderControlProps = ColorProps & */ onChange: ( value?: Border ) => void; /** - * A custom CSS class name to be assigned to the border control's - * dropdown popover content. + * An internal prop used to control the visibility of the dropdown. */ - popoverContentClassName?: string; + __unstablePopoverProps?: Record< string, unknown >; /** * If opted into, sanitizing the border means that if no width or color * have been selected, the border style is also cleared and `undefined` @@ -131,10 +130,9 @@ export type DropdownProps = ColorProps & { */ border?: Border; /** - * A custom CSS class name to be assigned to the border control's - * dropdown popover content. + * An internal prop used to control the visibility of the dropdown. */ - contentClassName?: string; + __unstablePopoverProps?: Record< string, unknown >; /** * This controls whether to render border style options. * diff --git a/packages/components/src/color-palette/index.js b/packages/components/src/color-palette/index.js index 9e2745fd57574..4c92fd35b1822 100644 --- a/packages/components/src/color-palette/index.js +++ b/packages/components/src/color-palette/index.js @@ -6,7 +6,6 @@ import { map } from 'lodash'; import { colord, extend } from 'colord'; import namesPlugin from 'colord/plugins/names'; import a11yPlugin from 'colord/plugins/a11y'; -import classnames from 'classnames'; /** * WordPress dependencies @@ -116,12 +115,12 @@ function MultiplePalettes( { export function CustomColorPickerDropdown( { isRenderedInSidebar, ...props } ) { return ( ); @@ -176,11 +175,6 @@ export default function ColorPalette( { /> ); - let dropdownPosition; - if ( __experimentalIsRenderedInSidebar ) { - dropdownPosition = 'bottom left'; - } - const colordColor = colord( value ); const valueWithoutLeadingHash = value?.startsWith( '#' ) @@ -211,7 +205,6 @@ export default function ColorPalette( { { ! disableCustomColors && ( ( diff --git a/packages/components/src/color-palette/style.scss b/packages/components/src/color-palette/style.scss index 3aab4914deee3..1abb50c6e3528 100644 --- a/packages/components/src/color-palette/style.scss +++ b/packages/components/src/color-palette/style.scss @@ -30,31 +30,16 @@ overflow: visible; box-shadow: 0 4px 4px rgba(0, 0, 0, 0.05); border: none; + outline: none; border-radius: $radius-block-ui; - max-height: fit-content !important; - & > div { - padding: 0; - } + padding: 0; + .react-colorful__saturation { border-top-right-radius: $radius-block-ui; border-top-left-radius: $radius-block-ui; } } -@include break-medium() { - .components-dropdown__content.components-color-palette__custom-color-dropdown-content.is-rendered-in-sidebar { - .components-popover__content.components-popover__content { - margin-right: #{ math.div($sidebar-width, 2) + $grid-unit-20 }; - } - &.is-from-top .components-popover__content { - margin-top: #{ -($grid-unit-60 + $grid-unit-15) }; - } - &.is-from-bottom .components-popover__content { - margin-bottom: #{ -($grid-unit-60 + $grid-unit-15) }; - } - } -} - .components-color-palette__custom-color-name { text-align: left; } diff --git a/packages/components/src/dropdown/index.js b/packages/components/src/dropdown/index.js index d9ebd10fab9ff..ce080c1be0adb 100644 --- a/packages/components/src/dropdown/index.js +++ b/packages/components/src/dropdown/index.js @@ -31,12 +31,12 @@ export default function Dropdown( props ) { const { renderContent, renderToggle, - position = 'bottom right', className, contentClassName, expandOnMobile, headerTitle, focusOnMount, + position, popoverProps, onClose, onToggle, @@ -82,6 +82,10 @@ export default function Dropdown( props ) { } const args = { isOpen, onToggle: toggle, onClose: close }; + const hasAnchorRef = + !! popoverProps?.anchorRef || + !! popoverProps?.getAnchorRect || + !! popoverProps?.anchorRect; return (
div { + .components-popover__content { padding: $grid-unit-10; } diff --git a/packages/components/src/flyout/context.js b/packages/components/src/flyout/context.js deleted file mode 100644 index f1678c99e3efd..0000000000000 --- a/packages/components/src/flyout/context.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * WordPress dependencies - */ -import { createContext, useContext } from '@wordpress/element'; - -/** - * @type {import('react').Context} - */ -export const FlyoutContext = createContext( {} ); -export const useFlyoutContext = () => useContext( FlyoutContext ); diff --git a/packages/components/src/flyout/flyout-content/component.js b/packages/components/src/flyout/flyout-content/component.js deleted file mode 100644 index e1f415e2dda1b..0000000000000 --- a/packages/components/src/flyout/flyout-content/component.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Internal dependencies - */ -import { useFlyoutContext } from '../context'; -import { FlyoutContentView, CardView } from '../styles'; -import { contextConnect, useContextSystem } from '../../ui/context'; - -/** - * - * @param {import('../../ui/context').WordPressComponentProps} props - * @param {import('react').ForwardedRef} forwardedRef forwardedRef - */ -function FlyoutContent( props, forwardedRef ) { - const { - children, - elevation, - maxWidth, - style = {}, - ...otherProps - } = useContextSystem( props, 'FlyoutContent' ); - - const { label, flyoutState } = useFlyoutContext(); - - if ( ! flyoutState ) { - throw new Error( - '`FlyoutContent` must only be used inside a `Flyout`.' - ); - } - - const showContent = flyoutState.visible || flyoutState.animating; - - return ( - - { showContent && ( - - { children } - - ) } - - ); -} - -const ConnectedFlyoutContent = contextConnect( FlyoutContent, 'FlyoutContent' ); - -export default ConnectedFlyoutContent; diff --git a/packages/components/src/flyout/flyout-content/index.js b/packages/components/src/flyout/flyout-content/index.js deleted file mode 100644 index b404d7fd44a81..0000000000000 --- a/packages/components/src/flyout/flyout-content/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './component'; diff --git a/packages/components/src/flyout/flyout/README.md b/packages/components/src/flyout/flyout/README.md deleted file mode 100644 index 49aa328bd6b1f..0000000000000 --- a/packages/components/src/flyout/flyout/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# Flyout - -
-This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. -
- -`Flyout` is a component to render a floating content modal. It is similar in purpose to a tooltip, but renders content of any sort, not only simple text. - -## Usage - -```jsx -import { Button, __experimentalFlyout as Flyout, __experimentalText as Text } from '@wordpress/components'; - -function Example() { - return ( - Show/Hide content }> - Code is Poetry - - ); -} -``` - -## Props - -### `state`: `PopoverStateReturn` - -- Required: No - -### `label`: `string` - -- Required: No - -### `animated`: `boolean` - -Determines if `Flyout` has animations. - -- Required: No -- Default: `true` - -### `animationDuration`: `boolean` - -The duration of `Flyout` animations. - -- Required: No -- Default: `160` - -### `baseId`: `string` - -ID that will serve as a base for all the items IDs. See https://reakit.io/docs/popover/#usepopoverstate - -- Required: No -- Default: `160` - -### `elevation`: `number` - -Size of the elevation shadow. For more information, check out [`Card`](/packages/components/src/card/card/README.md#props). - -- Required: No -- Default: `5` - -### `maxWidth`: `CSSProperties[ 'maxWidth' ]` - -Max-width for the `Flyout` element. - -- Required: No -- Default: `360` - -### `onVisibleChange`: `( ...args: any ) => void` - -Callback for when the `visible` state changes. - -- Required: No - -### `trigger`: `FunctionComponentElement< any >` - -Element that triggers the `visible` state of `Flyout` when clicked. - -```jsx -Greet}> - Hi! I'm Olaf! - -``` - -- Required: Yes - -### `visible`: `boolean` - -Whether `Flyout` is visible. See [the `Reakit` docs](https://reakit.io/docs/popover/#usepopoverstate) for more information. - -- Required: No -- Default: `false` - -### `placement`: `PopperPlacement` - -Position of the popover element. See [the `popper` docs](https://popper.js.org/docs/v1/#popperplacements--codeenumcode) for more information. - -- Required: No -- Default: `auto` diff --git a/packages/components/src/flyout/flyout/component.js b/packages/components/src/flyout/flyout/component.js deleted file mode 100644 index 1ad283d0ab1d6..0000000000000 --- a/packages/components/src/flyout/flyout/component.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * External dependencies - */ -// eslint-disable-next-line no-restricted-imports -import { PopoverDisclosure, Portal } from 'reakit'; - -/** - * WordPress dependencies - */ -import { useCallback, useMemo, cloneElement } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { contextConnect } from '../../ui/context'; -import { FlyoutContext } from '../context'; -import { useFlyoutResizeUpdater } from '../utils'; -import FlyoutContent from '../flyout-content'; -import { useUpdateEffect } from '../../utils/hooks'; -import { useFlyout } from './hook'; - -/** - * - * @param {import('../../ui/context').WordPressComponentProps} props - * @param {import('react').ForwardedRef} forwardedRef - */ -function Flyout( props, forwardedRef ) { - const { - children, - elevation, - label, - maxWidth, - onVisibleChange, - trigger, - flyoutState, - ...otherProps - } = useFlyout( props ); - - const resizeListener = useFlyoutResizeUpdater( { - onResize: flyoutState.unstable_update, - } ); - - const uniqueId = `flyout-${ flyoutState.baseId }`; - const labelId = label || uniqueId; - - const contextProps = useMemo( - () => ( { - label: labelId, - flyoutState, - } ), - [ labelId, flyoutState ] - ); - - const triggerContent = useCallback( - ( triggerProps ) => { - return cloneElement( trigger, triggerProps ); - }, - [ trigger ] - ); - - useUpdateEffect( () => { - onVisibleChange?.( flyoutState.visible ); - }, [ flyoutState.visible ] ); - - return ( - - { trigger && ( - - { triggerContent } - - ) } - - - { resizeListener } - { children } - - - - ); -} - -/** - * `Flyout` is a component to render a floating content modal. - * It is similar in purpose to a tooltip, but renders content of any sort, - * not only simple text. - * - * @example - * ```jsx - * import { Button, __experimentalFlyout as Flyout, __experimentalText as } from '@wordpress/components'; - * - * function Example() { - * return ( - * Show/Hide content }> - * Code is Poetry - * - * ); - * } - * ``` - */ -const ConnectedFlyout = contextConnect( Flyout, 'Flyout' ); - -export default ConnectedFlyout; diff --git a/packages/components/src/flyout/flyout/hook.js b/packages/components/src/flyout/flyout/hook.js deleted file mode 100644 index d917c1011f4b9..0000000000000 --- a/packages/components/src/flyout/flyout/hook.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * External dependencies - */ -// eslint-disable-next-line no-restricted-imports -import { usePopoverState } from 'reakit'; - -/** - * Internal dependencies - */ -import { useContextSystem } from '../../ui/context'; - -/** - * @param {import('../../ui/context').WordPressComponentProps} props - */ -export function useFlyout( props ) { - const { - animated = true, - animationDuration = 160, - baseId, - elevation = 5, - id, - maxWidth = 360, - placement, - state, - visible, - ...otherProps - } = useContextSystem( props, 'Flyout' ); - - const _flyoutState = usePopoverState( { - animated: animated ? animationDuration : undefined, - baseId: baseId || id, - placement, - visible, - ...otherProps, - } ); - - const flyoutState = state || _flyoutState; - - return { - ...otherProps, - elevation, - maxWidth, - flyoutState, - }; -} diff --git a/packages/components/src/flyout/flyout/index.js b/packages/components/src/flyout/flyout/index.js deleted file mode 100644 index ef5d7d27828f8..0000000000000 --- a/packages/components/src/flyout/flyout/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './component'; -export { useFlyout } from './hook'; diff --git a/packages/components/src/flyout/index.js b/packages/components/src/flyout/index.js deleted file mode 100644 index 28e34de445ba0..0000000000000 --- a/packages/components/src/flyout/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default as Flyout } from './flyout'; diff --git a/packages/components/src/flyout/stories/index.js b/packages/components/src/flyout/stories/index.js deleted file mode 100644 index df8f41b8f279e..0000000000000 --- a/packages/components/src/flyout/stories/index.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Internal dependencies - */ -import { CardBody, CardHeader } from '../../card'; -import Button from '../../button'; -import { Flyout } from '..'; - -export default { - component: Flyout, - title: 'Components (Experimental)/Flyout', -}; - -export const _default = () => { - return ( - Click } - visible - placement="bottom-start" - > - Go - Stuff - - ); -}; diff --git a/packages/components/src/flyout/styles.ts b/packages/components/src/flyout/styles.ts deleted file mode 100644 index 3ac211d808ffd..0000000000000 --- a/packages/components/src/flyout/styles.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * External dependencies - */ -import styled, { StyledComponent } from '@emotion/styled'; -// eslint-disable-next-line no-restricted-imports -import { Popover as ReakitPopover } from 'reakit'; - -/** - * Internal dependencies - */ -import { Card, CardBody } from '../card'; -import * as ZIndex from '../utils/z-index'; -import CONFIG from '../utils/config-values'; - -export const FlyoutContentView: StyledComponent< - React.ComponentPropsWithoutRef< typeof ReakitPopover > -> = styled( ReakitPopover )` - z-index: ${ ZIndex.Flyout }; - box-sizing: border-box; - opacity: 0; - outline: none; - position: relative; - transform-origin: center center; - transition: opacity ${ CONFIG.transitionDurationFastest } linear; - width: 100%; - - &[data-enter] { - opacity: 1; - } - - &::before, - &::after { - display: none; - } -`; - -export const CardView = styled( Card )` - ${ CardBody.selector } { - max-height: 80vh; - } -`; diff --git a/packages/components/src/flyout/test/__snapshots__/index.js.snap b/packages/components/src/flyout/test/__snapshots__/index.js.snap deleted file mode 100644 index 60c6d3fe10500..0000000000000 --- a/packages/components/src/flyout/test/__snapshots__/index.js.snap +++ /dev/null @@ -1,183 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`props should render correctly 1`] = ` -.emotion-1 { - background-color: #fff; - color: #1e1e1e; - position: relative; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1); - outline: none; - border-radius: calc(2px - 1px); -} - -.emotion-1 .components-card-body { - max-height: 80vh; -} - -.emotion-3 { - height: 100%; -} - -.emotion-5 { - box-sizing: border-box; - height: auto; - max-height: 100%; - padding: calc(4px * 4) calc(4px * 6); -} - -.emotion-5:first-of-type { - border-top-left-radius: calc(2px - 1px); - border-top-right-radius: calc(2px - 1px); -} - -.emotion-5:last-of-type { - border-bottom-left-radius: calc(2px - 1px); - border-bottom-right-radius: calc(2px - 1px); -} - -.emotion-7 { - background: transparent; - display: block; - margin: 0!important; - pointer-events: none; - position: absolute; - will-change: box-shadow; - border-radius: inherit; - bottom: 0; - box-shadow: 0 1px 2px 0 rgba(0 ,0, 0, 0.05); - opacity: 1; - left: 0; - right: 0; - top: 0; - -webkit-transition: box-shadow 200ms cubic-bezier(0.08, 0.52, 0.52, 1); - transition: box-shadow 200ms cubic-bezier(0.08, 0.52, 0.52, 1); - border-radius: 2px; -} - -@media ( prefers-reduced-motion: reduce ) { - .emotion-7 { - transition-duration: 0ms; - } -} - -.emotion-9 { - background: transparent; - display: block; - margin: 0!important; - pointer-events: none; - position: absolute; - will-change: box-shadow; - border-radius: inherit; - bottom: 0; - box-shadow: 0 5px 10px 0 rgba(0 ,0, 0, 0.25); - opacity: 1; - left: 0; - right: 0; - top: 0; - -webkit-transition: box-shadow 200ms cubic-bezier(0.08, 0.52, 0.52, 1); - transition: box-shadow 200ms cubic-bezier(0.08, 0.52, 0.52, 1); - border-radius: 2px; -} - -@media ( prefers-reduced-motion: reduce ) { - .emotion-9 { - transition-duration: 0ms; - } -} - -
-
- -