refactor: Boostrap to AntD - Tabs (#14048)

This commit is contained in:
Michael S. Molina 2021-04-29 03:49:50 -03:00 committed by GitHub
parent 4410fd047e
commit 1d6a746a09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 123 additions and 137 deletions

View File

@ -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')

View File

@ -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',

View 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,
},
},
};

View File

@ -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)`

View File

@ -244,10 +244,7 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
</ErrorBoundary>
</StickyVerticalBar>
)}
<DashboardContainer
topLevelTabs={topLevelTabs}
handleChangeTab={handleChangeTab}
/>
<DashboardContainer topLevelTabs={topLevelTabs} />
{editMode && <BuilderComponentPane topOffset={barTopOffset} />}
</StyledDashboardContent>
<ToastPresenter />

View File

@ -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>