Commit f5ee1f93 by Alex Khomenko Committed by GitHub

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
parent 88fbdd67
......@@ -95,6 +95,9 @@ export const Components = {
AlertTab: {
content: 'Alert editor tab content',
},
Alert: {
alert: (severity: string) => `Alert ${severity}`,
},
TransformTab: {
content: 'Transform editor tab content',
newTransform: (title: string) => `New transform ${title}`,
......
......@@ -2,6 +2,6 @@ import { e2e } from '../index';
export const assertSuccessNotification = () => {
e2e()
.get('.alert-success')
.get('[aria-label^="Alert success"]')
.should('exist');
};
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}/>
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')}
/>
);
};
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 { IconName } from '../../types/icon';
export type AlertVariant = 'success' | 'warning' | 'error' | 'info';
interface AlertProps {
export interface Props {
title: string;
buttonText?: string;
onButtonClick?: (event: React.MouseEvent) => void;
/** On click handler for alert button, mostly used for dismissing the alert */
onRemove?: (event: React.MouseEvent) => void;
severity?: AlertVariant;
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 {
switch (severity) {
case 'error': {
return 'exclamation-triangle';
}
case 'warning': {
case 'error':
case 'warning':
return 'exclamation-triangle';
}
case 'info': {
case 'info':
return 'info-circle';
}
case 'success': {
case 'success':
return 'check';
}
default:
return '';
}
}
export const Alert: FC<AlertProps> = ({ title, buttonText, onButtonClick, onRemove, children, severity = 'error' }) => {
const alertClass = classNames('alert', `alert-${severity}`);
export const Alert: FC<Props> = ({
title,
buttonText,
onButtonClick,
onRemove,
children,
buttonContent,
severity = 'error',
}) => {
const theme = useTheme();
const styles = getStyles(theme, severity, !!buttonContent);
return (
<div className="alert-container">
<div className={alertClass}>
<div className="alert-icon">
<div className={styles.container}>
<div className={styles.alert} aria-label={selectors.components.Alert.alert(severity)}>
<div className={styles.icon}>
<Icon size="xl" name={getIconFromSeverity(severity) as IconName} />
</div>
<div className="alert-body">
<div className="alert-title">{title}</div>
{children && <div className="alert-text">{children}</div>}
<div className={styles.body}>
<div className={styles.title}>{title}</div>
{children && <div>{children}</div>}
</div>
{/* If onRemove is specified , giving preference to onRemove */}
{onRemove && (
<button type="button" className="alert-close" onClick={onRemove}>
<Icon name="times" size="lg" />
{/* If onRemove is specified, giving preference to onRemove */}
{onRemove ? (
<button type="button" className={styles.close} onClick={onRemove}>
{buttonContent || <Icon name="times" size="lg" />}
</button>
)}
{onButtonClick && (
) : onButtonClick ? (
<button type="button" className="btn btn-outline-danger" onClick={onButtonClick}>
{buttonText}
</button>
)}
) : null}
</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};
`,
};
};
//
// 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 'TimePicker/TimeOfDayPicker';
@import 'Tooltip/Tooltip';
@import 'Alert/Alert';
@import 'Slider/Slider';
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment