Skip to content

Commit aae7b42

Browse files
authored
Merge pull request #1333 from shockey/bug/1332-standalone-menus
Bug/1332 standalone menus
2 parents e19f7d5 + ee483a4 commit aae7b42

10 files changed

+213316
-159
lines changed

dist/swagger-editor-bundle.js

Lines changed: 142994 additions & 103 deletions
Large diffs are not rendered by default.

dist/swagger-editor-bundle.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/swagger-editor-standalone-preset.js

Lines changed: 47864 additions & 41 deletions
Large diffs are not rendered by default.

dist/swagger-editor-standalone-preset.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/swagger-editor.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/validation.worker.js

Lines changed: 22286 additions & 9 deletions
Large diffs are not rendered by default.

dist/validation.worker.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
},
4242
"dependencies": {
4343
"boron": "^0.2.3",
44+
"classnames": "^2.1.3",
4445
"immutable": "^3.x.x",
4546
"js-yaml": "^3.5.5",
4647
"json-beautify": "^1.0.1",
@@ -51,6 +52,7 @@
5152
"react-dom": "^15.x",
5253
"react-file-download": "^0.3.2",
5354
"react-redux": "^4.x.x",
55+
"react-transition-group": "^1.1.1",
5456
"redux": "^3.x.x",
5557
"swagger-client": "~3.0.10",
5658
"swagger-ui": "^3.0.9",
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
// Adapted from https://github.com/mlaursen/react-dd-menu/blob/master/src/js/DropdownMenu.js
2+
3+
/* eslint react/no-find-dom-node: 0 */
4+
5+
import React, { PureComponent, PropTypes } from "react"
6+
import ReactDOM from "react-dom"
7+
import CSSTransitionGroup from "react-transition-group/CSSTransitionGroup"
8+
import classnames from "classnames"
9+
10+
const TAB = 9
11+
const SPACEBAR = 32
12+
const ALIGNMENTS = ["center", "right", "left"]
13+
const MENU_SIZES = ["sm", "md", "lg", "xl"]
14+
15+
16+
export default class DropdownMenu extends PureComponent {
17+
static propTypes = {
18+
isOpen: PropTypes.bool.isRequired,
19+
close: PropTypes.func.isRequired,
20+
toggle: PropTypes.node.isRequired,
21+
children: PropTypes.node,
22+
inverse: PropTypes.bool,
23+
align: PropTypes.oneOf(ALIGNMENTS),
24+
animAlign: PropTypes.oneOf(ALIGNMENTS),
25+
textAlign: PropTypes.oneOf(ALIGNMENTS),
26+
menuAlign: PropTypes.oneOf(ALIGNMENTS),
27+
className: PropTypes.string,
28+
size: PropTypes.oneOf(MENU_SIZES),
29+
upwards: PropTypes.bool,
30+
animate: PropTypes.bool,
31+
enterTimeout: PropTypes.number,
32+
leaveTimeout: PropTypes.number,
33+
closeOnInsideClick: PropTypes.bool,
34+
closeOnOutsideClick: PropTypes.bool,
35+
};
36+
37+
static defaultProps = {
38+
inverse: false,
39+
align: "center",
40+
animAlign: null,
41+
textAlign: null,
42+
menuAlign: null,
43+
className: null,
44+
size: null,
45+
upwards: false,
46+
animate: true,
47+
enterTimeout: 150,
48+
leaveTimeout: 150,
49+
closeOnInsideClick: true,
50+
closeOnOutsideClick: true,
51+
};
52+
53+
static MENU_SIZES = MENU_SIZES;
54+
static ALIGNMENTS = ALIGNMENTS;
55+
56+
componentDidUpdate(prevProps) {
57+
if(this.props.isOpen === prevProps.isOpen) {
58+
return
59+
}
60+
61+
const menuItems = ReactDOM.findDOMNode(this).querySelector(".dd-menu > .dd-menu-items")
62+
if(this.props.isOpen && !prevProps.isOpen) {
63+
this.lastWindowClickEvent = this.handleClickOutside
64+
document.addEventListener("click", this.lastWindowClickEvent)
65+
if(this.props.closeOnInsideClick) {
66+
menuItems.addEventListener("click", this.props.close)
67+
}
68+
menuItems.addEventListener("onkeydown", this.close)
69+
} else if(!this.props.isOpen && prevProps.isOpen) {
70+
document.removeEventListener("click", this.lastWindowClickEvent)
71+
if(prevProps.closeOnInsideClick) {
72+
menuItems.removeEventListener("click", this.props.close)
73+
}
74+
menuItems.removeEventListener("onkeydown", this.close)
75+
76+
this.lastWindowClickEvent = null
77+
}
78+
}
79+
80+
componentWillUnmount() {
81+
if(this.lastWindowClickEvent) {
82+
document.removeEventListener("click", this.lastWindowClickEvent)
83+
}
84+
}
85+
86+
close = (e) => {
87+
const key = e.which || e.keyCode
88+
if(key === SPACEBAR) {
89+
this.props.close()
90+
e.preventDefault()
91+
}
92+
};
93+
94+
handleClickOutside = (e) => {
95+
if(!this.props.closeOnOutsideClick) {
96+
return
97+
}
98+
99+
const node = ReactDOM.findDOMNode(this)
100+
let target = e.target
101+
102+
while(target.parentNode) {
103+
if(target === node) {
104+
return
105+
}
106+
107+
target = target.parentNode
108+
}
109+
110+
this.props.close(e)
111+
};
112+
113+
handleKeyDown = (e) => {
114+
const key = e.which || e.keyCode
115+
if(key !== TAB) {
116+
return
117+
}
118+
119+
const items = ReactDOM.findDOMNode(this).querySelectorAll("button,a")
120+
const id = e.shiftKey ? 1 : items.length - 1
121+
122+
if(e.target === items[id]) {
123+
this.props.close(e)
124+
}
125+
};
126+
127+
128+
render() {
129+
const { menuAlign, align, inverse, size, className } = this.props
130+
131+
const menuClassName = classnames(
132+
"dd-menu",
133+
`dd-menu-${menuAlign || align}`,
134+
{ "dd-menu-inverse": inverse },
135+
className,
136+
size ? ("dd-menu-" + size) : null
137+
)
138+
139+
const { textAlign, upwards, animAlign, animate, enterTimeout, leaveTimeout } = this.props
140+
141+
const listClassName = "dd-items-" + (textAlign || align)
142+
const transitionProps = {
143+
transitionName: "grow-from-" + (upwards ? "up-" : "") + (animAlign || align),
144+
component: "div",
145+
className: classnames("dd-menu-items", { "dd-items-upwards": upwards }),
146+
onKeyDown: this.handleKeyDown,
147+
transitionEnter: animate,
148+
transitionLeave: animate,
149+
transitionEnterTimeout: enterTimeout,
150+
transitionLeaveTimeout: leaveTimeout,
151+
}
152+
153+
return (
154+
<div className={menuClassName}>
155+
{this.props.toggle}
156+
<CSSTransitionGroup {...transitionProps}>
157+
{this.props.isOpen &&
158+
<ul key="items" className={listClassName}>{this.props.children}</ul>
159+
}
160+
</CSSTransitionGroup>
161+
</div>
162+
)
163+
}
164+
}

src/standalone/topbar/topbar.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { PropTypes } from "react"
22
import Swagger from "swagger-client"
33
import "whatwg-fetch"
4-
import DropdownMenu from "react-dd-menu"
4+
import DropdownMenu from "./DropdownMenu"
55
import Modal from "boron/DropModal"
66
import downloadFile from "react-file-download"
77
import YAML from "js-yaml"
@@ -164,7 +164,7 @@ export default class Topbar extends React.Component {
164164
let stateKey = `is${name}MenuOpen`
165165
let toggleFn = () => this.setState({ [stateKey]: !this.state[stateKey] })
166166
return {
167-
isOpen: this.state[stateKey],
167+
isOpen: !!this.state[stateKey],
168168
close: () => this.setState({ [stateKey]: false }),
169169
align: "left",
170170
toggle: <span className="menu-item" onClick={toggleFn}>{ name }</span>

0 commit comments

Comments
 (0)