Ver código fonte

feat: sidesheet use js animation

feat: sidesheet use js animation
DaiQiangReal 3 anos atrás
pai
commit
1ab417b3cd

+ 14 - 6
packages/semi-foundation/sideSheet/animation.scss

@@ -1,7 +1,15 @@
-$transition_duration-sideSheet_mask-bg: var(--semi-transition_delay-fastest); // 侧边栏打开后-蒙层颜色-动画持续时间
-$transition_function-sideSheet_mask-bg: var(--semi-transition_function-easeOut); // 侧边栏打开后-蒙层颜色-过渡曲线
-$transition_delay-sideSheet_mask-bg: 0ms; // 侧边栏打开后-蒙层颜色-延迟时间
+$animation_duration-sideSheet_mask_show: 0.18s; // 侧边栏打开时-蒙层颜色-动画持续时间
+$animation_function-sideSheet_mask_show: cubic-bezier(0.25, 0.46, 0.45, 0.94); // 侧边栏打开时-蒙层颜色-过渡曲线
+$animation_delay-sideSheet_mask_show: 0ms; // 侧边栏打开时-蒙层颜色-延迟时间
 
-$transition_duration-sideSheet_inner: var(--semi-transition_delay-fastest); // 侧边栏-动画持续时间
-$transition_function-sideSheet_inner: var(--semi-transition_function-easeOut); // 侧边栏-过渡曲线
-$transition_delay-sideSheet_inner: 0ms; // 侧边栏-延迟时间
+$animation_duration-sideSheet_mask_hide: 0.18s; // 侧边栏关闭时-蒙层颜色-动画持续时间
+$animation_function-sideSheet_mask_hide: cubic-bezier(0.25, 0.46, 0.45, 0.94); // 侧边栏关闭时-蒙层颜色-过渡曲线
+$animation_delay-sideSheet_mask_hide: 0ms; // 侧边栏关闭时-蒙层颜色-延迟时间
+
+$animation_duration-sideSheet_inner_show: 0.18s; // 侧边栏打开-动画持续时间
+$animation_function-sideSheet_inner_show: cubic-bezier(0.25, 0.46, 0.45, 0.94); // 侧边栏打开-过渡曲线
+$animation_delay-sideSheet_inner_show: 0ms; // 侧边栏打开-延迟时间
+
+$animation_duration-sideSheet_inner_hide: 0.18s; // 侧边栏关闭-动画持续时间
+$animation_function-sideSheet_inner_hide: cubic-bezier(0.25, 0.46, 0.45, 0.94); // 侧边栏关闭-过渡曲线
+$animation_delay-sideSheet_inner_hide: 0ms; // 侧边栏关闭-延迟时间

+ 143 - 5
packages/semi-foundation/sideSheet/sideSheet.scss

@@ -86,8 +86,6 @@ $module: #{$prefix}-sidesheet;
         background-color: $color-sideSheet-bg;
         // background-clip: padding-box;
         border: 0;
-        transition: $transition_duration-sideSheet_inner $transition_function-sideSheet_inner
-            $transition_delay-sideSheet_inner;
     }
 
     &-header {
@@ -120,9 +118,6 @@ $module: #{$prefix}-sidesheet;
         height: 100%;
         background-color: $color-sideSheet_mask-bg;
         opacity: 1;
-        transition: opacity $transition_duration-sideSheet_mask-bg $transition_function-sideSheet_mask-bg
-            $transition_delay-sideSheet_mask-bg;
-
         &-hidden {
             opacity: 0;
             display: none;
@@ -132,6 +127,143 @@ $module: #{$prefix}-sidesheet;
     &-footer {
         padding: $spacing-sideSheet_footer-padding;
     }
+
+    @keyframes #{$module}-slideShow_top {
+        from {
+            transform: translateY(-100%);
+        }
+        to {
+            transform: translateX(0);
+        }
+    }
+
+    @keyframes #{$module}-slideHide_top {
+        from {
+            transform: translateX(0);
+        }
+        to {
+            transform: translateY(-100%);
+        }
+    }
+
+    @keyframes #{$module}-slideShow_bottom {
+        from {
+            transform: translateY(100%);
+        }
+        to {
+            transform: translateX(0);
+        }
+    }
+
+    @keyframes #{$module}-slideHide_bottom {
+        from {
+            transform: translateX(0);
+        }
+        to {
+            transform: translateY(100%);
+        }
+    }
+
+    @keyframes #{$module}-slideShow_left {
+        from {
+            transform: translateX(-100%);
+        }
+        to {
+            transform: translateX(0);
+        }
+    }
+
+    @keyframes #{$module}-slideHide_left {
+        from {
+            transform: translateX(0);
+        }
+        to {
+            transform: translateX(-100%);
+        }
+    }
+
+    @keyframes #{$module}-slideShow_right {
+        from {
+            transform: translateX(100%);
+        }
+        to {
+            transform: translateX(0);
+        }
+    }
+
+    @keyframes #{$module}-slideHide_right {
+        from {
+            transform: translateX(0);
+        }
+        to {
+            transform: translateX(100%);
+        }
+    }
+
+    @keyframes #{$module}-opacityShow {
+        from {
+            opacity: 0;
+        }
+
+        to {
+            opacity: 1;
+        }
+    }
+
+    @keyframes #{$module}-opacityHide {
+        from {
+            opacity: 1;
+        }
+
+        to {
+            opacity: 0;
+        }
+
+    }
+
+
+    &-animation-content_show_top{
+        animation: #{$module}-slideShow_top $animation_duration-sideSheet_inner_show $animation_function-sideSheet_inner_show $animation_delay-sideSheet_inner_show;
+    }
+
+    &-animation-content_hide_top{
+        animation: #{$module}-slideHide_top $animation_duration-sideSheet_inner_hide $animation_function-sideSheet_inner_hide $animation_delay-sideSheet_inner_hide;
+    }
+
+    &-animation-content_show_bottom{
+        animation: #{$module}-slideShow_bottom $animation_duration-sideSheet_inner_show $animation_function-sideSheet_inner_show $animation_delay-sideSheet_inner_show;
+    }
+
+    &-animation-content_hide_bottom{
+        animation: #{$module}-slideHide_bottom $animation_duration-sideSheet_inner_hide $animation_function-sideSheet_inner_hide $animation_delay-sideSheet_inner_hide;
+    }
+
+    &-animation-content_show_left{
+        animation: #{$module}-slideShow_left $animation_duration-sideSheet_inner_show $animation_function-sideSheet_inner_show $animation_delay-sideSheet_inner_show;
+    }
+
+    &-animation-content_hide_left{
+        animation: #{$module}-slideHide_left $animation_duration-sideSheet_inner_hide $animation_function-sideSheet_inner_hide $animation_delay-sideSheet_inner_hide;
+    }
+
+    &-animation-content_show_right{
+        animation: #{$module}-slideShow_right $animation_duration-sideSheet_inner_show $animation_function-sideSheet_inner_show $animation_delay-sideSheet_inner_show;
+    }
+
+    &-animation-content_hide_right{
+        animation: #{$module}-slideHide_right $animation_duration-sideSheet_inner_hide $animation_function-sideSheet_inner_hide $animation_delay-sideSheet_inner_hide;
+    }
+
+    &-animation-mask_show{
+        animation: #{$module}-opacityShow $animation_duration-sideSheet_mask_show $animation_function-sideSheet_mask_show $animation_delay-sideSheet_mask_show;
+    }
+
+    &-animation-mask_hide{
+        animation: #{$module}-opacityHide $animation_duration-sideSheet_mask_hide $animation_function-sideSheet_mask_hide $animation_delay-sideSheet_mask_hide;
+    }
+
+
+
 }
 
 .#{$module}-fixed {
@@ -167,6 +299,7 @@ $module: #{$prefix}-sidesheet;
             bottom: 0;
         }
     }
+
 }
 
 .#{$module}.#{$module}-popup {
@@ -176,4 +309,9 @@ $module: #{$prefix}-sidesheet;
     display: none;
 }
 
+
+
+
+
+
 @import './rtl.scss';

+ 7 - 0
packages/semi-foundation/sideSheet/sideSheetFoundation.ts

@@ -34,6 +34,7 @@ export interface SideSheetProps {
 
 export interface SideSheetState {
     hidden: boolean;
+    shouldRender: boolean;
 }
 
 export interface SideSheetAdapter extends DefaultAdapter<SideSheetProps, SideSheetState> {
@@ -44,9 +45,11 @@ export interface SideSheetAdapter extends DefaultAdapter<SideSheetProps, SideShe
     setOnKeyDownListener: () => void;
     removeKeyDownListener: () => void;
     toggleHidden: (hidden: boolean) => void;
+    setShouldRender: (shouldRender: boolean) => void;
 }
 
 
+
 export default class SideSheetFoundation extends BaseFoundation<SideSheetAdapter> {
     constructor(adapter: SideSheetAdapter) {
         super({ ...SideSheetFoundation.defaultAdapter, ...adapter });
@@ -89,6 +92,10 @@ export default class SideSheetFoundation extends BaseFoundation<SideSheetAdapter
         }
     }
 
+    setShouldRender(shouldRender: boolean) {
+        this._adapter.setShouldRender(shouldRender);
+    }
+
     mergeMotionProp = (motion: any, prop: string, cb: () => void) => {
         const mergedMotion = typeof (motion) === 'undefined' || motion ? {
             ...motion,

+ 4 - 4
packages/semi-foundation/tooltip/animation.scss

@@ -1,7 +1,7 @@
-$animation_duration-tooltip-in: 0.1s;//tooltip-弹出动画-动画持续时间
-$animation_duration-tooltip-out: 0.1s;//tooltip-收起动画-动画持续时间
+$animation_duration-tooltip_in: 0.1s;//tooltip-弹出动画-动画持续时间
+$animation_duration-tooltip_out: 0.1s;//tooltip-收起动画-动画持续时间
 
 
-$animation_function-tooltip-in: cubic-bezier(0.215, 0.61, 0.355, 1);//tooltip-弹出动画-动画插值函数
-$animation_function-tooltip-out: cubic-bezier(0.215, 0.61, 0.355, 1);//tooltip-收起动画-动画插值函数
+$animation_function-tooltip_in: cubic-bezier(0.215, 0.61, 0.355, 1);//tooltip-弹出动画-动画插值函数
+$animation_function-tooltip_out: cubic-bezier(0.215, 0.61, 0.355, 1);//tooltip-收起动画-动画插值函数
 

+ 2 - 2
packages/semi-foundation/tooltip/tooltip.scss

@@ -90,11 +90,11 @@ $module-icon: #{$module}-icon-arrow;
     }
 
     &-animation-show{
-        animation: #{$module}-zoomIn $animation_duration-tooltip-in $animation_function-tooltip-in;
+        animation: #{$module}-zoomIn $animation_duration-tooltip_in $animation_function-tooltip_in;
     }
 
     &-animation-hide{
-        animation: #{$module}-zoomOut $animation_duration-tooltip-out $animation_function-tooltip-out;
+        animation: #{$module}-zoomOut $animation_duration-tooltip_out $animation_function-tooltip_out;
     }
 
 }

+ 27 - 5
packages/semi-ui/_cssAnimation/index.tsx

@@ -5,6 +5,8 @@ import { isEqual, noop } from "lodash";
 interface AnimationEventsNeedBind {
     onAnimationStart: (e: React.AnimationEvent) => void
     onAnimationEnd: (e: React.AnimationEvent) => void
+
+    [key: string]: (e: any) => void;
 }
 
 interface AnimationProps {
@@ -16,8 +18,8 @@ interface AnimationProps {
         animationEventsNeedBind: AnimationEventsNeedBind
     }) => ReactNode
     animationState: "enter" | "leave"
-    onAnimationEnd?:()=>void;
-    onAnimationStart?:()=>void;
+    onAnimationEnd?: () => void;
+    onAnimationStart?: () => void;
 }
 
 interface AnimationState {
@@ -39,7 +41,7 @@ class CSSAnimation extends React.Component<AnimationProps, AnimationState> {
         const changedKeys = Object.keys(this.props).filter(key => !isEqual(this.props[key], prevProps[key]));
         if (changedKeys.includes("animationState")) {
         }
-        if (changedKeys.includes("startClassName")){
+        if (changedKeys.includes("startClassName")) {
             this.setState({
                 currentClassName: this.props.startClassName,
                 extraStyle: {}
@@ -57,7 +59,7 @@ class CSSAnimation extends React.Component<AnimationProps, AnimationState> {
         this.setState({
             currentClassName: this.props.endClassName,
             extraStyle: {}
-        }, ()=>{
+        }, () => {
             this.props.onAnimationEnd?.();
         });
     }
@@ -73,7 +75,27 @@ class CSSAnimation extends React.Component<AnimationProps, AnimationState> {
             }
         });
     }
-
 }
 
+
+const mergeAnimationFunction = (eventHandleFunctions: AnimationEventsNeedBind[]) => {
+    //merge function in objects
+    const mergedFunction = {};
+    eventHandleFunctions.forEach(eventHandleFunction => {
+        Object.keys(eventHandleFunction).forEach(key => {
+            if (mergedFunction[key]) {
+                const oldFunction = mergedFunction[key];
+                mergedFunction[key] = (e) => {
+                    eventHandleFunction[key](e);
+                    oldFunction(e);
+                };
+            } else {
+                mergedFunction[key] = eventHandleFunction[key];
+            }
+        });
+    });
+    return mergedFunction;
+};
+
+export { mergeAnimationFunction };
 export default CSSAnimation;

+ 7 - 3
packages/semi-ui/sideSheet/SideSheetContent.tsx

@@ -15,6 +15,7 @@ export interface SideSheetContentProps {
     mask?: boolean;
     maskStyle?: CSSProperties;
     maskClosable?: boolean;
+    maskClassName?: string;
     title?: React.ReactNode;
     closable?: boolean;
     headerStyle?: CSSProperties;
@@ -23,9 +24,11 @@ export interface SideSheetContentProps {
     style: CSSProperties;
     bodyStyle?: CSSProperties;
     className: string;
+    dialogClassName?:string;
     children?: React.ReactNode;
     footer?: React.ReactNode;
     'aria-label'?: string;
+    eventHandlers?: {[key:string]: React.EventHandler<any>};
 }
 
 export default class SideSheetContent extends React.PureComponent<SideSheetContentProps> {
@@ -70,7 +73,7 @@ export default class SideSheetContent extends React.PureComponent<SideSheetConte
                 <div
                     aria-hidden={true}
                     key="mask"
-                    className={`${prefixCls}-mask`}
+                    className={cls(`${prefixCls}-mask`, this.props.maskClassName ?? "")}
                     style={maskStyle}
                     onClick={maskClosable ? this.onMaskClick : null}
                 />
@@ -133,7 +136,7 @@ export default class SideSheetContent extends React.PureComponent<SideSheetConte
                 key="dialog-element"
                 role="dialog"
                 tabIndex={-1}
-                className={`${prefixCls}-inner ${prefixCls}-inner-wrap`}
+                className={cls(`${prefixCls}-inner`, `${prefixCls}-inner-wrap`, this.props.dialogClassName??"")}
                 // onMouseDown={this.onDialogMouseDown}
                 style={{ ...props.style, ...style }}
                 // id={this.dialogId}
@@ -159,6 +162,7 @@ export default class SideSheetContent extends React.PureComponent<SideSheetConte
             mask,
             className,
             width,
+            eventHandlers = {}
         } = this.props;
         const wrapperCls = cls(className, {
             [`${prefixCls}-fixed`]: !mask,
@@ -168,7 +172,7 @@ export default class SideSheetContent extends React.PureComponent<SideSheetConte
             wrapperStyle.width = width;
         }
         return (
-            <div className={wrapperCls} style={wrapperStyle}>
+            <div className={wrapperCls} style={wrapperStyle} {...eventHandlers}>
                 {this.getMaskElement()}
                 {this.getDialogElement()}
             </div>

+ 0 - 104
packages/semi-ui/sideSheet/SideSheetTransition.tsx

@@ -1,104 +0,0 @@
-// @ts-ignore  Currently there is no types definition for semi-animation-react;
-import { Transition } from '@douyinfe/semi-animation-react';
-import React, { CSSProperties } from 'react';
-import { Motion } from '../_base/base';
-
-export interface SideSheetTransitionProps{
-    children?: React.ReactNode | ((TransitionProps: any) => React.ReactNode);
-    motion?: Motion<SideSheetTransitionProps>;
-    controlled?: boolean;
-    visible?: boolean;
-    placement?: 'left' | 'top' | 'right' | 'bottom';
-}
-
-// eslint-disable-next-line max-len
-const formatStyles = function formatStyles(styles: CSSProperties = {}, props: SideSheetTransitionProps = {}): CSSProperties {
-    const { placement } = props;
-    const { translate } = styles;
-    const { opacity } = styles;
-
-    let transform = '';
-
-    switch (placement) {
-        case 'left':
-            transform = `translateX(-${translate}%)`;
-            break;
-        case 'top':
-            transform = `translateY(-${translate}%)`;
-            break;
-        case 'right':
-            transform = `translateX(${translate}%)`;
-            break;
-        case 'bottom':
-            transform = `translateY(${translate}%)`;
-            break;
-        default:
-            break;
-    }
-
-    return {
-        transform,
-        opacity,
-    };
-};
-
-export default class SideSheetTransition extends React.PureComponent<SideSheetTransitionProps> {
-    render() {
-
-        let { motion = {} } = this.props;
-        const {
-            children,
-            controlled = false,
-            visible,
-        } = this.props;
-
-        if (typeof motion === 'function') {
-            motion = motion(this.props);
-        } else if (!motion || typeof motion !== 'object') {
-            motion = {};
-        }
-        let extra = {};
-        if (controlled) {
-            extra = {
-                state: visible ? 'enter' : 'leave',
-            };
-        }
-
-        return (
-            <Transition
-                config={{
-                    tension: 170,
-                    friction: 14,
-                    easing: 'linear',
-                    duration: 200,
-                }}
-                from={{
-                    translate: 100,
-                    opacity: {
-                        val: 0,
-                        duration: 180,
-                    },
-                }}
-                enter={{
-                    translate: 0,
-                    opacity: {
-                        val: 1,
-                        duration: 180,
-                    },
-                } as any}
-                leave={{
-                    translate: 100,
-                    opacity: {
-                        val: 0,
-                        duration: 180,
-                    },
-                } as any}
-                {...extra}
-                {...motion}
-            >
-                {/* eslint-disable-next-line max-len */}
-                {typeof children === 'function' ? (styles: CSSProperties) => (children as (styles: CSSProperties) => any)(formatStyles(styles, this.props)) : children}
-            </Transition>
-        );
-    }
-}

+ 47 - 18
packages/semi-ui/sideSheet/index.tsx

@@ -6,7 +6,6 @@ import Portal from '../_portal';
 import cls from 'classnames';
 import ConfigContext, { ContextValue } from '../configProvider/context';
 import { cssClasses, strings } from '@douyinfe/semi-foundation/sideSheet/constants';
-import SideSheetTransition from './SideSheetTransition';
 import SideSheetContent from './SideSheetContent';
 import { noop } from 'lodash';
 import SideSheetFoundation, {
@@ -15,13 +14,13 @@ import SideSheetFoundation, {
     SideSheetState
 } from '@douyinfe/semi-foundation/sideSheet/sideSheetFoundation';
 import '@douyinfe/semi-foundation/sideSheet/sideSheet.scss';
+import CSSAnimation, { mergeAnimationFunction } from "../_cssAnimation";
 
 const prefixCls = cssClasses.PREFIX;
 const defaultWidthList = strings.WIDTH;
 const defaultHeight = strings.HEIGHT;
 
 export { SideSheetContentProps } from './SideSheetContent';
-export { SideSheetTransitionProps } from './SideSheetTransition';
 
 export interface SideSheetReactProps extends SideSheetProps {
     bodyStyle?: CSSProperties;
@@ -87,7 +86,7 @@ export default class SideSheet extends BaseComponent<SideSheetReactProps, SideSh
 
     constructor(props: SideSheetReactProps) {
         super(props);
-        this.state = { hidden: !this.props.visible };
+        this.state = { hidden: !this.props.visible, shouldRender: false };
         this.foundation = new SideSheetFoundation(this.adapter);
         this._active = false;
     }
@@ -130,6 +129,11 @@ export default class SideSheet extends BaseComponent<SideSheetReactProps, SideSh
                     this.setState({ hidden });
                 }
             },
+            setShouldRender: (shouldRender: boolean) => {
+                if (shouldRender !== this.state.shouldRender) {
+                    this.setState({ shouldRender });
+                }
+            }
         };
     }
 
@@ -221,23 +225,48 @@ export default class SideSheet extends BaseComponent<SideSheetReactProps, SideSh
         const mergedMotion = this.foundation.getMergedMotion();
         this._active = this._active || visible;
         const shouldRender = (visible || keepDOM) && this._active;
+        if (shouldRender === true && !this.state.shouldRender) {
+            this.foundation.setShouldRender(true);
+        }
+
         if (mergedMotion) {
-            return (
-                <SideSheetTransition placement={placement} motion={mergedMotion} controlled={keepDOM} visible={visible}>
-                    {shouldRender ?
-                        transitionStyles => (
-                            <SideSheetContent
-                                {...contentProps}
-                                style={{ ...transitionStyles, ...style }}
-                                maskStyle={{ opacity: transitionStyles.opacity, ...maskStyle }}
-                            >
-                                {children}
-                            </SideSheetContent>
-                        ) : null}
-                </SideSheetTransition>
-            );
+            return <CSSAnimation animationState={visible ? 'enter' : 'leave'} startClassName={
+                visible ? `${prefixCls}-animation-mask_show` : `${prefixCls}-animation-mask_hide`
+            } onAnimationEnd={() => {
+                this.foundation.setShouldRender(shouldRender);
+            }}>
+                {
+                    ({
+                        animationClassName: maskAnimationClassName,
+                        animationStyle: maskAnimationStyle,
+                        animationEventsNeedBind: maskAnimationEventsNeedBind
+                    }) => {
+                        return <CSSAnimation
+                            animationState={visible ? 'enter' : 'leave'}
+                            startClassName={visible ? `${prefixCls}-animation-content_show_${this.props.placement}` : `${prefixCls}-animation-content_hide_${this.props.placement}`}
+                            onAnimationEnd={() => {
+                                // for no mask case
+                                this.foundation.setShouldRender(shouldRender);
+                            }}
+                        >
+                            {({ animationClassName, animationStyle, animationEventsNeedBind }) => {
+                                return this.state.shouldRender ? <SideSheetContent
+                                    eventHandlers={mergeAnimationFunction([animationEventsNeedBind, maskAnimationEventsNeedBind])}
+                                    {...contentProps}
+                                    dialogClassName={animationClassName}
+                                    maskClassName={maskAnimationClassName}
+                                    style={{ ...animationStyle, ...style }}>
+                                    {children}
+                                </SideSheetContent> : <></>;
+                            }}
+                        </CSSAnimation>;
+
+                    }
+                }
+            </CSSAnimation>;
         }
-        if (shouldRender) {
+        this.foundation.setShouldRender((visible || keepDOM) && this._active);
+        if (this.state.shouldRender) {
             return (
                 <SideSheetContent {...contentProps} style={style} maskStyle={maskStyle}>
                     {children}