refactor: Replace react-bootstrap tabs with Antd tabs (#11090)

* Replace tabs in profile

* Replace tabs in SouthPane

* Replace tabs in TabbedSqlEditors

* Add typing for dropdown

* Add license

* Remove isSelected

* Fixes

* Add data-test

* Fix test

* Remove unnecessary style

* Remove unnecessary style

* Tests fix

* Tests fix

* Update superset-frontend/src/common/components/Dropdown.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Update superset-frontend/src/common/components/Dropdown.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Update superset-frontend/src/common/components/Dropdown.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Update superset-frontend/src/common/components/Dropdown.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Update superset-frontend/src/common/components/Tabs.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Update superset-frontend/src/common/components/Dropdown.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Update superset-frontend/src/common/components/Dropdown.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Remove inModal prop

* Remove inModal from storybook

* Move inline style to styled component

Co-authored-by: Evan Rusackas <evan@preset.io>
This commit is contained in:
Kamil Gabryjelski 2020-10-02 22:07:52 +02:00 committed by GitHub
parent 53cd05d74a
commit 4fd993c4e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 369 additions and 179 deletions

View File

@ -27,15 +27,15 @@ describe('SqlLab query tabs', () => {
cy.get('[data-test="sql-editor-tabs"]').then(tabList => { cy.get('[data-test="sql-editor-tabs"]').then(tabList => {
const initialTabCount = tabList.length; const initialTabCount = tabList.length;
// add tab // add tab
cy.get('[data-test="add-tab-icon"]').click(); cy.get('[data-test="add-tab-icon"]').first().click();
// wait until we find the new tab // wait until we find the new tab
cy.get('[data-test="sql-editor-tabs"]') cy.get('[data-test="sql-editor-tabs"]')
.children() .children()
.eq(initialTabCount - 1) .eq(0)
.contains(`Untitled Query ${initialTabCount + 1}`); .contains(`Untitled Query ${initialTabCount + 1}`);
cy.get('[data-test="sql-editor-tabs"]') cy.get('[data-test="sql-editor-tabs"]')
.children() .children()
.eq(initialTabCount) .eq(0)
.contains(`Untitled Query ${initialTabCount + 2}`); .contains(`Untitled Query ${initialTabCount + 2}`);
}); });
}); });
@ -47,9 +47,12 @@ describe('SqlLab query tabs', () => {
const initialTabCount = tabListA.length; const initialTabCount = tabListA.length;
// open the tab dropdown to remove // open the tab dropdown to remove
cy.get('[data-test="dropdown-toggle-button"]').click({ cy.get('[data-test="dropdown-toggle-button"]')
force: true, .children()
}); .first()
.click({
force: true,
});
// first item is close // first item is close
cy.get('[data-test="close-tab-menu-option"]').click(); cy.get('[data-test="close-tab-menu-option"]').click();

View File

@ -17,9 +17,10 @@
* under the License. * under the License.
*/ */
import React from 'react'; import React from 'react';
import { Col, Row, Tab } from 'react-bootstrap'; import { Col, Row } from 'react-bootstrap';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import App from 'src/profile/components/App'; import App from 'src/profile/components/App';
import Tabs from 'src/common/components/Tabs';
import { user } from './fixtures'; import { user } from './fixtures';
@ -39,6 +40,6 @@ describe('App', () => {
it('renders 4 Tabs', () => { it('renders 4 Tabs', () => {
const wrapper = shallow(<App {...mockedProps} />); const wrapper = shallow(<App {...mockedProps} />);
expect(wrapper.find(Tab)).toHaveLength(4); expect(wrapper.find(Tabs.TabPane)).toHaveLength(4);
}); });
}); });

View File

@ -20,10 +20,10 @@ import React from 'react';
import configureStore from 'redux-mock-store'; import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
import URI from 'urijs'; import URI from 'urijs';
import { Tab } from 'react-bootstrap';
import { shallow, mount } from 'enzyme'; import { shallow, mount } from 'enzyme';
import sinon from 'sinon'; import sinon from 'sinon';
import { supersetTheme, ThemeProvider } from '@superset-ui/core'; import { supersetTheme, ThemeProvider } from '@superset-ui/core';
import { EditableTabs } from 'src/common/components/Tabs';
import TabbedSqlEditors from 'src/SqlLab/components/TabbedSqlEditors'; import TabbedSqlEditors from 'src/SqlLab/components/TabbedSqlEditors';
import SqlEditor from 'src/SqlLab/components/SqlEditor'; import SqlEditor from 'src/SqlLab/components/SqlEditor';
@ -206,36 +206,36 @@ describe('TabbedSqlEditors', () => {
}, },
}; };
wrapper = getWrapper(); wrapper = getWrapper();
sinon.spy(wrapper.instance(), 'newQueryEditor');
sinon.stub(wrapper.instance().props.actions, 'switchQueryEditor'); sinon.stub(wrapper.instance().props.actions, 'switchQueryEditor');
wrapper.instance().handleSelect('add_tab', mockEvent);
expect(wrapper.instance().newQueryEditor.callCount).toBe(1);
// cannot switch to current tab, switchQueryEditor never gets called // cannot switch to current tab, switchQueryEditor never gets called
wrapper.instance().handleSelect('dfsadfs', mockEvent); wrapper.instance().handleSelect('dfsadfs', mockEvent);
expect( expect(
wrapper.instance().props.actions.switchQueryEditor.callCount, wrapper.instance().props.actions.switchQueryEditor.callCount,
).toEqual(0); ).toEqual(0);
});
it('should handle add tab', () => {
wrapper = getWrapper();
sinon.spy(wrapper.instance(), 'newQueryEditor');
wrapper.instance().handleEdit('1', 'add');
expect(wrapper.instance().newQueryEditor.callCount).toBe(1);
wrapper.instance().newQueryEditor.restore(); wrapper.instance().newQueryEditor.restore();
}); });
it('should render', () => { it('should render', () => {
wrapper = getWrapper(); wrapper = getWrapper();
wrapper.setState({ hideLeftBar: true }); wrapper.setState({ hideLeftBar: true });
const firstTab = wrapper.find(Tab).first(); const firstTab = wrapper.find(EditableTabs.TabPane).first();
expect(firstTab.props().eventKey).toContain( expect(firstTab.props()['data-key']).toContain(
initialState.sqlLab.queryEditors[0].id, initialState.sqlLab.queryEditors[0].id,
); );
expect(firstTab.find(SqlEditor)).toHaveLength(1); expect(firstTab.find(SqlEditor)).toHaveLength(1);
const lastTab = wrapper.find(Tab).last();
expect(lastTab.props().eventKey).toContain('add_tab');
}); });
it('should disable new tab when offline', () => { it('should disable new tab when offline', () => {
wrapper = getWrapper(); wrapper = getWrapper();
expect(wrapper.find(Tab).last().props().disabled).toBe(false); expect(wrapper.find(EditableTabs).props().hideAdd).toBe(false);
wrapper.setProps({ offline: true }); wrapper.setProps({ offline: true });
expect(wrapper.find(Tab).last().props().disabled).toBe(true); expect(wrapper.find(EditableTabs).props().hideAdd).toBe(true);
}); });
}); });

View File

@ -19,7 +19,8 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import shortid from 'shortid'; import shortid from 'shortid';
import { Alert, Tab, Tabs } from 'react-bootstrap'; import { Alert } from 'react-bootstrap';
import Tabs from 'src/common/components/Tabs';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { t } from '@superset-ui/core'; import { t } from '@superset-ui/core';
@ -140,9 +141,8 @@ export class SouthPane extends React.PureComponent {
); );
} }
const dataPreviewTabs = props.dataPreviewQueries.map(query => ( const dataPreviewTabs = props.dataPreviewQueries.map(query => (
<Tab <Tabs.TabPane
title={t('Preview: `%s`', decodeURIComponent(query.tableName))} tab={t('Preview: `%s`', decodeURIComponent(query.tableName))}
eventKey={query.id}
key={query.id} key={query.id}
> >
<ResultSet <ResultSet
@ -154,29 +154,27 @@ export class SouthPane extends React.PureComponent {
height={innerTabContentHeight} height={innerTabContentHeight}
displayLimit={this.props.displayLimit} displayLimit={this.props.displayLimit}
/> />
</Tab> </Tabs.TabPane>
)); ));
return ( return (
<div className="SouthPane" ref={this.southPaneRef}> <div className="SouthPane" ref={this.southPaneRef}>
<Tabs <Tabs
bsStyle="tabs" defaultActiveKey={this.props.activeSouthPaneTab}
animation={false}
className="SouthPaneTabs" className="SouthPaneTabs"
id={shortid.generate()} id={shortid.generate()}
activeKey={this.props.activeSouthPaneTab} fullWidth={false}
onSelect={this.switchTab}
> >
<Tab title={t('Results')} eventKey="Results"> <Tabs.TabPane tab={t('Results')} key="Results">
{results} {results}
</Tab> </Tabs.TabPane>
<Tab title={t('Query History')} eventKey="History"> <Tabs.TabPane tab={t('Query History')} key="History">
<QueryHistory <QueryHistory
queries={props.editorQueries} queries={props.editorQueries}
actions={props.actions} actions={props.actions}
displayLimit={props.displayLimit} displayLimit={props.displayLimit}
/> />
</Tab> </Tabs.TabPane>
{dataPreviewTabs} {dataPreviewTabs}
</Tabs> </Tabs>
</div> </div>

View File

@ -18,18 +18,19 @@
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { MenuItem, DropdownButton, Tab, Tabs } from 'react-bootstrap'; import { EditableTabs } from 'src/common/components/Tabs';
import { Dropdown } from 'src/common/components/Dropdown';
import { Menu } from 'src/common/components';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import URI from 'urijs'; import URI from 'urijs';
import { t } from '@superset-ui/core'; import { styled, t } from '@superset-ui/core';
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import { areArraysShallowEqual } from 'src/reduxUtils';
import * as Actions from '../actions/sqlLab'; import * as Actions from '../actions/sqlLab';
import SqlEditor from './SqlEditor'; import SqlEditor from './SqlEditor';
import { areArraysShallowEqual } from '../../reduxUtils';
import TabStatusIcon from './TabStatusIcon'; import TabStatusIcon from './TabStatusIcon';
import Icon from '../../components/Icon';
const propTypes = { const propTypes = {
actions: PropTypes.object.isRequired, actions: PropTypes.object.isRequired,
@ -57,6 +58,10 @@ const defaultProps = {
let queryCount = 1; let queryCount = 1;
const TabTitle = styled.span`
margin-right: ${({ theme }) => theme.gridUnit * 2}px;
`;
class TabbedSqlEditors extends React.PureComponent { class TabbedSqlEditors extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
@ -74,6 +79,8 @@ class TabbedSqlEditors extends React.PureComponent {
this, this,
); );
this.duplicateQueryEditor = this.duplicateQueryEditor.bind(this); this.duplicateQueryEditor = this.duplicateQueryEditor.bind(this);
this.handleSelect = this.handleSelect.bind(this);
this.handleEdit = this.handleEdit.bind(this);
} }
componentDidMount() { componentDidMount() {
@ -253,17 +260,23 @@ class TabbedSqlEditors extends React.PureComponent {
} }
handleSelect(key) { handleSelect(key) {
if (key === 'add_tab') { const qeid = this.props.tabHistory[this.props.tabHistory.length - 1];
if (key !== qeid) {
const queryEditor = this.props.queryEditors.find(qe => qe.id === key);
this.props.actions.switchQueryEditor(
queryEditor,
this.props.displayLimit,
);
}
}
handleEdit(key, action) {
if (action === 'remove') {
const qe = this.props.queryEditors.find(qe => qe.id === key);
this.removeQueryEditor(qe);
}
if (action === 'add') {
this.newQueryEditor(); this.newQueryEditor();
} else {
const qeid = this.props.tabHistory[this.props.tabHistory.length - 1];
if (key !== qeid) {
const queryEditor = this.props.queryEditors.find(qe => qe.id === key);
this.props.actions.switchQueryEditor(
queryEditor,
this.props.displayLimit,
);
}
} }
} }
@ -286,10 +299,7 @@ class TabbedSqlEditors extends React.PureComponent {
} }
render() { render() {
const editors = this.props.queryEditors.map((qe, i) => { const editors = this.props.queryEditors.map(qe => {
const isSelected =
this.activeQueryEditor() && this.activeQueryEditor().id === qe.id;
let latestQuery; let latestQuery;
if (qe.latestQueryId) { if (qe.latestQueryId) {
latestQuery = this.props.queries[qe.latestQueryId]; latestQuery = this.props.queries[qe.latestQueryId];
@ -300,123 +310,97 @@ class TabbedSqlEditors extends React.PureComponent {
} }
const state = latestQuery ? latestQuery.state : ''; const state = latestQuery ? latestQuery.state : '';
const title = ( const menu = (
<> <Menu>
{qe.title} <TabStatusIcon tabState={state} />{' '} <Menu.Item
<Icon className="close-btn"
role="button" key="1"
tabIndex={0}
cursor="pointer"
name="cancel-x"
onClick={() => this.removeQueryEditor(qe)} onClick={() => this.removeQueryEditor(qe)}
/> data-test="close-tab-menu-option"
</> >
<div className="icon-container">
<i className="fa fa-close" />
</div>
{t('Close tab')}
</Menu.Item>
<Menu.Item key="2" onClick={() => this.renameTab(qe)}>
<div className="icon-container">
<i className="fa fa-i-cursor" />
</div>
{t('Rename tab')}
</Menu.Item>
<Menu.Item key="3" onClick={this.toggleLeftBar}>
<div className="icon-container">
<i className="fa fa-cogs" />
</div>
{this.state.hideLeftBar ? t('Expand tool bar') : t('Hide tool bar')}
</Menu.Item>
<Menu.Item
key="4"
onClick={() => this.removeAllOtherQueryEditors(qe)}
>
<div className="icon-container">
<i className="fa fa-times-circle-o" />
</div>
{t('Close all other tabs')}
</Menu.Item>
<Menu.Item key="5" onClick={() => this.duplicateQueryEditor(qe)}>
<div className="icon-container">
<i className="fa fa-files-o" />
</div>
{t('Duplicate tab')}
</Menu.Item>
</Menu>
); );
const tabTitle = (
const tabHeader = (
<> <>
{isSelected && ( <div data-test="dropdown-toggle-button">
<DropdownButton <Dropdown overlay={menu} trigger={['click']} />
data-test="dropdown-toggle-button" </div>
bsSize="small" <TabTitle>{qe.title}</TabTitle> <TabStatusIcon tabState={state} />{' '}
id={`ddbtn-tab-${i}`}
title={' '}
noCaret
>
<MenuItem
className="close-btn"
eventKey="1"
onClick={() => this.removeQueryEditor(qe)}
data-test="close-tab-menu-option"
>
<div className="icon-container">
<i className="fa fa-close" />
</div>
{t('Close tab')}
</MenuItem>
<MenuItem eventKey="2" onClick={() => this.renameTab(qe)}>
<div className="icon-container">
<i className="fa fa-i-cursor" />
</div>
{t('Rename tab')}
</MenuItem>
<MenuItem eventKey="3" onClick={this.toggleLeftBar}>
<div className="icon-container">
<i className="fa fa-cogs" />
</div>
{this.state.hideLeftBar
? t('Expand tool bar')
: t('Hide tool bar')}
</MenuItem>
<MenuItem
eventKey="4"
onClick={() => this.removeAllOtherQueryEditors(qe)}
>
<div className="icon-container">
<i className="fa fa-times-circle-o" />
</div>
{t('Close all other tabs')}
</MenuItem>
<MenuItem
eventKey="5"
onClick={() => this.duplicateQueryEditor(qe)}
>
<div className="icon-container">
<i className="fa fa-files-o" />
</div>
{t('Duplicate tab')}
</MenuItem>
</DropdownButton>
)}
<span className="ddbtn-tab">{title}</span>
</> </>
); );
return ( return (
<Tab key={qe.id} title={tabTitle} eventKey={qe.id}> <EditableTabs.TabPane
{isSelected && ( key={qe.id}
<SqlEditor tab={tabHeader}
tables={this.props.tables.filter( // for tests - key prop isn't handled by enzyme well bcs it's a react keyword
xt => xt.queryEditorId === qe.id, data-key={qe.id}
)} >
queryEditor={qe} <SqlEditor
editorQueries={this.state.queriesArray} tables={this.props.tables.filter(xt => xt.queryEditorId === qe.id)}
dataPreviewQueries={this.state.dataPreviewQueries} queryEditor={qe}
latestQuery={latestQuery} editorQueries={this.state.queriesArray}
database={database} dataPreviewQueries={this.state.dataPreviewQueries}
actions={this.props.actions} latestQuery={latestQuery}
hideLeftBar={this.state.hideLeftBar} database={database}
defaultQueryLimit={this.props.defaultQueryLimit} actions={this.props.actions}
maxRow={this.props.maxRow} hideLeftBar={this.state.hideLeftBar}
displayLimit={this.props.displayLimit} defaultQueryLimit={this.props.defaultQueryLimit}
saveQueryWarning={this.props.saveQueryWarning} maxRow={this.props.maxRow}
scheduleQueryWarning={this.props.scheduleQueryWarning} displayLimit={this.props.displayLimit}
/> saveQueryWarning={this.props.saveQueryWarning}
)} scheduleQueryWarning={this.props.scheduleQueryWarning}
</Tab> />
</EditableTabs.TabPane>
); );
}); });
return ( return (
<Tabs <EditableTabs
bsStyle="tabs"
animation={false}
activeKey={this.props.tabHistory[this.props.tabHistory.length - 1]} activeKey={this.props.tabHistory[this.props.tabHistory.length - 1]}
onSelect={this.handleSelect.bind(this)}
id="a11y-query-editor-tabs" id="a11y-query-editor-tabs"
className="SqlEditorTabs" className="SqlEditorTabs"
data-test="sql-editor-tabs" data-test="sql-editor-tabs"
onChange={this.handleSelect}
fullWidth={false}
hideAdd={this.props.offline}
onEdit={this.handleEdit}
addIcon={<i data-test="add-tab-icon" className="fa fa-plus-circle" />}
> >
{editors} {editors}
<Tab </EditableTabs>
title={
<div>
<i data-test="add-tab-icon" className="fa fa-plus-circle" />
&nbsp;
</div>
}
className="addEditorTab"
eventKey="add_tab"
disabled={this.props.offline}
/>
</Tabs>
); );
} }
} }

View File

@ -0,0 +1,78 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { Dropdown as AntdDropdown } from 'src/common/components';
import { css } from '@emotion/core';
import { styled } from '@superset-ui/core';
const dotStyle = css`
width: 3px;
height: 3px;
border-radius: 1.5px;
background-color: #bababa;
`;
const MenuDots = styled.div`
${dotStyle};
font-weight: ${({ theme }) => theme.typography.weights.normal};
display: inline-flex;
&:hover {
background-color: ${({ theme }) => theme.colors.primary.base};
&::before,
&::after {
background-color: ${({ theme }) => theme.colors.primary.base};
}
}
&::before,
&::after {
position: absolute;
content: ' ';
${dotStyle};
}
&::before {
transform: translateY(-${({ theme }) => theme.gridUnit}px);
}
&::after {
transform: translateY(${({ theme }) => theme.gridUnit}px);
}
`;
const MenuDotsWrapper = styled.div`
display: flex;
align-items: center;
padding: ${({ theme }) => theme.gridUnit * 2}px;
padding-left: ${({ theme }) => theme.gridUnit}px;
`;
interface DropdownProps {
overlay: React.ReactElement;
}
export const Dropdown = ({ overlay, ...rest }: DropdownProps) => (
<AntdDropdown overlay={overlay} {...rest}>
<MenuDotsWrapper>
<MenuDots />
</MenuDotsWrapper>
</AntdDropdown>
);

View File

@ -78,6 +78,11 @@ const StyledModal = styled(BaseModal)`
margin-left: 8px; margin-left: 8px;
} }
} }
// styling for Tabs component
.ant-tabs {
margin-top: -18px;
}
`; `;
export default function Modal({ export default function Modal({

View File

@ -16,27 +16,46 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import React from 'react';
import { styled } from '@superset-ui/core'; import { styled } from '@superset-ui/core';
import { Tabs as AntdTabs } from 'src/common/components'; import { Tabs as AntdTabs } from 'src/common/components';
import { css } from '@emotion/core';
import Icon from '../../components/Icon';
const StyledTabs = styled(AntdTabs)` interface TabsProps {
margin-top: -18px; fullWidth?: boolean;
}
.ant-tabs-nav-list { const notForwardedProps = ['fullWidth'];
width: 100%;
}
const StyledTabs = styled(AntdTabs, {
shouldForwardProp: prop => !notForwardedProps.includes(prop),
})<TabsProps>`
.ant-tabs-tab { .ant-tabs-tab {
flex: 1 1 auto; flex: 1 1 auto;
width: 0;
&.ant-tabs-tab-active .ant-tabs-tab-btn { &.ant-tabs-tab-active .ant-tabs-tab-btn {
color: inherit; color: inherit;
} }
} }
${({ fullWidth }) =>
fullWidth &&
css`
.ant-tabs-nav-list {
width: 100%;
}
.ant-tabs-tab {
width: 0;
}
`};
.ant-tabs-tab-btn { .ant-tabs-tab-btn {
display: flex;
flex: 1 1 auto; flex: 1 1 auto;
align-items: center;
justify-content: center;
font-size: ${({ theme }) => theme.typography.sizes.s}px; font-size: ${({ theme }) => theme.typography.sizes.s}px;
text-align: center; text-align: center;
text-transform: uppercase; text-transform: uppercase;
@ -59,4 +78,48 @@ const Tabs = Object.assign(StyledTabs, {
TabPane: StyledTabPane, TabPane: StyledTabPane,
}); });
Tabs.defaultProps = {
fullWidth: true,
};
const StyledEditableTabs = styled(StyledTabs)`
.ant-tabs-content-holder {
background: white;
}
& > .ant-tabs-nav {
margin-bottom: 0;
}
.ant-tabs-tab-remove {
padding-top: 0;
padding-bottom: 0;
height: ${({ theme }) => theme.gridUnit * 6}px;
}
${({ fullWidth }) =>
fullWidth &&
css`
.ant-tabs-nav-list {
width: 100%;
}
`}
`;
const EditableTabs = Object.assign(StyledEditableTabs, {
TabPane: StyledTabPane,
});
EditableTabs.defaultProps = {
type: 'editable-card',
fullWidth: false,
};
EditableTabs.TabPane.defaultProps = {
closeIcon: (
<Icon role="button" tabIndex={0} cursor="pointer" name="cancel-x" />
),
};
export default Tabs; export default Tabs;
export { EditableTabs };

View File

@ -20,7 +20,9 @@ import React from 'react';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import { withKnobs, boolean } from '@storybook/addon-knobs'; import { withKnobs, boolean } from '@storybook/addon-knobs';
import Modal from './Modal'; import Modal from './Modal';
import Tabs from './Tabs'; import Tabs, { EditableTabs } from './Tabs';
import { Menu } from '.';
import { Dropdown } from './Dropdown';
export default { export default {
title: 'Common Components', title: 'Common Components',
@ -42,7 +44,11 @@ export const StyledModal = () => (
); );
export const StyledTabs = () => ( export const StyledTabs = () => (
<Tabs defaultActiveKey="1" centered={boolean('Center tabs', false)}> <Tabs
defaultActiveKey="1"
centered={boolean('Center tabs', false)}
fullWidth={boolean('Full width', true)}
>
<Tabs.TabPane <Tabs.TabPane
tab="Tab 1" tab="Tab 1"
key="1" key="1"
@ -59,3 +65,54 @@ export const StyledTabs = () => (
</Tabs.TabPane> </Tabs.TabPane>
</Tabs> </Tabs>
); );
export const StyledEditableTabs = () => (
<EditableTabs
defaultActiveKey="1"
centered={boolean('Center tabs', false)}
fullWidth={boolean('Full width', true)}
>
<Tabs.TabPane
tab="Tab 1"
key="1"
disabled={boolean('Tab 1 Disabled', false)}
>
Tab 1 Content!
</Tabs.TabPane>
<Tabs.TabPane
tab="Tab 2"
key="2"
disabled={boolean('Tab 2 Disabled', false)}
>
Tab 2 Content!
</Tabs.TabPane>
</EditableTabs>
);
export const TabsWithDropdownMenu = () => (
<EditableTabs
defaultActiveKey="1"
centered={boolean('Center tabs', false)}
fullWidth={boolean('Full width', true)}
>
<Tabs.TabPane
tab={
<>
<Dropdown
overlay={
<Menu>
<Menu.Item key="1">Item 1</Menu.Item>
<Menu.Item key="2">Item 2</Menu.Item>
</Menu>
}
/>
Tab with dropdown menu
</>
}
key="1"
disabled={boolean('Tab 1 Disabled', false)}
>
Tab 1 Content!
</Tabs.TabPane>
</EditableTabs>
);

View File

@ -17,7 +17,8 @@
* under the License. * under the License.
*/ */
import React from 'react'; import React from 'react';
import { Col, Row, Tabs, Tab, Panel } from 'react-bootstrap'; import { Col, Row, Panel } from 'react-bootstrap';
import Tabs from 'src/common/components/Tabs';
import { t } from '@superset-ui/core'; import { t } from '@superset-ui/core';
import Favorites from './Favorites'; import Favorites from './Favorites';
@ -39,10 +40,10 @@ export default function App({ user }: AppProps) {
<UserInfo user={user} /> <UserInfo user={user} />
</Col> </Col>
<Col md={9}> <Col md={9}>
<Tabs id="options"> <Tabs centered>
<Tab <Tabs.TabPane
eventKey={1} key="1"
title={ tab={
<div> <div>
<i className="fa fa-star" /> {t('Favorites')} <i className="fa fa-star" /> {t('Favorites')}
</div> </div>
@ -53,10 +54,10 @@ export default function App({ user }: AppProps) {
<Favorites user={user} /> <Favorites user={user} />
</Panel.Body> </Panel.Body>
</Panel> </Panel>
</Tab> </Tabs.TabPane>
<Tab <Tabs.TabPane
eventKey={2} key="2"
title={ tab={
<div> <div>
<i className="fa fa-paint-brush" /> {t('Created Content')} <i className="fa fa-paint-brush" /> {t('Created Content')}
</div> </div>
@ -67,10 +68,10 @@ export default function App({ user }: AppProps) {
<CreatedContent user={user} /> <CreatedContent user={user} />
</Panel.Body> </Panel.Body>
</Panel> </Panel>
</Tab> </Tabs.TabPane>
<Tab <Tabs.TabPane
eventKey={3} key="3"
title={ tab={
<div> <div>
<i className="fa fa-list" /> {t('Recent Activity')} <i className="fa fa-list" /> {t('Recent Activity')}
</div> </div>
@ -81,10 +82,10 @@ export default function App({ user }: AppProps) {
<RecentActivity user={user} /> <RecentActivity user={user} />
</Panel.Body> </Panel.Body>
</Panel> </Panel>
</Tab> </Tabs.TabPane>
<Tab <Tabs.TabPane
eventKey={4} key="4"
title={ tab={
<div> <div>
<i className="fa fa-lock" /> {t('Security & Access')} <i className="fa fa-lock" /> {t('Security & Access')}
</div> </div>
@ -95,7 +96,7 @@ export default function App({ user }: AppProps) {
<Security user={user} /> <Security user={user} />
</Panel.Body> </Panel.Body>
</Panel> </Panel>
</Tab> </Tabs.TabPane>
</Tabs> </Tabs>
</Col> </Col>
</Row> </Row>