mirror of
https://github.com/grafana/grafana.git
synced 2025-02-14 09:33:34 -06:00
grafana/ui: Rename Flex component to Stack (#77453)
* grafana/ui: Remove Stack and rename FLex to Stack * Update types * Update grafana/ui imports * Update Grafana imports * Update docs
This commit is contained in:
parent
d511925fc9
commit
f5cbd4f9d0
@ -16,7 +16,7 @@ Use it whenever you would use custom CSS.
|
||||
|
||||
#### When not to use
|
||||
|
||||
If you need layout styles, use the Stack, Flex or Grid components instead.
|
||||
If you need layout styles, use the Stack or Grid components instead.
|
||||
|
||||
### How to add a prop to Box
|
||||
|
||||
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||
|
||||
import { SpacingTokenControl } from '../../../utils/storybook/themeStorybookControls';
|
||||
import { Text } from '../../Text/Text';
|
||||
import { Flex } from '../Flex/Flex';
|
||||
import { Stack } from '../Stack/Stack';
|
||||
|
||||
import { Box, BackgroundColor, BorderColor, BorderStyle, BorderRadius, BoxShadow } from './Box';
|
||||
import mdx from './Box.mdx';
|
||||
@ -48,11 +48,11 @@ const Item = ({ background }: { background?: string }) => {
|
||||
|
||||
export const Basic: StoryFn<typeof Box> = (args) => {
|
||||
return (
|
||||
<Flex>
|
||||
<Stack>
|
||||
<Box borderColor="medium" {...args}>
|
||||
Box
|
||||
</Box>
|
||||
</Flex>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@ -88,64 +88,64 @@ Basic.args = {
|
||||
|
||||
export const Background: StoryFn<typeof Box> = () => {
|
||||
return (
|
||||
<Flex gap={4}>
|
||||
<Stack gap={4}>
|
||||
{backgroundOptions.map((background) => (
|
||||
<Flex key={background} direction="column" alignItems="flex-start">
|
||||
<Stack key={background} direction="column" alignItems="flex-start">
|
||||
{background}
|
||||
<Box backgroundColor={background} borderColor="strong" borderStyle="solid">
|
||||
<Item />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Stack>
|
||||
))}
|
||||
</Flex>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const Border: StoryFn<typeof Box> = () => {
|
||||
return (
|
||||
<Flex direction="column" gap={4}>
|
||||
<Stack direction="column" gap={4}>
|
||||
<div>
|
||||
<Text variant="h4">Border Color</Text>
|
||||
<Flex gap={4} wrap="wrap">
|
||||
<Stack gap={4} wrap="wrap">
|
||||
{borderColorOptions.map((border) => (
|
||||
<Flex key={border} direction="column" alignItems="flex-start">
|
||||
<Stack key={border} direction="column" alignItems="flex-start">
|
||||
{border}
|
||||
<Box borderColor={border} borderStyle="solid">
|
||||
<Item />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Stack>
|
||||
))}
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
<div>
|
||||
<Text variant="h4">Border Style</Text>
|
||||
<Flex gap={4} wrap="wrap">
|
||||
<Stack gap={4} wrap="wrap">
|
||||
{borderStyleOptions.map((border) => (
|
||||
<Flex key={border} direction="column" alignItems="flex-start">
|
||||
<Stack key={border} direction="column" alignItems="flex-start">
|
||||
{border}
|
||||
<Box borderColor="info" borderStyle={border}>
|
||||
<Item />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Stack>
|
||||
))}
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
</Flex>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const Shadow: StoryFn<typeof Box> = () => {
|
||||
return (
|
||||
<Flex gap={4}>
|
||||
<Stack gap={4}>
|
||||
{boxShadowOptions.map((shadow) => (
|
||||
<Flex key={shadow} direction="column" alignItems="flex-start">
|
||||
<Stack key={shadow} direction="column" alignItems="flex-start">
|
||||
{shadow}
|
||||
<Box boxShadow={shadow} borderColor="strong" borderStyle="solid">
|
||||
<Item />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Stack>
|
||||
))}
|
||||
</Flex>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -4,7 +4,7 @@ import React, { ElementType, forwardRef, PropsWithChildren } from 'react';
|
||||
import { GrafanaTheme2, ThemeSpacingTokens, ThemeShape, ThemeShadows } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../../themes';
|
||||
import { AlignItems, JustifyContent } from '../Flex/Flex';
|
||||
import { AlignItems, JustifyContent } from '../Stack/Stack';
|
||||
import { ResponsiveProp, getResponsiveStyle } from '../utils/responsiveness';
|
||||
|
||||
type Display = 'flex' | 'block' | 'inline' | 'none';
|
||||
|
@ -1,202 +0,0 @@
|
||||
import { Meta, StoryFn } from '@storybook/react';
|
||||
import React from 'react';
|
||||
|
||||
import { ThemeSpacingTokens } from '@grafana/data';
|
||||
|
||||
import { useTheme2 } from '../../../themes';
|
||||
import { SpacingTokenControl } from '../../../utils/storybook/themeStorybookControls';
|
||||
|
||||
import { Flex, JustifyContent, Wrap, Direction } from './Flex';
|
||||
import mdx from './Flex.mdx';
|
||||
|
||||
const Item = ({ color, text, height }: { color: string; text?: string | number; height?: string }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '5em',
|
||||
height: height,
|
||||
backgroundColor: color,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{text && <h3 style={{ color: 'black' }}>{text}</h3>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const meta: Meta<typeof Flex> = {
|
||||
title: 'General/Layout/Flex',
|
||||
component: Flex,
|
||||
parameters: {
|
||||
docs: {
|
||||
page: mdx,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Basic: StoryFn<typeof Flex> = ({ direction, wrap, alignItems, justifyContent, gap }) => {
|
||||
const theme = useTheme2();
|
||||
return (
|
||||
<div style={{ width: '600px', height: '600px', border: '1px solid grey' }}>
|
||||
<Flex direction={direction} wrap={wrap} alignItems={alignItems} justifyContent={justifyContent} gap={gap}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Basic.argTypes = {
|
||||
gap: SpacingTokenControl,
|
||||
direction: { control: 'select', options: ['row', 'row-reverse', 'column', 'column-reverse'] },
|
||||
wrap: { control: 'select', options: ['nowrap', 'wrap', 'wrap-reverse'] },
|
||||
alignItems: {
|
||||
control: 'select',
|
||||
options: ['stretch', 'flex-start', 'flex-end', 'center', 'baseline', 'start', 'end', 'self-start', 'self-end'],
|
||||
},
|
||||
justifyContent: {
|
||||
control: 'select',
|
||||
options: [
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'center',
|
||||
'space-between',
|
||||
'space-around',
|
||||
'space-evenly',
|
||||
'start',
|
||||
'end',
|
||||
'left',
|
||||
'right',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const AlignItemsExamples: StoryFn<typeof Flex> = () => {
|
||||
const theme = useTheme2();
|
||||
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
<p>Align items flex-start</p>
|
||||
<Flex direction="row" wrap="wrap" alignItems="flex-start" justifyContent="start" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
<p>Align items flex-end</p>
|
||||
<Flex direction="row" wrap="wrap" alignItems="flex-end" justifyContent="end" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
<p>Align items baseline</p>
|
||||
<Flex direction="row" wrap="nowrap" alignItems="baseline" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
<p>Align items center</p>
|
||||
<Flex direction="row" wrap="wrap" alignItems="center" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
<p>Align items stretch</p>
|
||||
<Flex direction="row" wrap="wrap" alignItems="stretch" justifyContent="center" gap={2}>
|
||||
<Item color={theme.colors.error.main} height="10em" />
|
||||
<Item color={theme.colors.error.main} />
|
||||
<Item color={theme.colors.error.main} height="3em" />
|
||||
<Item color={theme.colors.error.main} />
|
||||
<Item color={theme.colors.error.main} />
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const JustifyContentExamples: StoryFn<typeof Flex> = () => {
|
||||
const theme = useTheme2();
|
||||
const justifyContentOptions: JustifyContent[] = [
|
||||
'space-between',
|
||||
'space-around',
|
||||
'space-evenly',
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'center',
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
{justifyContentOptions.map((justifyContent) => (
|
||||
<>
|
||||
<p>Justify Content {justifyContent}</p>
|
||||
<Flex direction="row" wrap="wrap" alignItems="center" justifyContent={justifyContent} gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const GapExamples: StoryFn<typeof Flex> = () => {
|
||||
const theme = useTheme2();
|
||||
const gapOptions: ThemeSpacingTokens[] = [2, 8, 10];
|
||||
return (
|
||||
<div style={{ width: '800px' }}>
|
||||
{gapOptions.map((gap) => (
|
||||
<>
|
||||
<p>Gap with spacingToken set to {gap}</p>
|
||||
<Flex direction="row" wrap="wrap" alignItems="flex-start" justifyContent="flex-start" gap={gap}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const WrapExamples: StoryFn<typeof Flex> = () => {
|
||||
const theme = useTheme2();
|
||||
const wrapOptions: Wrap[] = ['nowrap', 'wrap', 'wrap-reverse'];
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
{wrapOptions.map((wrap) => (
|
||||
<>
|
||||
<p>Wrap examples with {wrap} and gap set to spacingToken 2 (16px)</p>
|
||||
<Flex direction="row" wrap={wrap} alignItems="center" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 10 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DirectionExamples: StoryFn<typeof Flex> = () => {
|
||||
const theme = useTheme2();
|
||||
const directionOptions: Direction[] = ['row', 'row-reverse', 'column', 'column-reverse'];
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
{directionOptions.map((direction) => (
|
||||
<>
|
||||
<p>Direction {direction}</p>
|
||||
<Flex direction={direction} wrap="wrap" alignItems="center" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default meta;
|
@ -1,138 +0,0 @@
|
||||
import { Meta, ArgTypes } from '@storybook/blocks';
|
||||
import { Flex } from './Flex';
|
||||
|
||||
<Meta title="MDX|Flex" component={Flex} />
|
||||
|
||||
# Flex
|
||||
|
||||
The Flex Component aims at providing a more efficient way to lay out, align and distribute space among items in a container and
|
||||
the decision to create it is to ensure consistency in design across Grafana.
|
||||
|
||||
### Usage
|
||||
|
||||
#### When to use
|
||||
|
||||
Use when in need to align components and small parts of the application. Use as parent container to wrap elements that you wish to align in a certain way.
|
||||
|
||||
Also:
|
||||
|
||||
* when working with one dimension layout
|
||||
* to display the direction of the elements
|
||||
* to set the elements to wrap
|
||||
* to align items (vertically or horizontally)
|
||||
|
||||
#### When not to use
|
||||
|
||||
When you need to lay out bigger parts of the application or when you want to create page lay out.
|
||||
|
||||
Also:
|
||||
|
||||
* for complex grid layouts with various rows and columns
|
||||
* bidirectional layouts
|
||||
* complex nesting
|
||||
* equal height columns
|
||||
|
||||
### Variants
|
||||
|
||||
Flex component has few variants that can be used based on the desired alignment you need for your case.
|
||||
|
||||
Some examples of how to use the Flex component can be seen below:
|
||||
|
||||
- AlignItems stretch
|
||||
|
||||
```ts
|
||||
import { Flex } from '@grafana/ui'
|
||||
import { useTheme2 } from '../../themes';
|
||||
|
||||
const theme = useTheme2();
|
||||
|
||||
<header>
|
||||
<h1>Using Flex component to align-items stretch and justify-content to be center</h1>
|
||||
</header>
|
||||
<Flex direction="row" wrap="wrap" alignItems="stretch" justifyContent="center" gap={2}>
|
||||
<Item color={theme.colors.warning.main} height="10em" />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} height="3em" />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
</Flex>
|
||||
```
|
||||
|
||||
- Wrap items wrap-reverse
|
||||
|
||||
```ts
|
||||
import { Flex } from '@grafana/ui'
|
||||
import { useTheme2 } from '../../themes';
|
||||
|
||||
const theme = useTheme2();
|
||||
|
||||
<header>
|
||||
<h1>Using Flex component to align-items with wrap-reverse property</h1>
|
||||
</header>
|
||||
<Flex direction="row" wrap="wrap-reverse" alignItems="center" justifyContent="center" gap={4}>
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
</Flex>
|
||||
```
|
||||
|
||||
- JustifyContent flex-start
|
||||
|
||||
```ts
|
||||
import { Flex } from '@grafana/ui'
|
||||
import { useTheme2 } from '../../themes';
|
||||
|
||||
const theme = useTheme2();
|
||||
|
||||
<header>
|
||||
<h1>Using Flex component to align-items with justify-content property</h1>
|
||||
</header>
|
||||
<Flex direction="row" wrap="wrap" alignItems="center" justifyContent="flex-start" gap={2}>
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
</Flex>
|
||||
```
|
||||
|
||||
- Gap of 16px using the ThemeSpacingTokens
|
||||
|
||||
```ts
|
||||
import { Flex } from '@grafana/ui'
|
||||
import { useTheme2 } from '../../themes';
|
||||
|
||||
const theme = useTheme2();
|
||||
|
||||
<header>
|
||||
<h1>Using Flex component to align-items with gap of 16px</h1>
|
||||
</header>
|
||||
<Flex direction="row" wrap="wrap" alignItems="center" justifyContent="center" gap={2}>
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
</Flex>
|
||||
```
|
||||
|
||||
- Direction column
|
||||
|
||||
```ts
|
||||
import { Flex } from '@grafana/ui'
|
||||
import { useTheme2 } from '../../themes';
|
||||
|
||||
const theme = useTheme2();
|
||||
|
||||
<header>
|
||||
<h1>Using Flex component to align-items with direction column</h1>
|
||||
</header>
|
||||
<Flex direction="column" wrap="wrap" alignItems="center" justifyContent="center" gap={2}>
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
</Flex>
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
<ArgTypes of={Flex} />
|
@ -1,89 +0,0 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2, ThemeSpacingTokens } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../../themes';
|
||||
import { ResponsiveProp, getResponsiveStyle } from '../utils/responsiveness';
|
||||
|
||||
export type AlignItems =
|
||||
| 'stretch'
|
||||
| 'flex-start'
|
||||
| 'flex-end'
|
||||
| 'center'
|
||||
| 'baseline'
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'self-start'
|
||||
| 'self-end';
|
||||
|
||||
export type JustifyContent =
|
||||
| 'flex-start'
|
||||
| 'flex-end'
|
||||
| 'center'
|
||||
| 'space-between'
|
||||
| 'space-around'
|
||||
| 'space-evenly'
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'left'
|
||||
| 'right';
|
||||
|
||||
export type Direction = 'row' | 'row-reverse' | 'column' | 'column-reverse';
|
||||
|
||||
export type Wrap = 'nowrap' | 'wrap' | 'wrap-reverse';
|
||||
|
||||
interface FlexProps extends Omit<React.HTMLAttributes<HTMLElement>, 'className' | 'style'> {
|
||||
gap?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
alignItems?: ResponsiveProp<AlignItems>;
|
||||
justifyContent?: ResponsiveProp<JustifyContent>;
|
||||
direction?: ResponsiveProp<Direction>;
|
||||
wrap?: ResponsiveProp<Wrap>;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Flex = React.forwardRef<HTMLDivElement, FlexProps>(
|
||||
({ gap = 1, alignItems, justifyContent, direction, wrap, children, ...rest }, ref) => {
|
||||
const styles = useStyles2(getStyles, gap, alignItems, justifyContent, direction, wrap);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={styles.flex} {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Flex.displayName = 'Flex';
|
||||
|
||||
const getStyles = (
|
||||
theme: GrafanaTheme2,
|
||||
gap: FlexProps['gap'],
|
||||
alignItems: FlexProps['alignItems'],
|
||||
justifyContent: FlexProps['justifyContent'],
|
||||
direction: FlexProps['direction'],
|
||||
wrap: FlexProps['wrap']
|
||||
) => {
|
||||
return {
|
||||
flex: css([
|
||||
{
|
||||
display: 'flex',
|
||||
},
|
||||
getResponsiveStyle<Direction>(theme, direction, (val) => ({
|
||||
flexDirection: val,
|
||||
})),
|
||||
getResponsiveStyle<Wrap>(theme, wrap, (val) => ({
|
||||
flexWrap: val,
|
||||
})),
|
||||
getResponsiveStyle<AlignItems>(theme, alignItems, (val) => ({
|
||||
alignItems: val,
|
||||
})),
|
||||
getResponsiveStyle<JustifyContent>(theme, justifyContent, (val) => ({
|
||||
justifyContent: val,
|
||||
})),
|
||||
getResponsiveStyle<ThemeSpacingTokens>(theme, gap, (val) => ({
|
||||
gap: theme.spacing(val),
|
||||
})),
|
||||
]),
|
||||
};
|
||||
};
|
@ -17,11 +17,6 @@ Use the Grid component when you want to create structured and organized layouts
|
||||
|
||||
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.
|
||||
|
||||
|
@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ThemeSpacingTokens } from '@grafana/data';
|
||||
|
||||
import { ResponsiveProp } from '../utils/responsiveness';
|
||||
|
||||
import { Stack } from './Stack';
|
||||
|
||||
interface HorizontalStackProps extends Omit<React.HTMLAttributes<HTMLElement>, 'className' | 'style'> {
|
||||
gap?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
}
|
||||
|
||||
export const HorizontalStack = React.forwardRef<HTMLDivElement, React.PropsWithChildren<HorizontalStackProps>>(
|
||||
({ children, gap = 1, ...rest }, ref) => (
|
||||
<Stack ref={ref} direction="row" gap={gap} {...rest}>
|
||||
{children}
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
HorizontalStack.displayName = 'HorizontalStack';
|
@ -1,16 +1,31 @@
|
||||
import { Meta, StoryFn } from '@storybook/react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { ThemeSpacingTokens } from '@grafana/data';
|
||||
|
||||
import { useTheme2 } from '../../../themes';
|
||||
import { SpacingTokenControl } from '../../../utils/storybook/themeStorybookControls';
|
||||
import { Alert } from '../../Alert/Alert';
|
||||
import { Button } from '../../Button';
|
||||
import { Card } from '../../Card/Card';
|
||||
import { Text } from '../../Text/Text';
|
||||
|
||||
import { HorizontalStack } from './HorizontalStack';
|
||||
import { Stack } from './Stack';
|
||||
import { Stack, JustifyContent, Wrap, Direction } from './Stack';
|
||||
import mdx from './Stack.mdx';
|
||||
|
||||
const Item = ({ color, text, height }: { color: string; text?: string | number; height?: string }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '5em',
|
||||
height: height,
|
||||
backgroundColor: color,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{text && <h3 style={{ color: 'black' }}>{text}</h3>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const meta: Meta<typeof Stack> = {
|
||||
title: 'General/Layout/Stack',
|
||||
component: Stack,
|
||||
@ -19,204 +34,169 @@ const meta: Meta<typeof Stack> = {
|
||||
page: mdx,
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
gap: SpacingTokenControl,
|
||||
direction: { control: 'select', options: ['row', 'column'] },
|
||||
},
|
||||
};
|
||||
|
||||
const Item = ({ children }: { children: ReactNode }) => (
|
||||
<div style={{ backgroundColor: 'lightgrey', width: '100px', height: '50px' }}>{children}</div>
|
||||
);
|
||||
|
||||
export const Basic: StoryFn<typeof Stack> = ({ direction = 'column', gap = 2 }) => {
|
||||
export const Basic: StoryFn<typeof Stack> = ({ direction, wrap, alignItems, justifyContent, gap }) => {
|
||||
const theme = useTheme2();
|
||||
return (
|
||||
<Stack direction={direction} gap={gap}>
|
||||
<Item>Item 1</Item>
|
||||
<Item>Item 2</Item>
|
||||
<Item>Item 3</Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestCases: StoryFn<typeof Stack> = () => {
|
||||
return (
|
||||
<div style={{ width: '100%' }}>
|
||||
<Stack gap={4}>
|
||||
<h2>Comparisons Stack vs No stack</h2>
|
||||
<HorizontalStack>
|
||||
<Example title="No stack">
|
||||
<Button>A button</Button>
|
||||
<Button>Longer button button</Button>
|
||||
</Example>
|
||||
|
||||
<Example title="Horizontal stack">
|
||||
<HorizontalStack>
|
||||
<Button>A button</Button>
|
||||
<Button>Longer button button</Button>
|
||||
</HorizontalStack>
|
||||
</Example>
|
||||
|
||||
<Example title="Vertical stack">
|
||||
<Stack>
|
||||
<Button>A button</Button>
|
||||
<Button>Longer button button</Button>
|
||||
</Stack>
|
||||
</Example>
|
||||
</HorizontalStack>
|
||||
|
||||
<HorizontalStack>
|
||||
<Example title="No stack, mismatched heights">
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
<Card.Description>Ohhhhh - and now a description and some actions</Card.Description>
|
||||
<Card.Actions>
|
||||
<Button variant="secondary">Settings</Button>
|
||||
<Button variant="secondary">Explore</Button>
|
||||
</Card.Actions>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
<Card.Description>Ohhhhh - and now a description!</Card.Description>
|
||||
</Card>
|
||||
<Button>Please press me!</Button>
|
||||
</Example>
|
||||
<Example title="Vertical stack, mismatched heights">
|
||||
<Stack>
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
<Card.Description>Ohhhhh - and now a description and some actions</Card.Description>
|
||||
<Card.Actions>
|
||||
<Button variant="secondary">Settings</Button>
|
||||
<Button variant="secondary">Explore</Button>
|
||||
</Card.Actions>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
<Card.Description>Ohhhhh - and now a description!</Card.Description>
|
||||
</Card>
|
||||
<Button>Please press me!</Button>
|
||||
</Stack>
|
||||
</Example>
|
||||
</HorizontalStack>
|
||||
|
||||
<div style={{ width: 500 }}>
|
||||
<Example title="No stack, too many items">
|
||||
<Button>A button</Button>
|
||||
<Button>Longer button button</Button>
|
||||
<Button>Another button</Button>
|
||||
<Button>And another</Button>
|
||||
<Button>Why not - one last button!</Button>
|
||||
</Example>
|
||||
|
||||
<Example title="Horizontal stack, too many items">
|
||||
<HorizontalStack>
|
||||
<Button>A button</Button>
|
||||
<Button>Longer button button</Button>
|
||||
<Button>Another button</Button>
|
||||
<Button>And another</Button>
|
||||
<Button>Why not - one last button!</Button>
|
||||
</HorizontalStack>
|
||||
</Example>
|
||||
</div>
|
||||
|
||||
<h2>Child alignment</h2>
|
||||
|
||||
<div style={{ width: 500 }}>
|
||||
<Example title="Row, mismatched heights">
|
||||
<HorizontalStack>
|
||||
<MyComponent>
|
||||
<div style={{ height: 50, width: 100, background: 'blue' }} />
|
||||
</MyComponent>
|
||||
<MyComponent>
|
||||
<div style={{ height: 150, width: 100, background: 'orange' }} />
|
||||
</MyComponent>
|
||||
</HorizontalStack>
|
||||
</Example>
|
||||
</div>
|
||||
|
||||
<Example title="Horizontal stack, mismatched heights">
|
||||
<HorizontalStack>
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
<Card.Description>Ohhhhh - and now a description and some actions</Card.Description>
|
||||
<Card.Actions>
|
||||
<Button variant="secondary">Settings</Button>
|
||||
<Button variant="secondary">Explore</Button>
|
||||
</Card.Actions>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
<Card.Description>Ohhhhh - and now a description!</Card.Description>
|
||||
</Card>
|
||||
</HorizontalStack>
|
||||
</Example>
|
||||
|
||||
<Example title="Horizontal stack, mismatched heights with different components">
|
||||
<HorizontalStack>
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
<Card.Description>Ohhhhh - and now a description!</Card.Description>
|
||||
</Card>
|
||||
|
||||
<Alert severity="info" title="Plus an alert!" />
|
||||
</HorizontalStack>
|
||||
</Example>
|
||||
|
||||
<Example title="Horizontal stack, alerts with even heights">
|
||||
<HorizontalStack>
|
||||
<Alert severity="info" title="Plus an alert!" />
|
||||
<Alert severity="success" title="Plus an alert!" />
|
||||
<Alert severity="warning" title="Plus an alert!" />
|
||||
<Alert severity="error" title="Plus an alert!" />
|
||||
</HorizontalStack>
|
||||
</Example>
|
||||
|
||||
<Example title="Horizontal stack, alerts with mismatched heights">
|
||||
<HorizontalStack>
|
||||
<Alert severity="info" title="Plus an alert!" />
|
||||
<Alert severity="success" title="Plus an alert!" />
|
||||
<Alert severity="warning" title="Plus an alert!">
|
||||
Surprise - a description! What will happen to the height of all the other alerts?
|
||||
</Alert>
|
||||
<Alert severity="error" title="Plus an alert!" />
|
||||
</HorizontalStack>
|
||||
</Example>
|
||||
<div style={{ width: '600px', height: '600px', border: '1px solid grey' }}>
|
||||
<Stack direction={direction} wrap={wrap} alignItems={alignItems} justifyContent={justifyContent} gap={gap}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function Example({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
Basic.argTypes = {
|
||||
gap: SpacingTokenControl,
|
||||
direction: { control: 'select', options: ['row', 'row-reverse', 'column', 'column-reverse'] },
|
||||
wrap: { control: 'select', options: ['nowrap', 'wrap', 'wrap-reverse'] },
|
||||
alignItems: {
|
||||
control: 'select',
|
||||
options: ['stretch', 'flex-start', 'flex-end', 'center', 'baseline', 'start', 'end', 'self-start', 'self-end'],
|
||||
},
|
||||
justifyContent: {
|
||||
control: 'select',
|
||||
options: [
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'center',
|
||||
'space-between',
|
||||
'space-around',
|
||||
'space-evenly',
|
||||
'start',
|
||||
'end',
|
||||
'left',
|
||||
'right',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const AlignItemsExamples: StoryFn<typeof Stack> = () => {
|
||||
const theme = useTheme2();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text variant="h3">{title}</Text>
|
||||
<div style={{ background: 'rgba(255,255,255,0.1)', border: '1px dashed green' }}>{children}</div>
|
||||
<div style={{ width: '600px' }}>
|
||||
<p>Align items flex-start</p>
|
||||
<Stack direction="row" wrap="wrap" alignItems="flex-start" justifyContent="start" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
<p>Align items flex-end</p>
|
||||
<Stack direction="row" wrap="wrap" alignItems="flex-end" justifyContent="end" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
<p>Align items baseline</p>
|
||||
<Stack direction="row" wrap="nowrap" alignItems="baseline" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
<p>Align items center</p>
|
||||
<Stack direction="row" wrap="wrap" alignItems="center" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
<p>Align items stretch</p>
|
||||
<Stack direction="row" wrap="wrap" alignItems="stretch" justifyContent="center" gap={2}>
|
||||
<Item color={theme.colors.error.main} height="10em" />
|
||||
<Item color={theme.colors.error.main} />
|
||||
<Item color={theme.colors.error.main} height="3em" />
|
||||
<Item color={theme.colors.error.main} />
|
||||
<Item color={theme.colors.error.main} />
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function MyComponent({ children }: { children: React.ReactNode }) {
|
||||
return <div style={{ background: 'rgba(0,255,255, 0.2)', padding: 16 }}>{children}</div>;
|
||||
}
|
||||
export const JustifyContentExamples: StoryFn<typeof Stack> = () => {
|
||||
const theme = useTheme2();
|
||||
const justifyContentOptions: JustifyContent[] = [
|
||||
'space-between',
|
||||
'space-around',
|
||||
'space-evenly',
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'center',
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
{justifyContentOptions.map((justifyContent) => (
|
||||
<>
|
||||
<p>Justify Content {justifyContent}</p>
|
||||
<Stack direction="row" wrap="wrap" alignItems="center" justifyContent={justifyContent} gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const GapExamples: StoryFn<typeof Stack> = () => {
|
||||
const theme = useTheme2();
|
||||
const gapOptions: ThemeSpacingTokens[] = [2, 8, 10];
|
||||
return (
|
||||
<div style={{ width: '800px' }}>
|
||||
{gapOptions.map((gap) => (
|
||||
<>
|
||||
<p>Gap with spacingToken set to {gap}</p>
|
||||
<Stack direction="row" wrap="wrap" alignItems="flex-start" justifyContent="flex-start" gap={gap}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const WrapExamples: StoryFn<typeof Stack> = () => {
|
||||
const theme = useTheme2();
|
||||
const wrapOptions: Wrap[] = ['nowrap', 'wrap', 'wrap-reverse'];
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
{wrapOptions.map((wrap) => (
|
||||
<>
|
||||
<p>Wrap examples with {wrap} and gap set to spacingToken 2 (16px)</p>
|
||||
<Stack direction="row" wrap={wrap} alignItems="center" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 10 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DirectionExamples: StoryFn<typeof Stack> = () => {
|
||||
const theme = useTheme2();
|
||||
const directionOptions: Direction[] = ['row', 'row-reverse', 'column', 'column-reverse'];
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
{directionOptions.map((direction) => (
|
||||
<>
|
||||
<p>Direction {direction}</p>
|
||||
<Stack direction={direction} wrap="wrap" alignItems="center" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
@ -1,44 +1,27 @@
|
||||
import { Meta, ArgTypes, Canvas } from '@storybook/blocks';
|
||||
import { Stack, HorizontalStack } from './index';
|
||||
import * as Stories from './Stack.internal.story';
|
||||
import { Meta, ArgTypes } from '@storybook/blocks';
|
||||
import { Stack } from './Stack';
|
||||
|
||||
<Meta title="MDX|Stack" component={Stack} />
|
||||
|
||||
# Stack
|
||||
|
||||
The `Stack` component is designed to assist with layout and positioning of elements within a container, offering a simple and flexible way to stack elements vertically or horizontally. This documentation outlines the proper usage of the Stack component and provides guidance on when to use it over the Grid or Flex components.
|
||||
|
||||
There is also a `HorizontalStack` component, which is a thin wrapper around Stack, equivalent to `<Stack direction="row">`.
|
||||
The Stack component is a simple wrapper around the flexbox layout model that allows to easily create responsive and flexible layouts. It provides a simple and intuitive way to align and distribute items within a container either horizontally or vertically.
|
||||
|
||||
### Usage
|
||||
|
||||
#### When to use
|
||||
|
||||
Use the Stack component when you need to arrange elements in a linear format, either vertically or horizontally. It's particularly useful when you want to maintain consistent spacing between items.
|
||||
|
||||
Use the Stack component when:
|
||||
|
||||
- You need a simple way to stack elements with predictable spacing.
|
||||
- You want to ensure consistent alignment.
|
||||
- For creating responsive and flexible layouts that can adapt to different screen sizes and orientations.
|
||||
- When needing a simple and intuitive way to align and distribute items within a container either horizontally or vertically.
|
||||
- To easily reorder and rearrange elements without changing the HTML structure.
|
||||
- When aiming to create equal height columns.
|
||||
- To create a grid-like structure with automatic wrapping and sizing of items based on the available space.
|
||||
|
||||
#### When not to use
|
||||
|
||||
Use the `Grid` component instead for these use cases:
|
||||
- For complex multi-dimensional layouts with intricate requirements that are better suited for CSS frameworks or grid systems.
|
||||
- When precise control over spacing and positioning of elements is necessary.
|
||||
|
||||
- **Intricate Layouts:** Grids are ideal for complex dashboard and magazine-style designs with rows and columns.
|
||||
- **Structured Grid:** Use Grids when items need to span multiple rows/columns, creating structured layouts.
|
||||
|
||||
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.
|
||||
|
||||
## Props
|
||||
|
||||
### Stack
|
||||
### Props
|
||||
|
||||
<ArgTypes of={Stack} />
|
||||
|
||||
### HorizontalStack
|
||||
|
||||
<ArgTypes of={HorizontalStack} />
|
||||
|
@ -1,26 +1,89 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { ThemeSpacingTokens } from '@grafana/data';
|
||||
import { GrafanaTheme2, ThemeSpacingTokens } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../../themes';
|
||||
import { ResponsiveProp, getResponsiveStyle } from '../utils/responsiveness';
|
||||
|
||||
export type AlignItems =
|
||||
| 'stretch'
|
||||
| 'flex-start'
|
||||
| 'flex-end'
|
||||
| 'center'
|
||||
| 'baseline'
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'self-start'
|
||||
| 'self-end';
|
||||
|
||||
export type JustifyContent =
|
||||
| 'flex-start'
|
||||
| 'flex-end'
|
||||
| 'center'
|
||||
| 'space-between'
|
||||
| 'space-around'
|
||||
| 'space-evenly'
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'left'
|
||||
| 'right';
|
||||
|
||||
export type Direction = 'row' | 'row-reverse' | 'column' | 'column-reverse';
|
||||
|
||||
export type Wrap = 'nowrap' | 'wrap' | 'wrap-reverse';
|
||||
|
||||
import { Flex } from '../Flex/Flex';
|
||||
import { ResponsiveProp } from '../utils/responsiveness';
|
||||
interface StackProps extends Omit<React.HTMLAttributes<HTMLElement>, 'className' | 'style'> {
|
||||
direction?: ResponsiveProp<'column' | 'row'>;
|
||||
gap?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
alignItems?: ResponsiveProp<AlignItems>;
|
||||
justifyContent?: ResponsiveProp<JustifyContent>;
|
||||
direction?: ResponsiveProp<Direction>;
|
||||
wrap?: ResponsiveProp<Wrap>;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Stack = React.forwardRef<HTMLDivElement, React.PropsWithChildren<StackProps>>(
|
||||
({ gap = 1, direction = 'column', children, ...rest }, ref) => {
|
||||
export const Stack = React.forwardRef<HTMLDivElement, StackProps>(
|
||||
({ gap = 1, alignItems, justifyContent, direction, wrap, children, ...rest }, ref) => {
|
||||
const styles = useStyles2(getStyles, gap, alignItems, justifyContent, direction, wrap);
|
||||
|
||||
return (
|
||||
<Flex ref={ref} gap={gap} direction={direction} wrap="wrap" {...rest}>
|
||||
{React.Children.toArray(children)
|
||||
.filter(Boolean)
|
||||
.map((child, index) => (
|
||||
<div key={index}>{child}</div>
|
||||
))}
|
||||
</Flex>
|
||||
<div ref={ref} className={styles.flex} {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Stack.displayName = 'Stack';
|
||||
|
||||
const getStyles = (
|
||||
theme: GrafanaTheme2,
|
||||
gap: StackProps['gap'],
|
||||
alignItems: StackProps['alignItems'],
|
||||
justifyContent: StackProps['justifyContent'],
|
||||
direction: StackProps['direction'],
|
||||
wrap: StackProps['wrap']
|
||||
) => {
|
||||
return {
|
||||
flex: css([
|
||||
{
|
||||
display: 'flex',
|
||||
},
|
||||
getResponsiveStyle<Direction>(theme, direction, (val) => ({
|
||||
flexDirection: val,
|
||||
})),
|
||||
getResponsiveStyle<Wrap>(theme, wrap, (val) => ({
|
||||
flexWrap: val,
|
||||
})),
|
||||
getResponsiveStyle<AlignItems>(theme, alignItems, (val) => ({
|
||||
alignItems: val,
|
||||
})),
|
||||
getResponsiveStyle<JustifyContent>(theme, justifyContent, (val) => ({
|
||||
justifyContent: val,
|
||||
})),
|
||||
getResponsiveStyle<ThemeSpacingTokens>(theme, gap, (val) => ({
|
||||
gap: theme.spacing(val),
|
||||
})),
|
||||
]),
|
||||
};
|
||||
};
|
||||
|
@ -1,2 +0,0 @@
|
||||
export { Stack } from './Stack';
|
||||
export { HorizontalStack } from './HorizontalStack';
|
@ -9,7 +9,5 @@
|
||||
* be subject to the standard policies
|
||||
*/
|
||||
|
||||
export * from './components/Layout/Flex/Flex';
|
||||
|
||||
export { Grid } from './components/Layout/Grid/Grid';
|
||||
export { Stack, HorizontalStack } from './components/Layout/Stack';
|
||||
export { Stack } from './components/Layout/Stack/Stack';
|
||||
|
@ -6,7 +6,7 @@ import { useLocation } from 'react-router-dom';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { CustomScrollbar, Icon, IconButton, useStyles2 } from '@grafana/ui';
|
||||
import { Flex } from '@grafana/ui/src/unstable';
|
||||
import { Stack } from '@grafana/ui/src/unstable';
|
||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { useSelector } from 'app/types';
|
||||
@ -55,7 +55,7 @@ export const MegaMenu = React.memo(
|
||||
<CustomScrollbar showScrollIndicators hideHorizontalTrack>
|
||||
<ul className={styles.itemList}>
|
||||
{navItems.map((link, index) => (
|
||||
<Flex key={link.text} direction="row" alignItems="center">
|
||||
<Stack key={link.text} direction="row" alignItems="center">
|
||||
<MegaMenuItem
|
||||
link={link}
|
||||
onClick={state.megaMenu === 'open' ? onClose : undefined}
|
||||
@ -74,7 +74,7 @@ export const MegaMenu = React.memo(
|
||||
variant="secondary"
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Stack>
|
||||
))}
|
||||
</ul>
|
||||
</CustomScrollbar>
|
||||
|
@ -2,7 +2,7 @@ import React, { forwardRef, PropsWithChildren } from 'react';
|
||||
|
||||
import { IconName } from '@grafana/data';
|
||||
import { Icon, Tooltip, Box } from '@grafana/ui';
|
||||
import { Flex } from '@grafana/ui/src/unstable';
|
||||
import { Stack } from '@grafana/ui/src/unstable';
|
||||
import { Unit } from 'app/types';
|
||||
|
||||
type OrgUnitProps = { units?: Unit[]; icon: IconName };
|
||||
@ -15,7 +15,7 @@ export const OrgUnits = ({ units, icon }: OrgUnitProps) => {
|
||||
return units.length > 1 ? (
|
||||
<Tooltip
|
||||
placement={'top'}
|
||||
content={<Flex direction={'column'}>{units?.map((unit) => <span key={unit.name}>{unit.name}</span>)}</Flex>}
|
||||
content={<Stack direction={'column'}>{units?.map((unit) => <span key={unit.name}>{unit.name}</span>)}</Stack>}
|
||||
>
|
||||
<Content icon={icon}>{units.length}</Content>
|
||||
</Tooltip>
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
Avatar,
|
||||
Box,
|
||||
} from '@grafana/ui';
|
||||
import { Flex, Stack } from '@grafana/ui/src/unstable';
|
||||
import { Stack } from '@grafana/ui/src/unstable';
|
||||
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
||||
import { fetchRoleOptions } from 'app/core/components/RolePicker/api';
|
||||
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
|
||||
@ -223,9 +223,9 @@ export const OrgUsersTable = ({
|
||||
getRowId={(user) => String(user.userId)}
|
||||
fetchData={fetchData}
|
||||
/>
|
||||
<Flex justifyContent="flex-end">
|
||||
<Stack justifyContent="flex-end">
|
||||
<Pagination onNavigate={changePage} currentPage={page} numberOfPages={totalPages} hideWhenSinglePage={true} />
|
||||
</Flex>
|
||||
</Stack>
|
||||
</TableWrapper>
|
||||
{Boolean(userToRemove) && (
|
||||
<ConfirmModal
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
Text,
|
||||
Avatar,
|
||||
} from '@grafana/ui';
|
||||
import { Flex, Stack } from '@grafana/ui/src/unstable';
|
||||
import { Stack } from '@grafana/ui/src/unstable';
|
||||
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
|
||||
import { UserDTO } from 'app/types';
|
||||
|
||||
@ -68,14 +68,14 @@ export const UsersTable = ({
|
||||
header: 'Belongs to',
|
||||
cell: ({ cell: { value, row } }: Cell<'orgs'>) => {
|
||||
return (
|
||||
<Flex alignItems={'center'}>
|
||||
<Stack alignItems={'center'}>
|
||||
<OrgUnits units={value} icon={'building'} />
|
||||
{row.original.isAdmin && (
|
||||
<Tooltip placement="top" content="Grafana Admin">
|
||||
<Icon name="shield" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Flex>
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
sortType: (a, b) => (a.original.orgs?.length || 0) - (b.original.orgs?.length || 0),
|
||||
@ -146,9 +146,9 @@ export const UsersTable = ({
|
||||
<Stack gap={2}>
|
||||
<InteractiveTable columns={columns} data={users} getRowId={(user) => String(user.id)} fetchData={fetchData} />
|
||||
{showPaging && (
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Stack justifyContent={'flex-end'}>
|
||||
<Pagination numberOfPages={totalPages} currentPage={currentPage} onNavigate={onChangePage} />
|
||||
</Flex>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
|
@ -5,7 +5,7 @@ import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { config, locationService, reportInteraction } from '@grafana/runtime';
|
||||
import { Button, useStyles2, Text, Box } from '@grafana/ui';
|
||||
import { Flex } from '@grafana/ui/src/unstable';
|
||||
import { Stack } from '@grafana/ui/src/unstable';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
import { onAddLibraryPanel, onCreateNewPanel, onImportDashboard } from 'app/features/dashboard/utils/dashboard';
|
||||
@ -24,11 +24,11 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
|
||||
const initialDatasource = useSelector((state) => state.dashboard.initialDatasource);
|
||||
|
||||
return (
|
||||
<Flex alignItems="center" justifyContent="center">
|
||||
<Stack alignItems="center" justifyContent="center">
|
||||
<div className={styles.wrapper}>
|
||||
<Flex alignItems="stretch" justifyContent="center" gap={4} direction="column">
|
||||
<Stack alignItems="stretch" justifyContent="center" gap={4} direction="column">
|
||||
<Box borderColor="strong" borderStyle="dashed" padding={4}>
|
||||
<Flex direction="column" alignItems="center" gap={2}>
|
||||
<Stack direction="column" alignItems="center" gap={2}>
|
||||
<Text element="h1" textAlignment="center" weight="medium">
|
||||
<Trans i18nKey="dashboard.empty.add-visualization-header">
|
||||
Start your new dashboard by adding a visualization
|
||||
@ -56,12 +56,12 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
|
||||
>
|
||||
<Trans i18nKey="dashboard.empty.add-visualization-button">Add visualization</Trans>
|
||||
</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Flex direction={{ xs: 'column', md: 'row' }} wrap="wrap" gap={4}>
|
||||
<Stack direction={{ xs: 'column', md: 'row' }} wrap="wrap" gap={4}>
|
||||
{config.featureToggles.vizAndWidgetSplit && (
|
||||
<Box borderColor="strong" borderStyle="dashed" padding={3} grow={1}>
|
||||
<Flex direction="column" alignItems="center" gap={1}>
|
||||
<Stack direction="column" alignItems="center" gap={1}>
|
||||
<Text element="h3" textAlignment="center" weight="medium">
|
||||
<Trans i18nKey="dashboard.empty.add-widget-header">Add a widget</Trans>
|
||||
</Text>
|
||||
@ -82,11 +82,11 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
|
||||
>
|
||||
<Trans i18nKey="dashboard.empty.add-widget-button">Add widget</Trans>
|
||||
</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
<Box borderColor="strong" borderStyle="dashed" padding={3} grow={1}>
|
||||
<Flex direction="column" alignItems="center" gap={1}>
|
||||
<Stack direction="column" alignItems="center" gap={1}>
|
||||
<Text element="h3" textAlignment="center" weight="medium">
|
||||
<Trans i18nKey="dashboard.empty.add-library-panel-header">Import panel</Trans>
|
||||
</Text>
|
||||
@ -109,10 +109,10 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
|
||||
>
|
||||
<Trans i18nKey="dashboard.empty.add-library-panel-button">Add library panel</Trans>
|
||||
</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box borderColor="strong" borderStyle="dashed" padding={3} grow={1}>
|
||||
<Flex direction="column" alignItems="center" gap={1}>
|
||||
<Stack direction="column" alignItems="center" gap={1}>
|
||||
<Text element="h3" textAlignment="center" weight="medium">
|
||||
<Trans i18nKey="dashboard.empty.import-a-dashboard-header">Import a dashboard</Trans>
|
||||
</Text>
|
||||
@ -136,12 +136,12 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
|
||||
>
|
||||
<Trans i18nKey="dashboard.empty.import-dashboard-button">Import dashboard</Trans>
|
||||
</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</div>
|
||||
</Flex>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
InlineField,
|
||||
Pagination,
|
||||
} from '@grafana/ui';
|
||||
import { Flex } from '@grafana/ui/src/unstable';
|
||||
import { Stack } from '@grafana/ui/src/unstable';
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
||||
@ -253,9 +253,9 @@ export const ServiceAccountsListPageUnconnected = ({
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Flex justifyContent="flex-end">
|
||||
<Stack justifyContent="flex-end">
|
||||
<Pagination hideWhenSinglePage currentPage={page} numberOfPages={totalPages} onNavigate={changePage} />
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
Pagination,
|
||||
Avatar,
|
||||
} from '@grafana/ui';
|
||||
import { Stack, Flex } from '@grafana/ui/src/unstable';
|
||||
import { Stack } from '@grafana/ui/src/unstable';
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import { fetchRoleOptions } from 'app/core/components/RolePicker/api';
|
||||
@ -169,14 +169,14 @@ export const TeamList = ({
|
||||
getRowId={(team) => String(team.id)}
|
||||
fetchData={changeSort}
|
||||
/>
|
||||
<Flex justifyContent="flex-end">
|
||||
<Stack justifyContent="flex-end">
|
||||
<Pagination
|
||||
hideWhenSinglePage
|
||||
currentPage={page}
|
||||
numberOfPages={totalPages}
|
||||
onNavigate={changePage}
|
||||
/>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</TableWrapper>
|
||||
</Stack>
|
||||
</>
|
||||
|
Loading…
Reference in New Issue
Block a user