Skip to content

Commit e28f15a

Browse files
authored
Merge pull request #256 from plotly/button-in-menu
Button in menu
2 parents 283c6f8 + b720f2e commit e28f15a

File tree

11 files changed

+108
-38
lines changed

11 files changed

+108
-38
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ The [custom editor example](https://github.com/plotly/react-plotly.js-editor/tre
137137
* `<Fold />`: collapsable container within a `<Panel />`
138138
* `<Section />`: uncollapsable container within a `<Panel />` or `<Fold />`
139139
* `<MenuPanel />`: container child of `<Section />` that appears when a cog icon in the section title is clicked on
140+
* `<SingleSidebarItem/>`: wraps any item you would like to see appear in the sidebar menu.
140141

141142
### General-purpose Fields
142143

@@ -154,6 +155,12 @@ All Fields except `<Info />` accept an `attr` property to bind them to a key in
154155
<img src="examples/components.png" alt="Components" width="432" height="692" border="1">
155156
</p>
156157

158+
### Widgets
159+
160+
Simple component that takes in props and renders.
161+
162+
* `<Button/>`: simple button component, useful when combined with `<SingleSidebarItem/>` to add as menu item
163+
157164
### Special-Purpose Containers
158165

159166
* `<TraceAccordion />`: `<Panel />` whose children are replicated into `<Folds />` connected to traces via `connectTraceToPlot()`.

examples/custom/src/CustomEditor.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
Numeric,
1313
LayoutPanel,
1414
MenuPanel,
15+
Button,
16+
SingleSidebarItem,
1517
} from 'react-plotly.js-editor';
1618

1719
export default class CustomEditor extends Component {
@@ -69,6 +71,20 @@ export default class CustomEditor extends Component {
6971
</Section>
7072
</Fold>
7173
</LayoutPanel>
74+
<SingleSidebarItem>
75+
<Button
76+
variant="primary"
77+
label="save"
78+
onClick={() => alert('save button clicked!')}
79+
/>
80+
</SingleSidebarItem>
81+
<SingleSidebarItem>
82+
<Button
83+
variant="secondary"
84+
label="clear"
85+
onClick={() => alert('clear button clicked!')}
86+
/>
87+
</SingleSidebarItem>
7288
</PanelMenuWrapper>
7389
);
7490
}

src/components/PanelMenuWrapper.js

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
22
import React, {cloneElement, Component} from 'react';
33
import SidebarGroup from './sidebar/SidebarGroup';
44
import {bem} from 'lib';
5+
import SingleSidebarItem from './containers/SingleSidebarItem';
56

67
class PanelsWithSidebar extends Component {
78
constructor(props) {
@@ -15,57 +16,59 @@ class PanelsWithSidebar extends Component {
1516
};
1617

1718
this.setPanel = this.setPanel.bind(this);
18-
this.renderGroup = this.renderGroup.bind(this);
19+
this.renderSection = this.renderSection.bind(this);
1920
}
2021

2122
setPanel(group, panel) {
2223
this.setState({group, panel});
2324
}
2425

25-
renderGroup(group, i) {
26+
renderSection(section, i) {
27+
if (section.type && section.type === SingleSidebarItem) {
28+
const sectionWithKey = cloneElement(section, {key: i});
29+
return <div>{sectionWithKey}</div>;
30+
}
2631
return (
2732
<SidebarGroup
2833
key={i}
2934
selectedGroup={this.state.group}
3035
selectedPanel={this.state.panel}
31-
group={group.name}
32-
panels={group.panels}
36+
group={section.name}
37+
panels={section.panels}
3338
onChangeGroup={this.setPanel}
3439
/>
3540
);
3641
}
3742

3843
computeMenuOptions(props) {
39-
let obj, child, group, name;
40-
41-
let {children} = props;
42-
43-
if (!Array.isArray(children)) {
44-
children = [children];
45-
}
46-
44+
const {children} = props;
45+
const sections = [];
4746
const groupLookup = {};
4847
let groupIndex;
49-
const groups = [];
50-
51-
for (let i = 0; i < children.length; i++) {
52-
child = children[i];
53-
group = child.props.group;
54-
name = child.props.name;
55-
56-
if (groupLookup.hasOwnProperty(group)) {
57-
groupIndex = groupLookup[group];
58-
obj = groups[groupIndex];
59-
} else {
60-
groupLookup[group] = groups.length;
61-
obj = {name: group, panels: []};
62-
groups.push(obj);
48+
49+
React.Children.forEach(children, child => {
50+
const group = child.props.group;
51+
const name = child.props.name;
52+
53+
if (group && name) {
54+
let obj;
55+
if (groupLookup.hasOwnProperty(group)) {
56+
groupIndex = groupLookup[group];
57+
obj = sections[groupIndex];
58+
} else {
59+
groupLookup[group] = sections.length;
60+
obj = {name: group, panels: []};
61+
sections.push(obj);
62+
}
63+
obj.panels.push(name);
6364
}
6465

65-
obj.panels.push(name);
66-
}
66+
if (child.type === SingleSidebarItem) {
67+
sections.push(child);
68+
}
69+
});
6770

68-
return groups;
71+
return sections;
6972
}
7073

7174
render() {
@@ -77,7 +80,7 @@ class PanelsWithSidebar extends Component {
7780

7881
return (
7982
<div className={bem('plotly-editor', 'wrapper')}>
80-
<div className={bem('sidebar')}>{menuOpts.map(this.renderGroup)}</div>
83+
<div className={bem('sidebar')}>{menuOpts.map(this.renderSection)}</div>
8184
{children.map((child, i) =>
8285
cloneElement(child, {
8386
key: i,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import PropTypes from 'prop-types';
2+
import React, {cloneElement, Component} from 'react';
3+
import Button from '../widgets/Button';
4+
5+
export default class SingleSidebarItem extends Component {
6+
render() {
7+
let {children} = this.props;
8+
children = React.Children.map(children, child => {
9+
if (child.type === Button) {
10+
return cloneElement(child, {className: 'button--menu'});
11+
}
12+
return child;
13+
});
14+
return children ? (
15+
<div className="sidebar__item--single">{children}</div>
16+
) : null;
17+
}
18+
}
19+
20+
SingleSidebarItem.propTypes = {
21+
children: PropTypes.any,
22+
};

src/components/containers/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import TraceAccordion from './TraceAccordion';
77
import TraceMarkerSection from './TraceMarkerSection';
88
import {LayoutPanel, AxesFold} from './derived';
99
import TraceRequiredPanel from './TraceRequiredPanel';
10+
import SingleSidebarItem from './SingleSidebarItem';
1011

1112
export {
1213
AnnotationAccordion,
@@ -19,4 +20,5 @@ export {
1920
TraceRequiredPanel,
2021
LayoutPanel,
2122
AxesFold,
23+
SingleSidebarItem,
2224
};

src/components/index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,20 @@ import {
2424

2525
import {
2626
AnnotationAccordion,
27+
AxesFold,
2728
Fold,
29+
LayoutPanel,
2830
MenuPanel,
2931
Panel,
3032
Section,
3133
TraceAccordion,
3234
TraceMarkerSection,
3335
TraceRequiredPanel,
34-
LayoutPanel,
35-
AxesFold,
36+
SingleSidebarItem,
3637
} from './containers';
3738

39+
import {Button} from './widgets';
40+
3841
import PanelMenuWrapper from './PanelMenuWrapper';
3942

4043
export {
@@ -70,4 +73,6 @@ export {
7073
TraceSelector,
7174
LayoutPanel,
7275
AxesFold,
76+
Button,
77+
SingleSidebarItem,
7378
};

src/components/widgets/Button.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Button extends Component {
88
}
99

1010
render() {
11-
const {variant, className, icon, label, children, ...rest} = this.props;
11+
const {children, className, icon, label, variant, ...rest} = this.props;
1212

1313
let classes = `button`;
1414

@@ -18,7 +18,9 @@ class Button extends Component {
1818
classes += ` button--default`;
1919
}
2020

21-
classes += ` ${className}`;
21+
if (className) {
22+
classes += ` ${className}`;
23+
}
2224

2325
const Icon = icon ? (
2426
<div className={bem('button', 'icon')}>{icon}</div>
@@ -36,11 +38,11 @@ class Button extends Component {
3638
}
3739

3840
Button.propTypes = {
39-
variant: PropTypes.string,
40-
label: PropTypes.any,
41-
className: PropTypes.any,
4241
children: PropTypes.node,
42+
className: PropTypes.any,
4343
icon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
44+
label: PropTypes.any,
45+
variant: PropTypes.string,
4446
};
4547

4648
export default Button;

src/components/widgets/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Button from './Button';
2+
export {Button};

src/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ import {
4646
TraceSelector,
4747
LayoutPanel,
4848
AxesFold,
49+
Button,
50+
SingleSidebarItem,
4951
} from './components';
5052

5153
export {
@@ -92,6 +94,8 @@ export {
9294
walkObject,
9395
LayoutPanel,
9496
AxesFold,
97+
Button,
98+
SingleSidebarItem,
9599
};
96100

97101
export default PlotlyEditor;

src/styles/components/sidebar/_main.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@
109109
border-bottom: var(--border-light);
110110
position: relative;
111111
overflow: hidden;
112+
113+
&--single {
114+
margin-top: 15px;
115+
}
116+
112117
&::before {
113118
content: '';
114119
position: absolute;

src/styles/components/widgets/_button.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ button {
5151
padding-left: 0;
5252
}
5353
}
54-
54+
&--menu {
55+
min-width: 75px;
56+
}
5557
&--default {
5658
@include button();
5759
}

0 commit comments

Comments
 (0)