Files
tempo/ui/src/card/Card.tsx
T

156 lines
4.4 KiB
TypeScript

import * as React from "react";
import { Card as AntCard } from "antd";
import Loader from "../loader/Loader";
import Spacing from "../spacing/Spacing";
import Text from '../text/Text';
import Image from '../image/Image';
import Link from '../link/Link';
import CheckCircleIcon from "../icon/CheckCircleIcon";
import WarningIcon from "../icon/WarningIcon";
import IIconProps from "../icon/IIconProps";
import WarningCircleIcon from "../icon/WarningCircleIcon";
export interface ICardProps {
/** Card title */
title?: React.ReactNode;
/** Title icon */
icon?: React.ReactElement<IIconProps>;
/** Card type. Default is "normal." "success", "attention", and "error" override the icon prop */
type?: "normal" | "success" | "error" | "attention";
/** Card description below the title */
description?: string;
/** Content to the right of the card title */
extra?: React.ReactNode;
/** Cover image src. Cannot be used with title */
image?: string;
/** Card footer. Requires children to be set */
footer?: React.ReactNode;
/** Remove body padding */
removeBodyPadding?: boolean;
/** Min height of the card, excluding the height of the title or image */
height?: number | string;
/** Trigger an action on card click. Cannot be used with href */
onClick?: () => void;
/** Trigger a link on card click. Cannot be used with onClick */
href?: string;
/** Show loading mask */
loading?: boolean;
/** Add selected style */
selected?: boolean;
}
/** Cards structure content like text, forms, and tables. */
const Card: React.FC<ICardProps> = (props) => {
const { removeBodyPadding, loading, selected, type, image, description, height, onClick, href, ...validProps } = props;
let className = "tempo-card";
if (removeBodyPadding || !validProps.children) {
className += " tempo-card--body-no-padding";
}
if (selected) {
className += " tempo-card--selected"
}
let title = validProps.title;
if (title) {
title = <Text.Heading level={3}>{title}</Text.Heading>;
if (description) {
title = (
<Spacing column padding={"12px 0"}>
{title}
<Text color="secondary">{description}</Text>
</Spacing>
);
}
let icon: React.ReactElement<IIconProps> | null = null;
if (props.icon) {
icon = React.cloneElement(props.icon, {
size: props.icon.props.size || 20,
});
}
if (type === "success") {
icon = <CheckCircleIcon color='success' size={20} />
} else if (type === "error") {
icon = <WarningIcon color='error' size={20} />;
}
else if (type === "attention") {
icon = <WarningCircleIcon color='attention' size={20} />;
}
if (icon != null) {
title = (
<Spacing vAlign="center" itemSpacing={8}>
{icon}
{title}
</Spacing>
);
}
}
let extra = validProps.extra;
let cover;
if (!title && image) {
cover = <Image src={image} height={180} border="bottom" backgroundColor="#F5F5F5" />;
}
let contents = validProps.children;
if (contents) {
contents = <div style={{ minHeight: height }}>{contents}</div>;
if (validProps.footer) {
contents = (
<div className="tempo-card-contents-and-footer" style={{ minHeight: height }}>
<div>{validProps.children}</div>
<div className="tempo-card-footer">{validProps.footer}</div>
</div>
);
}
}
let tabIndex, onKeyPress;
if (onClick && !loading) {
tabIndex = 0;
onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
e.key === 'Enter' && onClick();
}
}
Object.assign(validProps, {
hoverable: props.onClick || href
});
const card = <AntCard {...validProps}
onClick={onClick}
onKeyPress={onKeyPress}
tabIndex={tabIndex}
title={title}
extra={extra}
cover={cover}
className={className}>{contents}</AntCard>;
return (
<Loader loading={loading}>
{href ? <Link href={href}>{card}</Link> : card}
</Loader>
);
}
export default Card;