import * as React from 'react'; import classNames from 'classnames'; import type { Tab } from 'rc-tabs/lib/interface'; import omit from 'rc-util/lib/omit'; import { devUseWarning } from '../_util/warning'; import { ConfigContext } from '../config-provider'; import useSize from '../config-provider/hooks/useSize'; import Skeleton from '../skeleton'; import type { TabsProps } from '../tabs'; import Tabs from '../tabs'; import Grid from './Grid'; import useStyle from './style'; export type CardType = 'inner'; export type CardSize = 'default' | 'small'; export interface CardTabListType extends Omit { key: string; /** @deprecated Please use `label` instead */ tab?: React.ReactNode; label?: React.ReactNode; } export interface CardProps extends Omit, 'title'> { prefixCls?: string; title?: React.ReactNode; extra?: React.ReactNode; bordered?: boolean; /** @deprecated Please use `styles.header` instead */ headStyle?: React.CSSProperties; /** @deprecated Please use `styles.body` instead */ bodyStyle?: React.CSSProperties; style?: React.CSSProperties; loading?: boolean; hoverable?: boolean; children?: React.ReactNode; id?: string; className?: string; rootClassName?: string; size?: CardSize; type?: CardType; cover?: React.ReactNode; actions?: React.ReactNode[]; tabList?: CardTabListType[]; tabBarExtraContent?: React.ReactNode; onTabChange?: (key: string) => void; activeTabKey?: string; defaultActiveTabKey?: string; tabProps?: TabsProps; classNames?: { header?: string; body?: string; extra?: string; title?: string; actions?: string; cover?: string; }; styles?: { header?: React.CSSProperties; body?: React.CSSProperties; extra?: React.CSSProperties; title?: React.CSSProperties; actions?: React.CSSProperties; cover?: React.CSSProperties; }; } type CardClassNamesModule = keyof Exclude; type CardStylesModule = keyof Exclude; const ActionNode: React.FC<{ actionClasses: string; actions: React.ReactNode[]; actionStyle: React.CSSProperties; }> = (props) => { const { actionClasses, actions = [], actionStyle } = props; return (
    {actions.map((action, index) => { // Move this out since eslint not allow index key // And eslint-disable makes conflict with rollup // ref https://github.com/ant-design/ant-design/issues/46022 const key = `action-${index}`; return (
  • {action}
  • ); })}
); }; const Card = React.forwardRef((props, ref) => { const { prefixCls: customizePrefixCls, className, rootClassName, style, extra, headStyle = {}, bodyStyle = {}, title, loading, bordered = true, size: customizeSize, type, cover, actions, tabList, children, activeTabKey, defaultActiveTabKey, tabBarExtraContent, hoverable, tabProps = {}, classNames: customClassNames, styles: customStyles, ...others } = props; const { getPrefixCls, direction, card } = React.useContext(ConfigContext); // =================Warning=================== if (process.env.NODE_ENV !== 'production') { const warning = devUseWarning('Card'); [ ['headStyle', 'styles.header'], ['bodyStyle', 'styles.body'], ].forEach(([deprecatedName, newName]) => { warning.deprecated(!(deprecatedName in props), deprecatedName, newName); }); } const onTabChange = (key: string) => { props.onTabChange?.(key); }; const moduleClass = (moduleName: CardClassNamesModule) => classNames(card?.classNames?.[moduleName], customClassNames?.[moduleName]); const moduleStyle = (moduleName: CardStylesModule): React.CSSProperties => ({ ...card?.styles?.[moduleName], ...customStyles?.[moduleName], }); const isContainGrid = React.useMemo(() => { let containGrid = false; React.Children.forEach(children as React.ReactElement, (element: JSX.Element) => { if (element && element.type && element.type === Grid) { containGrid = true; } }); return containGrid; }, [children]); const prefixCls = getPrefixCls('card', customizePrefixCls); const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls); const loadingBlock = ( {children} ); const hasActiveTabKey = activeTabKey !== undefined; const extraProps = { ...tabProps, [hasActiveTabKey ? 'activeKey' : 'defaultActiveKey']: hasActiveTabKey ? activeTabKey : defaultActiveTabKey, tabBarExtraContent, }; let head: React.ReactNode; const mergedSize = useSize(customizeSize); const tabSize = !mergedSize || mergedSize === 'default' ? 'large' : mergedSize; const tabs = tabList ? ( ({ label: tab, ...item }))} /> ) : null; if (title || extra || tabs) { const headClasses = classNames(`${prefixCls}-head`, moduleClass('header')); const titleClasses = classNames(`${prefixCls}-head-title`, moduleClass('title')); const extraClasses = classNames(`${prefixCls}-extra`, moduleClass('extra')); const mergedHeadStyle: React.CSSProperties = { ...headStyle, ...moduleStyle('header'), }; head = (
{title && (
{title}
)} {extra && (
{extra}
)}
{tabs}
); } const coverClasses = classNames(`${prefixCls}-cover`, moduleClass('cover')); const coverDom = cover ? (
{cover}
) : null; const bodyClasses = classNames(`${prefixCls}-body`, moduleClass('body')); const mergedBodyStyle: React.CSSProperties = { ...bodyStyle, ...moduleStyle('body'), }; const body = (
{loading ? loadingBlock : children}
); const actionClasses = classNames(`${prefixCls}-actions`, moduleClass('actions')); const actionDom = actions && actions.length ? ( ) : null; const divProps = omit(others, ['onTabChange']); const classString = classNames( prefixCls, card?.className, { [`${prefixCls}-loading`]: loading, [`${prefixCls}-bordered`]: bordered, [`${prefixCls}-hoverable`]: hoverable, [`${prefixCls}-contain-grid`]: isContainGrid, [`${prefixCls}-contain-tabs`]: tabList && tabList.length, [`${prefixCls}-${mergedSize}`]: mergedSize, [`${prefixCls}-type-${type}`]: !!type, [`${prefixCls}-rtl`]: direction === 'rtl', }, className, rootClassName, hashId, cssVarCls, ); const mergedStyle: React.CSSProperties = { ...card?.style, ...style }; return wrapCSSVar(
{head} {coverDom} {body} {actionDom}
, ); }); export default Card;