index.tsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. import React, { PureComponent, ReactNode, CSSProperties } from 'react';
  2. import { omit, isString } from 'lodash';
  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. /** aria label */
  49. 'aria-label'?: string;
  50. }
  51. class Card extends PureComponent<CardProps> {
  52. static Meta = Meta;
  53. static propTypes = {
  54. actions: PropTypes.array,
  55. bodyStyle: PropTypes.object,
  56. bordered: PropTypes.bool,
  57. children: PropTypes.node,
  58. className: PropTypes.string,
  59. cover: PropTypes.node,
  60. footer: PropTypes.node,
  61. footerLine: PropTypes.bool,
  62. footerStyle: PropTypes.object,
  63. header: PropTypes.node,
  64. headerExtraContent: PropTypes.node,
  65. headerLine: PropTypes.bool,
  66. headerStyle: PropTypes.object,
  67. loading: PropTypes.bool,
  68. shadows: PropTypes.oneOf(strings.SHADOWS),
  69. style: PropTypes.object,
  70. title: PropTypes.node,
  71. 'aria-label': PropTypes.string,
  72. };
  73. static defaultProps = {
  74. bordered: true,
  75. footerLine: false,
  76. headerLine: true,
  77. loading: false
  78. };
  79. renderHeader = (): ReactNode => {
  80. const {
  81. title,
  82. headerExtraContent,
  83. header,
  84. headerLine,
  85. headerStyle
  86. } = this.props;
  87. const headerCls = cls(`${prefixcls}-header`, {
  88. [`${prefixcls}-header-bordered`]: Boolean(headerLine)
  89. });
  90. const headerWrapperCls = cls(`${prefixcls}-header-wrapper`);
  91. const titleCls = cls(`${prefixcls}-header-wrapper-title`, {
  92. [`${prefixcls}-header-wrapper-spacing`]: Boolean(headerExtraContent)
  93. });
  94. if (header || headerExtraContent || title) {
  95. return (
  96. <div style={headerStyle} className={headerCls}>
  97. {
  98. header || ( // Priority of header over title and headerExtraContent
  99. <div className={headerWrapperCls}>
  100. {headerExtraContent &&
  101. (
  102. <div className={`${prefixcls}-header-wrapper-extra`}>
  103. {headerExtraContent}
  104. </div>
  105. )
  106. }
  107. {title &&
  108. (
  109. <div className={titleCls}>
  110. {
  111. isString(title) ?
  112. (
  113. <Typography.Title
  114. heading={6}
  115. ellipsis={{ showTooltip: true, rows: 1 }}
  116. >
  117. {title}
  118. </Typography.Title>
  119. ) :
  120. title
  121. }
  122. </div>
  123. )
  124. }
  125. </div>
  126. )
  127. }
  128. </div>
  129. );
  130. }
  131. return null;
  132. };
  133. renderCover = (): ReactNode => {
  134. const {
  135. cover
  136. } = this.props;
  137. const coverCls = cls(`${prefixcls}-cover`);
  138. return cover && <div className={coverCls}>{cover}</div>;
  139. };
  140. renderBody = (): ReactNode => {
  141. const {
  142. bodyStyle,
  143. children,
  144. actions,
  145. loading
  146. } = this.props;
  147. const bodyCls = cls(`${prefixcls}-body`);
  148. const actionsCls = cls(`${prefixcls}-body-actions`);
  149. const actionsItemCls = cls(`${prefixcls}-body-actions-item`);
  150. const placeholder = (
  151. <div>
  152. <Skeleton.Title />
  153. <br />
  154. <Skeleton.Paragraph rows={3} />
  155. </div>
  156. );
  157. return (
  158. <div style={bodyStyle} className={bodyCls}>
  159. {children && (
  160. <Skeleton placeholder={placeholder} loading={loading} active>
  161. {children}
  162. </Skeleton>
  163. )}
  164. {
  165. Array.isArray(actions) &&
  166. (
  167. <div className={actionsCls}>
  168. <Space spacing={12}>
  169. {actions.map((item, idx) => (
  170. <div key={idx} className={actionsItemCls}>{item}</div>
  171. ))}
  172. </Space>
  173. </div>
  174. )
  175. }
  176. </div>
  177. );
  178. };
  179. renderFooter = (): ReactNode => {
  180. const {
  181. footer,
  182. footerLine,
  183. footerStyle
  184. } = this.props;
  185. const footerCls = cls(`${prefixcls}-footer`, {
  186. [`${prefixcls}-footer-bordered`]: footerLine
  187. });
  188. return footer && <div style={footerStyle} className={footerCls}>{footer}</div>;
  189. };
  190. render(): ReactNode {
  191. const {
  192. bordered,
  193. shadows,
  194. style,
  195. className,
  196. ...otherProps
  197. } = this.props;
  198. const others = omit(otherProps, [ // Remove APIs in otherProps that do not need to be hung on the outer node
  199. 'actions',
  200. 'bodyStyle',
  201. 'cover',
  202. 'headerExtraContent',
  203. 'footer',
  204. 'footerLine',
  205. 'footerStyle',
  206. 'header',
  207. 'headerLine',
  208. 'headerStyle',
  209. 'loading',
  210. 'title'
  211. ]);
  212. const cardCls = cls(prefixcls, className, {
  213. [`${prefixcls}-bordered`]: bordered,
  214. [`${prefixcls}-shadows`]: shadows,
  215. [`${prefixcls}-shadows-${shadows}`]: shadows
  216. });
  217. return (
  218. <div {...others} aria-busy={this.props.loading} className={cardCls} style={style}>
  219. {this.renderHeader()}
  220. {this.renderCover()}
  221. {this.renderBody()}
  222. {this.renderFooter()}
  223. </div>
  224. );
  225. }
  226. }
  227. export default Card;