diff --git a/packages/grafana-ui/src/components/Layout/Layout.story.tsx b/packages/grafana-ui/src/components/Layout/Layout.story.tsx new file mode 100644 index 00000000000..d382ffa3e0c --- /dev/null +++ b/packages/grafana-ui/src/components/Layout/Layout.story.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { VerticalGroup, HorizontalGroup, Layout } from './Layout'; +import { Button } from '../Forms/Button'; +import { withStoryContainer } from '../../utils/storybook/withStoryContainer'; +import { select } from '@storybook/addon-knobs'; + +export default { + title: 'Layout/Groups', + component: Layout, + decorators: [withStoryContainer, withCenteredStory, withHorizontallyCenteredStory], +}; + +const justifyVariants = ['flex-start', 'flex-end', 'space-between']; + +const spacingVariants = ['xs', 'sm', 'md', 'lg']; + +export const horizontal = () => { + const justify = select('Justify elements', justifyVariants, 'flex-start'); + const spacing = select('Elements spacing', spacingVariants, 'sm'); + return ( + + + + + ); +}; + +export const vertical = () => { + const justify = select('Justify elements', justifyVariants, 'flex-start'); + const spacing = select('Elements spacing', spacingVariants, 'sm'); + return ( + + + + + ); +}; diff --git a/packages/grafana-ui/src/components/Layout/Layout.tsx b/packages/grafana-ui/src/components/Layout/Layout.tsx new file mode 100644 index 00000000000..c4d7b514e81 --- /dev/null +++ b/packages/grafana-ui/src/components/Layout/Layout.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { css } from 'emotion'; +import { stylesFactory, useTheme } from '../../themes'; +import { GrafanaTheme } from '@grafana/data'; + +enum Orientation { + Horizontal, + Vertical, +} +type Spacing = 'xs' | 'sm' | 'md' | 'lg'; +type Justify = 'flex-start' | 'flex-end' | 'space-between'; + +export interface LayoutProps { + children: React.ReactNode[]; + orientation?: Orientation; + spacing?: Spacing; + justify?: Justify; +} + +export const Layout: React.FC = ({ + children, + orientation = Orientation.Horizontal, + spacing = 'sm', + justify = 'flex-start', +}) => { + const theme = useTheme(); + const styles = getStyles(theme, orientation, spacing, justify); + return ( +
+ {React.Children.map(children, (child, index) => { + return
{child}
; + })} +
+ ); +}; + +export const HorizontalGroup: React.FC> = ({ children, spacing, justify }) => ( + + {children} + +); +export const VerticalGroup: React.FC> = ({ children, spacing, justify }) => ( + + {children} + +); + +const getStyles = stylesFactory((theme: GrafanaTheme, orientation: Orientation, spacing: Spacing, justify: Justify) => { + return { + layout: css` + display: flex; + flex-direction: ${orientation === Orientation.Vertical ? 'column' : 'row'}; + justify-content: ${justify}; + height: 100%; + `, + buttonWrapper: css` + margin-bottom: ${orientation === Orientation.Horizontal ? 0 : theme.spacing[spacing]}; + margin-right: ${orientation === Orientation.Horizontal ? theme.spacing[spacing] : 0}; + + &:last-child { + margin-bottom: 0; + margin-right: 0; + } + `, + }; +}); diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 98f4b884871..2efde49203d 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -25,7 +25,6 @@ export { SecretFormField } from './SecretFormFied/SecretFormField'; export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder'; export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker'; export { SeriesColorPickerPopover, SeriesColorPickerPopoverWithTheme } from './ColorPicker/SeriesColorPickerPopover'; - export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup'; export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid'; export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor'; @@ -142,3 +141,4 @@ export { default as Forms } from './Forms'; export { ValuePicker } from './ValuePicker/ValuePicker'; export { fieldMatchersUI } from './MatchersUI/fieldMatchersUI'; export { getStandardFieldConfigs } from './FieldConfigs/standardFieldConfigEditors'; +export { HorizontalGroup, VerticalGroup } from './Layout/Layout'; diff --git a/packages/grafana-ui/src/utils/storybook/withStoryContainer.tsx b/packages/grafana-ui/src/utils/storybook/withStoryContainer.tsx index cebc8ad3a20..d5d43402562 100644 --- a/packages/grafana-ui/src/utils/storybook/withStoryContainer.tsx +++ b/packages/grafana-ui/src/utils/storybook/withStoryContainer.tsx @@ -3,9 +3,15 @@ import { boolean, number } from '@storybook/addon-knobs'; import { css, cx } from 'emotion'; import { RenderFunction } from '../../types'; -const StoryContainer: React.FC<{ width?: number; showBoundaries: boolean }> = ({ children, width, showBoundaries }) => { +const StoryContainer: React.FC<{ width?: number; height?: number; showBoundaries: boolean }> = ({ + children, + width, + height, + showBoundaries, +}) => { const checkColor = '#f0f0f0'; const finalWidth = width ? `${width}px` : '100%'; + const finalHeight = height !== 0 ? `${height}px` : 'auto'; const bgStyles = showBoundaries && css` @@ -27,6 +33,7 @@ const StoryContainer: React.FC<{ width?: number; showBoundaries: boolean }> = ({ className={cx( css` width: ${finalWidth}; + height: ${finalHeight}; `, bgStyles )} @@ -52,8 +59,23 @@ export const withStoryContainer = (story: RenderFunction) => { }, CONTAINER_GROUP ); + const containerHeight = number( + 'Container height', + 0, + { + range: true, + min: 100, + max: 500, + step: 10, + }, + CONTAINER_GROUP + ); return ( - + {story()} );