SideSheetContent.tsx 5.5 KB

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