-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Components: Extract a generic dropdown component
- Loading branch information
1 parent
3a9048c
commit 3889e81
Showing
10 changed files
with
373 additions
and
325 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
Popover | ||
======= | ||
|
||
Dropdown is a React component to render a button that opens a floating content modal when clicked. | ||
This components takes care of updating the state of the dropdown menu (opened/closed), handles closing the menu when clicking outside | ||
and uses render props to render the button and the content. | ||
|
||
## Usage | ||
|
||
|
||
```jsx | ||
import { Dropdown } from '@wordpress/components'; | ||
|
||
function MyDropdownMenu() { | ||
return ( | ||
<Dropdown | ||
className="my-container-class-name" | ||
contentClassName="my-popover-content-classname" | ||
position="bottom right" | ||
renderToggle={ ( { isOpen, onToggle } ) => ( | ||
<button onClick={ onToggle } aria-expanded={ isOpen }> | ||
Toggle Popover! | ||
</button> | ||
) } | ||
renderContent={ () => ( | ||
This is the content of the popover. | ||
) } | ||
> | ||
); | ||
} | ||
``` | ||
|
||
## Props | ||
|
||
The component accepts the following props. Props not included in this set will be applied to the element wrapping Popover content. | ||
|
||
### className | ||
|
||
className of the global container | ||
|
||
- Type: `String` | ||
- Required: No | ||
|
||
### contentClassName | ||
|
||
If you want to target the dropdown menu for styling purposes, you need to provide a contentClassName because it's not being rendered as a children of the container nodee. | ||
|
||
- Type: `String` | ||
- Required: No | ||
|
||
### position | ||
|
||
The direction in which the popover should open relative to its parent node. Specify y- and x-axis as a space-separated string. Supports `"top"`, `"bottom"` y axis, and `"left"`, `"center"`, `"right"` x axis. | ||
|
||
- Type: `String` | ||
- Required: No | ||
- Default: `"top center"` | ||
|
||
## renderToggle | ||
|
||
A callback invoked to render the Dropdown Toggle Button. | ||
|
||
- Type: `Function` | ||
- Required: Yes | ||
|
||
The first argument of the callback is an object containing the following properties: | ||
|
||
- `isOpen`: whether the dropdown menu is opened or not | ||
- `onToggle`: A function switching the dropdown menu's state from open to closed and vice versa | ||
- `onClose`: A function that closes the menu if invoked | ||
|
||
## renderContent | ||
|
||
A callback invoked to render the content of the dropdown menu. Its first argument is the same as the `renderToggle` prop. | ||
|
||
- Type: `Function` | ||
- Required: Yes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/** | ||
* WordPress Dependeencies | ||
*/ | ||
import { Component } from '@wordpress/element'; | ||
|
||
/** | ||
* Internal Dependencies | ||
*/ | ||
import Popover from '../popover'; | ||
|
||
class Dropdown extends Component { | ||
constructor() { | ||
super( ...arguments ); | ||
this.toggle = this.toggle.bind( this ); | ||
this.close = this.close.bind( this ); | ||
this.clickOutside = this.clickOutside.bind( this ); | ||
this.bindContainer = this.bindContainer.bind( this ); | ||
this.state = { | ||
isOpen: false, | ||
}; | ||
} | ||
|
||
bindContainer( ref ) { | ||
this.container = ref; | ||
} | ||
|
||
toggle() { | ||
this.setState( ( state ) => ( { | ||
isOpen: ! state.isOpen, | ||
} ) ); | ||
} | ||
|
||
clickOutside( event ) { | ||
if ( ! this.container.contains( event.target ) ) { | ||
this.close(); | ||
} | ||
} | ||
|
||
close() { | ||
this.setState( { isOpen: false } ); | ||
} | ||
|
||
render() { | ||
const { isOpen } = this.state; | ||
const { renderContent, renderToggle, position = 'bottom', className, contentClassName } = this.props; | ||
const args = { isOpen, onToggle: this.toggle, onClose: this.close }; | ||
return ( | ||
<div className={ className } ref={ this.bindContainer }> | ||
{ renderToggle( args ) } | ||
<Popover | ||
className={ contentClassName } | ||
isOpen={ isOpen } | ||
position={ position } | ||
onClickOutside={ this.clickOutside } | ||
> | ||
{ renderContent( args ) } | ||
</Popover> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default Dropdown; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { mount } from 'enzyme'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import Dropdown from '../'; | ||
|
||
describe( 'Dropdown', () => { | ||
it( 'should toggle the dropdown properly', () => { | ||
const wrapper = mount( <Dropdown | ||
className="container" | ||
contentClassName="content" | ||
renderToggle={ ( { isOpen, onToggle } ) => ( | ||
<button aria-expanded={ isOpen } onClick={ onToggle }>Toggleee</button> | ||
) } | ||
renderContent={ () => 'content' } | ||
/> ); | ||
|
||
const button = wrapper.find( 'button' ); | ||
const popover = wrapper.find( 'Popover' ); | ||
|
||
expect( button ).toHaveLength( 1 ); | ||
expect( popover.prop( 'isOpen' ) ).toBe( false ); | ||
expect( button.prop( 'aria-expanded' ) ).toBe( false ); | ||
button.simulate( 'click' ); | ||
expect( popover.prop( 'isOpen' ) ).toBe( true ); | ||
expect( button.prop( 'aria-expanded' ) ).toBe( true ); | ||
} ); | ||
|
||
it( 'should close the dropdown when calling onClose', () => { | ||
const wrapper = mount( <Dropdown | ||
className="container" | ||
contentClassName="content" | ||
renderToggle={ ( { isOpen, onToggle, onClose } ) => [ | ||
<button key="open" className="open" aria-expanded={ isOpen } onClick={ onToggle }>Toggleee</button>, | ||
<button key="close" className="close" onClick={ onClose } >closee</button>, | ||
] } | ||
renderContent={ () => 'content' } | ||
/> ); | ||
|
||
const openButton = wrapper.find( '.open' ); | ||
const closeButton = wrapper.find( '.close' ); | ||
const popover = wrapper.find( 'Popover' ); | ||
expect( popover.prop( 'isOpen' ) ).toBe( false ); | ||
openButton.simulate( 'click' ); | ||
expect( popover.prop( 'isOpen' ) ).toBe( true ); | ||
closeButton.simulate( 'click' ); | ||
expect( popover.prop( 'isOpen' ) ).toBe( false ); | ||
} ); | ||
} ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.