mirror of https://github.com/grafana/grafana.git
				
				
				
			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:
		
							parent
							
								
									0d1cdbe227
								
							
						
					
					
						commit
						1336a57e99
					
				| 
						 | 
					@ -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}>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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%;
 | 
				
			||||||
    `,
 | 
					    `,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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),
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -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';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue