floatModal

27 阅读2分钟
import 'react-resizable/css/styles.css'; // 必须引入的样式

import React, { useRef, useState } from 'react'; // 添加 useRef
import Draggable from 'react-draggable';
import { Resizable } from 'react-resizable';

interface FloatingModalProps {
  title: string;
  subTitle?: string;
  children: React.ReactNode;
  onClose: () => void;
  defaultPosition?: { x: number; y: number };
  defaultSize?: { width: number; height: number };
}

const FloatingModal: React.FC<FloatingModalProps> = ({
  title,
  subTitle,
  children,
  onClose,
  defaultPosition = { x: 0, y: 0 },
  defaultSize = { width: 400, height: 300 },
}) => {
  const [position, setPosition] = useState(defaultPosition);
  const [size, setSize] = useState(defaultSize);
  const [isDragging, setIsDragging] = useState(false);
  const dragRef = useRef<HTMLDivElement>(null);  // 创建 ref 用于 Draggable
  // const resizeRef = useRef<HTMLDivElement>(null);  // 创建 ref 用于 Resizable

  // 处理拖动事件
  const handleDrag = (_: any, data: { x: number; y: number }) => {
    setPosition({ x: data.x, y: Math.max(data.y, 0) });
  };

  // 开始拖动时设置状态
  const handleStart = () => {
    setIsDragging(true);
  };

  // 结束拖动时重置状态
  const handleStop = () => {
    setIsDragging(false);
  };

  // 处理调整大小事件
  const handleResize = (_: any, { size }: { size: { width: number; height: number } }) => {
    setSize(size);
  };

  return (
    <Draggable
      nodeRef={dragRef}  // 添加 nodeRef 属性
      position={position}
      onDrag={handleDrag}
      onStart={handleStart}
      onStop={handleStop}
      handle=".floating-modal-header" // 只能通过标题栏拖动
      bounds="parent" // 限制在父容器内拖动
    >
      <Resizable
        width={size.width}
        height={size.height}
        onResize={handleResize}
        resizeHandles={['s', 'w', 'e', 'n', 'sw', 'nw', 'se', 'ne']} // 所有方向的调整把手
      >
        <div
          ref={dragRef}  // 将 ref 绑定到元素
          style={{
            position: 'fixed', // 脱离文档流
            width: `${size.width}px`,
            height: `${size.height}px`,
            zIndex: 100000,
            boxShadow: '0 4px 20px rgba(0, 0, 0, 0.15)',
            borderRadius: '8px',
            overflow: 'hidden',
            backgroundColor: '#242424',
            display: 'flex',
            flexDirection: 'column',
            pointerEvents: isDragging ? 'none' : 'auto', // 拖动时不拦截鼠标事件
            cursor: isDragging ? 'grabbing' : 'default',
          }}
        >
          {/* 标题栏 - 拖动区域 */}
          <div
            className="floating-modal-header"
            style={{
              padding: '8px 15px',
              backgroundColor: '#242424',
              borderBottom: '1px solid rgba(255, 255, 255, 0.05)',
              cursor: 'move',
              display: 'flex',
              justifyContent: 'space-between',
              alignItems: 'center',
            }}
          >
            <div style={{ display: 'flex', alignItems: 'flex-end' }}>
              <h3 style={{
                margin: 0,
                paddingRight: 15,
                borderRight: '1px solid rgba(255, 255, 255, 0.08)',
                fontSize: '14px', color: '#FCFCFC', fontWeight: 600, userSelect: 'none', // 防止文字被选中
              }}>{title}</h3>
              {subTitle && <div
                style={{ fontSize: 12, opacity: 0.6, paddingLeft: 15 }}
              >{subTitle}</div>}
            </div>
          </div>
          <button
            onClick={onClose}
            style={{
              position: 'absolute',
              right: '16px',
              top: '6px',
              background: 'none',
              border: 'none',
              cursor: 'pointer',
              fontSize: '18px',
              lineHeight: 1,
              padding: '4px',
              color: '#fff',
            }}
          >
            ×
          </button>
          {/* 内容区域 */}
          <div
            style={{
              flex: 1,
              padding: '10px',
              overflow: 'auto',
            }}
          >
            {children}
          </div>
        </div>
      </Resizable>
    </Draggable>
  );
};

export default FloatingModal;