import * as classNames from "classnames"; import * as _ from "lodash"; import * as React from "react"; import { stretch, UIReflowEventType } from "./../../utilities/styling"; import { Avatar, IAvatarProps } from "./../Avatar"; import { Button, IButtonProps } from "./../Button"; import { Link, ILinkProps } from "./../Link"; import { Lights } from "./../Lights"; import { Icon, IIconProps } from "./../Icon"; interface IHeaderProps { text?: string; } export enum SubheaderType { Simple = "Simple", Sectioned = "Sectioned", } interface ISubheaderSection { /** * Since we iterate through the provided sections, * we want to provide proper keys to React. */ id: string; text?: string; icon?: IIconProps; link?: ILinkProps; } interface ISubheaderProps { type: SubheaderType; text?: string; sections?: ISubheaderSection[]; } interface ICardProps { header?: IHeaderProps; /** * Render the main body of the */ renderContent?: () => JSX.Element; /** * Place the header and subheader next to each other */ inlineHeaders?: boolean; subheader?: ISubheaderProps; /** * If provided, a floating "action" button will render fixed * to the botton of the card. */ action?: IButtonProps; /** * This styling property removes the rounded edges from the bottom right and bottom left corners. */ flatBottom?: boolean; /** * This styling property will apply a `margin: 0 auto` to the element. This centers it horizontally * inside its containing element. */ center?: boolean; /** * The component calculates its height dynamically based on its parent container. If * the parent container also calculates its height dynamically a recalculation of height might be * needed. */ listenForReflowEvent?: boolean; /** * Should the stretch to fit inside its parent container */ stretch?: boolean; /** * Will set any string data on the 'data-card' attribute. */ dataAttr?: string; /** * Add a Christmas themed easter egg */ lights?: boolean; /** * Add an avatar to the left of the title */ avatar?: IAvatarProps; } export class Card extends React.PureComponent { public cardRef: React.RefObject; public contentContainerRef: React.RefObject; /** * We create a reference to the node which handles the scrollable * content content as we want to perform scroll fixes on it. * * @see `Utilities#provideBounce` */ public cardContentRef: React.RefObject; constructor(props: ICardProps) { super(props); this.cardRef = React.createRef(); this.contentContainerRef = React.createRef(); this.cardContentRef = React.createRef(); } public componentDidMount() { if (this.props.stretch) { this.stretchElements(); } if (this.props.listenForReflowEvent) { this.addReflowListener(); } } public componentWillUnmount() { if (this.props.listenForReflowEvent) { this.removeReflowListener(); } } public render() { const { header, subheader, flatBottom, inlineHeaders, center, action, dataAttr, lights } = this.props; return (
{lights && }
{this.props.avatar && (
)} {header.text}
{!_.isEmpty(subheader) && (
{subheader.type === SubheaderType.Sectioned ? ( {(subheader.sections || []).map((section: ISubheaderSection) => (
{section.icon && (
)} {(section.text || section.link) && (
{section.link ? : section.text}
)}
))}
) : subheader.text}
)}
{_.isFunction(this.props.renderContent) && (
{this.props.renderContent()}
)} {action && (
)}
); } private withCard(found: (node: HTMLDivElement) => T, notFound?: () => T): T { const cardNode = this.cardRef.current; if (cardNode) { return found(cardNode); } else if (notFound) { return notFound(); } return null; } private withContentContainer(found: (node: HTMLDivElement) => T, notFound?: () => T): T { const contentContainerNode = this.contentContainerRef.current; if (contentContainerNode) { return found(contentContainerNode); } else if (notFound) { return notFound(); } return null; } private addReflowListener = () => { window.addEventListener(UIReflowEventType.StretchCardElements, this.stretchElements); } private removeReflowListener = () => { window.removeEventListener(UIReflowEventType.StretchCardElements, this.stretchElements); } /** * Stretch the to the bottom of its parent and the inner content to the bottom of the */ private stretchElements = () => { if (this.props.stretch) { this.withCard((card) => { // Stretch the card to the bottom of its parent stretch(card); }); this.withContentContainer((contentContainer) => { // Stretch the card to the bottom of its parent stretch(contentContainer); }); } } }