Migrates Collapse component from Bootstrap to AntD (#12920)

This commit is contained in:
Michael S. Molina 2021-02-23 15:24:08 -03:00 committed by GitHub
parent e4a0233181
commit 9a05d6afe5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 529 additions and 253 deletions

View File

@ -27,8 +27,8 @@ import { supersetTheme, ThemeProvider } from '@superset-ui/core';
import '../src/theme.ts';
import './storybook.css';
const themeDecorator = storyFn => (
<ThemeProvider theme={supersetTheme}>{storyFn()}</ThemeProvider>
const themeDecorator = Story => (
<ThemeProvider theme={supersetTheme}>{<Story />}</ThemeProvider>
);
addDecorator(jsxDecorator);

View File

@ -21,12 +21,13 @@ import { mount, shallow } from 'enzyme';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
import Collapse from 'src/common/components/Collapse';
import { IconTooltip } from 'src/components/IconTooltip';
import Fade from 'src/common/components/Fade';
import TableElement from 'src/SqlLab/components/TableElement';
import ColumnElement from 'src/SqlLab/components/ColumnElement';
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
import { mockedActions, table } from './fixtures';
describe('TableElement', () => {
@ -43,9 +44,19 @@ describe('TableElement', () => {
it('renders with props', () => {
expect(React.isValidElement(<TableElement {...mockedProps} />)).toBe(true);
});
it('has 2 IconTooltip elements', () => {
const wrapper = shallow(<TableElement {...mockedProps} />);
expect(wrapper.find(IconTooltip)).toHaveLength(2);
it('has 4 IconTooltip elements', () => {
const wrapper = mount(
<Provider store={store}>
<TableElement {...mockedProps} />
</Provider>,
{
wrappingComponent: ThemeProvider,
wrappingComponentProps: {
theme: supersetTheme,
},
},
);
expect(wrapper.find(IconTooltip)).toHaveLength(4);
});
it('has 14 columns', () => {
const wrapper = shallow(<TableElement {...mockedProps} />);
@ -64,20 +75,45 @@ describe('TableElement', () => {
},
);
});
it('fades table', () => {
const wrapper = shallow(<TableElement {...mockedProps} />);
expect(wrapper.state().hovered).toBe(false);
it('fades table', async () => {
const wrapper = mount(
<Provider store={store}>
<TableElement {...mockedProps} />
</Provider>,
{
wrappingComponent: ThemeProvider,
wrappingComponentProps: {
theme: supersetTheme,
},
},
);
expect(wrapper.find(TableElement).state().hovered).toBe(false);
expect(wrapper.find(Fade).props().hovered).toBe(false);
wrapper.find('div.TableElement').simulate('mouseEnter');
expect(wrapper.state().hovered).toBe(true);
wrapper.find('.header-container').hostNodes().simulate('mouseEnter');
await waitForComponentToPaint(wrapper, 300);
expect(wrapper.find(TableElement).state().hovered).toBe(true);
expect(wrapper.find(Fade).props().hovered).toBe(true);
});
it('sorts columns', () => {
const wrapper = shallow(<TableElement {...mockedProps} />);
expect(wrapper.state().sortColumns).toBe(false);
const wrapper = mount(
<Provider store={store}>
<Collapse>
<TableElement {...mockedProps} />
</Collapse>
</Provider>,
{
wrappingComponent: ThemeProvider,
wrappingComponentProps: {
theme: supersetTheme,
},
},
);
expect(wrapper.find(TableElement).state().sortColumns).toBe(false);
wrapper.find('.header-container').hostNodes().simulate('click');
expect(wrapper.find(ColumnElement).first().props().column.name).toBe('id');
wrapper.find('.sort-cols').simulate('click');
expect(wrapper.state().sortColumns).toBe(true);
wrapper.find('.header-container').simulate('mouseEnter');
wrapper.find('.sort-cols').hostNodes().simulate('click');
expect(wrapper.find(TableElement).state().sortColumns).toBe(true);
expect(wrapper.find(ColumnElement).first().props().column.name).toBe(
'active',
);
@ -99,10 +135,18 @@ describe('TableElement', () => {
expect(mockedActions.collapseTable.called).toBe(true);
});
it('removes the table', () => {
const wrapper = shallow(<TableElement {...mockedProps} />);
expect(wrapper.state().expanded).toBe(true);
wrapper.find('.table-remove').simulate('click');
expect(wrapper.state().expanded).toBe(false);
const wrapper = mount(
<Provider store={store}>
<TableElement {...mockedProps} />
</Provider>,
{
wrappingComponent: ThemeProvider,
wrappingComponentProps: {
theme: supersetTheme,
},
},
);
wrapper.find('.table-remove').hostNodes().simulate('click');
expect(mockedActions.removeDataPreview.called).toBe(true);
});
});

View File

@ -19,7 +19,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import Button from 'src/components/Button';
import { t, styled } from '@superset-ui/core';
import { t, styled, css } from '@superset-ui/core';
import Collapse from 'src/common/components/Collapse';
import TableElement from './TableElement';
import TableSelector from '../../components/TableSelector';
@ -145,13 +146,38 @@ export default class SqlEditorLeftBar extends React.PureComponent {
<div className="divider" />
<StyledScrollbarContainer>
<StyledScrollbarContent contentHeight={tableMetaDataHeight}>
{this.props.tables.map(table => (
<TableElement
table={table}
key={table.id}
actions={this.props.actions}
/>
))}
<Collapse
ghost
expandIconPosition="right"
css={theme => css`
.ant-collapse-item {
margin-bottom: ${theme.gridUnit * 3}px;
}
.ant-collapse-header {
padding: 0px !important;
display: flex;
align-items: center;
}
.ant-collapse-content-box {
padding: 0px ${theme.gridUnit * 4}px 0px 0px !important;
}
.ant-collapse-arrow {
top: ${theme.gridUnit * 2}px !important;
color: ${theme.colors.primary.dark1} !important;
&: hover {
color: ${theme.colors.primary.dark2} !important;
}
}
`}
>
{this.props.tables.map(table => (
<TableElement
table={table}
key={table.id}
actions={this.props.actions}
/>
))}
</Collapse>
</StyledScrollbarContent>
</StyledScrollbarContainer>
{shouldShowReset && (

View File

@ -18,10 +18,12 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Collapse, Well } from 'react-bootstrap';
import { Well } from 'react-bootstrap';
import Collapse from 'src/common/components/Collapse';
import ButtonGroup from 'src/components/ButtonGroup';
import shortid from 'shortid';
import { t, styled } from '@superset-ui/core';
import { debounce } from 'lodash';
import Fade from 'src/common/components/Fade';
import { Tooltip } from 'src/common/components/Tooltip';
@ -35,13 +37,11 @@ import Loading from '../../components/Loading';
const propTypes = {
table: PropTypes.object,
actions: PropTypes.object,
timeout: PropTypes.number, // used for tests
};
const defaultProps = {
actions: {},
table: null,
timeout: 500,
};
const StyledSpan = styled.span`
@ -57,13 +57,11 @@ class TableElement extends React.PureComponent {
super(props);
this.state = {
sortColumns: false,
expanded: true,
hovered: false,
};
this.removeFromStore = this.removeFromStore.bind(this);
this.toggleSortColumns = this.toggleSortColumns.bind(this);
this.removeTable = this.removeTable.bind(this);
this.setHover = this.setHover.bind(this);
this.setHover = debounce(this.setHover.bind(this), 100);
}
setHover(hovered) {
@ -91,18 +89,14 @@ class TableElement extends React.PureComponent {
}
removeTable() {
this.setState({ expanded: false });
this.props.actions.removeDataPreview(this.props.table);
this.props.actions.removeTable(this.props.table);
}
toggleSortColumns() {
this.setState(prevState => ({ sortColumns: !prevState.sortColumns }));
}
removeFromStore() {
this.props.actions.removeTable(this.props.table);
}
renderWell() {
const { table } = this.props;
let header;
@ -208,7 +202,11 @@ class TableElement extends React.PureComponent {
renderHeader() {
const { table } = this.props;
return (
<div className="clearfix header-container">
<div
className="clearfix header-container"
onMouseEnter={() => this.setHover(true)}
onMouseLeave={() => this.setHover(false)}
>
<Tooltip
id="copy-to-clipboard-tooltip"
placement="top"
@ -231,21 +229,13 @@ class TableElement extends React.PureComponent {
{table.isMetadataLoading || table.isExtraMetadataLoading ? (
<Loading position="inline" />
) : (
<Fade hovered={this.state.hovered}>{this.renderControls()}</Fade>
<Fade
hovered={this.state.hovered}
onClick={e => e.stopPropagation()}
>
{this.renderControls()}
</Fade>
)}
<i
role="button"
aria-label="Toggle table"
tabIndex={0}
onClick={e => {
this.toggleTable(e);
}}
className={
'text-primary pointer m-l-10 ' +
'fa fa-lg ' +
`fa-angle-${table.expanded ? 'up' : 'down'}`
}
/>
</div>
</div>
);
@ -270,39 +260,36 @@ class TableElement extends React.PureComponent {
});
}
}
const metadata = (
<Collapse in={table.expanded} timeout={this.props.timeout}>
<div
onMouseEnter={() => this.setHover(true)}
onMouseLeave={() => this.setHover(false)}
css={{ paddingTop: 6 }}
>
{this.renderWell()}
<div>
{this.renderWell()}
<div className="table-columns m-t-5">
{cols &&
cols.map(col => <ColumnElement column={col} key={col.name} />)}
</div>
{cols &&
cols.map(col => <ColumnElement column={col} key={col.name} />)}
</div>
</Collapse>
</div>
);
return metadata;
}
render() {
return (
<Collapse
in={this.state.expanded}
timeout={this.props.timeout}
onExited={this.removeFromStore}
<Collapse.Panel
{...this.props}
header={this.renderHeader()}
className="TableElement"
>
<div
className="TableElement table-schema m-b-10"
onMouseEnter={() => this.setHover(true)}
onMouseLeave={() => this.setHover(false)}
>
{this.renderHeader()}
<div>{this.renderBody()}</div>
</div>
</Collapse>
{this.renderBody()}
</Collapse.Panel>
);
}
}
TableElement.propTypes = propTypes;
TableElement.defaultProps = defaultProps;

View File

@ -413,8 +413,6 @@ div.tablePopover {
}
.TableElement {
margin-right: 10px;
.well {
margin-top: 5px;
margin-bottom: 5px;
@ -428,18 +426,22 @@ div.tablePopover {
.header-container {
display: flex;
flex: 1;
align-items: center;
.table-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 16px;
flex: 1;
}
.header-right-side {
margin-left: auto;
display: flex;
align-items: center;
margin-right: 33px;
}
}
}

View File

@ -0,0 +1,23 @@
/**
* 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.
*/
{
"rules": {
"no-restricted-imports": 0
}
}

View File

@ -0,0 +1,71 @@
/**
* 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 { useTheme } from '@superset-ui/core';
import Collapse, { CollapseProps } from '.';
export default {
title: 'Collapse',
component: Collapse,
};
export const InteractiveCollapse = (args: CollapseProps) => {
const theme = useTheme();
return (
<Collapse
defaultActiveKey={['1']}
style={
args.light ? { background: theme.colors.grayscale.light2 } : undefined
}
{...args}
>
<Collapse.Panel header="Header 1" key="1">
Content 1
</Collapse.Panel>
<Collapse.Panel header="Header 2" key="2">
Content 2
</Collapse.Panel>
</Collapse>
);
};
InteractiveCollapse.args = {
ghost: false,
bordered: true,
accordion: false,
};
InteractiveCollapse.argTypes = {
theme: {
table: {
disable: true,
},
},
};
InteractiveCollapse.story = {
parameters: {
actions: {
disabled: true,
},
knobs: {
disabled: true,
},
},
};

View File

@ -0,0 +1,110 @@
/**
* 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 { render, screen } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { supersetTheme } from '@superset-ui/core';
import { hexToRgb } from 'src/utils/colorUtils';
import Collapse, { CollapseProps } from '.';
function renderCollapse(props?: CollapseProps) {
return render(
<Collapse {...props}>
<Collapse.Panel header="Header 1" key="1">
Content 1
</Collapse.Panel>
<Collapse.Panel header="Header 2" key="2">
Content 2
</Collapse.Panel>
</Collapse>,
);
}
test('renders collapsed with default props', () => {
renderCollapse();
const headers = screen.getAllByRole('button');
expect(headers[0]).toHaveTextContent('Header 1');
expect(headers[1]).toHaveTextContent('Header 2');
expect(screen.queryByText('Content 1')).not.toBeInTheDocument();
expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
});
test('renders with one item expanded by default', () => {
renderCollapse({ defaultActiveKey: ['1'] });
const headers = screen.getAllByRole('button');
expect(headers[0]).toHaveTextContent('Header 1');
expect(headers[1]).toHaveTextContent('Header 2');
expect(screen.getByText('Content 1')).toBeInTheDocument();
expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
});
test('expands on click', () => {
renderCollapse();
expect(screen.queryByText('Content 1')).not.toBeInTheDocument();
expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
userEvent.click(screen.getAllByRole('button')[0]);
expect(screen.getByText('Content 1')).toBeInTheDocument();
expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
});
test('collapses on click', () => {
renderCollapse({ defaultActiveKey: ['1'] });
expect(screen.getByText('Content 1')).toBeInTheDocument();
expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
userEvent.click(screen.getAllByRole('button')[0]);
expect(screen.getByText('Content 1').parentNode).toHaveClass(
'ant-collapse-content-hidden',
);
expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
});
test('renders with custom properties', () => {
renderCollapse({
light: true,
bigger: true,
bold: true,
animateArrows: true,
});
const header = document.getElementsByClassName('ant-collapse-header')[0];
const arrow = document.getElementsByClassName('ant-collapse-arrow')[0]
.children[0];
const headerStyle = window.getComputedStyle(header);
const arrowStyle = window.getComputedStyle(arrow);
expect(headerStyle.fontWeight).toBe(
supersetTheme.typography.weights.bold.toString(),
);
expect(headerStyle.fontSize).toBe(`${supersetTheme.gridUnit * 4}px`);
expect(headerStyle.color).toBe(
hexToRgb(supersetTheme.colors.grayscale.light4),
);
expect(arrowStyle.transition).toBe('transform 0.24s');
});

View File

@ -18,11 +18,10 @@
*/
import React from 'react';
import { styled } from '@superset-ui/core';
// eslint-disable-next-line no-restricted-imports
import { Collapse as AntdCollapse } from 'antd';
import { CollapseProps as AntdCollapseProps } from 'antd/lib/collapse';
interface CollapseProps extends AntdCollapseProps {
export interface CollapseProps extends AntdCollapseProps {
light?: boolean;
bigger?: boolean;
bold?: boolean;
@ -34,13 +33,7 @@ const Collapse = Object.assign(
styled(({ light, bigger, bold, animateArrows, ...props }: CollapseProps) => (
<AntdCollapse {...props} />
))`
height: 100%;
.ant-collapse-item {
border: 0;
height: 100%;
&:last-of-type.ant-collapse-item-active {
padding-bottom: ${({ theme }) => theme.gridUnit * 3}px;
}
.ant-collapse-header {
font-weight: ${({ bold, theme }) =>
bold
@ -80,9 +73,7 @@ const Collapse = Object.assign(
`}
}
.ant-collapse-content {
height: 100%;
.ant-collapse-content-box {
height: 100%;
.loading.inline {
margin: ${({ theme }) => theme.gridUnit * 12}px auto;
display: block;

View File

@ -17,9 +17,9 @@
* under the License.
*/
import { styled } from '@superset-ui/core';
import { Radio as BaseRadio } from 'src/common/components';
import { Radio as AntdRadio } from 'antd';
const StyledRadio = styled(BaseRadio)`
const StyledRadio = styled(AntdRadio)`
.ant-radio-inner {
top: -1px;
left: 2px;
@ -51,7 +51,7 @@ const StyledRadio = styled(BaseRadio)`
}
}
`;
const StyledGroup = styled(BaseRadio.Group)`
const StyledGroup = styled(AntdRadio.Group)`
font-size: inherit;
`;

View File

@ -18,13 +18,13 @@
*/
import React from 'react';
import { useTheme } from '@superset-ui/core';
import { Tooltip as BaseTooltip } from 'src/common/components';
import { Tooltip as AntdTooltip } from 'antd';
import { TooltipProps } from 'antd/lib/tooltip';
export const Tooltip = (props: TooltipProps) => {
const theme = useTheme();
return (
<BaseTooltip
<AntdTooltip
overlayStyle={{ fontSize: theme.typography.sizes.s, lineHeight: '1.6' }}
color={`${theme.colors.grayscale.dark2}e6`}
{...props}

View File

@ -34,7 +34,6 @@ import {
} from './DatePicker';
import Badge from './Badge';
import ProgressBar from './ProgressBar';
import Collapse from './Collapse';
import { CronPicker, CronError } from './CronPicker';
export default {
@ -264,75 +263,6 @@ export const BadgeSuccess = () => <Badge status="success" text="Success" />;
export const BadgeError = () => <Badge status="error" text="Error" />;
export const BadgeSmall = () => <Badge count={100} size="small" />;
export const CollapseDefault = () => (
<Collapse defaultActiveKey={['1']}>
<Collapse.Panel header="Hi! I am a header" key="1">
Hi! I am a sample content
</Collapse.Panel>
<Collapse.Panel header="Hi! I am another header" key="2">
Hi! I am another sample content
</Collapse.Panel>
</Collapse>
);
export const CollapseGhost = () => (
<Collapse defaultActiveKey={['1']} ghost>
<Collapse.Panel header="Hi! I am a header" key="1">
Hi! I am a sample content
</Collapse.Panel>
<Collapse.Panel header="Hi! I am another header" key="2">
Hi! I am another sample content
</Collapse.Panel>
</Collapse>
);
export const CollapseBold = () => (
<Collapse defaultActiveKey={['1']} bold>
<Collapse.Panel header="Hi! I am a header" key="1">
Hi! I am a sample content
</Collapse.Panel>
<Collapse.Panel header="Hi! I am another header" key="2">
Hi! I am another sample content
</Collapse.Panel>
</Collapse>
);
export const CollapseBigger = () => (
<Collapse defaultActiveKey={['1']} bigger>
<Collapse.Panel header="Hi! I am a header" key="1">
Hi! I am a sample content
</Collapse.Panel>
<Collapse.Panel header="Hi! I am another header" key="2">
Hi! I am another sample content
</Collapse.Panel>
</Collapse>
);
export const CollapseTextLight = () => (
<Collapse defaultActiveKey={['1']} light>
<Collapse.Panel
header="Hi! I am a header"
key="1"
style={{ background: '#BBB' }}
>
Hi! I am a sample content
</Collapse.Panel>
<Collapse.Panel
header="Hi! I am another header"
key="2"
style={{ background: '#BBB' }}
>
Hi! I am another sample content
</Collapse.Panel>
</Collapse>
);
export const CollapseAnimateArrows = () => (
<Collapse animateArrows defaultActiveKey={['1']}>
<Collapse.Panel header="Hi! I am a header" key="1">
Hi! I am a sample content
</Collapse.Panel>
<Collapse.Panel header="Hi! I am another header" key="2">
Hi! I am another sample content
</Collapse.Panel>
</Collapse>
);
export function StyledCronPicker() {
// @ts-ignore
const inputRef = useRef<Input>(null);

View File

@ -18,7 +18,6 @@
*/
import React from 'react';
import { styled } from '@superset-ui/core';
// eslint-disable-next-line no-restricted-imports
import { Dropdown, Menu as AntdMenu, Input as AntdInput, Skeleton } from 'antd';
import { DropDownProps } from 'antd/lib/dropdown';
/*
@ -26,11 +25,9 @@ import { DropDownProps } from 'antd/lib/dropdown';
For documentation, see https://ant.design/components/overview/
*/
// eslint-disable-next-line no-restricted-imports
export {
AutoComplete,
Avatar,
Button,
Card,
Checkbox,
Col,
@ -45,7 +42,6 @@ export {
Tree,
Popover,
Slider,
Radio,
Row,
Space,
Select,
@ -60,7 +56,7 @@ export { default as Alert, AlertProps } from 'antd/lib/alert';
export { TreeProps } from 'antd/lib/tree';
export { FormInstance } from 'antd/lib/form';
export { RadioChangeEvent } from 'antd/lib/radio';
export { default as Collapse } from './Collapse';
export { default as Badge } from './Badge';
export { default as Progress } from './ProgressBar';

View File

@ -0,0 +1,23 @@
/**
* 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.
*/
{
"rules": {
"no-restricted-imports": 0
}
}

View File

@ -20,7 +20,7 @@ import React, { CSSProperties, Children, ReactElement } from 'react';
import { kebabCase } from 'lodash';
import { mix } from 'polished';
import cx from 'classnames';
import { Button as AntdButton } from 'src/common/components';
import { Button as AntdButton } from 'antd';
import { useTheme } from '@superset-ui/core';
import { Tooltip } from 'src/common/components/Tooltip';

View File

@ -24,7 +24,8 @@ import {
CheckCircleFilled,
ExclamationCircleFilled,
} from '@ant-design/icons';
import { Collapse, Popover } from 'src/common/components/index';
import { Popover } from 'src/common/components/index';
import Collapse from 'src/common/components/Collapse';
import { Global } from '@emotion/core';
import {
Indent,
@ -173,7 +174,7 @@ const DetailsPanelPopover = ({
</Title>
}
>
<Indent>
<Indent css={{ paddingBottom: theme.gridUnit * 3 }}>
{appliedIndicators.map(indicator => (
<Indicator
key={indicatorKey(indicator)}
@ -197,7 +198,7 @@ const DetailsPanelPopover = ({
</Title>
}
>
<Indent>
<Indent css={{ paddingBottom: theme.gridUnit * 3 }}>
{incompatibleIndicators.map(indicator => (
<Indicator
key={indicatorKey(indicator)}
@ -219,7 +220,7 @@ const DetailsPanelPopover = ({
}
disabled={!unsetIndicators.length}
>
<Indent>
<Indent css={{ paddingBottom: theme.gridUnit * 3 }}>
{unsetIndicators.map(indicator => (
<Indicator
key={indicatorKey(indicator)}

View File

@ -24,8 +24,8 @@ import { connect } from 'react-redux';
import { t, styled, getChartControlPanelRegistry } from '@superset-ui/core';
import Tabs from 'src/common/components/Tabs';
import { Collapse } from 'src/common/components';
import Alert from 'src/components/Alert';
import Collapse from 'src/common/components/Collapse';
import { PluginContext } from 'src/components/DynamicPlugins';
import Loading from 'src/components/Loading';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';

View File

@ -17,8 +17,8 @@
* under the License.
*/
import React, { useCallback, useEffect, useState } from 'react';
import { styled, t } from '@superset-ui/core';
import { Collapse } from 'src/common/components';
import { styled, t, css } from '@superset-ui/core';
import Collapse from 'src/common/components/Collapse';
import Tabs from 'src/common/components/Tabs';
import Loading from 'src/components/Loading';
import TableView, { EmptyWrapperType } from 'src/components/TableView';
@ -264,8 +264,27 @@ export const DataTablesPane = ({
onChange={handleCollapseChange}
bold
ghost
css={css`
height: 100%;
.ant-collapse-item {
height: 100%;
.ant-collapse-content {
height: 100%;
.ant-collapse-content-box {
height: 100%;
}
}
}
`}
>
<Collapse.Panel header={t('Data')} key={DATAPANEL_KEY}>
<Collapse.Panel
header={t('Data')}
key={DATAPANEL_KEY}
css={{ paddingBottom: 12 }}
>
<Tabs
fullWidth={false}
tabBarExtraContent={TableControls}

View File

@ -18,7 +18,7 @@
*/
import React, { useEffect, useState } from 'react';
import { styled, t } from '@superset-ui/core';
import { Collapse } from 'src/common/components';
import Collapse from 'src/common/components/Collapse';
import {
ColumnOption,
MetricOption,

View File

@ -18,9 +18,10 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Panel } from 'react-bootstrap';
import { css } from '@emotion/core';
import { t } from '@superset-ui/core';
import Label from 'src/components/Label';
import Collapse from 'src/common/components/Collapse';
import TextControl from './TextControl';
import MetricsControl from './MetricControl/MetricsControl';
import ControlHeader from '../ControlHeader';
@ -54,7 +55,6 @@ export default class FixedOrMetricControl extends React.Component {
this.setType = this.setType.bind(this);
this.setFixedValue = this.setFixedValue.bind(this);
this.setMetric = this.setMetric.bind(this);
this.toggle = this.toggle.bind(this);
const type =
(props.value ? props.value.type : props.default.type) ||
controlTypes.fixed;
@ -89,12 +89,6 @@ export default class FixedOrMetricControl extends React.Component {
this.setState({ metricValue }, this.onChange);
}
toggle() {
this.setState(prevState => ({
expanded: !prevState.expanded,
}));
}
render() {
const value = this.props.value || this.props.default;
const type = value.type || controlTypes.fixed;
@ -107,67 +101,88 @@ export default class FixedOrMetricControl extends React.Component {
return (
<div>
<ControlHeader {...this.props} />
<Label onClick={this.toggle}>
{this.state.type === controlTypes.fixed && (
<span>{this.state.fixedValue}</span>
)}
{this.state.type === controlTypes.metric && (
<span>
<span style={{ fontWeight: 'normal' }}>metric: </span>
<strong>
{this.state.metricValue ? this.state.metricValue.label : null}
</strong>
</span>
)}
</Label>
<Panel
className="panel-spreaded"
collapsible
expanded={this.state.expanded}
onToggle={this.toggle}
<Collapse
ghost
css={theme => css`
&.ant-collapse
> .ant-collapse-item.ant-collapse-no-arrow
> .ant-collapse-header {
border: 0px;
padding: 0px 0px ${theme.gridUnit * 2}px 0px;
display: inline-block;
}
&.ant-collapse-ghost
> .ant-collapse-item
> .ant-collapse-content
> .ant-collapse-content-box {
padding: 0px;
& .well {
margin-bottom: 0px;
padding: ${theme.gridUnit * 2}px;
}
}
`}
>
<Panel.Collapse>
<Panel.Body>
<div className="well">
<PopoverSection
title="Fixed"
isSelected={type === controlTypes.fixed}
onSelect={() => {
<Collapse.Panel
showArrow={false}
header={
<Label onClick={() => undefined}>
{this.state.type === controlTypes.fixed && (
<span>{this.state.fixedValue}</span>
)}
{this.state.type === controlTypes.metric && (
<span>
<span>metric: </span>
<strong>
{this.state.metricValue
? this.state.metricValue.label
: null}
</strong>
</span>
)}
</Label>
}
>
<div className="well">
<PopoverSection
title={t('Fixed')}
isSelected={type === controlTypes.fixed}
onSelect={() => {
this.setType(controlTypes.fixed);
}}
>
<TextControl
isFloat
onChange={this.setFixedValue}
onFocus={() => {
this.setType(controlTypes.fixed);
}}
>
<TextControl
isFloat
onChange={this.setFixedValue}
onFocus={() => {
this.setType(controlTypes.fixed);
}}
value={this.state.fixedValue}
/>
</PopoverSection>
<PopoverSection
title="Based on a metric"
isSelected={type === controlTypes.metric}
onSelect={() => {
value={this.state.fixedValue}
/>
</PopoverSection>
<PopoverSection
title={t('Based on a metric')}
isSelected={type === controlTypes.metric}
onSelect={() => {
this.setType(controlTypes.metric);
}}
>
<MetricsControl
name="metric"
columns={columns}
savedMetrics={metrics}
multi={false}
onFocus={() => {
this.setType(controlTypes.metric);
}}
>
<MetricsControl
name="metric"
columns={columns}
savedMetrics={metrics}
multi={false}
onFocus={() => {
this.setType(controlTypes.metric);
}}
onChange={this.setMetric}
value={this.state.metricValue}
/>
</PopoverSection>
</div>
</Panel.Body>
</Panel.Collapse>
</Panel>
onChange={this.setMetric}
value={this.state.metricValue}
/>
</PopoverSection>
</div>
</Collapse.Panel>
</Collapse>
</div>
);
}

View File

@ -233,15 +233,3 @@ h2.section-header {
margin-top: 0;
padding-bottom: 5px;
}
.panel-spreaded,
.panel-spreaded .panel-body {
padding: 0;
margin: 0;
}
.panel-spreaded .well {
margin-top: 8px;
margin-bottom: 0;
padding: 8px;
}

View File

@ -0,0 +1,50 @@
/**
* 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.
*/
export function hexToRgb(h: string) {
let r = '0';
let g = '0';
let b = '0';
// 3 digits
if (h.length === 4) {
r = `0x${h[1]}${h[1]}`;
g = `0x${h[2]}${h[2]}`;
b = `0x${h[3]}${h[3]}`;
// 6 digits
} else if (h.length === 7) {
r = `0x${h[1]}${h[2]}`;
g = `0x${h[3]}${h[4]}`;
b = `0x${h[5]}${h[6]}`;
}
return `rgb(${+r}, ${+g}, ${+b})`;
}
export function rgbToHex(red: number, green: number, blue: number) {
let r = red.toString(16);
let g = green.toString(16);
let b = blue.toString(16);
if (r.length === 1) r = `0${r}`;
if (g.length === 1) g = `0${g}`;
if (b.length === 1) b = `0${b}`;
return `#${r}${g}${b}`;
}

View File

@ -18,7 +18,7 @@
*/
import React, { useEffect, useState } from 'react';
import { styled, t } from '@superset-ui/core';
import { Collapse } from 'src/common/components';
import Collapse from 'src/common/components/Collapse';
import { User } from 'src/types/bootstrapTypes';
import { reject } from 'lodash';
import withToasts from 'src/messageToasts/enhancers/withToasts';