From 8e1e5bc446f33cf911d4333f04fc3553db303f70 Mon Sep 17 00:00:00 2001 From: jguffey Date: Fri, 17 Mar 2017 14:14:22 -0700 Subject: [PATCH 1/5] Create a more designed Menu Dropdown Button I had a similar use case on a project in the past, so I think it would be useful to have a dropdown that acts as a button. Mostly it's just an example of what one can do with this library, but it might not be immediately apparent how to create one of these. I also added `yarn.lock` to the .gitignore for those of us that want to use yarn. --- .gitignore | 1 + stories/Menu.story.js | 11 +++++++++ stories/helpers/StatefulMenu.js | 40 +++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 stories/helpers/StatefulMenu.js diff --git a/.gitignore b/.gitignore index e3b86d0..4caa8a0 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ coverage examples/bundle.js examples/style.css npm-debug.log +yarn.lock diff --git a/stories/Menu.story.js b/stories/Menu.story.js index 7112c6f..175fb18 100644 --- a/stories/Menu.story.js +++ b/stories/Menu.story.js @@ -5,6 +5,7 @@ import faker from 'faker' import { Card, Button, IconButton } from 'react-mdl' import { Menu, MenuItem } from '../src' +import StatefulMenu from './helpers/StatefulMenu' const bigMenuItems = [...Array(35).keys()].map(i => Menu Item {i} @@ -22,6 +23,16 @@ storiesOf('Menu', module) console.log('select three')}>Three )) + .add('default with icon', () => ( +
+

Menu with Icon

+

Useful for a button-style dropdown.

+ +
+ )) .add('position', () => { const styles = { center: { diff --git a/stories/helpers/StatefulMenu.js b/stories/helpers/StatefulMenu.js new file mode 100644 index 0000000..b187a17 --- /dev/null +++ b/stories/helpers/StatefulMenu.js @@ -0,0 +1,40 @@ +import React, { Component, PropTypes } from 'react'; +import { Button, Icon } from 'react-mdl' +import { Menu, MenuItem } from '../../src' + +export default class StatefulMenu extends Component { + + constructor(props) { + super(props); + this.state = { value: null } + } + + static propTypes = { + options: PropTypes.arrayOf(React.PropTypes.string).isRequired, + value: PropTypes.string + } + + static defaultProps = { + value: null + } + + onChange = (value) => { + this.setState({ value }) + console.log(`select ${value}`) + } + + render() { + const { value, options } = this.props; + const val = this.state.value ? this.state.value : value ? value : options[0]; + return ( + {val} } + align={'tl bl'} + > + { options.map((option) => ( + this.onChange(option) }>{option} + ))} + + ) + } +} From 431924101bb426cb0a06ebdc3511e30251bb750a Mon Sep 17 00:00:00 2001 From: jguffey Date: Mon, 20 Mar 2017 15:33:54 -0700 Subject: [PATCH 2/5] Basically, within a dialog, we can't use this plugin, instead use the default MDL menu components --- stories/Menu.story.js | 41 +++++++++++++++++++++++- stories/helpers/DialogWrapper.js | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 stories/helpers/DialogWrapper.js diff --git a/stories/Menu.story.js b/stories/Menu.story.js index 7112c6f..fe27d31 100644 --- a/stories/Menu.story.js +++ b/stories/Menu.story.js @@ -2,9 +2,10 @@ import React, { Component } from 'react' import { storiesOf, action } from '@kadira/storybook' import faker from 'faker' -import { Card, Button, IconButton } from 'react-mdl' +import { Card, Button, IconButton, Menu as MDLMenu } from 'react-mdl' import { Menu, MenuItem } from '../src' +import DialogWrapper from './helpers/DialogWrapper' const bigMenuItems = [...Array(35).keys()].map(i => Menu Item {i} @@ -231,3 +232,41 @@ storiesOf('Menu', module) ) }) + .add('opens outside portal', () => { + return ( +
+ + + One + Two + Three + I + Am + Free + +
+ ) + }) + .add('opens within dialog', () => { + return ( +
+ + + + One + Two + Three + I + Am + Free + + +
+ ) + }) diff --git a/stories/helpers/DialogWrapper.js b/stories/helpers/DialogWrapper.js new file mode 100644 index 0000000..ab71d32 --- /dev/null +++ b/stories/helpers/DialogWrapper.js @@ -0,0 +1,55 @@ +import React, { Component, PropTypes } from 'react' +import { Button, Dialog, DialogTitle, DialogActions, DialogContent } from 'react-mdl' + + +export default class DialogWrapper extends Component { + + constructor(props) { + super(props); + this.state = { + dialogOpen: props.dialogOpen + } + } + + static propTypes = { + dialogOpen: PropTypes.bool, + children: PropTypes.arrayOf(PropTypes.element) + } + + static defaultProps = { + dialogOpen: false, + children: [] + } + + handelCloseDialog = () => { + this.setState({ + dialogOpen: false + }); + } + + handleOpenDialog = () => { + this.setState({ + dialogOpen: true + }); + } + + render() { + const { children } = this.props; + const { dialogOpen } = this.state; + return( +
+ + + How does it render within a Dialog? + + { children } + + + + + +
+ ); + } + +} From 55523a9a3e9ed025048daafdf8c1c6e939454197 Mon Sep 17 00:00:00 2001 From: jguffey Date: Mon, 20 Mar 2017 18:22:42 -0700 Subject: [PATCH 3/5] Finally got the dialog to render the dropdown. Needs to be cleaned up before PR. 1. Because only can be displayed over a , we must render the menu list within a 2. Various compolications with Portal, because of 1. we must also preserve Poral. This also perserves all other benefits of using this over mdl-react 3. Portal passes props.closePortal implicitly, thus we need DialogWrapper component to accept and pass this prop to children. This seems to be working, I need to do more tests and complete the following items: To do: 1. Split DialogWrapper into it's own file 2. Test cross browser --- src/Dropdown/Dropdown.css | 10 ++++++++++ src/Dropdown/Dropdown.js | 41 ++++++++++++++++++++++++++++++++++----- src/Menu/Menu.js | 2 +- stories/Menu.story.js | 18 +++++------------ 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/src/Dropdown/Dropdown.css b/src/Dropdown/Dropdown.css index f6b21bd..deb4be8 100644 --- a/src/Dropdown/Dropdown.css +++ b/src/Dropdown/Dropdown.css @@ -14,3 +14,13 @@ .mdl-dropdown *, .mdl-dropdown *:before, .mdl-dropdown *:after { box-sizing: inherit; } + +.portal__dialog { + border: 0 none; + margin: 0; + padding: 0; +} + +.portal__dialog::backdrop { + background: transparent; +} diff --git a/src/Dropdown/Dropdown.js b/src/Dropdown/Dropdown.js index ea1f6b4..bb78ad5 100644 --- a/src/Dropdown/Dropdown.js +++ b/src/Dropdown/Dropdown.js @@ -63,9 +63,10 @@ export default class Dropdown extends Component { useTargetMinHeight, viewportPadding: pad, } = this.props + const mdlParent = findDOMNode(this.dialogDom); // append class name - portalNode.classList.add('mdl-dropdown') + mdlParent.classList.add('mdl-dropdown') // window is our boundary const { innerWidth, innerHeight } = window @@ -74,7 +75,7 @@ export default class Dropdown extends Component { const targetNode = this.props.targetNode || findDOMNode(this) // get bounding rects - const portal = portalNode.getBoundingClientRect() + const portal = portalNode.getBoundingClientRect() // TOOD: Test const target = targetNode.getBoundingClientRect() // parse position @@ -167,7 +168,7 @@ export default class Dropdown extends Component { // tether this.tether = new Tether({ - element: portalNode, + element: mdlParent, target: targetNode, attachment: `${ay} ${ax}`, targetAttachment: `${ty} ${tx}`, @@ -179,7 +180,7 @@ export default class Dropdown extends Component { }) // fade in - this.applyStyles(portalNode, { opacity: 1 }) + this.applyStyles(mdlParent, { opacity: 1 }) // force reposition if (portal.height > maxHeight) { @@ -213,9 +214,39 @@ export default class Dropdown extends Component { onOpen={this.onOpen} beforeClose={this.beforeClose} > - {children} + this.dialogDom = c} + > + {children} + ) } +} + +/* + * We must make another component so that this.closePortal + * gets passed down and handed to children + */ +class DialogWrapper extends Component { + componentDidMount() { + this.dialogDom.showModal(this.props.target) + } + + componentWillUnmount() { + this.dialogDom.close() + } + render() { + const { closePortal, children } = this.props + return( + this.dialogDom = c}> + { React.cloneElement( + children, + {closePortal: closePortal} + )} + + ); + } } diff --git a/src/Menu/Menu.js b/src/Menu/Menu.js index 8a2d88d..5b45774 100644 --- a/src/Menu/Menu.js +++ b/src/Menu/Menu.js @@ -22,7 +22,7 @@ export default class Menu extends Component { const dropdownClass = classnames('mdl-portalmenu', className) return ( - + { debugger }}> {children} diff --git a/stories/Menu.story.js b/stories/Menu.story.js index fe27d31..6cd8957 100644 --- a/stories/Menu.story.js +++ b/stories/Menu.story.js @@ -2,7 +2,7 @@ import React, { Component } from 'react' import { storiesOf, action } from '@kadira/storybook' import faker from 'faker' -import { Card, Button, IconButton, Menu as MDLMenu } from 'react-mdl' +import { Card, Button, IconButton } from 'react-mdl' import { Menu, MenuItem } from '../src' import DialogWrapper from './helpers/DialogWrapper' @@ -235,18 +235,14 @@ storiesOf('Menu', module) .add('opens outside portal', () => { return (
- - + } align={'tr br'}> One Two Three I Am Free - +
) }) @@ -254,18 +250,14 @@ storiesOf('Menu', module) return (
- - + } align={'tr br'}> One Two Three I Am Free - +
) From 9e8f2389b144a53e3ab574429de6d2bb9e891ced Mon Sep 17 00:00:00 2001 From: Josh Guffey Date: Mon, 27 Mar 2017 19:49:48 -0700 Subject: [PATCH 4/5] wip --- src/Dropdown/DialogWrapper.js | 32 +++++++++++++++++++ src/Dropdown/Dropdown.js | 27 +--------------- src/Menu/Menu.js | 2 +- stories/Menu.story.js | 26 ++++----------- .../{DialogWrapper.js => DialogHelper.js} | 2 +- 5 files changed, 41 insertions(+), 48 deletions(-) create mode 100644 src/Dropdown/DialogWrapper.js rename stories/helpers/{DialogWrapper.js => DialogHelper.js} (92%) diff --git a/src/Dropdown/DialogWrapper.js b/src/Dropdown/DialogWrapper.js new file mode 100644 index 0000000..aeaaeef --- /dev/null +++ b/src/Dropdown/DialogWrapper.js @@ -0,0 +1,32 @@ +import React, { Component, PropTypes } from 'react'; +/* + * We must make another component so that this.closePortal + * gets passed down and handed to children + */ +export default class DialogWrapper extends Component { + static propTypes = { + closePortal: PropTypes.func, + children: React.PropTypes.any, + target: PropTypes.any.isRequired + } + + componentDidMount() { + this.dialogDom.showModal(this.props.target) + } + + componentWillUnmount() { + this.dialogDom.close() + } + + render() { + const { closePortal, children } = this.props + return( + this.dialogDom = c}> + { React.cloneElement( + children, + {closePortal: closePortal} + )} + + ); + } +} diff --git a/src/Dropdown/Dropdown.js b/src/Dropdown/Dropdown.js index bb78ad5..0973097 100644 --- a/src/Dropdown/Dropdown.js +++ b/src/Dropdown/Dropdown.js @@ -5,6 +5,7 @@ import Portal from 'react-portal' import Tether from 'tether' import './Dropdown.css' +import DialogWrapper from './DialogWrapper'; const POS = { t: 'top', @@ -224,29 +225,3 @@ export default class Dropdown extends Component { ) } } - -/* - * We must make another component so that this.closePortal - * gets passed down and handed to children - */ -class DialogWrapper extends Component { - componentDidMount() { - this.dialogDom.showModal(this.props.target) - } - - componentWillUnmount() { - this.dialogDom.close() - } - - render() { - const { closePortal, children } = this.props - return( - this.dialogDom = c}> - { React.cloneElement( - children, - {closePortal: closePortal} - )} - - ); - } -} diff --git a/src/Menu/Menu.js b/src/Menu/Menu.js index 5b45774..8a2d88d 100644 --- a/src/Menu/Menu.js +++ b/src/Menu/Menu.js @@ -22,7 +22,7 @@ export default class Menu extends Component { const dropdownClass = classnames('mdl-portalmenu', className) return ( - { debugger }}> + {children} diff --git a/stories/Menu.story.js b/stories/Menu.story.js index 6cd8957..9daea09 100644 --- a/stories/Menu.story.js +++ b/stories/Menu.story.js @@ -5,7 +5,7 @@ import faker from 'faker' import { Card, Button, IconButton } from 'react-mdl' import { Menu, MenuItem } from '../src' -import DialogWrapper from './helpers/DialogWrapper' +import DialogHelper from './helpers/DialogHelper' const bigMenuItems = [...Array(35).keys()].map(i => Menu Item {i} @@ -232,33 +232,19 @@ storiesOf('Menu', module) ) }) - .add('opens outside portal', () => { - return ( -
- } align={'tr br'}> - One - Two - Three - I - Am - Free - -
- ) - }) .add('opens within dialog', () => { return (
- + } align={'tr br'}> - One - Two - Three + console.log('select one')}>One + console.log('select two')}>Two + console.log('select three')}>Three I Am Free - +
) }) diff --git a/stories/helpers/DialogWrapper.js b/stories/helpers/DialogHelper.js similarity index 92% rename from stories/helpers/DialogWrapper.js rename to stories/helpers/DialogHelper.js index ab71d32..a276f7c 100644 --- a/stories/helpers/DialogWrapper.js +++ b/stories/helpers/DialogHelper.js @@ -38,7 +38,7 @@ export default class DialogWrapper extends Component { const { dialogOpen } = this.state; return(
- + How does it render within a Dialog? From 66850fd276d5d1fa295c9ec09b7c9bf1fd800f67 Mon Sep 17 00:00:00 2001 From: jguffey Date: Mon, 27 Mar 2017 21:31:27 -0700 Subject: [PATCH 5/5] removed unnessessary unmount handler --- src/Dropdown/DialogWrapper.js | 6 +----- stories/helpers/DialogHelper.js | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Dropdown/DialogWrapper.js b/src/Dropdown/DialogWrapper.js index aeaaeef..94ab3e5 100644 --- a/src/Dropdown/DialogWrapper.js +++ b/src/Dropdown/DialogWrapper.js @@ -6,7 +6,7 @@ import React, { Component, PropTypes } from 'react'; export default class DialogWrapper extends Component { static propTypes = { closePortal: PropTypes.func, - children: React.PropTypes.any, + children: PropTypes.any, target: PropTypes.any.isRequired } @@ -14,10 +14,6 @@ export default class DialogWrapper extends Component { this.dialogDom.showModal(this.props.target) } - componentWillUnmount() { - this.dialogDom.close() - } - render() { const { closePortal, children } = this.props return( diff --git a/stories/helpers/DialogHelper.js b/stories/helpers/DialogHelper.js index a276f7c..fc29b05 100644 --- a/stories/helpers/DialogHelper.js +++ b/stories/helpers/DialogHelper.js @@ -2,7 +2,7 @@ import React, { Component, PropTypes } from 'react' import { Button, Dialog, DialogTitle, DialogActions, DialogContent } from 'react-mdl' -export default class DialogWrapper extends Component { +export default class DialogHelper extends Component { constructor(props) { super(props); @@ -13,7 +13,7 @@ export default class DialogWrapper extends Component { static propTypes = { dialogOpen: PropTypes.bool, - children: PropTypes.arrayOf(PropTypes.element) + children: PropTypes.object } static defaultProps = {