mirror of
https://github.com/grafana/grafana.git
synced 2025-02-10 23:55:47 -06:00
PanelChrome: Implementing the new layout on PanelChrome @ grafana/ui (#57203)
* Use newPanelChromeUI feature flag in DashboardPanel panel rendering * just render the PanelChromeUI instead of the PanelChrome * add new props to PanelChrome; have ChromePanel from grafana/ui in DashboardPanel for testing (will remove before finished); * put icons next to the title of PanelChrome header space * arrange PanelChrome's title icons into view/edit/status sections * icons next to title in PanelChrome are surrounded by square focusable space * items to be render in Header in PanelChrome come in as props * PanelChrome accepts items next to title from the outside; currently them being ordered in the left side is okay, right side not so much * revert local changes to DashboardPanel * cleanup unused imports * simple PanelChrome render without any header props * CSS function * add test PanelChrome prop padding * add icons next to title if they are passed to PanelChrome * fixed PanelChrome header: hoverHeader, having a menu prop; * only show icons with correct icon names; show menu icon only on hover over panel container; minor other fixes * attempt to resolve hovering in an RTL test for the menu icon to work as expected * menu opens in a Dropdown if provided as prop * fixing tooltips and aria-labels * Fixed issue with light theme in storybook * comment out props and tests that are not yet used * Fixed issue where content was overflowing the boundaries Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
parent
f3da48bd50
commit
314c22bc5b
@ -57,16 +57,16 @@ export const Menu = Object.assign(MenuComp, {
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
header: css`
|
||||
padding: ${theme.spacing(0.5, 0.5, 1, 0.5)};
|
||||
border-bottom: 1px solid ${theme.colors.border.weak};
|
||||
`,
|
||||
wrapper: css`
|
||||
background: ${theme.colors.background.primary};
|
||||
box-shadow: ${theme.shadows.z3};
|
||||
display: inline-block;
|
||||
border-radius: ${theme.shape.borderRadius()};
|
||||
padding: ${theme.spacing(0.5, 0)};
|
||||
`,
|
||||
header: css({
|
||||
padding: `${theme.spacing(0.5, 0.5, 1, 0.5)}`,
|
||||
borderBottom: `1px solid ${theme.colors.border.weak}`,
|
||||
}),
|
||||
wrapper: css({
|
||||
background: `${theme.colors.background.primary}`,
|
||||
boxShadow: `${theme.shadows.z3}`,
|
||||
display: `inline-block`,
|
||||
borderRadius: `${theme.shape.borderRadius()}`,
|
||||
padding: `${theme.spacing(0.5, 0)}`,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
@ -38,11 +38,11 @@ export const ErrorIndicator: React.FC<ErrorIndicatorProps> = ({ error, onClick }
|
||||
|
||||
const getStyles = () => {
|
||||
return {
|
||||
clickable: css`
|
||||
cursor: pointer;
|
||||
`,
|
||||
icon: css`
|
||||
color: ${commonColorsPalette.red88};
|
||||
`,
|
||||
clickable: css({
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
icon: css({
|
||||
color: `${commonColorsPalette.red88}`,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
@ -40,8 +40,8 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({ onCancel, lo
|
||||
|
||||
const getStyles = () => {
|
||||
return {
|
||||
clickable: css`
|
||||
cursor: pointer;
|
||||
`,
|
||||
clickable: css({
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
@ -4,11 +4,14 @@ import { merge } from 'lodash';
|
||||
import React, { CSSProperties, useState, ReactNode } from 'react';
|
||||
import { useInterval } from 'react-use';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { PanelChrome, useTheme2, PanelChromeProps } from '@grafana/ui';
|
||||
import { PanelChrome, PanelChromeProps } from '@grafana/ui';
|
||||
|
||||
import { DashboardStoryCanvas } from '../../utils/storybook/DashboardStoryCanvas';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { HorizontalGroup, VerticalGroup } from '../Layout/Layout';
|
||||
import { Menu } from '../Menu/Menu';
|
||||
|
||||
import { PanelChromeInfoState } from './PanelChrome';
|
||||
|
||||
const meta: ComponentMeta<typeof PanelChrome> = {
|
||||
title: 'Visualizations/PanelChrome',
|
||||
@ -22,17 +25,16 @@ const meta: ComponentMeta<typeof PanelChrome> = {
|
||||
},
|
||||
};
|
||||
|
||||
function getContentStyle(theme: GrafanaTheme2): CSSProperties {
|
||||
function getContentStyle(): CSSProperties {
|
||||
return {
|
||||
background: theme.colors.background.secondary,
|
||||
color: theme.colors.text.primary,
|
||||
background: 'rgba(230,0,0,0.05)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
};
|
||||
}
|
||||
|
||||
function renderPanel(name: string, overrides: Partial<PanelChromeProps>, theme: GrafanaTheme2) {
|
||||
function renderPanel(name: string, overrides: Partial<PanelChromeProps>) {
|
||||
const props: PanelChromeProps = {
|
||||
width: 400,
|
||||
height: 130,
|
||||
@ -42,7 +44,7 @@ function renderPanel(name: string, overrides: Partial<PanelChromeProps>, theme:
|
||||
|
||||
merge(props, overrides);
|
||||
|
||||
const contentStyle = getContentStyle(theme);
|
||||
const contentStyle = getContentStyle();
|
||||
|
||||
return (
|
||||
<PanelChrome {...props}>
|
||||
@ -54,69 +56,75 @@ function renderPanel(name: string, overrides: Partial<PanelChromeProps>, theme:
|
||||
}
|
||||
|
||||
export const Examples = () => {
|
||||
const theme = useTheme2();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useInterval(() => setLoading(true), 5000);
|
||||
|
||||
return (
|
||||
<div style={{ background: theme.colors.background.canvas, padding: 100 }}>
|
||||
<HorizontalGroup spacing="md">
|
||||
<DashboardStoryCanvas>
|
||||
<HorizontalGroup spacing="md" align="flex-start">
|
||||
<VerticalGroup spacing="md">
|
||||
{renderPanel('Default panel', {}, theme)}
|
||||
{renderPanel('No padding', { padding: 'none' }, theme)}
|
||||
{renderPanel('Default panel with error state indicator', {
|
||||
title: 'Default title',
|
||||
leftItems: [
|
||||
<PanelChrome.ErrorIndicator
|
||||
key="errorIndicator"
|
||||
error="Error text"
|
||||
onClick={action('ErrorIndicator: onClick fired')}
|
||||
/>,
|
||||
],
|
||||
})}
|
||||
{renderPanel('No padding with error state indicator', {
|
||||
padding: 'none',
|
||||
title: 'Default title',
|
||||
leftItems: [
|
||||
<PanelChrome.ErrorIndicator
|
||||
key="errorIndicator"
|
||||
error="Error text"
|
||||
onClick={action('ErrorIndicator: onClick fired')}
|
||||
/>,
|
||||
],
|
||||
})}
|
||||
</VerticalGroup>
|
||||
<VerticalGroup spacing="md">
|
||||
{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', { title: '' })}
|
||||
{renderPanel('Very long title', {
|
||||
title: 'Very long title that should get ellipsis when there is no more space',
|
||||
})}
|
||||
</VerticalGroup>
|
||||
</HorizontalGroup>
|
||||
<div style={{ marginTop: theme.spacing(2) }} />
|
||||
<HorizontalGroup spacing="md">
|
||||
<VerticalGroup spacing="md">
|
||||
{renderPanel(
|
||||
'No title and loading indicator',
|
||||
{
|
||||
title: '',
|
||||
leftItems: [
|
||||
<PanelChrome.LoadingIndicator
|
||||
loading={loading}
|
||||
onCancel={() => setLoading(false)}
|
||||
key="loading-indicator"
|
||||
/>,
|
||||
],
|
||||
},
|
||||
theme
|
||||
)}
|
||||
{renderPanel('No title and loading indicator', {
|
||||
title: '',
|
||||
leftItems: [
|
||||
<PanelChrome.LoadingIndicator
|
||||
loading={loading}
|
||||
onCancel={() => setLoading(false)}
|
||||
key="loading-indicator"
|
||||
/>,
|
||||
],
|
||||
})}
|
||||
</VerticalGroup>
|
||||
<VerticalGroup spacing="md">
|
||||
{renderPanel(
|
||||
'Very long title',
|
||||
{
|
||||
title: 'Very long title that should get ellipsis when there is no more space',
|
||||
leftItems: [
|
||||
<PanelChrome.LoadingIndicator
|
||||
loading={loading}
|
||||
onCancel={() => 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: [
|
||||
<PanelChrome.LoadingIndicator
|
||||
loading={loading}
|
||||
onCancel={() => setLoading(false)}
|
||||
key="loading-indicator"
|
||||
/>,
|
||||
],
|
||||
})}
|
||||
</VerticalGroup>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</DashboardStoryCanvas>
|
||||
);
|
||||
};
|
||||
|
||||
export const Basic: ComponentStory<typeof PanelChrome> = (args: PanelChromeProps) => {
|
||||
const theme = useTheme2();
|
||||
const contentStyle = getContentStyle(theme);
|
||||
const contentStyle = getContentStyle();
|
||||
|
||||
return (
|
||||
<PanelChrome {...args}>
|
||||
@ -139,6 +147,59 @@ const ErrorIcon = [
|
||||
|
||||
const leftItems = { LoadingIcon, ErrorIcon, Default };
|
||||
|
||||
const titleItems: PanelChromeInfoState[] = [
|
||||
{
|
||||
icon: 'info',
|
||||
tooltip:
|
||||
'Description text with very long descriptive words that describe what is going on in the panel and not beyond. Or maybe beyond, not up to us.',
|
||||
},
|
||||
{
|
||||
icon: 'external-link-alt',
|
||||
tooltip: 'wearegoingonanadventure.openanewtab.maybe',
|
||||
onClick: () => {},
|
||||
},
|
||||
{
|
||||
icon: 'clock-nine',
|
||||
tooltip: 'Time range: 2021-09-01 00:00:00 to 2021-09-01 00:00:00',
|
||||
onClick: () => {},
|
||||
},
|
||||
{
|
||||
icon: 'heart',
|
||||
tooltip: 'Health of the panel',
|
||||
},
|
||||
];
|
||||
|
||||
const menu = (
|
||||
<Menu>
|
||||
<Menu.Item label="View" icon="eye" />
|
||||
<Menu.Item label="Edit" icon="edit" />
|
||||
<Menu.Item label="Share" icon="share-alt" />
|
||||
<Menu.Item label="Explore" icon="compass" />
|
||||
<Menu.Item
|
||||
label="Inspect"
|
||||
icon="info-circle"
|
||||
childItems={[
|
||||
<Menu.Item key="subitem1" label="Data" />,
|
||||
<Menu.Item key="subitem2" label="Query" />,
|
||||
<Menu.Item key="subitem3" label="Panel JSON" />,
|
||||
]}
|
||||
/>
|
||||
<Menu.Item
|
||||
label="More"
|
||||
icon="cube"
|
||||
childItems={[
|
||||
<Menu.Item key="subitem1" label="Duplicate" />,
|
||||
<Menu.Item key="subitem2" label="Copy" />,
|
||||
<Menu.Item key="subitem3" label="Create library panel" />,
|
||||
<Menu.Item key="subitem4" label="Hide legend" />,
|
||||
<Menu.Item key="subitem5" label="Get help" />,
|
||||
]}
|
||||
/>
|
||||
<Menu.Divider />
|
||||
<Menu.Item label="Remove" icon="trash-alt" />
|
||||
</Menu>
|
||||
);
|
||||
|
||||
Basic.argTypes = {
|
||||
leftItems: {
|
||||
options: Object.keys(leftItems),
|
||||
@ -157,7 +218,9 @@ Basic.argTypes = {
|
||||
Basic.args = {
|
||||
width: 400,
|
||||
height: 200,
|
||||
title: 'Title text',
|
||||
title: 'Very long title that should get ellipsis when there is no more space',
|
||||
titleItems,
|
||||
menu,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
@ -0,0 +1,107 @@
|
||||
import { screen, render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { PanelChrome, PanelChromeProps } from './PanelChrome';
|
||||
|
||||
const setup = (propOverrides?: Partial<PanelChromeProps>) => {
|
||||
const props: PanelChromeProps = {
|
||||
width: 100,
|
||||
height: 100,
|
||||
children: (innerWidth, innerHeight) => {
|
||||
return <div style={{ width: innerWidth, height: innerHeight, color: 'pink' }}>Panel's Content</div>;
|
||||
},
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
return render(<PanelChrome {...props} />);
|
||||
};
|
||||
|
||||
it('renders an empty panel with required props only', () => {
|
||||
setup();
|
||||
|
||||
expect(screen.getByText("Panel's Content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders an empty panel without padding', () => {
|
||||
setup({ padding: 'none' });
|
||||
|
||||
expect(screen.getByText("Panel's Content").parentElement).toHaveStyle({ padding: '0px' });
|
||||
});
|
||||
|
||||
it('renders an empty panel with padding', () => {
|
||||
setup({ padding: 'md' });
|
||||
|
||||
expect(screen.getByText("Panel's Content").style.getPropertyValue('height')).not.toBe('100px');
|
||||
expect(screen.getByText("Panel's Content").parentElement).not.toHaveStyle({ padding: '0px' });
|
||||
});
|
||||
|
||||
it('renders an empty panel without a header if no title or titleItems', () => {
|
||||
setup();
|
||||
|
||||
expect(screen.queryByTestId('header-container')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders panel with a header if prop title', () => {
|
||||
setup({ title: 'Test Panel Header' });
|
||||
|
||||
expect(screen.getByTestId('header-container')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders panel with a header with title in place if prop title', () => {
|
||||
setup({ title: 'Test Panel Header' });
|
||||
|
||||
expect(screen.getByText('Test Panel Header')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders panel with a header if prop titleItems', () => {
|
||||
setup({
|
||||
titleItems: [
|
||||
{
|
||||
icon: 'info-circle',
|
||||
tooltip: 'This is the panel description',
|
||||
onClick: () => {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('header-container')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders panel with a header with icons in place if prop titleItems', () => {
|
||||
setup({
|
||||
titleItems: [
|
||||
{
|
||||
icon: 'info-circle',
|
||||
tooltip: 'This is the panel description',
|
||||
onClick: () => {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('title-items-container')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders panel with a fixed header if prop hoverHeader is false', () => {
|
||||
setup({ title: 'Test Panel Header', hoverHeader: false });
|
||||
|
||||
expect(screen.getByTestId('header-container')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders panel with a header if prop menu', () => {
|
||||
setup({ menu: <div> Menu </div> });
|
||||
|
||||
expect(screen.getByTestId('header-container')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders panel with a show-on-hover menu icon if prop menu', () => {
|
||||
setup({ menu: <div> Menu </div> });
|
||||
|
||||
expect(screen.getByTestId('menu-icon')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('menu-icon')).not.toBeVisible();
|
||||
});
|
||||
|
||||
it.skip('renders states in the panel header if any given', () => {});
|
||||
|
||||
it.skip('renders leftItems in the panel header if any given when no states prop is given', () => {});
|
||||
|
||||
it.skip('renders states in the panel header if both leftItems and states are given', () => {});
|
@ -1,9 +1,25 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { CSSProperties, ReactNode } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { GrafanaTheme2, isIconName } from '@grafana/data';
|
||||
|
||||
import { useStyles2, useTheme2 } from '../../themes';
|
||||
import { IconName } from '../../types/icon';
|
||||
import { Dropdown } from '../Dropdown/Dropdown';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { IconButton, IconButtonVariant } from '../IconButton/IconButton';
|
||||
import { PopoverContent, Tooltip } from '../Tooltip';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface PanelChromeInfoState {
|
||||
icon: IconName;
|
||||
label?: string | ReactNode;
|
||||
tooltip?: PopoverContent;
|
||||
variant?: IconButtonVariant;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -11,10 +27,22 @@ import { useStyles2, useTheme2 } from '../../themes';
|
||||
export interface PanelChromeProps {
|
||||
width: number;
|
||||
height: number;
|
||||
title?: string;
|
||||
children: (innerWidth: number, innerHeight: number) => ReactNode;
|
||||
padding?: PanelPadding;
|
||||
leftItems?: React.ReactNode[]; // rightItems will be added later (actions links etc.)
|
||||
children: (innerWidth: number, innerHeight: number) => React.ReactNode;
|
||||
title?: string;
|
||||
titleItems?: PanelChromeInfoState[];
|
||||
menu?: React.ReactElement;
|
||||
/** dragClass, hoverHeader, loadingState, and states not yet implemented */
|
||||
// dragClass?: string;
|
||||
hoverHeader?: boolean;
|
||||
// loadingState?: LoadingState;
|
||||
// states?: ReactNode[];
|
||||
/** @deprecated in favor of prop states
|
||||
* which will serve the same purpose
|
||||
* of showing the panel state in the top right corner
|
||||
* of itself or its header
|
||||
* */
|
||||
leftItems?: ReactNode[];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -26,33 +54,86 @@ export type PanelPadding = 'none' | 'md';
|
||||
* @internal
|
||||
*/
|
||||
export const PanelChrome: React.FC<PanelChromeProps> = ({
|
||||
title = '',
|
||||
children,
|
||||
width,
|
||||
height,
|
||||
children,
|
||||
padding = 'md',
|
||||
title = '',
|
||||
titleItems = [],
|
||||
menu,
|
||||
// dragClass,
|
||||
hoverHeader = false,
|
||||
// loadingState,
|
||||
// states = [],
|
||||
leftItems = [],
|
||||
}) => {
|
||||
const theme = useTheme2();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const headerHeight = getHeaderHeight(theme, title, leftItems);
|
||||
const headerHeight = !hoverHeader ? getHeaderHeight(theme, title, leftItems) : 0;
|
||||
const { contentStyle, innerWidth, innerHeight } = getContentStyle(padding, theme, width, headerHeight, height);
|
||||
|
||||
const headerStyles: CSSProperties = {
|
||||
height: headerHeight,
|
||||
};
|
||||
|
||||
const itemStyles: CSSProperties = {
|
||||
minHeight: headerHeight,
|
||||
minWidth: headerHeight,
|
||||
};
|
||||
const containerStyles: CSSProperties = { width, height };
|
||||
|
||||
const handleMenuOpen = () => {};
|
||||
|
||||
const hasHeader = title || titleItems.length > 0 || menu;
|
||||
|
||||
return (
|
||||
<div className={styles.container} style={containerStyles}>
|
||||
<div className={styles.header} style={headerStyles}>
|
||||
<div className={styles.headerTitle}>{title}</div>
|
||||
{itemsRenderer(leftItems, (items) => {
|
||||
return <div className={styles.leftItems}>{items}</div>;
|
||||
})}
|
||||
</div>
|
||||
{hasHeader && !hoverHeader && (
|
||||
<div className={styles.headerContainer} style={headerStyles} data-testid="header-container">
|
||||
{title && (
|
||||
<div title={title} className={styles.title}>
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{titleItems.length > 0 && (
|
||||
<div className={styles.items} data-testid="title-items-container">
|
||||
{titleItems
|
||||
.filter((item) => isIconName(item.icon))
|
||||
.map((item, i) => (
|
||||
<div key={`${item.icon}-${i}`} className={styles.item} style={itemStyles}>
|
||||
{item.onClick ? (
|
||||
<IconButton tooltip={item.tooltip} name={item.icon} size="sm" onClick={item.onClick} />
|
||||
) : (
|
||||
<Tooltip content={item.tooltip ?? ''}>
|
||||
<Icon name={item.icon} size="sm" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{menu && (
|
||||
<Dropdown overlay={menu} placement="bottom">
|
||||
<div className={cx(styles.item, styles.menuItem, 'menu-icon')} data-testid="menu-icon" style={itemStyles}>
|
||||
<IconButton
|
||||
ariaLabel={`Menu for panel with ${title ? `title ${title}` : 'no title'}`}
|
||||
tooltip="Menu"
|
||||
name="ellipsis-v"
|
||||
size="sm"
|
||||
onClick={handleMenuOpen}
|
||||
/>
|
||||
</div>
|
||||
</Dropdown>
|
||||
)}
|
||||
|
||||
{leftItems.length > 0 && (
|
||||
<div className={cx(styles.rightAligned, styles.items)}>{itemsRenderer(leftItems, (item) => item)}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.content} style={contentStyle}>
|
||||
{children(innerWidth, innerHeight)}
|
||||
</div>
|
||||
@ -95,39 +176,59 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
const { padding, background, borderColor } = theme.components.panel;
|
||||
|
||||
return {
|
||||
container: css`
|
||||
label: panel-container;
|
||||
background-color: ${background};
|
||||
border: 1px solid ${borderColor};
|
||||
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.spacing(padding)};
|
||||
flex-grow: 1;
|
||||
font-weight: ${theme.typography.fontWeightMedium};
|
||||
`,
|
||||
leftItems: css`
|
||||
display: flex;
|
||||
padding-right: ${theme.spacing(padding)};
|
||||
`,
|
||||
container: css({
|
||||
label: 'panel-container',
|
||||
backgroundColor: background,
|
||||
border: `1px solid ${borderColor}`,
|
||||
position: 'relative',
|
||||
borderRadius: '3px',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: '0 0 0',
|
||||
|
||||
'&:focus-visible, &:hover': {
|
||||
// only show menu icon on hover or focused panel
|
||||
'.menu-icon': {
|
||||
visibility: 'visible',
|
||||
},
|
||||
},
|
||||
|
||||
'&:focus-visible': {
|
||||
outline: `1px solid ${theme.colors.action.focus}`,
|
||||
},
|
||||
}),
|
||||
content: css({
|
||||
label: 'panel-content',
|
||||
width: '100%',
|
||||
contain: 'strict',
|
||||
flexGrow: 1,
|
||||
}),
|
||||
headerContainer: css({
|
||||
label: 'panel-header',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: `0 ${theme.spacing(padding)}`,
|
||||
}),
|
||||
title: css({
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
}),
|
||||
items: css({
|
||||
display: 'flex',
|
||||
}),
|
||||
item: css({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-around',
|
||||
alignItems: 'center',
|
||||
}),
|
||||
menuItem: css({
|
||||
visibility: 'hidden',
|
||||
}),
|
||||
rightAligned: css({
|
||||
marginLeft: 'auto',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user