index.tsx 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import React, { PureComponent, ReactNode, CSSProperties } from 'react';
  2. import { omit, isString } from 'lodash-es';
  3. import PropTypes from 'prop-types';
  4. import { cssClasses, strings } from '@douyinfe/semi-foundation/card/constants';
  5. import '@douyinfe/semi-foundation/card/card.scss';
  6. import Meta from './meta';
  7. import cls from 'classnames';
  8. import Skeleton from '../skeleton';
  9. import Typography from '../typography';
  10. import Space from '../space';
  11. const prefixcls = cssClasses.PREFIX;
  12. export type Shadows = 'hover' | 'always';
  13. export { MetaProps } from './meta';
  14. export { CardGroupProps } from './cardGroup';
  15. export interface CardProps {
  16. /** Operation group at the bottom of the card content area */
  17. actions?: ReactNode[];
  18. /** Card content area inline style */
  19. bodyStyle?: CSSProperties;
  20. /** Whether there is an outer border */
  21. bordered?: boolean;
  22. /** Style class name */
  23. className?: string;
  24. /** Cover */
  25. cover?: ReactNode;
  26. /** Additional additions to the right of the title */
  27. headerExtraContent?: ReactNode;
  28. /** Custom end of page */
  29. footer?: ReactNode;
  30. /** Whether there is an edge between the bottom of the page and the content area */
  31. footerLine?: boolean;
  32. /** Inline style at the end of the page */
  33. footerStyle?: CSSProperties;
  34. /** Custom head */
  35. header?: ReactNode;
  36. /** Whether there is an edge line between the head and the content area */
  37. headerLine?: boolean;
  38. /** Head inline style */
  39. headerStyle?: CSSProperties;
  40. /** Whether to preload */
  41. loading?: boolean;
  42. /** Set shadow */
  43. shadows?: Shadows;
  44. /** Card inline style */
  45. style?: CSSProperties;
  46. /** Title */
  47. title?: ReactNode;
  48. }
  49. class Card extends PureComponent<CardProps> {
  50. static Meta = Meta;
  51. static propTypes = {
  52. actions: PropTypes.array,
  53. bodyStyle: PropTypes.object,
  54. bordered: PropTypes.bool,
  55. children: PropTypes.node,
  56. className: PropTypes.string,
  57. cover: PropTypes.node,
  58. footer: PropTypes.node,
  59. footerLine: PropTypes.bool,
  60. footerStyle: PropTypes.object,
  61. header: PropTypes.node,
  62. headerExtraContent: PropTypes.node,
  63. headerLine: PropTypes.bool,
  64. headerStyle: PropTypes.object,
  65. loading: PropTypes.bool,
  66. shadows: PropTypes.oneOf(strings.SHADOWS),
  67. style: PropTypes.object,
  68. title: PropTypes.node
  69. };
  70. static defaultProps = {
  71. bordered: true,
  72. footerLine: false,
  73. headerLine: true,
  74. loading: false
  75. };
  76. renderHeader = (): ReactNode => {
  77. const {
  78. title,
  79. headerExtraContent,
  80. header,
  81. headerLine,
  82. headerStyle
  83. } = this.props;
  84. const headerCls = cls(`${prefixcls}-header`, {
  85. [`${prefixcls}-header-bordered`]: Boolean(headerLine)
  86. });
  87. const headerWrapperCls = cls(`${prefixcls}-header-wrapper`);
  88. const titleCls = cls(`${prefixcls}-header-wrapper-title`, {
  89. [`${prefixcls}-header-wrapper-spacing`]: Boolean(headerExtraContent)
  90. });
  91. if (header || headerExtraContent || title) {
  92. return (
  93. <div style={headerStyle} className={headerCls}>
  94. {
  95. header || ( // Priority of header over title and headerExtraContent
  96. <div className={headerWrapperCls}>
  97. {headerExtraContent &&
  98. (
  99. <div className={`${prefixcls}-header-wrapper-extra`}>
  100. {headerExtraContent}
  101. </div>
  102. )
  103. }
  104. {title &&
  105. (
  106. <div className={titleCls}>
  107. {
  108. isString(title) ?
  109. (
  110. <Typography.Title
  111. heading={6}
  112. ellipsis={{ showTooltip: true, rows: 1 }}
  113. >
  114. {title}
  115. </Typography.Title>
  116. ) :
  117. title
  118. }
  119. </div>
  120. )
  121. }
  122. </div>
  123. )
  124. }
  125. </div>
  126. );
  127. }
  128. return null;
  129. };
  130. renderCover = (): ReactNode => {
  131. const {
  132. cover
  133. } = this.props;
  134. const coverCls = cls(`${prefixcls}-cover`);
  135. return cover && <div className={coverCls}>{cover}</div>;
  136. };
  137. renderBody = (): ReactNode => {
  138. const {
  139. bodyStyle,
  140. children,
  141. actions,
  142. loading
  143. } = this.props;
  144. const bodyCls = cls(`${prefixcls}-body`);
  145. const actionsCls = cls(`${prefixcls}-body-actions`);
  146. const actionsItemCls = cls(`${prefixcls}-body-actions-item`);
  147. const placeholder = (
  148. <div>
  149. <Skeleton.Title />
  150. <br />
  151. <Skeleton.Paragraph rows={3} />
  152. </div>
  153. );
  154. return (
  155. <div style={bodyStyle} className={bodyCls}>
  156. {children && (
  157. <Skeleton placeholder={placeholder} loading={loading} active>
  158. {children}
  159. </Skeleton>
  160. )}
  161. {
  162. Array.isArray(actions) &&
  163. (
  164. <div className={actionsCls}>
  165. <Space spacing={12}>
  166. {actions.map((item, idx) => (
  167. <div key={idx} className={actionsItemCls}>{item}</div>
  168. ))}
  169. </Space>
  170. </div>
  171. )
  172. }
  173. </div>
  174. );
  175. };
  176. renderFooter = (): ReactNode => {
  177. const {
  178. footer,
  179. footerLine,
  180. footerStyle
  181. } = this.props;
  182. const footerCls = cls(`${prefixcls}-footer`, {
  183. [`${prefixcls}-footer-bordered`]: footerLine
  184. });
  185. return footer && <div style={footerStyle} className={footerCls}>{footer}</div>;
  186. };
  187. render(): ReactNode {
  188. const {
  189. bordered,
  190. shadows,
  191. style,
  192. className,
  193. ...otherProps
  194. } = this.props;
  195. const others = omit(otherProps, [ // Remove APIs in otherProps that do not need to be hung on the outer node
  196. 'actions',
  197. 'bodyStyle',
  198. 'cover',
  199. 'headerExtraContent',
  200. 'footer',
  201. 'footerLine',
  202. 'footerStyle',
  203. 'header',
  204. 'headerLine',
  205. 'headerStyle',
  206. 'loading',
  207. 'title'
  208. ]);
  209. const cardCls = cls(prefixcls, className, {
  210. [`${prefixcls}-bordered`]: bordered,
  211. [`${prefixcls}-shadows`]: shadows,
  212. [`${prefixcls}-shadows-${shadows}`]: shadows
  213. });
  214. return (
  215. <div {...others} className={cardCls} style={style}>
  216. {this.renderHeader()}
  217. {this.renderCover()}
  218. {this.renderBody()}
  219. {this.renderFooter()}
  220. </div>
  221. );
  222. }
  223. }
  224. export default Card;