Drawer: align component style with PanelInspector (#23694)

* Drawer: add subtitle, expandable button and refactor style

* Drawer: update header style and z-index

* Drawer: refactor Drawer component and PanelInspector

* PanelInspector: add expandable

* Drawer: update stories with new props

* Inspector: rename InspectHeader -> InspectSubtitle

* Inspector: fix tabs spacing

* Drawer: remove z-index

* Update public/app/features/dashboard/components/Inspector/InspectSubtitle.tsx

Co-Authored-By: Dominik Prokop <dominik.prokop@grafana.com>

* Drawer: apply PR feedbacks

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
Agnès Toulet 2020-04-20 15:27:33 +02:00 committed by GitHub
parent 174ee95153
commit d5f8d976f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 108 additions and 139 deletions

View File

@ -33,6 +33,7 @@ export const global = () => {
{state.isOpen && (
<Drawer
title={drawerTitle}
subtitle="This is a subtitle."
onClose={() => {
updateValue({ isOpen: !state.isOpen });
}}
@ -70,6 +71,7 @@ export const longContent = () => {
{state.isOpen && (
<Drawer
scrollableContent
expandable
title="Drawer with long content"
onClose={() => {
updateValue({ isOpen: !state.isOpen });

View File

@ -1,21 +1,25 @@
import React, { CSSProperties, FC, ReactNode } from 'react';
import React, { CSSProperties, FC, ReactNode, useState } from 'react';
import { GrafanaTheme } from '@grafana/data';
import RcDrawer from 'rc-drawer';
import { css } from 'emotion';
import CustomScrollbar from '../CustomScrollbar/CustomScrollbar';
import { Icon } from '../Icon/Icon';
import { IconButton } from '../IconButton/IconButton';
import { stylesFactory, useTheme } from '../../themes';
export interface Props {
children: ReactNode;
/** Title shown at the top of the drawer */
title?: JSX.Element | string;
title?: ReactNode;
/** Subtitle shown below the title */
subtitle?: ReactNode;
/** Should the Drawer be closable by clicking on the mask */
closeOnMaskClick?: boolean;
/** Render the drawer inside a container on the page */
inline?: boolean;
/** Either a number in px or a string with unit postfix */
width?: number | string;
/** Should the Drawer be expandable to full width */
expandable?: boolean;
/** Set to true if the component rendered within in drawer content has its own scroll */
scrollableContent?: boolean;
@ -24,9 +28,6 @@ export interface Props {
}
const getStyles = stylesFactory((theme: GrafanaTheme, scollableContent: boolean) => {
const closeButtonWidth = '50px';
const borderColor = theme.colors.border2;
return {
drawer: css`
.drawer-content {
@ -36,25 +37,23 @@ const getStyles = stylesFactory((theme: GrafanaTheme, scollableContent: boolean)
overflow: hidden;
}
`,
titleWrapper: css`
font-size: ${theme.typography.size.lg};
display: flex;
align-items: baseline;
justify-content: space-between;
border-bottom: 1px solid ${borderColor};
padding: ${theme.spacing.sm} 0 ${theme.spacing.sm} ${theme.spacing.md};
background-color: ${theme.colors.bodyBg};
top: 0;
header: css`
background-color: ${theme.colors.bg2};
z-index: 1;
flex-grow: 0;
padding-top: ${theme.spacing.xs};
`,
close: css`
cursor: pointer;
width: ${closeButtonWidth};
height: 100%;
actions: css`
display: flex;
flex-shrink: 0;
justify-content: center;
align-items: baseline;
justify-content: flex-end;
`,
titleWrapper: css`
margin-bottom: ${theme.spacing.lg};
padding: 0 ${theme.spacing.sm} 0 ${theme.spacing.lg};
`,
titleSpacing: css`
margin-bottom: ${theme.spacing.md};
`,
content: css`
padding: ${theme.spacing.md};
@ -72,10 +71,14 @@ export const Drawer: FC<Props> = ({
closeOnMaskClick = false,
scrollableContent = false,
title,
subtitle,
width = '40%',
expandable = false,
}) => {
const theme = useTheme();
const drawerStyles = getStyles(theme, scrollableContent);
const [isExpanded, setIsExpanded] = useState(false);
const currentWidth = isExpanded ? '100%' : width;
return (
<RcDrawer
@ -85,16 +88,26 @@ export const Drawer: FC<Props> = ({
onClose={onClose}
maskClosable={closeOnMaskClick}
placement="right"
width={width}
width={currentWidth}
getContainer={inline ? false : 'body'}
style={{ position: `${inline && 'absolute'}` } as CSSProperties}
className={drawerStyles.drawer}
>
{typeof title === 'string' && (
<div className={drawerStyles.titleWrapper}>
<div>{title}</div>
<div className={drawerStyles.close} onClick={onClose}>
<Icon name="times" />
<div className={drawerStyles.header}>
<div className={drawerStyles.actions}>
{expandable && !isExpanded && (
<IconButton name="angle-left" size="xl" onClick={() => setIsExpanded(true)} surface="header" />
)}
{expandable && isExpanded && (
<IconButton name="angle-right" size="xl" onClick={() => setIsExpanded(false)} surface="header" />
)}
<IconButton name="times" size="xl" onClick={onClose} surface="header" />
</div>
<div className={drawerStyles.titleWrapper}>
<h3>{title}</h3>
{typeof subtitle === 'string' && <div className="muted">{subtitle}</div>}
{typeof subtitle !== 'string' && subtitle}
</div>
</div>
)}

View File

@ -1,97 +0,0 @@
import React, { FC } from 'react';
import { css } from 'emotion';
import { stylesFactory, Tab, TabsBar, useTheme, IconButton } from '@grafana/ui';
import { GrafanaTheme, SelectableValue, PanelData, getValueFormat, formattedValueToString } from '@grafana/data';
import { InspectTab } from './PanelInspector';
import { PanelModel } from '../../state';
interface Props {
tab: InspectTab;
tabs: Array<{ label: string; value: InspectTab }>;
panelData: PanelData;
panel: PanelModel;
isExpanded: boolean;
onSelectTab: (tab: SelectableValue<InspectTab>) => void;
onClose: () => void;
onToggleExpand: () => void;
}
export const InspectHeader: FC<Props> = ({
tab,
tabs,
onSelectTab,
onClose,
onToggleExpand,
panel,
panelData,
isExpanded,
}) => {
const theme = useTheme();
const styles = getStyles(theme);
return (
<div className={styles.header}>
<div className={styles.actions}>
{!isExpanded && <IconButton name="angle-left" size="xl" onClick={onToggleExpand} surface="header" />}
{isExpanded && <IconButton name="angle-right" size="xl" onClick={onToggleExpand} surface="header" />}
<IconButton name="times" size="xl" onClick={onClose} surface="header" />
</div>
<div className={styles.titleWrapper}>
<h3>{panel.title || 'Panel inspect'}</h3>
<div className="muted">{formatStats(panelData)}</div>
</div>
<TabsBar className={styles.tabsBar}>
{tabs.map((t, index) => {
return (
<Tab
key={`${t.value}-${index}`}
label={t.label}
active={t.value === tab}
onChangeTab={() => onSelectTab(t)}
/>
);
})}
</TabsBar>
</div>
);
};
const getStyles = stylesFactory((theme: GrafanaTheme) => {
const headerBackground = theme.colors.bg2;
return {
header: css`
background-color: ${headerBackground};
z-index: 1;
flex-grow: 0;
padding-top: ${theme.spacing.sm};
`,
actions: css`
position: absolute;
display: flex;
align-items: baseline;
justify-content: space-between;
right: ${theme.spacing.sm};
`,
tabsBar: css`
padding-left: ${theme.spacing.md};
`,
titleWrapper: css`
margin-bottom: ${theme.spacing.lg};
padding: ${theme.spacing.sm} ${theme.spacing.sm} 0 ${theme.spacing.lg};
`,
};
});
function formatStats(panelData: PanelData) {
const { request } = panelData;
if (!request) {
return '';
}
const queryCount = request.targets.length;
const requestTime = request.endTime ? request.endTime - request.startTime : 0;
const formatted = formattedValueToString(getValueFormat('ms')(requestTime));
return `${queryCount} queries with total query time of ${formatted}`;
}

View File

@ -0,0 +1,57 @@
import React, { FC } from 'react';
import { css } from 'emotion';
import { stylesFactory, Tab, TabsBar, useTheme } from '@grafana/ui';
import { GrafanaTheme, SelectableValue, PanelData, getValueFormat, formattedValueToString } from '@grafana/data';
import { InspectTab } from './PanelInspector';
interface Props {
tab: InspectTab;
tabs: Array<{ label: string; value: InspectTab }>;
panelData: PanelData;
onSelectTab: (tab: SelectableValue<InspectTab>) => void;
}
export const InspectSubtitle: FC<Props> = ({ tab, tabs, onSelectTab, panelData }) => {
const theme = useTheme();
const styles = getStyles(theme);
return (
<>
<div className="muted">{formatStats(panelData)}</div>
<TabsBar className={styles.tabsBar}>
{tabs.map((t, index) => {
return (
<Tab
key={`${t.value}-${index}`}
label={t.label}
active={t.value === tab}
onChangeTab={() => onSelectTab(t)}
/>
);
})}
</TabsBar>
</>
);
};
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
tabsBar: css`
padding-left: ${theme.spacing.md};
margin: ${theme.spacing.lg} -${theme.spacing.sm} -${theme.spacing.lg} -${theme.spacing.lg};
`,
};
});
function formatStats(panelData: PanelData) {
const { request } = panelData;
if (!request) {
return '';
}
const queryCount = request.targets.length;
const requestTime = request.endTime ? request.endTime - request.startTime : 0;
const formatted = formattedValueToString(getValueFormat('ms')(requestTime));
return `${queryCount} queries with total query time of ${formatted}`;
}

View File

@ -1,7 +1,7 @@
import React, { PureComponent } from 'react';
import { Unsubscribable } from 'rxjs';
import { connect, MapStateToProps } from 'react-redux';
import { InspectHeader } from './InspectHeader';
import { InspectSubtitle } from './InspectSubtitle';
import { InspectJSONTab } from './InspectJSONTab';
import { QueryInspector } from './QueryInspector';
@ -253,22 +253,10 @@ export class PanelInspectorUnconnected extends PureComponent<Props, State> {
);
}
drawerHeader(tabs: Array<{ label: string; value: InspectTab }>, activeTab: InspectTab) {
const { panel } = this.props;
drawerSubtitle(tabs: Array<{ label: string; value: InspectTab }>, activeTab: InspectTab) {
const { last } = this.state;
return (
<InspectHeader
tabs={tabs}
tab={activeTab}
panelData={last}
onSelectTab={this.onSelectTab}
onClose={this.onClose}
panel={panel}
onToggleExpand={this.onToggleExpand}
isExpanded={this.state.drawerWidth === '100%'}
/>
);
return <InspectSubtitle tabs={tabs} tab={activeTab} panelData={last} onSelectTab={this.onSelectTab} />;
}
getTabs() {
@ -318,7 +306,13 @@ export class PanelInspectorUnconnected extends PureComponent<Props, State> {
}
return (
<Drawer title={this.drawerHeader(tabs, activeTab)} width={drawerWidth} onClose={this.onClose}>
<Drawer
title={panel.title || 'Panel inspect'}
subtitle={this.drawerSubtitle(tabs, activeTab)}
width={drawerWidth}
onClose={this.onClose}
expandable
>
{activeTab === InspectTab.Data && this.renderDataTab()}
<CustomScrollbar autoHeightMin="100%">
<TabContent className={styles.tabContent}>