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.", "0"],
|
||||||
[0, 0, 0, "Styles should be written using objects.", "1"]
|
[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": [
|
"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.", "0"],
|
||||||
[0, 0, 0, "Styles should be written using objects.", "1"]
|
[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": [
|
"public/app/features/plugins/admin/components/PluginDetailsSignature.tsx:5381": [
|
||||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
|
[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": [
|
"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.", "0"],
|
||||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
[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/Box/Box';
|
||||||
export * from './components/Layout/Flex/Flex';
|
export * from './components/Layout/Flex/Flex';
|
||||||
|
|
||||||
|
export { Grid } from './components/Layout/Grid/Grid';
|
||||||
export { Stack, HorizontalStack } from './components/Layout/Stack';
|
export { Stack, HorizontalStack } from './components/Layout/Stack';
|
||||||
|
@ -3,16 +3,10 @@ import React from 'react';
|
|||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Card, useStyles2 } from '@grafana/ui';
|
import { Card, useStyles2 } from '@grafana/ui';
|
||||||
|
import { Grid } from '@grafana/ui/src/unstable';
|
||||||
import { PluginAngularBadge } from 'app/features/plugins/admin/components/Badges';
|
import { PluginAngularBadge } from 'app/features/plugins/admin/components/Badges';
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
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({
|
heading: css({
|
||||||
fontSize: theme.typography.h5.fontSize,
|
fontSize: theme.typography.h5.fontSize,
|
||||||
fontWeight: 'inherit',
|
fontWeight: 'inherit',
|
||||||
@ -55,6 +49,7 @@ export type CardGridItem = {
|
|||||||
logo?: string;
|
logo?: string;
|
||||||
angularDetected?: boolean;
|
angularDetected?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface CardGridProps {
|
export interface CardGridProps {
|
||||||
items: CardGridItem[];
|
items: CardGridItem[];
|
||||||
onClickItem?: (e: React.MouseEvent<HTMLElement>, item: CardGridItem) => void;
|
onClickItem?: (e: React.MouseEvent<HTMLElement>, item: CardGridItem) => void;
|
||||||
@ -64,7 +59,7 @@ export const CardGrid = ({ items, onClickItem }: CardGridProps) => {
|
|||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className={styles.sourcesList}>
|
<Grid gap={1.5} minColumnWidth={44}>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<Card
|
<Card
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@ -89,6 +84,6 @@ export const CardGrid = ({ items, onClickItem }: CardGridProps) => {
|
|||||||
) : null}
|
) : null}
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</Grid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { Grid } from '@grafana/ui/src/unstable';
|
||||||
|
|
||||||
import { CatalogPlugin, PluginListDisplayMode } from '../types';
|
import { CatalogPlugin, PluginListDisplayMode } from '../types';
|
||||||
|
|
||||||
@ -17,28 +15,14 @@ interface Props {
|
|||||||
|
|
||||||
export const PluginList = ({ plugins, displayMode }: Props) => {
|
export const PluginList = ({ plugins, displayMode }: Props) => {
|
||||||
const isList = displayMode === PluginListDisplayMode.List;
|
const isList = displayMode === PluginListDisplayMode.List;
|
||||||
const styles = useStyles2(getStyles);
|
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const pathName = config.appSubUrl + (pathname.endsWith('/') ? pathname.slice(0, -1) : pathname);
|
const pathName = config.appSubUrl + (pathname.endsWith('/') ? pathname.slice(0, -1) : pathname);
|
||||||
|
|
||||||
return (
|
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) => (
|
{plugins.map((plugin) => (
|
||||||
<PluginListItem key={plugin.id} plugin={plugin} pathName={pathName} displayMode={displayMode} />
|
<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