-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Try/block toolbar refactoring #9282
Changes from all commits
77c2961
8057edf
5b64759
a37162e
abf4336
7cd977e
5b9271c
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,24 @@ | ||
# ResponsiveToolbar | ||
|
||
A wrapper that displays a resonsive toolbar. If there's enough space in the wrapper, the children elements are shown inline. If not, the elements move to a dropdown menu. | ||
|
||
## Usage | ||
|
||
```jsx | ||
import { ResponsiveToolbar } from '@wordpress/components'; | ||
|
||
const MyResponsiveToolbar = () => ( | ||
<ResponsiveToolbar> | ||
<IconButton icon="plus" /> | ||
<Button>Long Button</Button> | ||
<IconButton icon="minus" /> | ||
<div> | ||
<Button>Group1</Button> | ||
<Button>Group2</Button> | ||
<Button>Group3</Button> | ||
</div> | ||
<Button>Other Button</Button> | ||
<Button>Long Button</Button> | ||
</ResponsiveToolbar> | ||
); | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import classnames from 'classnames'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Component, createRef } from '@wordpress/element'; | ||
import { withInstanceId } from '@wordpress/compose'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import IconButton from '../icon-button'; | ||
import Dropdown from '../dropdown'; | ||
import Disabled from '../disabled'; | ||
|
||
/** | ||
* Module constants | ||
*/ | ||
const OFFSET = 60; | ||
|
||
class ResponsiveToolbar extends Component { | ||
constructor() { | ||
super( ...arguments ); | ||
this.state = { | ||
countHiddenChildren: 0, | ||
}; | ||
this.container = createRef(); | ||
this.hiddenContainer = createRef(); | ||
|
||
this.updateHiddenItems = this.updateHiddenItems.bind( this ); | ||
this.throttledUpdateHiddenItems = this.throttledUpdateHiddenItems.bind( this ); | ||
} | ||
|
||
componentDidMount() { | ||
this.toggleWindowEvents( true ); | ||
this.updateHiddenItems(); | ||
|
||
// If the children change, we need to recompute | ||
this.observer = new window.MutationObserver( this.updateHiddenItems ); | ||
this.observer.observe( this.hiddenContainer.current, { childList: true } ); | ||
} | ||
|
||
componentWillUnmount() { | ||
this.toggleWindowEvents( false ); | ||
this.observer.disconnect(); | ||
if ( this.style ) { | ||
this.style.parentNode.removeChild( this.style ); | ||
} | ||
} | ||
|
||
toggleWindowEvents( isListening ) { | ||
const handler = isListening ? 'addEventListener' : 'removeEventListener'; | ||
|
||
window.cancelAnimationFrame( this.rafHandle ); | ||
window[ handler ]( 'resize', this.throttledUpdateHiddenItems ); | ||
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. Should we use |
||
} | ||
|
||
throttledUpdateHiddenItems() { | ||
this.rafHandle = window.requestAnimationFrame( () => this.updateHiddenItems() ); | ||
} | ||
|
||
updateHiddenItems() { | ||
const { instanceId } = this.props; | ||
const containerRect = this.container.current.getBoundingClientRect(); | ||
let countHiddenChildren = 0; | ||
const total = this.hiddenContainer.current.childNodes.length; | ||
this.hiddenContainer.current.childNodes.forEach( ( child ) => { | ||
const childRect = child.getBoundingClientRect(); | ||
if ( | ||
childRect.left < containerRect.left || | ||
childRect.right > containerRect.right - OFFSET | ||
) { | ||
countHiddenChildren++; | ||
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. Minor: This would make sense as a const total = childNodes.reduce( ( result, child ) => {
// ...
}, 0 ); |
||
} | ||
} ); | ||
|
||
if ( countHiddenChildren !== this.state.countHiddenChildren ) { | ||
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. Early |
||
this.setState( { | ||
countHiddenChildren, | ||
} ); | ||
|
||
if ( this.style ) { | ||
this.style.parentNode.removeChild( this.style ); | ||
} | ||
const styleNode = document.createElement( 'style' ); | ||
styleNode.innerHTML = ` | ||
#responsive-toolbar-${ instanceId } > *:nth-child(n+${ total - countHiddenChildren + 2 }):not(.components-responsive-toolbar__dropdown) { | ||
display: none; | ||
} | ||
|
||
.components-responsive-toolbar__dropdown-content-${ instanceId } > *:nth-child(-n+${ total - countHiddenChildren }) { | ||
display: none; | ||
} | ||
`; | ||
document.body.appendChild( styleNode ); | ||
this.style = styleNode; | ||
} | ||
} | ||
|
||
render() { | ||
const defaultRenderToggle = ( { onToggle, isOpen } ) => ( | ||
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. Why not a top-level constant variable? To avoid unnecessary garbage collection and React reconciliation. |
||
<IconButton | ||
icon="arrow-down-alt2" | ||
onClick={ onToggle } | ||
aria-expanded={ isOpen } | ||
/> | ||
); | ||
const { | ||
children, | ||
instanceId, | ||
className, | ||
extraContentClassName, | ||
renderToggle = defaultRenderToggle, | ||
...props | ||
} = this.props; | ||
const { countHiddenChildren } = this.state; | ||
|
||
return ( | ||
<div | ||
id={ `responsive-toolbar-${ instanceId }` } | ||
className={ classnames( className, 'components-responsive-toolbar' ) } | ||
ref={ this.container } | ||
{ ...props } | ||
> | ||
<Disabled> | ||
<div className="components-responsive-toolbar__compute-position" ref={ this.hiddenContainer }> | ||
{ children } | ||
</div> | ||
</Disabled> | ||
|
||
{ children } | ||
|
||
{ countHiddenChildren > 0 && ( | ||
<Dropdown | ||
position="inline" | ||
className="components-responsive-toolbar__dropdown" | ||
contentClassName={ classnames( | ||
extraContentClassName, | ||
'components-responsive-toolbar__dropdown-content', | ||
`components-responsive-toolbar__dropdown-content-${ instanceId }` ) | ||
} | ||
renderToggle={ renderToggle } | ||
renderContent={ () => children } | ||
/> | ||
) } | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default withInstanceId( ResponsiveToolbar ); |
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.
Tab once more.