Skip to content

Commit

Permalink
refactor: Boostrap to AntD - Tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-s-molina committed Apr 27, 2021
1 parent 20ab086 commit 2480a50
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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', {});

Expand Down Expand Up @@ -118,30 +121,28 @@ 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', () => {
const wrapper = setup({ dashboardLayout: undoableDashboardLayoutWithTabs });
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', () => {
Expand All @@ -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', () => {
Expand All @@ -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')
Expand Down
80 changes: 2 additions & 78 deletions superset-frontend/src/common/components/common.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
Expand Down
68 changes: 68 additions & 0 deletions superset-frontend/src/components/Tabs/Tabs.stories.tsx
Original file line number Diff line number Diff line change
@@ -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,
},
},
};
9 changes: 5 additions & 4 deletions superset-frontend/src/components/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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')};
Expand Down Expand Up @@ -96,14 +96,15 @@ const StyledTabs = styled(AntdTabs, {
}
`;

const StyledTabPane = styled(AntdTabs.TabPane)``;
const StyledTabPane = styled(AntDTabs.TabPane)``;

const Tabs = Object.assign(StyledTabs, {
TabPane: StyledTabPane,
});

Tabs.defaultProps = {
fullWidth: true,
animated: true,
};

const StyledEditableTabs = styled(StyledTabs)`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,7 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
</ErrorBoundary>
</StickyVerticalBar>
)}
<DashboardContainer
topLevelTabs={topLevelTabs}
handleChangeTab={handleChangeTab}
/>
<DashboardContainer topLevelTabs={topLevelTabs} />
{editMode && <BuilderComponentPane topOffset={barTopOffset} />}
</StyledDashboardContent>
<ToastPresenter />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
);
Expand All @@ -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>
Expand All @@ -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>
Expand Down

0 comments on commit 2480a50

Please sign in to comment.