[React Ocean 组件库] 实现 Modal 弹窗

334 阅读1分钟

交互展示

basic.gif

image.png

image.png

使用

const Basic = () => {
  const [visible, setVisible] = useState(false);
  return (
    <>
      <Modal
        visible={visible}
        title="Modal"
        onCancel={() => setVisible(false)}
        onOk={() => setVisible(false)}
      >
        I am Modal
      </Modal>

      <Button
        width={100}
        height={40}
        onClick={() => setVisible(!visible)}
        type="primary"
      >
        打开Modal
      </Button>
    </>
  );
};

需求

  • 支持基本弹框
  • 支持异步调用
  • 支持函数式调用
  • 支持函数式 + 异步调用
  • 支持自定义页脚位置
  • 自定义宽度
  • 自定义页脚组件
  • 自定义页脚文字

实现

const Modal = (props: Modal) => {
  const {
    visible,
    width = '500px',
    title,
    type,
    titleIcon,
    onCancel,
    children,
    footer,
    okText,
    cancelText,
    footerPosition = 'right',
    onOk,
    confirm,
  } = props;

  const [wrapperVisible, setWrapperVisible] = useState(visible);
  const [loading, setIsLoading] = useState(false);
  const [cancelLoading, setCancelLoading] = useState(false);

  const removeConfirmContainer = () => {
    const dom = document.querySelector('.' + CONTAINER_NAME);
    console.log(dom, 'dom');
    dom &&
      setTimeout(() => {
        document.body.removeChild(dom as HTMLElement);
      }, 400);
  };

  const okConfirm = (result: any, isConfirm: boolean, isCancel: boolean) => {
    if (result instanceof Promise && result.then) {
      isCancel ? setCancelLoading(true) : setIsLoading(true);
      result.then(() => {
        isCancel ? setCancelLoading(false) : setIsLoading(false);
        if (isConfirm) removeConfirmContainer();
        setWrapperVisible(false);
      });
    } else {
      if (isConfirm) removeConfirmContainer();
      setWrapperVisible(false);
    }
  };

  const cancel = () => {
    const result = onCancel?.();
    if (confirm) {
      okConfirm(result, true, true);
    } else {
      okConfirm(result, false, true);
    }
  };

  const clickDocumentCancel = (e: any) => {
    const clickDom = e.target as HTMLElement;
    e.stopPropagation();
    if (clickDom.getAttribute('class')?.includes('ocean-modal-mark')) {
      cancel();
    }
  };

  useEffect(() => {
    setWrapperVisible(visible);
  }, [visible]);

  useEffect(() => {
    wrapperVisible && onEvent(window, 'click', clickDocumentCancel)();
    return () => {
      offEvent(window, 'click', clickDocumentCancel)();
    };
  }, [wrapperVisible]);

  // 抽屉弹出时,body 滚动条隐藏
  // 抽屉合上时,body 滚动条显示
  useHiddenScroll('body', wrapperVisible as boolean);

  const ok = () => {
    const result = onOk?.();
    if (confirm) {
      okConfirm(result, true, false);
    } else {
      okConfirm(result, false, false);
    }
  };

  return (
    <ModalWrapper>
      <CSSTransition
        in={wrapperVisible}
        timeout={330}
        appear
        mountOnEnter
        classNames="fadeModal"
        unmountOnExit
      >
        <ModalMark className="ocean-modal-mark">
          <CSSTransition
            in={wrapperVisible}
            timeout={330}
            appear
            mountOnEnter
            classNames="fadeContent"
            unmountOnExit
          >
            <ModalContent
              className="ocean-modal-content"
              modalWidth={width}
              onClick={(e: any) => e.stopPropagation()}
            >
              <ModalHeader className="ocean-modal-header">
                <div className="ocean-title">
                  <i />
                  <div className="title">
                    {type && titleIcon}
                    <span>{title}</span>
                  </div>
                  <CloseOutlined className="close-icon" onClick={cancel} />
                </div>
              </ModalHeader>
              <ModalContentBody className="ocean-modal-content-body">
                {children}
              </ModalContentBody>
              <ModalContentFooter className="ocean-modal-content-footer">
                {footer || (
                  <ButtonWrapper footerPosition={footerPosition}>
                    <Button
                      type="outline"
                      width={92}
                      onClick={cancel}
                      className="cancel-btn"
                      loading={cancelLoading}
                    >
                      {cancelText || '取消'}
                    </Button>

                    <Button
                      width={90}
                      onClick={ok}
                      type="primary"
                      loading={loading}
                    >
                      {okText || '确定'}
                    </Button>
                  </ButtonWrapper>
                )}
              </ModalContentFooter>
            </ModalContent>
          </CSSTransition>
        </ModalMark>
      </CSSTransition>
    </ModalWrapper>
  );
};
Modal.confirm = (props: Modal) => {
  const div = document.createElement('div');
  div.setAttribute('class', CONTAINER_NAME);
  document.body.appendChild(div);
  const root = createRoot(div);

  root.render(
    <Modal {...props} visible confirm>
      {props.children}
    </Modal>,
  );
};

export default Modal;