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 '../src/theme.ts';
import './storybook.css'; import './storybook.css';
const themeDecorator = storyFn => ( const themeDecorator = Story => (
<ThemeProvider theme={supersetTheme}>{storyFn()}</ThemeProvider> <ThemeProvider theme={supersetTheme}>{<Story />}</ThemeProvider>
); );
addDecorator(jsxDecorator); addDecorator(jsxDecorator);

View File

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

View File

@ -19,7 +19,8 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Button from 'src/components/Button'; 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 TableElement from './TableElement';
import TableSelector from '../../components/TableSelector'; import TableSelector from '../../components/TableSelector';
@ -145,13 +146,38 @@ export default class SqlEditorLeftBar extends React.PureComponent {
<div className="divider" /> <div className="divider" />
<StyledScrollbarContainer> <StyledScrollbarContainer>
<StyledScrollbarContent contentHeight={tableMetaDataHeight}> <StyledScrollbarContent contentHeight={tableMetaDataHeight}>
{this.props.tables.map(table => ( <Collapse
<TableElement ghost
table={table} expandIconPosition="right"
key={table.id} css={theme => css`
actions={this.props.actions} .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> </StyledScrollbarContent>
</StyledScrollbarContainer> </StyledScrollbarContainer>
{shouldShowReset && ( {shouldShowReset && (

View File

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

View File

@ -413,8 +413,6 @@ div.tablePopover {
} }
.TableElement { .TableElement {
margin-right: 10px;
.well { .well {
margin-top: 5px; margin-top: 5px;
margin-bottom: 5px; margin-bottom: 5px;
@ -428,18 +426,22 @@ div.tablePopover {
.header-container { .header-container {
display: flex; display: flex;
flex: 1;
align-items: center;
.table-name { .table-name {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
font-size: 16px; font-size: 16px;
flex: 1;
} }
.header-right-side { .header-right-side {
margin-left: auto; margin-left: auto;
display: flex; display: flex;
align-items: center; 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 React from 'react';
import { styled } from '@superset-ui/core'; import { styled } from '@superset-ui/core';
// eslint-disable-next-line no-restricted-imports
import { Collapse as AntdCollapse } from 'antd'; import { Collapse as AntdCollapse } from 'antd';
import { CollapseProps as AntdCollapseProps } from 'antd/lib/collapse'; import { CollapseProps as AntdCollapseProps } from 'antd/lib/collapse';
interface CollapseProps extends AntdCollapseProps { export interface CollapseProps extends AntdCollapseProps {
light?: boolean; light?: boolean;
bigger?: boolean; bigger?: boolean;
bold?: boolean; bold?: boolean;
@ -34,13 +33,7 @@ const Collapse = Object.assign(
styled(({ light, bigger, bold, animateArrows, ...props }: CollapseProps) => ( styled(({ light, bigger, bold, animateArrows, ...props }: CollapseProps) => (
<AntdCollapse {...props} /> <AntdCollapse {...props} />
))` ))`
height: 100%;
.ant-collapse-item { .ant-collapse-item {
border: 0;
height: 100%;
&:last-of-type.ant-collapse-item-active {
padding-bottom: ${({ theme }) => theme.gridUnit * 3}px;
}
.ant-collapse-header { .ant-collapse-header {
font-weight: ${({ bold, theme }) => font-weight: ${({ bold, theme }) =>
bold bold
@ -80,9 +73,7 @@ const Collapse = Object.assign(
`} `}
} }
.ant-collapse-content { .ant-collapse-content {
height: 100%;
.ant-collapse-content-box { .ant-collapse-content-box {
height: 100%;
.loading.inline { .loading.inline {
margin: ${({ theme }) => theme.gridUnit * 12}px auto; margin: ${({ theme }) => theme.gridUnit * 12}px auto;
display: block; display: block;

View File

@ -17,9 +17,9 @@
* under the License. * under the License.
*/ */
import { styled } from '@superset-ui/core'; 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 { .ant-radio-inner {
top: -1px; top: -1px;
left: 2px; left: 2px;
@ -51,7 +51,7 @@ const StyledRadio = styled(BaseRadio)`
} }
} }
`; `;
const StyledGroup = styled(BaseRadio.Group)` const StyledGroup = styled(AntdRadio.Group)`
font-size: inherit; font-size: inherit;
`; `;

View File

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

View File

@ -34,7 +34,6 @@ import {
} from './DatePicker'; } from './DatePicker';
import Badge from './Badge'; import Badge from './Badge';
import ProgressBar from './ProgressBar'; import ProgressBar from './ProgressBar';
import Collapse from './Collapse';
import { CronPicker, CronError } from './CronPicker'; import { CronPicker, CronError } from './CronPicker';
export default { export default {
@ -264,75 +263,6 @@ export const BadgeSuccess = () => <Badge status="success" text="Success" />;
export const BadgeError = () => <Badge status="error" text="Error" />; export const BadgeError = () => <Badge status="error" text="Error" />;
export const BadgeSmall = () => <Badge count={100} size="small" />; 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() { export function StyledCronPicker() {
// @ts-ignore // @ts-ignore
const inputRef = useRef<Input>(null); const inputRef = useRef<Input>(null);

View File

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

View File

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

View File

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

View File

@ -17,8 +17,8 @@
* under the License. * under the License.
*/ */
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { styled, t } from '@superset-ui/core'; import { styled, t, css } from '@superset-ui/core';
import { Collapse } from 'src/common/components'; import Collapse from 'src/common/components/Collapse';
import Tabs from 'src/common/components/Tabs'; import Tabs from 'src/common/components/Tabs';
import Loading from 'src/components/Loading'; import Loading from 'src/components/Loading';
import TableView, { EmptyWrapperType } from 'src/components/TableView'; import TableView, { EmptyWrapperType } from 'src/components/TableView';
@ -264,8 +264,27 @@ export const DataTablesPane = ({
onChange={handleCollapseChange} onChange={handleCollapseChange}
bold bold
ghost 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 <Tabs
fullWidth={false} fullWidth={false}
tabBarExtraContent={TableControls} tabBarExtraContent={TableControls}

View File

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

View File

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

View File

@ -233,15 +233,3 @@ h2.section-header {
margin-top: 0; margin-top: 0;
padding-bottom: 5px; 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 React, { useEffect, useState } from 'react';
import { styled, t } from '@superset-ui/core'; 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 { User } from 'src/types/bootstrapTypes';
import { reject } from 'lodash'; import { reject } from 'lodash';
import withToasts from 'src/messageToasts/enhancers/withToasts'; import withToasts from 'src/messageToasts/enhancers/withToasts';