style: Pass at propagating (and enhancing) Button component throughout Superset (#10649)

* getting rid of weird focus/active outline ring

* Buttons... buttons _everywhere_

* linting

* Nixing views/CRUD/dataset/Button component

* fixing 2 typing errors

* fixing more TS errors

* prefer src path for include

* one more real button, one less CSS class

* one more "button" to "Button"

* Published Status is now a proper clickable Label

* nixing the CRUD button again

* touching up stories, with SupersetButton story

* SIP-34 button colors

* adding polished package to mix colors

* updating button colors to match Superset theme

* abstracting away from bootstrap-specific props (might pivot libraries soon!)

* more abstraction from bsStyle/bsSize props

* exchanging styles for a prop

* linting

* restoring feature flag to stock

* using src alias

* last <button> replacement

* this classname would never be applied

* more linting action

* fixing unsupported bsSize 'medium', and cta typing error

* more cta action

* unnecessary styles

* errant bsSize prop

* cleanup

* tweaks to make new New button work

* Linting

* fixing a couple tests

* fixing theme based test failure

* margin tweak for NEW button

* another fixed test

* another fixed test

* fixing two more tests

* fixing last broken tests.

* always be linting

* Adding tertiary/dashed buttons

* cleaning up QueryAndSave buttons

* fixing "link" button styles

* fixing/updating link button styles

* cta buttons on Modal component

* linting.

* exporting button story knobs, making ALL knobs safe for export.

* capitalizing a file... no big whoop

* Basic button tests

* renaming button - temporarily

* renaming file to fix capitalization issue

* passing theme through to a difficult popover.

* fixin' a newly busted unit test

* lint fixin'

* oops, shouldn't have changed this prop!

* adding a dive() to themedShallow, and fixing a cypress/jest test

* addressing lint stuff

* touching up stories, with SupersetButton story

* SIP-34 button colors

* updating button colors to match Superset theme

* abstracting away from bootstrap-specific props (might pivot libraries soon!)

* linting

* restoring feature flag to stock

* cleanup

* Linting

* renaming button - temporarily

* renaming file to fix capitalization issue

* oops, shouldn't have changed this prop!

* adding a dive() to themedShallow, and fixing a cypress/jest test

* addressing lint stuff

* nixing new modal button

* Fixing another popover/button issue that should break cypress

* lint 

* passing classNames through to new button (should fix some tests)

* cleaning unused classes, making cypress tests use data attrs

* fixin' the test

* fixing another class-based test with data-test attr

* no longer passing theme as prop to buttons in popovers... themeprovider is better

* outline/border tweaks!
This commit is contained in:
Evan Rusackas 2020-08-28 17:34:28 -07:00 committed by GitHub
parent 33fa9ebff1
commit 9fe30ab71e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
78 changed files with 760 additions and 562 deletions

View File

@ -40,7 +40,7 @@ describe('AdhocFilters', () => {
cy.get('button').contains('Save').click();
});
cy.get('button.query').click();
cy.get('button[data-test="run-query-button"]').click();
cy.verifySliceSuccess({
waitAlias: '@postJson',
chartSelector: 'svg',
@ -63,7 +63,7 @@ describe('AdhocFilters', () => {
cy.get('button').contains('Save').click();
});
cy.get('button.query').click();
cy.get('button[data-test="run-query-button"]').click();
cy.verifySliceSuccess({
waitAlias: '@postJson',
chartSelector: 'svg',

View File

@ -48,7 +48,7 @@ describe('AdhocMetrics', () => {
cy.get('.metrics-select .metric-option').contains(metricName);
cy.get('button.query').click();
cy.get('button[data-test="run-query-button"]').click();
cy.verifySliceSuccess({
waitAlias: '@postJson',
querySubstring: `${metric} AS "${metricName}"`, // SQL statement
@ -77,7 +77,7 @@ describe('AdhocMetrics', () => {
.type('/COUNT(DISTINCT name)', { force: true });
cy.get('#metrics-edit-popover').find('button').contains('Save').click();
cy.get('button.query').click();
cy.get('button[data-test="run-query-button"]').click();
const metric = 'SUM(num)/COUNT(DISTINCT name)';
cy.verifySliceSuccess({
@ -103,7 +103,7 @@ describe('AdhocMetrics', () => {
cy.get('button').contains('Save').click();
});
cy.get('button.query').click();
cy.get('button[data-test="run-query-button"]').click();
const metric = 'SUM(num)';
cy.verifySliceSuccess({
@ -124,7 +124,7 @@ describe('AdhocMetrics', () => {
});
const metric = 'AVG(sum_boys)';
cy.get('button.query').click();
cy.get('button[data-test="run-query-button"]').click();
cy.verifySliceSuccess({
waitAlias: '@postJson',
querySubstring: `${metric} AS "${metric}"`,

View File

@ -43,7 +43,7 @@ describe('Advanced analytics', () => {
.find('.Select__multi-value__label')
.contains('364 days');
cy.get('button.query').click();
cy.get('button[data-test="run-query-button"]').click();
cy.wait('@postJson');
cy.reload();
cy.verifySliceSuccess({
@ -90,7 +90,7 @@ describe('Annotations', () => {
cy.get('button').contains('OK').click();
});
cy.get('button.query').click();
cy.get('button[data-test="run-query-button"]').click();
cy.verifySliceSuccess({
waitAlias: '@postJson',
chartSelector: 'svg',

View File

@ -78,7 +78,7 @@ describe('Groupby control', () => {
cy.get('.Select__control').click();
cy.get('input[type=text]').type('state{enter}');
});
cy.get('button.query').click();
cy.get('button[data-test="run-query-button"]').click();
cy.verifySliceSuccess({ waitAlias: '@postJson', chartSelector: 'svg' });
});
});

View File

@ -34830,16 +34830,14 @@
"version": "3.6.5",
"resolved": "https://registry.npmjs.org/polished/-/polished-3.6.5.tgz",
"integrity": "sha512-VwhC9MlhW7O5dg/z7k32dabcAFW1VI2+7fSe8cE/kXcfL7mVdoa5UxciYGW2sJU78ldDLT6+ROEKIZKFNTnUXQ==",
"dev": true,
"requires": {
"@babel/runtime": "^7.9.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.10.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz",
"integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==",
"dev": true,
"version": "7.11.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
"integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
"requires": {
"regenerator-runtime": "^0.13.4"
}

View File

@ -145,6 +145,7 @@
"mousetrap": "^1.6.1",
"mustache": "^2.2.1",
"omnibar": "^2.1.1",
"polished": "^3.6.5",
"prop-types": "^15.7.2",
"re-resizable": "^4.3.1",
"react": "^16.13.1",

View File

@ -51,5 +51,5 @@ export function styledShallow(
theme: supersetTheme,
...options?.wrappingComponentProps,
},
});
}).dive();
}

View File

@ -18,7 +18,7 @@
*/
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { Button } from 'react-bootstrap';
import Button from 'src/components/Button';
import Select from 'src/components/Select';
import AddSliceContainer, {
AddSliceContainerProps,
@ -58,9 +58,9 @@ describe('AddSliceContainer', () => {
});
it('renders a disabled button if no datasource is selected', () => {
expect(
wrapper.find(Button).dive().find('.btn[disabled=true]'),
).toHaveLength(1);
expect(wrapper.find(Button).dive().find({ disabled: true })).toHaveLength(
1,
);
});
it('renders an enabled button if datasource is selected', () => {
@ -70,9 +70,9 @@ describe('AddSliceContainer', () => {
datasourceId: datasourceValue.split('__')[0],
datasourceType: datasourceValue.split('__')[1],
});
expect(
wrapper.find(Button).dive().find('.btn[disabled=false]'),
).toHaveLength(1);
expect(wrapper.find(Button).dive().find({ disabled: true })).toHaveLength(
0,
);
});
it('formats explore url', () => {

View File

@ -18,7 +18,7 @@
*/
import React from 'react';
import { mount } from 'enzyme';
import { Button } from 'react-bootstrap';
import Button from 'src/components/Button';
import { supersetTheme, ThemeProvider } from '@superset-ui/style';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
import Modal from 'src/components/Modal';
@ -33,7 +33,7 @@ describe('ConfirmStatusChange', () => {
<ConfirmStatusChange {...mockedProps}>
{confirm => (
<>
<button id="btn1" onClick={confirm} />
<Button id="btn1" onClick={confirm} />
</>
)}
</ConfirmStatusChange>,
@ -44,7 +44,7 @@ describe('ConfirmStatusChange', () => {
);
it('opens a confirm modal', () => {
wrapper.find('#btn1').props().onClick('foo');
wrapper.find('#btn1').first().props().onClick('foo');
wrapper.update();

View File

@ -18,7 +18,7 @@
*/
import { Provider } from 'react-redux';
import React from 'react';
import { mount } from 'enzyme';
import { styledMount as mount } from 'spec/helpers/theming';
import sinon from 'sinon';
import DashboardComponent from 'src/dashboard/containers/DashboardComponent';

View File

@ -83,7 +83,10 @@ describe('DatasourceModal', () => {
it('saves on confirm', async () => {
act(() => {
wrapper.find('[className="m-r-5"]').props().onClick();
wrapper
.find('button[data-test="datasource-modal-save"]')
.props()
.onClick();
});
await waitForComponentToPaint(wrapper);
act(() => {

View File

@ -20,7 +20,8 @@
import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import { Button, Popover, Tab, Tabs } from 'react-bootstrap';
import { Popover, Tab, Tabs } from 'react-bootstrap';
import Button from 'src/components/Button';
import AdhocFilter, {
EXPRESSION_TYPES,
@ -117,9 +118,9 @@ describe('AdhocFilterEditPopover', () => {
it('highlights save if changes are present', () => {
const { wrapper } = setup();
expect(wrapper.find(Button).find({ bsStyle: 'primary' })).not.toExist();
expect(wrapper.find(Button).find({ buttonStyle: 'primary' })).not.toExist();
wrapper.instance().onAdhocFilterChange(sqlAdhocFilter);
expect(wrapper.find(Button).find({ bsStyle: 'primary' })).toExist();
expect(wrapper.find(Button).find({ buttonStyle: 'primary' })).toExist();
});
it('will initiate a drag when clicked', () => {

View File

@ -19,7 +19,7 @@
/* eslint-disable no-unused-expressions */
import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import { styledShallow as shallow } from 'spec/helpers/theming';
import { OverlayTrigger } from 'react-bootstrap';
import Label from 'src/components/Label';
@ -46,7 +46,7 @@ function setup(overrides) {
datasource: {},
...overrides,
};
const wrapper = shallow(<AdhocFilterOption {...props} />);
const wrapper = shallow(<AdhocFilterOption {...props} />).dive();
return { wrapper };
}

View File

@ -20,7 +20,8 @@
import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import { Button, FormGroup, Popover } from 'react-bootstrap';
import { FormGroup, Popover } from 'react-bootstrap';
import Button from 'src/components/Button';
import AdhocMetric, { EXPRESSION_TYPES } from 'src/explore/AdhocMetric';
import AdhocMetricEditPopover from 'src/explore/components/AdhocMetricEditPopover';
@ -117,9 +118,9 @@ describe('AdhocMetricEditPopover', () => {
it('highlights save if changes are present', () => {
const { wrapper } = setup();
expect(wrapper.find(Button).find({ bsStyle: 'primary' })).not.toExist();
expect(wrapper.find(Button).find({ buttonStyle: 'primary' })).not.toExist();
wrapper.instance().onColumnChange({ column: columns[1] });
expect(wrapper.find(Button).find({ bsStyle: 'primary' })).toExist();
expect(wrapper.find(Button).find({ buttonStyle: 'primary' })).toExist();
});
it('will initiate a drag when clicked', () => {

View File

@ -19,7 +19,7 @@
/* eslint-disable no-unused-expressions */
import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import { styledShallow as shallow } from 'spec/helpers/theming';
import { OverlayTrigger } from 'react-bootstrap';
import Label from 'src/components/Label';
@ -46,7 +46,7 @@ function setup(overrides) {
columns,
...overrides,
};
const wrapper = shallow(<AdhocMetricOption {...props} />);
const wrapper = shallow(<AdhocMetricOption {...props} />).dive();
return { wrapper, onMetricEdit };
}

View File

@ -20,7 +20,7 @@
import React from 'react';
import sinon from 'sinon';
import { styledMount as mount } from 'spec/helpers/theming';
import { Button } from 'react-bootstrap';
import Button from 'src/components/Button';
import Label from 'src/components/Label';
import DateFilterControl from 'src/explore/components/controls/DateFilterControl';

View File

@ -54,7 +54,7 @@ describe('QueryAndSaveButtons', () => {
});
it('calls onQuery when query button is clicked', () => {
const queryButton = wrapper.find('.query');
const queryButton = wrapper.find('[data-test="run-query-button"]');
queryButton.simulate('click');
expect(defaultProps.onQuery.called).toBe(true);
});

View File

@ -21,8 +21,10 @@ import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { bindActionCreators } from 'redux';
import { shallow, mount } from 'enzyme';
import { FormControl, Modal, Button, Radio } from 'react-bootstrap';
import { shallow } from 'enzyme';
import { styledMount as mount } from 'spec/helpers/theming';
import { FormControl, Modal, Radio } from 'react-bootstrap';
import Button from 'src/components/Button';
import sinon from 'sinon';
import fetchMock from 'fetch-mock';

View File

@ -17,7 +17,7 @@
* under the License.
*/
import React from 'react';
import { Button } from 'react-bootstrap';
import Button from 'src/components/Button';
import { shallow } from 'enzyme';
import sinon from 'sinon';

View File

@ -78,7 +78,7 @@ describe('SouthPane', () => {
const getWrapper = () =>
shallow(<SouthPaneContainer {...mockedProps} />, {
context: { store },
}).dive();
});
let wrapper;

View File

@ -19,7 +19,7 @@
import React, { ReactNode } from 'react';
import shortid from 'shortid';
import { t } from '@superset-ui/translation';
import Button from '../components/Button';
import Button from 'src/components/Button';
import Fieldset from './Fieldset';
import { recurseReactClone } from './utils';
import './crud.less';
@ -167,7 +167,7 @@ export default class CRUDCollection extends React.PureComponent<
{allowDeletes && !allowAddItem && <th className="tiny-cell" />}
{allowAddItem && (
<th>
<Button bsStyle="primary" onClick={this.onAddItem}>
<Button buttonStyle="primary" onClick={this.onAddItem}>
<i className="fa fa-plus" /> {t('Add Item')}
</Button>
</th>

View File

@ -22,7 +22,7 @@ import { Table } from 'reactable-arc';
import { Alert } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import Button from '../../components/Button';
import Button from 'src/components/Button';
import Loading from '../../components/Loading';
import ModalTrigger from '../../components/ModalTrigger';
@ -85,8 +85,8 @@ class EstimateQueryCostButton extends React.PureComponent {
modalBody={this.renderModalBody()}
triggerNode={
<Button
bsStyle="warning"
bsSize="small"
buttonStyle="warning"
buttonSize="small"
onClick={this.onClick}
key="query-estimate-btn"
tooltip={tooltip}

View File

@ -24,9 +24,9 @@ import Dialog from 'react-bootstrap-dialog';
import { t } from '@superset-ui/translation';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import Button from 'src/components/Button';
import { exploreChart } from '../../explore/exploreUtils';
import * as actions from '../actions/sqlLab';
import Button from '../../components/Button';
const propTypes = {
actions: PropTypes.object.isRequired,
@ -89,7 +89,7 @@ class ExploreCtasResultsButton extends React.PureComponent {
return (
<>
<Button
bsSize="small"
buttonSize="small"
onClick={this.onClick}
tooltip={t('Explore the result set in the data exploration view')}
>

View File

@ -25,11 +25,11 @@ import { Alert } from 'react-bootstrap';
import Dialog from 'react-bootstrap-dialog';
import { t } from '@superset-ui/translation';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import shortid from 'shortid';
import Button from 'src/components/Button';
import { exploreChart } from '../../explore/exploreUtils';
import * as actions from '../actions/sqlLab';
import Button from '../../components/Button';
const propTypes = {
actions: PropTypes.object.isRequired,
@ -213,7 +213,7 @@ class ExploreResultsButton extends React.PureComponent {
return (
<>
<Button
bsSize="small"
buttonSize="small"
onClick={this.onClick}
disabled={!allowsSubquery}
tooltip={t('Explore the result set in the data exploration view')}

View File

@ -17,13 +17,8 @@
* under the License.
*/
import React from 'react';
import {
Button,
FormGroup,
FormControl,
Overlay,
Popover,
} from 'react-bootstrap';
import { FormGroup, FormControl, Overlay, Popover } from 'react-bootstrap';
import Button from 'src/components/Button';
import { t } from '@superset-ui/translation';
import styled from '@superset-ui/style';
@ -119,8 +114,8 @@ export default class LimitControl extends React.PureComponent<
</FormGroup>
<div className="clearfix">
<Button
bsSize="small"
bsStyle="primary"
buttonSize="small"
buttonStyle="primary"
className="float-right ok m-l-5"
disabled={!isValid}
onClick={this.submitAndClose}
@ -128,7 +123,7 @@ export default class LimitControl extends React.PureComponent<
{t('Ok')}
</Button>
<Button
bsSize="small"
buttonSize="small"
className="float-right reset"
onClick={this.setValueAndClose.bind(
this,

View File

@ -18,7 +18,7 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'react-bootstrap';
import Button from 'src/components/Button';
import Select from 'src/components/Select';
import { t } from '@superset-ui/translation';
import { SupersetClient } from '@superset-ui/connection';
@ -273,8 +273,8 @@ class QuerySearch extends React.PureComponent {
/>
<Button
bsSize="small"
bsStyle="success"
buttonSize="small"
buttonStyle="success"
onClick={this.refreshQueries}
>
{t('Search')}

View File

@ -24,6 +24,7 @@ import { ProgressBar, Well } from 'react-bootstrap';
import Label from 'src/components/Label';
import { t } from '@superset-ui/translation';
import Button from 'src/components/Button';
import Link from '../../components/Link';
import ResultSet from './ResultSet';
import ModalTrigger from '../../components/ModalTrigger';
@ -99,31 +100,34 @@ class QueryTable extends React.PureComponent {
</div>
);
q.user = (
<button
className="btn btn-link btn-xs"
<Button
buttonSize="small"
buttonStyle="link"
onClick={this.props.onUserClicked.bind(this, q.userId)}
>
{q.user}
</button>
</Button>
);
q.db = (
<button
className="btn btn-link btn-xs"
<Button
buttonSize="small"
buttonStyle="link"
onClick={this.props.onDbClicked.bind(this, q.dbId)}
>
{q.db}
</button>
</Button>
);
q.started = moment(q.startDttm).format('HH:mm:ss');
q.querylink = (
<div style={{ width: '100px' }}>
<button
className="btn btn-link btn-xs"
<Button
buttonSize="small"
buttonStyle="link"
onClick={this.openQuery.bind(this, q.queryId)}
>
<i className="fa fa-external-link m-r-3" />
{t('Edit')}
</button>
</Button>
</div>
);
q.sql = (

View File

@ -17,7 +17,8 @@
* under the License.
*/
import React, { CSSProperties } from 'react';
import { Alert, Button, ButtonGroup, ProgressBar } from 'react-bootstrap';
import { Alert, ButtonGroup, ProgressBar } from 'react-bootstrap';
import Button from 'src/components/Button';
import shortid from 'shortid';
import { t } from '@superset-ui/translation';
@ -166,7 +167,7 @@ export default class ResultSet extends React.PureComponent<
)}
{this.props.csv && (
<Button
bsSize="small"
buttonSize="small"
href={`/superset/csv/${this.props.query.id}`}
>
<i className="fa fa-file-text-o" /> {t('.CSV')}
@ -177,7 +178,7 @@ export default class ResultSet extends React.PureComponent<
text={prepareCopyToClipboardTabularData(data)}
wrapped={false}
copyNode={
<Button bsSize="small">
<Button buttonSize="small">
<i className="fa fa-clipboard" /> {t('Clipboard')}
</Button>
}
@ -243,7 +244,7 @@ export default class ResultSet extends React.PureComponent<
] {t('was created')} &nbsp;
<ButtonGroup>
<Button
bsSize="small"
buttonSize="small"
className="m-r-5"
onClick={() => this.popSelectStar(tempSchema, tempTable)}
>
@ -296,9 +297,9 @@ export default class ResultSet extends React.PureComponent<
if (query.isDataPreview) {
return (
<Button
bsSize="sm"
buttonSize="sm"
className="fetch"
bsStyle="primary"
buttonStyle="primary"
onClick={() =>
this.reFetchQueryResults({
...query,
@ -312,9 +313,9 @@ export default class ResultSet extends React.PureComponent<
} else if (query.resultsKey) {
return (
<Button
bsSize="sm"
buttonSize="sm"
className="fetch"
bsStyle="primary"
buttonStyle="primary"
onClick={() => this.fetchResults(query)}
>
{t('Refetch Results')}
@ -336,7 +337,7 @@ export default class ResultSet extends React.PureComponent<
if (query.trackingUrl) {
trackingUrl = (
<Button
bsSize="small"
buttonSize="small"
onClick={() => query.trackingUrl && window.open(query.trackingUrl)}
>
{t('Track Job')}

View File

@ -19,7 +19,7 @@
import React from 'react';
import { t } from '@superset-ui/translation';
import Button, { ButtonProps } from '../../components/Button';
import Button, { ButtonProps } from 'src/components/Button';
const NO_OP = () => undefined;
@ -32,9 +32,6 @@ interface Props {
stopQuery: () => void;
sql: string;
}
const commonBtnStyle = {
width: '140px',
};
const RunQueryActionButton = ({
allowAsync = false,
@ -51,15 +48,14 @@ const RunQueryActionButton = ({
!!queryState && ['running', 'pending'].indexOf(queryState) > -1;
const commonBtnProps: ButtonProps = {
bsSize: 'small',
bsStyle: btnStyle,
buttonSize: 'small',
buttonStyle: btnStyle,
disabled: !dbId,
style: commonBtnStyle,
};
if (shouldShowStopBtn) {
return (
<Button {...commonBtnProps} onClick={stopQuery}>
<Button {...commonBtnProps} cta onClick={stopQuery}>
<i className="fa fa-stop" /> {t('Stop')}
</Button>
);
@ -67,6 +63,7 @@ const RunQueryActionButton = ({
return (
<Button
{...commonBtnProps}
cta
onClick={() => runQuery(true)}
key="run-async-btn"
tooltip={t('Run query asynchronously (Ctrl + ↵)')}
@ -79,6 +76,7 @@ const RunQueryActionButton = ({
return (
<Button
{...commonBtnProps}
cta
onClick={() => runQuery(false)}
key="run-btn"
tooltip={t('Run query synchronously (Ctrl + ↵)')}

View File

@ -135,7 +135,7 @@ class SaveQuery extends React.PureComponent {
<Col md={12}>
{isSaved && (
<Button
bsStyle="primary"
buttonStyle="primary"
onClick={this.onUpdate}
className="m-r-3"
>
@ -143,7 +143,7 @@ class SaveQuery extends React.PureComponent {
</Button>
)}
<Button
bsStyle={isSaved ? undefined : 'primary'}
buttonStyle={isSaved ? undefined : 'primary'}
onClick={this.onSave}
className="m-r-3"
>
@ -169,7 +169,7 @@ class SaveQuery extends React.PureComponent {
backdrop="static"
triggerNode={
<Button
bsSize="small"
buttonSize="small"
className="toggleSave"
onClick={this.toggleSave}
>

View File

@ -192,7 +192,7 @@ class ScheduleQueryButton extends React.PureComponent {
modalBody={this.renderModalBody()}
triggerNode={
<Button
bsSize="small"
buttonSize="small"
className="toggleSchedule"
onClick={this.toggleSchedule}
disabled={this.props.disabled}

View File

@ -22,7 +22,7 @@ import { Popover, OverlayTrigger } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import Button from '../../components/Button';
import Button from 'src/components/Button';
import CopyToClipboard from '../../components/CopyToClipboard';
import { storeQuery } from '../../utils/common';
import getClientErrorObject from '../../utils/getClientErrorObject';
@ -110,7 +110,7 @@ class ShareSqlLabQuery extends React.Component {
shouldUpdatePosition
overlay={this.renderPopover()}
>
<Button bsSize="small" className="toggleSave">
<Button buttonSize="small" className="toggleSave">
<i className="fa fa-share" /> {t('Share')}
</Button>
</OverlayTrigger>

View File

@ -398,7 +398,7 @@ class SqlEditor extends React.PureComponent {
<InputGroup.Button>
{this.props.database.allow_ctas && (
<Button
bsSize="small"
buttonSize="small"
disabled={this.state.ctas.length === 0}
onClick={this.createTableAs.bind(this)}
tooltip={ctasToolTip}
@ -408,7 +408,7 @@ class SqlEditor extends React.PureComponent {
)}
{this.props.database.allow_cvas && (
<Button
bsSize="small"
buttonSize="small"
disabled={this.state.ctas.length === 0}
onClick={this.createViewAs.bind(this)}
tooltip={cvasToolTip}

View File

@ -18,7 +18,7 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'react-bootstrap';
import Button from 'src/components/Button';
import { t } from '@superset-ui/translation';
import TableElement from './TableElement';
import TableSelector from '../../components/TableSelector';
@ -140,7 +140,11 @@ export default class SqlEditorLeftBar extends React.PureComponent {
</div>
</div>
{shouldShowReset && (
<Button bsSize="small" bsStyle="danger" onClick={this.resetState}>
<Button
buttonSize="small"
buttonStyle="danger"
onClick={this.resetState}
>
<i className="fa fa-bomb" /> {t('Reset State')}
</Button>
)}

View File

@ -29,8 +29,8 @@ import 'brace/theme/textmate';
import { t } from '@superset-ui/translation';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import Button from 'src/components/Button';
import ModalTrigger from '../../components/ModalTrigger';
import Button from '../../components/Button';
const propTypes = {
onChange: PropTypes.func,

View File

@ -17,7 +17,8 @@
* under the License.
*/
import React from 'react';
import { Button, Panel } from 'react-bootstrap';
import { Panel } from 'react-bootstrap';
import Button from 'src/components/Button';
import Select from 'src/components/Select';
import { t } from '@superset-ui/translation';
@ -142,7 +143,7 @@ export default class AddSliceContainer extends React.PureComponent<
<br />
<hr />
<Button
bsStyle="primary"
buttonStyle="primary"
disabled={this.isBtnDisabled()}
onClick={this.gotoSlice}
>

View File

@ -20,7 +20,7 @@ import React from 'react';
import styled from '@superset-ui/style';
import { Modal as BaseModal } from 'src/common/components';
import { t } from '@superset-ui/translation';
import Button from 'src/views/CRUD/data/dataset/Button';
import Button from 'src/components/Button';
interface ModalProps {
className?: string;

View File

@ -25,13 +25,16 @@ export default {
title: 'Button',
component: Button,
decorators: [withKnobs],
excludeStories: /.*Knob$/,
};
const bsStyleKnob = {
export const buttonStyleKnob = {
label: 'Types',
options: {
Primary: 'primary',
Secondary: 'secondary',
Tertiary: 'tertiary',
Dashed: 'dashed',
Danger: 'danger',
Warning: 'warning',
Success: 'success',
@ -42,17 +45,18 @@ const bsStyleKnob = {
defaultValue: null,
// groupId: 'ButtonType',
};
const bsSizeKnob = {
export const buttonSizeKnob = {
label: 'Sizes',
options: {
XS: 'xsmall',
S: 'small',
M: 'medium',
Default: null,
L: 'large',
None: null,
},
defaultValue: null,
};
// TODO remove the use of these in the codebase where they're not necessary
// const classKnob = {
// label: 'Known Classes',
@ -62,7 +66,6 @@ const bsSizeKnob = {
// Reset: 'reset',
// Fetch: 'fetch',
// Query: 'query',
// saveBtn: 'save-btn',
// MR3: 'm-r-3',
// cancelQuery: 'cancelQuery',
// toggleSave: 'toggleSave',
@ -101,43 +104,43 @@ const hrefKnob = {
export const ButtonGallery = () => (
<>
{Object.values(bsSizeKnob.options)
.filter(a => a)
.map(size => (
<div>
<h4>{size}</h4>
{Object.values(bsStyleKnob.options)
.filter(o => o)
.map(style => (
<Button
disabled={boolean('Disabled', false)}
bsStyle={style}
bsSize={size}
onClick={action('clicked')}
style={{ marginRight: 5 }}
>
{style}
</Button>
))}
</div>
))}
{Object.entries(buttonSizeKnob.options).map(([name, size]) => (
<div key={size}>
<h4>{name}</h4>
{Object.values(buttonStyleKnob.options)
.filter(o => o)
.map(style => (
<Button
disabled={boolean('Disabled', false)}
cta={boolean('CTA', false)}
buttonStyle={style}
buttonSize={size}
onClick={action('clicked')}
key={`${style}_${size}`}
>
{style}
</Button>
))}
</div>
))}
</>
);
export const InteractiveButton = () => (
<Button
disabled={boolean('Disabled', false)}
bsStyle={select(
bsStyleKnob.label,
bsStyleKnob.options,
bsStyleKnob.defaultValue,
bsStyleKnob.groupId,
cta={boolean('CTA', false)}
buttonStyle={select(
buttonStyleKnob.label,
buttonStyleKnob.options,
buttonStyleKnob.defaultValue,
buttonStyleKnob.groupId,
)}
bsSize={select(
bsSizeKnob.label,
bsSizeKnob.options,
bsSizeKnob.defaultValue,
bsSizeKnob.groupId,
size={select(
buttonSizeKnob.label,
buttonSizeKnob.options,
buttonSizeKnob.defaultValue,
buttonSizeKnob.groupId,
)}
onClick={action('clicked')}
type={select(

View File

@ -0,0 +1,68 @@
/**
* 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 '.';
import {
ButtonGallery,
buttonSizeKnob,
buttonStyleKnob,
} from './Button.stories';
describe('Button', () => {
let wrapper: ReactWrapper;
// test the basic component
it('renders the base component', () => {
expect(React.isValidElement(<Button />)).toBe(true);
});
it('works with an onClick handler', () => {
const mockAction = jest.fn();
wrapper = mount(<Button onClick={mockAction} />);
wrapper.find('Button').first().simulate('click');
expect(mockAction).toHaveBeenCalled();
});
it('does not handle onClicks when disabled', () => {
const mockAction = jest.fn();
wrapper = mount(<Button onClick={mockAction} disabled />);
wrapper.find('Button').first().simulate('click');
expect(mockAction).toHaveBeenCalledTimes(0);
});
// test stories from the storybook!
it('All the sorybook gallery variants mount', () => {
wrapper = mount(<ButtonGallery />);
const permutationCount =
Object.values(buttonStyleKnob.options).filter(o => o).length *
Object.values(buttonSizeKnob.options).length;
expect(wrapper.find(Button).length).toEqual(permutationCount);
});
// test things NOT in the storybook!
it('renders custom button styles without melting', () => {
wrapper = mount(<Button buttonStyle="foobar" />);
expect(wrapper.find('Button.btn-foobar')).toHaveLength(1);
});
});

View File

@ -18,6 +18,8 @@
*/
import React from 'react';
import { kebabCase } from 'lodash';
import { mix } from 'polished';
import cx from 'classnames';
import {
Button as BootstrapButton,
Tooltip,
@ -40,52 +42,201 @@ export interface ButtonProps {
placement?: string;
onClick?: OnClickHandler;
disabled?: boolean;
bsStyle?: string;
buttonStyle?: string;
btnStyles?: string;
bsSize?: BootstrapButton.ButtonProps['bsSize'];
buttonSize?: BootstrapButton.ButtonProps['bsSize'];
style?: BootstrapButton.ButtonProps['style'];
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
cta?: boolean;
}
const BUTTON_WRAPPER_STYLE = { display: 'inline-block', cursor: 'not-allowed' };
const SupersetButton = styled(BootstrapButton)`
&.supersetButton {
border-radius: ${({ theme }) => theme.borderRadius}px;
border: none;
color: ${({ theme }) => theme.colors.secondary.light5};
font-size: ${({ theme }) => theme.typography.sizes.s}px;
font-weight: ${({ theme }) => theme.typography.weights.bold};
&: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;
}
i {
padding: 0 ${({ theme }) => theme.gridUnit * 2}px 0 0;
}
/* 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.light1};
}
}
/* big Call to Action buttons */
&.cta {
min-width: ${({ theme }) => theme.gridUnit * 36}px;
min-height: ${({ theme }) => theme.gridUnit * 8}px;
text-transform: uppercase;
margin-left: ${({ theme }) => theme.gridUnit * 4}px;
&:first-of-type {
margin-left: 0;
}
i {
padding: 0 ${({ theme }) => theme.gridUnit * 2}px 0 0;
}
&.primary {
background-color: ${({ theme }) => theme.colors.primary.base};
}
&.secondary {
color: ${({ theme }) => theme.colors.primary.base};
background-color: ${({ theme }) => theme.colors.primary.light4};
}
&.danger {
background-color: ${({ theme }) => theme.colors.error.base};
}
}
`;
export default function Button(props: ButtonProps) {
const buttonProps = {
...props,
bsSize: props.bsSize || 'sm',
bsSize: props.buttonSize,
placement: props.placement || 'top',
};
const tooltip = props.tooltip;
@ -100,17 +251,40 @@ export default function Button(props: ButtonProps) {
buttonProps.style = { pointerEvents: 'none' };
}
let button = (
<SupersetButton {...buttonProps}>{props.children}</SupersetButton>
);
const officialBootstrapStyles = [
'success',
'warning',
'danger',
'info',
'default',
'primary',
];
const whittledProps = { ...buttonProps };
delete whittledProps.dropdownItems;
const transformedProps = {
...buttonProps,
bsStyle: officialBootstrapStyles.includes(props.buttonStyle || '')
? props.buttonStyle
: 'default',
className: cx(props.className, {
cta: !!buttonProps.cta,
[`btn-${props.buttonStyle}`]: !officialBootstrapStyles.includes(
props.buttonStyle || '',
),
}),
};
delete transformedProps.dropdownItems;
delete transformedProps.buttonSize;
delete transformedProps.buttonStyle;
delete transformedProps.cta;
let button = (
<SupersetButton {...transformedProps}>{props.children}</SupersetButton>
);
if (dropdownItems) {
button = (
<div style={BUTTON_WRAPPER_STYLE}>
<SupersetButton {...whittledProps} data-toggle="dropdown">
<SupersetButton {...transformedProps} data-toggle="dropdown">
{props.children}
</SupersetButton>
<ul className="dropdown-menu">

View File

@ -21,7 +21,7 @@ import { Modal } from 'react-bootstrap';
import { styled, supersetTheme } from '@superset-ui/style';
import { t } from '@superset-ui/translation';
import { noOp } from 'src/utils/common';
import Button from 'src/views/CRUD/data/dataset/Button';
import Button from 'src/components/Button';
import Icon from '../Icon';
import { ErrorLevel, ErrorSource } from './types';
@ -190,7 +190,11 @@ export default function ErrorAlert({
copyNode={<Button onClick={noOp}>{t('Copy Message')}</Button>}
/>
)}
<Button bsStyle="primary" onClick={() => setIsModalOpen(false)}>
<Button
cta
buttonStyle="primary"
onClick={() => setIsModalOpen(false)}
>
{t('Close')}
</Button>
</Modal.Footer>

View File

@ -39,12 +39,12 @@ export default function ExpandableList({ items, display = 3 }: Props) {
const showMoreAction = items.length > display;
const lessAction = (
<Button bsStyle="link" bsSize="xsmall" onClick={toggleShowingAll}>
<Button buttonStyle="link" buttonSize="xsmall" onClick={toggleShowingAll}>
less
</Button>
);
const moreAction = (
<Button bsStyle="link" bsSize="xsmall" onClick={toggleShowingAll}>
<Button buttonStyle="link" buttonSize="xsmall" onClick={toggleShowingAll}>
{items.length - itemsToDisplay.length} more
</Button>
);

View File

@ -25,7 +25,7 @@ export default {
title: 'Label',
component: Label,
decorators: [withKnobs],
excludeStories: ['bsStyleKnob'],
excludeStories: /.*Knob$/,
};
export const bsStyleKnob = {

View File

@ -311,11 +311,12 @@ function ListView<T extends object = any>({
<Button
data-test="bulk-select-action"
key={action.key}
className={cx('supersetButton', {
className={cx({
danger: action.type === 'danger',
primary: action.type === 'primary',
secondary: action.type === 'secondary',
})}
cta
onClick={() =>
action.onSelect(selectedFlatRows.map(r => r.original))
}

View File

@ -129,6 +129,14 @@ const StyledHeader = styled.header`
margin-bottom: 8px;
border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
}
.navbar-right {
display: flex;
align-items: center;
.dropdown:first-of-type {
/* this is the "+ NEW" button. Sweep this up when it's replaced */
margin-right: ${({ theme }) => theme.gridUnit * 2}px;
}
}
`;
export function Menu({

View File

@ -17,15 +17,9 @@
* under the License.
*/
import React from 'react';
import styled from '@superset-ui/style';
import { t } from '@superset-ui/translation';
import Button, { DropdownItemProps } from '../Button';
const StyledButton = styled(Button)`
margin-top: 12px;
margin-right: 30px;
`;
const dropdownItems: DropdownItemProps[] = [
{
label: t('SQL Query'),
@ -47,12 +41,9 @@ const dropdownItems: DropdownItemProps[] = [
export default function NewMenu() {
return (
<li className="dropdown">
<StyledButton
className="dropdown-toggle btn btn-sm btn-primary"
dropdownItems={dropdownItems}
>
<Button buttonStyle="primary" dropdownItems={dropdownItems}>
<i className="fa fa-plus" /> New
</StyledButton>
</Button>
</li>
);
}

View File

@ -26,12 +26,8 @@ const StyledHeader = styled.header`
font-weight: ${({ theme }) => theme.typography.weights.bold};
}
.navbar-right {
.supersetButton {
margin: ${({ theme }) =>
`${theme.gridUnit * 2}px ${theme.gridUnit * 4}px ${
theme.gridUnit * 2
}px 0`};
}
padding: 8px 0;
margin-right: 0;
}
.navbar-nav {
li {
@ -94,16 +90,18 @@ const SubMenu: React.FunctionComponent<SubMenuProps> = props => {
<Nav className="navbar-right">
{props.secondaryButton && (
<Button
className="supersetButton secondary"
buttonStyle="secondary"
onClick={props.secondaryButton.onClick}
cta
>
{props.secondaryButton.name}
</Button>
)}
{props.primaryButton && (
<Button
className="supersetButton primary"
buttonStyle="primary"
onClick={props.primaryButton.onClick}
cta
>
{props.primaryButton.name}
</Button>

View File

@ -20,7 +20,7 @@ import React from 'react';
import styled from '@superset-ui/style';
import { Modal as BaseModal } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import Button from 'src/views/CRUD/data/dataset/Button';
import Button from 'src/components/Button';
interface ModalProps {
children: React.ReactNode;
@ -54,9 +54,6 @@ const StyledModal = styled(BaseModal)`
.modal-footer {
border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
padding: 16px;
.btn + .btn {
margin-left: 8px;
}
}
`;
@ -86,11 +83,14 @@ export default function Modal({
<BaseModal.Body>{children}</BaseModal.Body>
<BaseModal.Footer>
<span className="float-right">
<Button onClick={onHide}>{t('Cancel')}</Button>
<Button onClick={onHide} cta>
{t('Cancel')}
</Button>
<Button
bsStyle={primaryButtonType}
buttonStyle={primaryButtonType}
disabled={disablePrimaryButton}
onClick={onHandledPrimaryAction}
cta
>
{primaryButtonName}
</Button>

View File

@ -19,9 +19,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Modal, MenuItem } from 'react-bootstrap';
import cx from 'classnames';
import Button from './Button';
import Button from 'src/components/Button';
const propTypes = {
dialogClassName: PropTypes.string,
@ -96,9 +95,6 @@ export default class ModalTrigger extends React.Component {
}
render() {
const classNames = cx({
'btn btn-default btn-sm': this.props.isButton,
});
if (this.props.isButton) {
return (
<>
@ -123,7 +119,7 @@ export default class ModalTrigger extends React.Component {
/* eslint-disable jsx-a11y/interactive-supports-focus */
return (
<>
<span className={classNames} onClick={this.open} role="button">
<span onClick={this.open} role="button">
{this.props.triggerNode}
</span>
{this.renderModal()}

View File

@ -53,7 +53,7 @@ class RefreshChartOverlay extends React.PureComponent<Props> {
<Button
className="refresh-btn"
onClick={this.props.onQuery}
bsStyle="primary"
buttonStyle="primary"
>
{t('Run Query')}
</Button>

View File

@ -18,7 +18,7 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'react-bootstrap';
import Button from 'src/components/Button';
import { t } from '@superset-ui/translation';
import ModalTrigger from '../../components/ModalTrigger';
@ -66,7 +66,7 @@ export default class DeleteComponentModal extends React.PureComponent {
</div>
<div className="dashboard-modal-actions-container">
<Button onClick={this.close}>{t('Cancel')}</Button>
<Button bsStyle="primary" onClick={this.deleteTab}>
<Button buttonStyle="primary" onClick={this.deleteTab}>
{t('Delete')}
</Button>
</div>

View File

@ -26,10 +26,10 @@ import { CategoricalColorNamespace } from '@superset-ui/color';
import { t } from '@superset-ui/translation';
import Icon from 'src/components/Icon';
import Button from 'src/components/Button';
import HeaderActionsDropdown from './HeaderActionsDropdown';
import EditableTitle from '../../components/EditableTitle';
import Button from '../../components/Button';
import FaveStar from '../../components/FaveStar';
import PublishedStatus from './PublishedStatus';
import UndoRedoKeylisteners from './UndoRedoKeylisteners';
@ -390,36 +390,40 @@ class Header extends React.PureComponent {
<>
<ButtonGroup className="m-r-5">
<Button
bsSize="small"
buttonSize="small"
onClick={onUndo}
disabled={undoLength < 1}
bsStyle={this.state.emphasizeUndo ? 'primary' : undefined}
buttonStyle={
this.state.emphasizeUndo ? 'primary' : undefined
}
>
<i title="Undo" className="undo-action fa fa-reply" />
&nbsp;
</Button>
<Button
bsSize="small"
buttonSize="small"
onClick={onRedo}
disabled={redoLength < 1}
bsStyle={this.state.emphasizeRedo ? 'primary' : undefined}
buttonStyle={
this.state.emphasizeRedo ? 'primary' : undefined
}
>
&nbsp;
<i title="Redo" className="redo-action fa fa-share" />
</Button>
</ButtonGroup>
<Button
bsSize="small"
buttonSize="small"
className="m-r-5"
onClick={this.constructor.discardChanges}
bsStyle="default"
buttonStyle="default"
>
{t('Discard Changes')}
</Button>
<Button
bsSize="small"
buttonSize="small"
disabled={!hasUnsavedChanges}
bsStyle="primary"
buttonStyle="primary"
onClick={this.overwriteDashboard}
>
{t('Save')}

View File

@ -18,7 +18,8 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Row, Col, Button, Modal, FormControl } from 'react-bootstrap';
import { Row, Col, Modal, FormControl } from 'react-bootstrap';
import Button from 'src/components/Button';
import Dialog from 'react-bootstrap-dialog';
import { AsyncSelect } from 'src/components/Select';
import AceEditor from 'react-ace';
@ -288,11 +289,7 @@ class PropertiesModal extends React.PureComponent {
<Row>
<Col md={12}>
<h3 style={{ marginTop: '1em' }}>
<button
type="button"
className="text-button"
onClick={this.toggleAdvanced}
>
<Button buttonStyle="link" onClick={this.toggleAdvanced}>
<i
className={`fa fa-angle-${
isAdvancedOpen ? 'down' : 'right'
@ -300,7 +297,7 @@ class PropertiesModal extends React.PureComponent {
style={{ minWidth: '1em' }}
/>
{t('Advanced')}
</button>
</Button>
</h3>
{isAdvancedOpen && (
<>
@ -332,14 +329,15 @@ class PropertiesModal extends React.PureComponent {
<span className="float-right">
<Button
type="submit"
bsSize="sm"
bsStyle="primary"
buttonSize="sm"
buttonStyle="primary"
className="m-r-5"
disabled={errors.length > 0}
cta
>
{saveLabel}
</Button>
<Button type="button" bsSize="sm" onClick={onHide}>
<Button type="button" buttonSize="sm" onClick={onHide} cta>
{t('Cancel')}
</Button>
<Dialog

View File

@ -19,7 +19,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { t } from '@superset-ui/translation';
import TooltipWrapper from '../../components/TooltipWrapper';
import TooltipWrapper from 'src/components/TooltipWrapper';
import Label from 'src/components/Label';
const propTypes = {
dashboardId: PropTypes.number.isRequired,
@ -43,14 +44,6 @@ const publishedTooltip = t(
'This dashboard is published. Click to make it a draft.',
);
const divStyle = {
border: '1px dotted black',
backgroundColor: '#F9F9F9',
padding: '3px 7px 3px 7px',
fontFamily: 'Monospace',
fontSize: '16px',
};
export default class PublishedStatus extends React.Component {
componentDidMount() {
this.togglePublished = this.togglePublished.bind(this);
@ -71,14 +64,13 @@ export default class PublishedStatus extends React.Component {
placement="bottom"
tooltip={draftButtonTooltip}
>
<button
style={divStyle}
<Label
onClick={() => {
this.togglePublished();
}}
>
Draft
</button>
</Label>
</TooltipWrapper>
);
}
@ -88,7 +80,7 @@ export default class PublishedStatus extends React.Component {
placement="bottom"
tooltip={draftDivTooltip}
>
<div style={divStyle}>Draft</div>
<Label>Draft</Label>
</TooltipWrapper>
);
}
@ -101,14 +93,13 @@ export default class PublishedStatus extends React.Component {
placement="bottom"
tooltip={publishedTooltip}
>
<button
style={divStyle}
<Label
onClick={() => {
this.togglePublished();
}}
>
Published
</button>
</Label>
</TooltipWrapper>
);
}

View File

@ -20,7 +20,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import Select from 'src/components/Select';
import { t } from '@superset-ui/translation';
import { Alert, Button } from 'react-bootstrap';
import { Alert } from 'react-bootstrap';
import Button from 'src/components/Button';
import ModalTrigger from 'src/components/ModalTrigger';
import FormLabel from 'src/components/FormLabel';
@ -116,10 +117,10 @@ class RefreshIntervalModal extends React.PureComponent {
}
modalFooter={
<>
<Button bsStyle="primary" bsSize="sm" onClick={this.onSave}>
<Button buttonStyle="primary" buttonSize="sm" onClick={this.onSave}>
{editMode ? t('Save') : t('Save for this session')}
</Button>
<Button onClick={this.onCancel} bsSize="sm">
<Button onClick={this.onCancel} buttonSize="sm">
{t('Cancel')}
</Button>
</>

View File

@ -19,7 +19,8 @@
/* eslint-env browser */
import React from 'react';
import PropTypes from 'prop-types';
import { Button, FormControl, FormGroup, Radio } from 'react-bootstrap';
import { FormControl, FormGroup, Radio } from 'react-bootstrap';
import Button from 'src/components/Button';
import { CategoricalColorNamespace } from '@superset-ui/color';
import { t } from '@superset-ui/translation';
@ -191,7 +192,7 @@ class SaveModal extends React.PureComponent {
}
modalFooter={
<div>
<Button bsStyle="primary" onClick={this.saveDashboard}>
<Button buttonStyle="primary" onClick={this.saveDashboard}>
{t('Save')}
</Button>
</div>

View File

@ -19,7 +19,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { Button } from 'react-bootstrap';
import Button from 'src/components/Button';
import { t } from '@superset-ui/translation';
import buildFilterScopeTreeEntry from '../../util/buildFilterScopeTreeEntry';
@ -513,11 +513,11 @@ export default class FilterScopeSelector extends React.PureComponent {
</div>
<div className="dashboard-modal-actions-container">
<Button bsSize="sm" onClick={this.onClose}>
<Button buttonSize="sm" onClick={this.onClose}>
{t('Close')}
</Button>
{showSelector && (
<Button bsSize="sm" bsStyle="primary" onClick={this.onSave}>
<Button buttonSize="sm" buttonStyle="primary" onClick={this.onSave}>
{t('Save')}
</Button>
)}

View File

@ -41,14 +41,3 @@
padding-left: 8px;
font-size: @font-size-m;
}
.text-button {
outline: none;
border: none;
margin: 0;
padding: 0;
background: none;
text-decoration: none;
font-size: inherit;
font-weight: inherit;
}

View File

@ -784,7 +784,7 @@ export class DatasourceEditor extends React.PureComponent {
}
/>
<Button
bsStyle="primary"
buttonStyle="primary"
onClick={this.syncMetadata}
className="sync-from-source"
disabled={!!datasource.sql}

View File

@ -17,7 +17,8 @@
* under the License.
*/
import React, { FunctionComponent, useState, useRef } from 'react';
import { Alert, Button, Modal } from 'react-bootstrap';
import { Alert, Modal } from 'react-bootstrap';
import Button from 'src/components/Button';
// @ts-ignore
import Dialog from 'react-bootstrap-dialog';
import { t } from '@superset-ui/translation';
@ -158,8 +159,8 @@ const DatasourceModal: FunctionComponent<DatasourceModalProps> = ({
<Modal.Footer>
<span className="float-left">
<Button
bsSize="sm"
bsStyle="default"
buttonSize="sm"
buttonStyle="default"
target="_blank"
href={currentDatasource.edit_url || currentDatasource.url}
>
@ -169,15 +170,16 @@ const DatasourceModal: FunctionComponent<DatasourceModalProps> = ({
<span className="float-right">
<Button
bsSize="sm"
bsStyle="primary"
buttonSize="sm"
buttonStyle="primary"
className="m-r-5"
data-test="datasource-modal-save"
onClick={onClickSave}
disabled={errors.length > 0}
>
{t('Save')}
</Button>
<Button bsSize="sm" onClick={onHide}>
<Button buttonSize="sm" onClick={onHide}>
{t('Cancel')}
</Button>
<Dialog ref={dialog} />

View File

@ -18,7 +18,9 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Popover, Tab, Tabs } from 'react-bootstrap';
import { Popover, Tab, Tabs } from 'react-bootstrap';
import Button from 'src/components/Button';
import { ThemeProvider } from '@superset-ui/style';
import columnType from '../propTypes/columnType';
import adhocMetricType from '../propTypes/adhocMetricType';
@ -40,6 +42,7 @@ const propTypes = {
).isRequired,
datasource: PropTypes.object,
partitionColumn: PropTypes.string,
theme: PropTypes.object,
};
const startingWidth = 300;
@ -119,6 +122,7 @@ export default class AdhocFilterEditPopover extends React.Component {
onResize,
datasource,
partitionColumn,
theme,
...popoverProps
} = this.props;
@ -129,68 +133,74 @@ export default class AdhocFilterEditPopover extends React.Component {
return (
<Popover id="filter-edit-popover" {...popoverProps}>
<Tabs
id="adhoc-filter-edit-tabs"
defaultActiveKey={adhocFilter.expressionType}
className="adhoc-filter-edit-tabs"
style={{ height: this.state.height, width: this.state.width }}
>
<Tab
className="adhoc-filter-edit-tab"
eventKey={EXPRESSION_TYPES.SIMPLE}
title="Simple"
<ThemeProvider theme={theme}>
<Tabs
id="adhoc-filter-edit-tabs"
defaultActiveKey={adhocFilter.expressionType}
className="adhoc-filter-edit-tabs"
style={{ height: this.state.height, width: this.state.width }}
>
<AdhocFilterEditPopoverSimpleTabContent
adhocFilter={this.state.adhocFilter}
onChange={this.onAdhocFilterChange}
options={options}
datasource={datasource}
onHeightChange={this.adjustHeight}
partitionColumn={partitionColumn}
/>
</Tab>
<Tab
className="adhoc-filter-edit-tab"
eventKey={EXPRESSION_TYPES.SQL}
title="Custom SQL"
>
{!this.props.datasource ||
this.props.datasource.type !== 'druid' ? (
<AdhocFilterEditPopoverSqlTabContent
<Tab
className="adhoc-filter-edit-tab"
eventKey={EXPRESSION_TYPES.SIMPLE}
title="Simple"
>
<AdhocFilterEditPopoverSimpleTabContent
adhocFilter={this.state.adhocFilter}
onChange={this.onAdhocFilterChange}
options={this.props.options}
height={this.state.height}
options={options}
datasource={datasource}
onHeightChange={this.adjustHeight}
partitionColumn={partitionColumn}
/>
) : (
<div className="custom-sql-disabled-message">
Custom SQL Filters are not available on druid datasources
</div>
)}
</Tab>
</Tabs>
<div>
<Button
disabled={!stateIsValid}
bsStyle={hasUnsavedChanges && stateIsValid ? 'primary' : 'default'}
bsSize="small"
className="m-r-5"
onClick={this.onSave}
>
Save
</Button>
<Button bsSize="small" onClick={this.props.onClose}>
Close
</Button>
<i
role="button"
tabIndex={0}
onMouseDown={this.onDragDown}
className="fa fa-expand edit-popover-resize text-muted"
/>
</div>
</Tab>
<Tab
className="adhoc-filter-edit-tab"
eventKey={EXPRESSION_TYPES.SQL}
title="Custom SQL"
>
{!this.props.datasource ||
this.props.datasource.type !== 'druid' ? (
<AdhocFilterEditPopoverSqlTabContent
adhocFilter={this.state.adhocFilter}
onChange={this.onAdhocFilterChange}
options={this.props.options}
height={this.state.height}
/>
) : (
<div className="custom-sql-disabled-message">
Custom SQL Filters are not available on druid datasources
</div>
)}
</Tab>
</Tabs>
<div>
<Button
disabled={!stateIsValid}
buttonStyle={
hasUnsavedChanges && stateIsValid ? 'primary' : 'default'
}
buttonSize="small"
className="m-r-5"
onClick={this.onSave}
cta
>
Save
</Button>
<Button buttonSize="small" onClick={this.props.onClose} cta>
Close
</Button>
<i
role="button"
tabIndex={0}
onMouseDown={this.onDragDown}
className="fa fa-expand edit-popover-resize text-muted"
/>
</div>
</ThemeProvider>
</Popover>
);
}
}
AdhocFilterEditPopover.propTypes = propTypes;

View File

@ -21,6 +21,7 @@ import PropTypes from 'prop-types';
import { OverlayTrigger } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import { withTheme } from '@superset-ui/style';
import Label from 'src/components/Label';
import AdhocFilterEditPopover from './AdhocFilterEditPopover';
@ -41,8 +42,7 @@ const propTypes = {
datasource: PropTypes.object,
partitionColumn: PropTypes.string,
};
export default class AdhocFilterOption extends React.PureComponent {
class AdhocFilterOption extends React.PureComponent {
constructor(props) {
super(props);
this.closeFilterEditOverlay = this.closeFilterEditOverlay.bind(this);
@ -73,7 +73,7 @@ export default class AdhocFilterOption extends React.PureComponent {
}
render() {
const { adhocFilter } = this.props;
const { adhocFilter, theme } = this.props;
const overlay = (
<AdhocFilterEditPopover
onResize={this.onPopoverResize}
@ -83,6 +83,7 @@ export default class AdhocFilterOption extends React.PureComponent {
options={this.props.options}
datasource={this.props.datasource}
partitionColumn={this.props.partitionColumn}
theme={theme}
/>
);
return (
@ -123,4 +124,7 @@ export default class AdhocFilterOption extends React.PureComponent {
);
}
}
export default withTheme(AdhocFilterOption);
AdhocFilterOption.propTypes = propTypes;

View File

@ -18,7 +18,8 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Button, FormGroup, Popover, Tab, Tabs } from 'react-bootstrap';
import { FormGroup, Popover, Tab, Tabs } from 'react-bootstrap';
import Button from 'src/components/Button';
import Select from 'src/components/Select';
import ace from 'brace';
import AceEditor from 'react-ace';
@ -27,6 +28,7 @@ import 'brace/theme/github';
import 'brace/ext/language_tools';
import { t } from '@superset-ui/translation';
import { ColumnOption } from '@superset-ui/chart-controls';
import { ThemeProvider } from '@superset-ui/style';
import FormLabel from 'src/components/FormLabel';
@ -45,6 +47,7 @@ const propTypes = {
onResize: PropTypes.func.isRequired,
columns: PropTypes.arrayOf(columnType),
datasourceType: PropTypes.string,
theme: PropTypes.object,
};
const defaultProps = {
@ -192,6 +195,7 @@ export default class AdhocMetricEditPopover extends React.Component {
onClose,
onResize,
datasourceType,
theme,
...popoverProps
} = this.props;
@ -232,98 +236,104 @@ export default class AdhocMetricEditPopover extends React.Component {
const hasUnsavedChanges = !adhocMetric.equals(propsAdhocMetric);
return (
<Popover id="metrics-edit-popover" title={popoverTitle} {...popoverProps}>
<Tabs
id="adhoc-metric-edit-tabs"
defaultActiveKey={adhocMetric.expressionType}
className="adhoc-metric-edit-tabs"
style={{ height: this.state.height, width: this.state.width }}
onSelect={this.refreshAceEditor}
animation={false}
>
<Tab
className="adhoc-metric-edit-tab"
eventKey={EXPRESSION_TYPES.SIMPLE}
title="Simple"
<ThemeProvider theme={theme}>
<Tabs
id="adhoc-metric-edit-tabs"
defaultActiveKey={adhocMetric.expressionType}
className="adhoc-metric-edit-tabs"
style={{ height: this.state.height, width: this.state.width }}
onSelect={this.refreshAceEditor}
animation={false}
>
<FormGroup>
<FormLabel>
<strong>column</strong>
</FormLabel>
<Select
name="select-column"
{...this.selectProps}
{...columnSelectProps}
/>
</FormGroup>
<FormGroup>
<FormLabel>
<strong>aggregate</strong>
</FormLabel>
<Select
name="select-aggregate"
{...this.selectProps}
{...aggregateSelectProps}
autoFocus
/>
</FormGroup>
</Tab>
<Tab
className="adhoc-metric-edit-tab"
eventKey={EXPRESSION_TYPES.SQL}
title="Custom SQL"
data-test="adhoc-metric-edit-tab#custom"
>
{this.props.datasourceType !== 'druid' ? (
<Tab
className="adhoc-metric-edit-tab"
eventKey={EXPRESSION_TYPES.SIMPLE}
title="Simple"
>
<FormGroup>
<AceEditor
ref={this.handleAceEditorRef}
mode="sql"
theme="github"
height={`${this.state.height - 43}px`}
onChange={this.onSqlExpressionChange}
width="100%"
showGutter={false}
value={
adhocMetric.sqlExpression || adhocMetric.translateToSql()
}
editorProps={{ $blockScrolling: true }}
enableLiveAutocompletion
className="adhoc-filter-sql-editor"
wrapEnabled
<FormLabel>
<strong>column</strong>
</FormLabel>
<Select
name="select-column"
{...this.selectProps}
{...columnSelectProps}
/>
</FormGroup>
) : (
<div className="custom-sql-disabled-message">
Custom SQL Metrics are not available on druid datasources
</div>
)}
</Tab>
</Tabs>
<div>
<Button
disabled={!stateIsValid}
bsStyle={hasUnsavedChanges && stateIsValid ? 'primary' : 'default'}
bsSize="small"
className="m-r-5"
data-test="AdhocMetricEdit#save"
onClick={this.onSave}
>
Save
</Button>
<Button
bsSize="small"
onClick={this.props.onClose}
data-test="AdhocMetricEdit#cancel"
>
Close
</Button>
<i
role="button"
tabIndex={0}
onMouseDown={this.onDragDown}
className="fa fa-expand edit-popover-resize text-muted"
/>
</div>
<FormGroup>
<FormLabel>
<strong>aggregate</strong>
</FormLabel>
<Select
name="select-aggregate"
{...this.selectProps}
{...aggregateSelectProps}
autoFocus
/>
</FormGroup>
</Tab>
<Tab
className="adhoc-metric-edit-tab"
eventKey={EXPRESSION_TYPES.SQL}
title="Custom SQL"
data-test="adhoc-metric-edit-tab#custom"
>
{this.props.datasourceType !== 'druid' ? (
<FormGroup>
<AceEditor
ref={this.handleAceEditorRef}
mode="sql"
theme="github"
height={`${this.state.height - 43}px`}
onChange={this.onSqlExpressionChange}
width="100%"
showGutter={false}
value={
adhocMetric.sqlExpression || adhocMetric.translateToSql()
}
editorProps={{ $blockScrolling: true }}
enableLiveAutocompletion
className="adhoc-filter-sql-editor"
wrapEnabled
/>
</FormGroup>
) : (
<div className="custom-sql-disabled-message">
Custom SQL Metrics are not available on druid datasources
</div>
)}
</Tab>
</Tabs>
<div>
<Button
disabled={!stateIsValid}
buttonStyle={
hasUnsavedChanges && stateIsValid ? 'primary' : 'default'
}
buttonSize="small"
className="m-r-5"
data-test="AdhocMetricEdit#save"
onClick={this.onSave}
cta
>
Save
</Button>
<Button
buttonSize="small"
onClick={this.props.onClose}
data-test="AdhocMetricEdit#cancel"
cta
>
Close
</Button>
<i
role="button"
tabIndex={0}
onMouseDown={this.onDragDown}
className="fa fa-expand edit-popover-resize text-muted"
/>
</div>
</ThemeProvider>
</Popover>
);
}

View File

@ -19,6 +19,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { OverlayTrigger } from 'react-bootstrap';
import { withTheme } from '@superset-ui/style';
import Label from 'src/components/Label';
import AdhocMetricEditPopover from './AdhocMetricEditPopover';
@ -33,7 +34,7 @@ const propTypes = {
datasourceType: PropTypes.string,
};
export default class AdhocMetricOption extends React.PureComponent {
class AdhocMetricOption extends React.PureComponent {
constructor(props) {
super(props);
this.closeMetricEditOverlay = this.closeMetricEditOverlay.bind(this);
@ -65,7 +66,7 @@ export default class AdhocMetricOption extends React.PureComponent {
}
render() {
const { adhocMetric } = this.props;
const { adhocMetric, theme } = this.props;
const overlayContent = (
<AdhocMetricEditPopover
onResize={this.onPopoverResize}
@ -74,6 +75,7 @@ export default class AdhocMetricOption extends React.PureComponent {
onClose={this.closeMetricEditOverlay}
columns={this.props.columns}
datasourceType={this.props.datasourceType}
theme={theme}
/>
);
@ -109,4 +111,7 @@ export default class AdhocMetricOption extends React.PureComponent {
);
}
}
export default withTheme(AdhocMetricOption);
AdhocMetricOption.propTypes = propTypes;

View File

@ -38,13 +38,13 @@ import {
import { Table } from 'reactable-arc';
import { t } from '@superset-ui/translation';
import Button from 'src/components/Button';
import getClientErrorObject from '../../utils/getClientErrorObject';
import CopyToClipboard from './../../components/CopyToClipboard';
import { getChartDataRequest } from '../../chart/chartAction';
import downloadAsImage from '../../utils/downloadAsImage';
import Loading from '../../components/Loading';
import ModalTrigger from './../../components/ModalTrigger';
import Button from '../../components/Button';
import RowCountLabel from './RowCountLabel';
import {
applyFormattingToTabularData,

View File

@ -47,7 +47,7 @@ export default function ExploreActionButtons({
slice,
}) {
const exportToCSVClasses = cx('btn btn-default btn-sm', {
'disabled disabledButton': !canDownload,
disabled: !canDownload,
});
const doExportCSV = exportChart.bind(this, {
formData: latestQueryFormData,

View File

@ -18,7 +18,6 @@
*/
import React, { useState, useEffect, useRef } from 'react';
import {
Button,
Modal,
Row,
Col,
@ -26,6 +25,7 @@ import {
FormGroup,
// @ts-ignore
} from 'react-bootstrap';
import Button from 'src/components/Button';
// @ts-ignore
import Dialog from 'react-bootstrap-dialog';
import { OptionsType } from 'react-select/src/types';
@ -263,15 +263,15 @@ function PropertiesModal({ slice, onHide, onSave }: InternalProps) {
</Row>
</Modal.Body>
<Modal.Footer>
<Button type="button" bsSize="sm" onClick={onHide}>
<Button type="button" buttonSize="sm" onClick={onHide} cta>
{t('Cancel')}
</Button>
<Button
type="submit"
bsSize="sm"
bsStyle="primary"
className="m-r-5"
buttonSize="sm"
buttonStyle="primary"
disabled={!owners || submitting || !name}
cta
>
{t('Save')}
</Button>

View File

@ -19,11 +19,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ButtonGroup, OverlayTrigger, Tooltip } from 'react-bootstrap';
import classnames from 'classnames';
import { t } from '@superset-ui/translation';
import styled from '@superset-ui/style';
import Button from '../../components/Button';
import Button from 'src/components/Button';
import Hotkeys from '../../components/Hotkeys';
const propTypes = {
@ -61,7 +60,8 @@ const Styles = styled.div`
align-items: center;
padding-bottom: ${({ theme }) => 2 * theme.gridUnit}px;
.save-btn {
.btn {
/* just to make sure buttons don't jiggle */
width: 100px;
}
`;
@ -75,12 +75,7 @@ export default function QueryAndSaveBtns({
chartIsStale,
errorMessage,
}) {
const saveClasses = classnames({
'disabled disabledButton': !canAdd,
'save-btn': true,
});
let qryButtonStyle = 'default';
let qryButtonStyle = 'secondary';
if (errorMessage) {
qryButtonStyle = 'danger';
} else if (chartIsStale) {
@ -89,15 +84,21 @@ export default function QueryAndSaveBtns({
const saveButtonDisabled = errorMessage ? true : loading;
const qryOrStopButton = loading ? (
<Button onClick={onStop} bsStyle="warning" className="save-btn">
<Button
onClick={onStop}
buttonStyle="warning"
buttonSize="small"
disabled={!canAdd}
>
<i className="fa fa-stop-circle-o" /> Stop
</Button>
) : (
<Button
className="query save-btn"
buttonSize="small"
onClick={onQuery}
bsStyle={qryButtonStyle}
buttonStyle={qryButtonStyle}
disabled={!!errorMessage}
data-test="run-query-button"
>
<i className="fa fa-bolt" /> {t('Run')}
</Button>
@ -109,7 +110,8 @@ export default function QueryAndSaveBtns({
<ButtonGroup className="query-and-save">
{qryOrStopButton}
<Button
className={saveClasses}
buttonStyle="secondary"
buttonSize="small"
data-target="#save_modal"
data-toggle="modal"
disabled={saveButtonDisabled}

View File

@ -20,14 +20,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {
Alert,
Button,
FormControl,
FormGroup,
Modal,
Radio,
} from 'react-bootstrap';
import { Alert, FormControl, FormGroup, Modal, Radio } from 'react-bootstrap';
import Button from 'src/components/Button';
import FormLabel from 'src/components/FormLabel';
import { CreatableSelect } from 'src/components/Select/SupersetStyledSelect';
import { t } from '@superset-ui/translation';
@ -208,18 +202,12 @@ class SaveModal extends React.Component {
<Modal.Footer>
<div className="float-right">
<Button
type="button"
id="btn_cancel"
bsSize="sm"
onClick={this.props.onHide}
>
<Button id="btn_cancel" buttonSize="sm" onClick={this.props.onHide}>
{t('Cancel')}
</Button>
<Button
type="button"
id="btn_modal_save_goto_dash"
bsSize="sm"
buttonSize="sm"
disabled={
!this.state.newSliceName || !this.state.newDashboardName
}
@ -228,10 +216,9 @@ class SaveModal extends React.Component {
{t('Save & go to dashboard')}
</Button>
<Button
type="button"
id="btn_modal_save"
bsSize="sm"
bsStyle="primary"
buttonSize="sm"
buttonStyle="primary"
onClick={this.saveOrOverwrite.bind(this, false)}
disabled={!this.state.newSliceName}
>

View File

@ -19,13 +19,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CompactPicker } from 'react-color';
import { Button } from 'react-bootstrap';
import Button from 'src/components/Button';
import mathjs from 'mathjs';
import { t } from '@superset-ui/translation';
import { SupersetClient } from '@superset-ui/connection';
import { getCategoricalSchemeRegistry } from '@superset-ui/color';
import { getChartMetadataRegistry } from '@superset-ui/chart';
import { validateNonEmpty } from '@superset-ui/validator';
import { ThemeProvider } from '@superset-ui/style';
import SelectControl from './SelectControl';
import TextControl from './TextControl';
@ -63,6 +64,7 @@ const propTypes = {
timeColumn: PropTypes.string,
intervalEndColumn: PropTypes.string,
vizType: PropTypes.string,
theme: PropTypes.object,
error: PropTypes.string,
colorScheme: PropTypes.string,
@ -619,8 +621,8 @@ export default class AnnotationLayer extends React.PureComponent {
/>
<Button
style={{ marginTop: '0.5rem', marginBottom: '0.5rem' }}
bsStyle={color === AUTOMATIC_COLOR ? 'success' : 'default'}
bsSize="xsmall"
buttonStyle={color === AUTOMATIC_COLOR ? 'success' : 'default'}
buttonSize="xsmall"
onClick={() => this.setState({ color: AUTOMATIC_COLOR })}
>
Automatic Color
@ -661,7 +663,7 @@ export default class AnnotationLayer extends React.PureComponent {
render() {
const { isNew, name, annotationType, sourceType, show } = this.state;
const isValid = this.isValidForm();
const { theme } = this.props;
const metadata = getChartMetadataRegistry().get(this.props.vizType);
const supportedAnnotationTypes = metadata
? metadata.supportedAnnotationTypes.map(
@ -671,7 +673,7 @@ export default class AnnotationLayer extends React.PureComponent {
const supportedSourceTypes = this.getSupportedSourceTypes(annotationType);
return (
<div>
<ThemeProvider theme={theme}>
{this.props.error && (
<span style={{ color: 'red' }}>ERROR: {this.props.error}</span>
)}
@ -724,12 +726,12 @@ export default class AnnotationLayer extends React.PureComponent {
{this.renderDisplayConfiguration()}
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Button bsSize="sm" onClick={this.deleteAnnotation}>
<Button buttonSize="sm" onClick={this.deleteAnnotation}>
{!isNew ? t('Remove') : t('Cancel')}
</Button>
<div>
<Button
bsSize="sm"
buttonSize="sm"
disabled={!isValid}
onClick={this.applyAnnotation}
>
@ -737,7 +739,7 @@ export default class AnnotationLayer extends React.PureComponent {
</Button>
<Button
bsSize="sm"
buttonSize="sm"
disabled={!isValid}
onClick={this.submitAnnotation}
>
@ -745,7 +747,7 @@ export default class AnnotationLayer extends React.PureComponent {
</Button>
</div>
</div>
</div>
</ThemeProvider>
);
}
}

View File

@ -26,6 +26,7 @@ import {
} from 'react-bootstrap';
import { connect } from 'react-redux';
import { t } from '@superset-ui/translation';
import { withTheme } from '@superset-ui/style';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import { getChartKey } from '../../exploreUtils';
import { runAnnotationQuery } from '../../../chart/chartAction';
@ -100,6 +101,7 @@ class AnnotationLayerControl extends React.PureComponent {
renderPopover(parent, annotation, error) {
const id = !annotation ? '_new' : annotation.name;
const { theme } = this.props;
return (
<Popover
style={{ maxWidth: 'none' }}
@ -116,6 +118,7 @@ class AnnotationLayerControl extends React.PureComponent {
addAnnotationLayer={this.addAnnotationLayer}
removeAnnotationLayer={this.removeAnnotationLayer}
close={() => this.refs[parent].hide()}
theme={theme}
/>
</Popover>
);
@ -208,7 +211,9 @@ function mapDispatchToProps(dispatch) {
};
}
const themedAnnotationLayerControl = withTheme(AnnotationLayerControl);
export default connect(
mapStateToProps,
mapDispatchToProps,
)(AnnotationLayerControl);
)(themedAnnotationLayerControl);

View File

@ -19,7 +19,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Button,
DropdownButton,
FormControl,
FormGroup,
@ -32,6 +31,7 @@ import {
Tabs,
Tooltip,
} from 'react-bootstrap';
import Button from 'src/components/Button';
import Datetime from 'react-datetime';
import 'react-datetime/css/react-datetime.css';
import moment from 'moment';
@ -365,7 +365,7 @@ class DateFilterControl extends React.Component {
onClick={() => {}}
/>
<InputGroup.Button onClick={() => this.toggleCalendar(key)}>
<Button>
<Button theme={this.props.theme}>
<i className="fa fa-calendar" />
</Button>
</InputGroup.Button>
@ -563,10 +563,11 @@ class DateFilterControl extends React.Component {
</Tabs>
<div className="clearfix">
<Button
bsSize="small"
buttonSize="small"
className="float-right ok"
bsStyle="primary"
buttonStyle="primary"
onClick={this.close}
theme={this.props.theme}
>
Ok
</Button>

View File

@ -18,7 +18,8 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Row, Col, Button, OverlayTrigger, Popover } from 'react-bootstrap';
import { Row, Col, OverlayTrigger, Popover } from 'react-bootstrap';
import Button from 'src/components/Button';
import { t } from '@superset-ui/translation';
import Label from 'src/components/Label';
@ -204,9 +205,9 @@ export default class SpatialControl extends React.Component {
</PopoverSection>
<div className="clearfix">
<Button
bsSize="small"
buttonSize="small"
className="float-left ok"
bsStyle="primary"
buttonStyle="primary"
onClick={this.close.bind(this)}
>
Ok

View File

@ -18,7 +18,8 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Button, FormGroup, FormControl } from 'react-bootstrap';
import { FormGroup, FormControl } from 'react-bootstrap';
import Button from 'src/components/Button';
import AceEditor from 'react-ace';
import 'brace/mode/sql';
@ -121,7 +122,7 @@ export default class TextAreaControl extends React.Component {
bsSize="large"
modalTitle={controlHeader}
triggerNode={
<Button bsSize="small" className="m-t-5">
<Button buttonSize="small" className="m-t-5">
{t('Edit')} <strong>{this.props.language}</strong>{' '}
{t('in modal')}
</Button>

View File

@ -1,72 +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 styled from '@superset-ui/style';
import BaseButton from 'src/components/Button';
interface ModalProps {
children: React.ReactNode;
disabled?: boolean;
onClick: () => void;
padding?: number;
bsStyle?: 'default' | 'primary' | 'danger';
width?: number;
}
const StyledButton = styled(BaseButton)`
border-radius: ${({ theme }) => theme.borderRadius}px;
border: none;
padding: ${(props: ModalProps) => props.padding || 8}px;
text-transform: uppercase;
width: ${(props: ModalProps) => props.width || 160}px;
&.btn,
&.btn:hover {
background-color: ${({ theme }) => theme.colors.primary.light4};
color: ${({ theme }) => theme.colors.primary.base};
}
&.btn[disabled],
&.btn[disabled]:hover {
background-color: ${({ theme }) => theme.colors.grayscale.light2};
color: ${({ theme }) => theme.colors.grayscale.light1};
}
&.btn-primary,
&.btn-primary:hover {
background-color: ${({ theme }) => theme.colors.primary.base};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
&.btn-danger,
&.btn-danger:hover {
background-color: ${({ theme }) => theme.colors.error.base};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
`;
export default function Modal({
bsStyle = 'default',
disabled,
onClick,
children,
}: ModalProps) {
return (
<StyledButton disabled={disabled} bsStyle={bsStyle} onClick={onClick}>
{children}
</StyledButton>
);
}

View File

@ -21,7 +21,7 @@ import PropTypes from 'prop-types';
import { debounce } from 'lodash';
import { max as d3Max } from 'd3-array';
import { AsyncCreatableSelect, CreatableSelect } from 'src/components/Select';
import { Button } from 'react-bootstrap';
import Button from 'src/components/Button';
import { t } from '@superset-ui/translation';
import { SupersetClient } from '@superset-ui/connection';
import styled from '@superset-ui/style';
@ -438,8 +438,8 @@ class FilterBox extends React.Component {
{this.renderFilters()}
{!instantFiltering && (
<Button
bsSize="small"
bsStyle="primary"
buttonSize="small"
buttonStyle="primary"
onClick={this.clickApply.bind(this)}
disabled={!this.state.hasChanged}
>

View File

@ -196,6 +196,10 @@ table.table-no-hover tr:hover {
background-color: initial;
}
.editable-title {
margin-right: 8px;
}
.editable-title input {
outline: none;
background: transparent;

View File

@ -26,7 +26,7 @@ import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import { exploreChart } from '../../explore/exploreUtils';
import * as actions from '../actions/sqlLab';
import Button from '../../components/Button';
import Button from 'src/components/Button';
const propTypes = {
actions: PropTypes.object.isRequired,
@ -88,7 +88,7 @@ class ExploreCtasResultsButton extends React.PureComponent {
return (
<>
<Button
bsSize="small"
buttonSize="small"
onClick={this.onClick}
tooltip={t('Explore the result set in the data exploration view')}
>