mirror of
https://github.com/grafana/grafana.git
synced 2024-11-22 08:56:43 -06:00
GrafanaUI: Create Box component (#73637)
This commit is contained in:
parent
7e4ae5fdb6
commit
42cc6b1842
@ -1,6 +1,5 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { AriaRole, HTMLAttributes, ReactNode } from 'react';
|
||||
import tinycolor2 from 'tinycolor2';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
@ -9,6 +8,8 @@ import { useTheme2 } from '../../themes';
|
||||
import { IconName } from '../../types/icon';
|
||||
import { Button } from '../Button/Button';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { Box } from '../Layout/Box/Box';
|
||||
import { Text } from '../Text/Text';
|
||||
|
||||
export type AlertVariant = 'success' | 'warning' | 'error' | 'info';
|
||||
|
||||
@ -55,42 +56,55 @@ export const Alert = React.forwardRef<HTMLDivElement, Props>(
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cx(styles.alert, className)}
|
||||
className={cx(styles.wrapper, className)}
|
||||
data-testid={selectors.components.Alert.alertV2(severity)}
|
||||
role={role}
|
||||
aria-label={ariaLabel}
|
||||
{...restProps}
|
||||
>
|
||||
<div className={styles.icon}>
|
||||
<Icon size="xl" name={getIconFromSeverity(severity)} />
|
||||
</div>
|
||||
<Box
|
||||
display="flex"
|
||||
backgroundColor={severity}
|
||||
borderRadius="default"
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
borderStyle="solid"
|
||||
borderColor={severity}
|
||||
alignItems="stretch"
|
||||
boxShadow={elevated ? 'z3' : undefined}
|
||||
>
|
||||
<Box paddingTop={1} paddingRight={2}>
|
||||
<div className={styles.icon}>
|
||||
<Icon size="xl" name={getIconFromSeverity(severity)} />
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div className={styles.body}>
|
||||
<div className={styles.title}>{title}</div>
|
||||
{children && <div className={styles.content}>{children}</div>}
|
||||
</div>
|
||||
<Box paddingY={1} grow={1}>
|
||||
<Text weight="medium">{title}</Text>
|
||||
{children && <div className={styles.content}>{children}</div>}
|
||||
</Box>
|
||||
{/* If onRemove is specified, giving preference to onRemove */}
|
||||
{onRemove && !buttonContent && (
|
||||
<div className={styles.close}>
|
||||
<Button
|
||||
aria-label="Close alert"
|
||||
icon="times"
|
||||
onClick={onRemove}
|
||||
type="button"
|
||||
fill="text"
|
||||
variant="secondary"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* If onRemove is specified, giving preference to onRemove */}
|
||||
{onRemove && !buttonContent && (
|
||||
<div className={styles.close}>
|
||||
<Button
|
||||
aria-label="Close alert"
|
||||
icon="times"
|
||||
onClick={onRemove}
|
||||
type="button"
|
||||
fill="text"
|
||||
variant="secondary"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{onRemove && buttonContent && (
|
||||
<div className={styles.buttonWrapper}>
|
||||
<Button aria-label="Close alert" variant="secondary" onClick={onRemove} type="button">
|
||||
{buttonContent}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{onRemove && buttonContent && (
|
||||
<Box marginLeft={1} display="flex" alignItems="center">
|
||||
<Button aria-label="Close alert" variant="secondary" onClick={onRemove} type="button">
|
||||
{buttonContent}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -120,24 +134,13 @@ const getStyles = (
|
||||
topSpacing?: number
|
||||
) => {
|
||||
const color = theme.colors[severity];
|
||||
const borderRadius = theme.shape.radius.default;
|
||||
const borderColor = tinycolor2(color.border).setAlpha(0.2).toString();
|
||||
|
||||
return {
|
||||
alert: css({
|
||||
label: 'alert',
|
||||
wrapper: css({
|
||||
flexGrow: 1,
|
||||
position: 'relative',
|
||||
borderRadius,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'stretch',
|
||||
background: color.transparent,
|
||||
boxShadow: elevated ? theme.shadows.z3 : 'none',
|
||||
padding: theme.spacing(1, 2),
|
||||
border: `1px solid ${borderColor}`,
|
||||
marginBottom: theme.spacing(bottomSpacing ?? 2),
|
||||
marginTop: theme.spacing(topSpacing ?? 0),
|
||||
position: 'relative',
|
||||
|
||||
'&:before': {
|
||||
content: '""',
|
||||
@ -151,33 +154,13 @@ const getStyles = (
|
||||
},
|
||||
}),
|
||||
icon: css({
|
||||
padding: theme.spacing(1, 2, 0, 0),
|
||||
color: color.text,
|
||||
display: 'flex',
|
||||
}),
|
||||
title: css({
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
}),
|
||||
body: css({
|
||||
padding: theme.spacing(1, 0),
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
overflowWrap: 'break-word',
|
||||
wordBreak: 'break-word',
|
||||
}),
|
||||
content: css({
|
||||
paddingTop: hasTitle ? theme.spacing(0.5) : 0,
|
||||
maxHeight: '50vh',
|
||||
overflowY: 'auto',
|
||||
}),
|
||||
buttonWrapper: css({
|
||||
marginLeft: theme.spacing(1),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
alignSelf: 'center',
|
||||
}),
|
||||
close: css({
|
||||
position: 'relative',
|
||||
color: theme.colors.text.secondary,
|
||||
|
@ -0,0 +1,138 @@
|
||||
import { Meta, StoryFn } from '@storybook/react';
|
||||
import React from 'react';
|
||||
|
||||
import { SpacingTokenControl } from '../../../utils/storybook/themeStorybookControls';
|
||||
import { Text } from '../../Text/Text';
|
||||
import { Flex } from '../Flex/Flex';
|
||||
|
||||
import { Box, BackgroundColor, BorderColor, BorderStyle, BorderRadius, BoxShadow } from './Box';
|
||||
import mdx from './Box.mdx';
|
||||
|
||||
const backgroundOptions: BackgroundColor[] = ['primary', 'secondary', 'canvas', 'error', 'success', 'warning', 'info'];
|
||||
const borderColorOptions: BorderColor[] = ['weak', 'medium', 'strong', 'error', 'success', 'warning', 'info'];
|
||||
const borderStyleOptions: BorderStyle[] = ['dashed', 'solid'];
|
||||
const borderRadiusOptions: BorderRadius[] = ['default', 'pill', 'circle'];
|
||||
const boxShadowOptions: BoxShadow[] = ['z1', 'z2', 'z3'];
|
||||
|
||||
const meta: Meta<typeof Box> = {
|
||||
title: 'General/Layout/Box',
|
||||
component: Box,
|
||||
parameters: {
|
||||
docs: {
|
||||
page: mdx,
|
||||
},
|
||||
controls: { exclude: ['element'] },
|
||||
},
|
||||
};
|
||||
|
||||
const Item = ({ background }: { background?: string }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '50px',
|
||||
height: '50px',
|
||||
background,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Basic: StoryFn<typeof Box> = (args) => {
|
||||
return (
|
||||
<div style={{ backgroundColor: 'green' }}>
|
||||
<Box {...args}>
|
||||
<Item background="red" />
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Basic.argTypes = {
|
||||
grow: { control: 'number' },
|
||||
shrink: { control: 'number' },
|
||||
margin: SpacingTokenControl,
|
||||
marginX: SpacingTokenControl,
|
||||
marginY: SpacingTokenControl,
|
||||
marginTop: SpacingTokenControl,
|
||||
marginBottom: SpacingTokenControl,
|
||||
marginLeft: SpacingTokenControl,
|
||||
marginRight: SpacingTokenControl,
|
||||
padding: SpacingTokenControl,
|
||||
paddingX: SpacingTokenControl,
|
||||
paddingY: SpacingTokenControl,
|
||||
paddingTop: SpacingTokenControl,
|
||||
paddingBottom: SpacingTokenControl,
|
||||
paddingLeft: SpacingTokenControl,
|
||||
paddingRight: SpacingTokenControl,
|
||||
display: { control: 'select', options: ['flex', 'block', 'inline', 'none'] },
|
||||
backgroundColor: { control: 'select', options: backgroundOptions },
|
||||
borderStyle: { control: 'select', options: borderStyleOptions },
|
||||
borderColor: { control: 'select', options: borderColorOptions },
|
||||
borderRadius: { control: 'select', options: borderRadiusOptions },
|
||||
boxShadow: { control: 'select', options: boxShadowOptions },
|
||||
};
|
||||
|
||||
export const Background: StoryFn<typeof Box> = () => {
|
||||
return (
|
||||
<Flex gap={4}>
|
||||
{backgroundOptions.map((background) => (
|
||||
<Flex key={background} direction="column" alignItems="flex-start">
|
||||
{background}
|
||||
<Box backgroundColor={background} borderColor="strong" borderStyle="solid">
|
||||
<Item />
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const Border: StoryFn<typeof Box> = () => {
|
||||
return (
|
||||
<Flex direction="column" gap={4}>
|
||||
<div>
|
||||
<Text variant="h4">Border Color</Text>
|
||||
<Flex gap={4} wrap="wrap">
|
||||
{borderColorOptions.map((border) => (
|
||||
<Flex key={border} direction="column" alignItems="flex-start">
|
||||
{border}
|
||||
<Box borderColor={border} borderStyle="solid">
|
||||
<Item />
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</div>
|
||||
<div>
|
||||
<Text variant="h4">Border Style</Text>
|
||||
<Flex gap={4} wrap="wrap">
|
||||
{borderStyleOptions.map((border) => (
|
||||
<Flex key={border} direction="column" alignItems="flex-start">
|
||||
{border}
|
||||
<Box borderColor="info" borderStyle={border}>
|
||||
<Item />
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</div>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const Shadow: StoryFn<typeof Box> = () => {
|
||||
return (
|
||||
<Flex gap={4}>
|
||||
{boxShadowOptions.map((shadow) => (
|
||||
<Flex key={shadow} direction="column" alignItems="flex-start">
|
||||
{shadow}
|
||||
<Box boxShadow={shadow} borderColor="strong" borderStyle="solid">
|
||||
<Item />
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default meta;
|
39
packages/grafana-ui/src/components/Layout/Box/Box.mdx
Normal file
39
packages/grafana-ui/src/components/Layout/Box/Box.mdx
Normal file
@ -0,0 +1,39 @@
|
||||
import { Meta, ArgTypes } from '@storybook/blocks';
|
||||
import { Box } from './Box';
|
||||
|
||||
<Meta title="MDX|Box" component={Box} />
|
||||
|
||||
# Box
|
||||
|
||||
The Box Component is the most basic layout component. It can be used to build more complex components and layouts with properties
|
||||
that use our design tokens instead of using CSS.
|
||||
|
||||
### Usage
|
||||
|
||||
#### When to use
|
||||
|
||||
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.
|
||||
|
||||
### How to add a prop to Box
|
||||
|
||||
1. Make sure you absolutely need this prop. If in doubt, ask someone from the design system team.
|
||||
2. Add the prop to the `BoxProps` interface in `Box.tsx`.
|
||||
- Make sure it is strictly typed, making use of design tokens if needed. Instead of `[propName]: number`, use `[propName]: ThemeSpacingTokens`;
|
||||
- If it is a CSS prop, you should make it responsive. To do so, instead of defining it as `[propName]: ThemeSpacingTokens`,
|
||||
define it as `[propName]: ResponsiveProp<ThemeSpacingTokens>`.
|
||||
3. Add it to the CSS array in `getStyles` in `Box.tsx`.
|
||||
- If it is a `ResponsiveProp`, you should use the `getResponsiveStyle` helper function
|
||||
```
|
||||
getResponsiveStyle(theme, [propName], (val) => ({
|
||||
[cssProp]: theme.spacing(val),
|
||||
})),
|
||||
```
|
||||
4. Add it to the `Box` story in `Box.internal.story.tsx`, by explicity adding it to `Basic.argTypes`
|
||||
|
||||
### Props
|
||||
|
||||
<ArgTypes of={Box} />
|
267
packages/grafana-ui/src/components/Layout/Box/Box.tsx
Normal file
267
packages/grafana-ui/src/components/Layout/Box/Box.tsx
Normal file
@ -0,0 +1,267 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { ElementType } from 'react';
|
||||
|
||||
import { GrafanaTheme2, ThemeSpacingTokens, ThemeShape, ThemeShadows } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../../themes';
|
||||
import { AlignItems, JustifyContent } from '../Flex/Flex';
|
||||
import { ResponsiveProp, getResponsiveStyle } from '../utils/responsiveness';
|
||||
|
||||
type Display = 'flex' | 'block' | 'inline' | 'none';
|
||||
export type BackgroundColor = keyof GrafanaTheme2['colors']['background'] | 'error' | 'success' | 'warning' | 'info';
|
||||
export type BorderStyle = 'solid' | 'dashed';
|
||||
export type BorderColor = keyof GrafanaTheme2['colors']['border'] | 'error' | 'success' | 'warning' | 'info';
|
||||
export type BorderRadius = keyof ThemeShape['radius'];
|
||||
export type BoxShadow = keyof ThemeShadows;
|
||||
|
||||
interface BoxProps {
|
||||
// Margin props
|
||||
/** Sets the property `margin` */
|
||||
margin?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
/** Sets the properties `margin-top` and `margin-bottom`. Higher priority than margin. */
|
||||
marginX?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
/** Sets the properties `margin-left` and `margin-right`. Higher priority than margin. */
|
||||
marginY?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
/** Sets the property `margin-top`. Higher priority than margin and marginY. */
|
||||
marginTop?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
/** Sets the property `margin-bottom`. Higher priority than margin and marginXY */
|
||||
marginBottom?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
/** Sets the property `margin-left`. Higher priority than margin and marginX. */
|
||||
marginLeft?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
/** Sets the property `margin-right`. Higher priority than margin and marginX. */
|
||||
marginRight?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
|
||||
// Padding props
|
||||
/** Sets the property `padding` */
|
||||
padding?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
/** Sets the properties `padding-top` and `padding-bottom`. Higher priority than padding. */
|
||||
paddingX?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
/** Sets the properties `padding-left` and `padding-right`. Higher priority than padding. */
|
||||
paddingY?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
/** Sets the property `padding-top`. Higher priority than padding and paddingY. */
|
||||
paddingTop?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
/** Sets the property `padding-bottom`. Higher priority than padding and paddingY. */
|
||||
paddingBottom?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
/** Sets the property `padding-left`. Higher priority than padding and paddingX. */
|
||||
paddingLeft?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
/** Sets the property `padding-right`. Higher priority than padding and paddingX. */
|
||||
paddingRight?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
|
||||
// Border Props
|
||||
borderStyle?: ResponsiveProp<BorderStyle>;
|
||||
borderColor?: ResponsiveProp<BorderColor>;
|
||||
borderRadius?: ResponsiveProp<BorderRadius>;
|
||||
|
||||
// Flex Props
|
||||
/** Sets the property `flex` */
|
||||
grow?: ResponsiveProp<number>;
|
||||
/** Sets the property `flex-shrink` */
|
||||
shrink?: ResponsiveProp<number>;
|
||||
alignItems?: ResponsiveProp<AlignItems>;
|
||||
justifyContent?: ResponsiveProp<JustifyContent>;
|
||||
|
||||
// Other props
|
||||
backgroundColor?: ResponsiveProp<BackgroundColor>;
|
||||
display?: ResponsiveProp<Display>;
|
||||
boxShadow?: ResponsiveProp<BoxShadow>;
|
||||
/** Sets the HTML element that will be rendered as a Box. Defaults to 'div' */
|
||||
element?: ElementType;
|
||||
}
|
||||
|
||||
export const Box = ({
|
||||
children,
|
||||
margin,
|
||||
marginX,
|
||||
marginY,
|
||||
marginTop,
|
||||
marginBottom,
|
||||
marginLeft,
|
||||
marginRight,
|
||||
padding,
|
||||
paddingX,
|
||||
paddingY,
|
||||
paddingTop,
|
||||
paddingBottom,
|
||||
paddingLeft,
|
||||
paddingRight,
|
||||
display,
|
||||
backgroundColor,
|
||||
grow,
|
||||
shrink,
|
||||
borderColor,
|
||||
borderStyle,
|
||||
borderRadius,
|
||||
justifyContent,
|
||||
alignItems,
|
||||
boxShadow,
|
||||
element,
|
||||
}: React.PropsWithChildren<BoxProps>) => {
|
||||
const styles = useStyles2(
|
||||
getStyles,
|
||||
margin,
|
||||
marginX,
|
||||
marginY,
|
||||
marginTop,
|
||||
marginBottom,
|
||||
marginLeft,
|
||||
marginRight,
|
||||
padding,
|
||||
paddingX,
|
||||
paddingY,
|
||||
paddingTop,
|
||||
paddingBottom,
|
||||
paddingLeft,
|
||||
paddingRight,
|
||||
display,
|
||||
backgroundColor,
|
||||
grow,
|
||||
shrink,
|
||||
borderColor,
|
||||
borderStyle,
|
||||
borderRadius,
|
||||
justifyContent,
|
||||
alignItems,
|
||||
boxShadow
|
||||
);
|
||||
const Element = element ?? 'div';
|
||||
|
||||
return <Element className={styles.root}>{children}</Element>;
|
||||
};
|
||||
|
||||
Box.displayName = 'Box';
|
||||
|
||||
const customBorderColor = (color: BorderColor, theme: GrafanaTheme2) => {
|
||||
switch (color) {
|
||||
case 'error':
|
||||
case 'success':
|
||||
case 'info':
|
||||
case 'warning':
|
||||
return theme.colors[color].border;
|
||||
default:
|
||||
return color ? theme.colors.border[color] : undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const customBackgroundColor = (color: BackgroundColor, theme: GrafanaTheme2) => {
|
||||
switch (color) {
|
||||
case 'error':
|
||||
case 'success':
|
||||
case 'info':
|
||||
case 'warning':
|
||||
return theme.colors[color].transparent;
|
||||
default:
|
||||
return color ? theme.colors.background[color] : undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const getStyles = (
|
||||
theme: GrafanaTheme2,
|
||||
margin: BoxProps['margin'],
|
||||
marginX: BoxProps['marginX'],
|
||||
marginY: BoxProps['marginY'],
|
||||
marginTop: BoxProps['marginTop'],
|
||||
marginBottom: BoxProps['marginBottom'],
|
||||
marginLeft: BoxProps['marginLeft'],
|
||||
marginRight: BoxProps['marginRight'],
|
||||
padding: BoxProps['padding'],
|
||||
paddingX: BoxProps['paddingX'],
|
||||
paddingY: BoxProps['paddingY'],
|
||||
paddingTop: BoxProps['paddingTop'],
|
||||
paddingBottom: BoxProps['paddingBottom'],
|
||||
paddingLeft: BoxProps['paddingLeft'],
|
||||
paddingRight: BoxProps['paddingRight'],
|
||||
display: BoxProps['display'],
|
||||
backgroundColor: BoxProps['backgroundColor'],
|
||||
grow: BoxProps['grow'],
|
||||
shrink: BoxProps['shrink'],
|
||||
borderColor: BoxProps['borderColor'],
|
||||
borderStyle: BoxProps['borderStyle'],
|
||||
borderRadius: BoxProps['borderRadius'],
|
||||
justifyContent: BoxProps['justifyContent'],
|
||||
alignItems: BoxProps['alignItems'],
|
||||
boxShadow: BoxProps['boxShadow']
|
||||
) => {
|
||||
return {
|
||||
root: css([
|
||||
getResponsiveStyle(theme, margin, (val) => ({
|
||||
margin: theme.spacing(val),
|
||||
})),
|
||||
getResponsiveStyle(theme, marginX, (val) => ({
|
||||
marginLeft: theme.spacing(val),
|
||||
marginRight: theme.spacing(val),
|
||||
})),
|
||||
getResponsiveStyle(theme, marginY, (val) => ({
|
||||
marginTop: theme.spacing(val),
|
||||
marginBottom: theme.spacing(val),
|
||||
})),
|
||||
getResponsiveStyle(theme, marginTop, (val) => ({
|
||||
marginTop: theme.spacing(val),
|
||||
})),
|
||||
getResponsiveStyle(theme, marginBottom, (val) => ({
|
||||
marginBottom: theme.spacing(val),
|
||||
})),
|
||||
getResponsiveStyle(theme, marginLeft, (val) => ({
|
||||
marginLeft: theme.spacing(val),
|
||||
})),
|
||||
getResponsiveStyle(theme, marginRight, (val) => ({
|
||||
marginRight: theme.spacing(val),
|
||||
})),
|
||||
getResponsiveStyle(theme, padding, (val) => ({
|
||||
padding: theme.spacing(val),
|
||||
})),
|
||||
getResponsiveStyle(theme, paddingX, (val) => ({
|
||||
paddingLeft: theme.spacing(val),
|
||||
paddingRight: theme.spacing(val),
|
||||
})),
|
||||
getResponsiveStyle(theme, paddingY, (val) => ({
|
||||
paddingTop: theme.spacing(val),
|
||||
paddingBottom: theme.spacing(val),
|
||||
})),
|
||||
getResponsiveStyle(theme, paddingTop, (val) => ({
|
||||
paddingTop: theme.spacing(val),
|
||||
})),
|
||||
getResponsiveStyle(theme, paddingBottom, (val) => ({
|
||||
paddingBottom: theme.spacing(val),
|
||||
})),
|
||||
getResponsiveStyle(theme, paddingLeft, (val) => ({
|
||||
paddingLeft: theme.spacing(val),
|
||||
})),
|
||||
getResponsiveStyle(theme, paddingRight, (val) => ({
|
||||
paddingRight: theme.spacing(val),
|
||||
})),
|
||||
getResponsiveStyle(theme, display, (val) => ({
|
||||
display: val,
|
||||
})),
|
||||
getResponsiveStyle(theme, backgroundColor, (val) => ({
|
||||
backgroundColor: customBackgroundColor(val, theme),
|
||||
})),
|
||||
getResponsiveStyle(theme, grow, (val) => ({
|
||||
flex: val,
|
||||
})),
|
||||
getResponsiveStyle(theme, shrink, (val) => ({
|
||||
flexShrink: val,
|
||||
})),
|
||||
getResponsiveStyle(theme, borderStyle, (val) => ({
|
||||
borderStyle: val,
|
||||
})),
|
||||
getResponsiveStyle(theme, borderColor, (val) => ({
|
||||
borderColor: customBorderColor(val, theme),
|
||||
})),
|
||||
(borderStyle || borderColor) && {
|
||||
borderWidth: '1px',
|
||||
},
|
||||
getResponsiveStyle(theme, justifyContent, (val) => ({
|
||||
justifyContent: val,
|
||||
})),
|
||||
getResponsiveStyle(theme, alignItems, (val) => ({
|
||||
alignItems: val,
|
||||
})),
|
||||
getResponsiveStyle(theme, borderRadius, (val) => ({
|
||||
borderRadius: theme.shape.radius[val],
|
||||
})),
|
||||
getResponsiveStyle(theme, boxShadow, (val) => ({
|
||||
boxShadow: theme.shadows[val],
|
||||
})),
|
||||
]),
|
||||
};
|
||||
};
|
@ -3,7 +3,8 @@ import React from 'react';
|
||||
|
||||
import { ThemeSpacingTokens } from '@grafana/data';
|
||||
|
||||
import { useTheme2 } from '../../themes';
|
||||
import { useTheme2 } from '../../../themes';
|
||||
import { SpacingTokenControl } from '../../../utils/storybook/themeStorybookControls';
|
||||
|
||||
import { Flex, JustifyContent, Wrap, Direction } from './Flex';
|
||||
import mdx from './Flex.mdx';
|
||||
@ -48,6 +49,31 @@ export const Basic: StoryFn<typeof Flex> = ({ direction, wrap, alignItems, justi
|
||||
);
|
||||
};
|
||||
|
||||
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();
|
||||
|
@ -3,7 +3,8 @@ import React from 'react';
|
||||
|
||||
import { GrafanaTheme2, ThemeSpacingTokens } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { useStyles2 } from '../../../themes';
|
||||
import { ResponsiveProp, getResponsiveStyle } from '../utils/responsiveness';
|
||||
|
||||
export type AlignItems =
|
||||
| 'stretch'
|
||||
@ -33,11 +34,11 @@ export type Direction = 'row' | 'row-reverse' | 'column' | 'column-reverse';
|
||||
export type Wrap = 'nowrap' | 'wrap' | 'wrap-reverse';
|
||||
|
||||
interface FlexProps {
|
||||
gap?: ThemeSpacingTokens;
|
||||
alignItems?: AlignItems;
|
||||
justifyContent?: JustifyContent;
|
||||
direction?: Direction;
|
||||
wrap?: Wrap;
|
||||
gap?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
alignItems?: ResponsiveProp<AlignItems>;
|
||||
justifyContent?: ResponsiveProp<JustifyContent>;
|
||||
direction?: ResponsiveProp<Direction>;
|
||||
wrap?: ResponsiveProp<Wrap>;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
@ -57,20 +58,32 @@ Flex.displayName = 'Flex';
|
||||
|
||||
const getStyles = (
|
||||
theme: GrafanaTheme2,
|
||||
gap: ThemeSpacingTokens,
|
||||
gap: FlexProps['gap'],
|
||||
alignItems: FlexProps['alignItems'],
|
||||
justifyContent: FlexProps['justifyContent'],
|
||||
direction: FlexProps['direction'],
|
||||
wrap: FlexProps['wrap']
|
||||
) => {
|
||||
return {
|
||||
flex: css({
|
||||
display: 'flex',
|
||||
flexDirection: direction,
|
||||
flexWrap: wrap,
|
||||
alignItems: alignItems,
|
||||
justifyContent: justifyContent,
|
||||
gap: theme.spacing(gap),
|
||||
}),
|
||||
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,6 +1,8 @@
|
||||
import { Meta, StoryFn } from '@storybook/react';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
import { SpacingTokenControl } from '../../../utils/storybook/themeStorybookControls';
|
||||
|
||||
import { Stack } from './Stack';
|
||||
import mdx from './Stack.mdx';
|
||||
|
||||
@ -12,6 +14,10 @@ const meta: Meta<typeof Stack> = {
|
||||
page: mdx,
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
gap: SpacingTokenControl,
|
||||
direction: { control: 'select', options: ['row', 'row-reverse', 'column', 'column-reverse'] },
|
||||
},
|
||||
};
|
||||
|
||||
const Item = ({ children }: { children: ReactNode }) => (
|
@ -3,10 +3,11 @@ import React from 'react';
|
||||
import { ThemeSpacingTokens } from '@grafana/data';
|
||||
|
||||
import { Direction, Flex } from '../Flex/Flex';
|
||||
import { ResponsiveProp } from '../utils/responsiveness';
|
||||
|
||||
interface StackProps {
|
||||
direction?: Direction;
|
||||
gap?: ThemeSpacingTokens;
|
||||
direction?: ResponsiveProp<Direction>;
|
||||
gap?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
}
|
||||
|
||||
export const Stack = ({ gap = 1, direction = 'column', children }: React.PropsWithChildren<StackProps>) => {
|
@ -0,0 +1,65 @@
|
||||
import { CSSInterpolation } from '@emotion/css';
|
||||
|
||||
import { GrafanaTheme2, ThemeBreakpointsKey } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* Type that represents a prop that can be responsive.
|
||||
*
|
||||
* @example To turn a prop like `margin: number` responsive, change it to `margin: ResponsiveProp<number>`.
|
||||
*/
|
||||
export type ResponsiveProp<T> = T | Responsive<T>;
|
||||
|
||||
type Responsive<T> = {
|
||||
xs: T;
|
||||
sm?: T;
|
||||
md?: T;
|
||||
lg?: T;
|
||||
xl?: T;
|
||||
xxl?: T;
|
||||
};
|
||||
|
||||
function breakpointCSS<T>(
|
||||
theme: GrafanaTheme2,
|
||||
prop: Responsive<T>,
|
||||
getCSS: (val: T) => CSSInterpolation,
|
||||
key: ThemeBreakpointsKey
|
||||
) {
|
||||
const value = prop[key];
|
||||
if (value !== undefined && value !== null) {
|
||||
return {
|
||||
[theme.breakpoints.up(key)]: getCSS(value),
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Function that converts a ResponsiveProp object into CSS
|
||||
*
|
||||
* @param theme Grafana theme object
|
||||
* @param prop Prop as it is passed to the component
|
||||
* @param getCSS Function that returns the css block for the prop
|
||||
* @returns The CSS block repeated for each breakpoint
|
||||
*
|
||||
* @example To get the responsive css equivalent of `margin && { margin }`, you can write `getResponsiveStyle(theme, margin, (val) => { margin: val })`
|
||||
*/
|
||||
export function getResponsiveStyle<T>(
|
||||
theme: GrafanaTheme2,
|
||||
prop: ResponsiveProp<T> | undefined,
|
||||
getCSS: (val: T) => CSSInterpolation
|
||||
): CSSInterpolation {
|
||||
if (prop === undefined || prop === null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof prop !== 'object' || !('xs' in prop)) {
|
||||
return getCSS(prop);
|
||||
}
|
||||
|
||||
return [
|
||||
breakpointCSS(theme, prop, getCSS, 'xs'),
|
||||
breakpointCSS(theme, prop, getCSS, 'sm'),
|
||||
breakpointCSS(theme, prop, getCSS, 'md'),
|
||||
breakpointCSS(theme, prop, getCSS, 'lg'),
|
||||
breakpointCSS(theme, prop, getCSS, 'xl'),
|
||||
breakpointCSS(theme, prop, getCSS, 'xxl'),
|
||||
];
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
* be subject to the standard policies
|
||||
*/
|
||||
|
||||
export * from './components/Flex/Flex';
|
||||
export * from './components/Layout/Box/Box';
|
||||
export * from './components/Layout/Flex/Flex';
|
||||
|
||||
export { Stack } from './components/Stack/Stack';
|
||||
export { Stack } from './components/Layout/Stack/Stack';
|
||||
|
@ -0,0 +1 @@
|
||||
export const SpacingTokenControl = { control: 'select', options: [0, 0.25, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10] };
|
@ -1,10 +1,11 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { config, locationService, reportInteraction } from '@grafana/runtime';
|
||||
import { Button, useStyles2, Text } from '@grafana/ui';
|
||||
import { Box, Flex } 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';
|
||||
@ -23,121 +24,124 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
|
||||
const initialDatasource = useSelector((state) => state.dashboard.initialDatasource);
|
||||
|
||||
return (
|
||||
<div className={styles.centeredContent}>
|
||||
<div className={cx(styles.centeredContent, styles.wrapper)}>
|
||||
<div className={cx(styles.containerBox, styles.centeredContent, styles.visualizationContainer)}>
|
||||
<div className={styles.headerBig}>
|
||||
<Text element="h1" textAlignment="center" weight="medium">
|
||||
<Trans i18nKey="dashboard.empty.add-visualization-header">
|
||||
Start your new dashboard by adding a visualization
|
||||
</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<div className={styles.bodyBig}>
|
||||
<Text element="p" textAlignment="center" color="secondary">
|
||||
<Trans i18nKey="dashboard.empty.add-visualization-body">
|
||||
Select a data source and then query and visualize your data with charts, stats and tables or create
|
||||
lists, markdowns and other widgets.
|
||||
</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<Button
|
||||
size="lg"
|
||||
icon="plus"
|
||||
data-testid={selectors.pages.AddDashboard.itemButton('Create new panel button')}
|
||||
onClick={() => {
|
||||
const id = onCreateNewPanel(dashboard, initialDatasource);
|
||||
reportInteraction('dashboards_emptydashboard_clicked', { item: 'add_visualization' });
|
||||
locationService.partial({ editPanel: id, firstPanel: true });
|
||||
dispatch(setInitialDatasource(undefined));
|
||||
}}
|
||||
disabled={!canCreate}
|
||||
>
|
||||
<Trans i18nKey="dashboard.empty.add-visualization-button">Add visualization</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={cx(styles.centeredContent, styles.others)}>
|
||||
{config.featureToggles.vizAndWidgetSplit && (
|
||||
<div className={cx(styles.containerBox, styles.centeredContent, styles.widgetContainer)}>
|
||||
<div className={styles.headerSmall}>
|
||||
<Text element="h3" textAlignment="center" weight="medium">
|
||||
<Trans i18nKey="dashboard.empty.add-widget-header">Add a widget</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<div className={styles.bodySmall}>
|
||||
<Flex alignItems="center" justifyContent="center">
|
||||
<div className={styles.wrapper}>
|
||||
<Flex alignItems="stretch" justifyContent="center" gap={4} direction="column">
|
||||
<Box borderStyle="dashed" borderColor="info" padding={4}>
|
||||
<Flex 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
|
||||
</Trans>
|
||||
</Text>
|
||||
<Box marginBottom={2} paddingX={4}>
|
||||
<Text element="p" textAlignment="center" color="secondary">
|
||||
<Trans i18nKey="dashboard.empty.add-widget-body">Create lists, markdowns and other widgets</Trans>
|
||||
<Trans i18nKey="dashboard.empty.add-visualization-body">
|
||||
Select a data source and then query and visualize your data with charts, stats and tables or create
|
||||
lists, markdowns and other widgets.
|
||||
</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
</Box>
|
||||
<Button
|
||||
size="lg"
|
||||
icon="plus"
|
||||
fill="outline"
|
||||
data-testid={selectors.pages.AddDashboard.itemButton('Create new widget button')}
|
||||
data-testid={selectors.pages.AddDashboard.itemButton('Create new panel button')}
|
||||
onClick={() => {
|
||||
reportInteraction('dashboards_emptydashboard_clicked', { item: 'add_widget' });
|
||||
locationService.partial({ addWidget: true });
|
||||
const id = onCreateNewPanel(dashboard, initialDatasource);
|
||||
reportInteraction('dashboards_emptydashboard_clicked', { item: 'add_visualization' });
|
||||
locationService.partial({ editPanel: id, firstPanel: true });
|
||||
dispatch(setInitialDatasource(undefined));
|
||||
}}
|
||||
disabled={!canCreate}
|
||||
>
|
||||
<Trans i18nKey="dashboard.empty.add-widget-button">Add widget</Trans>
|
||||
<Trans i18nKey="dashboard.empty.add-visualization-button">Add visualization</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className={cx(styles.containerBox, styles.centeredContent, styles.libraryContainer)}>
|
||||
<div className={styles.headerSmall}>
|
||||
<Text element="h3" textAlignment="center" weight="medium">
|
||||
<Trans i18nKey="dashboard.empty.add-library-panel-header">Import panel</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<div className={styles.bodySmall}>
|
||||
<Text element="p" textAlignment="center" color="secondary">
|
||||
<Trans i18nKey="dashboard.empty.add-library-panel-body">
|
||||
Add visualizations that are shared with other dashboards.
|
||||
</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<Button
|
||||
icon="plus"
|
||||
fill="outline"
|
||||
data-testid={selectors.pages.AddDashboard.itemButton('Add a panel from the panel library button')}
|
||||
onClick={() => {
|
||||
reportInteraction('dashboards_emptydashboard_clicked', { item: 'import_from_library' });
|
||||
onAddLibraryPanel(dashboard);
|
||||
}}
|
||||
disabled={!canCreate}
|
||||
>
|
||||
<Trans i18nKey="dashboard.empty.add-library-panel-button">Add library panel</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={cx(styles.containerBox, styles.centeredContent, styles.rowContainer)}>
|
||||
<div className={styles.headerSmall}>
|
||||
<Text element="h3" textAlignment="center" weight="medium">
|
||||
<Trans i18nKey="dashboard.empty.import-a-dashboard-header">Import a dashboard</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<div className={styles.bodySmall}>
|
||||
<Text element="p" textAlignment="center" color="secondary">
|
||||
<Trans i18nKey="dashboard.empty.import-a-dashboard-body">
|
||||
Import dashboards from files or <a href="https://grafana.com/grafana/dashboards/">grafana.com</a>.
|
||||
</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<Button
|
||||
icon="upload"
|
||||
fill="outline"
|
||||
data-testid={selectors.pages.AddDashboard.itemButton('Import dashboard button')}
|
||||
onClick={() => {
|
||||
reportInteraction('dashboards_emptydashboard_clicked', { item: 'import_dashboard' });
|
||||
onImportDashboard();
|
||||
}}
|
||||
disabled={!canCreate}
|
||||
>
|
||||
<Trans i18nKey="dashboard.empty.import-dashboard-button">Import dashboard</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Flex direction={{ xs: 'column', md: 'row' }} wrap="wrap" gap={4}>
|
||||
{config.featureToggles.vizAndWidgetSplit && (
|
||||
<Box borderStyle="dashed" borderColor="info" padding={3} grow={1}>
|
||||
<Flex 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>
|
||||
<Box marginBottom={2}>
|
||||
<Text element="p" textAlignment="center" color="secondary">
|
||||
<Trans i18nKey="dashboard.empty.add-widget-body">Create lists, markdowns and other widgets</Trans>
|
||||
</Text>
|
||||
</Box>
|
||||
<Button
|
||||
icon="plus"
|
||||
fill="outline"
|
||||
data-testid={selectors.pages.AddDashboard.itemButton('Create new widget button')}
|
||||
onClick={() => {
|
||||
reportInteraction('dashboards_emptydashboard_clicked', { item: 'add_widget' });
|
||||
locationService.partial({ addWidget: true });
|
||||
}}
|
||||
disabled={!canCreate}
|
||||
>
|
||||
<Trans i18nKey="dashboard.empty.add-widget-button">Add widget</Trans>
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
<Box borderStyle="dashed" borderColor="info" padding={3} grow={1}>
|
||||
<Flex 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>
|
||||
<Box marginBottom={2}>
|
||||
<Text element="p" textAlignment="center" color="secondary">
|
||||
<Trans i18nKey="dashboard.empty.add-library-panel-body">
|
||||
Add visualizations that are shared with other dashboards.
|
||||
</Trans>
|
||||
</Text>
|
||||
</Box>
|
||||
<Button
|
||||
icon="plus"
|
||||
fill="outline"
|
||||
data-testid={selectors.pages.AddDashboard.itemButton('Add a panel from the panel library button')}
|
||||
onClick={() => {
|
||||
reportInteraction('dashboards_emptydashboard_clicked', { item: 'import_from_library' });
|
||||
onAddLibraryPanel(dashboard);
|
||||
}}
|
||||
disabled={!canCreate}
|
||||
>
|
||||
<Trans i18nKey="dashboard.empty.add-library-panel-button">Add library panel</Trans>
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box borderStyle="dashed" borderColor="info" padding={3} grow={1}>
|
||||
<Flex 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>
|
||||
<Box marginBottom={2}>
|
||||
<Text element="p" textAlignment="center" color="secondary">
|
||||
<Trans i18nKey="dashboard.empty.import-a-dashboard-body">
|
||||
Import dashboards from files or
|
||||
<a href="https://grafana.com/grafana/dashboards/">grafana.com</a>.
|
||||
</Trans>
|
||||
</Text>
|
||||
</Box>
|
||||
<Button
|
||||
icon="upload"
|
||||
fill="outline"
|
||||
data-testid={selectors.pages.AddDashboard.itemButton('Import dashboard button')}
|
||||
onClick={() => {
|
||||
reportInteraction('dashboards_emptydashboard_clicked', { item: 'import_dashboard' });
|
||||
onImportDashboard();
|
||||
}}
|
||||
disabled={!canCreate}
|
||||
>
|
||||
<Trans i18nKey="dashboard.empty.import-dashboard-button">Import dashboard</Trans>
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</div>
|
||||
</div>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@ -156,60 +160,5 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
paddingTop: theme.spacing(12),
|
||||
},
|
||||
}),
|
||||
containerBox: css({
|
||||
label: 'container-box',
|
||||
flexDirection: 'column',
|
||||
boxSizing: 'border-box',
|
||||
border: '1px dashed rgba(110, 159, 255, 0.5)',
|
||||
}),
|
||||
centeredContent: css({
|
||||
label: 'centered',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}),
|
||||
visualizationContainer: css({
|
||||
label: 'visualization-container',
|
||||
padding: theme.spacing.gridSize * 4,
|
||||
}),
|
||||
others: css({
|
||||
width: '100%',
|
||||
label: 'others-wrapper',
|
||||
alignItems: 'stretch',
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing.gridSize * 4,
|
||||
|
||||
[theme.breakpoints.down('md')]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
}),
|
||||
widgetContainer: css({
|
||||
label: 'widget-container',
|
||||
padding: theme.spacing.gridSize * 3,
|
||||
flex: 1,
|
||||
}),
|
||||
rowContainer: css({
|
||||
label: 'row-container',
|
||||
padding: theme.spacing.gridSize * 3,
|
||||
flex: 1,
|
||||
}),
|
||||
libraryContainer: css({
|
||||
label: 'library-container',
|
||||
padding: theme.spacing.gridSize * 3,
|
||||
flex: 1,
|
||||
}),
|
||||
headerBig: css({
|
||||
marginBottom: theme.spacing.gridSize * 2,
|
||||
}),
|
||||
headerSmall: css({
|
||||
marginBottom: theme.spacing.gridSize,
|
||||
}),
|
||||
bodyBig: css({
|
||||
maxWidth: '75%',
|
||||
marginBottom: theme.spacing.gridSize * 4,
|
||||
}),
|
||||
bodySmall: css({
|
||||
marginBottom: theme.spacing.gridSize * 3,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ composableKinds: DataQuery: {
|
||||
// Allows to group the results.
|
||||
groupBy: [...string]
|
||||
// Sets the maximum number of nodes in the flamegraph.
|
||||
maxNodes?: int64
|
||||
maxNodes?: int64
|
||||
#PyroscopeQueryType: "metrics" | "profile" | *"both" @cuetsy(kind="type")
|
||||
}
|
||||
}]
|
||||
|
Loading…
Reference in New Issue
Block a user