mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Grafana-UI: Alert components tweaks (#27128)
* Grafana-UI: Add Alert docs and story * Grafana-UI: Move Alert styles to emotion * Grafana-UI: Tweak docs * Grafana-UI: Update test * Grafana-UI: Add outline to custom content * Grafana-UI: Remove class name * Grafana-UI: Remove _Alert.scss * Grafana-UI: Add e2e Alert selector * Grafana-UI: Use @deprecated * Grafana-UI: Remove circular reference * Grafana-UI: Final tweaks
This commit is contained in:
@@ -95,6 +95,9 @@ export const Components = {
|
|||||||
AlertTab: {
|
AlertTab: {
|
||||||
content: 'Alert editor tab content',
|
content: 'Alert editor tab content',
|
||||||
},
|
},
|
||||||
|
Alert: {
|
||||||
|
alert: (severity: string) => `Alert ${severity}`,
|
||||||
|
},
|
||||||
TransformTab: {
|
TransformTab: {
|
||||||
content: 'Transform editor tab content',
|
content: 'Transform editor tab content',
|
||||||
newTransform: (title: string) => `New transform ${title}`,
|
newTransform: (title: string) => `New transform ${title}`,
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import { e2e } from '../index';
|
|||||||
|
|
||||||
export const assertSuccessNotification = () => {
|
export const assertSuccessNotification = () => {
|
||||||
e2e()
|
e2e()
|
||||||
.get('.alert-success')
|
.get('[aria-label^="Alert success"]')
|
||||||
.should('exist');
|
.should('exist');
|
||||||
};
|
};
|
||||||
|
|||||||
17
packages/grafana-ui/src/components/Alert/Alert.mdx
Normal file
17
packages/grafana-ui/src/components/Alert/Alert.mdx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
|
||||||
|
import { Alert } from './Alert';
|
||||||
|
|
||||||
|
<Meta title="MDX|Alert" component={Alert} />
|
||||||
|
|
||||||
|
# Alert
|
||||||
|
|
||||||
|
An alert displays an important message in a way that attracts the user's attention without interrupting the user's task.
|
||||||
|
`onRemove` handler can be used to enable manually dismissing the alert.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<Alert title="Some very important message" severity="info" />
|
||||||
|
```
|
||||||
|
|
||||||
|
<Props of={Alert}/>
|
||||||
41
packages/grafana-ui/src/components/Alert/Alert.story.tsx
Normal file
41
packages/grafana-ui/src/components/Alert/Alert.story.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { select } from '@storybook/addon-knobs';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
import { Alert, AlertVariant } from './Alert';
|
||||||
|
import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||||
|
import mdx from '../Alert/Alert.mdx';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Overlays/Alert',
|
||||||
|
component: Alert,
|
||||||
|
decorators: [withCenteredStory, withHorizontallyCenteredStory],
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
page: mdx,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const severities: AlertVariant[] = ['error', 'warning', 'info', 'success'];
|
||||||
|
|
||||||
|
export const basic = () => {
|
||||||
|
const severity = select('Severity', severities, 'info');
|
||||||
|
return <Alert title="Some very important message" severity={severity} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const withRemove = () => {
|
||||||
|
const severity = select('Severity', severities, 'info');
|
||||||
|
return <Alert title="Some very important message" severity={severity} onRemove={action('Remove button clicked')} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const customButtonContent = () => {
|
||||||
|
const severity = select('Severity', severities, 'info');
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
title="Some very important message"
|
||||||
|
severity={severity}
|
||||||
|
buttonContent={<span>Close</span>}
|
||||||
|
onRemove={action('Remove button clicked')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,62 +1,138 @@
|
|||||||
import React, { FC, ReactNode } from 'react';
|
import React, { FC, ReactNode } from 'react';
|
||||||
import classNames from 'classnames';
|
import { css } from 'emotion';
|
||||||
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
import { useTheme } from '../../themes';
|
||||||
import { Icon } from '../Icon/Icon';
|
import { Icon } from '../Icon/Icon';
|
||||||
import { IconName } from '../../types/icon';
|
import { IconName } from '../../types/icon';
|
||||||
|
|
||||||
export type AlertVariant = 'success' | 'warning' | 'error' | 'info';
|
export type AlertVariant = 'success' | 'warning' | 'error' | 'info';
|
||||||
|
|
||||||
interface AlertProps {
|
export interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
buttonText?: string;
|
/** On click handler for alert button, mostly used for dismissing the alert */
|
||||||
onButtonClick?: (event: React.MouseEvent) => void;
|
|
||||||
onRemove?: (event: React.MouseEvent) => void;
|
onRemove?: (event: React.MouseEvent) => void;
|
||||||
severity?: AlertVariant;
|
severity?: AlertVariant;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
/** Custom component or text for alert button */
|
||||||
|
buttonContent?: ReactNode | string;
|
||||||
|
/** @deprecated */
|
||||||
|
/** Deprecated use onRemove instead */
|
||||||
|
onButtonClick?: (event: React.MouseEvent) => void;
|
||||||
|
/** @deprecated */
|
||||||
|
/** Deprecated use buttonContent instead */
|
||||||
|
buttonText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIconFromSeverity(severity: AlertVariant): string {
|
function getIconFromSeverity(severity: AlertVariant): string {
|
||||||
switch (severity) {
|
switch (severity) {
|
||||||
case 'error': {
|
case 'error':
|
||||||
|
case 'warning':
|
||||||
return 'exclamation-triangle';
|
return 'exclamation-triangle';
|
||||||
}
|
case 'info':
|
||||||
case 'warning': {
|
|
||||||
return 'exclamation-triangle';
|
|
||||||
}
|
|
||||||
case 'info': {
|
|
||||||
return 'info-circle';
|
return 'info-circle';
|
||||||
}
|
case 'success':
|
||||||
case 'success': {
|
|
||||||
return 'check';
|
return 'check';
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Alert: FC<AlertProps> = ({ title, buttonText, onButtonClick, onRemove, children, severity = 'error' }) => {
|
export const Alert: FC<Props> = ({
|
||||||
const alertClass = classNames('alert', `alert-${severity}`);
|
title,
|
||||||
|
buttonText,
|
||||||
|
onButtonClick,
|
||||||
|
onRemove,
|
||||||
|
children,
|
||||||
|
buttonContent,
|
||||||
|
severity = 'error',
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getStyles(theme, severity, !!buttonContent);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="alert-container">
|
<div className={styles.container}>
|
||||||
<div className={alertClass}>
|
<div className={styles.alert} aria-label={selectors.components.Alert.alert(severity)}>
|
||||||
<div className="alert-icon">
|
<div className={styles.icon}>
|
||||||
<Icon size="xl" name={getIconFromSeverity(severity) as IconName} />
|
<Icon size="xl" name={getIconFromSeverity(severity) as IconName} />
|
||||||
</div>
|
</div>
|
||||||
<div className="alert-body">
|
<div className={styles.body}>
|
||||||
<div className="alert-title">{title}</div>
|
<div className={styles.title}>{title}</div>
|
||||||
{children && <div className="alert-text">{children}</div>}
|
{children && <div>{children}</div>}
|
||||||
</div>
|
</div>
|
||||||
{/* If onRemove is specified , giving preference to onRemove */}
|
{/* If onRemove is specified, giving preference to onRemove */}
|
||||||
{onRemove && (
|
{onRemove ? (
|
||||||
<button type="button" className="alert-close" onClick={onRemove}>
|
<button type="button" className={styles.close} onClick={onRemove}>
|
||||||
<Icon name="times" size="lg" />
|
{buttonContent || <Icon name="times" size="lg" />}
|
||||||
</button>
|
</button>
|
||||||
)}
|
) : onButtonClick ? (
|
||||||
{onButtonClick && (
|
|
||||||
<button type="button" className="btn btn-outline-danger" onClick={onButtonClick}>
|
<button type="button" className="btn btn-outline-danger" onClick={onButtonClick}>
|
||||||
{buttonText}
|
{buttonText}
|
||||||
</button>
|
</button>
|
||||||
)}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme, severity: AlertVariant, outline: boolean) => {
|
||||||
|
const { redBase, redShade, greenBase, greenShade, blue80, blue77, white } = theme.palette;
|
||||||
|
const backgrounds = {
|
||||||
|
error: css`
|
||||||
|
background: linear-gradient(90deg, ${redBase}, ${redShade});
|
||||||
|
`,
|
||||||
|
warning: css`
|
||||||
|
background: linear-gradient(90deg, ${redBase}, ${redShade});
|
||||||
|
`,
|
||||||
|
info: css`
|
||||||
|
background: linear-gradient(100deg, ${blue80}, ${blue77});
|
||||||
|
`,
|
||||||
|
success: css`
|
||||||
|
background: linear-gradient(100deg, ${greenBase}, ${greenShade});
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
container: css`
|
||||||
|
z-index: ${theme.zIndex.tooltip};
|
||||||
|
`,
|
||||||
|
alert: css`
|
||||||
|
padding: 15px 20px;
|
||||||
|
margin-bottom: ${theme.spacing.xs};
|
||||||
|
position: relative;
|
||||||
|
color: ${white};
|
||||||
|
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: ${theme.border.radius.md};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
${backgrounds[severity]}
|
||||||
|
`,
|
||||||
|
icon: css`
|
||||||
|
padding: 0 ${theme.spacing.md} 0 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 35px;
|
||||||
|
`,
|
||||||
|
title: css`
|
||||||
|
font-weight: ${theme.typography.weight.semibold};
|
||||||
|
`,
|
||||||
|
body: css`
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0 ${theme.spacing.md} 0 0;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: ${white};
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
close: css`
|
||||||
|
background: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: ${outline ? `1px solid ${white}` : 'none'};
|
||||||
|
border-radius: ${theme.border.radius.sm};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
//
|
|
||||||
// Alerts
|
|
||||||
// --------------------------------------------------
|
|
||||||
|
|
||||||
// Base styles
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
padding: 15px 20px;
|
|
||||||
margin-bottom: $space-xs;
|
|
||||||
text-shadow: 0 2px 0 rgba(255, 255, 255, 0.5);
|
|
||||||
background: $alert-error-bg;
|
|
||||||
position: relative;
|
|
||||||
color: $white;
|
|
||||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
|
|
||||||
border-radius: $border-radius;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alternate styles
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
.alert-success {
|
|
||||||
background: $alert-success-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-danger,
|
|
||||||
.alert-error {
|
|
||||||
background: $alert-error-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-info {
|
|
||||||
background: $alert-info-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-warning {
|
|
||||||
background: $alert-warning-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-container {
|
|
||||||
z-index: $zindex-tooltip;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-alert-list {
|
|
||||||
z-index: 8000;
|
|
||||||
min-width: 400px;
|
|
||||||
max-width: 600px;
|
|
||||||
position: fixed;
|
|
||||||
right: 10px;
|
|
||||||
top: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-close {
|
|
||||||
padding: 0 0 0 $space-md;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.fa {
|
|
||||||
align-self: flex-end;
|
|
||||||
font-size: 21px;
|
|
||||||
color: rgba(255, 255, 255, 0.75);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-title {
|
|
||||||
font-weight: $font-weight-semi-bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-icon {
|
|
||||||
padding: 0 $space-md 0 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 35px;
|
|
||||||
.fa {
|
|
||||||
font-size: 21px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-body {
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: $white;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-icon-on-top {
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
@@ -12,5 +12,4 @@
|
|||||||
@import 'TableInputCSV/TableInputCSV';
|
@import 'TableInputCSV/TableInputCSV';
|
||||||
@import 'TimePicker/TimeOfDayPicker';
|
@import 'TimePicker/TimeOfDayPicker';
|
||||||
@import 'Tooltip/Tooltip';
|
@import 'Tooltip/Tooltip';
|
||||||
@import 'Alert/Alert';
|
|
||||||
@import 'Slider/Slider';
|
@import 'Slider/Slider';
|
||||||
|
|||||||
Reference in New Issue
Block a user