From 1454c3723db9a9a7e9786de66fc02f24e7bb0083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 18 Mar 2021 13:22:19 +0100 Subject: [PATCH] PanelChrome: new logic-less emotion based component with no dependency on PanelModel or DashboardModel (#29456) * WIP: Started work on a new panel chrome component * Minor progress * Next icons & state * adding support for leftItems. * fixing duplicated exports of PanelChrome. * adding examples on loading indicator in storybook. * adding API stability docs. * removed dependency on stylesFactory. * fixed docs errors. Co-authored-by: Marcus Andersson --- .../PanelChrome/LoadingIndicator.tsx | 30 +++++ .../PanelChrome/PanelChrome.story.tsx | 103 +++++++++++++++ .../components/PanelChrome/PanelChrome.tsx | 120 ++++++++++++++++++ .../src/components/PanelChrome/index.ts | 21 +++ packages/grafana-ui/src/components/index.ts | 1 + 5 files changed, 275 insertions(+) create mode 100644 packages/grafana-ui/src/components/PanelChrome/LoadingIndicator.tsx create mode 100644 packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx create mode 100644 packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx create mode 100644 packages/grafana-ui/src/components/PanelChrome/index.ts diff --git a/packages/grafana-ui/src/components/PanelChrome/LoadingIndicator.tsx b/packages/grafana-ui/src/components/PanelChrome/LoadingIndicator.tsx new file mode 100644 index 00000000000..3ba2bea7dca --- /dev/null +++ b/packages/grafana-ui/src/components/PanelChrome/LoadingIndicator.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { selectors } from '@grafana/e2e-selectors'; +import { Icon } from '../Icon/Icon'; +import { Tooltip } from '../Tooltip/Tooltip'; + +type LoadingIndicatorProps = { + loading: boolean; + onCancel: () => void; +}; + +/** + * @internal + */ +export const LoadingIndicator: React.FC = ({ onCancel, loading }) => { + if (!loading) { + return null; + } + + return ( + + + + ); +}; diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx new file mode 100644 index 00000000000..c71c3aca5e1 --- /dev/null +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx @@ -0,0 +1,103 @@ +import React, { CSSProperties, useState } from 'react'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { PanelChrome, useTheme, PanelChromeProps } from '@grafana/ui'; +import { HorizontalGroup, VerticalGroup } from '../Layout/Layout'; +import { merge } from 'lodash'; +import { GrafanaTheme } from '@grafana/data'; +import { useInterval } from 'react-use'; + +export default { + title: 'Visualizations/PanelChrome', + component: PanelChrome, + decorators: [withCenteredStory], + parameters: { + docs: {}, + }, +}; + +function renderPanel(name: string, overrides: Partial, theme: GrafanaTheme) { + const props: PanelChromeProps = { + width: 400, + height: 130, + title: 'Default title', + children: () => undefined, + }; + + merge(props, overrides); + + const contentStyle: CSSProperties = { + background: theme.colors.bg2, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }; + + return ( + + {(innerWidth, innerHeight) => { + return
{name}
; + }} +
+ ); +} + +export const StandardPanel = () => { + const theme = useTheme(); + const [loading, setLoading] = useState(true); + + useInterval(() => setLoading(true), 5000); + + return ( +
+ + + {renderPanel('Default panel', {}, theme)} + {renderPanel('No padding', { padding: 'none' }, theme)} + + + {renderPanel('No title', { title: '' }, theme)} + {renderPanel( + 'Very long title', + { title: 'Very long title that should get ellipsis when there is no more space' }, + theme + )} + + +
+ + + {renderPanel( + 'No title and loading indicator', + { + title: '', + leftItems: [ + setLoading(false)} + key="loading-indicator" + />, + ], + }, + theme + )} + + + {renderPanel( + 'Very long title', + { + title: 'Very long title that should get ellipsis when there is no more space', + leftItems: [ + setLoading(false)} + key="loading-indicator" + />, + ], + }, + theme + )} + + +
+ ); +}; diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx new file mode 100644 index 00000000000..9a63bf29b36 --- /dev/null +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx @@ -0,0 +1,120 @@ +import React, { CSSProperties, ReactNode } from 'react'; +import { css } from 'emotion'; +import { useStyles, useTheme } from '../../themes'; +import { GrafanaTheme } from '@grafana/data'; + +/** + * @internal + */ +export interface PanelChromeProps { + width: number; + height: number; + title?: string; + padding?: PanelPadding; + leftItems?: React.ReactNode[]; + children: (innerWidth: number, innerHeight: number) => React.ReactNode; +} + +/** + * @internal + */ +export type PanelPadding = 'none' | 'md'; + +/** + * @internal + */ +export const PanelChrome: React.FC = ({ + title = '', + children, + width, + height, + padding = 'md', + leftItems = [], +}) => { + const theme = useTheme(); + const styles = useStyles(getStyles); + const headerHeight = getHeaderHeight(theme, title, leftItems); + const { contentStyle, innerWidth, innerHeight } = getContentStyle(padding, theme, width, headerHeight, height); + + const headerStyles: CSSProperties = { + height: theme.panelHeaderHeight, + }; + + const containerStyles: CSSProperties = { width, height }; + + return ( +
+
+
{title}
+ {itemsRenderer(leftItems, (items) => { + return
{items}
; + })} +
+
+ {children(innerWidth, innerHeight)} +
+
+ ); +}; + +const getStyles = (theme: GrafanaTheme) => { + return { + container: css` + label: panel-container; + background-color: ${theme.colors.panelBg}; + border: 1px solid ${theme.colors.panelBorder}; + position: relative; + border-radius: 3px; + height: 100%; + display: flex; + flex-direction: column; + flex: 0 0 0; + `, + content: css` + label: panel-content; + width: 100%; + flex-grow: 1; + `, + header: css` + label: panel-header; + display: flex; + align-items: center; + `, + headerTitle: css` + label: panel-header; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + padding-left: ${theme.panelPadding}px; + flex-grow: 1; + `, + leftItems: css` + padding-right: ${theme.panelPadding}px; + `, + }; +}; + +const itemsRenderer = (items: ReactNode[], renderer: (items: ReactNode[]) => ReactNode): ReactNode => { + const toRender = React.Children.toArray(items).filter(Boolean); + return toRender.length > 0 ? renderer(toRender) : null; +}; + +const getHeaderHeight = (theme: GrafanaTheme, title: string, items: ReactNode[]) => { + if (title.length > 0 || items.length > 0) { + return theme.panelHeaderHeight; + } + return 0; +}; + +const getContentStyle = (padding: string, theme: GrafanaTheme, width: number, headerHeight: number, height: number) => { + const chromePadding = padding === 'md' ? theme.panelPadding : 0; + const panelBorder = 1 * 2; + const innerWidth = width - chromePadding * 2 - panelBorder; + const innerHeight = height - headerHeight - chromePadding * 2 - panelBorder; + + const contentStyle: CSSProperties = { + padding: chromePadding, + }; + + return { contentStyle, innerWidth, innerHeight }; +}; diff --git a/packages/grafana-ui/src/components/PanelChrome/index.ts b/packages/grafana-ui/src/components/PanelChrome/index.ts new file mode 100644 index 00000000000..738d07347de --- /dev/null +++ b/packages/grafana-ui/src/components/PanelChrome/index.ts @@ -0,0 +1,21 @@ +import React from 'react'; +import { LoadingIndicator } from './LoadingIndicator'; +import { PanelChrome as PanelChromeComponent, PanelChromeProps } from './PanelChrome'; + +/** + * @internal + */ +export { PanelChromeProps, PanelPadding } from './PanelChrome'; + +/** + * @internal + */ +export interface PanelChromeType extends React.FC { + LoadingIndicator: typeof LoadingIndicator; +} + +/** + * @internal + */ +export const PanelChrome = PanelChromeComponent as PanelChromeType; +PanelChrome.LoadingIndicator = LoadingIndicator; diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index a17d900a7d2..0d609de2171 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -81,6 +81,7 @@ export { BarGauge, BarGaugeDisplayMode } from './BarGauge/BarGauge'; export { GraphTooltipOptions } from './Graph/GraphTooltip/types'; export { VizRepeater, VizRepeaterRenderValueProps } from './VizRepeater/VizRepeater'; export { graphTimeFormat, graphTickFormatter } from './Graph/utils'; +export { PanelChrome, PanelChromeProps, PanelPadding, PanelChromeType } from './PanelChrome'; export { VizLayout, VizLayoutComponentType, VizLayoutLegendProps, VizLayoutProps } from './VizLayout/VizLayout'; export { VizLegendItem, LegendPlacement, LegendDisplayMode, VizLegendOptions } from './VizLegend/types'; export { VizLegend } from './VizLegend/VizLegend';