SideSheetContent.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import React, { CSSProperties } from 'react';
  2. import PropTypes from 'prop-types';
  3. import cls from 'classnames';
  4. import { cssClasses } from '@douyinfe/semi-foundation/sideSheet/constants';
  5. import Button from '../iconButton';
  6. import { noop } from 'lodash';
  7. import { IconClose } from '@douyinfe/semi-icons';
  8. let uuid = 0;
  9. const prefixCls = cssClasses.PREFIX;
  10. export interface SideSheetContentProps {
  11. onClose?: (e: React.MouseEvent) => void;
  12. mask?: boolean;
  13. maskStyle?: CSSProperties;
  14. maskClosable?: boolean;
  15. title?: React.ReactNode;
  16. closable?: boolean;
  17. headerStyle?: CSSProperties;
  18. width: CSSProperties['width'];
  19. height: CSSProperties['height'];
  20. style: CSSProperties;
  21. bodyStyle?: CSSProperties;
  22. className: string;
  23. footer?: React.ReactNode;
  24. 'aria-label'?: string;
  25. }
  26. export default class SideSheetContent extends React.PureComponent<SideSheetContentProps> {
  27. static propTypes = {
  28. onClose: PropTypes.func,
  29. };
  30. static defaultProps = {
  31. onClose: noop,
  32. };
  33. private sideSheetId: string;
  34. private timeoutId: number;
  35. componentDidMount() {
  36. this.sideSheetId = `sidesheet-${uuid++}`;
  37. }
  38. componentWillUnmount() {
  39. clearTimeout(this.timeoutId);
  40. }
  41. onMaskClick = (e: React.MouseEvent) => {
  42. if (e.target === e.currentTarget) {
  43. this.close(e);
  44. }
  45. };
  46. close = (e: React.MouseEvent) => {
  47. const { onClose } = this.props;
  48. onClose && onClose(e);
  49. };
  50. getMaskElement() {
  51. const {
  52. mask,
  53. maskStyle,
  54. maskClosable,
  55. } = this.props;
  56. if (mask) {
  57. return (
  58. <div
  59. aria-hidden={true}
  60. key="mask"
  61. className={`${prefixCls}-mask`}
  62. style={maskStyle}
  63. onClick={maskClosable ? this.onMaskClick : null}
  64. />
  65. );
  66. }
  67. return null;
  68. }
  69. renderHeader() {
  70. const {
  71. title,
  72. closable,
  73. headerStyle,
  74. } = this.props;
  75. let header, closer;
  76. if (title) {
  77. header = (
  78. <div className={`${prefixCls}-title`}>
  79. {this.props.title}
  80. </div>
  81. );
  82. }
  83. if (closable) {
  84. closer = (
  85. <Button
  86. className={`${prefixCls}-close`}
  87. key="close-btn"
  88. onClick={this.close}
  89. type="tertiary"
  90. icon={<IconClose/>}
  91. theme="borderless"
  92. size="small"
  93. />
  94. );
  95. }
  96. return (
  97. <div className={`${prefixCls}-header`} role={'heading'} aria-level={1} style={{ ...headerStyle }}>
  98. {header}
  99. {closer}
  100. </div>
  101. );
  102. }
  103. getDialogElement() {
  104. const { ...props } = this.props;
  105. const style: CSSProperties = {};
  106. if (props.width) {
  107. style.width = props.width;
  108. // When the mask is false, the width is set on the wrapper. At this time, sidesheet-inner does not need to set the width again, otherwise, the percentage will be accumulated repeatedly when the width is a percentage
  109. if (!props.mask) {
  110. style.width = '100%';
  111. }
  112. }
  113. if (props.height) {
  114. style.height = props.height;
  115. }
  116. const header = this.renderHeader();
  117. const dialogElement = (
  118. <div
  119. key="dialog-element"
  120. role="dialog"
  121. tabIndex={-1}
  122. className={`${prefixCls}-inner ${prefixCls}-inner-wrap`}
  123. // onMouseDown={this.onDialogMouseDown}
  124. style={{ ...props.style, ...style }}
  125. // id={this.dialogId}
  126. >
  127. <div className={`${prefixCls}-content`}>
  128. {header}
  129. <div className={`${prefixCls}-body`} style={props.bodyStyle}>
  130. {props.children}
  131. </div>
  132. {props.footer ? (
  133. <div className={`${prefixCls}-footer`}>
  134. {props.footer}
  135. </div>
  136. ) : null}
  137. </div>
  138. </div>
  139. );
  140. return dialogElement;
  141. }
  142. render() {
  143. const {
  144. mask,
  145. className,
  146. width,
  147. } = this.props;
  148. const wrapperCls = cls(className, {
  149. [`${prefixCls}-fixed`]: !mask,
  150. });
  151. const wrapperStyle: CSSProperties = {};
  152. if (!mask && width) {
  153. wrapperStyle.width = width;
  154. }
  155. return (
  156. <div className={wrapperCls} style={wrapperStyle}>
  157. {this.getMaskElement()}
  158. {this.getDialogElement()}
  159. </div>
  160. );
  161. }
  162. }