156 lines
4.4 KiB
TypeScript
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; |