diff --git a/package.json b/package.json index 66b0dcc8e..76f2193ef 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "react-colorscales": "^0.4.2", "react-dom": "^16.2.0", "react-select": "^1.0.0-rc.10", + "react-tabs": "^2.2.1", "tinycolor2": "^1.4.1" }, "devDependencies": { diff --git a/src/components/containers/Panel.js b/src/components/containers/Panel.js index e0458802f..ab586b392 100644 --- a/src/components/containers/Panel.js +++ b/src/components/containers/Panel.js @@ -106,7 +106,9 @@ class Panel extends Component { ); return ( -
+
{ return ( @@ -39,13 +46,64 @@ class TraceAccordion extends Component { } }, }; - return {content ? content : null}; + return ( + + {individualTraces ? individualTraces : null} + + ); } - return {content ? content : null}; + if (canGroup && data.length > 1) { + const tracesByGroup = data.reduce((allTraces, next, index) => { + const traceType = plotlyTraceToCustomTrace( + fullData.filter(trace => trace.index === index)[0] + ); + if (!allTraces[traceType]) { + allTraces[traceType] = []; + } + allTraces[traceType].push(index); + return allTraces; + }, {}); + + const groupedTraces = Object.keys(tracesByGroup).map( + (traceType, index) => { + return ( + + {this.props.children} + + ); + } + ); + return ( + + + + {_('All Traces')} + {_('Individual')} + + + {groupedTraces ? groupedTraces : null} + + + {individualTraces ? individualTraces : null} + + + + ); + } + return ( + + {individualTraces ? individualTraces : null} + + ); } } TraceAccordion.contextTypes = { + fullData: PropTypes.array, data: PropTypes.array, }; @@ -53,6 +111,7 @@ TraceAccordion.propTypes = { localize: PropTypes.func, children: PropTypes.node, canAdd: PropTypes.bool, + canGroup: PropTypes.bool, messageIfEmptyFold: PropTypes.string, }; diff --git a/src/components/containers/__tests__/Fold-test.js b/src/components/containers/__tests__/Fold-test.js index 2fcfe062c..753c9b4eb 100644 --- a/src/components/containers/__tests__/Fold-test.js +++ b/src/components/containers/__tests__/Fold-test.js @@ -11,7 +11,7 @@ describe('', () => { const withoutDelete = mount( - + @@ -22,7 +22,7 @@ describe('', () => { const withDelete = mount( - + @@ -36,7 +36,7 @@ describe('', () => { mount( - + diff --git a/src/components/containers/__tests__/Layout-test.js b/src/components/containers/__tests__/Layout-test.js index a71b87682..e1b6512be 100644 --- a/src/components/containers/__tests__/Layout-test.js +++ b/src/components/containers/__tests__/Layout-test.js @@ -15,7 +15,7 @@ Layouts.forEach(Layout => { const wrapper = mount( - + @@ -35,7 +35,7 @@ Layouts.forEach(Layout => { {...fixtures.scatter({layout: {width: 100}})} > - + diff --git a/src/components/containers/__tests__/Section-test.js b/src/components/containers/__tests__/Section-test.js index bb05d54ef..d95300503 100644 --- a/src/components/containers/__tests__/Section-test.js +++ b/src/components/containers/__tests__/Section-test.js @@ -14,7 +14,7 @@ describe('Section', () => { // mode is visible with scatter. Hole is not visible. Section should show. const wrapper = mount( - + { // pull and hole are not scatter attrs. Section should not show. const wrapper = mount( - - - + + + ) @@ -85,8 +85,8 @@ describe('Section', () => { const TraceSection = connectTraceToPlot(Section); const wrapper = mount( - - + + INFO @@ -106,8 +106,8 @@ describe('Section', () => { const TraceSection = connectTraceToPlot(Section); const wrapper = mount( - - + + INFO @@ -123,8 +123,8 @@ describe('Section', () => { const TraceSection = connectTraceToPlot(Section); const wrapper = mount( - - + + INFO @@ -141,7 +141,7 @@ describe('TraceTypeSection', () => { { { const wrapper = mount( - + @@ -28,7 +28,7 @@ describe('CanvasSize', () => { const wrapper = mount( - + diff --git a/src/components/fields/__tests__/DataSelector-test.js b/src/components/fields/__tests__/DataSelector-test.js index 6be9e79f5..805a293d6 100644 --- a/src/components/fields/__tests__/DataSelector-test.js +++ b/src/components/fields/__tests__/DataSelector-test.js @@ -16,7 +16,6 @@ function render(overrides = {}, children) { {children} ) - .find(`[traceIndex=1]`) .find(`[attr="${attr}"]`) .last(); } @@ -64,7 +63,7 @@ describe('DataSelector', () => { const wrapper = render( {}, diff --git a/src/components/fields/__tests__/Radio-test.js b/src/components/fields/__tests__/Radio-test.js index bb4018bde..e87b6b660 100644 --- a/src/components/fields/__tests__/Radio-test.js +++ b/src/components/fields/__tests__/Radio-test.js @@ -12,7 +12,7 @@ describe('', () => { it('enables centering by default', () => { const wrapper = mount( - + ', () => { it('permits centering to be disabled', () => { const wrapper = mount( - + { }; const wrapper = mount( - + @@ -37,7 +37,7 @@ describe('TraceSelector', () => { }; const wrapper = mount( - + @@ -59,7 +59,7 @@ describe('TraceSelector', () => { const wrapper = mount( - + @@ -77,7 +77,7 @@ describe('TraceSelector', () => { }; const wrapper = mount( - + @@ -95,7 +95,7 @@ describe('TraceSelector', () => { }; const wrapper = mount( - + @@ -113,7 +113,7 @@ describe('TraceSelector', () => { }; const wrapper = mount( - + @@ -133,7 +133,7 @@ describe('TraceSelector', () => { }; const wrapper = mount( - + @@ -159,7 +159,7 @@ describe('TraceSelector', () => { }; const wrapper = mount( - + diff --git a/src/default_panels/StyleColorbarsPanel.js b/src/default_panels/StyleColorbarsPanel.js index 5f3736315..311e97b92 100644 --- a/src/default_panels/StyleColorbarsPanel.js +++ b/src/default_panels/StyleColorbarsPanel.js @@ -18,7 +18,7 @@ import { import {localize} from '../lib'; const StyleColorBarsPanel = ({localize: _}) => ( - + ( - +
{ const wrapper = mount( - + @@ -37,7 +37,7 @@ Layouts.forEach(Layout => { {...fixtures.scatter({layout: {width: 100}})} > - + @@ -60,7 +60,7 @@ Layouts.forEach(Layout => { {...fixtures.scatter({layout: {showlegend: true}})} > - + diff --git a/src/lib/__tests__/connectToContainer-test.js b/src/lib/__tests__/connectToContainer-test.js index c56743294..30a54b110 100644 --- a/src/lib/__tests__/connectToContainer-test.js +++ b/src/lib/__tests__/connectToContainer-test.js @@ -20,7 +20,7 @@ describe('connectToContainer', () => { const numeric = mount( - + ).find(Numeric); diff --git a/src/lib/__tests__/connectTraceToPlot-test.js b/src/lib/__tests__/connectTraceToPlot-test.js index 79236de8a..b38cf6719 100644 --- a/src/lib/__tests__/connectTraceToPlot-test.js +++ b/src/lib/__tests__/connectTraceToPlot-test.js @@ -19,7 +19,7 @@ Traces.forEach(Trace => { const wrapper = mount( - + @@ -36,7 +36,7 @@ Traces.forEach(Trace => { const wrapper = mount( - + @@ -58,7 +58,7 @@ Traces.forEach(Trace => { const wrapper = mount( - + @@ -76,7 +76,7 @@ Traces.forEach(Trace => { const wrapper = mount( - + ) diff --git a/src/lib/__tests__/nestedContainerConnections-test.js b/src/lib/__tests__/nestedContainerConnections-test.js index 24d6425b0..221956fd0 100644 --- a/src/lib/__tests__/nestedContainerConnections-test.js +++ b/src/lib/__tests__/nestedContainerConnections-test.js @@ -41,7 +41,7 @@ describe('Plot Connection', () => { ); mount( - + ) .find('[attr="width"]') @@ -70,7 +70,11 @@ describe('Plot Connection', () => { const wrapper = mount(
- +
) @@ -100,9 +104,13 @@ describe('Plot Connection', () => { mount( - +
- +
@@ -139,9 +147,9 @@ describe('Plot Connection', () => { const wrapper = mount( - +
- +
diff --git a/src/lib/connectTraceToPlot.js b/src/lib/connectTraceToPlot.js index 423c7f6e0..43050c3c2 100644 --- a/src/lib/connectTraceToPlot.js +++ b/src/lib/connectTraceToPlot.js @@ -24,11 +24,14 @@ export default function connectTraceToPlot(WrappedComponent) { } setLocals(props, context) { - const {traceIndex} = props; + const {traceIndexes} = props; const {data, fullData, plotly} = context; - const trace = data[traceIndex] || {}; - const fullTraceIndex = findFullTraceIndex(fullData, traceIndex); + const trace = traceIndexes.length > 0 ? data[traceIndexes[0]] : {}; + const fullTraceIndex = + traceIndexes.length > 0 + ? findFullTraceIndex(fullData, traceIndexes[0]) + : findFullTraceIndex(fullData, 0); const fullTrace = fullData[fullTraceIndex] || {}; let getValObject; @@ -53,12 +56,17 @@ export default function connectTraceToPlot(WrappedComponent) { container: trace, fullContainer: fullTrace, }; - this.icon = renderTraceIcon(plotlyTraceToCustomTrace(trace)); - this.name = fullTrace.name; - const DEFAULT_FIN_CHART_TRACE_NAME = ' - increasing'; - if (fullTrace.name.indexOf(DEFAULT_FIN_CHART_TRACE_NAME) && !trace.name) { - this.name = fullTrace.name.replace(DEFAULT_FIN_CHART_TRACE_NAME, ''); + if (trace && fullTrace) { + this.icon = renderTraceIcon(plotlyTraceToCustomTrace(trace)); + this.name = fullTrace.name; + const DEFAULT_FIN_CHART_TRACE_NAME = ' - increasing'; + if ( + fullTrace.name.indexOf(DEFAULT_FIN_CHART_TRACE_NAME) && + !trace.name + ) { + this.name = fullTrace.name.replace(DEFAULT_FIN_CHART_TRACE_NAME, ''); + } } } @@ -72,7 +80,7 @@ export default function connectTraceToPlot(WrappedComponent) { type: EDITOR_ACTIONS.UPDATE_TRACES, payload: { update, - traceIndexes: [this.props.traceIndex], + traceIndexes: this.props.traceIndexes, }, }); } @@ -82,7 +90,7 @@ export default function connectTraceToPlot(WrappedComponent) { if (this.context.onUpdate) { this.context.onUpdate({ type: EDITOR_ACTIONS.DELETE_TRACE, - payload: {traceIndexes: [this.props.traceIndex]}, + payload: {traceIndexes: this.props.traceIndexes}, }); } } @@ -99,7 +107,7 @@ export default function connectTraceToPlot(WrappedComponent) { )}`; TraceConnectedComponent.propTypes = { - traceIndex: PropTypes.number.isRequired, + traceIndexes: PropTypes.arrayOf(PropTypes.number).isRequired, }; TraceConnectedComponent.contextTypes = { diff --git a/src/styles/components/containers/_fold.scss b/src/styles/components/containers/_fold.scss index 6745c9e92..602eaacba 100644 --- a/src/styles/components/containers/_fold.scss +++ b/src/styles/components/containers/_fold.scss @@ -150,7 +150,6 @@ font-family: var(--font-family-body); font-size: var(--font-size-small); text-align: center; - font-size: 13px; color: var(--color-text-base); } } @@ -159,12 +158,6 @@ border-width: 1px 1px 1px 1px; } } - - .panel { - background-color: var(--fold-background); - border-right: none; - width: 100%; - } } .fold + .fold { diff --git a/src/styles/components/containers/_main.scss b/src/styles/components/containers/_main.scss index 93140140e..fde545d50 100644 --- a/src/styles/components/containers/_main.scss +++ b/src/styles/components/containers/_main.scss @@ -4,3 +4,4 @@ @import "menupanel"; @import "info"; @import "modalbox"; +@import "tabs"; diff --git a/src/styles/components/containers/_panel.scss b/src/styles/components/containers/_panel.scss index 0bc1880e4..14b8221fb 100644 --- a/src/styles/components/containers/_panel.scss +++ b/src/styles/components/containers/_panel.scss @@ -1,17 +1,30 @@ .panel { flex-grow: 1; - flex-shrink: 0; - background-color: var(--panel-background); + overflow-x: hidden; overflow-y: auto; padding: var(--spacing-half-unit); - border-right: var(--border-default); box-sizing: border-box; position: relative; - width: calc(var(--panel-width)); + display: flex; + flex-direction: column; + width: 100%; @include scrollbar(); + + @at-root .plotly-editor__wrapper > .panel { + // These are for the first panel + background-color: var(--panel-background); + border-right: var(--border-default); + width: calc(var(--panel-width)); + } + &__content { + flex-grow: 1; + display: flex; + flex-direction: column; + } &__header { margin-bottom: var(--spacing-half-unit); display: flex; + flex-shrink: 0; &__content { flex-grow: 1; } @@ -75,4 +88,7 @@ } } } + &--no-padding { + padding: 0; + } } diff --git a/src/styles/components/containers/_tabs.scss b/src/styles/components/containers/_tabs.scss new file mode 100644 index 000000000..aafecdc1e --- /dev/null +++ b/src/styles/components/containers/_tabs.scss @@ -0,0 +1,86 @@ +.panel { + .react-tabs { + $tab-bar-height: 32px; + flex-grow: 1; + display: flex; + flex-direction: column; + &__tab { + &-list { + background: var(--color-background-medium); + margin: 0; + flex-shrink:0; + list-style: none; + display: flex; + align-items: flex-end; + padding-top: var(--spacing-half-unit); + padding-left: var(--spacing-half-unit); + padding-right: var(--spacing-half-unit); + padding-bottom: 0; + height: $tab-bar-height; + } + // Tab Styles + flex-grow: 1; + flex-shrink:0; + display: flex; + align-items: center; + justify-content: center; + padding: var(--spacing-quarter-unit); + color: var(--color-text-base); + font-size: var(--font-size-base); + background: var(--color-background-medium); + border: var(--border-default); + border-bottom: 0; + position: relative; + background: var(--color-background-light); + transition: border-color 0.15s ease-in-out; + + &:first-of-type { + border-top-left-radius: var(--border-radius); + } + &:last-of-type { + border-top-right-radius: var(--border-radius); + } + + + &:hover { + background-color: var(--color-background-base); + cursor: pointer; + } + + &--selected { + background-color: var(--color-background-base); + pointer-events: none; + margin-top: 0; + color: var(--color-text-active); + border-top-color: var(--color-accent); + border-top-width: 2px; + + &::before { + position: absolute; + top: 100%; + width: 100%; + height: 1px; + content: ''; + background-color: var(--color-background-base); + left: 0; + z-index: 4; + } + } + &:not(:first-of-type):not(:last-of-type) { + border-left: 0; + } + &:last-of-type { + border-left: none; + } + &-panel { + border-top: var(--border-default); + display: none; + &--selected { + flex-grow: 1; + display: flex; + flex-direction: column; + } + } + } + } +} diff --git a/src/styles/components/widgets/_radio-block.scss b/src/styles/components/widgets/_radio-block.scss index 9c4825ef7..762ee0af8 100644 --- a/src/styles/components/widgets/_radio-block.scss +++ b/src/styles/components/widgets/_radio-block.scss @@ -1,7 +1,9 @@ .radio-block { width: 100%; line-height: var(--font-leading-head); + display: flex; &__option { + flex-grow: 1; padding: var(--spacing-quarter-unit) var(--spacing-half-unit); background-color: var(--color-background-top); border: var(--border-default); diff --git a/src/styles/variables/_layout.scss b/src/styles/variables/_layout.scss index 811645680..8d03afacf 100644 --- a/src/styles/variables/_layout.scss +++ b/src/styles/variables/_layout.scss @@ -2,4 +2,4 @@ * Layout */ $layout-panel-width: 345px; -$layout-sidebar-width: 100px; +$layout-sidebar-width: 120px;