From 831ecb467cfaad7942cbe785f732bd91a43b479a Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Tue, 8 Nov 2022 14:20:05 +0000 Subject: [PATCH] Add new PageInfo component (#58421) --- public/app/core/components/Page/types.ts | 5 ++ .../components/PageInfo/PageInfo.test.tsx | 62 +++++++++++++++++++ .../app/core/components/PageInfo/PageInfo.tsx | 52 ++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 public/app/core/components/PageInfo/PageInfo.test.tsx create mode 100644 public/app/core/components/PageInfo/PageInfo.tsx diff --git a/public/app/core/components/Page/types.ts b/public/app/core/components/Page/types.ts index 759a4380155..0e23c966835 100644 --- a/public/app/core/components/Page/types.ts +++ b/public/app/core/components/Page/types.ts @@ -22,6 +22,11 @@ export interface PageProps extends HTMLAttributes { scrollTop?: number; } +export interface PageInfoItem { + label: string; + value: React.ReactNode; +} + export interface PageType extends FC { Header: typeof PageHeader; OldNavOnly: typeof OldNavOnly; diff --git a/public/app/core/components/PageInfo/PageInfo.test.tsx b/public/app/core/components/PageInfo/PageInfo.test.tsx new file mode 100644 index 00000000000..73aa06c8b3b --- /dev/null +++ b/public/app/core/components/PageInfo/PageInfo.test.tsx @@ -0,0 +1,62 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { PageInfoItem } from '../Page/types'; + +import { PageInfo } from './PageInfo'; + +describe('PageInfo', () => { + it('renders the label and value for each info item', () => { + const info: PageInfoItem[] = [ + { + label: 'label1', + value: 'value1', + }, + { + label: 'label2', + value: 2, + }, + ]; + render(); + + // Check labels are visible + expect(screen.getByText('label1')).toBeInTheDocument(); + expect(screen.getByText('label2')).toBeInTheDocument(); + + // Check values are visible + expect(screen.getByText('value1')).toBeInTheDocument(); + expect(screen.getByText('2')).toBeInTheDocument(); + }); + + it('can render a custom element as a value', () => { + const info: PageInfoItem[] = [ + { + label: 'label1', + value:
value1
, + }, + ]; + render(); + + expect(screen.getByTestId('custom-value')).toBeInTheDocument(); + }); + + it('renders separators between the info items', () => { + const info: PageInfoItem[] = [ + { + label: 'label1', + value: 'value1', + }, + { + label: 'label2', + value: 'value2', + }, + { + label: 'label3', + value: 'value3', + }, + ]; + render(); + + expect(screen.getAllByTestId('page-info-separator')).toHaveLength(info.length - 1); + }); +}); diff --git a/public/app/core/components/PageInfo/PageInfo.tsx b/public/app/core/components/PageInfo/PageInfo.tsx new file mode 100644 index 00000000000..388853ca375 --- /dev/null +++ b/public/app/core/components/PageInfo/PageInfo.tsx @@ -0,0 +1,52 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; + +import { PageInfoItem } from '../Page/types'; + +export interface Props { + info: PageInfoItem[]; +} + +export function PageInfo({ info }: Props) { + const styles = useStyles2(getStyles); + + return ( +
+ {info.map((infoItem, index) => ( + +
+
{infoItem.label}
+ {infoItem.value} +
+ {index + 1 < info.length &&
} + + ))} +
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + container: css({ + display: 'flex', + flexDirection: 'row', + gap: theme.spacing(1.5), + overflow: 'auto', + }), + infoItem: css({ + ...theme.typography.bodySmall, + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(0.5), + }), + label: css({ + color: theme.colors.text.secondary, + }), + separator: css({ + borderLeft: `1px solid ${theme.colors.border.weak}`, + }), + }; +};