diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 72489b57204..9b78254df32 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -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}`, diff --git a/packages/grafana-e2e/src/flows/assertSuccessNotification.ts b/packages/grafana-e2e/src/flows/assertSuccessNotification.ts index 0d1507a1a9b..ffc04883d96 100644 --- a/packages/grafana-e2e/src/flows/assertSuccessNotification.ts +++ b/packages/grafana-e2e/src/flows/assertSuccessNotification.ts @@ -2,6 +2,6 @@ import { e2e } from '../index'; export const assertSuccessNotification = () => { e2e() - .get('.alert-success') + .get('[aria-label^="Alert success"]') .should('exist'); }; diff --git a/packages/grafana-ui/src/components/Alert/Alert.mdx b/packages/grafana-ui/src/components/Alert/Alert.mdx new file mode 100644 index 00000000000..0c9a1ada63a --- /dev/null +++ b/packages/grafana-ui/src/components/Alert/Alert.mdx @@ -0,0 +1,17 @@ +import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks'; +import { Alert } from './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 + +``` + + diff --git a/packages/grafana-ui/src/components/Alert/Alert.story.tsx b/packages/grafana-ui/src/components/Alert/Alert.story.tsx new file mode 100644 index 00000000000..2ec320a16e8 --- /dev/null +++ b/packages/grafana-ui/src/components/Alert/Alert.story.tsx @@ -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 ; +}; + +export const withRemove = () => { + const severity = select('Severity', severities, 'info'); + return ; +}; + +export const customButtonContent = () => { + const severity = select('Severity', severities, 'info'); + return ( + Close} + onRemove={action('Remove button clicked')} + /> + ); +}; diff --git a/packages/grafana-ui/src/components/Alert/Alert.tsx b/packages/grafana-ui/src/components/Alert/Alert.tsx index 6fb68fbbb3e..3281824bba9 100644 --- a/packages/grafana-ui/src/components/Alert/Alert.tsx +++ b/packages/grafana-ui/src/components/Alert/Alert.tsx @@ -1,62 +1,138 @@ 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': { + case 'error': + case 'warning': return 'exclamation-triangle'; - } - case 'warning': { - return 'exclamation-triangle'; - } - case 'info': { + case 'info': return 'info-circle'; - } - case 'success': { + case 'success': return 'check'; - } default: return ''; } } -export const Alert: FC = ({ title, buttonText, onButtonClick, onRemove, children, severity = 'error' }) => { - const alertClass = classNames('alert', `alert-${severity}`); +export const Alert: FC = ({ + title, + buttonText, + onButtonClick, + onRemove, + children, + buttonContent, + severity = 'error', +}) => { + const theme = useTheme(); + const styles = getStyles(theme, severity, !!buttonContent); + return ( -
-
-
+
+
+
-
-
{title}
- {children &&
{children}
} +
+
{title}
+ {children &&
{children}
}
- {/* If onRemove is specified , giving preference to onRemove */} - {onRemove && ( - - )} - {onButtonClick && ( + ) : onButtonClick ? ( - )} + ) : null}
); }; + +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}; + `, + }; +}; diff --git a/packages/grafana-ui/src/components/Alert/_Alert.scss b/packages/grafana-ui/src/components/Alert/_Alert.scss deleted file mode 100644 index 0d801ebc20b..00000000000 --- a/packages/grafana-ui/src/components/Alert/_Alert.scss +++ /dev/null @@ -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; -} diff --git a/packages/grafana-ui/src/components/index.scss b/packages/grafana-ui/src/components/index.scss index ab8dd512e2f..4f8a24b7bd9 100644 --- a/packages/grafana-ui/src/components/index.scss +++ b/packages/grafana-ui/src/components/index.scss @@ -12,5 +12,4 @@ @import 'TableInputCSV/TableInputCSV'; @import 'TimePicker/TimeOfDayPicker'; @import 'Tooltip/Tooltip'; -@import 'Alert/Alert'; @import 'Slider/Slider';