Modals: design update (#33368)

* Modals: Style update draft

* Modal.ButtonRow to control spacing better in a centralised way

* Remove header border if no tabs

* Added border and made buttons right aligned and changed order of buttons

* Updating the overlay

* Tweaks to paddings

* Updated share modals
This commit is contained in:
Torkel Ödegaard 2021-04-26 18:26:56 +02:00 committed by GitHub
parent de65cef850
commit 4643bfa539
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 176 additions and 166 deletions

View File

@ -74,7 +74,7 @@ export function createComponents(colors: ThemeColors, shadows: ThemeShadows): Th
padding: 1,
},
overlay: {
background: colors.mode === 'dark' ? 'rgba(0, 0, 0, 0.27)' : 'rgba(208, 209, 211, 0.24)',
background: colors.mode === 'dark' ? 'rgba(0, 0, 0, 0.45)' : 'rgba(208, 209, 211, 0.24)',
},
};
}

View File

@ -57,37 +57,35 @@ export const ConfirmModal = ({
return (
<Modal className={styles.modal} title={title} icon={icon} isOpen={isOpen} onDismiss={onDismiss}>
<div className={styles.modalContent}>
<div className={styles.modalText}>
{body}
{description ? <div className={styles.modalDescription}>{description}</div> : null}
{confirmationText ? (
<div className={styles.modalConfirmationInput}>
<HorizontalGroup justify="center">
<Input placeholder={`Type ${confirmationText} to confirm`} onChange={onConfirmationTextChange} />
</HorizontalGroup>
</div>
) : null}
</div>
<HorizontalGroup justify="center">
{onAlternative ? (
<Button variant="primary" onClick={onAlternative}>
{alternativeText}
</Button>
) : null}
<Button
variant="destructive"
onClick={onConfirm}
disabled={disabled}
aria-label={selectors.pages.ConfirmModal.delete}
>
{confirmText}
</Button>
<Button variant="secondary" onClick={onDismiss}>
{dismissText}
</Button>
</HorizontalGroup>
<div className={styles.modalText}>
{body}
{description ? <div className={styles.modalDescription}>{description}</div> : null}
{confirmationText ? (
<div className={styles.modalConfirmationInput}>
<HorizontalGroup>
<Input placeholder={`Type ${confirmationText} to confirm`} onChange={onConfirmationTextChange} />
</HorizontalGroup>
</div>
) : null}
</div>
<Modal.ButtonRow>
<Button variant="secondary" onClick={onDismiss}>
{dismissText}
</Button>
<Button
variant="destructive"
onClick={onConfirm}
disabled={disabled}
aria-label={selectors.pages.ConfirmModal.delete}
>
{confirmText}
</Button>
{onAlternative ? (
<Button variant="primary" onClick={onAlternative}>
{alternativeText}
</Button>
) : null}
</Modal.ButtonRow>
</Modal>
);
};
@ -96,20 +94,14 @@ const getStyles = (theme: GrafanaThemeV2) => ({
modal: css`
width: 500px;
`,
modalContent: css`
text-align: center;
`,
modalText: css({
fontSize: theme.typography.h4.fontSize,
fontSize: theme.typography.h5.fontSize,
color: theme.colors.text.primary,
marginBottom: `calc(${theme.spacing(2)}*2)`,
paddingTop: theme.spacing(2),
}),
modalDescription: css({
fontSize: theme.typography.h6.fontSize,
paddingTop: theme.spacing(2),
fontSize: theme.typography.body.fontSize,
}),
modalConfirmationInput: css({
paddingTop: theme.spacing(2),
paddingTop: theme.spacing(1),
}),
});

View File

@ -26,6 +26,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
wrapper: css`
margin-bottom: ${theme.spacing.formSpacingBase * 4}px;
&:last-child {
margin-bottom: 0;
}
`,
};
});

View File

@ -54,7 +54,7 @@ IconButton.displayName = 'IconButton';
const getStyles = stylesFactory((theme: GrafanaThemeV2, size: IconSize) => {
const hoverColor = theme.colors.action.hover;
const pixelSize = getSvgSize(size);
const hoverSize = pixelSize / 2;
const hoverSize = Math.max(pixelSize / 3, 8);
return {
button: css`

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { oneLineTrim } from 'common-tags';
import { Story, Meta } from '@storybook/react';
import { getAvailableIcons } from '../../types';
import { Modal, ModalTabsHeader, TabContent } from '@grafana/ui';
import { Button, Modal, ModalTabsHeader, TabContent } from '@grafana/ui';
import { css, cx } from '@emotion/css';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
@ -54,6 +54,10 @@ export const Basic: Story = ({ body, title, ...args }) => {
return (
<Modal title={title} {...args}>
{body}
<Modal.ButtonRow>
<Button>Button1</Button>
<Button variant="secondary">Cancel</Button>
</Modal.ButtonRow>
</Modal>
);
};

View File

@ -1,4 +1,4 @@
import React, { FC, PropsWithChildren, useCallback, useEffect } from 'react';
import React, { PropsWithChildren, useCallback, useEffect } from 'react';
import { Portal } from '../Portal/Portal';
import { cx } from '@emotion/css';
import { useTheme2 } from '../../themes';
@ -6,9 +6,12 @@ import { IconName } from '../../types';
import { getModalStyles } from './getModalStyles';
import { ModalHeader } from './ModalHeader';
import { IconButton } from '../IconButton/IconButton';
import { HorizontalGroup } from '../Layout/Layout';
export interface Props {
/** @deprecated no longer used */
icon?: IconName;
/** @deprecated no longer used */
iconTooltip?: string;
/** Title for the modal or custom header element */
title: string | JSX.Element;
@ -23,7 +26,7 @@ export interface Props {
onClickBackdrop?: () => void;
}
export function Modal(props: PropsWithChildren<Props>): ReturnType<FC<Props>> {
export function Modal(props: PropsWithChildren<Props>) {
const {
title,
children,
@ -62,14 +65,16 @@ export function Modal(props: PropsWithChildren<Props>): ReturnType<FC<Props>> {
return null;
}
const headerClass = cx(styles.modalHeader, typeof title !== 'string' && styles.modalHeaderWithTabs);
return (
<Portal>
<div className={cx(styles.modal, className)}>
<div className={styles.modalHeader}>
<div className={headerClass}>
{typeof title === 'string' && <DefaultModalHeader {...props} title={title} />}
{typeof title !== 'string' && title}
<div className={styles.modalHeaderClose}>
<IconButton surface="header" name="times" size="lg" onClick={onDismiss} />
<IconButton surface="header" name="times" size="xl" onClick={onDismiss} />
</div>
</div>
<div className={cx(styles.modalContent, contentClassName)}>{children}</div>
@ -79,6 +84,21 @@ export function Modal(props: PropsWithChildren<Props>): ReturnType<FC<Props>> {
);
}
function ModalButtonRow({ children }: { children: React.ReactNode }) {
const theme = useTheme2();
const styles = getModalStyles(theme);
return (
<div className={styles.modalButtonRow}>
<HorizontalGroup justify="flex-end" spacing="md">
{children}
</HorizontalGroup>
</div>
);
}
Modal.ButtonRow = ModalButtonRow;
interface DefaultModalHeaderProps {
title: string;
icon?: IconName;

View File

@ -2,29 +2,22 @@ import React from 'react';
import { getModalStyles } from './getModalStyles';
import { IconName } from '../../types';
import { useStyles2 } from '../../themes';
import { Icon } from '../Icon/Icon';
import { Tooltip } from '..';
interface Props {
title: string;
/** @deprecated */
icon?: IconName;
/** @deprecated */
iconTooltip?: string;
}
/** @internal */
export const ModalHeader: React.FC<Props> = ({ icon, iconTooltip, title, children }) => {
const styles = useStyles2(getModalStyles);
return (
<>
<h2 className={styles.modalHeaderTitle}>
{icon && !iconTooltip && <Icon name={icon} size="lg" className={styles.modalHeaderIcon} />}
{icon && iconTooltip && (
<Tooltip content={iconTooltip}>
<Icon name={icon} size="lg" className={styles.modalHeaderIcon} />
</Tooltip>
)}
{title}
</h2>
<h2 className={styles.modalHeaderTitle}>{title}</h2>
{children}
</>
);

View File

@ -3,7 +3,7 @@ import { GrafanaThemeV2 } from '@grafana/data';
import { stylesFactory } from '../../themes';
export const getModalStyles = stylesFactory((theme: GrafanaThemeV2) => {
const borderRadius = theme.shape.borderRadius(2);
const borderRadius = theme.shape.borderRadius(1);
return {
modal: css`
@ -12,6 +12,7 @@ export const getModalStyles = stylesFactory((theme: GrafanaThemeV2) => {
background: ${theme.colors.background.primary};
box-shadow: ${theme.shadows.z3};
border-radius: ${borderRadius};
border: 1px solid ${theme.colors.border.weak};
background-clip: padding-box;
outline: none;
width: 750px;
@ -34,17 +35,21 @@ export const getModalStyles = stylesFactory((theme: GrafanaThemeV2) => {
`,
modalHeader: css`
label: modalHeader;
background: ${theme.colors.background.secondary};
border-radius: ${borderRadius} ${borderRadius} 0 0;
display: flex;
height: 42px;
align-items: center;
min-height: 42px;
margin: ${theme.spacing(1, 2, 0, 2)};
`,
modalHeaderWithTabs: css`
border-bottom: 1px solid ${theme.colors.border.weak};
`,
modalHeaderTitle: css`
font-size: ${theme.typography.size.lg};
margin: 0 ${theme.spacing(2)};
margin: ${theme.spacing(0, 4, 0, 1)};
display: flex;
align-items: center;
line-height: 42px;
position: relative;
top: 2px;
`,
modalHeaderIcon: css`
margin-right: ${theme.spacing(2)};
@ -57,15 +62,18 @@ export const getModalStyles = stylesFactory((theme: GrafanaThemeV2) => {
height: 100%;
display: flex;
align-items: center;
color: ${theme.colors.text.secondary};
flex-grow: 1;
justify-content: flex-end;
padding-right: ${theme.spacing(1)};
`,
modalContent: css`
padding: ${theme.spacing(2)};
padding: ${theme.spacing(3)};
overflow: auto;
width: 100%;
max-height: calc(90vh - ${theme.spacing(2)});
max-height: calc(90vh - ${theme.spacing(4)});
`,
modalButtonRow: css`
padding-top: ${theme.spacing(3)};
`,
};
});

View File

@ -10,7 +10,6 @@ interface Props extends HTMLAttributes<HTMLDivElement> {
const getTabContentStyle = stylesFactory((theme: GrafanaThemeV2) => {
return {
tabContent: css`
padding: ${theme.spacing(1)};
background: ${theme.colors.background.primary};
`,
};

View File

@ -1,7 +1,7 @@
import React from 'react';
import { css } from '@emotion/css';
import { sumBy } from 'lodash';
import { Modal, ConfirmModal, HorizontalGroup, Button } from '@grafana/ui';
import { Modal, ConfirmModal, Button } from '@grafana/ui';
import { DashboardModel, PanelModel } from '../../state';
import { useDashboardDelete } from './useDashboardDelete';
import useAsyncFn from 'react-use/lib/useAsyncFn';
@ -88,10 +88,10 @@ const ProvisionedDeleteModal = ({ hideModal, provisionedId }: { hideModal(): voi
<br />
File path: {provisionedId}
</p>
<HorizontalGroup justify="center">
<Modal.ButtonRow>
<Button variant="secondary" onClick={hideModal}>
OK
</Button>
</HorizontalGroup>
</Modal.ButtonRow>
</Modal>
);

View File

@ -1,5 +1,5 @@
import React, { FC, useCallback, useState } from 'react';
import { Button, Field, Form, HorizontalGroup, Input } from '@grafana/ui';
import { Button, Field, Form, Modal, Input } from '@grafana/ui';
import { RepeatRowSelect } from '../RepeatRowSelect/RepeatRowSelect';
@ -33,12 +33,12 @@ export const RowOptionsForm: FC<Props> = ({ repeat, title, onUpdate, onCancel })
<RepeatRowSelect repeat={newRepeat} onChange={onChangeRepeat} />
</Field>
<HorizontalGroup>
<Modal.ButtonRow>
<Button type="submit">Update</Button>
<Button variant="secondary" onClick={onCancel}>
Cancel
</Button>
</HorizontalGroup>
</Modal.ButtonRow>
</>
)}
</Form>

View File

@ -1,5 +1,5 @@
import React, { useEffect } from 'react';
import { Button, ConfirmModal, HorizontalGroup, Modal, stylesFactory, useTheme } from '@grafana/ui';
import { Button, ConfirmModal, Modal, stylesFactory, useTheme } from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data';
import { css } from '@emotion/css';
import { DashboardModel } from 'app/features/dashboard/state';
@ -89,7 +89,7 @@ const ConfirmPluginDashboardSaveModal: React.FC<SaveDashboardModalProps> = ({ on
Use <strong>Save As</strong> to create custom version.
</small>
</div>
<HorizontalGroup justify="center">
<Modal.ButtonRow>
<SaveDashboardAsButton dashboard={dashboard} onSaveSuccess={onDismiss} />
<Button
variant="destructive"
@ -103,7 +103,7 @@ const ConfirmPluginDashboardSaveModal: React.FC<SaveDashboardModalProps> = ({ on
<Button variant="secondary" onClick={onDismiss}>
Cancel
</Button>
</HorizontalGroup>
</Modal.ButtonRow>
</div>
</Modal>
);

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Button, HorizontalGroup, Modal, VerticalGroup } from '@grafana/ui';
import { Button, Modal } from '@grafana/ui';
import { SaveDashboardButton } from './SaveDashboardButton';
import { DashboardModel } from '../../state';
import { css } from '@emotion/css';
@ -27,24 +27,22 @@ export const UnsavedChangesModal: React.FC<UnsavedChangesModalProps> = ({
width: 500px;
`}
>
<VerticalGroup align={'center'} spacing={'md'}>
<h4>Do you want to save your changes?</h4>
<HorizontalGroup justify="center">
<SaveDashboardButton dashboard={dashboard} onSaveSuccess={onSaveSuccess} />
<Button
variant="destructive"
onClick={() => {
onDiscard();
onDismiss();
}}
>
Discard
</Button>
<Button variant="secondary" onClick={onDismiss}>
Cancel
</Button>
</HorizontalGroup>
</VerticalGroup>
<h5>Do you want to save your changes?</h5>
<Modal.ButtonRow>
<Button variant="secondary" onClick={onDismiss}>
Cancel
</Button>
<Button
variant="destructive"
onClick={() => {
onDiscard();
onDismiss();
}}
>
Discard
</Button>
<SaveDashboardButton dashboard={dashboard} onSaveSuccess={onSaveSuccess} />
</Modal.ButtonRow>
</Modal>
);
};

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Button, HorizontalGroup, Input, Switch, Form, Field, InputControl } from '@grafana/ui';
import { Button, Input, Switch, Form, Field, InputControl, Modal } from '@grafana/ui';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
import { SaveDashboardFormProps } from '../types';
@ -114,14 +114,14 @@ export const SaveDashboardAsForm: React.FC<SaveDashboardFormProps & { isNew?: bo
<Field label="Copy tags">
<Switch name="copyTags" ref={register} />
</Field>
<HorizontalGroup>
<Button type="submit" aria-label="Save dashboard button">
Save
</Button>
<Modal.ButtonRow>
<Button variant="secondary" onClick={onCancel}>
Cancel
</Button>
</HorizontalGroup>
<Button type="submit" aria-label="Save dashboard button">
Save
</Button>
</Modal.ButtonRow>
</>
)}
</Form>

View File

@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { Button, Checkbox, Form, HorizontalGroup, TextArea } from '@grafana/ui';
import { Button, Checkbox, Form, Modal, TextArea } from '@grafana/ui';
import { selectors } from '@grafana/e2e-selectors';
import { SaveDashboardFormProps } from '../types';
@ -36,7 +36,7 @@ export const SaveDashboardForm: React.FC<SaveDashboardFormProps> = ({ dashboard,
>
{({ register, errors }) => (
<>
<div className="gf-form-group">
<div>
{hasTimeChanged && (
<Checkbox
label="Save current time range as dashboard default"
@ -58,14 +58,14 @@ export const SaveDashboardForm: React.FC<SaveDashboardFormProps> = ({ dashboard,
<TextArea name="message" ref={register} placeholder="Add a note to describe your changes." autoFocus />
</div>
<HorizontalGroup>
<Button type="submit" aria-label={selectors.pages.SaveDashboardModal.save}>
Save
</Button>
<Modal.ButtonRow>
<Button variant="secondary" onClick={onCancel}>
Cancel
</Button>
</HorizontalGroup>
<Button type="submit" aria-label={selectors.pages.SaveDashboardModal.save}>
Save
</Button>
</Modal.ButtonRow>
</>
)}
</Form>

View File

@ -1,7 +1,7 @@
import React, { useCallback, useState } from 'react';
import { css } from '@emotion/css';
import { saveAs } from 'file-saver';
import { Button, HorizontalGroup, stylesFactory, TextArea, useTheme, VerticalGroup } from '@grafana/ui';
import { Button, Modal, stylesFactory, TextArea, useTheme } from '@grafana/ui';
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
import { SaveDashboardFormProps } from '../types';
import { AppEvents, GrafanaTheme } from '@grafana/data';
@ -29,8 +29,8 @@ export const SaveProvisionedDashboardForm: React.FC<SaveDashboardFormProps> = ({
const styles = getStyles(theme);
return (
<>
<VerticalGroup spacing="lg">
<small>
<div>
<div>
This dashboard cannot be saved from the Grafana UI because it has been provisioned from another source. Copy
the JSON or save it to a file below, then you can update your dashboard in the provisioning source.
<br />
@ -46,8 +46,7 @@ export const SaveProvisionedDashboardForm: React.FC<SaveDashboardFormProps> = ({
</a>{' '}
for more information about provisioning.
</i>
</small>
<div>
<br /> <br />
<strong>File path: </strong> {dashboard.meta.provisionedExternalId}
</div>
<TextArea
@ -58,16 +57,16 @@ export const SaveProvisionedDashboardForm: React.FC<SaveDashboardFormProps> = ({
}}
className={styles.json}
/>
<HorizontalGroup>
<Modal.ButtonRow>
<Button variant="secondary" onClick={onCancel}>
Cancel
</Button>
<CopyToClipboard text={() => dashboardJSON} elType={Button} onSuccess={onCopyToClipboardSuccess}>
Copy JSON to clipboard
</CopyToClipboard>
<Button onClick={saveToFile}>Save JSON to file</Button>
<Button variant="secondary" onClick={onCancel}>
Cancel
</Button>
</HorizontalGroup>
</VerticalGroup>
</Modal.ButtonRow>
</div>
</>
);
};

View File

@ -1,5 +1,5 @@
import React, { FormEvent, PureComponent } from 'react';
import { RadioButtonGroup, Switch, Field, TextArea, ClipboardButton } from '@grafana/ui';
import { RadioButtonGroup, Switch, Field, TextArea, ClipboardButton, Modal } from '@grafana/ui';
import { SelectableValue, AppEvents } from '@grafana/data';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { appEvents } from 'app/core/core';
@ -96,9 +96,11 @@ export class ShareEmbed extends PureComponent<Props, State> {
>
<TextArea rows={5} value={iframeHtml} onChange={this.onIframeHtmlChange}></TextArea>
</Field>
<ClipboardButton variant="primary" getText={this.getIframeHtml} onClipboardCopy={this.onIframeHtmlCopy}>
Copy to clipboard
</ClipboardButton>
<Modal.ButtonRow>
<ClipboardButton variant="primary" getText={this.getIframeHtml} onClipboardCopy={this.onIframeHtmlCopy}>
Copy to clipboard
</ClipboardButton>
</Modal.ButtonRow>
</>
);
}

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react';
import { saveAs } from 'file-saver';
import { Button, Field, Switch } from '@grafana/ui';
import { Button, Field, Modal, Switch } from '@grafana/ui';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal';
import { appEvents } from 'app/core/core';
@ -95,17 +95,17 @@ export class ShareExport extends PureComponent<Props, State> {
<Field label="Export for sharing externally">
<Switch value={shareExternally} onChange={this.onShareExternallyChange} />
</Field>
<div className="gf-form-button-row">
<Button variant="primary" onClick={this.onSaveAsFile}>
Save to file
<Modal.ButtonRow>
<Button variant="secondary" onClick={onDismiss}>
Cancel
</Button>
<Button variant="secondary" onClick={this.onViewJson}>
View JSON
</Button>
<Button variant="secondary" onClick={onDismiss}>
Cancel
<Button variant="primary" onClick={this.onSaveAsFile}>
Save to file
</Button>
</div>
</Modal.ButtonRow>
</>
);
}

View File

@ -1,5 +1,5 @@
import React, { PureComponent } from 'react';
import { Button, ClipboardButton, Icon, Spinner, Select, Input, LinkButton, Field } from '@grafana/ui';
import { Button, ClipboardButton, Icon, Spinner, Select, Input, LinkButton, Field, Modal } from '@grafana/ui';
import { AppEvents, SelectableValue } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
@ -233,19 +233,19 @@ export class ShareSnapshot extends PureComponent<Props, State> {
<Input type="number" width={21} value={timeoutSeconds} onChange={this.onTimeoutChange} />
</Field>
<div className="gf-form-button-row">
<Button variant="primary" disabled={isLoading} onClick={this.createSnapshot()}>
Local Snapshot
<Modal.ButtonRow>
<Button variant="secondary" onClick={onDismiss}>
Cancel
</Button>
{externalEnabled && (
<Button variant="secondary" disabled={isLoading} onClick={this.createSnapshot(true)}>
{sharingButtonText}
</Button>
)}
<Button variant="secondary" onClick={onDismiss}>
Cancel
<Button variant="primary" disabled={isLoading} onClick={this.createSnapshot()}>
Local Snapshot
</Button>
</div>
</Modal.ButtonRow>
</>
);
}

View File

@ -1,8 +1,7 @@
import React, { useState } from 'react';
import { Button, Field, Input, Modal, useStyles } from '@grafana/ui';
import { Button, Field, Input, Modal } from '@grafana/ui';
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
import { PanelModel } from '../../../dashboard/state';
import { css } from '@emotion/css';
import { usePanelSave } from '../../utils/usePanelSave';
interface AddLibraryPanelContentsProps {
onDismiss: () => void;
@ -11,7 +10,6 @@ interface AddLibraryPanelContentsProps {
}
export const AddLibraryPanelContents = ({ panel, initialFolderId, onDismiss }: AddLibraryPanelContentsProps) => {
const styles = useStyles(getStyles);
const [folderId, setFolderId] = useState(initialFolderId);
const [panelTitle, setPanelTitle] = useState(panel.title);
const { saveLibraryPanel } = usePanelSave();
@ -25,7 +23,7 @@ export const AddLibraryPanelContents = ({ panel, initialFolderId, onDismiss }: A
<FolderPicker onChange={({ id }) => setFolderId(id)} initialFolderId={initialFolderId} />
</Field>
<div className={styles.buttons}>
<Modal.ButtonRow>
<Button
onClick={() => {
panel.title = panelTitle;
@ -37,7 +35,7 @@ export const AddLibraryPanelContents = ({ panel, initialFolderId, onDismiss }: A
<Button variant="secondary" onClick={onDismiss}>
Cancel
</Button>
</div>
</Modal.ButtonRow>
</>
);
};
@ -53,10 +51,3 @@ export const AddLibraryPanelModal: React.FC<Props> = ({ isOpen = false, panel, i
</Modal>
);
};
const getStyles = () => ({
buttons: css`
display: flex;
gap: 10px;
`,
});

View File

@ -1,5 +1,5 @@
import React, { FC, useEffect, useMemo, useReducer } from 'react';
import { Button, HorizontalGroup, Modal, useStyles } from '@grafana/ui';
import { Button, Modal, useStyles } from '@grafana/ui';
import { LoadingState } from '@grafana/data';
import { LibraryPanelDTO } from '../../types';
@ -35,14 +35,14 @@ export const DeleteLibraryPanelModal: FC<Props> = ({ libraryPanel, onDismiss, on
{connected ? <HasConnectedDashboards dashboardTitles={dashboardTitles} /> : null}
{!connected ? <Confirm /> : null}
<HorizontalGroup>
<Button variant="destructive" onClick={onConfirm} disabled={connected}>
Delete
</Button>
<Modal.ButtonRow>
<Button variant="secondary" onClick={onDismiss}>
Cancel
</Button>
</HorizontalGroup>
<Button variant="destructive" onClick={onConfirm} disabled={connected}>
Delete
</Button>
</Modal.ButtonRow>
</div>
) : null}
</Modal>

View File

@ -1,5 +1,5 @@
import React, { useCallback, useState } from 'react';
import { Button, HorizontalGroup, Icon, Input, Modal, useStyles } from '@grafana/ui';
import { Button, Icon, Input, Modal, useStyles } from '@grafana/ui';
import { useAsync, useDebounce } from 'react-use';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { usePanelSave } from '../../utils/usePanelSave';
@ -98,7 +98,13 @@ export const SaveLibraryPanelModal: React.FC<Props> = ({
</tbody>
</table>
)}
<HorizontalGroup>
<Modal.ButtonRow>
<Button variant="secondary" onClick={onDismiss}>
Cancel
</Button>
<Button variant="destructive" onClick={discardAndClose}>
Discard
</Button>
<Button
onClick={() => {
saveLibraryPanel(panel, folderId).then(() => {
@ -109,13 +115,7 @@ export const SaveLibraryPanelModal: React.FC<Props> = ({
>
Update all
</Button>
<Button variant="destructive" onClick={discardAndClose}>
Discard
</Button>
<Button variant="secondary" onClick={onDismiss}>
Cancel
</Button>
</HorizontalGroup>
</Modal.ButtonRow>
</div>
</Modal>
);

View File

@ -132,11 +132,11 @@ export const StartModal: FC<StartModalProps> = ({ playlist, onDismiss }) => {
onChange={(e) => setAutofit(e.currentTarget.checked)}
/>
</VerticalGroup>
<div className="gf-form-button-row">
<Modal.ButtonRow>
<Button variant="primary" onClick={onStart}>
Start {playlist.name}
</Button>
</div>
</Modal.ButtonRow>
</Modal>
);
};