mirror of https://github.com/grafana/grafana
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 docspull/76670/head
parent
d511925fc9
commit
f5cbd4f9d0
@ -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), |
||||
})), |
||||
]), |
||||
}; |
||||
}; |
@ -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,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'; |
Loading…
Reference in new issue