Grafana-UI: Export Card container styles (#33386)

* Export Card container styles

* Export CardContainer as a separate component

* Update story

* Remove tooltip
This commit is contained in:
Alex Khomenko 2021-04-28 13:41:20 +03:00 committed by GitHub
parent 0d1cdbe227
commit 1336a57e99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 112 additions and 111 deletions

View File

@ -21,13 +21,9 @@ export default {
knobs: { knobs: {
disable: true, disable: true,
}, },
}, controls: {
argTypes: { exclude: ['onClick', 'href', 'heading', 'description', 'className'],
heading: { control: { disable: true } }, },
description: { control: { disable: true } },
href: { control: { disable: true } },
tooltip: { control: { disable: true } },
onClick: { control: { disable: true } },
}, },
}; };
@ -63,17 +59,6 @@ export const AsLink: Story<Props> = ({ disabled }) => {
); );
}; };
export const WithTooltip: Story<Props> = ({ disabled }) => {
return (
<Card
heading="Reduce"
description="Reduce all rows or data points to a single value using a function like max, min, mean or last."
tooltip="Click to apply this transformation."
disabled={disabled}
/>
);
};
export const WithTags: Story<Props> = ({ disabled }) => { export const WithTags: Story<Props> = ({ disabled }) => {
return ( return (
<Card heading="Elasticsearch Custom Templated Query" disabled={disabled}> <Card heading="Elasticsearch Custom Templated Query" disabled={disabled}>

View File

@ -1,50 +1,13 @@
import React, { memo, cloneElement, FC, HTMLAttributes, ReactNode, useCallback } from 'react'; import React, { memo, cloneElement, FC, ReactNode, useCallback } from 'react';
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import { GrafanaThemeV2 } from '@grafana/data'; import { GrafanaThemeV2 } from '@grafana/data';
import { useTheme2, styleMixins, stylesFactory } from '../../themes'; import { useTheme2, stylesFactory } from '../../themes';
import { Tooltip, PopoverContent } from '../Tooltip/Tooltip'; import { CardContainer, CardContainerProps } from './CardContainer';
/** /**
* @public * @public
*/ */
export interface ContainerProps extends HTMLAttributes<HTMLOrSVGElement> { export interface Props extends Omit<CardContainerProps, 'disableEvents' | 'disableHover'> {
/** Content for the card's tooltip */
tooltip?: PopoverContent;
}
const CardContainer: FC<ContainerProps> = ({ children, tooltip, ...props }) => {
return tooltip ? (
<Tooltip placement="top" content={tooltip} theme="info">
<div {...props}>{children}</div>
</Tooltip>
) : (
<div {...props}>{children}</div>
);
};
/**
* @public
*/
export interface CardInnerProps {
href?: string;
}
const CardInner: FC<CardInnerProps> = ({ children, href }) => {
const theme = useTheme2();
const styles = getCardStyles(theme);
return href ? (
<a className={styles.innerLink} href={href}>
{children}
</a>
) : (
<div className={styles.innerLink}>{children}</div>
);
};
/**
* @public
*/
export interface Props extends ContainerProps {
/** Main heading for the Card **/ /** Main heading for the Card **/
heading: ReactNode; heading: ReactNode;
/** Card description text */ /** Card description text */
@ -74,7 +37,6 @@ export const Card: CardInterface = ({
heading, heading,
description, description,
disabled, disabled,
tooltip,
href, href,
onClick, onClick,
className, className,
@ -100,66 +62,40 @@ export const Card: CardInterface = ({
const disableHover = disabled || (!onClick && !href); const disableHover = disabled || (!onClick && !href);
const disableEvents = disabled && !actions; const disableEvents = disabled && !actions;
const containerStyles = getContainerStyles(theme, disableEvents, disableHover);
const onCardClick = useCallback(() => (disableHover ? () => {} : onClick?.()), [disableHover, onClick]); const onCardClick = useCallback(() => (disableHover ? () => {} : onClick?.()), [disableHover, onClick]);
return ( return (
<CardContainer <CardContainer
tooltip={tooltip}
tabIndex={disableHover ? undefined : 0} tabIndex={disableHover ? undefined : 0}
className={cx(containerStyles, className)}
onClick={onCardClick} onClick={onCardClick}
disableEvents={disableEvents}
disableHover={disableHover}
href={href}
{...htmlProps} {...htmlProps}
> >
<CardInner href={href}> {figure}
{figure} <div className={styles.inner}>
<div className={styles.inner}> <div className={styles.info}>
<div className={styles.info}> <div>
<div> <div className={styles.heading} role="heading">
<div className={styles.heading} role="heading"> {heading}
{heading}
</div>
{meta}
{description && <p className={styles.description}>{description}</p>}
</div> </div>
{tags} {meta}
{description && <p className={styles.description}>{description}</p>}
</div> </div>
{hasActions && ( {tags}
<div className={styles.actionRow}>
{actions}
{secondaryActions}
</div>
)}
</div> </div>
</CardInner> {hasActions && (
<div className={styles.actionRow}>
{actions}
{secondaryActions}
</div>
)}
</div>
</CardContainer> </CardContainer>
); );
}; };
const getContainerStyles = stylesFactory((theme: GrafanaThemeV2, disabled = false, disableHover = false) => {
return css({
display: 'flex',
width: '100%',
background: theme.colors.background.secondary,
borderRadius: theme.shape.borderRadius(),
position: 'relative',
pointerEvents: disabled ? 'none' : 'auto',
marginBottom: theme.spacing(1),
transition: theme.transitions.create(['background-color', 'box-shadow', 'border-color', 'color'], {
duration: theme.transitions.duration.short,
}),
...(!disableHover && {
'&:hover': {
background: theme.colors.emphasize(theme.colors.background.secondary, 0.03),
cursor: 'pointer',
zIndex: 1,
},
'&:focus': styleMixins.getFocusStyles(theme),
}),
});
});
/** /**
* @public * @public
*/ */
@ -245,11 +181,6 @@ export const getCardStyles = stylesFactory((theme: GrafanaThemeV2) => {
separator: css` separator: css`
margin: 0 ${theme.spacing(1)}; margin: 0 ${theme.spacing(1)};
`, `,
innerLink: css`
display: flex;
width: 100%;
padding: ${theme.spacing(2)};
`,
tagList: css` tagList: css`
max-width: 50%; max-width: 50%;
`, `,

View File

@ -0,0 +1,84 @@
import React, { HTMLAttributes, ReactNode } from 'react';
import { css, cx } from '@emotion/css';
import { GrafanaThemeV2 } from '@grafana/data';
import { styleMixins, stylesFactory, useTheme2 } from '../../themes';
/**
* @public
*/
export interface CardInnerProps {
href?: string;
children?: ReactNode;
}
const CardInner = ({ children, href }: CardInnerProps) => {
const theme = useTheme2();
const { inner } = getCardContainerStyles(theme);
return href ? (
<a className={inner} href={href}>
{children}
</a>
) : (
<div className={inner}>{children}</div>
);
};
/**
* @public
*/
export interface CardContainerProps extends HTMLAttributes<HTMLOrSVGElement>, CardInnerProps {
/** Disable pointer events for the Card, e.g. click events */
disableEvents?: boolean;
/** No style change on hover */
disableHover?: boolean;
/** Custom container styles */
className?: string;
}
export const CardContainer = ({
href,
children,
disableEvents,
disableHover,
className,
...props
}: CardContainerProps) => {
const theme = useTheme2();
const { container } = getCardContainerStyles(theme, disableEvents, disableHover);
return (
<div {...props} className={cx(container, className)}>
<CardInner href={href}>{children}</CardInner>
</div>
);
};
const getCardContainerStyles = stylesFactory((theme: GrafanaThemeV2, disabled = false, disableHover = false) => {
return {
container: css({
display: 'flex',
width: '100%',
background: theme.colors.background.secondary,
borderRadius: theme.shape.borderRadius(),
position: 'relative',
pointerEvents: disabled ? 'none' : 'auto',
marginBottom: theme.spacing(1),
transition: theme.transitions.create(['background-color', 'box-shadow', 'border-color', 'color'], {
duration: theme.transitions.duration.short,
}),
...(!disableHover && {
'&:hover': {
background: theme.colors.emphasize(theme.colors.background.secondary, 0.03),
cursor: 'pointer',
zIndex: 1,
},
'&:focus': styleMixins.getFocusStyles(theme),
}),
}),
inner: css({
display: 'flex',
width: '100%',
padding: theme.spacing(2),
}),
};
});

View File

@ -189,7 +189,8 @@ export { Checkbox } from './Forms/Checkbox';
export { TextArea } from './TextArea/TextArea'; export { TextArea } from './TextArea/TextArea';
export { FileUpload } from './FileUpload/FileUpload'; export { FileUpload } from './FileUpload/FileUpload';
export { TimeRangeInput } from './TimePicker/TimeRangeInput'; export { TimeRangeInput } from './TimePicker/TimeRangeInput';
export { Card, Props as CardProps, ContainerProps, CardInnerProps, getCardStyles } from './Card/Card'; export { Card, Props as CardProps, getCardStyles } from './Card/Card';
export { CardContainer, CardContainerProps } from './Card/CardContainer';
export { FormattedValueDisplay } from './FormattedValueDisplay/FormattedValueDisplay'; export { FormattedValueDisplay } from './FormattedValueDisplay/FormattedValueDisplay';
export { ButtonSelect } from './Dropdown/ButtonSelect'; export { ButtonSelect } from './Dropdown/ButtonSelect';