SideSheetContent.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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. maskClassName?: string;
  16. title?: React.ReactNode;
  17. closable?: boolean;
  18. headerStyle?: CSSProperties;
  19. width: CSSProperties['width'];
  20. height: CSSProperties['height'];
  21. style: CSSProperties;
  22. bodyStyle?: CSSProperties;
  23. className: string;
  24. dialogClassName?:string;
  25. children?: React.ReactNode;
  26. footer?: React.ReactNode;
  27. 'aria-label'?: string;
  28. maskExtraProps?: {[key:string]: any};
  29. wrapperExtraProps?: {[key:string]: any};
  30. }
  31. export default class SideSheetContent extends React.PureComponent<SideSheetContentProps> {
  32. static propTypes = {
  33. onClose: PropTypes.func,
  34. };
  35. static defaultProps = {
  36. onClose: noop,
  37. };
  38. private sideSheetId: string;
  39. private timeoutId: number;
  40. componentDidMount() {
  41. this.sideSheetId = `sidesheet-${uuid++}`;
  42. }
  43. componentWillUnmount() {
  44. clearTimeout(this.timeoutId);
  45. }
  46. onMaskClick = (e: React.MouseEvent) => {
  47. if (e.target === e.currentTarget) {
  48. this.close(e);
  49. }
  50. };
  51. close = (e: React.MouseEvent) => {
  52. const { onClose } = this.props;
  53. onClose && onClose(e);
  54. };
  55. getMaskElement() {
  56. const {
  57. mask,
  58. maskStyle,
  59. maskClosable,
  60. } = this.props;
  61. if (mask) {
  62. return (
  63. <div
  64. aria-hidden={true}
  65. key="mask"
  66. className={cls(`${prefixCls}-mask`, this.props.maskClassName ?? "")}
  67. style={maskStyle}
  68. onClick={maskClosable ? this.onMaskClick : null}
  69. {...this.props.maskExtraProps}
  70. />
  71. );
  72. }
  73. return null;
  74. }
  75. renderHeader() {
  76. const {
  77. title,
  78. closable,
  79. headerStyle,
  80. } = this.props;
  81. let header, closer;
  82. if (title) {
  83. header = (
  84. <div className={`${prefixCls}-title`} x-semi-prop="title">
  85. {this.props.title}
  86. </div>
  87. );
  88. }
  89. if (closable) {
  90. closer = (
  91. <Button
  92. className={`${prefixCls}-close`}
  93. key="close-btn"
  94. onClick={this.close}
  95. type="tertiary"
  96. icon={<IconClose/>}
  97. theme="borderless"
  98. size="small"
  99. />
  100. );
  101. }
  102. return (
  103. <div className={`${prefixCls}-header`} role={'heading'} aria-level={1} style={{ ...headerStyle }}>
  104. {header}
  105. {closer}
  106. </div>
  107. );
  108. }
  109. getDialogElement() {
  110. const { ...props } = this.props;
  111. const style: CSSProperties = {};
  112. if (props.width) {
  113. style.width = props.width;
  114. // 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
  115. if (!props.mask) {
  116. style.width = '100%';
  117. }
  118. }
  119. if (props.height) {
  120. style.height = props.height;
  121. }
  122. const header = this.renderHeader();
  123. const dialogElement = (
  124. <div
  125. key="dialog-element"
  126. role="dialog"
  127. tabIndex={-1}
  128. className={cls(`${prefixCls}-inner`, `${prefixCls}-inner-wrap`, this.props.dialogClassName??"")}
  129. // onMouseDown={this.onDialogMouseDown}
  130. style={{ ...props.style, ...style }}
  131. {...this.props.wrapperExtraProps}
  132. // id={this.dialogId}
  133. >
  134. <div className={`${prefixCls}-content`}>
  135. {header}
  136. <div className={`${prefixCls}-body`} style={props.bodyStyle} x-semi-prop="children">
  137. {props.children}
  138. </div>
  139. {props.footer ? (
  140. <div className={`${prefixCls}-footer`} x-semi-prop="footer">
  141. {props.footer}
  142. </div>
  143. ) : null}
  144. </div>
  145. </div>
  146. );
  147. return dialogElement;
  148. }
  149. render() {
  150. const {
  151. mask,
  152. className,
  153. width,
  154. } = this.props;
  155. const wrapperCls = cls(className, {
  156. [`${prefixCls}-fixed`]: !mask,
  157. });
  158. const wrapperStyle: CSSProperties = {};
  159. if (!mask && width) {
  160. wrapperStyle.width = width;
  161. }
  162. return (
  163. <div className={wrapperCls} style={wrapperStyle}>
  164. {this.getMaskElement()}
  165. {this.getDialogElement()}
  166. </div>
  167. );
  168. }
  169. }