Form Migrations: Button (#23019)

* Update legacy exports and fix Type errors

* Remove Button and LinkButton from Forms namespace

* Fix errors

* Update snapshots

* Move Legacy button

* Migrate more Buttons

* Remove legacy button dependency

* Move button up

* Remove legacy button

* Update Snapshots

* Fix ComponentSize issues

* Switch primary button

* Switch primary

* Add classNames and fix some angular directive issues

* Fix failing build and remove log

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
Tobias Skarhed 2020-03-26 11:50:27 +01:00 committed by GitHub
parent f63877f247
commit 5cdb8f8e44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 388 additions and 693 deletions

View File

@ -59,5 +59,22 @@ Used for removing or deleting entities.
</div> </div>
</Preview> </Preview>
## Link
Used for for hyperlinks.
<Preview>
<div>
<Button href="/" variant="link" size="sm" renderAs="button" style={{ margin: '5px' }}>
Small
</Button>
<Button href="/" variant="link" size="md" renderAs="button" style={{ margin: '5px' }}>
Medium
</Button>
<Button href="/" variant="link" size="lg" renderAs="button" style={{ margin: '5px' }}>
Large
</Button>
</div>
</Preview>
<Props of={Button} /> <Props of={Button} />

View File

@ -1,45 +1,35 @@
import { storiesOf } from '@storybook/react'; import React from 'react';
import { Button, LinkButton } from './Button'; import { select, text } from '@storybook/addon-knobs';
// @ts-ignore import { Button, ButtonVariant } from './Button';
import withPropsCombinations from 'react-storybook-addon-props-combinations'; import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory';
import { action } from '@storybook/addon-actions';
import { ThemeableCombinationsRowRenderer } from '../../utils/storybook/CombinationsRowRenderer';
import { boolean } from '@storybook/addon-knobs';
import { getIconKnob } from '../../utils/storybook/knobs'; import { getIconKnob } from '../../utils/storybook/knobs';
import mdx from './Button.mdx';
import { ComponentSize } from '../../types/size';
const ButtonStories = storiesOf('General/Button', module); export default {
title: 'Forms/Button',
const defaultProps = { component: Button,
onClick: [action('Button clicked')], decorators: [withCenteredStory, withHorizontallyCenteredStory],
children: ['Click click!'], parameters: {
docs: {
page: mdx,
},
},
}; };
const variants = { const variants = ['primary', 'secondary', 'destructive', 'link'];
size: ['xs', 'sm', 'md', 'lg'],
variant: ['primary', 'secondary', 'danger', 'inverse', 'transparent', 'link'],
};
const combinationOptions = {
CombinationRenderer: ThemeableCombinationsRowRenderer,
};
const renderButtonStory = (buttonComponent: typeof Button | typeof LinkButton) => { const sizes = ['sm', 'md', 'lg'];
const isDisabled = boolean('Disable button', false);
return withPropsCombinations(
buttonComponent,
{ ...variants, ...defaultProps, disabled: [isDisabled] },
combinationOptions
)();
};
ButtonStories.add('as button element', () => renderButtonStory(Button)); export const simple = () => {
const variant = select('Variant', variants, 'primary');
ButtonStories.add('as link element', () => renderButtonStory(LinkButton)); const size = select('Size', sizes, 'md');
const buttonText = text('text', 'Button');
ButtonStories.add('with icon', () => {
const icon = getIconKnob(); const icon = getIconKnob();
return withPropsCombinations(
Button, return (
{ ...variants, ...defaultProps, icon: [icon && `fa fa-${icon}`] }, <Button variant={variant as ButtonVariant} size={size as ComponentSize} icon={icon && `fa fa-${icon}`}>
combinationOptions {buttonText}
)(); </Button>
}); );
};

View File

@ -1,26 +0,0 @@
import React from 'react';
import { Button, LinkButton } from './Button';
import { mount } from 'enzyme';
describe('Button', () => {
it('renders correct html', () => {
const wrapper = mount(<Button icon={'fa fa-plus'}>Click me</Button>);
expect(wrapper.html()).toMatchSnapshot();
});
});
describe('LinkButton', () => {
it('renders correct html', () => {
const wrapper = mount(<LinkButton icon={'fa fa-plus'}>Click me</LinkButton>);
expect(wrapper.html()).toMatchSnapshot();
});
it('allows a disable state on link button', () => {
const wrapper = mount(
<LinkButton disabled icon={'fa fa-plus'}>
Click me
</LinkButton>
);
expect(wrapper.find('a[disabled]').length).toBe(1);
});
});

View File

@ -1,72 +1,178 @@
import React, { AnchorHTMLAttributes, ButtonHTMLAttributes, useContext } from 'react'; import React, { AnchorHTMLAttributes, ButtonHTMLAttributes, useContext } from 'react';
import { ThemeContext } from '../../themes'; import { css, cx } from 'emotion';
import { getButtonStyles } from './styles'; import tinycolor from 'tinycolor2';
import { selectThemeVariant, stylesFactory, ThemeContext } from '../../themes';
import { getFocusStyle, getPropertiesForButtonSize } from '../Forms/commonStyles';
import { GrafanaTheme } from '@grafana/data';
import { ButtonContent } from './ButtonContent'; import { ButtonContent } from './ButtonContent';
import { ComponentSize } from '../../types/size'; import { ComponentSize } from '../../types/size';
import { ButtonStyles, ButtonVariant } from './types';
import { cx } from 'emotion'; const buttonVariantStyles = (from: string, to: string, textColor: string) => css`
background: linear-gradient(180deg, ${from} 0%, ${to} 100%);
color: ${textColor};
&:hover {
background: ${from};
color: ${textColor};
}
&:focus {
background: ${from};
outline: none;
}
`;
const getPropertiesForVariant = (theme: GrafanaTheme, variant: ButtonVariant) => {
switch (variant) {
case 'secondary':
const from = selectThemeVariant({ light: theme.colors.gray7, dark: theme.colors.gray15 }, theme.type) as string;
const to = selectThemeVariant(
{
light: tinycolor(from)
.darken(5)
.toString(),
dark: tinycolor(from)
.lighten(4)
.toString(),
},
theme.type
) as string;
return {
borderColor: selectThemeVariant({ light: theme.colors.gray85, dark: theme.colors.gray25 }, theme.type),
background: buttonVariantStyles(
from,
to,
selectThemeVariant({ light: theme.colors.gray25, dark: theme.colors.gray4 }, theme.type) as string
),
};
case 'destructive':
return {
borderColor: theme.colors.redShade,
background: buttonVariantStyles(theme.colors.redBase, theme.colors.redShade, theme.colors.white),
};
case 'link':
return {
borderColor: 'transparent',
background: buttonVariantStyles('transparent', 'transparent', theme.colors.linkExternal),
variantStyles: css`
&:focus {
outline: none;
box-shadow: none;
}
`,
};
case 'primary':
default:
return {
borderColor: theme.colors.blueShade,
background: buttonVariantStyles(theme.colors.blueBase, theme.colors.blueShade, theme.colors.white),
};
}
};
export interface StyleProps {
theme: GrafanaTheme;
size: ComponentSize;
variant: ButtonVariant;
textAndIcon?: boolean;
}
export const getButtonStyles = stylesFactory(({ theme, size, variant }: StyleProps) => {
const { padding, fontSize, height } = getPropertiesForButtonSize(theme, size);
const { background, borderColor, variantStyles } = getPropertiesForVariant(theme, variant);
return {
button: cx(
css`
label: button;
display: inline-flex;
align-items: center;
font-weight: ${theme.typography.weight.semibold};
font-family: ${theme.typography.fontFamily.sansSerif};
font-size: ${fontSize};
padding: ${padding};
height: ${height};
vertical-align: middle;
cursor: pointer;
border: 1px solid ${borderColor};
border-radius: ${theme.border.radius.sm};
${background};
&[disabled],
&:disabled {
cursor: not-allowed;
opacity: 0.65;
box-shadow: none;
}
`,
getFocusStyle(theme),
css`
${variantStyles}
`
),
buttonWithIcon: css`
padding-left: ${theme.spacing.sm};
`,
// used for buttons with icon only
iconButton: css`
padding-right: 0;
`,
iconWrap: css`
label: button-icon-wrap;
& + * {
margin-left: ${theme.spacing.sm};
}
`,
};
});
export type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'link';
type CommonProps = { type CommonProps = {
size?: ComponentSize; size?: ComponentSize;
variant?: ButtonVariant; variant?: ButtonVariant;
/**
* icon prop is a temporary solution. It accepts legacy icon class names for the icon to be rendered.
* TODO: migrate to a component when we are going to migrate icons to @grafana/ui
*/
icon?: string; icon?: string;
className?: string; className?: string;
styles?: ButtonStyles;
}; };
export type ButtonProps = CommonProps & ButtonHTMLAttributes<HTMLButtonElement>; export type ButtonProps = CommonProps & ButtonHTMLAttributes<HTMLButtonElement>;
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
const theme = useContext(ThemeContext);
const { size, variant, icon, children, className, styles: stylesProp, ...buttonProps } = props;
// Default this to 'button', otherwise html defaults to 'submit' which then submits any form it is in. export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
buttonProps.type = buttonProps.type || 'button'; ({ variant, icon, children, className, ...otherProps }, ref) => {
const theme = useContext(ThemeContext);
const styles: ButtonStyles = const styles = getButtonStyles({
stylesProp ||
getButtonStyles({
theme, theme,
size: size || 'md', size: otherProps.size || 'md',
variant: variant || 'primary', variant: variant || 'primary',
textAndIcon: !!(children && icon),
}); });
return ( return (
<button className={cx(styles.button, className)} {...buttonProps} ref={ref}> <button className={cx(styles.button, className)} {...otherProps} ref={ref}>
<ButtonContent icon={icon}>{children}</ButtonContent> <ButtonContent icon={icon}>{children}</ButtonContent>
</button> </button>
); );
}); }
);
Button.displayName = 'Button'; Button.displayName = 'Button';
export type LinkButtonProps = CommonProps & type ButtonLinkProps = CommonProps & AnchorHTMLAttributes<HTMLAnchorElement>;
AnchorHTMLAttributes<HTMLAnchorElement> & { export const LinkButton = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(
// We allow disabled here even though it is not standard for a link. We use it as a selector to style it as ({ variant, icon, children, className, ...otherProps }, ref) => {
// disabled. const theme = useContext(ThemeContext);
disabled?: boolean; const styles = getButtonStyles({
};
export const LinkButton = React.forwardRef<HTMLAnchorElement, LinkButtonProps>((props, ref) => {
const theme = useContext(ThemeContext);
const { size, variant, icon, children, className, styles: stylesProp, ...anchorProps } = props;
const styles: ButtonStyles =
stylesProp ||
getButtonStyles({
theme, theme,
size: size || 'md', size: otherProps.size || 'md',
variant: variant || 'primary', variant: variant || 'primary',
textAndIcon: !!(children && icon),
}); });
return ( return (
<a className={cx(styles.button, className)} {...anchorProps} ref={ref}> <a className={cx(styles.button, className)} {...otherProps} ref={ref}>
<ButtonContent icon={icon}>{children}</ButtonContent> <ButtonContent icon={icon}>{children}</ButtonContent>
</a> </a>
); );
}); }
);
LinkButton.displayName = 'LinkButton'; LinkButton.displayName = 'LinkButton';

View File

@ -1,5 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Button renders correct html 1`] = `"<button class=\\"css-12s5hlm-button\\" type=\\"button\\"><span class=\\"css-1beih13\\"><span class=\\"css-1rgbe4\\"><i class=\\"fa fa-plus\\"></i></span><span>Click me</span></span></button>"`;
exports[`LinkButton renders correct html 1`] = `"<a class=\\"css-12s5hlm-button\\"><span class=\\"css-1beih13\\"><span class=\\"css-1rgbe4\\"><i class=\\"fa fa-plus\\"></i></span><span>Click me</span></span></a>"`;

View File

@ -0,0 +1 @@
export * from './Button';

View File

@ -1,164 +0,0 @@
import tinycolor from 'tinycolor2';
import { css } from 'emotion';
import { selectThemeVariant, stylesFactory } from '../../themes';
import { ComponentSize } from '../../types/size';
import { StyleDeps } from './types';
import { GrafanaTheme } from '@grafana/data';
const buttonVariantStyles = (
from: string,
to: string,
textColor: string,
textShadowColor = 'rgba(0, 0, 0, 0.1)',
invert = false
) => css`
background: linear-gradient(to bottom, ${from}, ${to});
color: ${textColor};
text-shadow: 0 ${invert ? '1px' : '-1px'} ${textShadowColor};
&:hover {
background: ${from};
color: ${textColor};
}
&:focus {
background: ${from};
outline: none;
}
`;
export const getButtonStyles = stylesFactory(({ theme, size, variant, textAndIcon }: StyleDeps) => {
const borderRadius = theme.border.radius.sm;
const { padding, fontSize, height, fontWeight } = calculateMeasures(theme, size, !!textAndIcon);
let background;
switch (variant) {
case 'primary':
background = buttonVariantStyles(theme.colors.greenBase, theme.colors.greenShade, theme.colors.white);
break;
case 'secondary':
background = buttonVariantStyles(theme.colors.blueBase, theme.colors.blueShade, theme.colors.white);
break;
case 'danger':
background = buttonVariantStyles(theme.colors.redBase, theme.colors.redShade, theme.colors.white);
break;
case 'inverse':
const from = selectThemeVariant({ light: theme.colors.gray5, dark: theme.colors.dark6 }, theme.type) as string;
const to = selectThemeVariant(
{
light: tinycolor(from)
.darken(5)
.toString(),
dark: tinycolor(from)
.lighten(4)
.toString(),
},
theme.type
) as string;
background = buttonVariantStyles(from, to, theme.colors.link, 'rgba(0, 0, 0, 0.1)', true);
break;
case 'transparent':
background = css`
${buttonVariantStyles('', '', theme.colors.link, 'rgba(0, 0, 0, 0.1)', true)};
background: transparent;
`;
break;
case 'link':
background = css`
${buttonVariantStyles('', '', theme.colors.linkExternal, 'rgba(0, 0, 0, 0.1)', true)};
background: transparent;
`;
break;
}
return {
button: css`
label: button;
display: inline-flex;
align-items: center;
font-weight: ${fontWeight};
font-size: ${fontSize};
font-family: ${theme.typography.fontFamily.sansSerif};
line-height: ${theme.typography.lineHeight.md};
padding: ${padding};
vertical-align: middle;
cursor: pointer;
border: none;
height: ${height};
border-radius: ${borderRadius};
${background};
&[disabled],
&:disabled {
cursor: not-allowed;
opacity: 0.65;
box-shadow: none;
}
`,
iconWrap: css`
label: button-icon-wrap;
& + * {
margin-left: ${theme.spacing.sm};
}
`,
};
});
type ButtonMeasures = {
padding: string;
fontSize: string;
height: string;
fontWeight: number;
};
const calculateMeasures = (theme: GrafanaTheme, size: ComponentSize, textAndIcon: boolean): ButtonMeasures => {
switch (size) {
case 'sm': {
return {
padding: `0 ${theme.spacing.sm}`,
fontSize: theme.typography.size.sm,
height: theme.height.sm,
fontWeight: theme.typography.weight.semibold,
};
}
case 'md': {
const leftPadding = textAndIcon ? theme.spacing.sm : theme.spacing.md;
return {
padding: `0 ${theme.spacing.md} 0 ${leftPadding}`,
fontSize: theme.typography.size.md,
height: theme.height.md,
fontWeight: theme.typography.weight.semibold,
};
}
case 'lg': {
const leftPadding = textAndIcon ? theme.spacing.md : theme.spacing.lg;
return {
padding: `0 ${theme.spacing.lg} 0 ${leftPadding}`,
fontSize: theme.typography.size.lg,
height: theme.height.lg,
fontWeight: theme.typography.weight.regular,
};
}
default: {
const leftPadding = textAndIcon ? theme.spacing.sm : theme.spacing.md;
return {
padding: `0 ${theme.spacing.md} 0 ${leftPadding}`,
fontSize: theme.typography.size.base,
height: theme.height.md,
fontWeight: theme.typography.weight.regular,
};
}
}
};

View File

@ -1,17 +0,0 @@
import { GrafanaTheme } from '@grafana/data';
import { ComponentSize } from '../../types/size';
export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'inverse' | 'transparent' | 'destructive' | 'link';
export interface StyleDeps {
theme: GrafanaTheme;
size: ComponentSize;
variant: ButtonVariant;
textAndIcon?: boolean;
}
export interface ButtonStyles {
button: string;
iconWrap: string;
icon?: string;
}

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import Clipboard from 'clipboard'; import Clipboard from 'clipboard';
import { Button, ButtonProps } from '../Button/Button'; import { Button, ButtonProps } from '../Button';
interface Props extends ButtonProps { interface Props extends ButtonProps {
getText(): string; getText(): string;

View File

@ -1,10 +1,11 @@
export { ClipboardButton } from '../ClipboardButton/ClipboardButton';
import React from 'react'; import React from 'react';
import { storiesOf } from '@storybook/react'; import { storiesOf } from '@storybook/react';
import { text, boolean, select } from '@storybook/addon-knobs'; import { text, boolean, select } from '@storybook/addon-knobs';
import { ConfirmButton } from './ConfirmButton'; import { ConfirmButton } from './ConfirmButton';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import { Button } from '../Button/Button'; import { Button } from '../Button';
const getKnobs = () => { const getKnobs = () => {
return { return {
@ -16,9 +17,8 @@ const getKnobs = () => {
{ {
primary: 'primary', primary: 'primary',
secondary: 'secondary', secondary: 'secondary',
danger: 'danger', destructive: 'destructive',
inverse: 'inverse', link: 'link',
transparent: 'transparent',
}, },
'primary' 'primary'
), ),

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { ConfirmButton } from './ConfirmButton'; import { ConfirmButton } from './ConfirmButton';
import { mount, ShallowWrapper } from 'enzyme'; import { mount, ShallowWrapper } from 'enzyme';
import { Button } from '../Button/Button'; import { Button } from '../Button';
describe('ConfirmButton', () => { describe('ConfirmButton', () => {
let wrapper: any; let wrapper: any;

View File

@ -4,9 +4,7 @@ import { stylesFactory, withTheme } from '../../themes';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
import { Themeable } from '../../types'; import { Themeable } from '../../types';
import { ComponentSize } from '../../types/size'; import { ComponentSize } from '../../types/size';
import { Button } from '../Button/Button'; import { Button, ButtonVariant } from '../Button';
import Forms from '../Forms';
import { ButtonVariant } from '../Button/types';
const getStyles = stylesFactory((theme: GrafanaTheme) => { const getStyles = stylesFactory((theme: GrafanaTheme) => {
return { return {
@ -135,9 +133,9 @@ class UnThemedConfirmButton extends PureComponent<Props, State> {
<span className={styles.buttonContainer}> <span className={styles.buttonContainer}>
{typeof children === 'string' ? ( {typeof children === 'string' ? (
<span className={buttonClass}> <span className={buttonClass}>
<Forms.Button size={size} variant="link" onClick={onClick}> <Button size={size} variant="link" onClick={onClick}>
{children} {children}
</Forms.Button> </Button>
</span> </span>
) : ( ) : (
<span className={buttonClass} onClick={onClick}> <span className={buttonClass} onClick={onClick}>
@ -146,7 +144,7 @@ class UnThemedConfirmButton extends PureComponent<Props, State> {
)} )}
<span className={styles.confirmButtonContainer}> <span className={styles.confirmButtonContainer}>
<span className={confirmButtonClass}> <span className={confirmButtonClass}>
<Button size={size} variant="transparent" onClick={this.onClickCancel}> <Button size={size} variant="secondary" onClick={this.onClickCancel}>
Cancel Cancel
</Button> </Button>
<Button size={size} variant={confirmButtonVariant} onClick={onConfirm}> <Button size={size} variant={confirmButtonVariant} onClick={onConfirm}>

View File

@ -1,7 +1,7 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { ConfirmButton } from './ConfirmButton'; import { ConfirmButton } from './ConfirmButton';
import { Button } from '../Button/Button';
import { ComponentSize } from '../../types/size'; import { ComponentSize } from '../../types/size';
import { Button } from '../Button';
interface Props { interface Props {
size?: ComponentSize; size?: ComponentSize;
@ -13,12 +13,12 @@ export const DeleteButton: FC<Props> = ({ size, disabled, onConfirm }) => {
return ( return (
<ConfirmButton <ConfirmButton
confirmText="Delete" confirmText="Delete"
confirmVariant="danger" confirmVariant="destructive"
size={size || 'md'} size={size || 'md'}
disabled={disabled} disabled={disabled}
onConfirm={onConfirm} onConfirm={onConfirm}
> >
<Button variant="danger" icon="fa fa-remove" size={size || 'sm'} /> <Button variant="destructive" icon="fa fa-remove" size={size || 'sm'} />
</ConfirmButton> </ConfirmButton>
); );
}; };

View File

@ -2,7 +2,7 @@ import React, { FC, useContext } from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { Modal } from '../Modal/Modal'; import { Modal } from '../Modal/Modal';
import { IconType } from '../Icon/types'; import { IconType } from '../Icon/types';
import { Button } from '../Button/Button'; import { Button } from '../Button';
import { stylesFactory, ThemeContext } from '../../themes'; import { stylesFactory, ThemeContext } from '../../themes';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
import { HorizontalGroup } from '..'; import { HorizontalGroup } from '..';
@ -53,10 +53,10 @@ export const ConfirmModal: FC<Props> = ({
<div className={styles.modalContent}> <div className={styles.modalContent}>
<div className={styles.modalText}>{body}</div> <div className={styles.modalText}>{body}</div>
<HorizontalGroup justify="center"> <HorizontalGroup justify="center">
<Button variant="danger" onClick={onConfirm}> <Button variant="destructive" onClick={onConfirm}>
{confirmText} {confirmText}
</Button> </Button>
<Button variant="inverse" onClick={onDismiss}> <Button variant="secondary" onClick={onDismiss}>
{dismissText} {dismissText}
</Button> </Button>
</HorizontalGroup> </HorizontalGroup>

View File

@ -73,7 +73,7 @@ export const DataLinksEditor: FC<DataLinksEditorProps> = React.memo(
)} )}
{(!value || (value && value.length < (maxLinks || Infinity))) && ( {(!value || (value && value.length < (maxLinks || Infinity))) && (
<Button variant="inverse" icon="fa fa-plus" onClick={() => onAdd()}> <Button variant="secondary" icon="fa fa-plus" onClick={() => onAdd()}>
Add link Add link
</Button> </Button>
)} )}

View File

@ -2,7 +2,7 @@ import { DataFrame, DataLink, VariableSuggestion } from '@grafana/data';
import React, { FC, useState } from 'react'; import React, { FC, useState } from 'react';
import { DataLinkEditor } from '../DataLinkEditor'; import { DataLinkEditor } from '../DataLinkEditor';
import { HorizontalGroup } from '../../Layout/Layout'; import { HorizontalGroup } from '../../Layout/Layout';
import Forms from '../../Forms'; import { Button } from '../../Button';
interface DataLinkEditorModalContentProps { interface DataLinkEditorModalContentProps {
link: DataLink; link: DataLink;
@ -34,17 +34,17 @@ export const DataLinkEditorModalContent: FC<DataLinkEditorModalContentProps> = (
onRemove={() => {}} onRemove={() => {}}
/> />
<HorizontalGroup> <HorizontalGroup>
<Forms.Button <Button
onClick={() => { onClick={() => {
onChange(index, dirtyLink); onChange(index, dirtyLink);
onClose(); onClose();
}} }}
> >
Save Save
</Forms.Button> </Button>
<Forms.Button variant="secondary" onClick={() => onClose()}> <Button variant="secondary" onClick={() => onClose()}>
Cancel Cancel
</Forms.Button> </Button>
</HorizontalGroup> </HorizontalGroup>
</> </>
); );

View File

@ -1,7 +1,7 @@
import { DataFrame, DataLink, GrafanaTheme, VariableSuggestion } from '@grafana/data'; import { DataFrame, DataLink, GrafanaTheme, VariableSuggestion } from '@grafana/data';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import Forms from '../../Forms'; import { Button } from '../../Button/Button';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import { Modal } from '../../Modal/Modal'; import { Modal } from '../../Modal/Modal';
import { FullWidthButtonContainer } from '../../Button/FullWidthButtonContainer'; import { FullWidthButtonContainer } from '../../Button/FullWidthButtonContainer';
@ -100,9 +100,9 @@ export const DataLinksInlineEditor: React.FC<DataLinksInlineEditorProps> = ({ li
)} )}
<FullWidthButtonContainer> <FullWidthButtonContainer>
<Forms.Button size="sm" icon="fa fa-plus" onClick={onDataLinkAdd}> <Button size="sm" icon="fa fa-plus" onClick={onDataLinkAdd}>
Add link Add link
</Forms.Button> </Button>
</FullWidthButtonContainer> </FullWidthButtonContainer>
</> </>
); );

View File

@ -57,7 +57,7 @@ describe('Render', () => {
}, },
}, },
}); });
const removeButton = wrapper.find('Button').find({ variant: 'transparent' }); const removeButton = wrapper.find('Button').find({ variant: 'destructive' });
removeButton.simulate('click', { preventDefault: () => {} }); removeButton.simulate('click', { preventDefault: () => {} });
expect(wrapper.find('FormField').exists()).toBeFalsy(); expect(wrapper.find('FormField').exists()).toBeFalsy();
expect(wrapper.find('SecretFormField').exists()).toBeFalsy(); expect(wrapper.find('SecretFormField').exists()).toBeFalsy();

View File

@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import uniqueId from 'lodash/uniqueId'; import uniqueId from 'lodash/uniqueId';
import { DataSourceSettings } from '@grafana/data'; import { DataSourceSettings } from '@grafana/data';
import { Button } from '../Button/Button'; import { Button } from '../Button';
import { FormField } from '../FormField/FormField'; import { FormField } from '../FormField/FormField';
import { SecretFormField } from '../SecretFormFied/SecretFormField'; import { SecretFormField } from '../SecretFormFied/SecretFormField';
import { stylesFactory } from '../../themes'; import { stylesFactory } from '../../themes';
@ -76,7 +76,7 @@ const CustomHeaderRow: React.FC<CustomHeaderRowProps> = ({ header, onBlur, onCha
onChange={e => onChange({ ...header, value: e.target.value })} onChange={e => onChange({ ...header, value: e.target.value })}
onBlur={onBlur} onBlur={onBlur}
/> />
<Button variant="transparent" size="xs" onClick={_e => onRemove(header.id)}> <Button variant="destructive" size="xs" onClick={_e => onRemove(header.id)}>
<i className="fa fa-trash" /> <i className="fa fa-trash" />
</Button> </Button>
</div> </div>
@ -202,7 +202,7 @@ export class CustomHeadersSettings extends PureComponent<Props, State> {
</div> </div>
<div className="gf-form"> <div className="gf-form">
<Button <Button
variant="inverse" variant="secondary"
size="xs" size="xs"
onClick={e => { onClick={e => {
this.onHeaderAdd(); this.onHeaderAdd();

View File

@ -1,35 +0,0 @@
import React from 'react';
import { select, text } from '@storybook/addon-knobs';
import { Button, ButtonVariant } from './Button';
import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory';
import { getIconKnob } from '../../utils/storybook/knobs';
import { ComponentSize } from '../../types/size';
import mdx from './Button.mdx';
export default {
title: 'Forms/Button',
component: Button,
decorators: [withCenteredStory, withHorizontallyCenteredStory],
parameters: {
docs: {
page: mdx,
},
},
};
const variants = ['primary', 'secondary', 'destructive', 'link'];
const sizes = ['sm', 'md', 'lg'];
export const simple = () => {
const variant = select('Variant', variants, 'primary');
const size = select('Size', sizes, 'md');
const buttonText = text('text', 'Button');
const icon = getIconKnob();
return (
<Button variant={variant as ButtonVariant} size={size as ComponentSize} icon={icon && `fa fa-${icon}`}>
{buttonText}
</Button>
);
};

View File

@ -1,161 +0,0 @@
import React, { AnchorHTMLAttributes, ButtonHTMLAttributes, useContext } from 'react';
import { css, cx } from 'emotion';
import tinycolor from 'tinycolor2';
import { selectThemeVariant, stylesFactory, ThemeContext } from '../../themes';
import { Button as DefaultButton, LinkButton as DefaultLinkButton } from '../Button/Button';
import { getFocusStyle, getPropertiesForButtonSize } from './commonStyles';
import { ComponentSize } from '../../types/size';
import { StyleDeps } from '../Button/types';
import { GrafanaTheme } from '@grafana/data';
const buttonVariantStyles = (from: string, to: string, textColor: string) => css`
background: linear-gradient(180deg, ${from} 0%, ${to} 100%);
color: ${textColor};
&:hover {
background: ${from};
color: ${textColor};
}
&:focus {
background: ${from};
outline: none;
}
`;
const getPropertiesForVariant = (theme: GrafanaTheme, variant: ButtonVariant) => {
switch (variant) {
case 'secondary':
const from = selectThemeVariant({ light: theme.colors.gray7, dark: theme.colors.gray15 }, theme.type) as string;
const to = selectThemeVariant(
{
light: tinycolor(from)
.darken(5)
.toString(),
dark: tinycolor(from)
.lighten(4)
.toString(),
},
theme.type
) as string;
return {
borderColor: selectThemeVariant({ light: theme.colors.gray85, dark: theme.colors.gray25 }, theme.type),
background: buttonVariantStyles(
from,
to,
selectThemeVariant({ light: theme.colors.gray25, dark: theme.colors.gray4 }, theme.type) as string
),
};
case 'destructive':
return {
borderColor: theme.colors.redShade,
background: buttonVariantStyles(theme.colors.redBase, theme.colors.redShade, theme.colors.white),
};
case 'link':
return {
borderColor: 'transparent',
background: buttonVariantStyles('transparent', 'transparent', theme.colors.linkExternal),
variantStyles: css`
&:focus {
outline: none;
box-shadow: none;
}
`,
};
case 'primary':
default:
return {
borderColor: theme.colors.blueShade,
background: buttonVariantStyles(theme.colors.blueBase, theme.colors.blueShade, theme.colors.white),
};
}
};
// Need to do this because of mismatch between variants in standard buttons and here
type StyleProps = Omit<StyleDeps, 'variant'> & { variant: ButtonVariant };
export const getButtonStyles = stylesFactory(({ theme, size, variant }: StyleProps) => {
const { padding, fontSize, height } = getPropertiesForButtonSize(theme, size);
const { background, borderColor, variantStyles } = getPropertiesForVariant(theme, variant);
return {
button: cx(
css`
label: button;
display: inline-flex;
align-items: center;
font-weight: ${theme.typography.weight.semibold};
font-family: ${theme.typography.fontFamily.sansSerif};
line-height: ${theme.typography.lineHeight.md};
font-size: ${fontSize};
padding: ${padding};
height: ${height};
vertical-align: middle;
cursor: pointer;
border: 1px solid ${borderColor};
border-radius: ${theme.border.radius.sm};
${background};
&[disabled],
&:disabled {
cursor: not-allowed;
opacity: 0.65;
box-shadow: none;
}
`,
getFocusStyle(theme),
css`
${variantStyles}
`
),
buttonWithIcon: css`
padding-left: ${theme.spacing.sm};
`,
// used for buttons with icon only
iconButton: css`
padding-right: 0;
`,
iconWrap: css`
label: button-icon-wrap;
& + * {
margin-left: ${theme.spacing.sm};
}
`,
};
});
// These are different from the standard Button where there are more variants.
export type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'link';
// These also needs to be different because the ButtonVariant is different
type CommonProps = {
size?: ComponentSize;
variant?: ButtonVariant;
icon?: string;
className?: string;
};
export type ButtonProps = CommonProps & ButtonHTMLAttributes<HTMLButtonElement>;
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ variant, ...otherProps }, ref) => {
const theme = useContext(ThemeContext);
const styles = getButtonStyles({
theme,
size: otherProps.size || 'md',
variant: variant || 'primary',
});
return <DefaultButton {...otherProps} variant={variant} styles={styles} ref={ref} />;
});
type ButtonLinkProps = CommonProps & AnchorHTMLAttributes<HTMLAnchorElement>;
export const LinkButton = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(({ variant, ...otherProps }, ref) => {
const theme = useContext(ThemeContext);
const styles = getButtonStyles({
theme,
size: otherProps.size || 'md',
variant: variant || 'primary',
});
return <DefaultLinkButton {...otherProps} variant={variant} styles={styles} ref={ref} />;
});

View File

@ -5,7 +5,7 @@ import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { withStoryContainer } from '../../utils/storybook/withStoryContainer'; import { withStoryContainer } from '../../utils/storybook/withStoryContainer';
import { Field } from './Field'; import { Field } from './Field';
import { Input } from './Input/Input'; import { Input } from './Input/Input';
import { Button } from './Button'; import { Button } from '../Button';
import { Form } from './Form'; import { Form } from './Form';
import { Switch } from './Switch'; import { Switch } from './Switch';
import { Checkbox } from './Checkbox'; import { Checkbox } from './Checkbox';

View File

@ -2,7 +2,7 @@ import React from 'react';
import { boolean, text, select, number } from '@storybook/addon-knobs'; import { boolean, text, select, number } from '@storybook/addon-knobs';
import { withCenteredStory } from '../../../utils/storybook/withCenteredStory'; import { withCenteredStory } from '../../../utils/storybook/withCenteredStory';
import { Input } from './Input'; import { Input } from './Input';
import { Button } from '../Button'; import { Button } from '../../Button';
import mdx from './Input.mdx'; import mdx from './Input.mdx';
import { getAvailableIcons, IconType } from '../../Icon/types'; import { getAvailableIcons, IconType } from '../../Icon/types';
import { KeyValue } from '@grafana/data'; import { KeyValue } from '@grafana/data';

View File

@ -2,7 +2,7 @@ import React from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
import { Button, ButtonVariant, ButtonProps } from '../Button'; import { Button, ButtonVariant, ButtonProps } from '../../Button';
import { ComponentSize } from '../../../types/size'; import { ComponentSize } from '../../../types/size';
import { SelectCommonProps, CustomControlProps } from './types'; import { SelectCommonProps, CustomControlProps } from './types';
import { SelectBase } from './SelectBase'; import { SelectBase } from './SelectBase';

View File

@ -5,7 +5,7 @@ import { SelectableValue } from '@grafana/data';
import { getAvailableIcons, IconType } from '../../Icon/types'; import { getAvailableIcons, IconType } from '../../Icon/types';
import { select, boolean } from '@storybook/addon-knobs'; import { select, boolean } from '@storybook/addon-knobs';
import { Icon } from '../../Icon/Icon'; import { Icon } from '../../Icon/Icon';
import { Button } from '../Button'; import { Button } from '../../Button';
import { ButtonSelect } from './ButtonSelect'; import { ButtonSelect } from './ButtonSelect';
import { getIconKnob } from '../../../utils/storybook/knobs'; import { getIconKnob } from '../../../utils/storybook/knobs';
import kebabCase from 'lodash/kebabCase'; import kebabCase from 'lodash/kebabCase';

View File

@ -3,7 +3,7 @@ import { GrafanaTheme } from '@grafana/data';
import { getLabelStyles } from './Label'; import { getLabelStyles } from './Label';
import { getLegendStyles } from './Legend'; import { getLegendStyles } from './Legend';
import { getFieldValidationMessageStyles } from './FieldValidationMessage'; import { getFieldValidationMessageStyles } from './FieldValidationMessage';
import { getButtonStyles, ButtonVariant } from './Button'; import { getButtonStyles, ButtonVariant } from '../Button';
import { ComponentSize } from '../../types/size'; import { ComponentSize } from '../../types/size';
import { getInputStyles } from './Input/Input'; import { getInputStyles } from './Input/Input';
import { getSwitchStyles } from './Switch'; import { getSwitchStyles } from './Switch';

View File

@ -7,21 +7,22 @@ import { RadioButtonGroup } from './RadioButtonGroup/RadioButtonGroup';
import { AsyncSelect, Select } from './Select/Select'; import { AsyncSelect, Select } from './Select/Select';
import { Form } from './Form'; import { Form } from './Form';
import { Field } from './Field'; import { Field } from './Field';
import { Button, LinkButton } from './Button';
import { Switch } from './Switch'; import { Switch } from './Switch';
import { TextArea } from './TextArea/TextArea'; import { TextArea } from './TextArea/TextArea';
import { Checkbox } from './Checkbox'; import { Checkbox } from './Checkbox';
//Will be removed after Enterprise changes have been merged
import { Button, LinkButton } from '../Button';
const Forms = { const Forms = {
RadioButtonGroup, RadioButtonGroup,
Button,
LinkButton,
Switch, Switch,
getFormStyles, getFormStyles,
Label, Label,
Input, Input,
Form, Form,
Field, Field,
Button,
LinkButton,
Select, Select,
ButtonSelect, ButtonSelect,
InputControl, InputControl,
@ -30,5 +31,4 @@ const Forms = {
Checkbox, Checkbox,
}; };
export { ButtonVariant } from './Button';
export default Forms; export default Forms;

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory'; import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory';
import { VerticalGroup, HorizontalGroup, Layout } from './Layout'; import { VerticalGroup, HorizontalGroup, Layout } from './Layout';
import { Button } from '../Forms/Button'; import { Button } from '../Button';
import { withStoryContainer } from '../../utils/storybook/withStoryContainer'; import { withStoryContainer } from '../../utils/storybook/withStoryContainer';
import { select } from '@storybook/addon-knobs'; import { select } from '@storybook/addon-knobs';
@ -11,7 +11,7 @@ export default {
decorators: [withStoryContainer, withCenteredStory, withHorizontallyCenteredStory], decorators: [withStoryContainer, withCenteredStory, withHorizontallyCenteredStory],
}; };
const justifyVariants = ['flex-start', 'flex-end', 'space-between']; const justifyVariants = ['flex-start', 'flex-end', 'space-betw een'];
const spacingVariants = ['xs', 'sm', 'md', 'lg']; const spacingVariants = ['xs', 'sm', 'md', 'lg'];

View File

@ -127,7 +127,7 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
<> <>
&nbsp; &nbsp;
<LinkButton <LinkButton
variant={'transparent'} variant="link"
size={'sm'} size={'sm'}
icon={cx('fa', link.onClick ? 'fa-list' : 'fa-external-link')} icon={cx('fa', link.onClick ? 'fa-list' : 'fa-external-link')}
href={link.href} href={link.href}

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { stylesFactory } from '../../themes'; import { stylesFactory } from '../../themes';
import { Button, ButtonVariant } from '../Forms/Button'; import { Button, ButtonVariant } from '../Button';
interface Props { interface Props {
currentPage: number; currentPage: number;

View File

@ -1,7 +1,7 @@
import React, { ChangeEvent, KeyboardEvent, PureComponent } from 'react'; import React, { ChangeEvent, KeyboardEvent, PureComponent } from 'react';
import { css, cx } from 'emotion'; import { css, cx } from 'emotion';
import { stylesFactory } from '../../themes/stylesFactory'; import { stylesFactory } from '../../themes/stylesFactory';
import { Button } from '../Button/Button'; import { Button } from '../Button';
import { Input } from '../Input/Input'; import { Input } from '../Input/Input';
import { TagItem } from './TagItem'; import { TagItem } from './TagItem';
@ -106,7 +106,7 @@ export class TagsInput extends PureComponent<Props, State> {
)} )}
> >
<Input placeholder="Add Name" onChange={this.onNameChange} value={newTag} onKeyUp={this.onKeyboardAdd} /> <Input placeholder="Add Name" onChange={this.onNameChange} value={newTag} onKeyUp={this.onKeyboardAdd} />
<Button className={getStyles().addButtonStyle} onClick={this.onAdd} variant="secondary" size="md"> <Button className={getStyles().addButtonStyle} onClick={this.onAdd} variant="primary" size="md">
Add Add
</Button> </Button>
</div> </div>

View File

@ -16,7 +16,7 @@ import { stylesFactory } from '../../themes';
import { Icon } from '../Icon/Icon'; import { Icon } from '../Icon/Icon';
import { RadioButtonGroup } from '../Forms/RadioButtonGroup/RadioButtonGroup'; import { RadioButtonGroup } from '../Forms/RadioButtonGroup/RadioButtonGroup';
import { Field } from '../Forms/Field'; import { Field } from '../Forms/Field';
import { Button } from '../Forms/Button'; import { Button } from '../Button';
import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer'; import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer';
const modes: Array<SelectableValue<ThresholdsMode>> = [ const modes: Array<SelectableValue<ThresholdsMode>> = [

View File

@ -5,7 +5,7 @@ import { GrafanaTheme, dateTime, TIME_FORMAT } from '@grafana/data';
import { stringToDateTimeType } from '../time'; import { stringToDateTimeType } from '../time';
import { useTheme, stylesFactory } from '../../../themes'; import { useTheme, stylesFactory } from '../../../themes';
import { TimePickerTitle } from './TimePickerTitle'; import { TimePickerTitle } from './TimePickerTitle';
import Forms from '../../Forms'; import { Button } from '../../Button';
import { Portal } from '../../Portal/Portal'; import { Portal } from '../../Portal/Portal';
import { getThemeColors } from './colors'; import { getThemeColors } from './colors';
import { ClickOutsideWrapper } from '../../ClickOutsideWrapper/ClickOutsideWrapper'; import { ClickOutsideWrapper } from '../../ClickOutsideWrapper/ClickOutsideWrapper';
@ -281,12 +281,12 @@ const Footer = memo<Props>(({ onClose, onApply }) => {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<Forms.Button className={styles.apply} onClick={onApply}> <Button className={styles.apply} onClick={onApply}>
Apply time range Apply time range
</Forms.Button> </Button>
<Forms.Button variant="secondary" onClick={onClose}> <Button variant="secondary" onClick={onClose}>
Cancel Cancel
</Forms.Button> </Button>
</div> </div>
); );
}); });

View File

@ -4,6 +4,7 @@ import { stringToDateTimeType, isValidTimeString } from '../time';
import { mapStringsToTimeRange } from './mapper'; import { mapStringsToTimeRange } from './mapper';
import { TimePickerCalendar } from './TimePickerCalendar'; import { TimePickerCalendar } from './TimePickerCalendar';
import Forms from '../../Forms'; import Forms from '../../Forms';
import { Button } from '../../Button';
interface Props { interface Props {
isFullscreen: boolean; isFullscreen: boolean;
@ -60,7 +61,7 @@ export const TimeRangeForm: React.FC<Props> = props => {
[timeZone] [timeZone]
); );
const icon = isFullscreen ? null : <Forms.Button icon="fa fa-calendar" variant="secondary" onClick={onOpen} />; const icon = isFullscreen ? null : <Button icon="fa fa-calendar" variant="secondary" onClick={onOpen} />;
return ( return (
<> <>
@ -82,7 +83,7 @@ export const TimeRangeForm: React.FC<Props> = props => {
value={to.value} value={to.value}
/> />
</Forms.Field> </Forms.Field>
<Forms.Button onClick={onApply}>Apply time range</Forms.Button> <Button onClick={onApply}>Apply time range</Button>
<TimePickerCalendar <TimePickerCalendar
isFullscreen={isFullscreen} isFullscreen={isFullscreen}

View File

@ -3,7 +3,7 @@ import { Select } from '../Select/Select';
import { transformersUIRegistry } from './transformers'; import { transformersUIRegistry } from './transformers';
import React from 'react'; import React from 'react';
import { TransformationRow } from './TransformationRow'; import { TransformationRow } from './TransformationRow';
import { Button } from '../Button/Button'; import { Button } from '../Button';
import { css } from 'emotion'; import { css } from 'emotion';
interface TransformationsEditorState { interface TransformationsEditorState {
@ -118,7 +118,7 @@ export class TransformationsEditor extends React.PureComponent<TransformationsEd
return ( return (
<> <>
{this.renderTransformationEditors()} {this.renderTransformationEditors()}
<Button variant="inverse" icon="fa fa-plus" onClick={this.onTransformationAdd}> <Button variant="secondary" icon="fa fa-plus" onClick={this.onTransformationAdd}>
Add transformation Add transformation
</Button> </Button>
</> </>

View File

@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
import LegacyMappingRow from './LegacyMappingRow'; import LegacyMappingRow from './LegacyMappingRow';
import { MappingType, ValueMapping } from '@grafana/data'; import { MappingType, ValueMapping } from '@grafana/data';
import { Button } from '../Button/Button'; import { Button } from '../Button';
import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup'; import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup';
export interface Props { export interface Props {
@ -98,7 +98,7 @@ export class LegacyValueMappingsEditor extends PureComponent<Props, State> {
removeValueMapping={() => this.onRemoveMapping(valueMapping.id)} removeValueMapping={() => this.onRemoveMapping(valueMapping.id)}
/> />
))} ))}
<Button variant="inverse" icon="fa fa-plus" onClick={this.onAddMapping}> <Button variant="primary" icon="fa fa-plus" onClick={this.onAddMapping}>
Add mapping Add mapping
</Button> </Button>
</div> </div>

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { MappingType, ValueMapping } from '@grafana/data'; import { MappingType, ValueMapping } from '@grafana/data';
import Forms from '../Forms'; import { Button } from '../Button/Button';
import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer'; import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer';
import { MappingRow } from './MappingRow'; import { MappingRow } from './MappingRow';
@ -66,9 +66,9 @@ export const ValueMappingsEditor: React.FC<Props> = ({ valueMappings, onChange,
</> </>
)} )}
<FullWidthButtonContainer> <FullWidthButtonContainer>
<Forms.Button size="sm" icon="fa fa-plus" onClick={onAdd} aria-label="ValueMappingsEditor add mapping button"> <Button size="sm" icon="fa fa-plus" onClick={onAdd} aria-label="ValueMappingsEditor add mapping button">
Add mapping Add mapping
</Forms.Button> </Button>
</FullWidthButtonContainer> </FullWidthButtonContainer>
</> </>
); );

View File

@ -37,7 +37,7 @@ exports[`Render should render component 1`] = `
<Button <Button
icon="fa fa-plus" icon="fa fa-plus"
onClick={[Function]} onClick={[Function]}
variant="inverse" variant="primary"
> >
Add mapping Add mapping
</Button> </Button>

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { IconType } from '../Icon/types'; import { IconType } from '../Icon/types';
import { SelectableValue } from '@grafana/data'; import { SelectableValue } from '@grafana/data';
import { Button, ButtonVariant } from '../Forms/Button'; import { Button, ButtonVariant } from '../Button';
import { Select } from '../Forms/Select/Select'; import { Select } from '../Forms/Select/Select';
import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer'; import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer';

View File

@ -6,7 +6,6 @@ export { Popover } from './Tooltip/Popover';
export { Portal } from './Portal/Portal'; export { Portal } from './Portal/Portal';
export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar'; export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
export * from './Button/Button';
export { ClipboardButton } from './ClipboardButton/ClipboardButton'; export { ClipboardButton } from './ClipboardButton/ClipboardButton';
// Select // Select
@ -99,7 +98,7 @@ export { LogLabels } from './Logs/LogLabels';
export { LogRows } from './Logs/LogRows'; export { LogRows } from './Logs/LogRows';
export { getLogRowStyles } from './Logs/getLogRowStyles'; export { getLogRowStyles } from './Logs/getLogRowStyles';
export { ToggleButtonGroup, ToggleButton } from './ToggleButtonGroup/ToggleButtonGroup'; export { ToggleButtonGroup, ToggleButton } from './ToggleButtonGroup/ToggleButtonGroup';
// Panel editors // Panel editors./Forms/Legacy/Button/FullWidthButtonContainer
export { FullWidthButtonContainer } from './Button/FullWidthButtonContainer'; export { FullWidthButtonContainer } from './Button/FullWidthButtonContainer';
export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor'; export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor';
export { ClickOutsideWrapper } from './ClickOutsideWrapper/ClickOutsideWrapper'; export { ClickOutsideWrapper } from './ClickOutsideWrapper/ClickOutsideWrapper';
@ -151,7 +150,8 @@ export {
export { FieldConfigItemHeaderTitle } from './FieldConfigs/FieldConfigItemHeaderTitle'; export { FieldConfigItemHeaderTitle } from './FieldConfigs/FieldConfigItemHeaderTitle';
// Next-gen forms // Next-gen forms
export { default as Forms, ButtonVariant } from './Forms'; export { default as Forms } from './Forms';
export * from './Button';
export { ValuePicker } from './ValuePicker/ValuePicker'; export { ValuePicker } from './ValuePicker/ValuePicker';
export { fieldMatchersUI } from './MatchersUI/fieldMatchersUI'; export { fieldMatchersUI } from './MatchersUI/fieldMatchersUI';
export { getStandardFieldConfigs } from './FieldConfigs/standardFieldConfigEditors'; export { getStandardFieldConfigs } from './FieldConfigs/standardFieldConfigEditors';

View File

@ -168,11 +168,11 @@ $table-bg-hover: $dark-6;
// Buttons // Buttons
// ------------------------- // -------------------------
$btn-secondary-bg: $blue-base; $btn-primary-bg: $blue-base;
$btn-secondary-bg-hl: $blue-shade; $btn-primary-bg-hl: $blue-shade;
$btn-primary-bg: $green-base; $btn-secondary-bg: $dark-6;
$btn-primary-bg-hl: $green-shade; $btn-secondary-bg-hl: lighten($dark-6, 4%);
$btn-success-bg: $green-base; $btn-success-bg: $green-base;
$btn-success-bg-hl: $green-shade; $btn-success-bg-hl: $green-shade;

View File

@ -160,11 +160,11 @@ $table-bg-hover: $gray-5;
// Buttons // Buttons
// ------------------------- // -------------------------
$btn-primary-bg: $green-base; $btn-secondary-bg: $gray-5;
$btn-primary-bg-hl: $green-shade; $btn-secondary-bg-hl: $gray-4;
$btn-secondary-bg: $blue-base; $btn-primary-bg: $blue-base;
$btn-secondary-bg-hl: $blue-shade; $btn-primary-bg-hl: $blue-shade;
$btn-success-bg: $green-base; $btn-success-bg: $green-base;
$btn-success-bg-hl: $green-shade; $btn-success-bg-hl: $green-shade;
@ -173,7 +173,6 @@ $btn-danger-bg: $red-base;
$btn-danger-bg-hl: $red-shade; $btn-danger-bg-hl: $red-shade;
$btn-inverse-bg: $gray-5; $btn-inverse-bg: $gray-5;
$btn-inverse-bg-hl: darken($gray-5, 5%);
$btn-inverse-bg-hl: $gray-4; $btn-inverse-bg-hl: $gray-4;
$btn-inverse-text-color: $gray-1; $btn-inverse-text-color: $gray-1;
$btn-inverse-text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4); $btn-inverse-text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);

View File

@ -173,7 +173,6 @@ export function registerAngularDirectives() {
]); ]);
react2AngularDirective('saveDashboardAsButton', SaveDashboardAsButtonConnected, [ react2AngularDirective('saveDashboardAsButton', SaveDashboardAsButtonConnected, [
'variant', 'variant',
'useNewForms',
['getDashboard', { watchDepth: 'reference', wrapApply: true }], ['getDashboard', { watchDepth: 'reference', wrapApply: true }],
['onSaveSuccess', { watchDepth: 'reference', wrapApply: true }], ['onSaveSuccess', { watchDepth: 'reference', wrapApply: true }],
]); ]);

View File

@ -65,7 +65,7 @@ export class OrgSwitcher extends React.PureComponent<Props, State> {
{org.orgId === currentOrgId ? ( {org.orgId === currentOrgId ? (
<Button size="sm">Current</Button> <Button size="sm">Current</Button>
) : ( ) : (
<Button variant="inverse" size="sm" onClick={() => this.setCurrentOrg(org)}> <Button variant="secondary" size="sm" onClick={() => this.setCurrentOrg(org)}>
Switch to Switch to
</Button> </Button>
)} )}

View File

@ -3,7 +3,7 @@ import { css } from 'emotion';
import { NavModel } from '@grafana/data'; import { NavModel } from '@grafana/data';
import Page from '../../core/components/Page/Page'; import Page from '../../core/components/Page/Page';
import { LicenseChrome } from './LicenseChrome'; import { LicenseChrome } from './LicenseChrome';
import { Forms } from '@grafana/ui'; import { LinkButton } from '@grafana/ui';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { StoreState } from '../../types'; import { StoreState } from '../../types';
import { getNavModel } from '../../core/selectors/navModel'; import { getNavModel } from '../../core/selectors/navModel';
@ -69,13 +69,13 @@ const GetEnterprise: React.FC = () => {
const CallToAction: React.FC = () => { const CallToAction: React.FC = () => {
return ( return (
<Forms.LinkButton <LinkButton
variant="primary" variant="primary"
size="lg" size="lg"
href="https://grafana.com/contact?about=grafana-enterprise&utm_source=grafana-upgrade-page" href="https://grafana.com/contact?about=grafana-enterprise&utm_source=grafana-upgrade-page"
> >
Contact us and get a free trial Contact us and get a free trial
</Forms.LinkButton> </LinkButton>
); );
}; };

View File

@ -1,7 +1,7 @@
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Forms } from '@grafana/ui'; import { Forms, Button } from '@grafana/ui';
import { NavModel } from '@grafana/data'; import { NavModel } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
import { StoreState } from '../../types'; import { StoreState } from '../../types';
@ -62,7 +62,7 @@ const UserCreatePage: React.FC<UserCreatePageProps> = ({ navModel, updateLocatio
})} })}
/> />
</Forms.Field> </Forms.Field>
<Forms.Button type="submit">Create user</Forms.Button> <Button type="submit">Create user</Button>
</> </>
); );
}} }}

View File

@ -71,7 +71,7 @@ export class UserLdapSyncInfo extends PureComponent<Props, State> {
<Button variant="secondary" onClick={this.onUserSync}> <Button variant="secondary" onClick={this.onUserSync}>
Sync user Sync user
</Button> </Button>
<LinkButton variant="inverse" href={debugLDAPMappingURL}> <LinkButton variant="secondary" href={debugLDAPMappingURL}>
Debug LDAP Mapping Debug LDAP Mapping
</LinkButton> </LinkButton>
</div> </div>

View File

@ -3,7 +3,7 @@ import { css, cx } from 'emotion';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
import { NavModel } from '@grafana/data'; import { NavModel } from '@grafana/data';
import { Pagination, Forms, Tooltip, HorizontalGroup, stylesFactory } from '@grafana/ui'; import { Pagination, Forms, Tooltip, HorizontalGroup, stylesFactory, LinkButton } from '@grafana/ui';
import { StoreState, UserDTO } from '../../types'; import { StoreState, UserDTO } from '../../types';
import Page from 'app/core/components/Page/Page'; import Page from 'app/core/components/Page/Page';
import { getNavModel } from '../../core/selectors/navModel'; import { getNavModel } from '../../core/selectors/navModel';
@ -53,9 +53,9 @@ const UserListAdminPageUnConnected: React.FC<Props> = props => {
onChange={event => props.changeQuery(event.currentTarget.value)} onChange={event => props.changeQuery(event.currentTarget.value)}
prefix={<i className="fa fa-search" />} prefix={<i className="fa fa-search" />}
/> />
<Forms.LinkButton href="admin/users/create" variant="primary"> <LinkButton href="admin/users/create" variant="primary">
New user New user
</Forms.LinkButton> </LinkButton>
</HorizontalGroup> </HorizontalGroup>
</div> </div>

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { css, cx } from 'emotion'; import { css, cx } from 'emotion';
import { Modal, Themeable, stylesFactory, withTheme, ConfirmButton, Forms } from '@grafana/ui'; import { Modal, Themeable, stylesFactory, withTheme, ConfirmButton, Button } from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
import { UserOrg, Organization } from 'app/types'; import { UserOrg, Organization } from 'app/types';
import { OrgPicker, OrgSelectItem } from 'app/core/components/Select/OrgPicker'; import { OrgPicker, OrgSelectItem } from 'app/core/components/Select/OrgPicker';
@ -52,9 +52,9 @@ export class UserOrgs extends PureComponent<Props, State> {
</table> </table>
</div> </div>
<div className={addToOrgContainerClass}> <div className={addToOrgContainerClass}>
<Forms.Button variant="secondary" onClick={this.showOrgAddModal(true)}> <Button variant="secondary" onClick={this.showOrgAddModal(true)}>
Add user to organization Add user to organization
</Forms.Button> </Button>
</div> </div>
<AddToOrgModal isOpen={showAddOrgModal} onOrgAdd={onOrgAdd} onDismiss={this.showOrgAddModal(false)} /> <AddToOrgModal isOpen={showAddOrgModal} onOrgAdd={onOrgAdd} onDismiss={this.showOrgAddModal(false)} />
</div> </div>
@ -169,7 +169,7 @@ class UnThemedOrgRow extends PureComponent<OrgRowProps, OrgRowState> {
<div className="pull-right"> <div className="pull-right">
<ConfirmButton <ConfirmButton
confirmText="Confirm removal" confirmText="Confirm removal"
confirmVariant="danger" confirmVariant="destructive"
onClick={this.onOrgRemoveClick} onClick={this.onOrgRemoveClick}
onCancel={this.onCancelClick} onCancel={this.onCancelClick}
onConfirm={this.onOrgRemove} onConfirm={this.onOrgRemove}
@ -258,12 +258,12 @@ export class AddToOrgModal extends PureComponent<AddToOrgModalProps, AddToOrgMod
</div> </div>
</div> </div>
<div className={buttonRowClass}> <div className={buttonRowClass}>
<Forms.Button variant="primary" onClick={this.onAddUserToOrg}> <Button variant="primary" onClick={this.onAddUserToOrg}>
Add to organization Add to organization
</Forms.Button> </Button>
<Forms.Button variant="secondary" onClick={this.onCancel}> <Button variant="secondary" onClick={this.onCancel}>
Cancel Cancel
</Forms.Button> </Button>
</div> </div>
</Modal> </Modal>
); );

View File

@ -3,7 +3,7 @@ import { UserDTO } from 'app/types';
import { cx, css } from 'emotion'; import { cx, css } from 'emotion';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
import { ConfirmButton, Input, ConfirmModal, InputStatus, Forms, stylesFactory } from '@grafana/ui'; import { ConfirmButton, Input, ConfirmModal, InputStatus, Button, stylesFactory } from '@grafana/ui';
interface Props { interface Props {
user: UserDTO; user: UserDTO;
@ -125,9 +125,9 @@ export class UserProfile extends PureComponent<Props, State> {
</table> </table>
</div> </div>
<div className={styles.buttonRow}> <div className={styles.buttonRow}>
<Forms.Button variant="destructive" onClick={this.showDeleteUserModal(true)}> <Button variant="destructive" onClick={this.showDeleteUserModal(true)}>
Delete User Delete User
</Forms.Button> </Button>
<ConfirmModal <ConfirmModal
isOpen={showDeleteModal} isOpen={showDeleteModal}
title="Delete user" title="Delete user"
@ -137,13 +137,13 @@ export class UserProfile extends PureComponent<Props, State> {
onDismiss={this.showDeleteUserModal(false)} onDismiss={this.showDeleteUserModal(false)}
/> />
{user.isDisabled ? ( {user.isDisabled ? (
<Forms.Button variant="secondary" onClick={this.onUserEnable}> <Button variant="secondary" onClick={this.onUserEnable}>
Enable User Enable User
</Forms.Button> </Button>
) : ( ) : (
<Forms.Button variant="secondary" onClick={this.showDisableUserModal(true)}> <Button variant="secondary" onClick={this.showDisableUserModal(true)}>
Disable User Disable User
</Forms.Button> </Button>
)} )}
<ConfirmModal <ConfirmModal
isOpen={showDisableModal} isOpen={showDisableModal}

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { ConfirmButton, ConfirmModal, Forms } from '@grafana/ui'; import { ConfirmButton, ConfirmModal, Button } from '@grafana/ui';
import { UserSession } from 'app/types'; import { UserSession } from 'app/types';
interface Props { interface Props {
@ -68,7 +68,7 @@ export class UserSessions extends PureComponent<Props, State> {
<div className="pull-right"> <div className="pull-right">
<ConfirmButton <ConfirmButton
confirmText="Confirm logout" confirmText="Confirm logout"
confirmVariant="danger" confirmVariant="destructive"
onConfirm={this.onSessionRevoke(session.id)} onConfirm={this.onSessionRevoke(session.id)}
> >
Force logout Force logout
@ -82,9 +82,9 @@ export class UserSessions extends PureComponent<Props, State> {
</div> </div>
<div className={logoutFromAllDevicesClass}> <div className={logoutFromAllDevicesClass}>
{sessions.length > 0 && ( {sessions.length > 0 && (
<Forms.Button variant="secondary" onClick={this.showLogoutConfirmationModal(true)}> <Button variant="secondary" onClick={this.showLogoutConfirmationModal(true)}>
Force logout from all devices Force logout from all devices
</Forms.Button> </Button>
)} )}
<ConfirmModal <ConfirmModal
isOpen={showLogoutModal} isOpen={showLogoutModal}

View File

@ -13,7 +13,7 @@
<save-dashboard-button getDashboard="ctrl.getDashboard" aria-label="Dashboard settings aside actions Save button" /> <save-dashboard-button getDashboard="ctrl.getDashboard" aria-label="Dashboard settings aside actions Save button" />
</div> </div>
<div ng-show="ctrl.canSaveAs"> <div ng-show="ctrl.canSaveAs">
<save-dashboard-as-button getDashboard="ctrl.getDashboard" aria-label="Dashboard settings aside actions Save as button" variant="'secondary'" useNewForms="true"/> <save-dashboard-as-button getDashboard="ctrl.getDashboard" aria-label="Dashboard settings aside actions Save as button" variant="'secondary'" />
</div> </div>
</div> </div>
</aside> </aside>

View File

@ -6,7 +6,7 @@ import { css } from 'emotion';
import { InspectHeader } from './InspectHeader'; import { InspectHeader } from './InspectHeader';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { JSONFormatter, Drawer, Select, Table, TabContent, Forms, stylesFactory, CustomScrollbar } from '@grafana/ui'; import { JSONFormatter, Drawer, Select, Table, TabContent, stylesFactory, CustomScrollbar, Button } from '@grafana/ui';
import { getLocationSrv, getDataSourceSrv } from '@grafana/runtime'; import { getLocationSrv, getDataSourceSrv } from '@grafana/runtime';
import { import {
DataFrame, DataFrame,
@ -189,9 +189,9 @@ export class PanelInspector extends PureComponent<Props, State> {
</div> </div>
)} )}
<div className={styles.downloadCsv}> <div className={styles.downloadCsv}>
<Forms.Button variant="primary" onClick={() => this.exportCsv(processed[selected])}> <Button variant="primary" onClick={() => this.exportCsv(processed[selected])}>
Download CSV Download CSV
</Forms.Button> </Button>
</div> </div>
</div> </div>
<div style={{ flexGrow: 1 }}> <div style={{ flexGrow: 1 }}>

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin, SelectableValue } from '@grafana/data'; import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin, SelectableValue } from '@grafana/data';
import { Forms, stylesFactory } from '@grafana/ui'; import { Forms, stylesFactory, Button } from '@grafana/ui';
import { css, cx } from 'emotion'; import { css, cx } from 'emotion';
import config from 'app/core/config'; import config from 'app/core/config';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
@ -198,9 +198,9 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
</div> </div>
<div className={styles.toolbarLeft}> <div className={styles.toolbarLeft}>
<div className={styles.toolbarItem}> <div className={styles.toolbarItem}>
<Forms.Button className={styles.toolbarItem} variant="secondary" onClick={this.onDiscard}> <Button className={styles.toolbarItem} variant="secondary" onClick={this.onDiscard}>
Discard changes Discard changes
</Forms.Button> </Button>
</div> </div>
<div className={styles.toolbarItem}> <div className={styles.toolbarItem}>
<Forms.Select <Forms.Select
@ -213,7 +213,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
<DashNavTimeControls dashboard={dashboard} location={location} updateLocation={updateLocation} /> <DashNavTimeControls dashboard={dashboard} location={location} updateLocation={updateLocation} />
</div> </div>
<div className={styles.toolbarItem}> <div className={styles.toolbarItem}>
<Forms.Button <Button
className={styles.toolbarItem} className={styles.toolbarItem}
icon="fa fa-sliders" icon="fa fa-sliders"
variant="secondary" variant="secondary"

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { css } from 'emotion'; import { Button, ButtonVariant, ModalsController, FullWidthButtonContainer } from '@grafana/ui';
import { Button, Forms, ModalsController } from '@grafana/ui';
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel } from 'app/features/dashboard/state';
import { connectWithProvider } from 'app/core/utils/connectWithReduxStore'; import { connectWithProvider } from 'app/core/utils/connectWithReduxStore';
import { provideModalsContext } from 'app/routes/ReactContainer'; import { provideModalsContext } from 'app/routes/ReactContainer';
@ -24,12 +23,11 @@ export const SaveDashboardButton: React.FC<SaveDashboardButtonProps> = ({
getDashboard, getDashboard,
useNewForms, useNewForms,
}) => { }) => {
const ButtonComponent = useNewForms ? Forms.Button : Button;
return ( return (
<ModalsController> <ModalsController>
{({ showModal, hideModal }) => { {({ showModal, hideModal }) => {
return ( return (
<ButtonComponent <Button
onClick={() => { onClick={() => {
showModal(SaveDashboardModalProxy, { showModal(SaveDashboardModalProxy, {
// TODO[angular-migrations]: Remove tenary op when we migrate Dashboard Settings view to React // TODO[angular-migrations]: Remove tenary op when we migrate Dashboard Settings view to React
@ -40,46 +38,41 @@ export const SaveDashboardButton: React.FC<SaveDashboardButtonProps> = ({
}} }}
> >
Save dashboard Save dashboard
</ButtonComponent> </Button>
); );
}} }}
</ModalsController> </ModalsController>
); );
}; };
export const SaveDashboardAsButton: React.FC<SaveDashboardButtonProps & { variant?: string }> = ({ export const SaveDashboardAsButton: React.FC<SaveDashboardButtonProps & { variant?: ButtonVariant }> = ({
dashboard, dashboard,
onSaveSuccess, onSaveSuccess,
getDashboard, getDashboard,
useNewForms,
variant, variant,
}) => { }) => {
const ButtonComponent = useNewForms ? Forms.Button : Button;
return ( return (
<ModalsController> <ModalsController>
{({ showModal, hideModal }) => { {({ showModal, hideModal }) => {
return ( return (
<ButtonComponent <FullWidthButtonContainer>
/* Styles applied here are specific to dashboard settings view */ <Button
className={css` onClick={() => {
width: 100%; showModal(SaveDashboardAsModal, {
justify-content: center; // TODO[angular-migrations]: Remove tenary op when we migrate Dashboard Settings view to React
`} dashboard: getDashboard ? getDashboard() : dashboard,
onClick={() => { onSaveSuccess,
showModal(SaveDashboardAsModal, { onDismiss: hideModal,
// TODO[angular-migrations]: Remove tenary op when we migrate Dashboard Settings view to React });
dashboard: getDashboard ? getDashboard() : dashboard, }}
onSaveSuccess, // TODO[angular-migrations]: Hacking the different variants for this single button
onDismiss: hideModal, // In Dashboard Settings in sidebar we need to use new form but with inverse variant to make it look like it should
}); // Everywhere else we use old button component :(
}} variant={variant as ButtonVariant}
// TODO[angular-migrations]: Hacking the different variants for this single button >
// In Dashboard Settings in sidebar we need to use new form but with inverse variant to make it look like it should Save As...
// Everywhere else we use old button component :( </Button>
variant={variant as any} </FullWidthButtonContainer>
>
Save As...
</ButtonComponent>
); );
}} }}
</ModalsController> </ModalsController>

View File

@ -89,7 +89,7 @@ const ConfirmPluginDashboardSaveModal: React.FC<SaveDashboardModalProps> = ({ on
<HorizontalGroup justify="center"> <HorizontalGroup justify="center">
<SaveDashboardAsButton dashboard={dashboard} onSaveSuccess={onDismiss} /> <SaveDashboardAsButton dashboard={dashboard} onSaveSuccess={onDismiss} />
<Button <Button
variant="danger" variant="destructive"
onClick={async () => { onClick={async () => {
await onDashboardSave(dashboard.getSaveModelClone(), { overwrite: true }, dashboard); await onDashboardSave(dashboard.getSaveModelClone(), { overwrite: true }, dashboard);
onDismiss(); onDismiss();
@ -97,7 +97,7 @@ const ConfirmPluginDashboardSaveModal: React.FC<SaveDashboardModalProps> = ({ on
> >
Overwrite Overwrite
</Button> </Button>
<Button variant="inverse" onClick={onDismiss}> <Button variant="secondary" onClick={onDismiss}>
Cancel Cancel
</Button> </Button>
</HorizontalGroup> </HorizontalGroup>

View File

@ -101,9 +101,9 @@ export const SaveDashboardAsForm: React.FC<SaveDashboardFormProps & { isNew?: bo
<Button type="submit" aria-label="Save dashboard button"> <Button type="submit" aria-label="Save dashboard button">
Save Save
</Button> </Button>
<Forms.Button variant="secondary" onClick={onCancel}> <Button variant="secondary" onClick={onCancel}>
Cancel Cancel
</Forms.Button> </Button>
</HorizontalGroup> </HorizontalGroup>
</> </>
)} )}

View File

@ -62,9 +62,9 @@ export const SaveDashboardForm: React.FC<SaveDashboardFormProps> = ({ dashboard,
<Button type="submit" aria-label={e2e.pages.SaveDashboardModal.selectors.save}> <Button type="submit" aria-label={e2e.pages.SaveDashboardModal.selectors.save}>
Save Save
</Button> </Button>
<Forms.Button variant="secondary" onClick={onCancel}> <Button variant="secondary" onClick={onCancel}>
Cancel Cancel
</Forms.Button> </Button>
</HorizontalGroup> </HorizontalGroup>
</> </>
)} )}

View File

@ -1,7 +1,7 @@
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import { CustomScrollbar, Forms, Button, HorizontalGroup, JSONFormatter, VerticalGroup } from '@grafana/ui'; import { CustomScrollbar, Button, HorizontalGroup, JSONFormatter, VerticalGroup } from '@grafana/ui';
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard'; import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
import { SaveDashboardFormProps } from '../types'; import { SaveDashboardFormProps } from '../types';
@ -61,9 +61,9 @@ export const SaveProvisionedDashboardForm: React.FC<SaveDashboardFormProps> = ({
Copy JSON to clipboard Copy JSON to clipboard
</CopyToClipboard> </CopyToClipboard>
<Button onClick={saveToFile}>Save JSON to file</Button> <Button onClick={saveToFile}>Save JSON to file</Button>
<Forms.Button variant="secondary" onClick={onCancel}> <Button variant="secondary" onClick={onCancel}>
Cancel Cancel
</Forms.Button> </Button>
</HorizontalGroup> </HorizontalGroup>
</VerticalGroup> </VerticalGroup>
</> </>

View File

@ -108,7 +108,7 @@ export class ShareExport extends PureComponent<Props, State> {
<Button variant="secondary" onClick={this.onViewJson}> <Button variant="secondary" onClick={this.onViewJson}>
View JSON View JSON
</Button> </Button>
<Button variant="inverse" onClick={onDismiss}> <Button variant="secondary" onClick={onDismiss}>
Cancel Cancel
</Button> </Button>
</div> </div>

View File

@ -121,7 +121,7 @@ export class ShareLink extends PureComponent<Props, State> {
<input type="text" className="gf-form-input" defaultValue={shareUrl} /> <input type="text" className="gf-form-input" defaultValue={shareUrl} />
</div> </div>
<div className="gf-form"> <div className="gf-form">
<ClipboardButton variant="inverse" getText={this.getShareUrl} onClipboardCopy={this.onShareUrlCopy}> <ClipboardButton variant="primary" getText={this.getShareUrl} onClipboardCopy={this.onShareUrlCopy}>
Copy Copy
</ClipboardButton> </ClipboardButton>
</div> </div>

View File

@ -249,7 +249,7 @@ export class ShareSnapshot extends PureComponent<Props, State> {
{sharingButtonText} {sharingButtonText}
</Button> </Button>
)} )}
<Button variant="inverse" onClick={onDismiss}> <Button variant="secondary" onClick={onDismiss}>
Cancel Cancel
</Button> </Button>
</div> </div>
@ -268,7 +268,7 @@ export class ShareSnapshot extends PureComponent<Props, State> {
<i className="fa fa-external-link-square"></i> {snapshotUrl} <i className="fa fa-external-link-square"></i> {snapshotUrl}
</a> </a>
<br /> <br />
<ClipboardButton variant="inverse" getText={this.getSnapshotUrl} onClipboardCopy={this.onSnapshotUrlCopy}> <ClipboardButton variant="secondary" getText={this.getSnapshotUrl} onClipboardCopy={this.onSnapshotUrlCopy}>
Copy Link Copy Link
</ClipboardButton> </ClipboardButton>
</div> </div>

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { css, cx } from 'emotion'; import { css, cx } from 'emotion';
import { stylesFactory, useTheme, Forms } from '@grafana/ui'; import { stylesFactory, useTheme, Forms, Button } from '@grafana/ui';
import { GrafanaTheme, AppEvents, DataSourceApi } from '@grafana/data'; import { GrafanaTheme, AppEvents, DataSourceApi } from '@grafana/data';
import { RichHistoryQuery, ExploreId } from 'app/types/explore'; import { RichHistoryQuery, ExploreId } from 'app/types/explore';
import { copyStringToClipboard, createUrlFromRichHistory, createDataQuery } from 'app/core/utils/richHistory'; import { copyStringToClipboard, createUrlFromRichHistory, createDataQuery } from 'app/core/utils/richHistory';
@ -202,10 +202,10 @@ export function RichHistoryCard(props: Props) {
className={styles.textArea} className={styles.textArea}
/> />
<div className={styles.commentButtonRow}> <div className={styles.commentButtonRow}>
<Forms.Button onClick={onUpdateComment}>Save comment</Forms.Button> <Button onClick={onUpdateComment}>Save comment</Button>
<Forms.Button variant="secondary" onClick={onCancelUpdateComment}> <Button variant="secondary" onClick={onCancelUpdateComment}>
Cancel Cancel
</Forms.Button> </Button>
</div> </div>
</div> </div>
); );
@ -257,9 +257,9 @@ export function RichHistoryCard(props: Props) {
</div> </div>
{!activeUpdateComment && ( {!activeUpdateComment && (
<div className={styles.runButton}> <div className={styles.runButton}>
<Forms.Button variant="secondary" onClick={onRunQuery} disabled={isRemoved}> <Button variant="secondary" onClick={onRunQuery} disabled={isRemoved}>
{datasourceInstance?.name === query.datasourceName ? 'Run query' : 'Switch data source and run query'} {datasourceInstance?.name === query.datasourceName ? 'Run query' : 'Switch data source and run query'}
</Forms.Button> </Button>
</div> </div>
)} )}
</div> </div>

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { stylesFactory, useTheme, Forms } from '@grafana/ui'; import { stylesFactory, useTheme, Forms, Button } from '@grafana/ui';
import { GrafanaTheme, AppEvents } from '@grafana/data'; import { GrafanaTheme, AppEvents } from '@grafana/data';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { CoreEvents } from 'app/types'; import { CoreEvents } from 'app/types';
@ -112,9 +112,9 @@ export function RichHistorySettings(props: RichHistorySettingsProps) {
> >
Delete all of your query history, permanently. Delete all of your query history, permanently.
</div> </div>
<Forms.Button variant="destructive" onClick={onDelete}> <Button variant="destructive" onClick={onDelete}>
Clear query history Clear query history
</Forms.Button> </Button>
</div> </div>
); );
} }

View File

@ -36,7 +36,7 @@ export function RunButton(props: Props) {
title={loading ? 'Cancel' : 'Run Query'} title={loading ? 'Cancel' : 'Run Query'}
onClick={() => onRun(loading)} onClick={() => onRun(loading)}
buttonClassName={classNames({ buttonClassName={classNames({
'navbar-button--secondary': !loading, 'navbar-button--primary': !loading,
'navbar-button--danger': loading, 'navbar-button--danger': loading,
'btn--radius-right-0': showDropdown, 'btn--radius-right-0': showDropdown,
})} })}
@ -49,7 +49,7 @@ export function RunButton(props: Props) {
<RefreshPicker <RefreshPicker
onIntervalChanged={onChangeRefreshInterval} onIntervalChanged={onChangeRefreshInterval}
value={refreshInterval} value={refreshInterval}
buttonSelectClassName={`${loading ? 'navbar-button--danger' : 'navbar-button--secondary'} ${ buttonSelectClassName={`${loading ? 'navbar-button--danger' : 'navbar-button--primary'} ${
styles.selectButtonOverride styles.selectButtonOverride
}`} }`}
refreshButton={runButton} refreshButton={runButton}

View File

@ -1,7 +1,7 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
import { NavModel } from '@grafana/data'; import { NavModel } from '@grafana/data';
import { Forms } from '@grafana/ui'; import { Forms, Button } from '@grafana/ui';
import Page from 'app/core/components/Page/Page'; import Page from 'app/core/components/Page/Page';
import { createNewFolder } from '../state/actions'; import { createNewFolder } from '../state/actions';
import { getNavModel } from 'app/core/selectors/navModel'; import { getNavModel } from 'app/core/selectors/navModel';
@ -63,7 +63,7 @@ export class NewDashboardsFolder extends PureComponent<Props> {
})} })}
/> />
</Forms.Field> </Forms.Field>
<Forms.Button type="submit">Create</Forms.Button> <Button type="submit">Create</Button>
</> </>
)} )}
</Forms.Form> </Forms.Form>

View File

@ -1,7 +1,7 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { getBackendSrv } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
import Page from 'app/core/components/Page/Page'; import Page from 'app/core/components/Page/Page';
import { Forms } from '@grafana/ui'; import { Forms, Button } from '@grafana/ui';
import { getConfig } from 'app/core/config'; import { getConfig } from 'app/core/config';
import { StoreState } from 'app/types'; import { StoreState } from 'app/types';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
@ -68,7 +68,7 @@ export const NewOrgPage: FC<PropsWithState> = ({ navModel }) => {
})} })}
/> />
</Forms.Field> </Forms.Field>
<Forms.Button type="submit">Create</Forms.Button> <Button type="submit">Create</Button>
</> </>
); );
}} }}

View File

@ -1,5 +1,5 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { Forms, HorizontalGroup } from '@grafana/ui'; import { Forms, HorizontalGroup, Button, LinkButton } from '@grafana/ui';
import { getConfig } from 'app/core/config'; import { getConfig } from 'app/core/config';
import { OrgRole } from 'app/types'; import { OrgRole } from 'app/types';
import { getBackendSrv } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
@ -71,10 +71,10 @@ export const UserInviteForm: FC<Props> = ({ updateLocation }) => {
<Forms.Switch name="sendEmail" ref={register} /> <Forms.Switch name="sendEmail" ref={register} />
</Forms.Field> </Forms.Field>
<HorizontalGroup> <HorizontalGroup>
<Forms.Button type="submit">Submit</Forms.Button> <Button type="submit">Submit</Button>
<Forms.LinkButton href={assureBaseUrl(getConfig().appSubUrl + '/org/users')} variant="secondary"> <LinkButton href={assureBaseUrl(getConfig().appSubUrl + '/org/users')} variant="secondary">
Back Back
</Forms.LinkButton> </LinkButton>
</HorizontalGroup> </HorizontalGroup>
</> </>
); );

View File

@ -80,7 +80,7 @@ export class AppConfigCtrlWrapper extends PureComponent<Props, State> {
</Button> </Button>
)} )}
{model.enabled && ( {model.enabled && (
<Button variant="danger" onClick={this.disable} className={withRightMargin}> <Button variant="destructive" onClick={this.disable} className={withRightMargin}>
Disable Disable
</Button> </Button>
)} )}

View File

@ -63,7 +63,7 @@ export class ChangePasswordForm extends PureComponent<Props, State> {
<Button variant="primary" onClick={this.onSubmitChangePassword} disabled={isSaving}> <Button variant="primary" onClick={this.onSubmitChangePassword} disabled={isSaving}>
Change Password Change Password
</Button> </Button>
<LinkButton variant="transparent" href={`${config.appSubUrl}/profile`}> <LinkButton variant="secondary" href={`${config.appSubUrl}/profile`}>
Cancel Cancel
</LinkButton> </LinkButton>
</div> </div>

View File

@ -1,5 +1,5 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { Forms } from '@grafana/ui'; import { Forms, Button, LinkButton } from '@grafana/ui';
import { css } from 'emotion'; import { css } from 'emotion';
import { getConfig } from 'app/core/config'; import { getConfig } from 'app/core/config';
@ -106,11 +106,11 @@ export const SignupForm: FC<Props> = props => {
/> />
</Forms.Field> </Forms.Field>
<Forms.Button type="submit">Submit</Forms.Button> <Button type="submit">Submit</Button>
<span className={buttonSpacing}> <span className={buttonSpacing}>
<Forms.LinkButton href={getConfig().appSubUrl + '/login'} variant="secondary"> <LinkButton href={getConfig().appSubUrl + '/login'} variant="secondary">
Back Back
</Forms.LinkButton> </LinkButton>
</span> </span>
</> </>
); );

View File

@ -48,7 +48,7 @@ export class UserOrganizations extends PureComponent<Props> {
<span className="btn btn-primary btn-small">Current</span> <span className="btn btn-primary btn-small">Current</span>
) : ( ) : (
<Button <Button
variant="inverse" variant="secondary"
size="sm" size="sm"
onClick={() => { onClick={() => {
this.props.setUserOrg(org); this.props.setUserOrg(org);

View File

@ -4,7 +4,7 @@ import { connect, MapStateToProps, MapDispatchToProps } from 'react-redux';
import { StoreState } from 'app/types'; import { StoreState } from 'app/types';
import { updateLocation } from 'app/core/actions'; import { updateLocation } from 'app/core/actions';
import { UrlQueryValue, getBackendSrv } from '@grafana/runtime'; import { UrlQueryValue, getBackendSrv } from '@grafana/runtime';
import { Forms } from '@grafana/ui'; import { Forms, Button } from '@grafana/ui';
import { useAsync } from 'react-use'; import { useAsync } from 'react-use';
import Page from 'app/core/components/Page/Page'; import Page from 'app/core/components/Page/Page';
import { contextSrv } from 'app/core/core'; import { contextSrv } from 'app/core/core';
@ -115,7 +115,7 @@ const SingupInvitedPageUnconnected: FC<DispatchProps & ConnectedProps> = ({ code
/> />
</Forms.Field> </Forms.Field>
<Forms.Button type="submit">Sign Up</Forms.Button> <Button type="submit">Sign Up</Button>
</> </>
)} )}
</Forms.Form> </Forms.Form>

View File

@ -49,7 +49,7 @@ export const DataLink = (props: Props) => {
onChange={handleChange('field')} onChange={handleChange('field')}
/> />
<Button <Button
variant={'inverse'} variant={'destructive'}
title="Remove field" title="Remove field"
icon={'fa fa-times'} icon={'fa fa-times'}
onClick={event => { onClick={event => {

View File

@ -63,7 +63,7 @@ export const DataLinks = (props: Props) => {
})} })}
<div> <div>
<Button <Button
variant={'inverse'} variant={'secondary'}
className={css` className={css`
margin-right: 10px; margin-right: 10px;
`} `}

View File

@ -66,7 +66,7 @@ export const DerivedField = (props: Props) => {
} }
/> />
<Button <Button
variant={'inverse'} variant="destructive"
title="Remove field" title="Remove field"
icon={'fa fa-times'} icon={'fa fa-times'}
onClick={event => { onClick={event => {

View File

@ -66,7 +66,7 @@ export const DerivedFields = (props: Props) => {
})} })}
<div> <div>
<Button <Button
variant={'inverse'} variant="primary"
className={css` className={css`
margin-right: 10px; margin-right: 10px;
`} `}
@ -81,7 +81,7 @@ export const DerivedFields = (props: Props) => {
</Button> </Button>
{value && value.length > 0 && ( {value && value.length > 0 && (
<Button variant="inverse" onClick={() => setShowDebug(!showDebug)}> <Button variant="secondary" onClick={() => setShowDebug(!showDebug)}>
{showDebug ? 'Hide example log message' : 'Show example log message'} {showDebug ? 'Hide example log message' : 'Show example log message'}
</Button> </Button>
)} )}

View File

@ -58,7 +58,7 @@ export class NewsPanelEditor extends PureComponent<PanelEditorProps<NewsOptions>
<div> <div>
<br /> <br />
<div>If the feed is unable to connect, consider a CORS proxy</div> <div>If the feed is unable to connect, consider a CORS proxy</div>
<Button variant="inverse" onClick={this.onSetProxyPrefix}> <Button variant="secondary" onClick={this.onSetProxyPrefix}>
Use Proxy Use Proxy
</Button> </Button>
</div> </div>

View File

@ -171,11 +171,11 @@ $table-bg-hover: $dark-6;
// Buttons // Buttons
// ------------------------- // -------------------------
$btn-secondary-bg: $blue-base; $btn-primary-bg: $blue-base;
$btn-secondary-bg-hl: $blue-shade; $btn-primary-bg-hl: $blue-shade;
$btn-primary-bg: $green-base; $btn-secondary-bg: $dark-6;
$btn-primary-bg-hl: $green-shade; $btn-secondary-bg-hl: lighten($dark-6, 4%);
$btn-success-bg: $green-base; $btn-success-bg: $green-base;
$btn-success-bg-hl: $green-shade; $btn-success-bg-hl: $green-shade;

View File

@ -163,11 +163,11 @@ $table-bg-hover: $gray-5;
// Buttons // Buttons
// ------------------------- // -------------------------
$btn-primary-bg: $green-base; $btn-secondary-bg: $gray-5;
$btn-primary-bg-hl: $green-shade; $btn-secondary-bg-hl: $gray-4;
$btn-secondary-bg: $blue-base; $btn-primary-bg: $blue-base;
$btn-secondary-bg-hl: $blue-shade; $btn-primary-bg-hl: $blue-shade;
$btn-success-bg: $green-base; $btn-success-bg: $green-base;
$btn-success-bg-hl: $green-shade; $btn-success-bg-hl: $green-shade;
@ -176,7 +176,6 @@ $btn-danger-bg: $red-base;
$btn-danger-bg-hl: $red-shade; $btn-danger-bg-hl: $red-shade;
$btn-inverse-bg: $gray-5; $btn-inverse-bg: $gray-5;
$btn-inverse-bg-hl: darken($gray-5, 5%);
$btn-inverse-bg-hl: $gray-4; $btn-inverse-bg-hl: $gray-4;
$btn-inverse-text-color: $gray-1; $btn-inverse-text-color: $gray-1;
$btn-inverse-text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4); $btn-inverse-text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);

View File

@ -171,8 +171,8 @@ i.navbar-page-btn__search {
} }
} }
&--secondary { &--primary {
@include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl); @include buttonBackground($btn-primary-bg, $btn-primary-bg-hl);
} }
&:hover { &:hover {