mirror of
https://github.com/apache/superset.git
synced 2024-09-06 05:47:43 -04:00
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:
parent
53cd05d74a
commit
4fd993c4e0
@ -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();
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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>
|
||||||
|
@ -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" />
|
|
||||||
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
className="addEditorTab"
|
|
||||||
eventKey="add_tab"
|
|
||||||
disabled={this.props.offline}
|
|
||||||
/>
|
|
||||||
</Tabs>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
78
superset-frontend/src/common/components/Dropdown.tsx
Normal file
78
superset-frontend/src/common/components/Dropdown.tsx
Normal 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>
|
||||||
|
);
|
@ -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({
|
||||||
|
@ -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 };
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user