mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Grafana UI: create Grid
component in the unstable
package (#75299)
This commit is contained in:
parent
1aa911dee6
commit
611df82259
@ -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"],
|
||||
|
@ -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;
|
32
packages/grafana-ui/src/components/Layout/Grid/Grid.mdx
Normal file
32
packages/grafana-ui/src/components/Layout/Grid/Grid.mdx
Normal 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} />
|
56
packages/grafana-ui/src/components/Layout/Grid/Grid.tsx
Normal file
56
packages/grafana-ui/src/components/Layout/Grid/Grid.tsx
Normal 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)`,
|
||||
},
|
||||
]),
|
||||
};
|
||||
};
|
@ -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';
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user