123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- import React, { PureComponent, ReactNode, CSSProperties } from 'react';
- import { omit, isString } from 'lodash';
- import PropTypes from 'prop-types';
- import { cssClasses, strings } from '@douyinfe/semi-foundation/card/constants';
- import '@douyinfe/semi-foundation/card/card.scss';
- import Meta from './meta';
- import cls from 'classnames';
- import Skeleton from '../skeleton';
- import Typography from '../typography';
- import Space from '../space';
- const prefixcls = cssClasses.PREFIX;
- export type Shadows = 'hover' | 'always';
- export { MetaProps } from './meta';
- export { CardGroupProps } from './cardGroup';
- export interface CardProps {
- /** Operation group at the bottom of the card content area */
- actions?: ReactNode[];
- /** Card content area inline style */
- bodyStyle?: CSSProperties;
- /** Whether there is an outer border */
- bordered?: boolean;
- /** Style class name */
- className?: string;
- children?: React.ReactNode;
- /** Cover */
- cover?: ReactNode;
- /** Additional additions to the right of the title */
- headerExtraContent?: ReactNode;
- /** Custom end of page */
- footer?: ReactNode;
- /** Whether there is an edge between the bottom of the page and the content area */
- footerLine?: boolean;
- /** Inline style at the end of the page */
- footerStyle?: CSSProperties;
- /** Custom head */
- header?: ReactNode;
- /** Whether there is an edge line between the head and the content area */
- headerLine?: boolean;
- /** Head inline style */
- headerStyle?: CSSProperties;
- /** Whether to preload */
- loading?: boolean;
- /** Set shadow */
- shadows?: Shadows;
- /** Card inline style */
- style?: CSSProperties;
- /** Title */
- title?: ReactNode;
- /** aria label */
- 'aria-label'?: string;
- }
- class Card extends PureComponent<CardProps> {
- static Meta = Meta;
- static propTypes = {
- actions: PropTypes.array,
- bodyStyle: PropTypes.object,
- bordered: PropTypes.bool,
- children: PropTypes.node,
- className: PropTypes.string,
- cover: PropTypes.node,
- footer: PropTypes.node,
- footerLine: PropTypes.bool,
- footerStyle: PropTypes.object,
- header: PropTypes.node,
- headerExtraContent: PropTypes.node,
- headerLine: PropTypes.bool,
- headerStyle: PropTypes.object,
- loading: PropTypes.bool,
- shadows: PropTypes.oneOf(strings.SHADOWS),
- style: PropTypes.object,
- title: PropTypes.node,
- 'aria-label': PropTypes.string,
- };
- static defaultProps = {
- bordered: true,
- footerLine: false,
- headerLine: true,
- loading: false
- };
- renderHeader = (): ReactNode => {
- const {
- title,
- headerExtraContent,
- header,
- headerLine,
- headerStyle
- } = this.props;
- const headerCls = cls(`${prefixcls}-header`, {
- [`${prefixcls}-header-bordered`]: Boolean(headerLine)
- });
- const headerWrapperCls = cls(`${prefixcls}-header-wrapper`);
- const titleCls = cls(`${prefixcls}-header-wrapper-title`, {
- [`${prefixcls}-header-wrapper-spacing`]: Boolean(headerExtraContent)
- });
- if (header || headerExtraContent || title) {
- return (
- <div style={headerStyle} className={headerCls}>
- {header || ( // Priority of header over title and headerExtraContent
- <div className={headerWrapperCls}>
- {headerExtraContent && (
- <div
- className={`${prefixcls}-header-wrapper-extra`}
- x-semi-prop="headerExtraContent"
- >
- {headerExtraContent}
- </div>
- )}
- {title && (
- <div className={titleCls}>
- {isString(title) ? (
- <Typography.Title
- heading={6}
- ellipsis={{ showTooltip: true, rows: 1 }}
- x-semi-prop="title"
- >
- {title}
- </Typography.Title>
- ) : (
- title
- )}
- </div>
- )}
- </div>
- )}
- </div>
- );
- }
- return null;
- };
- renderCover = (): ReactNode => {
- const {
- cover
- } = this.props;
- const coverCls = cls(`${prefixcls}-cover`);
- return (
- cover && (
- <div className={coverCls} x-semi-prop="cover">
- {cover}
- </div>
- )
- );
- };
- renderBody = (): ReactNode => {
- const { bodyStyle, children, actions, loading } = this.props;
- const bodyCls = cls(`${prefixcls}-body`);
- const actionsCls = cls(`${prefixcls}-body-actions`);
- const actionsItemCls = cls(`${prefixcls}-body-actions-item`);
- const placeholder = (
- <div>
- <Skeleton.Title />
- <br />
- <Skeleton.Paragraph rows={3} />
- </div>
- );
- return (
- <div style={bodyStyle} className={bodyCls}>
- {children && (
- <Skeleton placeholder={placeholder} loading={loading} active>
- {children}
- </Skeleton>
- )}
- {
- Array.isArray(actions) &&
- (
- <div className={actionsCls}>
- <Space spacing={12}>
- {actions.map((item, idx) => (
- <div key={idx} className={actionsItemCls} x-semi-prop={`actions.${idx}`}>{item}</div>
- ))}
- </Space>
- </div>
- )
- }
- </div>
- );
- };
- renderFooter = (): ReactNode => {
- const {
- footer,
- footerLine,
- footerStyle
- } = this.props;
- const footerCls = cls(`${prefixcls}-footer`, {
- [`${prefixcls}-footer-bordered`]: footerLine
- });
- return (
- footer && (
- <div style={footerStyle} className={footerCls} x-semi-prop="footer">
- {footer}
- </div>
- )
- );
- };
- render(): ReactNode {
- const {
- bordered,
- shadows,
- style,
- className,
- ...otherProps
- } = this.props;
- const others = omit(otherProps, [ // Remove APIs in otherProps that do not need to be hung on the outer node
- 'actions',
- 'bodyStyle',
- 'cover',
- 'headerExtraContent',
- 'footer',
- 'footerLine',
- 'footerStyle',
- 'header',
- 'headerLine',
- 'headerStyle',
- 'loading',
- 'title'
- ]);
- const cardCls = cls(prefixcls, className, {
- [`${prefixcls}-bordered`]: bordered,
- [`${prefixcls}-shadows`]: shadows,
- [`${prefixcls}-shadows-${shadows}`]: shadows
- });
- return (
- <div {...others} aria-busy={this.props.loading} className={cardCls} style={style}>
- {this.renderHeader()}
- {this.renderCover()}
- {this.renderBody()}
- {this.renderFooter()}
- </div>
- );
- }
- }
- export default Card;
|