From 30e93ed58293396e881a3c4fc16776b7aab6cd8f Mon Sep 17 00:00:00 2001 From: Mitchell Austin Date: Tue, 17 Sep 2024 21:08:28 -0700 Subject: [PATCH 01/16] Replace details element with custom toggle --- .../edit-post/src/components/layout/index.js | 215 +++++++++--------- .../src/components/layout/style.scss | 112 +++++---- 2 files changed, 170 insertions(+), 157 deletions(-) diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index e6451c6a4a4082..768db1643b5e02 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -31,6 +31,7 @@ import { useRef, useState, } from '@wordpress/element'; +import { chevronDown, chevronUp } from '@wordpress/icons'; import { store as noticesStore } from '@wordpress/notices'; import { store as preferencesStore } from '@wordpress/preferences'; import { @@ -43,12 +44,13 @@ import { addQueryArgs } from '@wordpress/url'; import { decodeEntities } from '@wordpress/html-entities'; import { store as coreStore } from '@wordpress/core-data'; import { - ResizableBox, + Icon, SlotFillProvider, Tooltip, VisuallyHidden, } from '@wordpress/components'; import { + __experimentalUseDragging as useDragging, useMediaQuery, useRefEffect, useViewportMatch, @@ -183,7 +185,7 @@ function MetaBoxesMain( { isLegacy } ) { ]; }, [] ); const { set: setPreference } = useDispatch( preferencesStore ); - const resizableBoxRef = useRef(); + const metaBoxesMainRef = useRef(); const isShort = useMediaQuery( '(max-height: 549px)' ); const [ { min, max }, setHeightConstraints ] = useState( () => ( {} ) ); @@ -198,7 +200,7 @@ function MetaBoxesMain( { isLegacy } ) { ':scope > .components-notice-list' ); const resizeHandle = container.querySelector( - '.edit-post-meta-boxes-main__resize-handle' + '.edit-post-meta-boxes-main__presenter' ); const actualize = () => { const fullHeight = container.offsetHeight; @@ -220,13 +222,51 @@ function MetaBoxesMain( { isLegacy } ) { const separatorRef = useRef(); const separatorHelpId = useId(); - const [ isUntouched, setIsUntouched ] = useState( true ); + const heightRef = useRef(); + const { startDrag, endDrag, isDragging } = useDragging( { + onDragStart: () => { + if ( isAutoHeight || heightRef.current === undefined ) { + // Sets the starting height to avoid visual jumps in height and + // aria-valuenow being `NaN` for the first (few) resize events. + heightRef.current = metaBoxesMainRef.current.offsetHeight; + metaBoxesMainRef.current.style.height = `${ heightRef.current }px`; + } else if ( heightRef.current > max ) { + // Starts from max in case shortening the window has imposed it. + heightRef.current = max; + } + }, + onDragMove: ( { movementY } ) => { + const { current: fromHeight } = heightRef; + const candidateHeight = fromHeight - movementY; + // Keeps the unconstrained height for subsequent calculations so that when + // dragging takes the height out of range and the cursor detaches from its + // initial position relative to the drag handle, any movement back toward the + // range is absorbed without affecting the applied height as that would + // otherwise maintain the cursor’s detachment. + heightRef.current = candidateHeight; + const nextHeight = Math.min( + max, + Math.max( min, candidateHeight ) + ); + metaBoxesMainRef.current.style.height = `${ nextHeight }px`; + separatorRef.current.ariaValueNow = getAriaValueNow( nextHeight ); + }, + onDragEnd: () => { + const nextHeight = metaBoxesMainRef.current.offsetHeight; + // Syncs the final height to ensure the next resize starts with it. + heightRef.current = nextHeight; + setPreference( + 'core/edit-post', + 'metaBoxesMainOpenHeight', + nextHeight + ); + }, + } ); if ( ! hasAnyVisible ) { return; } - const className = 'edit-post-meta-boxes-main'; const contents = (
@@ -256,45 +296,23 @@ function MetaBoxesMain( { isLegacy } ) { const usedAriaValueNow = max === undefined || isAutoHeight ? 50 : getAriaValueNow( openHeight ); - if ( isShort ) { - return ( -
{ - setPreference( - 'core/edit-post', - 'metaBoxesMainIsOpen', - target.open - ); - } } - > - { __( 'Meta Boxes' ) } - { contents } -
- ); - } + const toggle = () => + setPreference( 'core/edit-post', 'metaBoxesMainIsOpen', ! isOpen ); // TODO: Support more/all keyboard interactions from the window splitter pattern: // https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/ const onSeparatorKeyDown = ( event ) => { const delta = { ArrowUp: 20, ArrowDown: -20 }[ event.key ]; if ( delta ) { - const { resizable } = resizableBoxRef.current; - const fromHeight = isAutoHeight - ? resizable.offsetHeight - : openHeight; + const pane = metaBoxesMainRef.current; + const fromHeight = isAutoHeight ? pane.offsetHeight : openHeight; const nextHeight = Math.min( max, Math.max( min, delta + fromHeight ) ); - resizableBoxRef.current.updateSize( { - height: nextHeight, - // Oddly, if left unspecified a subsequent drag gesture applies a fixed - // width and the pane fails to shrink/grow with parent width changes from - // sidebars opening/closing or window resizes. - width: 'auto', - } ); + // Syncs the ref’d height to ensure a dragged resize starts with it. + // TODO: there may be a nicer way to handle things so this isn’t required. + heightRef.current = nextHeight; setPreference( 'core/edit-post', 'metaBoxesMainOpenHeight', @@ -303,86 +321,63 @@ function MetaBoxesMain( { isLegacy } ) { } }; + const paneLabel = __( 'Meta Boxes' ); + return ( - { - target.setPointerCapture( pointerId ); - } } - onResizeStart={ ( event, direction, elementRef ) => { - if ( isAutoHeight ) { - const heightNow = elementRef.offsetHeight; - // Sets the starting height to avoid visual jumps in height and - // aria-valuenow being `NaN` for the first (few) resize events. - resizableBoxRef.current.updateSize( { height: heightNow } ); - // Causes `maxHeight` to update to full `max` value instead of half. - setIsUntouched( false ); + ); } diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index 5d0a9a29c329ca..b9b3cc8de945ff 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -1,69 +1,87 @@ -$resize-handle-height: $grid-unit-30; - .edit-post-meta-boxes-main { filter: drop-shadow(0 -1px rgba($color: #000, $alpha: 0.133)); // 0.133 = $gray-200 but with alpha. background-color: $white; clear: both; // This is seemingly only needed in case the canvas is not iframe’d. + display: flex; + flex-direction: column; + overflow: hidden; +} - &:not(details) { - padding-top: $resize-handle-height; - } +.edit-post-meta-boxes-main__presenter { + flex-shrink: 0; + display: flex; + box-shadow: 0 $border-width $gray-300; + position: relative; + z-index: 1; + appearance: none; + padding: 0; + border: none; + outline: none; + background-color: transparent; - // The component renders as a details element in short viewports. - &:is(details) { - & > summary { - cursor: pointer; - color: $gray-900; - background-color: $white; - height: $button-size-compact; - line-height: $button-size-compact; - font-size: 13px; - padding-left: $grid-unit-30; - box-shadow: 0 $border-width $gray-300; - } + .is-toggle-only > & { + cursor: pointer; + height: $button-size-compact; + justify-content: space-between; + align-items: center; + padding-inline: $grid-unit-30 $grid-unit-15; - &[open] > summary { - position: sticky; - top: 0; - z-index: 1; + &:is(:hover, :focus-visible) { + color: var(--wp-admin-theme-color); + } + &:focus-visible::after { + content: ""; + position: absolute; + inset: 2px 3px 2px 2px; + @include button-style__focus(); } } -} - -.edit-post-meta-boxes-main__resize-handle { - display: flex; - // The position is absolute by default inline style of ResizableBox. - inset: 0 0 auto 0; - height: $resize-handle-height; - box-shadow: 0 $border-width $gray-300; - & > button { - appearance: none; - cursor: inherit; - margin: auto; - padding: 0; - border: none; - outline: none; - background-color: $gray-300; - width: $grid-unit-80; - height: $grid-unit-05; - border-radius: $radius-small; - transition: width 0.3s ease-out; - @include reduce-motion("transition"); + .is-toggle-only:not(.is-open) > & { + overflow: hidden; } - &:hover > button, - > button:focus { - background-color: var(--wp-admin-theme-color); - width: $grid-unit-80 + $grid-unit-20; + .is-resizable > & { + cursor: row-resize; + height: $grid-unit-30; + @media (pointer: coarse) { + height: $button-size-compact; + } + + > div { + width: $grid-unit-80; + height: inherit; + margin: auto; + + &::before { + content: ""; + background-color: $gray-300; + position: absolute; + inset: 50% auto auto 50%; + transform: translate(-50%, -50%); + width: inherit; + height: $grid-unit-05; + border-radius: $radius-small; + transition: width 0.3s ease-out; + @include reduce-motion("transition"); + } + } + + &:is(:hover, :focus) > div::before { + background-color: var(--wp-admin-theme-color); + width: $grid-unit-80 + $grid-unit-20; + } } } .edit-post-meta-boxes-main__liner { overflow: auto; - max-height: 100%; // Keep the contents behind the resize handle or details summary. isolation: isolate; + + .is-toggle-only:not(.is-open) > & { + height: 0; + } } .has-metaboxes .editor-visual-editor { From 0fb80be35aec158d73210f9933539bcb2564dfa3 Mon Sep 17 00:00:00 2001 From: Mitchell Austin Date: Wed, 18 Sep 2024 11:12:40 -0700 Subject: [PATCH 02/16] Consolidate details of updating height into function --- .../edit-post/src/components/layout/index.js | 67 +++++++------------ 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 768db1643b5e02..3f1c40858471f5 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -223,44 +223,40 @@ function MetaBoxesMain( { isLegacy } ) { const separatorHelpId = useId(); const heightRef = useRef(); - const { startDrag, endDrag, isDragging } = useDragging( { - onDragStart: () => { - if ( isAutoHeight || heightRef.current === undefined ) { - // Sets the starting height to avoid visual jumps in height and - // aria-valuenow being `NaN` for the first (few) resize events. - heightRef.current = metaBoxesMainRef.current.offsetHeight; - metaBoxesMainRef.current.style.height = `${ heightRef.current }px`; - } else if ( heightRef.current > max ) { - // Starts from max in case shortening the window has imposed it. - heightRef.current = max; - } - }, - onDragMove: ( { movementY } ) => { - const { current: fromHeight } = heightRef; - const candidateHeight = fromHeight - movementY; + const actualizeHeight = ( candidateHeight, isPersistent ) => { + const nextHeight = Math.min( max, Math.max( min, candidateHeight ) ); + if ( isPersistent ) { + heightRef.current = nextHeight; + setPreference( + 'core/edit-post', + 'metaBoxesMainOpenHeight', + nextHeight + ); + } else { // Keeps the unconstrained height for subsequent calculations so that when // dragging takes the height out of range and the cursor detaches from its // initial position relative to the drag handle, any movement back toward the // range is absorbed without affecting the applied height as that would // otherwise maintain the cursor’s detachment. heightRef.current = candidateHeight; - const nextHeight = Math.min( - max, - Math.max( min, candidateHeight ) - ); metaBoxesMainRef.current.style.height = `${ nextHeight }px`; separatorRef.current.ariaValueNow = getAriaValueNow( nextHeight ); + } + }; + const { startDrag, endDrag, isDragging } = useDragging( { + onDragStart: () => { + if ( isAutoHeight || heightRef.current === undefined ) { + // Sets the starting height to avoid visual jumps in height and + // aria-valuenow being `NaN` for the first (few) resize events. + actualizeHeight( metaBoxesMainRef.current.offsetHeight ); + } else if ( heightRef.current > max ) { + // Starts from max in case shortening the window has imposed it. + actualizeHeight( max ); + } }, - onDragEnd: () => { - const nextHeight = metaBoxesMainRef.current.offsetHeight; - // Syncs the final height to ensure the next resize starts with it. - heightRef.current = nextHeight; - setPreference( - 'core/edit-post', - 'metaBoxesMainOpenHeight', - nextHeight - ); - }, + onDragMove: ( { movementY } ) => + actualizeHeight( heightRef.current - movementY ), + onDragEnd: () => actualizeHeight( heightRef.current, true ), } ); if ( ! hasAnyVisible ) { @@ -306,18 +302,7 @@ function MetaBoxesMain( { isLegacy } ) { if ( delta ) { const pane = metaBoxesMainRef.current; const fromHeight = isAutoHeight ? pane.offsetHeight : openHeight; - const nextHeight = Math.min( - max, - Math.max( min, delta + fromHeight ) - ); - // Syncs the ref’d height to ensure a dragged resize starts with it. - // TODO: there may be a nicer way to handle things so this isn’t required. - heightRef.current = nextHeight; - setPreference( - 'core/edit-post', - 'metaBoxesMainOpenHeight', - nextHeight - ); + actualizeHeight( delta + fromHeight, true ); } }; From c95b5c9c4c977ef9dd4e91bd81a3a089f6e48ddd Mon Sep 17 00:00:00 2001 From: Mitchell Austin Date: Wed, 18 Sep 2024 11:16:23 -0700 Subject: [PATCH 03/16] Reduce conditionals for toggle vs. resizable --- .../edit-post/src/components/layout/index.js | 103 +++++++++--------- 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 3f1c40858471f5..2ffc3e43d83888 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -305,62 +305,65 @@ function MetaBoxesMain( { isLegacy } ) { actualizeHeight( delta + fromHeight, true ); } }; - + const className = 'edit-post-meta-boxes-main'; const paneLabel = __( 'Meta Boxes' ); + let paneProps, paneButtonProps; + if ( isShort ) { + paneProps = { + className: clsx( className, 'is-toggle-only', isOpen && 'is-open' ), + }; + paneButtonProps = { + 'aria-label': __( 'Expand or collapse meta boxes pane' ), + onClick: toggle, + children: ( + <> + { paneLabel } + + + ), + }; + } else { + paneProps = { + ref: metaBoxesMainRef, + className: clsx( className, 'is-resizable' ), + style: { height: openHeight, maxHeight: usedMax }, + }; + paneButtonProps = { + ref: separatorRef, + role: 'separator', + 'aria-valuenow': usedAriaValueNow, + 'aria-label': __( 'Drag to resize' ), + 'aria-describedby': separatorHelpId, + onKeyDown: onSeparatorKeyDown, + onMouseDown: startDrag, + onMouseUp: endDrag, + // Avoids hiccups while dragging over objects like iframes and ensures that + // the event to end the drag is captured by the target (resize handle) + // whether or not it’s under the pointer. + onPointerDown: ( { pointerId, target } ) => { + target.setPointerCapture( pointerId ); + }, + children: ( + +
+ + { __( + 'Use up and down arrow keys to resize the metabox pane.' + ) } + +
+
+ ), + }; + } return ( -