【React】如何实现并封装一个Popup组件(tsx)

写在前面:

在移动端展示数据时,常常会用到popup组件展示,本文实现一个可以上下拖动并且超出视口会自动回弹的popup组件

效果如下:

实现如下:

安装依赖库

首先引入antdreact-draggable来。

封装步骤:

  1. 定义Popup组件。这是一个函数式组件,接受onClosewidget作为参数。在组件内部,通过调用useState钩子函数创建了一个名为position的状态变量,并初始化{ x: 0, y: 0 }position用于跟踪弹出窗口的位置信息。

  2. 定义一个handleDrag函数,用于处理拖拽事件。当拖拽事件发生时,该函数会根据拖拽的位置更新position的状态。同时,通过一些条件判断限制了弹出窗口的垂直拖拽范围。

  3. 使用StyledPopup组件作为根容器,并嵌套了其他子元素。其中,使用了Draggable组件实现了弹出窗口的可拖拽功能,handle=".handle-area"表示只能通过拖拽.handle-area类的元素来进行拖拽。在拖拽过程中,会触发handleDrag函数来更新position的状态。

代码如下:

tsx 复制代码
import { Table } from 'antd';
import React, { useState } from 'react';
import Draggable from 'react-draggable';
import styled from 'styled-components';
import closeSvg from '../../../../assets/images/close.svg';
import { Widget } from '../../types/widgetTypes';

interface PopupProps {
  onClose: () => void;
  widget: Widget | undefined;
}

const Popup: React.FC<PopupProps> = ({ onClose, widget }) => {
  const closePopup = () => {
    onClose();
  };
  const tableData = widget?.config?.content?.dataChart?.config?.sampleData;

  const [position, setPosition] = useState({ x: 0, y: 0 });
  const handleDrag = (e, ui) => {
    if (ui.y < 0 && ui.y < -window.innerHeight * 0.48) {
      ui.y = -window.innerHeight * 0.48;
    } else if (ui.y > 0 && ui.y > window.innerHeight * 0.45) {
      ui.y = window.innerHeight * 0.45;
    }
    setPosition({ x: 0, y: ui.y });
  };
  return (
    <StyledPopup>
      <Draggable
        handle=".handle-area"
        axis="y"
        onDrag={handleDrag}
        position={position}
      >
        <div className="popup">
          <div className="handle-area">
            <div className="popup-handle"></div>
          </div>
          <div className="popup-header">
            <span>查看数据</span>
            <img onClick={closePopup} src={closeSvg} alt="Close" />
          </div>
          <div className="popup-content">
            {
              <TableWrapper>
                <Table
                  size="small"
                  dataSource={tableData?.rows}
                  columns={tableData?.columns?.map((col, index) => ({
                    key: col.name,
                    title: col.name,
                    dataIndex: index,
                  }))}
                  bordered
                />
              </TableWrapper>
            }
          </div>
        </div>
      </Draggable>
    </StyledPopup>
  );
};
const TableWrapper = styled.div`
  padding: 10px;
`;
const StyledPopup = styled.div`
  position: fixed;
  top: 50%;
  width: 100vw;
  & .popup {
    display: flex;
    flex-direction: column;
    border-top-left-radius: 20px;
    border-top-right-radius: 20px;
    height: 150vh;
    background-color: #fff;

    .popup-handle {
      height: 5px;
      width: 10%;
      background-color: rgb(217, 217, 217);
      cursor: row-resize;
      border-radius: 5px;
      margin: 8px auto;
      margin-bottom: 16px;
    }

    .popup-header {
      position: relative;
      display: flex;
      flex-direction: row;
      justify-content: center;
      font-size: 20px;
      font-weight: 700;
      color: rgb(19, 23, 55);

      img {
        position: absolute;
        right: 5px;
        top: 50%;
        transform: translate(-50%, -50%);
        width: 36px;
        height: 36px;
      }
    }
  }

  .popup-content {
    display: flex;
    flex-direction: column;
    margin: 20px 15px;
    max-height: 80vh;
    overflow: auto;
  }
`;

export default Popup;

3.使用参考:

比如可以在打开组件的函数、tsx内,传入数据以及打开的boolen值,并暴露一个onclose函数实现关闭popup的解构

tsx 复制代码
   const [popupOpen, setPopupOpen] = useState(false);
    const [popupWidget, setPopupWidget] = useState<Widget>();
    const onClosePopup = () => {
      setPopupOpen(false);
    };
onWidgetOpenPopup: (widget: Widget) => {
          setPopupOpen(true);
          setPopupWidget(widget);
        },
return (
        ...
        {popupOpen && <Popup onClose={onClosePopup} widget={popupWidget} />
        ...}
        
    );
相关推荐
LaughingZhu5 小时前
Product Hunt 每日热榜 | 2026-05-21
前端·人工智能·经验分享·chatgpt·html
怕浪猫5 小时前
Electron 开发实战(一):从零入门核心基础与环境搭建
前端·electron·ai编程
小鹏linux6 小时前
Ubuntu 22.04 部署开源免费具有精美现代web页面的Casdoor账号管理系统
linux·前端·ubuntu·开源·堡垒机
前端若水7 小时前
会话管理:创建、切换、删除对话历史
前端·人工智能·python·react.js
Bigger7 小时前
mini-cc:一个轻量级 AI 编程助手的诞生
前端·ai编程·claude
涵涵(互关)7 小时前
Naive-ui树型选择器只显示根节点
前端·ui·vue
BY组态7 小时前
Ricon组态系统最佳实践:从零开始构建物联网监控平台
前端·物联网·iot·web组态·组态
BY组态7 小时前
Ricon组态系统vs传统组态软件:为什么选择新一代Web组态平台
前端·物联网·iot·web组态·组态
SoaringHeart7 小时前
Flutter进阶:OverlayEntry 插入图层管理器 NOverlayZIndexManager
前端·flutter
放下华子我只抽RuiKe58 小时前
React 从入门到生产(四):自定义 Hook
前端·javascript·人工智能·深度学习·react.js·自然语言处理·前端框架