React 模态框的设计(五)主体设计

弹窗的主体设计没什么特别,就是把细分化后的各个功能封装在一个个的小组件内,然后再整合。这样逻辑就分开了,不乱。

弹窗容器

这个容器是弹窗主体的根组件(不含遮罩),要能根据主题的变化能做出相应的改变。还要记录渲染后的主体的大小参数,原为有最大化和最小化的操作,这些操作是依据当前主体的大小而计算得来的。

_ModelContianer.jsx

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'
import { useRef, useState } from 'react';
import { Paper } from '@mui/material';
import { useModelState } from './_useModel';
import { infoLevel } from './_ModelConfigure';

// 计算不同状态下的高度
const calHeight = (sizeMode, normalHeight) => {
    switch (sizeMode) {
        case 0:
            return '45px';
        case 1:
            return normalHeight > 0 ? normalHeight + 'px' : 'auto';
        case 2:
            return '100vh';
        default:
            return 'auto';
    }
}

// 最大化时的固定样式
const maxSizeCss = css`
        width: 100vw;
        height: 100vh;
        top: 0;
        left: 0;
    `;

/**
 * 弹窗容器
 * @param {*} param0 
 * @returns 
 */
const ModelContainer = ({ children }) => {
    const modelState = useModelState();
    const {
        stateMode, // 弹窗的状态,0: 最小化, 1: 正常, 2: 最大化
        level, // 弹窗的类型(主要是颜色类型),选项有:default, error, warning, success, info
        isDark, //是否是暗黑模式 
    } = modelState;

    const [nomalSize, setNormalSize] = useState({ width: 0, height: 0 });
    const containerRef = useRef(null);
    return (
        <Paper
            ref={containerRef}
            css={css`
                border: 1px solid #A0A0A0;
                border-radius: 5px;
                width: ${ stateMode === 2 ? '100vw' : stateMode === 0 ? '300px' : '576px' };
                height: ${ calHeight(stateMode, nomalSize.height) };
                overflow: hidden;
                max-width: 100%;
                max-height: 100vh;
                display: flex;
                flex-direction: column;
                background-color: ${isDark ? '#333' : infoLevel[level] ? infoLevel[level].color :  "white" };
                ${stateMode === 2 ? maxSizeCss : null};
                transition: all 0.3s;
            `}
            onPointerEnter={() => {
                if (nomalSize.width === 0) {
                    const rect = containerRef.current.getBoundingClientRect();
                    setNormalSize({
                        width: rect.width,
                        height: rect.height,
                    });
                }                
            }}
        >
            {
                children
            }
        </Paper>
    );
};

export default ModelContainer;

控制按钮

就是最大化、最小化、关闭按钮,最大化按钮和最小化按钮的功能是互斥的,也就是说最大化时不能最小化,最小化时不能最大化。

import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';
import Stack from '@mui/material/Stack';
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
import Fullscreen from '@mui/icons-material/Fullscreen';
import MinimizeIcon from '@mui/icons-material/Minimize';
import { useModelState } from './_useModel';

/**
 * 弹窗的控制按钮, 最小化,最大化,关闭
 * @param { 弹窗大小状态, 0: 最小化; 1: 正常; 2: 最大化} sizeMode
 * @param {设置弹窗大小状态} setSizeMode
 * @returns 
 */
function ModelController() {
    const modelstate = useModelState();
    const {
        stateMode, // 弹窗的状态,0: 最小化, 1: 正常, 2: 最大化
        setStateMode, // 设置弹窗的状态
        onClose, //关闭弹窗
    } = modelstate;

    // 最大化事件
    const maxmizeEvent = (e) => {
        setStateMode(stateMode === 2 ? 1 : 2);
    };
       
    // 最小化事件
    const minimizeEvent = (e) => {
        setStateMode(stateMode === 0 ? 1 : 0);
    };

    // 关闭事件
    const onCloseHandler = (e) => {
        e.preventDefault();
        e.stopPropagation();

        onClose && onClose();
    }

    return (
        <Stack direction="row" spacing={1}>
            <IconButton
                aria-label="close"
                color="inherit"
                size="small"
                onClick={minimizeEvent}
                disabled={stateMode === 2}
            >
                {
                    <MinimizeIcon fontSize="inherit" />
                }
            </IconButton>
            <IconButton
                aria-label="close"
                color="inherit"
                size="small"
                onClick={maxmizeEvent}
                disabled={stateMode === 0}
            >
                {
                    stateMode === 2 ? <FullscreenExitIcon fontSize="inherit" /> : <Fullscreen fontSize="inherit" />
                }
            </IconButton>
            <IconButton
                aria-label="close"
                color="inherit"
                size="small"
                onClick={onCloseHandler}
            >
                <CloseIcon fontSize="inherit" />
            </IconButton>
        </Stack>
    );
};

export default ModelController;

标题栏

标题栏有标题图标、标题、控制按钮组。

_ModelHeader.jsx

import React, { useRef } from 'react';
import Stack from '@mui/material/Stack';
import AlertTitle from '@mui/material/AlertTitle';

import ModelController from './_ModelController';
import { useModelState } from './_useModel';

import { iconSize, infoLevel } from './_ModelConfigure';


// 计算标题长度, 最小化时最多显示8个字符, 正常时最多显示20个字符
const calTitle = (sizeMode, title) => {
    if (sizeMode === 0) {
        return title.length > 8 ? title.substring(0, 8) + "..." : title;
    }
    return title.length > 20 ? title.substring(0, 20) + " ..." : title;
}

/**
 * 弹窗的头部标题栏
 * @param {*} props 
 * @returns 
 */
function ModelHeader(props) {
    const containerRef = useRef();
    const {
        level = "default", // 弹窗的类型(主要是颜色类型),选项有:normal, error, warning, success, info
        title = "提示", //标题
        enableController = true,
        ...others
    } = props;
    const { Icon, iColor } = infoLevel[level] || infoLevel["default"]; //弹窗的颜色和图标

    const modelstate = useModelState();
    const {
        stateMode, // 弹窗的状态,0: 最小化, 1: 正常, 2: 最大化
    } = modelstate;
    return (
        <Stack
            ref={containerRef}
            direction="row"
            justifyContent="space-between"
            justifyItems={"center"}
            spacing={2}
            sx={{
                p: 1,
                borderBottom: stateMode === 0 ? "none" : 1,
                borderColor: "divider",
                height: 45,
            }}
            {...others}
        >
            <Stack
                direction="row"
                justifyContent="flex-start"
                alignItems="center"
                spacing={1}
                sx={{ userSelect: "none" }}
            >
                <Icon sx={{ fontSize: iconSize, color: iColor }} />
                <AlertTitle
                    sx={{
                        userSelect: "none",
                        textOverflow: "ellipsis",
                        overflow: "hidden",
                        wordBreak: "break-all",
                        whiteSpace: "nowrap",
                    }}
                >
                    {
                        calTitle(stateMode, title)
                    }
                </AlertTitle>
            </Stack>
            {
                enableController && <ModelController />
            }
            
        </Stack>
    );
};

export default ModelHeader;

内容区

这个没什么好说的,就是弹窗的内容呈现部分

_ModelContent.jsx

import React from 'react';
import "./_ModelContent.css";
import Box from '@mui/material/Box';

function ModelContent({ children}) {
    return (
        <Box
            className="noscrollbar"
            sx={{
                flex: 1,
                p: 2,
                pt: 1,
                pb: 1,
                width: "100%",
                overflowY: "auto",
                userSelect: "none",
            }}
        >
            {
                children
            }
        </Box>
    )
}

export default ModelContent;

功能按钮

这个功能按钮带有Loding状态,是MUILab中自带的。

_StateButton.jsx

import React, { useState } from 'react';
import LoadingButton from '@mui/lab/LoadingButton';

/**
 * 带Loading状态的按钮
 * @param {按钮标题} title 
 * @param {按钮点击事件, onClick的类型为: (setLoading, setTitle, setDisable, onClose)=> {}} onClick
 * @param {模态框的关闭事件} onClose
 * @param {是否为警告按钮} attention
 * @returns 
 */
export default function StateButton({ title, onClick, onClose, attention = false }) {
    const [loading, setLoading] = useState(false);
    const [disabled, setDisable] = useState(false);
    const [actionTitle, setTitle] = useState(title);
    const onClickEvent = () => {
        onClick(setLoading, setTitle, setDisable, onClose);
    }
    return (
        <LoadingButton
            variant={attention ? "contained" : "outlined" }
            color={attention ? "error" : "primary"}
            disabled={disabled}
            loading={loading}
            onClick={ onClickEvent }
            size="small"
        >
            {actionTitle }
        </LoadingButton>
    )
}

功能区

就是根据配置的数据生成多少个功能按钮,如下所示:

_ModelActions.jsx

import React from 'react';
import DialogActions from '@mui/material/DialogActions';
import StateButton from './_StateButton';

function ModelActions({ actions, onClose }) {
    return (
        <DialogActions sx={{ mr: 1 }}>
            {
                actions.map((item, index) => {
                    return (
                        <StateButton
                            key={index}
                            title={item.title}
                            onClick={item.onClick}
                            onClose={onClose}
                            attention={item.attention}
                        />
                    )
                })
            }
        </DialogActions>
  )
}

export default ModelActions;

到这里所有主体的子组件都设计 完成了。当然我的文章并没有结束,重头戏还在后面。再回分解。

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码蚁先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值