PanelChrome: Menu is wrapped in a render prop for full outside control (#60537)

* setup menu as a render prop sent down from PanelStateWrapper to PanelChrome

* let the Dropdown take care of opening the menu in PanelChrome

* menu and leftItems are on the right side of the header together

* add storybook examples with menu

* menu does not need to be a callback because it's opened in a Dropdown anyway

* pass down to getPanelMenu whether or not data is streaming atm

* stop loading data as well as streaming from menu

* override menu's style where needed

* reduce snapshot matching in tests
This commit is contained in:
Polina Boneva
2023-01-12 11:10:09 +02:00
committed by GitHub
parent f85a948214
commit 84eb275c8d
11 changed files with 338 additions and 195 deletions

View File

@@ -72,17 +72,16 @@ Dropdown.displayName = 'Dropdown';
const getStyles = (duration: number) => {
return {
appear: css`
opacity: 0;
position: relative;
transform: scaleY(0.5);
transform-origin: top;
`,
appearActive: css`
opacity: 1;
transform: scaleY(1);
transition: transform ${duration}ms cubic-bezier(0.2, 0, 0.2, 1),
opacity ${duration}ms cubic-bezier(0.2, 0, 0.2, 1);
`,
appear: css({
opacity: '0',
position: 'relative',
transform: 'scaleY(0.5)',
transformOrigin: 'top',
}),
appearActive: css({
opacity: '1',
transform: 'scaleY(1)',
transition: `transform ${duration}ms cubic-bezier(0.2, 0, 0.2, 1), opacity ${duration}ms cubic-bezier(0.2, 0, 0.2, 1)`,
}),
};
};

View File

@@ -35,11 +35,10 @@ function getContentStyle(): CSSProperties {
};
}
function renderPanel(name: string, overrides: Partial<PanelChromeProps>) {
function renderPanel(name: string, overrides?: Partial<PanelChromeProps>) {
const props: PanelChromeProps = {
width: 400,
height: 130,
title: 'Default title',
children: () => undefined,
};
@@ -56,6 +55,37 @@ function renderPanel(name: string, overrides: Partial<PanelChromeProps>) {
);
}
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>
);
export const Examples = () => {
const [loading, setLoading] = useState(true);
@@ -65,33 +95,81 @@ export const Examples = () => {
<DashboardStoryCanvas>
<HorizontalGroup spacing="md" align="flex-start">
<VerticalGroup spacing="md">
{renderPanel('Default panel with error status', {
{renderPanel('Error status', {
title: 'Default title',
status: {
message: 'Error text',
onClick: action('ErrorIndicator: onClick fired'),
},
})}
{renderPanel('No padding with error state', {
{renderPanel('No padding, error loadingState', {
padding: 'none',
title: 'Default title',
loadingState: LoadingState.Error,
})}
{renderPanel('Default panel with streaming state', {
{renderPanel('No title, error loadingState', {
loadingState: LoadingState.Error,
})}
{renderPanel('Streaming loadingState', {
title: 'Default title',
loadingState: LoadingState.Streaming,
})}
{renderPanel('Loading loadingState', {
title: 'Default title',
loadingState: LoadingState.Loading,
})}
</VerticalGroup>
<VerticalGroup spacing="md">
{renderPanel('No title', { title: '' })}
{renderPanel('Default panel: no non-required props')}
{renderPanel('No padding, no title', {
padding: 'none',
})}
{renderPanel('Very long title', {
title: 'Very long title that should get ellipsis when there is no more space',
})}
{renderPanel('No title, streaming loadingState', {
loadingState: LoadingState.Streaming,
})}
{renderPanel('No title, loading loadingState', {
loadingState: LoadingState.Loading,
})}
</VerticalGroup>
<VerticalGroup spacing="md">
{renderPanel('Error status, menu', {
title: 'Default title',
menu,
status: {
message: 'Error text',
onClick: action('ErrorIndicator: onClick fired'),
},
})}
{renderPanel('No padding, error loadingState, menu', {
padding: 'none',
title: 'Default title',
menu,
loadingState: LoadingState.Error,
})}
{renderPanel('No title, error loadingState, menu', {
menu,
loadingState: LoadingState.Error,
})}
{renderPanel('Streaming loadingState, menu', {
title: 'Default title',
menu,
loadingState: LoadingState.Streaming,
})}
{renderPanel('Loading loadingState, menu', {
title: 'Default title',
menu,
loadingState: LoadingState.Loading,
})}
</VerticalGroup>
</HorizontalGroup>
<HorizontalGroup spacing="md" align="flex-start">
<VerticalGroup spacing="md">
{renderPanel('Default panel with deprecated error indicator', {
{renderPanel('Deprecated error indicator', {
title: 'Default title',
leftItems: [
<PanelChrome.ErrorIndicator
@@ -101,7 +179,7 @@ export const Examples = () => {
/>,
],
})}
{renderPanel('No padding with deprecated loading indicator', {
{renderPanel('No padding, deprecated loading indicator', {
padding: 'none',
title: 'Default title',
leftItems: [
@@ -113,6 +191,19 @@ export const Examples = () => {
],
})}
</VerticalGroup>
<VerticalGroup spacing="md">
{renderPanel('Deprecated error indicator, menu', {
title: 'Default title',
menu,
leftItems: [
<PanelChrome.ErrorIndicator
key="errorIndicator"
error="Error text"
onClick={action('ErrorIndicator: onClick fired')}
/>,
],
})}
</VerticalGroup>
</HorizontalGroup>
</DashboardStoryCanvas>
);
@@ -166,37 +257,6 @@ const titleItems: PanelChromeInfoState[] = [
},
];
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),

View File

@@ -40,7 +40,7 @@ export interface PanelChromeProps {
padding?: PanelPadding;
title?: string;
titleItems?: PanelChromeInfoState[];
menu?: ReactElement;
menu?: ReactElement | (() => ReactElement);
/** dragClass, hoverHeader not yet implemented */
// dragClass?: string;
hoverHeader?: boolean;
@@ -100,15 +100,15 @@ export function PanelChrome({
const showStreaming = loadingState === LoadingState.Streaming && !isUsingDeprecatedLeftItems;
const renderStatus = () => {
if (isUsingDeprecatedLeftItems) {
return <div className={cx(styles.rightAligned, styles.items)}>{itemsRenderer(leftItems, (item) => item)}</div>;
} else {
const showError = loadingState === LoadingState.Error || status?.message;
return showError ? (
const showError = loadingState === LoadingState.Error || status?.message;
if (!isUsingDeprecatedLeftItems && showError) {
return (
<div className={styles.errorContainer}>
<PanelStatus message={status?.message} onClick={status?.onClick} />
</div>
) : null;
);
} else {
return null;
}
};
return (
@@ -150,18 +150,22 @@ export function PanelChrome({
</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"
/>
</div>
</Dropdown>
)}
<div className={styles.rightAligned}>
{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"
/>
</div>
</Dropdown>
)}
{isUsingDeprecatedLeftItems && <div className={styles.items}>{itemsRenderer(leftItems, (item) => item)}</div>}
</div>
{renderStatus()}
</div>
@@ -286,7 +290,10 @@ const getStyles = (theme: GrafanaTheme2) => {
justifyContent: 'center',
}),
rightAligned: css({
label: 'right-aligned-container',
marginLeft: 'auto',
display: 'flex',
alignItems: 'center',
}),
};
};