Grafana UI: create Grid component in the unstable package (#75299)

This commit is contained in:
Laura Fernández 2023-09-26 14:02:50 +02:00 committed by GitHub
parent 1aa911dee6
commit 611df82259
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 160 additions and 35 deletions

View File

@ -2970,9 +2970,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Styles should be written using objects.", "0"],
[0, 0, 0, "Styles should be written using objects.", "1"]
],
"public/app/features/connections/tabs/ConnectData/CardGrid/CardGrid.tsx:5381": [
[0, 0, 0, "Styles should be written using objects.", "0"]
],
"public/app/features/connections/tabs/ConnectData/CategoryHeader/CategoryHeader.tsx:5381": [
[0, 0, 0, "Styles should be written using objects.", "0"],
[0, 0, 0, "Styles should be written using objects.", "1"]
@ -4781,10 +4778,6 @@ exports[`better eslint`] = {
"public/app/features/plugins/admin/components/PluginDetailsSignature.tsx:5381": [
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
],
"public/app/features/plugins/admin/components/PluginList.tsx:5381": [
[0, 0, 0, "Styles should be written using objects.", "0"],
[0, 0, 0, "Styles should be written using objects.", "1"]
],
"public/app/features/plugins/admin/components/PluginListItem.tsx:5381": [
[0, 0, 0, "Styles should be written using objects.", "0"],
[0, 0, 0, "Styles should be written using objects.", "1"],

View File

@ -0,0 +1,64 @@
import { Meta, StoryFn } from '@storybook/react';
import React from 'react';
import { useTheme2 } from '../../../themes';
import { Grid } from './Grid';
import mdx from './Grid.mdx';
const meta: Meta<typeof Grid> = {
title: 'General/Layout/Grid',
component: Grid,
parameters: {
docs: {
page: mdx,
},
},
args: {
gap: 1,
},
};
export const ColumnsNumber: StoryFn<typeof Grid> = (args) => {
const theme = useTheme2();
return (
<Grid gap={args.gap} columns={args.columns}>
{Array.from({ length: 9 }).map((_, i) => (
<div key={i} style={{ background: theme.colors.background.secondary, textAlign: 'center' }}>
N# {i}
</div>
))}
</Grid>
);
};
ColumnsNumber.args = {
columns: 3,
};
ColumnsNumber.parameters = {
controls: {
exclude: ['minColumnWidth'],
},
};
export const ColumnsMinWidth: StoryFn<typeof Grid> = (args) => {
const theme = useTheme2();
return (
<Grid gap={args.gap} minColumnWidth={args.minColumnWidth}>
{Array.from({ length: 9 }).map((_, i) => (
<div key={i} style={{ background: theme.colors.background.secondary, textAlign: 'center' }}>
N# {i}
</div>
))}
</Grid>
);
};
ColumnsMinWidth.args = {
minColumnWidth: 21,
};
ColumnsMinWidth.parameters = {
controls: {
exclude: ['columns'],
},
};
export default meta;

View File

@ -0,0 +1,32 @@
import { Meta, ArgTypes } from '@storybook/blocks';
import { Grid } from './Grid';
<Meta title="MDX|Grid" component={Grid} />
# Grid
The `Grid` component allows for the organized layout and alignment of content into a grid-based structure.
## Usage
### When to use
Use the Grid component when you want to create structured and organized layouts where content or elements need to be aligned in rows and columns for clarity and consistency.
### When not to use
Use the `Stack` component instead for these use cases:
- **Simple layouts:** When you need to arrange elements in a linear format, either vertically or horizontally.
- **Regular flow:** When you want a "regular" site flow but with standardized spacing between the elements.
Use the `Flex` component instead for these use cases:
- **Alignment:** More options for item alignment.
- **Flex items:** Custom flex basis or configure how items stretch and wrap.
## Properties
_Note: There is no support for using `columns` and `minColumnWidth` props at the same time. The correct behaviour is working just with one of them not both._
<ArgTypes of={Grid} />

View File

@ -0,0 +1,56 @@
import { css } from '@emotion/css';
import React, { forwardRef, HTMLAttributes } from 'react';
import { GrafanaTheme2, ThemeSpacingTokens } from '@grafana/data';
import { useStyles2 } from '../../../themes';
interface GridProps extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> {
children: NonNullable<React.ReactNode>;
/** Specifies the gutters between columns and rows. It is overwritten when a column or row gap has a value */
gap?: ThemeSpacingTokens;
/** Number of columns */
columns?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
/** For a responsive layout, fit as many columns while maintaining this minimum column width.
* The real width will be calculated based on the theme spacing tokens: `theme.spacing(minColumnWidth)`
*/
minColumnWidth?: 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | 44 | 55 | 72 | 89 | 144;
}
export const Grid = forwardRef<HTMLDivElement, GridProps>((props, ref) => {
const { children, gap, columns, minColumnWidth, ...rest } = props;
const styles = useStyles2(getGridStyles, gap, columns, minColumnWidth);
return (
<div ref={ref} {...rest} className={styles.grid}>
{children}
</div>
);
});
Grid.displayName = 'Grid';
const getGridStyles = (
theme: GrafanaTheme2,
gap: GridProps['gap'],
columns: GridProps['columns'],
minColumnWidth: GridProps['minColumnWidth']
) => {
return {
grid: css([
{
display: 'grid',
gap: gap ? theme.spacing(gap) : undefined,
},
minColumnWidth && {
gridTemplateColumns: `repeat(auto-fill, minmax(${theme.spacing(minColumnWidth)}, 1fr))`,
},
columns && {
gridTemplateColumns: `repeat(${columns}, 1fr)`,
},
]),
};
};

View File

@ -12,4 +12,5 @@
export * from './components/Layout/Box/Box';
export * from './components/Layout/Flex/Flex';
export { Grid } from './components/Layout/Grid/Grid';
export { Stack, HorizontalStack } from './components/Layout/Stack';

View File

@ -3,16 +3,10 @@ import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Card, useStyles2 } from '@grafana/ui';
import { Grid } from '@grafana/ui/src/unstable';
import { PluginAngularBadge } from 'app/features/plugins/admin/components/Badges';
const getStyles = (theme: GrafanaTheme2) => ({
sourcesList: css`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
gap: 12px;
list-style: none;
margin-bottom: 80px;
`,
heading: css({
fontSize: theme.typography.h5.fontSize,
fontWeight: 'inherit',
@ -55,6 +49,7 @@ export type CardGridItem = {
logo?: string;
angularDetected?: boolean;
};
export interface CardGridProps {
items: CardGridItem[];
onClickItem?: (e: React.MouseEvent<HTMLElement>, item: CardGridItem) => void;
@ -64,7 +59,7 @@ export const CardGrid = ({ items, onClickItem }: CardGridProps) => {
const styles = useStyles2(getStyles);
return (
<ul className={styles.sourcesList}>
<Grid gap={1.5} minColumnWidth={44}>
{items.map((item) => (
<Card
key={item.id}
@ -89,6 +84,6 @@ export const CardGrid = ({ items, onClickItem }: CardGridProps) => {
) : null}
</Card>
))}
</ul>
</Grid>
);
};

View File

@ -1,10 +1,8 @@
import { css, cx } from '@emotion/css';
import React from 'react';
import { useLocation } from 'react-router-dom';
import { GrafanaTheme2 } from '@grafana/data';
import { config } from '@grafana/runtime';
import { useStyles2 } from '@grafana/ui';
import { Grid } from '@grafana/ui/src/unstable';
import { CatalogPlugin, PluginListDisplayMode } from '../types';
@ -17,28 +15,14 @@ interface Props {
export const PluginList = ({ plugins, displayMode }: Props) => {
const isList = displayMode === PluginListDisplayMode.List;
const styles = useStyles2(getStyles);
const { pathname } = useLocation();
const pathName = config.appSubUrl + (pathname.endsWith('/') ? pathname.slice(0, -1) : pathname);
return (
<div className={cx(styles.container, { [styles.list]: isList })} data-testid="plugin-list">
<Grid gap={3} columns={isList ? 1 : undefined} minColumnWidth={isList ? undefined : 34} data-testid="plugin-list">
{plugins.map((plugin) => (
<PluginListItem key={plugin.id} plugin={plugin} pathName={pathName} displayMode={displayMode} />
))}
</div>
</Grid>
);
};
const getStyles = (theme: GrafanaTheme2) => {
return {
container: css`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(288px, 1fr));
gap: ${theme.spacing(3)};
`,
list: css`
grid-template-columns: 1fr;
`,
};
};