diff --git a/CHANGELOG.md b/CHANGELOG.md
index de24f7ab8..6fc1e4e34 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- node.customdata and link.customdata in `sankey` traces [#4621](https://github.com/plotly/plotly.js/pull/4621)
- `opacityscale` for `surface` traces [#4480](https://github.com/plotly/plotly.js/pull/4480)
+### Added
+- [#786](https://github.com/plotly/dash-core-components/pull/786) Added 'Next Tab' optional button to dcc.Tabs
+
## [1.8.1] -2020-02-27
### Added
- [#760](https://github.com/plotly/dash-core-components/pull/760) Added R examples to package help
diff --git a/src/components/Tabs.react.js b/src/components/Tabs.react.js
index 3c78189de..7f179b669 100644
--- a/src/components/Tabs.react.js
+++ b/src/components/Tabs.react.js
@@ -121,6 +121,96 @@ EnhancedTab.defaultProps = {
},
};
+const NextTab = ({
+ className,
+ style,
+ selectHandler,
+ value,
+ disabled,
+ disabled_style,
+ disabled_className,
+ mobile_breakpoint,
+ amountOfTabs,
+ colors,
+ vertical,
+ loading_state,
+ title,
+}) => {
+ let tabStyle = style;
+ if (disabled) {
+ tabStyle = {tabStyle, ...disabled_style};
+ }
+ let tabClassName = `tab ${className || ''}`;
+ if (disabled) {
+ tabClassName += ` tab--disabled ${disabled_className || ''}`;
+ }
+ return (
+
{
+ if (!disabled) {
+ selectHandler(value);
+ }
+ }}
+ >
+ {title}
+
+
+ );
+};
/**
* A Dash component that lets you render pages with tabs - the Tabs component's children
* can be dcc.Tab components, which can hold a label that will be displayed as a tab, and can in turn hold
@@ -168,6 +258,7 @@ export default class Tabs extends Component {
render() {
let EnhancedTabs;
let selectedTab;
+ let nextTab;
if (this.props.children) {
const children = this.parseChildrenToArray();
@@ -229,6 +320,69 @@ export default class Tabs extends Component {
});
}
+ if (this.props.children) {
+ nextTab = () => {
+ // TODO: handle components that are not dcc.Tab components (throw error)
+ // enhance Tab components coming from Dash (as dcc.Tab) with methods needed for handling logic
+ let childProps;
+ const child = this.parseChildrenToArray()[0];
+ const length = this.props.children.length;
+ let currentIndex = 0;
+
+ const nextTabHandler = () => {
+ currentIndex = this.props.value.slice(-1) - 1;
+ currentIndex = ((currentIndex + 1) % length) + 1;
+ this.props.setProps({value: `tab-${currentIndex}`});
+ };
+
+ if (
+ // disabled is a defaultProp (so it's always set)
+ // meaning that if it's not set on child.props, the actual
+ // props we want are lying a bit deeper - which means they
+ // are coming from Dash
+ isNil(child.props.disabled) &&
+ child.props._dashprivate_layout &&
+ child.props._dashprivate_layout.props
+ ) {
+ // props are coming from Dash
+ childProps = child.props._dashprivate_layout.props;
+ } else {
+ // else props are coming from React (Demo.react.js, or Tabs.test.js)
+ childProps = child.props;
+ }
+
+ return (
+
+
+
+ );
+ };
+ }
+
+ if (this.props.next_tab) {
+ EnhancedTabs = EnhancedTabs.concat(nextTab());
+ }
+
const selectedTabContent = !isNil(selectedTab) ? selectedTab : '';
const tabContainerClass = this.props.vertical
@@ -326,8 +480,10 @@ Tabs.defaultProps = {
background: '#f9f9f9',
},
vertical: false,
+ next_tab: false,
persisted_props: ['value'],
persistence_type: 'local',
+ next_tab_layout: {title: 'Next', style: null},
};
Tabs.propTypes = {
@@ -378,6 +534,16 @@ Tabs.propTypes = {
*/
vertical: PropTypes.bool,
+ /**
+ * Adds a next tab button to the component, enabling sequential scrolling through the tabs.
+ */
+ next_tab: PropTypes.bool,
+
+ /**
+ * An object that contains the entries for "title" and "style" to modify the Next Tab button.
+ */
+ next_tab_layout: PropTypes.object,
+
/**
* Breakpoint at which tabs are rendered full width (can be 0 if you don't want full width tabs on mobile)
*/
diff --git a/tests/integration/tab/test_tabs_next_button.py b/tests/integration/tab/test_tabs_next_button.py
new file mode 100644
index 000000000..fdd6ffff0
--- /dev/null
+++ b/tests/integration/tab/test_tabs_next_button.py
@@ -0,0 +1,40 @@
+import pytest
+import dash
+from dash.dependencies import Input, Output
+import dash_core_components as dcc
+import dash_html_components as html
+
+
+@pytest.mark.DCC768
+def test_tane001_next(dash_dcc):
+ app = dash.Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.H1('Dash Tabs Next Button Test'),
+ dcc.Tabs(
+ id="tabs-example",
+ value='tab-1-example',
+ next_tab=True,
+ children=[
+ dcc.Tab(
+ label='Tab One', value='tab-1', id='tab-1', children="Tab 1 Content"
+ ),
+ dcc.Tab(
+ label='Tab Two', value='tab-2', id='tab-2', children="Tab 2 Content"
+ ),
+ ],
+ ),
+ html.Div(id='tabs-output'),
+ ]
+ )
+ @app.callback(Output("tabs-output", "children"), [Input("tabs-example", "value")])
+ def display_value(value):
+ return value
+
+ dash_dcc.start_server(app)
+
+ dash_dcc.wait_for_element('#tab-1').click()
+
+ dash_dcc.wait_for_element('#next-tab').click()
+
+ dash_dcc.wait_for_text_to_equal("#tabs-output", "tab-2")