Refactor into a generic `DropDown` component

This commit is contained in:
jos 2018-09-19 13:05:15 +02:00
parent 2c560264c0
commit 2999fbc380
7 changed files with 149 additions and 156 deletions

View File

@ -290,71 +290,6 @@ div.jsoneditor-menu-panel-right {
max-width: 100%; max-width: 100%;
} }
/******************************* Action Menu **********************************/
div.jsoneditor-actionmenu {
position: absolute;
box-sizing: border-box;
z-index: 99999;
top: 20px;
left: 18px; /* 20px - 2px where 2px half the difference between 24x24 icons of the menu and the 20x20 icons of the editor */
background: white;
border: 1px solid #d3d3d3;
box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3);
}
div.jsoneditor-actionmenu.jsoneditor-actionmenu-top {
top: auto;
bottom: 20px;
}
div.jsoneditor-modemenu.jsoneditor-modemenu {
top: 26px;
left: 0;
}
div.jsoneditor-menu-item {
line-height: 0;
font-size: 0;
}
button.jsoneditor-menu-button {
width: 136px;
height: 24px;
padding: 0;
margin: 0;
line-height: 24px;
background: transparent;
border: transparent;
display: inline-block;
box-sizing: border-box;
cursor: pointer;
color: #4d4d4d;
font-size: 10pt;
font-family: arial, sans-serif;
text-align: left;
}
button.jsoneditor-menu-button:hover,
button.jsoneditor-menu-button:focus {
color: $black;
background-color: #f5f5f5;
outline: none;
}
button.jsoneditor-menu-button.jsoneditor-selected {
color: white;
background-color: #ee422e;
}
button.jsoneditor-menu-default {
width: 104px; /* 136px - 32px */
}
/******************************* Floating Menu **********************************/ /******************************* Floating Menu **********************************/
div.jsoneditor-node-container { div.jsoneditor-node-container {
@ -504,30 +439,6 @@ div.jsoneditor-node-container {
/******************************* **********************************/ /******************************* **********************************/
div.jsoneditor-modes {
position: relative;
display: inline-block;
button {
background: none;
width: auto;
padding: 2px 6px;
font-size: $fontSize;
}
button.jsoneditor-type-modes {
width: 120px;
height: auto;
padding: 2px 6px;
border-radius: 0;
opacity: 1;
&:hover {
border: none;
}
}
}
textarea.jsoneditor-text { textarea.jsoneditor-text {
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -1,18 +1,28 @@
import { createElement as h, Component } from 'react' import { Component, createElement as h } from 'react'
import { toCapital } from '../../utils/stringUtils' import { toCapital } from '../../utils/stringUtils'
import fontawesome from '@fortawesome/fontawesome' import fontawesome from '@fortawesome/fontawesome'
import faChevronDown from '@fortawesome/fontawesome-free-solid/faChevronDown' import faChevronDown from '@fortawesome/fontawesome-free-solid/faChevronDown'
import { keyComboFromEvent } from '../../utils/keyBindings' import { keyComboFromEvent } from '../../utils/keyBindings'
import PropTypes from 'prop-types'
import './Menu.css' import './DropDown.css'
fontawesome.library.add(faChevronDown) fontawesome.library.add(faChevronDown)
const MENU_CLASS_NAME = 'jsoneditor-actionmenu' export default class DropDown extends Component {
const MODE_MENU_CLASS_NAME = MENU_CLASS_NAME + ' jsoneditor-modemenu'
static propTypes = {
value: PropTypes.string,
options: PropTypes.arrayOf(PropTypes.shape({
value: PropTypes.string.isRequired,
text: PropTypes.string,
title: PropTypes.string,
})).isRequired,
onChange: PropTypes.func.isRequired,
onError: PropTypes.func
}
export default class ModeDropDown extends Component {
constructor (props) { constructor (props) {
super (props) super (props)
@ -29,14 +39,22 @@ export default class ModeDropDown extends Component {
* props {{modes: string[], mode: string, onChangeMode: function, onError: function}} * props {{modes: string[], mode: string, onChangeMode: function, onError: function}}
*/ */
render () { render () {
return h('div', {className: 'jsoneditor-modes'}, [ const selected = this.props.options
? this.props.options.find(option => option.value === this.props.value)
: null
const selectedText = selected
? (selected.text || selected.value)
: ''
return h('div', {className: 'jsoneditor-dropdown'}, [
h('button', { h('button', {
key: 'button', key: 'button',
className: 'current-mode', className: 'jsoneditor-dropdown-main-button',
title: 'Switch mode', title: 'Switch mode',
onClick: this.handleOpen onClick: this.handleOpen
}, [ }, [
toCapital(this.props.mode) + ' ', toCapital(selectedText) + ' ',
h('i', { key: 'icon', className: 'fa fa-chevron-down' }) h('i', { key: 'icon', className: 'fa fa-chevron-down' })
]), ]),
@ -63,28 +81,29 @@ export default class ModeDropDown extends Component {
*/ */
renderDropDown () { renderDropDown () {
if (this.state.open) { if (this.state.open) {
const items = this.props.modes.map(mode => { const items = this.props.options.map(option => {
return h('button', { return h('button', {
key: mode, key: option.value,
title: `Switch to ${mode} mode`, ref: 'button',
className: 'jsoneditor-menu-button jsoneditor-type-modes' + title: option.title || option.text || option.value,
((mode === this.props.mode) ? ' jsoneditor-selected' : ''), className: 'jsoneditor-menu-item' +
((option.value === this.props.value) ? ' jsoneditor-menu-item-selected' : ''),
onClick: () => { onClick: () => {
try { try {
this.handleRequestClose() this.handleRequestClose()
this.props.onChangeMode(mode) this.props.onChange(option.value)
} }
catch (err) { catch (err) {
this.props.onError(err) this.props.onError(err)
} }
} }
}, toCapital(mode)) }, toCapital(option.text || option.value))
}) })
return h('div', { return h('div', {
key: 'dropdown', key: 'dropdown',
className: MODE_MENU_CLASS_NAME, className: 'jsoneditor-dropdown-list',
ref: 'menu', ref: 'menu',
onKeyDown: this.handleKeyDown onKeyDown: this.handleKeyDown
}, items) }, items)
@ -112,6 +131,11 @@ export default class ModeDropDown extends Component {
event.target.nextSibling.focus() event.target.nextSibling.focus()
} }
} }
if (combo ==='Escape') {
this.handleRequestClose()
setTimeout(() => this.focusToDropDownButton()) // FIXME: doesn't work
}
} }
addRequestCloseListener () { addRequestCloseListener () {
@ -141,5 +165,11 @@ export default class ModeDropDown extends Component {
} }
} }
focusToDropDownButton() {
if (this.refs.button) {
this.refs.button.focus()
}
}
handleWindowRequestClose = null handleWindowRequestClose = null
} }

View File

@ -0,0 +1,60 @@
@import '../style.scss';
.jsoneditor-dropdown {
position: relative;
display: inline-block;
button.jsoneditor-dropdown-main-button {
background: none;
width: auto;
padding: 2px 6px;
font-size: $fontSize;
font-weight: bold;
}
div.jsoneditor-dropdown-list {
position: absolute;
box-sizing: border-box;
z-index: 99999;
top: 26px;
left: 0;
background: white;
border: 1px solid #d3d3d3;
box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3);
button.jsoneditor-menu-item {
display: inline-block;
box-sizing: border-box;
width: 120px;
height: auto;
padding: 2px 6px;
margin: 0;
line-height: 24px;
border-radius: 0;
opacity: 1;
background: transparent;
border: transparent;
color: #4d4d4d;
font-size: 10pt;
font-family: arial, sans-serif;
text-align: left;
cursor: pointer;
}
button.jsoneditor-menu-item:hover,
button.jsoneditor-menu-item:focus {
color: $black;
background-color: #f5f5f5;
outline: none;
}
button.jsoneditor-menu-item.jsoneditor-menu-item-selected {
color: white;
background-color: #ee422e;
}
}
}

View File

@ -1,5 +1,4 @@
@import '../style.scss'; @import '../style.scss';
@import './MenuButton.scss';
.jsoneditor-menu { .jsoneditor-menu {
// TODO: there is also css for this menu in jsoneditor.scss, move this here // TODO: there is also css for this menu in jsoneditor.scss, move this here
@ -13,4 +12,37 @@
.jsoneditor-menu-group { .jsoneditor-menu-group {
display: inline-block; display: inline-block;
} }
.jsoneditor-menu-group:not(:first-child) {
border-left: 1px solid rgba(255,255,255, 0.1);
margin-left: 5px;
padding-left: 5px;
}
button {
width: 26px;
height: 26px;
margin: 2px;
padding: 0;
border-radius: 2px;
border: 1px solid transparent;
background: transparent;
color: white;
opacity: 0.8;
font-family: arial, sans-serif;
font-size: 16px;
}
button:hover {
background-color: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.4);
}
button:focus,
button:active {
background-color: rgba(255,255,255,0.3);
}
button:disabled {
opacity: 0.5;
}
} }

View File

@ -1,40 +0,0 @@
.jsoneditor-menu {
.jsoneditor-menu-group:not(:first-child) {
border-left: 1px solid rgba(255,255,255, 0.1);
margin-left: 5px;
padding-left: 5px;
}
button {
width: 26px;
height: 26px;
margin: 2px;
padding: 0;
border-radius: 2px;
border: 1px solid transparent;
background: transparent;
color: white;
opacity: 0.8;
font-family: arial, sans-serif;
font-size: 16px;
&.current-mode {
font-weight: bold;
}
}
button:hover {
background-color: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.4);
}
button:focus,
button:active {
background-color: rgba(255,255,255,0.3);
}
button:disabled {
opacity: 0.5;
}
}

View File

@ -1,5 +1,5 @@
import { createElement as h, PureComponent } from 'react' import { createElement as h, PureComponent } from 'react'
import ModeDropDown from './ModeDropDown' import DropDown from './DropDown'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import fontawesome from '@fortawesome/fontawesome' import fontawesome from '@fortawesome/fontawesome'
@ -30,11 +30,11 @@ export default class TreeModeMenu extends PureComponent {
if (this.props.modes ) { if (this.props.modes ) {
items = items.concat([ items = items.concat([
h('div', {className: 'jsoneditor-menu-group', key: 'mode'}, [ h('div', {className: 'jsoneditor-menu-group', key: 'mode'}, [
h(ModeDropDown, { h(DropDown, {
key: 'mode', key: 'mode',
modes: this.props.modes, options: this.props.modes.map(mode => ({ value: mode })),
mode: this.props.mode, value: this.props.mode,
onChangeMode: this.props.onChangeMode, onChange: this.props.onChangeMode,
onError: this.props.onError onError: this.props.onError
}) })
]) ])

View File

@ -1,5 +1,5 @@
import { createElement as h, PureComponent } from 'react' import { createElement as h, PureComponent } from 'react'
import ModeDropDown from './ModeDropDown' import DropDown from './DropDown'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import fontawesome from '@fortawesome/fontawesome' import fontawesome from '@fortawesome/fontawesome'
@ -69,11 +69,11 @@ export default class TreeModeMenu extends PureComponent {
if (this.props.modes ) { if (this.props.modes ) {
items = items.concat([ items = items.concat([
h('div', {className: 'jsoneditor-menu-group', key: 'mode'}, [ h('div', {className: 'jsoneditor-menu-group', key: 'mode'}, [
h(ModeDropDown, { h(DropDown, {
key: 'mode', key: 'mode',
modes: this.props.modes, options: this.props.modes.map(mode => ({ value: mode })),
mode: this.props.mode, value: this.props.mode,
onChangeMode: this.props.onChangeMode, onChange: this.props.onChangeMode,
onError: this.props.onError onError: this.props.onError
}) })
]) ])