-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
List View: allow expanding and collapsing of nested blocks #32117
Changes from 13 commits
b440453
2e30a75
747348d
e4ea861
c63a159
8bcdeb4
c8c03e0
7f8901e
f05cff0
92f353c
c36de9e
7fab9c8
1b8b13d
f4b5a5f
90dd73f
7065ab6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { chevronRightSmall, Icon } from '@wordpress/icons'; | ||
export default function BlockNavigationExpander( { onClick } ) { | ||
return ( | ||
// Keyboard events are handled by TreeGrid see: components/src/tree-grid/index.js | ||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions | ||
<span | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The implementation as a pseudo-element in the w3 example was really surprising to me. I guess that makes it completely hidden from the accessibility tree, in which case the span could have It might be worth adding some comments to the component explaining why this is implemented like it is—I imagine many devs with good intentions might be tempted to change it to a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call, I've updated this in f4b5a5f For folks 👀 we can see the pseudo element in https://www.w3.org/TR/wai-aria-practices/examples/treegrid/treegrid-1.html |
||
className="block-editor-block-navigation__expander" | ||
onClick={ ( event ) => onClick( event, { forceToggle: true } ) } | ||
> | ||
<Icon icon={ chevronRightSmall } /> | ||
</span> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,6 +40,12 @@ | |
&.is-branch-selected.is-selected .block-editor-block-navigation-block-contents { | ||
border-radius: 2px 2px 0 0; | ||
} | ||
|
||
&[aria-expanded="false"] { | ||
&.is-branch-selected.is-selected .block-editor-block-navigation-block-contents { | ||
border-radius: 2px; | ||
} | ||
} | ||
&.is-branch-selected:not(.is-selected) .block-editor-block-navigation-block-contents { | ||
// Lighten a CSS variable without introducing a new SASS variable | ||
background: | ||
|
@@ -60,7 +66,7 @@ | |
align-items: center; | ||
width: 100%; | ||
height: auto; | ||
padding: ($grid-unit-15 / 2) $grid-unit-15; | ||
padding: ($grid-unit-15 / 2) $grid-unit-15 ($grid-unit-15 / 2) 0; | ||
text-align: left; | ||
color: $gray-900; | ||
border-radius: 2px; | ||
|
@@ -121,8 +127,8 @@ | |
|
||
.block-editor-block-icon { | ||
align-self: flex-start; | ||
margin-right: ( $grid-unit-05 * 2.5 ); // 10px is off the 4px grid, but required for visual alignment between block name and subsequent nested icon | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CC: @jameskoster, the icon was scaled down to 20px, so it didn't show true pixels. I shifted the metrics around a bit, but would appreciate you taking a look when you have a chance, thank you. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
width: 20px; | ||
margin-right: $grid-unit-10; | ||
width: $icon-size; | ||
} | ||
|
||
.block-editor-block-navigation-block__menu-cell, | ||
|
@@ -274,15 +280,53 @@ | |
} | ||
} | ||
|
||
// Chevron container metrics. | ||
.block-editor-block-navigation__expander { | ||
height: $icon-size; | ||
margin-left: $grid-unit-05; | ||
width: $icon-size; | ||
} | ||
|
||
// First level of indentation is aria-level 2, max indent is 8. | ||
// Indent is a full icon size, plus 4px which optically aligns child icons to the text label above. | ||
$block-navigation-max-indent: 8; | ||
.block-editor-block-navigation-leaf[aria-level] .block-editor-block-icon { | ||
margin-left: ( $grid-unit-30 + $grid-unit-05 ) * $block-navigation-max-indent; | ||
.block-editor-block-navigation-leaf[aria-level] .block-editor-block-navigation__expander { | ||
margin-left: ( $icon-size ) * $block-navigation-max-indent + 4 * ( $block-navigation-max-indent - 1 ); | ||
} | ||
|
||
.block-editor-block-navigation-leaf:not([aria-level="1"]) { | ||
.block-editor-block-navigation__expander { | ||
margin-right: 4px; | ||
} | ||
} | ||
@for $i from 0 through $block-navigation-max-indent { | ||
.block-editor-block-navigation-leaf[aria-level="#{ $i + 1 }"] .block-editor-block-icon { | ||
margin-left: ( $grid-unit-30 + $grid-unit-05 ) * $i; | ||
|
||
@for $i from 0 to $block-navigation-max-indent { | ||
.block-editor-block-navigation-leaf[aria-level="#{ $i + 1 }"] .block-editor-block-navigation__expander { | ||
@if $i - 1 >= 0 { | ||
margin-left: ( $icon-size * $i ) + 4 * ($i - 1); | ||
} | ||
@else { | ||
margin-left: ( $icon-size * $i ); | ||
} | ||
} | ||
} | ||
|
||
.block-editor-block-navigation-leaf .block-editor-block-navigation__expander { | ||
visibility: hidden; | ||
} | ||
|
||
// Point downwards when open. | ||
.block-editor-block-navigation-leaf[aria-expanded="true"] .block-editor-block-navigation__expander svg { | ||
visibility: visible; | ||
transition: transform 0.2s ease; | ||
transform: rotate(90deg); | ||
@include reduce-motion("transition"); | ||
} | ||
|
||
// Point rightwards when closed | ||
.block-editor-block-navigation-leaf[aria-expanded="false"] .block-editor-block-navigation__expander svg { | ||
visibility: visible; | ||
transform: rotate(0deg); | ||
transition: transform 0.2s ease; | ||
@include reduce-motion("transition"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,13 @@ | |
|
||
import { __experimentalTreeGrid as TreeGrid } from '@wordpress/components'; | ||
import { useDispatch } from '@wordpress/data'; | ||
import { useCallback, useEffect, useMemo, useRef } from '@wordpress/element'; | ||
import { | ||
useCallback, | ||
useEffect, | ||
useMemo, | ||
useRef, | ||
useReducer, | ||
} from '@wordpress/element'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
/** | ||
|
@@ -17,6 +23,16 @@ import useBlockNavigationDropZone from './use-block-navigation-drop-zone'; | |
import { store as blockEditorStore } from '../../store'; | ||
|
||
const noop = () => {}; | ||
const expanded = ( state, action ) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only record state when someone toggles an expand/collapse chevron. One case I haven't covered is when blocks are deleted, we might have some extra state left over eg: state = {
'block-client-id-a': false,
'block-client-id-b': true,
'deleted-client-id': false
}; It is possible to prune this by dispatching an event as a useEffect side effect when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, agreed, it's probably not a big issue. I doubt this will ever become a huge amount of data. I have ideas but I think it's an effort vs. reward question more than anything:
|
||
switch ( action.type ) { | ||
case 'expand': | ||
return { ...state, ...{ [ action.clientId ]: true } }; | ||
case 'collapse': | ||
return { ...state, ...{ [ action.clientId ]: false } }; | ||
default: | ||
return state; | ||
} | ||
}; | ||
|
||
/** | ||
* Wrap `BlockNavigationRows` with `TreeGrid`. BlockNavigationRows is a | ||
|
@@ -52,6 +68,7 @@ export default function BlockNavigationTree( { | |
}, | ||
[ selectBlock, onSelect ] | ||
); | ||
const [ expandedState, setExpandedState ] = useReducer( expanded, {} ); | ||
|
||
let { | ||
ref: treeGridRef, | ||
|
@@ -67,18 +84,29 @@ export default function BlockNavigationTree( { | |
blockDropTarget = undefined; | ||
} | ||
|
||
const expand = ( clientId ) => | ||
setExpandedState( { type: 'expand', clientId } ); | ||
const collapse = ( clientId ) => | ||
setExpandedState( { type: 'collapse', clientId } ); | ||
|
||
const contextValue = useMemo( | ||
() => ( { | ||
__experimentalFeatures, | ||
__experimentalPersistentListViewFeatures, | ||
blockDropTarget, | ||
isTreeGridMounted: isMounted.current, | ||
expandedState, | ||
expand, | ||
collapse, | ||
} ), | ||
[ | ||
__experimentalFeatures, | ||
__experimentalPersistentListViewFeatures, | ||
blockDropTarget, | ||
isMounted.current, | ||
expandedState, | ||
expand, | ||
collapse, | ||
] | ||
); | ||
|
||
|
@@ -87,6 +115,8 @@ export default function BlockNavigationTree( { | |
className="block-editor-block-navigation-tree" | ||
aria-label={ __( 'Block navigation structure' ) } | ||
ref={ treeGridRef } | ||
onCollapseRow={ collapse } | ||
onExpandRow={ expand } | ||
> | ||
<BlockNavigationContext.Provider value={ contextValue }> | ||
<BlockNavigationBranch | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally in the future we don't need a component like this, and can instead use CSS rules to point at an SVG image.