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")