mirror of
https://github.com/grafana/grafana.git
synced 2025-02-10 23:55:47 -06:00
Navigation: Expose new props to extend Page
/PluginPage
(#58465)
* add extensions and customisation to `Page` * adjust alignment
This commit is contained in:
parent
9778d642df
commit
b3c761aaa7
@ -2,7 +2,18 @@ import React from 'react';
|
||||
|
||||
import { NavModelItem, PageLayoutType } from '@grafana/data';
|
||||
|
||||
export interface PageInfoItem {
|
||||
label: string;
|
||||
value: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface PluginPageProps {
|
||||
/** Can be used to place actions inline with the heading */
|
||||
info?: PageInfoItem[];
|
||||
/** Can be used to place actions inline with the heading */
|
||||
actions?: React.ReactNode;
|
||||
/** Can be used to customize rendering of title */
|
||||
renderTitle?: (title: string) => React.ReactNode;
|
||||
/** Shown under main heading */
|
||||
subTitle?: React.ReactNode;
|
||||
pageNav?: NavModelItem;
|
||||
|
@ -26,7 +26,10 @@ export const OldPage: PageType = ({
|
||||
scrollRef,
|
||||
scrollTop,
|
||||
layout = PageLayoutType.Standard,
|
||||
renderTitle,
|
||||
subTitle,
|
||||
actions,
|
||||
info,
|
||||
...otherProps
|
||||
}) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
@ -41,7 +44,15 @@ export const OldPage: PageType = ({
|
||||
{layout === PageLayoutType.Standard && (
|
||||
<CustomScrollbar autoHeightMin={'100%'} scrollTop={scrollTop} scrollRefCallback={scrollRef}>
|
||||
<div className={cx('page-scrollbar-content', className)}>
|
||||
{pageHeaderNav && <PageHeader navItem={pageHeaderNav} />}
|
||||
{pageHeaderNav && (
|
||||
<PageHeader
|
||||
actions={actions}
|
||||
info={info}
|
||||
navItem={pageHeaderNav}
|
||||
renderTitle={renderTitle}
|
||||
subTitle={subTitle}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
<Footer />
|
||||
</div>
|
||||
@ -67,7 +78,6 @@ export const OldPage: PageType = ({
|
||||
);
|
||||
};
|
||||
|
||||
OldPage.Header = PageHeader;
|
||||
OldPage.Contents = PageContents;
|
||||
OldPage.OldNavOnly = OldNavOnly;
|
||||
|
||||
|
@ -2,8 +2,6 @@ import React, { FC, HTMLAttributes, RefCallback } from 'react';
|
||||
|
||||
import { NavModel, NavModelItem, PageLayoutType } from '@grafana/data';
|
||||
|
||||
import { PageHeader } from '../PageHeader/PageHeader';
|
||||
|
||||
import { OldNavOnly } from './OldNavOnly';
|
||||
import { PageContents } from './PageContents';
|
||||
|
||||
@ -12,7 +10,15 @@ export interface PageProps extends HTMLAttributes<HTMLDivElement> {
|
||||
navId?: string;
|
||||
navModel?: NavModel;
|
||||
pageNav?: NavModelItem;
|
||||
/** Can be used to place info inline with the heading */
|
||||
info?: PageInfoItem[];
|
||||
/** Can be used to place actions inline with the heading */
|
||||
actions?: React.ReactNode;
|
||||
/** Can be used to customize rendering of title */
|
||||
renderTitle?: (title: string) => React.ReactNode;
|
||||
/** Can be used to customize or customize and set a page sub title */
|
||||
subTitle?: React.ReactNode;
|
||||
/** Control the page layout. */
|
||||
layout?: PageLayoutType;
|
||||
/** Something we can remove when we remove the old nav. */
|
||||
toolbar?: React.ReactNode;
|
||||
@ -28,7 +34,6 @@ export interface PageInfoItem {
|
||||
}
|
||||
|
||||
export interface PageType extends FC<PageProps> {
|
||||
Header: typeof PageHeader;
|
||||
OldNavOnly: typeof OldNavOnly;
|
||||
Contents: typeof PageContents;
|
||||
}
|
||||
|
@ -1,14 +1,20 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import { NavModelItem, NavModelBreadcrumb, GrafanaTheme2 } from '@grafana/data';
|
||||
import { Tab, TabsBar, Icon, useStyles2, toIconName } from '@grafana/ui';
|
||||
import { PanelHeaderMenuItem } from 'app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem';
|
||||
|
||||
import { PageInfoItem } from '../Page/types';
|
||||
import { PageInfo } from '../PageInfo/PageInfo';
|
||||
import { ProBadge } from '../Upgrade/ProBadge';
|
||||
|
||||
export interface Props {
|
||||
navItem: NavModelItem;
|
||||
renderTitle?: (title: string) => React.ReactNode;
|
||||
actions?: React.ReactNode;
|
||||
info?: PageInfoItem[];
|
||||
subTitle?: React.ReactNode;
|
||||
}
|
||||
|
||||
const SelectNav = ({ children, customCss }: { children: NavModelItem[]; customCss: string }) => {
|
||||
@ -80,18 +86,43 @@ const Navigation = ({ children }: { children: NavModelItem[] }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const PageHeader: FC<Props> = ({ navItem: model }) => {
|
||||
export const PageHeader: FC<Props> = ({ navItem: model, renderTitle, actions, info, subTitle }) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderHeader = (main: NavModelItem) => {
|
||||
const marginTop = main.icon === 'grafana' ? 12 : 14;
|
||||
const icon = main.icon && toIconName(main.icon);
|
||||
const sub = subTitle ?? main.subTitle;
|
||||
|
||||
return (
|
||||
<div className="page-header__inner">
|
||||
<span className="page-header__logo">
|
||||
{icon && <Icon name={icon} size="xxxl" style={{ marginTop }} />}
|
||||
{main.img && <img className="page-header__img" src={main.img} alt={`logo of ${main.text}`} />}
|
||||
</span>
|
||||
|
||||
<div className={cx('page-header__info-block', styles.headerText)}>
|
||||
{renderTitle
|
||||
? renderTitle(main.text)
|
||||
: renderHeaderTitle(main.text, main.breadcrumbs ?? [], main.highlightText)}
|
||||
{info && <PageInfo info={info} />}
|
||||
{sub && <div className="page-header__sub-title">{sub}</div>}
|
||||
{main.headerExtra && <main.headerExtra />}
|
||||
{actions && <div className={styles.actions}>{actions}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.headerCanvas}>
|
||||
<div className="page-container">
|
||||
<div className="page-header">
|
||||
{renderHeaderTitle(model)}
|
||||
{renderHeader(model)}
|
||||
{model.children && model.children.length > 0 && <Navigation>{model.children}</Navigation>}
|
||||
</div>
|
||||
</div>
|
||||
@ -99,27 +130,11 @@ export const PageHeader: FC<Props> = ({ navItem: model }) => {
|
||||
);
|
||||
};
|
||||
|
||||
function renderHeaderTitle(main: NavModelItem) {
|
||||
const marginTop = main.icon === 'grafana' ? 12 : 14;
|
||||
const icon = main.icon && toIconName(main.icon);
|
||||
|
||||
return (
|
||||
<div className="page-header__inner">
|
||||
<span className="page-header__logo">
|
||||
{icon && <Icon name={icon} size="xxxl" style={{ marginTop }} />}
|
||||
{main.img && <img className="page-header__img" src={main.img} alt={`logo of ${main.text}`} />}
|
||||
</span>
|
||||
|
||||
<div className="page-header__info-block">
|
||||
{renderTitle(main.text, main.breadcrumbs ?? [], main.highlightText)}
|
||||
{main.subTitle && <div className="page-header__sub-title">{main.subTitle}</div>}
|
||||
{main.headerExtra && <main.headerExtra />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderTitle(title: string, breadcrumbs: NavModelBreadcrumb[], highlightText: NavModelItem['highlightText']) {
|
||||
function renderHeaderTitle(
|
||||
title: string,
|
||||
breadcrumbs: NavModelBreadcrumb[],
|
||||
highlightText: NavModelItem['highlightText']
|
||||
) {
|
||||
if (!title && (!breadcrumbs || breadcrumbs.length === 0)) {
|
||||
return null;
|
||||
}
|
||||
@ -158,6 +173,16 @@ function renderTitle(title: string, breadcrumbs: NavModelBreadcrumb[], highlight
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
actions: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing(1),
|
||||
}),
|
||||
headerText: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(1),
|
||||
}),
|
||||
headerCanvas: css`
|
||||
background: ${theme.colors.background.canvas};
|
||||
`,
|
||||
|
@ -20,9 +20,12 @@ export const Page: PageType = ({
|
||||
navId,
|
||||
navModel: oldNavProp,
|
||||
pageNav,
|
||||
renderTitle,
|
||||
actions,
|
||||
subTitle,
|
||||
children,
|
||||
className,
|
||||
info,
|
||||
layout = PageLayoutType.Standard,
|
||||
toolbar,
|
||||
scrollTop,
|
||||
@ -54,7 +57,15 @@ export const Page: PageType = ({
|
||||
<div className={styles.pageContainer}>
|
||||
<CustomScrollbar autoHeightMin={'100%'} scrollTop={scrollTop} scrollRefCallback={scrollRef}>
|
||||
<div className={styles.pageInner}>
|
||||
{pageHeaderNav && <PageHeader navItem={pageHeaderNav} subTitle={subTitle} />}
|
||||
{pageHeaderNav && (
|
||||
<PageHeader
|
||||
actions={actions}
|
||||
navItem={pageHeaderNav}
|
||||
renderTitle={renderTitle}
|
||||
info={info}
|
||||
subTitle={subTitle}
|
||||
/>
|
||||
)}
|
||||
{pageNav && pageNav.children && <PageTabs navItem={pageNav} />}
|
||||
<div className={styles.pageContent}>{children}</div>
|
||||
</div>
|
||||
@ -81,12 +92,11 @@ export const Page: PageType = ({
|
||||
);
|
||||
};
|
||||
|
||||
const OldNavOnly = () => null;
|
||||
OldNavOnly.displayName = 'OldNavOnly';
|
||||
|
||||
Page.Header = PageHeader;
|
||||
Page.Contents = PageContents;
|
||||
Page.OldNavOnly = OldNavOnly;
|
||||
|
||||
Page.OldNavOnly = function OldNavOnly() {
|
||||
return null;
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
const shadow = theme.isDark
|
||||
|
@ -5,41 +5,84 @@ import { NavModelItem, GrafanaTheme2 } from '@grafana/data';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { getNavSubTitle, getNavTitle } from '../NavBar/navBarItem-translations';
|
||||
import { PageInfoItem } from '../Page/types';
|
||||
import { PageInfo } from '../PageInfo/PageInfo';
|
||||
|
||||
export interface Props {
|
||||
navItem: NavModelItem;
|
||||
renderTitle?: (title: string) => React.ReactNode;
|
||||
actions?: React.ReactNode;
|
||||
info?: PageInfoItem[];
|
||||
subTitle?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function PageHeader({ navItem, subTitle }: Props) {
|
||||
export function PageHeader({ navItem, renderTitle, actions, info, subTitle }: Props) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const sub = subTitle ?? getNavSubTitle(navItem.id) ?? navItem.subTitle;
|
||||
|
||||
const title = getNavTitle(navItem.id) ?? navItem.text;
|
||||
const titleElement = renderTitle ? renderTitle(title) : <h1 className={styles.pageTitle}>{title}</h1>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className={styles.pageTitle}>
|
||||
{navItem.img && <img className={styles.pageImg} src={navItem.img} alt={`logo for ${navItem.text}`} />}
|
||||
{getNavTitle(navItem.id) ?? navItem.text}
|
||||
</h1>
|
||||
{sub && <div className={styles.pageSubTitle}>{sub}</div>}
|
||||
<div className={styles.pageHeader}>
|
||||
<div className={styles.topRow}>
|
||||
<div className={styles.titleInfoContainer}>
|
||||
<div className={styles.title}>
|
||||
{navItem.img && <img className={styles.img} src={navItem.img} alt={`logo for ${navItem.text}`} />}
|
||||
{titleElement}
|
||||
</div>
|
||||
{info && <PageInfo info={info} />}
|
||||
</div>
|
||||
<div className={styles.actions}>{actions}</div>
|
||||
</div>
|
||||
{sub && <div className={styles.subTitle}>{sub}</div>}
|
||||
{navItem.headerExtra && <navItem.headerExtra />}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
topRow: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: theme.spacing(1, 2),
|
||||
}),
|
||||
title: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
}),
|
||||
actions: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing(1),
|
||||
}),
|
||||
titleInfoContainer: css({
|
||||
display: 'flex',
|
||||
label: 'title-info-container',
|
||||
flex: 1,
|
||||
flexWrap: 'wrap',
|
||||
gap: theme.spacing(1, 4),
|
||||
justifyContent: 'space-between',
|
||||
maxWidth: '100%',
|
||||
}),
|
||||
pageHeader: css({
|
||||
label: 'page-header',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(1),
|
||||
}),
|
||||
pageTitle: css({
|
||||
display: 'flex',
|
||||
marginBottom: theme.spacing(3),
|
||||
marginBottom: 0,
|
||||
}),
|
||||
pageSubTitle: css({
|
||||
subTitle: css({
|
||||
marginBottom: theme.spacing(2),
|
||||
position: 'relative',
|
||||
top: theme.spacing(-1),
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
pageImg: css({
|
||||
img: css({
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
marginRight: theme.spacing(2),
|
||||
|
@ -5,11 +5,19 @@ import { PluginPageContext } from 'app/features/plugins/components/PluginPageCon
|
||||
|
||||
import { Page } from '../Page/Page';
|
||||
|
||||
export function PluginPage({ children, pageNav, layout, subTitle }: PluginPageProps) {
|
||||
export function PluginPage({ actions, children, info, pageNav, layout, renderTitle, subTitle }: PluginPageProps) {
|
||||
const context = useContext(PluginPageContext);
|
||||
|
||||
return (
|
||||
<Page navModel={context.sectionNav} pageNav={pageNav} layout={layout} subTitle={subTitle}>
|
||||
<Page
|
||||
navModel={context.sectionNav}
|
||||
pageNav={pageNav}
|
||||
layout={layout}
|
||||
actions={actions}
|
||||
renderTitle={renderTitle}
|
||||
info={info}
|
||||
subTitle={subTitle}
|
||||
>
|
||||
<Page.Contents>{children}</Page.Contents>
|
||||
</Page>
|
||||
);
|
||||
|
@ -72,7 +72,7 @@ export const getStyles = (theme: GrafanaTheme2) => {
|
||||
margin-bottom: ${theme.spacing(1)};
|
||||
`,
|
||||
description: css`
|
||||
margin: ${theme.spacing(-1, 0, 1)};
|
||||
margin-bottom: ${theme.spacing(1)};
|
||||
`,
|
||||
breadcrumb: css`
|
||||
font-size: ${theme.typography.h2.fontSize};
|
||||
|
Loading…
Reference in New Issue
Block a user