mirror of https://github.com/apache/superset.git
Migrates Button component from Bootstrap to AntD (#12832)
This commit is contained in:
parent
51195af4fa
commit
c781ab8adf
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import Button from 'src/components/Button';
|
||||
import Select from 'src/components/Select';
|
||||
import AddSliceContainer, {
|
||||
|
@ -25,6 +25,7 @@ import AddSliceContainer, {
|
|||
AddSliceContainerState,
|
||||
} from 'src/addSlice/AddSliceContainer';
|
||||
import VizTypeControl from 'src/explore/components/controls/VizTypeControl';
|
||||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
|
||||
const defaultProps = {
|
||||
datasources: [
|
||||
|
@ -34,14 +35,18 @@ const defaultProps = {
|
|||
};
|
||||
|
||||
describe('AddSliceContainer', () => {
|
||||
let wrapper: ShallowWrapper<
|
||||
let wrapper: ReactWrapper<
|
||||
AddSliceContainerProps,
|
||||
AddSliceContainerState,
|
||||
AddSliceContainer
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<AddSliceContainer {...defaultProps} />);
|
||||
wrapper = mount(<AddSliceContainer {...defaultProps} />) as ReactWrapper<
|
||||
AddSliceContainerProps,
|
||||
AddSliceContainerState,
|
||||
AddSliceContainer
|
||||
>;
|
||||
});
|
||||
|
||||
it('uses table as default visType', () => {
|
||||
|
@ -58,9 +63,9 @@ describe('AddSliceContainer', () => {
|
|||
});
|
||||
|
||||
it('renders a disabled button if no datasource is selected', () => {
|
||||
expect(wrapper.find(Button).dive().find({ disabled: true })).toHaveLength(
|
||||
1,
|
||||
);
|
||||
expect(
|
||||
wrapper.find(Button).find({ disabled: true }).hostNodes(),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders an enabled button if datasource is selected', () => {
|
||||
|
@ -70,9 +75,9 @@ describe('AddSliceContainer', () => {
|
|||
datasourceId: datasourceValue.split('__')[0],
|
||||
datasourceType: datasourceValue.split('__')[1],
|
||||
});
|
||||
expect(wrapper.find(Button).dive().find({ disabled: true })).toHaveLength(
|
||||
0,
|
||||
);
|
||||
expect(
|
||||
wrapper.find(Button).find({ disabled: true }).hostNodes(),
|
||||
).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('formats explore url', () => {
|
||||
|
|
|
@ -20,6 +20,7 @@ import React from 'react';
|
|||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
import { Provider } from 'react-redux';
|
||||
import FilterBar from 'src/dashboard/components/nativeFilters/FilterBar';
|
||||
import Button from 'src/components/Button';
|
||||
import { mockStore } from 'spec/fixtures/mockStore';
|
||||
|
||||
describe('FilterBar', () => {
|
||||
|
@ -42,7 +43,8 @@ describe('FilterBar', () => {
|
|||
expect(wrapper.find({ name: 'collapse' })).toExist();
|
||||
});
|
||||
it('has apply and reset all buttons', () => {
|
||||
expect(wrapper.find('.btn-primary')).toExist();
|
||||
expect(wrapper.find('.btn-secondary')).toExist();
|
||||
expect(wrapper.find(Button).length).toBe(2);
|
||||
expect(wrapper.find(Button).at(0)).toHaveProp('buttonStyle', 'secondary');
|
||||
expect(wrapper.find(Button).at(1)).toHaveProp('buttonStyle', 'primary');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import QueryAndSaveButtons from 'src/explore/components/QueryAndSaveBtns';
|
||||
|
@ -38,23 +38,21 @@ describe('QueryAndSaveButtons', () => {
|
|||
|
||||
// Test the output
|
||||
describe('output', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<QueryAndSaveButtons {...defaultProps} />);
|
||||
});
|
||||
const wrapper = mount(<QueryAndSaveButtons {...defaultProps} />);
|
||||
|
||||
it('renders 2 buttons', () => {
|
||||
expect(wrapper.find(Button)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('renders buttons with correct text', () => {
|
||||
expect(wrapper.find(Button).contains('Run')).toBe(true);
|
||||
expect(wrapper.find(Button).contains('Save')).toBe(true);
|
||||
expect(wrapper.find(Button).at(0).text().trim()).toBe('Run');
|
||||
expect(wrapper.find(Button).at(1).text().trim()).toBe('Save');
|
||||
});
|
||||
|
||||
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');
|
||||
expect(defaultProps.onQuery.called).toBe(true);
|
||||
});
|
||||
|
|
|
@ -308,7 +308,7 @@ export default class CRUDCollection extends React.PureComponent<
|
|||
{this.props.allowAddItem && (
|
||||
<span className="m-t-10 m-r-10">
|
||||
<Button
|
||||
buttonSize="sm"
|
||||
buttonSize="small"
|
||||
buttonStyle="primary"
|
||||
onClick={this.onAddItem}
|
||||
data-test="add-item-button"
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
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 moment from 'moment';
|
||||
import { RadioChangeEvent } from 'antd/lib/radio';
|
||||
|
@ -580,7 +581,7 @@ export default class ResultSet extends React.PureComponent<
|
|||
if (query.isDataPreview) {
|
||||
return (
|
||||
<Button
|
||||
buttonSize="sm"
|
||||
buttonSize="small"
|
||||
className="fetch"
|
||||
buttonStyle="primary"
|
||||
onClick={() =>
|
||||
|
@ -597,7 +598,7 @@ export default class ResultSet extends React.PureComponent<
|
|||
if (query.resultsKey) {
|
||||
return (
|
||||
<Button
|
||||
buttonSize="sm"
|
||||
buttonSize="small"
|
||||
className="fetch"
|
||||
buttonStyle="primary"
|
||||
onClick={() => this.fetchResults(query)}
|
||||
|
|
|
@ -101,7 +101,6 @@ export const SaveDatasetModal: FunctionComponent<SaveDatasetModalProps> = ({
|
|||
{!shouldOverwriteDataset && (
|
||||
<Button
|
||||
disabled={disableSaveAndExploreBtn}
|
||||
buttonSize="medium"
|
||||
buttonStyle="primary"
|
||||
onClick={onOk}
|
||||
>
|
||||
|
@ -110,12 +109,9 @@ export const SaveDatasetModal: FunctionComponent<SaveDatasetModalProps> = ({
|
|||
)}
|
||||
{shouldOverwriteDataset && (
|
||||
<>
|
||||
<Button buttonSize="medium" onClick={handleOverwriteCancel}>
|
||||
Back
|
||||
</Button>
|
||||
<Button onClick={handleOverwriteCancel}>Back</Button>
|
||||
<Button
|
||||
className="md"
|
||||
buttonSize="medium"
|
||||
buttonStyle="primary"
|
||||
onClick={handleOverwriteDataset}
|
||||
disabled={disableSaveAndExploreBtn}
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
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 { t, styled } from '@superset-ui/core';
|
||||
|
||||
|
|
|
@ -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' } };
|
|
@ -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' },
|
||||
};
|
|
@ -59,10 +59,4 @@ describe('Button', () => {
|
|||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,18 +20,11 @@ import React, { CSSProperties } from 'react';
|
|||
import { kebabCase } from 'lodash';
|
||||
import { mix } from 'polished';
|
||||
import cx from 'classnames';
|
||||
import { Button as BootstrapButton } from 'react-bootstrap';
|
||||
import { styled } from '@superset-ui/core';
|
||||
import { Button as AntdButton } from 'src/common/components';
|
||||
import { useTheme } from '@superset-ui/core';
|
||||
import { Tooltip } from 'src/common/components/Tooltip';
|
||||
import { Menu } from 'src/common/components';
|
||||
|
||||
export type OnClickHandler = React.MouseEventHandler<BootstrapButton>;
|
||||
|
||||
export interface DropdownItemProps {
|
||||
label: string;
|
||||
url: string;
|
||||
icon?: string;
|
||||
}
|
||||
export type OnClickHandler = React.MouseEventHandler<HTMLElement>;
|
||||
|
||||
export interface ButtonProps {
|
||||
id?: string;
|
||||
|
@ -52,264 +45,164 @@ export interface ButtonProps {
|
|||
| 'rightBottom';
|
||||
onClick?: OnClickHandler;
|
||||
disabled?: boolean;
|
||||
buttonStyle?: string;
|
||||
btnStyles?: string;
|
||||
buttonSize?: BootstrapButton.ButtonProps['bsSize'];
|
||||
style?: BootstrapButton.ButtonProps['style'];
|
||||
buttonStyle?:
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'tertiary'
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| 'danger'
|
||||
| 'default'
|
||||
| 'link'
|
||||
| 'dashed';
|
||||
buttonSize?: 'default' | 'small' | 'xsmall';
|
||||
style?: CSSProperties;
|
||||
children?: React.ReactNode;
|
||||
dropdownItems?: DropdownItemProps[];
|
||||
href?: string; // React-Bootstrap creates a link when this is passed in.
|
||||
target?: string; // React-Bootstrap creates a link when this is passed in.
|
||||
type?: string; // React-Bootstrap supports this when rendering an HTML button element
|
||||
href?: string;
|
||||
htmlType?: 'button' | 'submit' | 'reset';
|
||||
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)`
|
||||
&:focus,
|
||||
&:active,
|
||||
&:focus:active {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
transition: all ${({ theme }) => theme.transitionTiming}s;
|
||||
border-radius: ${({ theme }) => theme.borderRadius}px;
|
||||
border: none;
|
||||
font-size: ${({ theme }) => theme.typography.sizes.s}px;
|
||||
font-weight: ${({ theme }) => theme.typography.weights.bold};
|
||||
margin-left: ${({ theme }) => theme.gridUnit * 4}px;
|
||||
&:first-of-type {
|
||||
margin-left: 0;
|
||||
const theme = useTheme();
|
||||
const { colors, transitionTiming, borderRadius, typography } = theme;
|
||||
const { primary, grayscale, success, warning, error } = colors;
|
||||
|
||||
let height = 32;
|
||||
let padding = 18;
|
||||
if (buttonSize === 'xsmall') {
|
||||
height = 22;
|
||||
padding = 5;
|
||||
} else if (buttonSize === 'small') {
|
||||
height = 30;
|
||||
padding = 10;
|
||||
}
|
||||
|
||||
i {
|
||||
padding: 0 ${({ theme }) => theme.gridUnit * 2}px 0 0;
|
||||
let backgroundColor = primary.light4;
|
||||
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! */
|
||||
&.btn {
|
||||
border: 1px solid transparent; /* this just makes sure the height is the same as tertiary/dashed buttons */
|
||||
&:hover,
|
||||
&:active {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
&-default,
|
||||
&-secondary {
|
||||
background-color: ${({ theme }) => theme.colors.primary.light4};
|
||||
color: ${({ theme }) => theme.colors.primary.dark1};
|
||||
&:hover {
|
||||
background-color: ${({ theme }) =>
|
||||
mix(0.1, theme.colors.grayscale.light5, theme.colors.primary.light4)};
|
||||
color: ${({ theme }) => theme.colors.primary.dark1};
|
||||
}
|
||||
&:active {
|
||||
background-color: ${({ theme }) =>
|
||||
mix(0.25, theme.colors.primary.base, theme.colors.primary.light4)};
|
||||
color: ${({ theme }) => theme.colors.primary.dark1};
|
||||
}
|
||||
}
|
||||
&-tertiary,
|
||||
&-dashed {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
background-color: ${({ theme }) => theme.colors.grayscale.light5};
|
||||
color: ${({ theme }) => theme.colors.primary.dark1};
|
||||
border-color: ${({ theme }) => theme.colors.primary.dark1};
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.colors.grayscale.light5};
|
||||
color: ${({ theme }) => theme.colors.primary.dark1};
|
||||
border-color: ${({ theme }) => theme.colors.primary.light1};
|
||||
}
|
||||
&:active {
|
||||
background-color: ${({ theme }) => theme.colors.grayscale.light5};
|
||||
color: ${({ theme }) => theme.colors.primary.dark1};
|
||||
border-color: ${({ theme }) => theme.colors.primary.dark1};
|
||||
}
|
||||
&[disabled],
|
||||
&[disabled]:hover {
|
||||
background-color: ${({ theme }) => theme.colors.grayscale.light5};
|
||||
color: ${({ theme }) => theme.colors.grayscale.base};
|
||||
border-color: ${({ theme }) => theme.colors.grayscale.light2};
|
||||
}
|
||||
}
|
||||
&-dashed {
|
||||
border-style: dashed;
|
||||
&:hover,
|
||||
&:active {
|
||||
border-style: dashed;
|
||||
}
|
||||
}
|
||||
&-link {
|
||||
background: none;
|
||||
text-decoration: none;
|
||||
color: ${({ theme }) => theme.colors.primary.dark1};
|
||||
&: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>
|
||||
const button = (
|
||||
<AntdButton
|
||||
href={disabled ? undefined : href}
|
||||
disabled={disabled}
|
||||
className={cx(className, { cta: !!cta })}
|
||||
css={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
lineHeight: 1.5715,
|
||||
fontSize: typography.sizes.s,
|
||||
fontWeight: typography.weights.bold,
|
||||
height,
|
||||
textTransform: 'uppercase',
|
||||
padding: `0px ${padding}px`,
|
||||
transition: `all ${transitionTiming}s`,
|
||||
minWidth: cta ? theme.gridUnit * 36 : undefined,
|
||||
minHeight: cta ? theme.gridUnit * 8 : undefined,
|
||||
boxShadow: 'none',
|
||||
borderWidth,
|
||||
borderStyle,
|
||||
borderColor,
|
||||
borderRadius,
|
||||
color,
|
||||
backgroundColor,
|
||||
'&:hover': {
|
||||
color: colorHover,
|
||||
backgroundColor: backgroundColorHover,
|
||||
borderColor: borderColorHover,
|
||||
},
|
||||
'&:active': {
|
||||
color,
|
||||
backgroundColor: backgroundColorActive,
|
||||
},
|
||||
'&:focus': {
|
||||
color,
|
||||
backgroundColor,
|
||||
borderColor,
|
||||
},
|
||||
'&[disabled], &[disabled]:hover': {
|
||||
color: grayscale.base,
|
||||
backgroundColor: backgroundColorDisabled,
|
||||
borderColor: borderColorDisabled,
|
||||
},
|
||||
'i:first-of-type, svg:first-of-type': {
|
||||
marginRight: theme.gridUnit * 2,
|
||||
padding: `0 ${theme.gridUnit * 2} 0 0`,
|
||||
},
|
||||
marginLeft: theme.gridUnit * 2,
|
||||
'&:first-of-type': {
|
||||
marginLeft: 0,
|
||||
},
|
||||
}}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</AntdButton>
|
||||
);
|
||||
|
||||
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}`} />
|
||||
{dropdownItem.label}
|
||||
</a>
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (tooltip) {
|
||||
return (
|
||||
<Tooltip
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
};
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -21,7 +21,7 @@ import thunk from 'redux-thunk';
|
|||
import configureStore from 'redux-mock-store';
|
||||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
|
||||
import Button from 'src/components/Button';
|
||||
import { ImportResourceName } from 'src/views/CRUD/types';
|
||||
import ImportModelsModal from 'src/components/ImportModal';
|
||||
import Modal from 'src/common/components/Modal';
|
||||
|
@ -82,18 +82,13 @@ describe('ImportModelsModal', () => {
|
|||
});
|
||||
|
||||
it('should render the import button initially disabled', () => {
|
||||
expect(wrapper.find('button[children="Import"]').prop('disabled')).toBe(
|
||||
true,
|
||||
);
|
||||
expect(wrapper.find(Button).at(1).prop('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('should render the import button enabled when a file is selected', () => {
|
||||
const file = new File([new ArrayBuffer(1)], 'model_export.zip');
|
||||
wrapper.find('input').simulate('change', { target: { files: [file] } });
|
||||
|
||||
expect(wrapper.find('button[children="Import"]').prop('disabled')).toBe(
|
||||
false,
|
||||
);
|
||||
expect(wrapper.find(Button).at(1).prop('disabled')).toBe(false);
|
||||
});
|
||||
|
||||
it('should render password fields when needed for import', () => {
|
||||
|
|
|
@ -21,7 +21,7 @@ import moment from 'moment';
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { styled, CategoricalColorNamespace, t } from '@superset-ui/core';
|
||||
import { ButtonGroup } from 'react-bootstrap';
|
||||
import ButtonGroup from 'src/components/ButtonGroup';
|
||||
|
||||
import {
|
||||
LOG_ACTIONS_PERIODIC_RENDER_DASHBOARD,
|
||||
|
|
|
@ -296,8 +296,8 @@ class PropertiesModal extends React.PureComponent {
|
|||
footer={
|
||||
<>
|
||||
<Button
|
||||
type="button"
|
||||
buttonSize="sm"
|
||||
htmlType="button"
|
||||
buttonSize="small"
|
||||
onClick={onHide}
|
||||
data-test="properties-modal-cancel-button"
|
||||
cta
|
||||
|
@ -306,7 +306,7 @@ class PropertiesModal extends React.PureComponent {
|
|||
</Button>
|
||||
<Button
|
||||
onClick={this.submit}
|
||||
buttonSize="sm"
|
||||
buttonSize="small"
|
||||
buttonStyle="primary"
|
||||
className="m-r-5"
|
||||
disabled={errors.length > 0}
|
||||
|
|
|
@ -135,10 +135,14 @@ class RefreshIntervalModal extends React.PureComponent<
|
|||
}
|
||||
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')}
|
||||
</Button>
|
||||
<Button onClick={this.onCancel} buttonSize="sm">
|
||||
<Button onClick={this.onCancel} buttonSize="small">
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</>
|
||||
|
|
|
@ -531,11 +531,15 @@ export default class FilterScopeSelector extends React.PureComponent {
|
|||
</div>
|
||||
|
||||
<ActionsContainer>
|
||||
<Button buttonSize="sm" onClick={this.onClose}>
|
||||
<Button buttonSize="small" onClick={this.onClose}>
|
||||
{t('Close')}
|
||||
</Button>
|
||||
{showSelector && (
|
||||
<Button buttonSize="sm" buttonStyle="primary" onClick={this.onSave}>
|
||||
<Button
|
||||
buttonSize="small"
|
||||
buttonStyle="primary"
|
||||
onClick={this.onSave}
|
||||
>
|
||||
{t('Save')}
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
@ -497,15 +497,15 @@ const FilterBar: React.FC<FiltersBarProps> = ({
|
|||
<ActionButtons>
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
buttonSize="sm"
|
||||
buttonSize="small"
|
||||
onClick={handleResetAll}
|
||||
>
|
||||
{t('Reset all')}
|
||||
</Button>
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
type="submit"
|
||||
buttonSize="sm"
|
||||
htmlType="submit"
|
||||
buttonSize="small"
|
||||
onClick={handleApply}
|
||||
>
|
||||
{t('Apply')}
|
||||
|
|
|
@ -1000,7 +1000,7 @@ class DatasourceEditor extends React.PureComponent {
|
|||
<ColumnButtonWrapper>
|
||||
<span className="m-t-10 m-r-10">
|
||||
<Button
|
||||
buttonSize="sm"
|
||||
buttonSize="small"
|
||||
buttonStyle="primary"
|
||||
onClick={this.syncMetadata}
|
||||
className="sync-from-source"
|
||||
|
|
|
@ -181,7 +181,7 @@ const DatasourceModal: FunctionComponent<DatasourceModalProps> = ({
|
|||
<>
|
||||
{isFeatureEnabled(FeatureFlag.ENABLE_REACT_CRUD_VIEWS) && (
|
||||
<Button
|
||||
buttonSize="sm"
|
||||
buttonSize="small"
|
||||
buttonStyle="default"
|
||||
data-test="datasource-modal-legacy-edit"
|
||||
className="m-r-5"
|
||||
|
@ -195,14 +195,14 @@ const DatasourceModal: FunctionComponent<DatasourceModalProps> = ({
|
|||
)}
|
||||
<Button
|
||||
data-test="datasource-modal-cancel"
|
||||
buttonSize="sm"
|
||||
buttonSize="small"
|
||||
className="m-r-5"
|
||||
onClick={onHide}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
buttonSize="sm"
|
||||
buttonSize="small"
|
||||
buttonStyle="primary"
|
||||
data-test="datasource-modal-save"
|
||||
onClick={onClickSave}
|
||||
|
|
|
@ -45,7 +45,7 @@ export const CopyButton = styled(Button)`
|
|||
`;
|
||||
|
||||
const CopyNode = (
|
||||
<CopyButton buttonSize="xs">
|
||||
<CopyButton buttonSize="xsmall">
|
||||
<i className="fa fa-clipboard" />
|
||||
</CopyButton>
|
||||
);
|
||||
|
|
|
@ -140,7 +140,7 @@ export const DisplayQueryButton = props => {
|
|||
text={query}
|
||||
shouldShowText={false}
|
||||
copyNode={
|
||||
<CopyButtonViewQuery buttonSize="xs">
|
||||
<CopyButtonViewQuery buttonSize="xsmall">
|
||||
<i className="fa fa-clipboard" />
|
||||
</CopyButtonViewQuery>
|
||||
}
|
||||
|
|
|
@ -167,8 +167,8 @@ export default function PropertiesModal({
|
|||
<>
|
||||
<Button
|
||||
data-test="properties-modal-cancel-button"
|
||||
type="button"
|
||||
buttonSize="sm"
|
||||
htmlType="button"
|
||||
buttonSize="small"
|
||||
onClick={onHide}
|
||||
cta
|
||||
>
|
||||
|
@ -176,8 +176,8 @@ export default function PropertiesModal({
|
|||
</Button>
|
||||
<Button
|
||||
data-test="properties-modal-save-button"
|
||||
type="button"
|
||||
buttonSize="sm"
|
||||
htmlType="button"
|
||||
buttonSize="small"
|
||||
buttonStyle="primary"
|
||||
// @ts-ignore
|
||||
onClick={onSubmit}
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ButtonGroup } from 'react-bootstrap';
|
||||
import { t, styled } from '@superset-ui/core';
|
||||
import ButtonGroup from 'src/components/ButtonGroup';
|
||||
import { t, useTheme } from '@superset-ui/core';
|
||||
|
||||
import { Tooltip } from 'src/common/components/Tooltip';
|
||||
import Button from 'src/components/Button';
|
||||
|
@ -39,20 +39,6 @@ const defaultProps = {
|
|||
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({
|
||||
canAdd,
|
||||
onQuery,
|
||||
|
@ -91,37 +77,51 @@ export default function QueryAndSaveBtns({
|
|||
</Button>
|
||||
);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Styles>
|
||||
<div>
|
||||
<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"
|
||||
<div
|
||||
css={{
|
||||
display: 'flex',
|
||||
flexShrink: 0,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingTop: theme.gridUnit * 2,
|
||||
paddingRight: theme.gridUnit * 2,
|
||||
paddingBottom: 0,
|
||||
paddingLeft: theme.gridUnit * 4,
|
||||
'& 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')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
{errorMessage && (
|
||||
<span>
|
||||
{' '}
|
||||
<Tooltip
|
||||
id="query-error-tooltip"
|
||||
placement="right"
|
||||
title={errorMessage}
|
||||
>
|
||||
<i className="fa fa-exclamation-circle text-danger fa-lg" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Styles>
|
||||
<i className="fa fa-exclamation-circle text-danger fa-lg" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -170,12 +170,16 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
|
|||
title={t('Save chart')}
|
||||
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')}
|
||||
</Button>
|
||||
<Button
|
||||
id="btn_modal_save_goto_dash"
|
||||
buttonSize="sm"
|
||||
buttonSize="small"
|
||||
disabled={
|
||||
!this.state.newSliceName ||
|
||||
(!this.state.saveToDashboardId && !this.state.newDashboardName)
|
||||
|
@ -186,7 +190,7 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
|
|||
</Button>
|
||||
<Button
|
||||
id="btn_modal_save"
|
||||
buttonSize="sm"
|
||||
buttonSize="small"
|
||||
buttonStyle="primary"
|
||||
onClick={() => this.saveOrOverwrite(false)}
|
||||
disabled={!this.state.newSliceName}
|
||||
|
|
|
@ -745,17 +745,17 @@ export default class AnnotationLayer extends React.PureComponent {
|
|||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
{isNew ? (
|
||||
<Button buttonSize="sm" onClick={() => this.props.close()}>
|
||||
<Button buttonSize="small" onClick={() => this.props.close()}>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button buttonSize="sm" onClick={this.deleteAnnotation}>
|
||||
<Button buttonSize="small" onClick={this.deleteAnnotation}>
|
||||
{t('Remove')}
|
||||
</Button>
|
||||
)}
|
||||
<div>
|
||||
<Button
|
||||
buttonSize="sm"
|
||||
buttonSize="small"
|
||||
disabled={!isValid}
|
||||
onClick={this.applyAnnotation}
|
||||
>
|
||||
|
@ -763,7 +763,7 @@ export default class AnnotationLayer extends React.PureComponent {
|
|||
</Button>
|
||||
|
||||
<Button
|
||||
buttonSize="sm"
|
||||
buttonSize="small"
|
||||
buttonStyle="primary"
|
||||
disabled={!isValid}
|
||||
onClick={this.submitAnnotation}
|
||||
|
|
|
@ -302,10 +302,3 @@ export const CardStyles = styled.div`
|
|||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const IconContainer = styled.div`
|
||||
svg {
|
||||
vertical-align: -7px;
|
||||
color: ${({ theme }) => theme.colors.primary.dark1};
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -27,13 +27,12 @@ import withToasts from 'src/messageToasts/enhancers/withToasts';
|
|||
import { useHistory } from 'react-router-dom';
|
||||
import PropertiesModal from 'src/explore/components/PropertiesModal';
|
||||
import { User } from 'src/types/bootstrapTypes';
|
||||
import Icon from 'src/components/Icon';
|
||||
import ChartCard from 'src/views/CRUD/chart/ChartCard';
|
||||
import Chart from 'src/types/Chart';
|
||||
import ErrorBoundary from 'src/components/ErrorBoundary';
|
||||
import SubMenu from 'src/components/Menu/SubMenu';
|
||||
import EmptyState from './EmptyState';
|
||||
import { CardContainer, IconContainer } from '../utils';
|
||||
import { CardContainer } from '../utils';
|
||||
|
||||
const PAGE_SIZE = 3;
|
||||
|
||||
|
@ -143,10 +142,10 @@ function ChartTable({
|
|||
buttons={[
|
||||
{
|
||||
name: (
|
||||
<IconContainer>
|
||||
<Icon name="plus-small" />
|
||||
<div>
|
||||
<i className="fa fa-plus" />
|
||||
{t('Chart')}
|
||||
</IconContainer>
|
||||
</div>
|
||||
),
|
||||
buttonStyle: 'tertiary',
|
||||
onClick: () => {
|
||||
|
|
|
@ -25,9 +25,8 @@ import withToasts from 'src/messageToasts/enhancers/withToasts';
|
|||
import PropertiesModal from 'src/dashboard/components/PropertiesModal';
|
||||
import DashboardCard from 'src/views/CRUD/dashboard/DashboardCard';
|
||||
import SubMenu from 'src/components/Menu/SubMenu';
|
||||
import Icon from 'src/components/Icon';
|
||||
import EmptyState from './EmptyState';
|
||||
import { createErrorHandler, CardContainer, IconContainer } from '../utils';
|
||||
import { createErrorHandler, CardContainer } from '../utils';
|
||||
|
||||
const PAGE_SIZE = 3;
|
||||
|
||||
|
@ -149,9 +148,9 @@ function DashboardTable({
|
|||
buttons={[
|
||||
{
|
||||
name: (
|
||||
<IconContainer>
|
||||
<Icon name="plus-small" /> Dashboard{' '}
|
||||
</IconContainer>
|
||||
<div>
|
||||
<i className="fa fa-plus" /> Dashboard{' '}
|
||||
</div>
|
||||
),
|
||||
buttonStyle: 'tertiary',
|
||||
onClick: () => {
|
||||
|
|
|
@ -20,8 +20,6 @@ import React from 'react';
|
|||
import Button from 'src/components/Button';
|
||||
import { Empty } from 'src/common/components';
|
||||
import { t, styled } from '@superset-ui/core';
|
||||
import Icon from 'src/components/Icon';
|
||||
import { IconContainer } from '../utils';
|
||||
|
||||
interface EmptyStateProps {
|
||||
tableName: string;
|
||||
|
@ -108,16 +106,14 @@ export default function EmptyState({ tableName, tab }: EmptyStateProps) {
|
|||
window.location = mineRedirects[tableName];
|
||||
}}
|
||||
>
|
||||
<IconContainer>
|
||||
<Icon name="plus-small" />{' '}
|
||||
{tableName === 'SAVED_QUERIES'
|
||||
? t('SQL query')
|
||||
: t(`${tableName
|
||||
.split('')
|
||||
.slice(0, tableName.length - 1)
|
||||
.join('')}
|
||||
<i className="fa fa-plus" />
|
||||
{tableName === 'SAVED_QUERIES'
|
||||
? t('SQL query')
|
||||
: t(`${tableName
|
||||
.split('')
|
||||
.slice(0, tableName.length - 1)
|
||||
.join('')}
|
||||
`)}
|
||||
</IconContainer>
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
)}
|
||||
|
|
|
@ -29,12 +29,7 @@ import DeleteModal from 'src/components/DeleteModal';
|
|||
import Icon from 'src/components/Icon';
|
||||
import SubMenu from 'src/components/Menu/SubMenu';
|
||||
import EmptyState from './EmptyState';
|
||||
import {
|
||||
IconContainer,
|
||||
CardContainer,
|
||||
createErrorHandler,
|
||||
shortenSQL,
|
||||
} from '../utils';
|
||||
import { CardContainer, createErrorHandler, shortenSQL } from '../utils';
|
||||
|
||||
SyntaxHighlighter.registerLanguage('sql', sql);
|
||||
|
||||
|
@ -272,9 +267,10 @@ const SavedQueries = ({
|
|||
buttons={[
|
||||
{
|
||||
name: (
|
||||
<IconContainer>
|
||||
<Icon name="plus-small" /> SQL Query{' '}
|
||||
</IconContainer>
|
||||
<div>
|
||||
<i className="fa fa-plus" />
|
||||
SQL Query{' '}
|
||||
</div>
|
||||
),
|
||||
buttonStyle: 'tertiary',
|
||||
onClick: () => {
|
||||
|
|
Loading…
Reference in New Issue