Skip to content

Commit 9367381

Browse files
Merge pull request #54 from plotly/SubMenus
Sub menus and Colorpicker
2 parents 46a2eba + 7becfbe commit 9367381

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+748
-6657
lines changed

src/DefaultEditor.js

+37
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import React, {Component} from 'react';
22
import PropTypes from 'prop-types';
33
import {
4+
SubPanel,
45
ColorPicker,
56
DataSelector,
67
Dropdown,
78
Flaglist,
89
Fold,
10+
Info,
911
Numeric,
1012
Panel,
1113
PanelMenuWrapper,
@@ -202,6 +204,7 @@ class DefaultEditor extends Component {
202204
attr="legend.font.size"
203205
postfix="px"
204206
/>
207+
<ColorPicker label={_('Color')} attr="legend.font.color" />
205208
</Section>
206209
<Section name={_('Legend Box')}>
207210
<Numeric
@@ -210,8 +213,42 @@ class DefaultEditor extends Component {
210213
attr="legend.borderwidth"
211214
postfix="px"
212215
/>
216+
<ColorPicker
217+
label={_('Border Color')}
218+
attr="legend.bordercolor"
219+
/>
220+
<ColorPicker
221+
label={_('Background Color')}
222+
attr="legend.bgcolor"
223+
/>
213224
</Section>
214225
<Section name={_('Positioning')}>
226+
<SubPanel>
227+
<Section name={_('Anchor Point')}>
228+
<Info>
229+
{_(
230+
'The positioning inputs are relative to the ' +
231+
'anchor points on the text box'
232+
)}
233+
</Info>
234+
<Radio
235+
attr="legend.xanchor"
236+
options={[
237+
{label: _('Left'), value: 'left'},
238+
{label: _('Center'), value: 'center'},
239+
{label: _('Right'), value: 'right'},
240+
]}
241+
/>
242+
<Radio
243+
attr="legend.yanchor"
244+
options={[
245+
{label: _('Top'), value: 'top'},
246+
{label: _('Middle'), value: 'middle'},
247+
{label: _('Bottom'), value: 'bottom'},
248+
]}
249+
/>
250+
</Section>
251+
</SubPanel>
215252
<Numeric
216253
label={_('X Position')}
217254
step={0.01}

src/components/containers/Fold.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ export default class Fold extends Component {
88
const {canDelete, name} = this.props;
99
const doDelete = canDelete && typeof deleteContainer === 'function';
1010
return (
11-
<div className={bem('accordion-panel', 'top', ['active'])}>
11+
<div className={bem('fold', 'top', ['active'])}>
1212
{this.props.name}
1313
{doDelete ? (
1414
<a
15-
className={bem('accordion-panel', 'delete')}
15+
className={bem('fold', 'delete')}
1616
href="#"
1717
onClick={deleteContainer}
1818
>
@@ -28,9 +28,7 @@ export default class Fold extends Component {
2828
return (
2929
<div>
3030
{this.props.hideHeader ? null : this.renderHeader()}
31-
<div className={bem('accordion-panel', 'panel', modifiers)}>
32-
{this.props.children}
33-
</div>
31+
<div className={bem('fold', modifiers)}>{this.props.children}</div>
3432
</div>
3533
);
3634
}

src/components/containers/Section.js

+34-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import SubPanel from './SubPanel';
12
import React, {Component, cloneElement} from 'react';
23
import PropTypes from 'prop-types';
3-
import {bem} from '../../lib';
4+
import {icon} from '../../lib';
45
import unpackPlotProps from '../../lib/unpackPlotProps';
56

67
function childIsVisible(child) {
@@ -11,42 +12,63 @@ class Section extends Component {
1112
constructor(props, context) {
1213
super(props, context);
1314

14-
this.children = this.processChildren(context);
15+
this.children = null;
16+
this.subPanel = null;
17+
18+
this.processAndSetChildren(context);
1519
}
1620

1721
componentWillReceiveProps(nextProps, nextContext) {
18-
this.children = this.processChildren(nextContext);
22+
this.processAndSetChildren(nextContext);
1923
}
2024

21-
processChildren(context) {
25+
processAndSetChildren(context) {
2226
let children = this.props.children;
2327
if (!Array.isArray(children)) {
2428
children = [children];
2529
}
26-
children = children.filter(c => Boolean(c));
30+
31+
const attrChildren = [];
32+
let subPanel = null;
2733

2834
for (let i = 0; i < children.length; i++) {
2935
let child = children[i];
36+
if (!child) {
37+
continue;
38+
}
39+
if (child.type === SubPanel) {
40+
// Process the first subPanel. Ignore the rest.
41+
if (subPanel) {
42+
continue;
43+
}
44+
subPanel = child;
45+
continue;
46+
}
3047

3148
let isAttr = !!child.props.attr;
3249
let plotProps = isAttr
3350
? unpackPlotProps(child.props, context, child.constructor)
34-
: {};
51+
: {isVisible: true};
3552
let childProps = Object.assign({plotProps}, child.props);
3653
childProps.key = i;
37-
38-
children[i] = cloneElement(child, childProps, child.children);
54+
attrChildren.push(cloneElement(child, childProps));
3955
}
4056

41-
return children;
57+
this.children = attrChildren.length ? attrChildren : null;
58+
this.subPanel = subPanel;
4259
}
4360

4461
render() {
45-
const hasVisibleChildren = this.children.some(childIsVisible);
62+
const hasVisibleChildren =
63+
(this.children && this.children.some(childIsVisible)) ||
64+
Boolean(this.subPanel);
4665

4766
return hasVisibleChildren ? (
48-
<div className={bem('section')}>
49-
<div className={bem('section', 'heading')}>{this.props.name}</div>
67+
<div className="section">
68+
<div className="section__heading">
69+
{this.props.name}
70+
{this.subPanel}
71+
</div>
5072
{this.children}
5173
</div>
5274
) : null;

src/components/containers/SubPanel.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React, {Component} from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
export default class SubPanel extends Component {
5+
constructor() {
6+
super();
7+
this.state = {isVisible: false};
8+
9+
this.toggleVisibility = this.toggleVisibility.bind(this);
10+
}
11+
12+
toggleVisibility() {
13+
this.setState({isVisible: !this.state.isVisible});
14+
}
15+
16+
render() {
17+
const toggleClass = `subpanel__toggle ${this.props.toggleIconClass}`;
18+
const isVisible = this.props.show || this.state.isVisible;
19+
return (
20+
<span>
21+
<span>
22+
<i className={toggleClass} onClick={this.toggleVisibility} />
23+
</span>
24+
{isVisible ? (
25+
<div className="subpanel">
26+
<div className="subpanel__cover" onClick={this.toggleVisibility} />
27+
<div>{this.props.children}</div>
28+
</div>
29+
) : null}
30+
</span>
31+
);
32+
}
33+
}
34+
35+
SubPanel.propTypes = {
36+
toggleIconClass: PropTypes.string.isRequired,
37+
show: PropTypes.bool,
38+
};
39+
40+
SubPanel.defaultProps = {
41+
toggleIconClass: 'plotlyjs_editor__icon-cog',
42+
};

src/components/containers/__tests__/Section-test.js

+42-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import Section from '../Section';
3-
import {Flaglist, Numeric} from '../../fields';
3+
import SubPanel from '../SubPanel';
4+
import {Flaglist, Info, Numeric} from '../../fields';
45
import {TestEditor, fixtures, plotly} from '../../../lib/test-utils';
56
import {connectTraceToPlot} from '../../../lib';
67
import {mount} from 'enzyme';
@@ -47,6 +48,24 @@ describe('Section', () => {
4748
).toBe(false);
4849
});
4950

51+
it('is visible if it contains any non attr children', () => {
52+
const wrapper = mount(
53+
<TestEditor
54+
plotly={plotly}
55+
onUpdate={jest.fn()}
56+
{...fixtures.scatter({deref: true})}
57+
>
58+
<Section name="test-section">
59+
<Info>INFO</Info>
60+
</Section>
61+
</TestEditor>
62+
).find('[name="test-section"]');
63+
64+
expect(wrapper.children().length).toBe(1);
65+
expect(wrapper.find(Info).exists()).toBe(true);
66+
expect(wrapper.find(Info).text()).toBe('INFO');
67+
});
68+
5069
it('is not visible if it contains no visible children', () => {
5170
// pull and hole are not scatter attrs. Section should not show.
5271
const wrapper = mount(
@@ -71,4 +90,26 @@ describe('Section', () => {
7190
expect(wrapper.find(Flaglist).exists()).toBe(false);
7291
expect(wrapper.find(Numeric).exists()).toBe(false);
7392
});
93+
94+
it('will render first subPanel even with no visible attrs', () => {
95+
const wrapper = mount(
96+
<TestEditor
97+
plotly={plotly}
98+
onUpdate={jest.fn()}
99+
{...fixtures.scatter({deref: true})}
100+
>
101+
<Section name="test-section">
102+
<SubPanel show>
103+
<Info>INFO</Info>
104+
</SubPanel>
105+
<SubPanel show>
106+
<Info>MISINFORMATION</Info>
107+
</SubPanel>
108+
</Section>
109+
</TestEditor>
110+
).find('[name="test-section"]');
111+
112+
expect(wrapper.find(Info).length).toBe(1);
113+
expect(wrapper.find(Info).text()).toBe('INFO');
114+
});
74115
});

src/components/containers/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import SubPanel from './SubPanel';
12
import Fold from './Fold';
23
import Panel from './Panel';
34
import Section from './Section';
45
import TraceAccordion from './TraceAccordion';
56

6-
export {Fold, Panel, Section, TraceAccordion};
7+
export {SubPanel, Fold, Panel, Section, TraceAccordion};

src/components/fields/Info.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Field from './Field';
2+
import PropTypes from 'prop-types';
3+
import React, {Component} from 'react';
4+
5+
export default class Info extends Component {
6+
render() {
7+
return <Field {...this.props}>{this.props.children}</Field>;
8+
}
9+
}
10+
11+
Info.propTypes = {
12+
...Field.propTypes,
13+
};

src/components/fields/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import ColorPicker from './Color';
22
import Dropdown from './Dropdown';
33
import Flaglist from './Flaglist';
4+
import Info from './Info';
45
import Radio from './Radio';
56
import DataSelector from './DataSelector';
67
import Numeric from './Numeric';
@@ -10,6 +11,7 @@ export {
1011
ColorPicker,
1112
Dropdown,
1213
Flaglist,
14+
Info,
1315
Radio,
1416
DataSelector,
1517
Numeric,

src/components/index.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,24 @@ import {
22
ColorPicker,
33
Dropdown,
44
Flaglist,
5+
Info,
56
Radio,
67
DataSelector,
78
Numeric,
89
TraceSelector,
910
} from './fields';
1011

11-
import {Fold, Panel, Section, TraceAccordion} from './containers';
12+
import {SubPanel, Fold, Panel, Section, TraceAccordion} from './containers';
1213
import PanelMenuWrapper from './PanelMenuWrapper';
1314

1415
export {
16+
SubPanel,
1517
ColorPicker,
1618
DataSelector,
1719
Dropdown,
1820
Flaglist,
1921
Fold,
22+
Info,
2023
Numeric,
2124
Panel,
2225
PanelMenuWrapper,

0 commit comments

Comments
 (0)