Migrates Button component from Bootstrap to AntD (#12832)

This commit is contained in:
Michael S. Molina 2021-02-01 20:13:10 -03:00 committed by GitHub
parent 51195af4fa
commit c781ab8adf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 605 additions and 558 deletions

View File

@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import React from 'react'; import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme'; import { ReactWrapper } from 'enzyme';
import Button from 'src/components/Button'; import Button from 'src/components/Button';
import Select from 'src/components/Select'; import Select from 'src/components/Select';
import AddSliceContainer, { import AddSliceContainer, {
@ -25,6 +25,7 @@ import AddSliceContainer, {
AddSliceContainerState, AddSliceContainerState,
} from 'src/addSlice/AddSliceContainer'; } from 'src/addSlice/AddSliceContainer';
import VizTypeControl from 'src/explore/components/controls/VizTypeControl'; import VizTypeControl from 'src/explore/components/controls/VizTypeControl';
import { styledMount as mount } from 'spec/helpers/theming';
const defaultProps = { const defaultProps = {
datasources: [ datasources: [
@ -34,14 +35,18 @@ const defaultProps = {
}; };
describe('AddSliceContainer', () => { describe('AddSliceContainer', () => {
let wrapper: ShallowWrapper< let wrapper: ReactWrapper<
AddSliceContainerProps, AddSliceContainerProps,
AddSliceContainerState, AddSliceContainerState,
AddSliceContainer AddSliceContainer
>; >;
beforeEach(() => { beforeEach(() => {
wrapper = shallow(<AddSliceContainer {...defaultProps} />); wrapper = mount(<AddSliceContainer {...defaultProps} />) as ReactWrapper<
AddSliceContainerProps,
AddSliceContainerState,
AddSliceContainer
>;
}); });
it('uses table as default visType', () => { it('uses table as default visType', () => {
@ -58,9 +63,9 @@ describe('AddSliceContainer', () => {
}); });
it('renders a disabled button if no datasource is selected', () => { it('renders a disabled button if no datasource is selected', () => {
expect(wrapper.find(Button).dive().find({ disabled: true })).toHaveLength( expect(
1, wrapper.find(Button).find({ disabled: true }).hostNodes(),
); ).toHaveLength(1);
}); });
it('renders an enabled button if datasource is selected', () => { it('renders an enabled button if datasource is selected', () => {
@ -70,9 +75,9 @@ describe('AddSliceContainer', () => {
datasourceId: datasourceValue.split('__')[0], datasourceId: datasourceValue.split('__')[0],
datasourceType: datasourceValue.split('__')[1], datasourceType: datasourceValue.split('__')[1],
}); });
expect(wrapper.find(Button).dive().find({ disabled: true })).toHaveLength( expect(
0, wrapper.find(Button).find({ disabled: true }).hostNodes(),
); ).toHaveLength(0);
}); });
it('formats explore url', () => { it('formats explore url', () => {

View File

@ -20,6 +20,7 @@ import React from 'react';
import { styledMount as mount } from 'spec/helpers/theming'; import { styledMount as mount } from 'spec/helpers/theming';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import FilterBar from 'src/dashboard/components/nativeFilters/FilterBar'; import FilterBar from 'src/dashboard/components/nativeFilters/FilterBar';
import Button from 'src/components/Button';
import { mockStore } from 'spec/fixtures/mockStore'; import { mockStore } from 'spec/fixtures/mockStore';
describe('FilterBar', () => { describe('FilterBar', () => {
@ -42,7 +43,8 @@ describe('FilterBar', () => {
expect(wrapper.find({ name: 'collapse' })).toExist(); expect(wrapper.find({ name: 'collapse' })).toExist();
}); });
it('has apply and reset all buttons', () => { it('has apply and reset all buttons', () => {
expect(wrapper.find('.btn-primary')).toExist(); expect(wrapper.find(Button).length).toBe(2);
expect(wrapper.find('.btn-secondary')).toExist(); expect(wrapper.find(Button).at(0)).toHaveProp('buttonStyle', 'secondary');
expect(wrapper.find(Button).at(1)).toHaveProp('buttonStyle', 'primary');
}); });
}); });

View File

@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { styledMount as mount } from 'spec/helpers/theming';
import sinon from 'sinon'; import sinon from 'sinon';
import QueryAndSaveButtons from 'src/explore/components/QueryAndSaveBtns'; import QueryAndSaveButtons from 'src/explore/components/QueryAndSaveBtns';
@ -38,23 +38,21 @@ describe('QueryAndSaveButtons', () => {
// Test the output // Test the output
describe('output', () => { describe('output', () => {
let wrapper; const wrapper = mount(<QueryAndSaveButtons {...defaultProps} />);
beforeEach(() => {
wrapper = shallow(<QueryAndSaveButtons {...defaultProps} />);
});
it('renders 2 buttons', () => { it('renders 2 buttons', () => {
expect(wrapper.find(Button)).toHaveLength(2); expect(wrapper.find(Button)).toHaveLength(2);
}); });
it('renders buttons with correct text', () => { it('renders buttons with correct text', () => {
expect(wrapper.find(Button).contains('Run')).toBe(true); expect(wrapper.find(Button).at(0).text().trim()).toBe('Run');
expect(wrapper.find(Button).contains('Save')).toBe(true); expect(wrapper.find(Button).at(1).text().trim()).toBe('Save');
}); });
it('calls onQuery when query button is clicked', () => { it('calls onQuery when query button is clicked', () => {
const queryButton = wrapper.find('[data-test="run-query-button"]'); const queryButton = wrapper
.find('[data-test="run-query-button"]')
.hostNodes();
queryButton.simulate('click'); queryButton.simulate('click');
expect(defaultProps.onQuery.called).toBe(true); expect(defaultProps.onQuery.called).toBe(true);
}); });

View File

@ -308,7 +308,7 @@ export default class CRUDCollection extends React.PureComponent<
{this.props.allowAddItem && ( {this.props.allowAddItem && (
<span className="m-t-10 m-r-10"> <span className="m-t-10 m-r-10">
<Button <Button
buttonSize="sm" buttonSize="small"
buttonStyle="primary" buttonStyle="primary"
onClick={this.onAddItem} onClick={this.onAddItem}
data-test="add-item-button" data-test="add-item-button"

View File

@ -17,7 +17,8 @@
* under the License. * under the License.
*/ */
import React, { CSSProperties } from 'react'; import React, { CSSProperties } from 'react';
import { Alert, ButtonGroup } from 'react-bootstrap'; import { Alert } from 'react-bootstrap';
import ButtonGroup from 'src/components/ButtonGroup';
import ProgressBar from 'src/common/components/ProgressBar'; import ProgressBar from 'src/common/components/ProgressBar';
import moment from 'moment'; import moment from 'moment';
import { RadioChangeEvent } from 'antd/lib/radio'; import { RadioChangeEvent } from 'antd/lib/radio';
@ -580,7 +581,7 @@ export default class ResultSet extends React.PureComponent<
if (query.isDataPreview) { if (query.isDataPreview) {
return ( return (
<Button <Button
buttonSize="sm" buttonSize="small"
className="fetch" className="fetch"
buttonStyle="primary" buttonStyle="primary"
onClick={() => onClick={() =>
@ -597,7 +598,7 @@ export default class ResultSet extends React.PureComponent<
if (query.resultsKey) { if (query.resultsKey) {
return ( return (
<Button <Button
buttonSize="sm" buttonSize="small"
className="fetch" className="fetch"
buttonStyle="primary" buttonStyle="primary"
onClick={() => this.fetchResults(query)} onClick={() => this.fetchResults(query)}

View File

@ -101,7 +101,6 @@ export const SaveDatasetModal: FunctionComponent<SaveDatasetModalProps> = ({
{!shouldOverwriteDataset && ( {!shouldOverwriteDataset && (
<Button <Button
disabled={disableSaveAndExploreBtn} disabled={disableSaveAndExploreBtn}
buttonSize="medium"
buttonStyle="primary" buttonStyle="primary"
onClick={onOk} onClick={onOk}
> >
@ -110,12 +109,9 @@ export const SaveDatasetModal: FunctionComponent<SaveDatasetModalProps> = ({
)} )}
{shouldOverwriteDataset && ( {shouldOverwriteDataset && (
<> <>
<Button buttonSize="medium" onClick={handleOverwriteCancel}> <Button onClick={handleOverwriteCancel}>Back</Button>
Back
</Button>
<Button <Button
className="md" className="md"
buttonSize="medium"
buttonStyle="primary" buttonStyle="primary"
onClick={handleOverwriteDataset} onClick={handleOverwriteDataset}
disabled={disableSaveAndExploreBtn} disabled={disableSaveAndExploreBtn}

View File

@ -18,7 +18,8 @@
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ButtonGroup, Collapse, Well } from 'react-bootstrap'; import { Collapse, Well } from 'react-bootstrap';
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';

View File

@ -1,147 +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.
*/
import React from 'react';
import Button from './index';
export default {
title: 'Button',
component: Button,
includeStories: ['ButtonGallery', 'InteractiveButton'],
};
export const STYLES = {
label: 'Types',
options: {
Primary: 'primary',
Secondary: 'secondary',
Tertiary: 'tertiary',
Dashed: 'dashed',
Danger: 'danger',
Warning: 'warning',
Success: 'success',
Link: 'link',
Default: 'default',
None: null,
},
defaultValue: null,
// groupId: 'ButtonType',
};
export const SIZES = {
label: 'Sizes',
options: {
XS: 'xsmall',
S: 'small',
Default: null,
L: 'large',
},
defaultValue: null,
};
// TODO remove the use of these class names in the codebase where they're not necessary
// 'fetch' // haven't yet seen this (in ResultSet.tsx) actually show up to verify the styles are needed
// 'm-r-3' // open a PR with a prop of `pullRight` that adds an automatic right-margin for second and subseqent sibling buttons.
const TYPES = {
label: 'Type',
options: {
Submit: 'submit',
Button: 'button',
None: null,
},
defaultValue: null,
};
const TARGETS = {
label: 'Target',
options: {
Blank: '_blank',
None: null,
},
defaultValue: null,
};
const HREFS = {
label: 'HREF',
options: {
Superset: 'http://https://superset.apache.org/',
None: null,
},
defaultValue: null,
};
export const ButtonGallery = () => (
<>
{Object.entries(SIZES.options).map(([name, size]) => (
<div key={size}>
<h4>{name}</h4>
{Object.values(STYLES.options)
.filter(o => o)
.map(style => (
<Button
buttonStyle={style}
buttonSize={size}
onClick={() => true}
key={`${style}_${size}`}
>
{style}
</Button>
))}
</div>
))}
</>
);
export const InteractiveButton = args => {
const { label, ...btnArgs } = args;
return <Button {...btnArgs}>{label}</Button>;
};
InteractiveButton.args = {
buttonStyle: STYLES.defaultValue,
buttonSize: SIZES.defaultValue,
type: TYPES.defaultValue,
target: TARGETS.defaultValue,
href: HREFS.defaultValue,
label: 'Button!',
};
InteractiveButton.argTypes = {
buttonStyle: {
name: STYLES.label,
control: { type: 'select', options: Object.values(STYLES.options) },
},
size: {
name: SIZES.label,
control: { type: 'select', options: Object.values(SIZES.options) },
},
type: {
name: TYPES.label,
control: { type: 'select', options: Object.values(TYPES.options) },
},
target: {
name: TARGETS.label,
control: { type: 'select', options: Object.values(TARGETS.options) },
},
href: {
name: HREFS.label,
control: { type: 'select', options: Object.values(HREFS.options) },
},
onClick: { action: 'clicked' },
label: { name: 'Label', control: { type: 'text' } },
};
ButtonGallery.argTypes = { onClick: { action: 'clicked' } };

View File

@ -0,0 +1,141 @@
/**
* 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 Button, { ButtonProps } from './index';
type ButtonStyle = Pick<ButtonProps, 'buttonStyle'>;
type ButtonStyleValue = ButtonStyle[keyof ButtonStyle];
type ButtonSize = Pick<ButtonProps, 'buttonSize'>;
type ButtonSizeValue = ButtonSize[keyof ButtonSize];
export default {
title: 'Button',
component: Button,
includeStories: ['ButtonGallery', 'InteractiveButton'],
};
const buttonStyles: ButtonStyleValue[] = [
'primary',
'secondary',
'tertiary',
'dashed',
'danger',
'warning',
'success',
'link',
'default',
];
const buttonSizes: ButtonSizeValue[] = ['xsmall', 'small', 'default'];
export const STYLES = {
label: 'styles',
options: buttonStyles,
defaultValue: undefined,
};
export const SIZES = {
label: 'sizes',
options: buttonSizes,
defaultValue: undefined,
};
const TARGETS = {
label: 'target',
options: {
blank: '_blank',
none: null,
},
defaultValue: null,
};
const HREFS = {
label: 'href',
options: {
superset: 'https://superset.apache.org/',
none: null,
},
defaultValue: null,
};
export const ButtonGallery = () => (
<>
{SIZES.options.map(size => (
<div key={size} style={{ marginBottom: 40 }}>
<h4>{size}</h4>
{Object.values(STYLES.options).map(style => (
<Button
buttonStyle={style}
buttonSize={size}
onClick={() => true}
key={`${style}_${size}`}
style={{ marginRight: 20, marginBottom: 10 }}
>
{style}
</Button>
))}
</div>
))}
</>
);
ButtonGallery.story = {
parameters: {
actions: {
disabled: true,
},
controls: {
disabled: true,
},
knobs: {
disabled: true,
},
},
};
export const InteractiveButton = (args: ButtonProps & { label: string }) => {
const { label, ...btnArgs } = args;
return <Button {...btnArgs}>{label}</Button>;
};
InteractiveButton.story = {
parameters: {
knobs: {
disabled: true,
},
},
};
InteractiveButton.args = {
buttonStyle: 'default',
buttonSize: 'default',
label: 'Button!',
};
InteractiveButton.argTypes = {
target: {
name: TARGETS.label,
control: { type: 'select', options: Object.values(TARGETS.options) },
},
href: {
name: HREFS.label,
control: { type: 'select', options: Object.values(HREFS.options) },
},
onClick: { action: 'clicked' },
};

View File

@ -59,10 +59,4 @@ describe('Button', () => {
expect(wrapper.find(Button).length).toEqual(permutationCount); expect(wrapper.find(Button).length).toEqual(permutationCount);
}); });
// test things NOT in the storybook!
it('renders custom button styles without melting', () => {
wrapper = mount(<Button buttonStyle="foobar" />);
expect(wrapper.find('Button.btn-foobar')).toHaveLength(1);
});
}); });

View File

@ -20,18 +20,11 @@ import React, { CSSProperties } 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 BootstrapButton } from 'react-bootstrap'; import { Button as AntdButton } from 'src/common/components';
import { styled } from '@superset-ui/core'; import { useTheme } from '@superset-ui/core';
import { Tooltip } from 'src/common/components/Tooltip'; import { Tooltip } from 'src/common/components/Tooltip';
import { Menu } from 'src/common/components';
export type OnClickHandler = React.MouseEventHandler<BootstrapButton>; export type OnClickHandler = React.MouseEventHandler<HTMLElement>;
export interface DropdownItemProps {
label: string;
url: string;
icon?: string;
}
export interface ButtonProps { export interface ButtonProps {
id?: string; id?: string;
@ -52,264 +45,164 @@ export interface ButtonProps {
| 'rightBottom'; | 'rightBottom';
onClick?: OnClickHandler; onClick?: OnClickHandler;
disabled?: boolean; disabled?: boolean;
buttonStyle?: string; buttonStyle?:
btnStyles?: string; | 'primary'
buttonSize?: BootstrapButton.ButtonProps['bsSize']; | 'secondary'
style?: BootstrapButton.ButtonProps['style']; | 'tertiary'
| 'success'
| 'warning'
| 'danger'
| 'default'
| 'link'
| 'dashed';
buttonSize?: 'default' | 'small' | 'xsmall';
style?: CSSProperties;
children?: React.ReactNode; children?: React.ReactNode;
dropdownItems?: DropdownItemProps[]; href?: string;
href?: string; // React-Bootstrap creates a link when this is passed in. htmlType?: 'button' | 'submit' | 'reset';
target?: string; // React-Bootstrap creates a link when this is passed in.
type?: string; // React-Bootstrap supports this when rendering an HTML button element
cta?: boolean; cta?: boolean;
} }
const BUTTON_WRAPPER_STYLE = { display: 'inline-block', cursor: 'not-allowed' }; export default function Button(props: ButtonProps) {
const {
tooltip,
placement,
disabled = false,
buttonSize,
buttonStyle,
className,
cta,
children,
href,
...restProps
} = props;
const SupersetButton = styled(BootstrapButton)` const theme = useTheme();
&:focus, const { colors, transitionTiming, borderRadius, typography } = theme;
&:active, const { primary, grayscale, success, warning, error } = colors;
&:focus:active {
outline: none; let height = 32;
box-shadow: none; let padding = 18;
} if (buttonSize === 'xsmall') {
transition: all ${({ theme }) => theme.transitionTiming}s; height = 22;
border-radius: ${({ theme }) => theme.borderRadius}px; padding = 5;
border: none; } else if (buttonSize === 'small') {
font-size: ${({ theme }) => theme.typography.sizes.s}px; height = 30;
font-weight: ${({ theme }) => theme.typography.weights.bold}; padding = 10;
margin-left: ${({ theme }) => theme.gridUnit * 4}px;
&:first-of-type {
margin-left: 0;
} }
i { let backgroundColor = primary.light4;
padding: 0 ${({ theme }) => theme.gridUnit * 2}px 0 0; let backgroundColorHover = mix(0.1, primary.base, primary.light4);
let backgroundColorActive = mix(0.25, primary.base, primary.light4);
let backgroundColorDisabled = grayscale.light2;
let color = primary.dark1;
let colorHover = color;
let borderWidth = 0;
let borderStyle = 'none';
let borderColor = 'transparent';
let borderColorHover = 'transparent';
let borderColorDisabled = 'transparent';
if (buttonStyle === 'primary') {
backgroundColor = primary.dark1;
backgroundColorHover = mix(0.1, grayscale.light5, primary.dark1);
backgroundColorActive = mix(0.2, grayscale.dark2, primary.dark1);
color = grayscale.light5;
colorHover = color;
} else if (buttonStyle === 'tertiary' || buttonStyle === 'dashed') {
backgroundColor = grayscale.light5;
backgroundColorHover = grayscale.light5;
backgroundColorActive = grayscale.light5;
backgroundColorDisabled = grayscale.light5;
borderWidth = 1;
borderStyle = buttonStyle === 'dashed' ? 'dashed' : 'solid';
borderColor = primary.dark1;
borderColorHover = primary.light1;
borderColorDisabled = grayscale.light2;
} else if (buttonStyle === 'danger') {
backgroundColor = error.base;
backgroundColorHover = mix(0.1, grayscale.light5, error.base);
backgroundColorActive = mix(0.2, grayscale.dark2, error.base);
color = grayscale.light5;
colorHover = color;
} else if (buttonStyle === 'warning') {
backgroundColor = warning.base;
backgroundColorHover = mix(0.1, grayscale.dark2, warning.base);
backgroundColorActive = mix(0.2, grayscale.dark2, warning.base);
color = grayscale.light5;
colorHover = color;
} else if (buttonStyle === 'success') {
backgroundColor = success.base;
backgroundColorHover = mix(0.1, grayscale.light5, success.base);
backgroundColorActive = mix(0.2, grayscale.dark2, success.base);
color = grayscale.light5;
colorHover = color;
} else if (buttonStyle === 'link') {
backgroundColor = 'transparent';
backgroundColorHover = 'transparent';
backgroundColorActive = 'transparent';
colorHover = primary.base;
} }
/* SIP 34 colors! */ const button = (
&.btn { <AntdButton
border: 1px solid transparent; /* this just makes sure the height is the same as tertiary/dashed buttons */ href={disabled ? undefined : href}
&:hover, disabled={disabled}
&:active { className={cx(className, { cta: !!cta })}
border: 1px solid transparent; css={{
} display: 'inline-flex',
&-default, alignItems: 'center',
&-secondary { justifyContent: 'center',
background-color: ${({ theme }) => theme.colors.primary.light4}; lineHeight: 1.5715,
color: ${({ theme }) => theme.colors.primary.dark1}; fontSize: typography.sizes.s,
&:hover { fontWeight: typography.weights.bold,
background-color: ${({ theme }) => height,
mix(0.1, theme.colors.grayscale.light5, theme.colors.primary.light4)}; textTransform: 'uppercase',
color: ${({ theme }) => theme.colors.primary.dark1}; padding: `0px ${padding}px`,
} transition: `all ${transitionTiming}s`,
&:active { minWidth: cta ? theme.gridUnit * 36 : undefined,
background-color: ${({ theme }) => minHeight: cta ? theme.gridUnit * 8 : undefined,
mix(0.25, theme.colors.primary.base, theme.colors.primary.light4)}; boxShadow: 'none',
color: ${({ theme }) => theme.colors.primary.dark1}; borderWidth,
} borderStyle,
} borderColor,
&-tertiary, borderRadius,
&-dashed { color,
border-width: 1px; backgroundColor,
border-style: solid; '&:hover': {
background-color: ${({ theme }) => theme.colors.grayscale.light5}; color: colorHover,
color: ${({ theme }) => theme.colors.primary.dark1}; backgroundColor: backgroundColorHover,
border-color: ${({ theme }) => theme.colors.primary.dark1}; borderColor: borderColorHover,
&:hover { },
background-color: ${({ theme }) => theme.colors.grayscale.light5}; '&:active': {
color: ${({ theme }) => theme.colors.primary.dark1}; color,
border-color: ${({ theme }) => theme.colors.primary.light1}; backgroundColor: backgroundColorActive,
} },
&:active { '&:focus': {
background-color: ${({ theme }) => theme.colors.grayscale.light5}; color,
color: ${({ theme }) => theme.colors.primary.dark1}; backgroundColor,
border-color: ${({ theme }) => theme.colors.primary.dark1}; borderColor,
} },
&[disabled], '&[disabled], &[disabled]:hover': {
&[disabled]:hover { color: grayscale.base,
background-color: ${({ theme }) => theme.colors.grayscale.light5}; backgroundColor: backgroundColorDisabled,
color: ${({ theme }) => theme.colors.grayscale.base}; borderColor: borderColorDisabled,
border-color: ${({ theme }) => theme.colors.grayscale.light2}; },
} 'i:first-of-type, svg:first-of-type': {
} marginRight: theme.gridUnit * 2,
&-dashed { padding: `0 ${theme.gridUnit * 2} 0 0`,
border-style: dashed; },
&:hover, marginLeft: theme.gridUnit * 2,
&:active { '&:first-of-type': {
border-style: dashed; marginLeft: 0,
} },
} }}
&-link { {...restProps}
background: none; >
text-decoration: none; {children}
color: ${({ theme }) => theme.colors.primary.dark1}; </AntdButton>
&:hover {
background: none;
color: ${({ theme }) => theme.colors.primary.base};
}
&:active {
background: none;
color: ${({ theme }) => theme.colors.primary.dark1};
}
&[disabled],
&[disabled]:hover {
background: none;
color: ${({ theme }) => theme.colors.grayscale.base};
}
}
&-primary {
background-color: ${({ theme }) => theme.colors.primary.dark1};
color: ${({ theme }) => theme.colors.grayscale.light5};
&:hover {
background-color: ${({ theme }) =>
mix(0.1, theme.colors.grayscale.light5, theme.colors.primary.dark1)};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
&:active {
background-color: ${({ theme }) =>
mix(0.2, theme.colors.grayscale.dark2, theme.colors.primary.dark1)};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
}
&-danger {
background-color: ${({ theme }) => theme.colors.error.base};
color: ${({ theme }) => theme.colors.grayscale.light5};
&:hover {
background-color: ${({ theme }) =>
mix(0.1, theme.colors.grayscale.light5, theme.colors.error.base)};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
&:active {
background-color: ${({ theme }) =>
mix(0.2, theme.colors.grayscale.dark2, theme.colors.error.base)};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
}
&-success {
background-color: ${({ theme }) => theme.colors.success.base};
color: ${({ theme }) => theme.colors.grayscale.light5};
&:hover {
background-color: ${({ theme }) =>
mix(0.1, theme.colors.grayscale.light5, theme.colors.success.base)};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
&:active {
background-color: ${({ theme }) =>
mix(0.2, theme.colors.grayscale.dark2, theme.colors.success.base)};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
}
&-warning {
background-color: ${({ theme }) => theme.colors.warning.base};
color: ${({ theme }) => theme.colors.grayscale.light5};
&:hover {
background-color: ${({ theme }) =>
mix(0.1, theme.colors.grayscale.light5, theme.colors.warning.base)};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
&:active {
background-color: ${({ theme }) =>
mix(0.2, theme.colors.grayscale.dark2, theme.colors.warning.base)};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
}
&-info {
background-color: ${({ theme }) => theme.colors.info.dark1};
color: ${({ theme }) => theme.colors.grayscale.light5};
&:hover {
background-color: ${({ theme }) =>
mix(0.1, theme.colors.grayscale.light5, theme.colors.info.dark1)};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
&:active {
background-color: ${({ theme }) =>
mix(0.2, theme.colors.grayscale.dark2, theme.colors.info.dark1)};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
}
&[disabled],
&[disabled]:hover {
background-color: ${({ theme }) => theme.colors.grayscale.light2};
color: ${({ theme }) => theme.colors.grayscale.base};
}
}
/* big Call to Action buttons */
&.cta {
min-width: ${({ theme }) => theme.gridUnit * 36}px;
min-height: ${({ theme }) => theme.gridUnit * 8}px;
text-transform: uppercase;
}
`;
export default function Button({
tooltip,
placement,
dropdownItems,
disabled = false,
buttonSize: bsSize,
buttonStyle: bsStyle,
className,
style: style_,
cta,
children,
...restProps
}: ButtonProps) {
// Working around the fact that tooltips don't get triggered when buttons are disabled
// https://github.com/react-bootstrap/react-bootstrap/issues/1588
const style: CSSProperties | undefined =
tooltip && disabled ? { ...style_, pointerEvents: 'none' } : style_;
const officialBootstrapStyles = [
'success',
'warning',
'danger',
'info',
'default',
'primary',
];
const transformedProps = {
...restProps,
disabled,
bsSize,
bsStyle: officialBootstrapStyles.includes(bsStyle || '')
? bsStyle
: 'default',
className: cx(className, {
cta: !!cta,
[`btn-${bsStyle}`]: !officialBootstrapStyles.includes(bsStyle || ''),
}),
style,
};
let button = (
<SupersetButton {...transformedProps}>{children}</SupersetButton>
); );
if (dropdownItems) {
button = (
<div style={BUTTON_WRAPPER_STYLE}>
<SupersetButton {...transformedProps} data-toggle="dropdown">
{children}
</SupersetButton>
<ul className="dropdown-menu">
<Menu>
{dropdownItems.map((dropdownItem: DropdownItemProps) => (
<Menu.Item key={`${dropdownItem.label}`}>
<a href={dropdownItem.url}>
<i className={`fa ${dropdownItem.icon}`} />
&nbsp; {dropdownItem.label}
</a>
</Menu.Item>
))}
</Menu>
</ul>
</div>
);
}
if (tooltip) { if (tooltip) {
return ( return (
<Tooltip <Tooltip

View File

@ -0,0 +1,70 @@
/**
* 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 Button, { ButtonProps } from 'src/components/Button';
import { STYLES, SIZES } from 'src/components/Button/Button.stories';
import ButtonGroup from './index';
export default {
title: 'ButtonGroup',
component: ButtonGroup,
};
export const InteractiveButtonGroup = (args: ButtonProps) => (
<>
<ButtonGroup css={{ marginBottom: 40 }}>
<Button {...args}>Button 1</Button>
</ButtonGroup>
<ButtonGroup css={{ marginBottom: 40 }}>
<Button {...args}>Button 1</Button>
<Button {...args}>Button 2</Button>
</ButtonGroup>
<ButtonGroup>
<Button {...args}>Button 1</Button>
<Button {...args}>Button 2</Button>
<Button {...args}>Button 3</Button>
</ButtonGroup>
</>
);
InteractiveButtonGroup.args = {
buttonStyle: 'tertiary',
buttonSize: 'default',
};
InteractiveButtonGroup.argTypes = {
buttonStyle: {
name: STYLES.label,
control: { type: 'select', options: STYLES.options },
},
buttonSize: {
name: SIZES.label,
control: { type: 'select', options: SIZES.options },
},
};
InteractiveButtonGroup.story = {
parameters: {
actions: {
disabled: true,
},
knobs: {
disabled: true,
},
},
};

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.
*/
import React from 'react';
import { ReactWrapper } from 'enzyme';
import { styledMount as mount } from 'spec/helpers/theming';
import Button from 'src/components/Button';
import ButtonGroup from '.';
describe('ButtonGroup', () => {
let wrapper: ReactWrapper;
it('renders 1 button', () => {
expect(
React.isValidElement(
<ButtonGroup>
<Button>Button</Button>
</ButtonGroup>,
),
).toBe(true);
});
it('renders 3 buttons', () => {
wrapper = mount(
<ButtonGroup>
<Button>Button</Button>
<Button>Button</Button>
<Button>Button</Button>
</ButtonGroup>,
);
expect(wrapper.find(Button).length).toEqual(3);
});
});

View File

@ -0,0 +1,53 @@
/**
* 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';
export interface ButtonGroupProps {
className?: string;
children?: React.ReactNode;
}
export default function ButtonGroup(props: ButtonGroupProps) {
const { className, children } = props;
return (
<div
className={className}
css={{
'& :nth-child(1):not(:nth-last-child(1))': {
borderTopRightRadius: 0,
borderBottomRightRadius: 0,
borderRight: 0,
marginLeft: 0,
},
'& :not(:nth-child(1)):not(:nth-last-child(1))': {
borderRadius: 0,
borderRight: 0,
marginLeft: 0,
},
'& :nth-last-child(1):not(:nth-child(1))': {
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
marginLeft: 0,
},
}}
>
{children}
</div>
);
}

View File

@ -21,7 +21,7 @@ import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store'; import configureStore from 'redux-mock-store';
import { styledMount as mount } from 'spec/helpers/theming'; import { styledMount as mount } from 'spec/helpers/theming';
import { ReactWrapper } from 'enzyme'; import { ReactWrapper } from 'enzyme';
import Button from 'src/components/Button';
import { ImportResourceName } from 'src/views/CRUD/types'; import { ImportResourceName } from 'src/views/CRUD/types';
import ImportModelsModal from 'src/components/ImportModal'; import ImportModelsModal from 'src/components/ImportModal';
import Modal from 'src/common/components/Modal'; import Modal from 'src/common/components/Modal';
@ -82,18 +82,13 @@ describe('ImportModelsModal', () => {
}); });
it('should render the import button initially disabled', () => { it('should render the import button initially disabled', () => {
expect(wrapper.find('button[children="Import"]').prop('disabled')).toBe( expect(wrapper.find(Button).at(1).prop('disabled')).toBe(true);
true,
);
}); });
it('should render the import button enabled when a file is selected', () => { it('should render the import button enabled when a file is selected', () => {
const file = new File([new ArrayBuffer(1)], 'model_export.zip'); const file = new File([new ArrayBuffer(1)], 'model_export.zip');
wrapper.find('input').simulate('change', { target: { files: [file] } }); wrapper.find('input').simulate('change', { target: { files: [file] } });
expect(wrapper.find(Button).at(1).prop('disabled')).toBe(false);
expect(wrapper.find('button[children="Import"]').prop('disabled')).toBe(
false,
);
}); });
it('should render password fields when needed for import', () => { it('should render password fields when needed for import', () => {

View File

@ -21,7 +21,7 @@ import moment from 'moment';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { styled, CategoricalColorNamespace, t } from '@superset-ui/core'; import { styled, CategoricalColorNamespace, t } from '@superset-ui/core';
import { ButtonGroup } from 'react-bootstrap'; import ButtonGroup from 'src/components/ButtonGroup';
import { import {
LOG_ACTIONS_PERIODIC_RENDER_DASHBOARD, LOG_ACTIONS_PERIODIC_RENDER_DASHBOARD,

View File

@ -296,8 +296,8 @@ class PropertiesModal extends React.PureComponent {
footer={ footer={
<> <>
<Button <Button
type="button" htmlType="button"
buttonSize="sm" buttonSize="small"
onClick={onHide} onClick={onHide}
data-test="properties-modal-cancel-button" data-test="properties-modal-cancel-button"
cta cta
@ -306,7 +306,7 @@ class PropertiesModal extends React.PureComponent {
</Button> </Button>
<Button <Button
onClick={this.submit} onClick={this.submit}
buttonSize="sm" buttonSize="small"
buttonStyle="primary" buttonStyle="primary"
className="m-r-5" className="m-r-5"
disabled={errors.length > 0} disabled={errors.length > 0}

View File

@ -135,10 +135,14 @@ class RefreshIntervalModal extends React.PureComponent<
} }
modalFooter={ modalFooter={
<> <>
<Button buttonStyle="primary" buttonSize="sm" onClick={this.onSave}> <Button
buttonStyle="primary"
buttonSize="small"
onClick={this.onSave}
>
{editMode ? t('Save') : t('Save for this session')} {editMode ? t('Save') : t('Save for this session')}
</Button> </Button>
<Button onClick={this.onCancel} buttonSize="sm"> <Button onClick={this.onCancel} buttonSize="small">
{t('Cancel')} {t('Cancel')}
</Button> </Button>
</> </>

View File

@ -531,11 +531,15 @@ export default class FilterScopeSelector extends React.PureComponent {
</div> </div>
<ActionsContainer> <ActionsContainer>
<Button buttonSize="sm" onClick={this.onClose}> <Button buttonSize="small" onClick={this.onClose}>
{t('Close')} {t('Close')}
</Button> </Button>
{showSelector && ( {showSelector && (
<Button buttonSize="sm" buttonStyle="primary" onClick={this.onSave}> <Button
buttonSize="small"
buttonStyle="primary"
onClick={this.onSave}
>
{t('Save')} {t('Save')}
</Button> </Button>
)} )}

View File

@ -497,15 +497,15 @@ const FilterBar: React.FC<FiltersBarProps> = ({
<ActionButtons> <ActionButtons>
<Button <Button
buttonStyle="secondary" buttonStyle="secondary"
buttonSize="sm" buttonSize="small"
onClick={handleResetAll} onClick={handleResetAll}
> >
{t('Reset all')} {t('Reset all')}
</Button> </Button>
<Button <Button
buttonStyle="primary" buttonStyle="primary"
type="submit" htmlType="submit"
buttonSize="sm" buttonSize="small"
onClick={handleApply} onClick={handleApply}
> >
{t('Apply')} {t('Apply')}

View File

@ -1000,7 +1000,7 @@ class DatasourceEditor extends React.PureComponent {
<ColumnButtonWrapper> <ColumnButtonWrapper>
<span className="m-t-10 m-r-10"> <span className="m-t-10 m-r-10">
<Button <Button
buttonSize="sm" buttonSize="small"
buttonStyle="primary" buttonStyle="primary"
onClick={this.syncMetadata} onClick={this.syncMetadata}
className="sync-from-source" className="sync-from-source"

View File

@ -181,7 +181,7 @@ const DatasourceModal: FunctionComponent<DatasourceModalProps> = ({
<> <>
{isFeatureEnabled(FeatureFlag.ENABLE_REACT_CRUD_VIEWS) && ( {isFeatureEnabled(FeatureFlag.ENABLE_REACT_CRUD_VIEWS) && (
<Button <Button
buttonSize="sm" buttonSize="small"
buttonStyle="default" buttonStyle="default"
data-test="datasource-modal-legacy-edit" data-test="datasource-modal-legacy-edit"
className="m-r-5" className="m-r-5"
@ -195,14 +195,14 @@ const DatasourceModal: FunctionComponent<DatasourceModalProps> = ({
)} )}
<Button <Button
data-test="datasource-modal-cancel" data-test="datasource-modal-cancel"
buttonSize="sm" buttonSize="small"
className="m-r-5" className="m-r-5"
onClick={onHide} onClick={onHide}
> >
{t('Cancel')} {t('Cancel')}
</Button> </Button>
<Button <Button
buttonSize="sm" buttonSize="small"
buttonStyle="primary" buttonStyle="primary"
data-test="datasource-modal-save" data-test="datasource-modal-save"
onClick={onClickSave} onClick={onClickSave}

View File

@ -45,7 +45,7 @@ export const CopyButton = styled(Button)`
`; `;
const CopyNode = ( const CopyNode = (
<CopyButton buttonSize="xs"> <CopyButton buttonSize="xsmall">
<i className="fa fa-clipboard" /> <i className="fa fa-clipboard" />
</CopyButton> </CopyButton>
); );

View File

@ -140,7 +140,7 @@ export const DisplayQueryButton = props => {
text={query} text={query}
shouldShowText={false} shouldShowText={false}
copyNode={ copyNode={
<CopyButtonViewQuery buttonSize="xs"> <CopyButtonViewQuery buttonSize="xsmall">
<i className="fa fa-clipboard" /> <i className="fa fa-clipboard" />
</CopyButtonViewQuery> </CopyButtonViewQuery>
} }

View File

@ -167,8 +167,8 @@ export default function PropertiesModal({
<> <>
<Button <Button
data-test="properties-modal-cancel-button" data-test="properties-modal-cancel-button"
type="button" htmlType="button"
buttonSize="sm" buttonSize="small"
onClick={onHide} onClick={onHide}
cta cta
> >
@ -176,8 +176,8 @@ export default function PropertiesModal({
</Button> </Button>
<Button <Button
data-test="properties-modal-save-button" data-test="properties-modal-save-button"
type="button" htmlType="button"
buttonSize="sm" buttonSize="small"
buttonStyle="primary" buttonStyle="primary"
// @ts-ignore // @ts-ignore
onClick={onSubmit} onClick={onSubmit}

View File

@ -18,8 +18,8 @@
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ButtonGroup } from 'react-bootstrap'; import ButtonGroup from 'src/components/ButtonGroup';
import { t, styled } from '@superset-ui/core'; import { t, useTheme } from '@superset-ui/core';
import { Tooltip } from 'src/common/components/Tooltip'; import { Tooltip } from 'src/common/components/Tooltip';
import Button from 'src/components/Button'; import Button from 'src/components/Button';
@ -39,20 +39,6 @@ const defaultProps = {
onSave: () => {}, onSave: () => {},
}; };
const Styles = styled.div`
display: flex;
flex-shrink: 0;
flex-direction: row;
align-items: center;
padding: ${({ theme }) => 2 * theme.gridUnit}px
${({ theme }) => 2 * theme.gridUnit}px 0
${({ theme }) => 4 * theme.gridUnit}px;
.btn {
/* just to make sure buttons don't jiggle */
width: 100px;
}
`;
export default function QueryAndSaveBtns({ export default function QueryAndSaveBtns({
canAdd, canAdd,
onQuery, onQuery,
@ -91,37 +77,51 @@ export default function QueryAndSaveBtns({
</Button> </Button>
); );
const theme = useTheme();
return ( return (
<Styles> <div
<div> css={{
<ButtonGroup className="query-and-save"> display: 'flex',
{qryOrStopButton} flexShrink: 0,
<Button flexDirection: 'row',
buttonStyle="tertiary" alignItems: 'center',
buttonSize="small" paddingTop: theme.gridUnit * 2,
data-target="#save_modal" paddingRight: theme.gridUnit * 2,
data-toggle="modal" paddingBottom: 0,
disabled={saveButtonDisabled} paddingLeft: theme.gridUnit * 4,
onClick={onSave} '& button': {
data-test="query-save-button" width: 100,
},
}}
>
<ButtonGroup className="query-and-save">
{qryOrStopButton}
<Button
buttonStyle="tertiary"
buttonSize="small"
data-target="#save_modal"
data-toggle="modal"
disabled={saveButtonDisabled}
onClick={onSave}
data-test="query-save-button"
>
<i className="fa fa-plus-circle" /> {t('Save')}
</Button>
</ButtonGroup>
{errorMessage && (
<span>
{' '}
<Tooltip
id="query-error-tooltip"
placement="right"
title={errorMessage}
> >
<i className="fa fa-plus-circle" /> {t('Save')} <i className="fa fa-exclamation-circle text-danger fa-lg" />
</Button> </Tooltip>
</ButtonGroup> </span>
{errorMessage && ( )}
<span> </div>
{' '}
<Tooltip
id="query-error-tooltip"
placement="right"
title={errorMessage}
>
<i className="fa fa-exclamation-circle text-danger fa-lg" />
</Tooltip>
</span>
)}
</div>
</Styles>
); );
} }

View File

@ -170,12 +170,16 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
title={t('Save chart')} title={t('Save chart')}
footer={ footer={
<div data-test="save-modal-footer"> <div data-test="save-modal-footer">
<Button id="btn_cancel" buttonSize="sm" onClick={this.props.onHide}> <Button
id="btn_cancel"
buttonSize="small"
onClick={this.props.onHide}
>
{t('Cancel')} {t('Cancel')}
</Button> </Button>
<Button <Button
id="btn_modal_save_goto_dash" id="btn_modal_save_goto_dash"
buttonSize="sm" buttonSize="small"
disabled={ disabled={
!this.state.newSliceName || !this.state.newSliceName ||
(!this.state.saveToDashboardId && !this.state.newDashboardName) (!this.state.saveToDashboardId && !this.state.newDashboardName)
@ -186,7 +190,7 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
</Button> </Button>
<Button <Button
id="btn_modal_save" id="btn_modal_save"
buttonSize="sm" buttonSize="small"
buttonStyle="primary" buttonStyle="primary"
onClick={() => this.saveOrOverwrite(false)} onClick={() => this.saveOrOverwrite(false)}
disabled={!this.state.newSliceName} disabled={!this.state.newSliceName}

View File

@ -745,17 +745,17 @@ export default class AnnotationLayer extends React.PureComponent {
</div> </div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}> <div style={{ display: 'flex', justifyContent: 'space-between' }}>
{isNew ? ( {isNew ? (
<Button buttonSize="sm" onClick={() => this.props.close()}> <Button buttonSize="small" onClick={() => this.props.close()}>
{t('Cancel')} {t('Cancel')}
</Button> </Button>
) : ( ) : (
<Button buttonSize="sm" onClick={this.deleteAnnotation}> <Button buttonSize="small" onClick={this.deleteAnnotation}>
{t('Remove')} {t('Remove')}
</Button> </Button>
)} )}
<div> <div>
<Button <Button
buttonSize="sm" buttonSize="small"
disabled={!isValid} disabled={!isValid}
onClick={this.applyAnnotation} onClick={this.applyAnnotation}
> >
@ -763,7 +763,7 @@ export default class AnnotationLayer extends React.PureComponent {
</Button> </Button>
<Button <Button
buttonSize="sm" buttonSize="small"
buttonStyle="primary" buttonStyle="primary"
disabled={!isValid} disabled={!isValid}
onClick={this.submitAnnotation} onClick={this.submitAnnotation}

View File

@ -302,10 +302,3 @@ export const CardStyles = styled.div`
text-decoration: none; text-decoration: none;
} }
`; `;
export const IconContainer = styled.div`
svg {
vertical-align: -7px;
color: ${({ theme }) => theme.colors.primary.dark1};
}
`;

View File

@ -27,13 +27,12 @@ import withToasts from 'src/messageToasts/enhancers/withToasts';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import PropertiesModal from 'src/explore/components/PropertiesModal'; import PropertiesModal from 'src/explore/components/PropertiesModal';
import { User } from 'src/types/bootstrapTypes'; import { User } from 'src/types/bootstrapTypes';
import Icon from 'src/components/Icon';
import ChartCard from 'src/views/CRUD/chart/ChartCard'; import ChartCard from 'src/views/CRUD/chart/ChartCard';
import Chart from 'src/types/Chart'; import Chart from 'src/types/Chart';
import ErrorBoundary from 'src/components/ErrorBoundary'; import ErrorBoundary from 'src/components/ErrorBoundary';
import SubMenu from 'src/components/Menu/SubMenu'; import SubMenu from 'src/components/Menu/SubMenu';
import EmptyState from './EmptyState'; import EmptyState from './EmptyState';
import { CardContainer, IconContainer } from '../utils'; import { CardContainer } from '../utils';
const PAGE_SIZE = 3; const PAGE_SIZE = 3;
@ -143,10 +142,10 @@ function ChartTable({
buttons={[ buttons={[
{ {
name: ( name: (
<IconContainer> <div>
<Icon name="plus-small" /> <i className="fa fa-plus" />
{t('Chart')} {t('Chart')}
</IconContainer> </div>
), ),
buttonStyle: 'tertiary', buttonStyle: 'tertiary',
onClick: () => { onClick: () => {

View File

@ -25,9 +25,8 @@ import withToasts from 'src/messageToasts/enhancers/withToasts';
import PropertiesModal from 'src/dashboard/components/PropertiesModal'; import PropertiesModal from 'src/dashboard/components/PropertiesModal';
import DashboardCard from 'src/views/CRUD/dashboard/DashboardCard'; import DashboardCard from 'src/views/CRUD/dashboard/DashboardCard';
import SubMenu from 'src/components/Menu/SubMenu'; import SubMenu from 'src/components/Menu/SubMenu';
import Icon from 'src/components/Icon';
import EmptyState from './EmptyState'; import EmptyState from './EmptyState';
import { createErrorHandler, CardContainer, IconContainer } from '../utils'; import { createErrorHandler, CardContainer } from '../utils';
const PAGE_SIZE = 3; const PAGE_SIZE = 3;
@ -149,9 +148,9 @@ function DashboardTable({
buttons={[ buttons={[
{ {
name: ( name: (
<IconContainer> <div>
<Icon name="plus-small" /> Dashboard{' '} <i className="fa fa-plus" /> Dashboard{' '}
</IconContainer> </div>
), ),
buttonStyle: 'tertiary', buttonStyle: 'tertiary',
onClick: () => { onClick: () => {

View File

@ -20,8 +20,6 @@ import React from 'react';
import Button from 'src/components/Button'; import Button from 'src/components/Button';
import { Empty } from 'src/common/components'; import { Empty } from 'src/common/components';
import { t, styled } from '@superset-ui/core'; import { t, styled } from '@superset-ui/core';
import Icon from 'src/components/Icon';
import { IconContainer } from '../utils';
interface EmptyStateProps { interface EmptyStateProps {
tableName: string; tableName: string;
@ -108,16 +106,14 @@ export default function EmptyState({ tableName, tab }: EmptyStateProps) {
window.location = mineRedirects[tableName]; window.location = mineRedirects[tableName];
}} }}
> >
<IconContainer> <i className="fa fa-plus" />
<Icon name="plus-small" />{' '} {tableName === 'SAVED_QUERIES'
{tableName === 'SAVED_QUERIES' ? t('SQL query')
? t('SQL query') : t(`${tableName
: t(`${tableName .split('')
.split('') .slice(0, tableName.length - 1)
.slice(0, tableName.length - 1) .join('')}
.join('')}
`)} `)}
</IconContainer>
</Button> </Button>
</ButtonContainer> </ButtonContainer>
)} )}

View File

@ -29,12 +29,7 @@ import DeleteModal from 'src/components/DeleteModal';
import Icon from 'src/components/Icon'; import Icon from 'src/components/Icon';
import SubMenu from 'src/components/Menu/SubMenu'; import SubMenu from 'src/components/Menu/SubMenu';
import EmptyState from './EmptyState'; import EmptyState from './EmptyState';
import { import { CardContainer, createErrorHandler, shortenSQL } from '../utils';
IconContainer,
CardContainer,
createErrorHandler,
shortenSQL,
} from '../utils';
SyntaxHighlighter.registerLanguage('sql', sql); SyntaxHighlighter.registerLanguage('sql', sql);
@ -272,9 +267,10 @@ const SavedQueries = ({
buttons={[ buttons={[
{ {
name: ( name: (
<IconContainer> <div>
<Icon name="plus-small" /> SQL Query{' '} <i className="fa fa-plus" />
</IconContainer> SQL Query{' '}
</div>
), ),
buttonStyle: 'tertiary', buttonStyle: 'tertiary',
onClick: () => { onClick: () => {