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.
|
* 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', () => {
|
||||||
|
|
|
@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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);
|
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 { 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}`} />
|
|
||||||
{dropdownItem.label}
|
|
||||||
</a>
|
|
||||||
</Menu.Item>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tooltip) {
|
if (tooltip) {
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<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 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', () => {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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')}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
|
@ -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: () => {
|
||||||
|
|
|
@ -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: () => {
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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: () => {
|
||||||
|
|
Loading…
Reference in New Issue