【问题标题】:How to pass a react-transition-group TransitionComponent to react-bootstrap Toast?如何将 react-transition-group TransitionComponent 传递给 react-bootstrap Toast?
【发布时间】:2022-01-16 06:58:33
【问题描述】:

我有以下使用react-bootstrap 构建的自定义<CompleteToast/> 组件。我想应用自定义 CSS 过渡,但不明白如何实现这一点。 docs 说我应该传递一个自定义的 react-transition-group TransitionComponent 但不要详细说明如何或举个例子。

我试过查看默认的<ToastFade/>,但不理解 Typescript。我尝试将<ToastTransition/> 作为参考传递,但孩子们不渲染(参考作为“退出动画完成!”被记录)。传递<ToastTransition children={...}/> 会导致错误。

我应该在这里采取什么方法?如果没有 TypeScript,这可能吗?

我的直觉是,我需要弄清楚ToastTransition 是如何自动继承Toast 的孩子的。

CompleteToast.js

import PropTypes from 'prop-types';
import {Toast} from "react-bootstrap";
import {useContext, useEffect, useRef, useState} from "react";
import {IoCheckmarkCircleOutline} from "react-icons/all";
import {GlobalAppContext} from "../../App";
import ToastTransition from "../react-transition-group/ToastTransition";

const dividers = {
    s: 1000,
    m: 60000,
    h: 3.6e+6,
    d: 8.64e+7,
    w: 6.048e+8,
    mo: 2.628e+9,
    y: 3.154e+10
}

const calculateTimeDiff = (timeInMs) => {
    const diff = Date.now() - timeInMs;
    switch (true) {
        case (diff < dividers.s):
            return "Just now";
        case (diff < dividers.m):
            return Math.floor(diff / dividers.s) + "s";
        case (diff < dividers.h):
            return Math.floor(diff / dividers.m) + "m";
        case (diff < dividers.d):
            return Math.floor(diff / dividers.h) + "h";
        case (diff < dividers.w):
            return Math.floor(diff / dividers.d) + "d";
        case (diff < dividers.mo):
            return Math.floor(diff / dividers.w) + "w";
        case (diff < dividers.y):
            return Math.floor(diff / dividers.mo) + "mo";
        case (diff >= dividers.y):
            return Math.floor(diff / dividers.y) + "y";
        default:
            return diff + "ms";
    }
}

const timeUntilNext = (from, unit = "s") => {
    let divider = dividers[unit];
    return (Math.ceil(from / divider) * divider) - from;
}

function CompleteToast({show, title, timestamp, bodyText, headerClass, updateDeleteIds, id, deleteIds}) {
    const [showState, setShowState] = useState(show);
    const [timestampUpdated, setTimestampUpdated] = useState(null);
    const [timestampState, setTimestampState] = useState(timestamp);
    const [timestampText, setTimestampText] = useState("Just now");
    const setToasts = useContext(GlobalAppContext)[0].setStateFunctions.toasts;
    const shownOnce = useRef(false);
    const deleteTimeout = useRef(null);
    const hovering = useRef(false);

    const close = () => {
        if (!hovering.current) {
            setShowState(false);
        }
    }

    const setDeleteTimeout = () => {
        if (shownOnce.current && !hovering.current) {
            deleteTimeout.current = setTimeout(() => {
                setToasts(prevState => prevState.filter(x => x.id !== id));
            }, 2000)
        }
    }

    useEffect(() => {
        if (showState) {
            setTimestampUpdated(Date.now());
            setTimestampState(Date.now);
        } else {
            setDeleteTimeout();
        }
    }, [showState]);

    useEffect(() => {
        if (!showState) {
            setShowState(true);
            shownOnce.current = true;
        }
    }, []);

    useEffect(() => {
        if (showState) {
            //timestamp has been updated and the toast is still showing - update the text to the current time difference
            const timeDiff = calculateTimeDiff(timestampState);
            setTimestampText(timeDiff);
            setTimeout(() => {
                //trigger new update to timestamp text on the next second
                setTimestampUpdated(Date.now());
            }, timeUntilNext(Date.now(), "s"));
        }
    }, [timestampUpdated]);

    return (
        <Toast style={{whiteSpace: "pre-wrap"}}
               onClose={close}
               onClick={close}
               onMouseEnter={() => {
                   hovering.current = true;
                   if (deleteTimeout.current) {
                       clearTimeout(deleteTimeout.current);
                       deleteTimeout.current = null;
                       setShowState(true);
                   }
               }}
               onMouseLeave={() => {
                   hovering.current = false;
                   setTimeout(close, 3000);
               }}
               show={showState}
               delay={4000}
               autohide
               id={id}
               transition={ToastTransition}
               className="cursor-pointer">
            <Toast.Header className={headerClass} closeButton={false}>
                <IoCheckmarkCircleOutline className={"smallIcon me-2"}/>
                <p className="fs-5 my-0 me-auto">{title}</p>
                <small>{timestampText}</small>
            </Toast.Header>
            <Toast.Body className={"position-relative"}>
                {bodyText}
            </Toast.Body>
        </Toast>
    );
}

CompleteToast.propTypes = {
    show: PropTypes.bool,
    handleClick: PropTypes.func,
    buttonText: PropTypes.string,
    buttonVariant: PropTypes.string,
    bodyText: PropTypes.string,
    headerClass: PropTypes.string,
    title: PropTypes.string
};

CompleteToast.defaultProps = {
    show: false,
    timestamp: Date.now(),
    timestampText: Date.now(),
    title: "Success",
    bodyText: "The operation was completed successfully",
    headerClass: "bg-success text-white",
    buttonVariant: "primary"
}

export default CompleteToast;

ToastTransition.js

import {Transition} from 'react-transition-group';

//copied from react-transition-group docks - aim is to getit working, then customise the actual transition

const duration = 300;

const defaultStyle = {
    transition: `opacity ${duration}ms ease-out, maxHeight opacity ${duration}ms ease-out`,
    opacity: 0,
    maxHeight: 0,
}

const transitionStyles = {
    entering: {opacity: 1, maxHeight: "200px"},
    entered: {opacity: 1, maxHeight: "200px"},
    exiting: {opacity: 0, maxHeight: 0},
    exited: {opacity: 0, maxHeight: 0},
};

const ToastTransition = ({in: inProp, children}) => (
    <Transition
        in={inProp}
        timeout={duration}
        onExited={() => {
        console.log("Exit animation complete!")
    }}>
        {state => (
            <div style={{
                ...defaultStyle,
                ...transitionStyles[state]
            }}>
                {children}
            </div>
        )}
    </Transition>
);

export default ToastTransition;

【问题讨论】:

    标签: javascript reactjs react-bootstrap react-transition-group


    【解决方案1】:

    我是个白痴 - 就像调用 children 属性一样简单,它是“特殊的”并且会自动传递。

    它是可视化渲染的,因为我需要在&lt;CompleteToast/&gt; 组件上手动设置一个show 类,即:

    className="cursor-pointer show"
    

    react-transition-group 组件处理它看起来的实际显示/隐藏(这与默认行为不一致,但据我所知,到目前为止它并没有引起问题)。

    工作 ToastTransition.js(需要调整 CSS)

    import {Transition} from 'react-transition-group';
    
    const duration = 500;
    
    const defaultStyle = {
        transition: `opacity ${duration}ms ease-in-out, max-height ${duration}ms ease-out`,
        opacity: 0,
        maxHeight: 0
    }
    
    const transitionStyles = {
        entering: {opacity: 1, maxHeight: "200px"},
        entered: {opacity: 1, maxHeight: "200px"},
        exiting: {opacity: 0, maxHeight: 0},
        exited: {opacity: 0, maxHeight: 0},
    };
    
    const ToastTransition = ({in: inProp, children}) => {
    
        return (
            <Transition
                in={inProp}
                timeout={duration}
                onExited={() => {
                    children.props.onExited();
                }}>
                {(state) => (
                    <div style={{
                        ...defaultStyle,
                        ...transitionStyles[state]
                    }}>
                        {children}
                    </div>
                )}
            </Transition>
        )
    }
    
    export default ToastTransition;
    

    【讨论】:

      猜你喜欢
      • 2020-09-09
      • 2018-02-10
      • 2020-06-13
      • 1970-01-01
      • 2023-03-07
      • 2020-06-07
      • 2019-01-17
      • 1970-01-01
      • 2018-05-27
      相关资源
      最近更新 更多