Stack: Improve layout (#75144)

* stack test cases

* more example

* Make changes to Stack

* Update mdx

* Add forwardRef

* Export Vertical and Horizontal components

* Fix spelling mistake

* PR feedback

* Fix export

* horizantal and vertical to row and column

* Fix failing import

---------

Co-authored-by: Josh Hunt <joshhunt@users.noreply.github.com>
Co-authored-by: joshhunt <josh@trtr.co>
pull/75258/head^2
Tobias Skarhed 2 years ago committed by GitHub
parent cfd468bcdd
commit 10a874ba6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      packages/grafana-ui/src/components/Layout/Stack/HorizontalStack.tsx
  2. 187
      packages/grafana-ui/src/components/Layout/Stack/Stack.internal.story.tsx
  3. 16
      packages/grafana-ui/src/components/Layout/Stack/Stack.mdx
  4. 25
      packages/grafana-ui/src/components/Layout/Stack/Stack.tsx
  5. 2
      packages/grafana-ui/src/components/Layout/Stack/index.ts
  6. 2
      packages/grafana-ui/src/unstable.ts

@ -0,0 +1,17 @@
import React from 'react';
import { ThemeSpacingTokens } from '@grafana/data';
import { ResponsiveProp } from '../utils/responsiveness';
import { Stack } from './Stack';
export const HorizontalStack = React.forwardRef<
HTMLDivElement,
React.PropsWithChildren<{ gap?: ResponsiveProp<ThemeSpacingTokens> }>
>(({ children, gap = 1 }, ref) => (
<Stack ref={ref} direction="row" gap={gap}>
{children}
</Stack>
));
HorizontalStack.displayName = 'HorizontalStack';

@ -2,7 +2,12 @@ import { Meta, StoryFn } from '@storybook/react';
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { SpacingTokenControl } from '../../../utils/storybook/themeStorybookControls'; 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 } from './Stack';
import mdx from './Stack.mdx'; import mdx from './Stack.mdx';
@ -16,7 +21,7 @@ const meta: Meta<typeof Stack> = {
}, },
argTypes: { argTypes: {
gap: SpacingTokenControl, gap: SpacingTokenControl,
direction: { control: 'select', options: ['row', 'row-reverse', 'column', 'column-reverse'] }, direction: { control: 'select', options: ['row', 'column'] },
}, },
}; };
@ -34,4 +39,184 @@ export const Basic: StoryFn<typeof Stack> = ({ direction = 'column', gap = 2 })
); );
}; };
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>
</Stack>
</div>
);
};
function Example({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div>
<Text variant="h3">{title}</Text>
<div style={{ background: 'rgba(255,255,255,0.1)', border: '1px dashed green' }}>{children}</div>
</div>
);
}
function MyComponent({ children }: { children: React.ReactNode }) {
return <div style={{ background: 'rgba(0,255,255, 0.2)', padding: 16 }}>{children}</div>;
}
export default meta; export default meta;

@ -1,5 +1,5 @@
import { Meta, ArgTypes, Canvas } from '@storybook/blocks'; import { Meta, ArgTypes, Canvas } from '@storybook/blocks';
import { Stack } from './Stack'; import { Stack, HorizontalStack } from './index';
import * as Stories from './Stack.internal.story'; import * as Stories from './Stack.internal.story';
<Meta title="MDX|Stack" component={Stack} /> <Meta title="MDX|Stack" component={Stack} />
@ -8,6 +8,8 @@ import * as Stories from './Stack.internal.story';
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. 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">`.
### Usage ### Usage
#### When to use #### When to use
@ -28,7 +30,15 @@ Use the `Grid` component instead for these use cases:
Use the `Flex` component instead for these use cases: Use the `Flex` component instead for these use cases:
- **Centering:** Perfect for centering elements both vertically and horizontally. - **Alignment:** More options for item alignment.
- **Dynamic Order:** Easily reorder elements for responsive layouts without changing code. - **Flex items:** Custom flex basis or configure how items stretch and wrap.
## Props
### Stack
<ArgTypes of={Stack} /> <ArgTypes of={Stack} />
### HorizontalStack
<ArgTypes of={HorizontalStack} />

@ -2,18 +2,23 @@ import React from 'react';
import { ThemeSpacingTokens } from '@grafana/data'; import { ThemeSpacingTokens } from '@grafana/data';
import { Direction, Flex } from '../Flex/Flex'; import { Flex } from '../Flex/Flex';
import { ResponsiveProp } from '../utils/responsiveness'; import { ResponsiveProp } from '../utils/responsiveness';
interface StackProps { interface StackProps {
direction?: ResponsiveProp<Direction>; direction?: ResponsiveProp<'column' | 'row'>;
gap?: ResponsiveProp<ThemeSpacingTokens>; gap?: ResponsiveProp<ThemeSpacingTokens>;
} }
export const Stack = ({ gap = 1, direction = 'column', children }: React.PropsWithChildren<StackProps>) => { export const Stack = React.forwardRef<HTMLDivElement, React.PropsWithChildren<StackProps>>(
return ( ({ gap = 1, direction = 'column', children }, ref) => {
<Flex gap={gap} direction={direction}> return (
{children} <Flex ref={ref} gap={gap} direction={direction} wrap="wrap">
</Flex> {React.Children.map(children, (child) => (
); <div>{child}</div>
}; ))}
</Flex>
);
}
);
Stack.displayName = 'Stack';

@ -0,0 +1,2 @@
export { Stack } from './Stack';
export { HorizontalStack } from './HorizontalStack';

@ -12,4 +12,4 @@
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 { Stack } from './components/Layout/Stack/Stack'; export { Stack, HorizontalStack } from './components/Layout/Stack';

Loading…
Cancel
Save