refactor: Bootstrap to AntD - ListGroup (#13996)
* refactor: Bootstrap to AntD - ListGroup
* Improves theme handling on touched files
diff --git a/superset-frontend/babel.config.js b/superset-frontend/babel.config.js
index 51ae332..5aa3420 100644
--- a/superset-frontend/babel.config.js
+++ b/superset-frontend/babel.config.js
@@ -63,6 +63,7 @@
targets: { node: 'current' },
},
],
+ ['@emotion/babel-preset-css-prop'],
],
plugins: ['babel-plugin-dynamic-import-node'],
},
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/annotations.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/annotations.test.ts
index ebbaddf..a771ea5 100644
--- a/superset-frontend/cypress-base/cypress/integration/explore/annotations.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/explore/annotations.test.ts
@@ -29,7 +29,7 @@
const layerLabel = 'Goal line';
- cy.get('[data-test=annotation_layers] button').click();
+ cy.get('[data-test=annotation_layers]').click();
cy.get('[data-test="popover-content"]').within(() => {
cy.get('[data-test=annotation-layer-name-header]')
diff --git a/superset-frontend/spec/javascripts/dashboard/components/Header_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/Header_spec.jsx
index da0467b..e5a7f52 100644
--- a/superset-frontend/spec/javascripts/dashboard/components/Header_spec.jsx
+++ b/superset-frontend/spec/javascripts/dashboard/components/Header_spec.jsx
@@ -17,7 +17,7 @@
* under the License.
*/
import React from 'react';
-import { shallow } from 'enzyme';
+import { styledMount as mount } from 'spec/helpers/theming';
import Header from 'src/dashboard/components/Header';
import EditableTitle from 'src/components/EditableTitle';
import FaveStar from 'src/components/FaveStar';
@@ -83,7 +83,7 @@
};
function setup(overrideProps) {
- const wrapper = shallow(<Header {...props} {...overrideProps} />);
+ const wrapper = mount(<Header {...props} {...overrideProps} />);
return wrapper;
}
diff --git a/superset-frontend/spec/javascripts/sqllab/ShareSqlLabQuery_spec.jsx b/superset-frontend/spec/javascripts/sqllab/ShareSqlLabQuery_spec.jsx
index cb32ab1..8a68d38 100644
--- a/superset-frontend/spec/javascripts/sqllab/ShareSqlLabQuery_spec.jsx
+++ b/superset-frontend/spec/javascripts/sqllab/ShareSqlLabQuery_spec.jsx
@@ -121,14 +121,12 @@
remoteId: undefined,
},
};
- await act(async () => {
- render(<ShareSqlLabQuery {...updatedProps} />, {
- wrapper: standardProvider,
- });
+
+ render(<ShareSqlLabQuery {...updatedProps} />, {
+ wrapper: standardProvider,
});
- const button = screen.getByRole('button', { name: /copy link/i });
- const style = window.getComputedStyle(button);
- expect(style.color).toBe('rgb(102, 102, 102)');
+ const button = await screen.findByRole('button', { name: /copy link/i });
+ expect(button).toBeDisabled();
});
});
});
diff --git a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery.tsx b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery.tsx
index 66c709c..10e3e43 100644
--- a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery.tsx
+++ b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery.tsx
@@ -17,9 +17,7 @@
* under the License.
*/
import React from 'react';
-import { Tooltip } from 'src/common/components/Tooltip';
-import { t, styled, supersetTheme } from '@superset-ui/core';
-import cx from 'classnames';
+import { t, useTheme } from '@superset-ui/core';
import Button from 'src/components/Button';
import withToasts from 'src/messageToasts/enhancers/withToasts';
@@ -41,24 +39,12 @@
addDangerToast: (msg: string) => void;
}
-const Styles = styled.div`
- .btn-disabled {
- &,
- &:hover {
- cursor: default;
- background-color: ${supersetTheme.colors.grayscale.light2};
- color: ${supersetTheme.colors.grayscale.base};
- }
- }
- svg {
- vertical-align: -${supersetTheme.gridUnit * 1.25}px;
- }
-`;
-
function ShareSqlLabQuery({
queryEditor,
addDangerToast,
}: ShareSqlLabQueryPropTypes) {
+ const theme = useTheme();
+
const getCopyUrlForKvStore = (callback: Function) => {
const { dbId, title, schema, autorun, sql } = queryEditor;
const sharedQuery = { dbId, title, schema, autorun, sql };
@@ -94,58 +80,41 @@
return getCopyUrlForSavedQuery(callback);
};
- const buildButton = () => {
- const canShare =
- queryEditor.remoteId ||
- isFeatureEnabled(FeatureFlag.SHARE_QUERIES_VIA_KV_STORE);
+ const buildButton = (canShare: boolean) => {
+ const tooltip = canShare
+ ? t('Copy query link to your clipboard')
+ : t('Save the query to enable this feature');
return (
- <Styles>
- <Button buttonSize="small" className={cx(!canShare && 'btn-disabled')}>
- <Icon
- name="link"
- color={
- canShare
- ? supersetTheme.colors.primary.base
- : supersetTheme.colors.grayscale.base
- }
- width={20}
- height={20}
- />{' '}
- {t('Copy link')}
- </Button>
- </Styles>
+ <Button buttonSize="small" tooltip={tooltip} disabled={!canShare}>
+ <Icon
+ name="link"
+ color={
+ canShare ? theme.colors.primary.base : theme.colors.grayscale.base
+ }
+ width={20}
+ height={20}
+ />{' '}
+ {t('Copy link')}
+ </Button>
);
};
const canShare =
- queryEditor.remoteId ||
+ !!queryEditor.remoteId ||
isFeatureEnabled(FeatureFlag.SHARE_QUERIES_VIA_KV_STORE);
return (
- <Tooltip
- id="copy_link"
- placement="top"
- overlayStyle={{
- fontSize: supersetTheme.typography.sizes.s,
- lineHeight: '1.6',
- maxWidth: '125px',
- }}
- title={
- canShare
- ? t('Copy query link to your clipboard')
- : t('Save the query to enable this feature')
- }
- >
+ <>
{canShare ? (
<CopyToClipboard
getText={getCopyUrl}
wrapped={false}
- copyNode={buildButton()}
+ copyNode={buildButton(canShare)}
/>
) : (
- buildButton()
+ buildButton(canShare)
)}
- </Tooltip>
+ </>
);
}
diff --git a/superset-frontend/src/common/components/index.tsx b/superset-frontend/src/common/components/index.tsx
index 4d52d3e..9c33611 100644
--- a/superset-frontend/src/common/components/index.tsx
+++ b/superset-frontend/src/common/components/index.tsx
@@ -57,6 +57,7 @@
export { TreeProps } from 'antd/lib/tree';
export { default as Alert, AlertProps } from 'antd/lib/alert';
export { default as Select, SelectProps } from 'antd/lib/select';
+export { default as List, ListItemProps } from 'antd/lib/list';
export { default as Collapse } from './Collapse';
export { default as Badge } from 'src/components/Badge';
diff --git a/superset-frontend/src/explore/components/controls/AnnotationLayerControl/index.jsx b/superset-frontend/src/explore/components/controls/AnnotationLayerControl/index.jsx
index eae832c..e5965ee 100644
--- a/superset-frontend/src/explore/components/controls/AnnotationLayerControl/index.jsx
+++ b/superset-frontend/src/explore/components/controls/AnnotationLayerControl/index.jsx
@@ -18,7 +18,7 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
-import { ListGroup, ListGroupItem } from 'react-bootstrap';
+import { List } from 'src/common/components';
import { connect } from 'react-redux';
import { t, withTheme } from '@superset-ui/core';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
@@ -26,6 +26,7 @@
import AsyncEsmComponent from 'src/components/AsyncEsmComponent';
import { getChartKey } from 'src/explore/exploreUtils';
import { runAnnotationQuery } from 'src/chart/chartAction';
+import CustomListItem from 'src/explore/components/controls/CustomListItem';
const AnnotationLayer = AsyncEsmComponent(
() => import('./AnnotationLayer'),
@@ -164,6 +165,12 @@
trigger="click"
placement="right"
title={t('Edit annotation layer')}
+ css={theme => ({
+ '&:hover': {
+ cursor: 'pointer',
+ backgroundColor: theme.colors.grayscale.light4,
+ },
+ })}
content={this.renderPopover(
i,
anno,
@@ -172,17 +179,17 @@
visible={this.state.popoverVisible[i]}
onVisibleChange={visible => this.handleVisibleChange(visible, i)}
>
- <ListGroupItem>
+ <CustomListItem selectable>
<span>{anno.name}</span>
<span style={{ float: 'right' }}>{this.renderInfo(anno)}</span>
- </ListGroupItem>
+ </CustomListItem>
</Popover>
));
const addLayerPopoverKey = 'add';
return (
<div>
- <ListGroup>
+ <List bordered css={theme => ({ borderRadius: theme.gridUnit })}>
{annotations}
<Popover
trigger="click"
@@ -195,15 +202,15 @@
this.handleVisibleChange(visible, addLayerPopoverKey)
}
>
- <ListGroupItem>
+ <CustomListItem selectable>
<i
data-test="add-annotation-layer-button"
className="fa fa-plus"
/>{' '}
{t('Add annotation layer')}
- </ListGroupItem>
+ </CustomListItem>
</Popover>
- </ListGroup>
+ </List>
</div>
);
}
diff --git a/superset-frontend/src/explore/components/controls/CollectionControl/CollectionControl.less b/superset-frontend/src/explore/components/controls/CollectionControl/CollectionControl.less
deleted file mode 100644
index 5d23e4e..0000000
--- a/superset-frontend/src/explore/components/controls/CollectionControl/CollectionControl.less
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * 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.
- */
-.CollectionControl .list-group-item i.fa {
- padding-top: 5px;
-}
diff --git a/superset-frontend/src/explore/components/controls/CollectionControl/CollectionControl.test.tsx b/superset-frontend/src/explore/components/controls/CollectionControl/CollectionControl.test.tsx
index f2c0490..53d5703 100644
--- a/superset-frontend/src/explore/components/controls/CollectionControl/CollectionControl.test.tsx
+++ b/superset-frontend/src/explore/components/controls/CollectionControl/CollectionControl.test.tsx
@@ -74,7 +74,7 @@
description: null,
hovered: false,
itemGenerator: jest.fn(),
- keyAccessor: jest.fn(),
+ keyAccessor: jest.fn(() => 'hrYAZ5iBH'),
label: 'Time series columns',
name: 'column_collection',
onChange: jest.fn(),
@@ -105,7 +105,7 @@
render(<CollectionControl {...props} />);
expect(props.onChange).toBeCalledTimes(0);
- userEvent.click(screen.getByRole('button', { name: 'add-item' }));
+ userEvent.click(screen.getByRole('button', { name: 'plus-large' }));
expect(props.onChange).toBeCalledWith([{ key: 'hrYAZ5iBH' }, undefined]);
});
@@ -121,7 +121,7 @@
test('Should have SortableDragger icon', () => {
const props = createProps();
render(<CollectionControl {...props} />);
- expect(screen.getByRole('img')).toBeVisible();
+ expect(screen.getByLabelText('drag')).toBeVisible();
});
test('Should call Control component', () => {
diff --git a/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx b/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx
index 7f49d71..c6e7128 100644
--- a/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx
+++ b/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx
@@ -18,19 +18,24 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
-import { ListGroup, ListGroupItem } from 'react-bootstrap';
+import { List } from 'src/common/components';
import shortid from 'shortid';
+import { t, withTheme } from '@superset-ui/core';
import {
SortableContainer,
SortableHandle,
SortableElement,
arrayMove,
} from 'react-sortable-hoc';
-
+import Icon from 'src/components/Icon';
+import {
+ HeaderContainer,
+ AddIconButton,
+} from 'src/explore/components/controls/OptionControls';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import ControlHeader from 'src/explore/components/ControlHeader';
+import CustomListItem from 'src/explore/components/controls/CustomListItem';
import controlMap from '..';
-import './CollectionControl.less';
const propTypes = {
name: PropTypes.string.isRequired,
@@ -51,23 +56,24 @@
label: null,
description: null,
onChange: () => {},
- placeholder: 'Empty collection',
+ placeholder: t('Empty collection'),
itemGenerator: () => ({ key: shortid.generate() }),
keyAccessor: o => o.key,
value: [],
- addTooltip: 'Add an item',
+ addTooltip: t('Add an item'),
};
-const SortableListGroupItem = SortableElement(ListGroupItem);
-const SortableListGroup = SortableContainer(ListGroup);
+const SortableListItem = SortableElement(CustomListItem);
+const SortableList = SortableContainer(List);
const SortableDragger = SortableHandle(() => (
<i
role="img"
+ aria-label="drag"
className="fa fa-bars text-primary"
style={{ cursor: 'ns-resize' }}
/>
));
-export default class CollectionControl extends React.Component {
+class CollectionControl extends React.Component {
constructor(props) {
super(props);
this.onAdd = this.onAdd.bind(this);
@@ -96,59 +102,69 @@
}
const Control = controlMap[this.props.controlName];
return (
- <SortableListGroup
+ <SortableList
useDragHandle
lockAxis="y"
onSortEnd={this.onSortEnd.bind(this)}
+ bordered
+ css={theme => ({
+ borderRadius: theme.gridUnit,
+ })}
>
{this.props.value.map((o, i) => {
// label relevant only for header, not here
const { label, ...commonProps } = this.props;
return (
- <SortableListGroupItem
+ <SortableListItem
className="clearfix"
+ css={{ justifyContent: 'flex-start' }}
key={this.props.keyAccessor(o)}
index={i}
>
- <div className="pull-left m-r-5">
- <SortableDragger />
- </div>
- <div className="pull-left">
+ <SortableDragger />
+ <div
+ css={theme => ({
+ flex: 1,
+ marginLeft: theme.gridUnit * 2,
+ marginRight: theme.gridUnit * 2,
+ })}
+ >
<Control
{...commonProps}
{...o}
onChange={this.onChange.bind(this, i)}
/>
</div>
- <div className="pull-right">
- <InfoTooltipWithTrigger
- icon="times"
- label="remove-item"
- tooltip="remove item"
- bsStyle="primary"
- onClick={this.removeItem.bind(this, i)}
- />
- </div>
- </SortableListGroupItem>
+ <InfoTooltipWithTrigger
+ icon="times"
+ label="remove-item"
+ tooltip={t('Remove item')}
+ bsStyle="primary"
+ onClick={this.removeItem.bind(this, i)}
+ />
+ </SortableListItem>
);
})}
- </SortableListGroup>
+ </SortableList>
);
}
render() {
+ const { theme } = this.props;
return (
<div data-test="CollectionControl" className="CollectionControl">
- <ControlHeader {...this.props} />
+ <HeaderContainer>
+ <ControlHeader {...this.props} />
+ <AddIconButton onClick={this.onAdd}>
+ <Icon
+ name="plus-large"
+ width={theme.gridUnit * 3}
+ height={theme.gridUnit * 3}
+ color={theme.colors.grayscale.light5}
+ />
+ </AddIconButton>
+ </HeaderContainer>
{this.renderList()}
- <InfoTooltipWithTrigger
- icon="plus-circle"
- label="add-item"
- tooltip={this.props.addTooltip}
- bsStyle="primary"
- className="fa-lg"
- onClick={this.onAdd}
- />
</div>
);
}
@@ -156,3 +172,5 @@
CollectionControl.propTypes = propTypes;
CollectionControl.defaultProps = defaultProps;
+
+export default withTheme(CollectionControl);
diff --git a/superset-frontend/src/explore/components/controls/CustomListItem/index.tsx b/superset-frontend/src/explore/components/controls/CustomListItem/index.tsx
new file mode 100644
index 0000000..e83552c
--- /dev/null
+++ b/superset-frontend/src/explore/components/controls/CustomListItem/index.tsx
@@ -0,0 +1,56 @@
+/**
+ * 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 { List, ListItemProps } from 'src/common/components';
+
+export interface CustomListItemProps extends ListItemProps {
+ selectable: boolean;
+}
+
+export default function CustomListItem(props: CustomListItemProps) {
+ const { selectable, children, ...rest } = props;
+ const theme = useTheme();
+ const css = {
+ '&.ant-list-item': {
+ padding: `${theme.gridUnit + 2}px ${theme.gridUnit * 3}px`,
+ ':first-of-type': {
+ borderTopLeftRadius: theme.gridUnit,
+ borderTopRightRadius: theme.gridUnit,
+ },
+ ':last-of-type': {
+ borderBottomLeftRadius: theme.gridUnit,
+ borderBottomRightRadius: theme.gridUnit,
+ },
+ },
+ };
+
+ if (selectable) {
+ css['&:hover'] = {
+ cursor: 'pointer',
+ backgroundColor: theme.colors.grayscale.light4,
+ };
+ }
+
+ return (
+ <List.Item {...rest} css={css}>
+ {children}
+ </List.Item>
+ );
+}