mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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)`,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user