mirror of
https://github.com/apache/superset.git
synced 2024-09-12 16:49:40 -04:00
refactor: Boostrap to AntD - Tabs (#14048)
This commit is contained in:
parent
4410fd047e
commit
1d6a746a09
@ -24,7 +24,7 @@ import fetchMock from 'fetch-mock';
|
||||
import { ParentSize } from '@vx/responsive';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
import { Sticky, StickyContainer } from 'react-sticky';
|
||||
import { TabContainer, TabContent, TabPane } from 'react-bootstrap';
|
||||
import Tabs from 'src/components/Tabs';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import BuilderComponentPane from 'src/dashboard/components/BuilderComponentPane';
|
||||
@ -39,7 +39,10 @@ import {
|
||||
} from 'spec/fixtures/mockDashboardLayout';
|
||||
import { mockStoreWithTabs, storeWithState } from 'spec/fixtures/mockStore';
|
||||
import mockState from 'spec/fixtures/mockState';
|
||||
import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
|
||||
import {
|
||||
DASHBOARD_ROOT_ID,
|
||||
DASHBOARD_GRID_ID,
|
||||
} from 'src/dashboard/util/constants';
|
||||
|
||||
fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {});
|
||||
|
||||
@ -118,19 +121,17 @@ describe('DashboardBuilder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should render a TabContainer and TabContent', () => {
|
||||
it('should render one Tabs and two TabPane', () => {
|
||||
const wrapper = setup({ dashboardLayout: undoableDashboardLayoutWithTabs });
|
||||
const parentSize = wrapper.find(ParentSize);
|
||||
expect(parentSize.find(TabContainer)).toHaveLength(1);
|
||||
expect(parentSize.find(TabContent)).toHaveLength(1);
|
||||
expect(parentSize.find(Tabs)).toHaveLength(1);
|
||||
expect(parentSize.find(Tabs.TabPane)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should set animation=true, mountOnEnter=true, and unmounOnExit=false on TabContainer for perf', () => {
|
||||
it('should set animated=true on Tabs for perf', () => {
|
||||
const wrapper = setup({ dashboardLayout: undoableDashboardLayoutWithTabs });
|
||||
const tabProps = wrapper.find(ParentSize).find(TabContainer).props();
|
||||
expect(tabProps.animation).toBe(true);
|
||||
expect(tabProps.mountOnEnter).toBe(true);
|
||||
expect(tabProps.unmountOnExit).toBe(false);
|
||||
const tabProps = wrapper.find(ParentSize).find(Tabs).props();
|
||||
expect(tabProps.animated).toBe(true);
|
||||
});
|
||||
|
||||
it('should render a TabPane and DashboardGrid for first Tab', () => {
|
||||
@ -138,10 +139,10 @@ describe('DashboardBuilder', () => {
|
||||
const parentSize = wrapper.find(ParentSize);
|
||||
const expectedCount =
|
||||
undoableDashboardLayoutWithTabs.present.TABS_ID.children.length;
|
||||
expect(parentSize.find(TabPane)).toHaveLength(expectedCount);
|
||||
expect(parentSize.find(TabPane).first().find(DashboardGrid)).toHaveLength(
|
||||
1,
|
||||
);
|
||||
expect(parentSize.find(Tabs.TabPane)).toHaveLength(expectedCount);
|
||||
expect(
|
||||
parentSize.find(Tabs.TabPane).first().find(DashboardGrid),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should render a TabPane and DashboardGrid for second Tab', () => {
|
||||
@ -155,8 +156,10 @@ describe('DashboardBuilder', () => {
|
||||
const parentSize = wrapper.find(ParentSize);
|
||||
const expectedCount =
|
||||
undoableDashboardLayoutWithTabs.present.TABS_ID.children.length;
|
||||
expect(parentSize.find(TabPane)).toHaveLength(expectedCount);
|
||||
expect(parentSize.find(TabPane).at(1).find(DashboardGrid)).toHaveLength(1);
|
||||
expect(parentSize.find(Tabs.TabPane)).toHaveLength(expectedCount);
|
||||
expect(
|
||||
parentSize.find(Tabs.TabPane).at(1).find(DashboardGrid),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should render a BuilderComponentPane if editMode=false and user selects "Insert Components" pane', () => {
|
||||
@ -179,7 +182,7 @@ describe('DashboardBuilder', () => {
|
||||
dashboardLayout: undoableDashboardLayoutWithTabs,
|
||||
});
|
||||
|
||||
expect(wrapper.find(TabContainer).prop('activeKey')).toBe(0);
|
||||
expect(wrapper.find(Tabs).prop('activeKey')).toBe(DASHBOARD_GRID_ID);
|
||||
|
||||
wrapper
|
||||
.find('.dashboard-component-tabs .ant-tabs .ant-tabs-tab')
|
||||
|
@ -18,13 +18,11 @@
|
||||
*/
|
||||
import React, { useState, useRef, useCallback } from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { withKnobs, boolean } from '@storybook/addon-knobs';
|
||||
import { withKnobs } from '@storybook/addon-knobs';
|
||||
import { CronPicker, CronError } from 'src/components/CronPicker';
|
||||
import Modal from 'src/components/Modal';
|
||||
import InfoTooltip from 'src/components/InfoTooltip';
|
||||
import { Dropdown } from 'src/components/Dropdown';
|
||||
import Tabs, { EditableTabs } from 'src/components/Tabs';
|
||||
import { Menu, Input, Divider } from '.';
|
||||
import { Input, Divider } from '.';
|
||||
|
||||
export default {
|
||||
title: 'Common components',
|
||||
@ -45,80 +43,6 @@ export const StyledModal = () => (
|
||||
</Modal>
|
||||
);
|
||||
|
||||
export const StyledTabs = () => (
|
||||
<Tabs
|
||||
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>
|
||||
</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>
|
||||
);
|
||||
|
||||
export const StyledInfoTooltip = (args: any) => {
|
||||
const styles = {
|
||||
padding: '100px 0 0 200px',
|
||||
|
68
superset-frontend/src/components/Tabs/Tabs.stories.tsx
Normal file
68
superset-frontend/src/components/Tabs/Tabs.stories.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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 Tabs, { TabsProps } from '.';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
export default {
|
||||
title: 'Tabs',
|
||||
component: Tabs,
|
||||
};
|
||||
|
||||
export const InteractiveTabs = (args: TabsProps) => (
|
||||
<Tabs {...args}>
|
||||
<TabPane tab="Tab 1" key="1">
|
||||
Content of Tab Pane 1
|
||||
</TabPane>
|
||||
<TabPane tab="Tab 2" key="2">
|
||||
Content of Tab Pane 2
|
||||
</TabPane>
|
||||
<TabPane tab="Tab 3" key="3">
|
||||
Content of Tab Pane 3
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
);
|
||||
|
||||
InteractiveTabs.args = {
|
||||
defaultActiveKey: '1',
|
||||
animated: true,
|
||||
centered: false,
|
||||
fullWidth: false,
|
||||
allowOverflow: false,
|
||||
};
|
||||
|
||||
InteractiveTabs.argTypes = {
|
||||
onChange: { action: 'onChange' },
|
||||
type: {
|
||||
defaultValue: 'line',
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
options: ['line', 'card', 'editable-card'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
InteractiveTabs.story = {
|
||||
parameters: {
|
||||
knobs: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
};
|
@ -18,17 +18,17 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { css, styled } from '@superset-ui/core';
|
||||
import { Tabs as AntdTabs } from 'src/common/components';
|
||||
import AntDTabs, { TabsProps as AntDTabsProps } from 'antd/lib/tabs';
|
||||
import Icon from 'src/components/Icon';
|
||||
|
||||
interface TabsProps {
|
||||
export interface TabsProps extends AntDTabsProps {
|
||||
fullWidth?: boolean;
|
||||
allowOverflow?: boolean;
|
||||
}
|
||||
|
||||
const notForwardedProps = ['fullWidth', 'allowOverflow'];
|
||||
|
||||
const StyledTabs = styled(AntdTabs, {
|
||||
const StyledTabs = styled(AntDTabs, {
|
||||
shouldForwardProp: prop => !notForwardedProps.includes(prop),
|
||||
})<TabsProps>`
|
||||
overflow: ${({ allowOverflow }) => (allowOverflow ? 'visible' : 'hidden')};
|
||||
@ -96,7 +96,7 @@ const StyledTabs = styled(AntdTabs, {
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTabPane = styled(AntdTabs.TabPane)``;
|
||||
const StyledTabPane = styled(AntDTabs.TabPane)``;
|
||||
|
||||
const Tabs = Object.assign(StyledTabs, {
|
||||
TabPane: StyledTabPane,
|
||||
@ -104,6 +104,7 @@ const Tabs = Object.assign(StyledTabs, {
|
||||
|
||||
Tabs.defaultProps = {
|
||||
fullWidth: true,
|
||||
animated: true,
|
||||
};
|
||||
|
||||
const StyledEditableTabs = styled(StyledTabs)`
|
||||
|
@ -244,10 +244,7 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
|
||||
</ErrorBoundary>
|
||||
</StickyVerticalBar>
|
||||
)}
|
||||
<DashboardContainer
|
||||
topLevelTabs={topLevelTabs}
|
||||
handleChangeTab={handleChangeTab}
|
||||
/>
|
||||
<DashboardContainer topLevelTabs={topLevelTabs} />
|
||||
{editMode && <BuilderComponentPane topOffset={barTopOffset} />}
|
||||
</StyledDashboardContent>
|
||||
<ToastPresenter />
|
||||
|
@ -19,8 +19,8 @@
|
||||
// ParentSize uses resize observer so the dashboard will update size
|
||||
// when its container size changes, due to e.g., builder side panel opening
|
||||
import { ParentSize } from '@vx/responsive';
|
||||
import { TabContainer, TabContent, TabPane } from 'react-bootstrap';
|
||||
import React, { FC, SyntheticEvent, useEffect, useState } from 'react';
|
||||
import Tabs from 'src/components/Tabs';
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import DashboardGrid from 'src/dashboard/containers/DashboardGrid';
|
||||
import getLeafComponentIdFromPath from 'src/dashboard/util/getLeafComponentIdFromPath';
|
||||
@ -33,13 +33,9 @@ import { getRootLevelTabIndex } from './utils';
|
||||
|
||||
type DashboardContainerProps = {
|
||||
topLevelTabs?: LayoutItem;
|
||||
handleChangeTab: (event: SyntheticEvent<TabContainer, Event>) => void;
|
||||
};
|
||||
|
||||
const DashboardContainer: FC<DashboardContainerProps> = ({
|
||||
topLevelTabs,
|
||||
handleChangeTab,
|
||||
}) => {
|
||||
const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
|
||||
const dashboardLayout = useSelector<RootState, DashboardLayout>(
|
||||
state => state.dashboardLayout.present,
|
||||
);
|
||||
@ -58,6 +54,9 @@ const DashboardContainer: FC<DashboardContainerProps> = ({
|
||||
? topLevelTabs.children
|
||||
: [DASHBOARD_GRID_ID];
|
||||
|
||||
const min = Math.min(tabIndex, childIds.length - 1);
|
||||
const activeKey = min === 0 ? DASHBOARD_GRID_ID : min.toString();
|
||||
|
||||
return (
|
||||
<div className="grid-container" data-test="grid-container">
|
||||
<ParentSize>
|
||||
@ -68,35 +67,29 @@ const DashboardContainer: FC<DashboardContainerProps> = ({
|
||||
the entire dashboard upon adding/removing top-level tabs, which would otherwise
|
||||
happen because of React's diffing algorithm
|
||||
*/
|
||||
<TabContainer
|
||||
<Tabs
|
||||
id={DASHBOARD_GRID_ID}
|
||||
activeKey={Math.min(tabIndex, childIds.length - 1)}
|
||||
onSelect={handleChangeTab}
|
||||
// @ts-ignore
|
||||
animation
|
||||
mountOnEnter
|
||||
unmountOnExit={false}
|
||||
activeKey={activeKey}
|
||||
renderTabBar={() => <></>}
|
||||
fullWidth={false}
|
||||
>
|
||||
<TabContent>
|
||||
{childIds.map((id, index) => (
|
||||
// Matching the key of the first TabPane irrespective of topLevelTabs
|
||||
// lets us keep the same React component tree when !!topLevelTabs changes.
|
||||
// This avoids expensive mounts/unmounts of the entire dashboard.
|
||||
<TabPane
|
||||
key={index === 0 ? DASHBOARD_GRID_ID : id}
|
||||
eventKey={index}
|
||||
>
|
||||
<DashboardGrid
|
||||
gridComponent={dashboardLayout[id]}
|
||||
// see isValidChild for why tabs do not increment the depth of their children
|
||||
depth={DASHBOARD_ROOT_DEPTH + 1} // (topLevelTabs ? 0 : 1)}
|
||||
width={width}
|
||||
isComponentVisible={index === tabIndex}
|
||||
/>
|
||||
</TabPane>
|
||||
))}
|
||||
</TabContent>
|
||||
</TabContainer>
|
||||
{childIds.map((id, index) => (
|
||||
// Matching the key of the first TabPane irrespective of topLevelTabs
|
||||
// lets us keep the same React component tree when !!topLevelTabs changes.
|
||||
// This avoids expensive mounts/unmounts of the entire dashboard.
|
||||
<Tabs.TabPane
|
||||
key={index === 0 ? DASHBOARD_GRID_ID : index.toString()}
|
||||
>
|
||||
<DashboardGrid
|
||||
gridComponent={dashboardLayout[id]}
|
||||
// see isValidChild for why tabs do not increment the depth of their children
|
||||
depth={DASHBOARD_ROOT_DEPTH + 1} // (topLevelTabs ? 0 : 1)}
|
||||
width={width}
|
||||
isComponentVisible={index === tabIndex}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
))}
|
||||
</Tabs>
|
||||
)}
|
||||
</ParentSize>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user