Răsfoiți Sursa

feat: table navigation js2css

代强 3 ani în urmă
părinte
comite
fe54cd11c2

+ 2 - 1
content/show/collapsible/index.md

@@ -209,10 +209,11 @@ import { Collapsible, Button } from '@douyinfe/semi-ui';
 ## API 参考
 
 | 属性 | 说明 | 类型 | 默认值 | 版本 |
-| --- | --- | --- | --- | --- |
+| -- | --- | --- | --- | --- |
 | className | 类名 | string | - | 0.34.0 |
 | collapseHeight | 折叠高度 | number | 0 | 1.0.0 |
 | duration | 动画执行的时间 | number | 250 | - |
+| fade | 是否开启淡入淡出 | boolean | false | 2.21.0 |
 | isOpen | 是否展开内容区域 | boolean | `false` | - |
 | keepDOM | 是否保留隐藏的面板 DOM 树,默认销毁 | boolean | `false` | 0.25.0 |
 | motion | 是否开启动画 | Motion | `true` | - |

+ 4 - 0
packages/semi-foundation/collapsible/animation.scss

@@ -2,5 +2,9 @@ $transition_duration-collapsible-height: 0.25s;//折叠-高度-动画持续时
 $transition_function-collapsible-height: var(--semi-transition_function-easeIn);//折叠-高度-过渡曲线
 $transition_delay-collapsible-height: var(--semi-transition_delay-fastest);//折叠-高度-延迟时间
 
+$transition_duration-collapsible-opacity: 0.25s;//折叠-透明度-动画持续时间
+$transition_function-collapsible-opacity: var(--semi-transition_function-easeIn);//折叠-透明度-过渡曲线
+$transition_delay-collapsible-opacity: var(--semi-transition_delay-fastest);//折叠-透明度-延迟时间
+
 
 

+ 2 - 1
packages/semi-foundation/collapsible/collapsible.scss

@@ -6,5 +6,6 @@ $module: #{$prefix}-collapsible;
 
 
 .#{$module}-transition{
-    transition: height $transition_duration-collapsible-height $transition_function-collapsible-height $transition_delay-collapsible-height;
+    transition: height $transition_duration-collapsible-height $transition_function-collapsible-height $transition_delay-collapsible-height,
+    opacity $transition_duration-collapsible-opacity $transition_function-collapsible-opacity $transition_delay-collapsible-opacity,
 }

+ 1 - 0
packages/semi-foundation/collapsible/foundation.ts

@@ -8,6 +8,7 @@ export interface CollapsibleFoundationProps{
     collapseHeight?: number;
     reCalcKey?: number | string;
     id?:string,
+    fade?:boolean
 }
 
 export interface CollapsibleFoundationState{

+ 11 - 0
packages/semi-foundation/navigation/navigation.scss

@@ -318,6 +318,17 @@ $module: #{$prefix}-navigation;
             height: inherit;
         }
     }
+
+
+    &-icon-rotate-0{
+        transition: transform 200ms ease-in-out;
+        transform: rotate(0);
+    }
+    &-icon-rotate-180{
+        transition: transform 200ms ease-in-out;
+        transform: rotate(-180deg);
+    }
+
 }
 
 /* Header、Footer-Common */

+ 10 - 0
packages/semi-foundation/table/table.scss

@@ -563,6 +563,16 @@ $module: #{$prefix}-table;
         align-items: center;
         justify-content: center;
     }
+
+    .#{$module}-expandedIcon-show{
+        transition: transform 150ms cubic-bezier(0.62, 0.05, 0.36, 0.95);
+        transform: rotate(90deg);
+    }
+
+    .#{$module}-expandedIcon-hide{
+        transition: transform 150ms cubic-bezier(0.62, 0.05, 0.36, 0.95);
+        transform: rotate(0deg);
+    }
 }
 
 @import './rtl.scss';

+ 3 - 1
packages/semi-ui/collapsible/index.tsx

@@ -37,7 +37,8 @@ class Collapsible extends BaseComponent<CollapsibleProps, CollapsibleState> {
         duration: 250,
         motion: true,
         keepDOM: false,
-        collapseHeight: 0
+        collapseHeight: 0,
+        fade:false
     };
 
     private domRef = React.createRef<HTMLDivElement>();
@@ -168,6 +169,7 @@ class Collapsible extends BaseComponent<CollapsibleProps, CollapsibleState> {
         const wrapperStyle: React.CSSProperties = {
             overflow: 'hidden',
             height: this.props.isOpen ? (this.props.collapseHeight || this.state.domHeight) : 0,
+            opacity: (this.props.isOpen || !this.props.fade) ? 1 : 0,
             transitionDuration: `${this.props.motion && this.state.isTransitioning ? this.props.duration : 0}ms`,
             ...this.props.style
         }

+ 0 - 64
packages/semi-ui/motions/Rotate.tsx

@@ -1,64 +0,0 @@
-import React, { useEffect, useState, Children, cloneElement, isValidElement } from 'react';
-import { Transition } from '@douyinfe/semi-animation-react';
-import PropTypes from 'prop-types';
-
-const formatStyle = function formatStyle({ rotate = 0 }) {
-    return ({
-        transform: `rotate(${rotate}deg)`,
-    });
-};
-
-export interface TransitionStyle {
-    rotate?: number;
-}
-
-export interface OpenIconTransitionProps {
-    isOpen?: boolean;
-    children?: React.ReactNode;
-    enterDeg?: number;
-    fromDeg?: number;
-    duration?: number;
-}
-
-function OpenIconTransition(props: OpenIconTransitionProps = {}): React.ReactElement {
-    const { children, isOpen, enterDeg = 180, fromDeg = 0, duration = 150 } = props;
-
-    const [immediate, setImmediate] = useState(true);
-
-    useEffect(() => {
-        // eslint-disable-next-line @typescript-eslint/no-implied-eval
-        setImmediate(false);
-    }, [isOpen]);
-
-    return (
-        <Transition
-            immediate={immediate}
-            state={isOpen ? 'enter' : 'leave'}
-            from={{ rotate: fromDeg }}
-            enter={{ rotate: { val: enterDeg, duration, easing: 'cubic-bezier(.62, .05, .36, .95)' } }}
-            leave={{ rotate: { val: fromDeg, duration, easing: 'cubic-bezier(.62, .05, .36, .95)' } }}
-        >
-            {(transitionStyle: TransitionStyle) =>
-                Children.map(children, child => (isValidElement(child) ?
-                    cloneElement(child, {
-                        ...child.props,
-                        style: {
-                            ...child.props.style,
-                            ...formatStyle(transitionStyle),
-                        },
-                    }) :
-                    child))
-            }
-        </Transition>
-    );
-}
-
-OpenIconTransition.propTypes = {
-    isOpen: PropTypes.bool.isRequired,
-    children: PropTypes.any.isRequired,
-    enterDeg: PropTypes.number,
-    fromDeg: PropTypes.number,
-    duration: PropTypes.number,
-};
-
-export default OpenIconTransition;

+ 0 - 59
packages/semi-ui/navigation/OpenIconTransition.tsx

@@ -1,59 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { Transition } from '@douyinfe/semi-animation-react';
-import PropTypes from 'prop-types';
-
-const formatStyle = function formatStyle({ rotate = 0 }) {
-    return {
-        transform: `rotate(${Math.ceil(rotate)}deg)`,
-    };
-};
-
-export interface OpenIconTransitionProps {
-    children?: React.ReactNode | ((transitionArgus?: any) => React.ReactNode);
-    isCollapsed?: boolean;
-    isOpen?: boolean;
-}
-
-function OpenIconTransition(props: OpenIconTransitionProps = {}) {
-    const { children, isOpen } = props;
-
-    const [immediate, setImmediate] = useState(true);
-
-    useEffect(() => {
-        setImmediate(false);
-    }, []);
-
-    return (
-        <Transition
-            immediate={immediate}
-            state={isOpen ? 'enter' : 'leave'}
-            from={{ rotate: 0 }}
-            enter={{ rotate: { val: 180, duration: 200, easing: 'cubic-bezier(.62, .05, .36, .95)' } }}
-            leave={{ rotate: { val: 0, duration: 200, easing: 'cubic-bezier(.62, .05, .36, .95)' } }}
-        >
-            {(transitionStyle: any) => {
-                const formatedStyle = formatStyle(transitionStyle);
-                if (typeof children === 'function') {
-                    return children(formatedStyle);
-                }
-                if (React.isValidElement(children)) {
-                    return React.cloneElement(children, {
-                        // @ts-ignore
-                        style: {
-                            ...(children.props && children.props.style),
-                            ...formatedStyle,
-                        },
-                    });
-                }
-                return children;
-            }}
-        </Transition>
-    );
-}
-
-OpenIconTransition.propTypes = {
-    isOpen: PropTypes.bool.isRequired,
-    children: PropTypes.any.isRequired,
-};
-
-export default OpenIconTransition;

+ 19 - 20
packages/semi-ui/navigation/SubNav.tsx

@@ -9,7 +9,7 @@ import '@douyinfe/semi-foundation/navigation/navigation.scss';
 
 import isNullOrUndefined from '@douyinfe/semi-foundation/utils/isNullOrUndefined';
 import SubNavFoundation, { SubNavAdapter } from '@douyinfe/semi-foundation/navigation/subNavFoundation';
-import { strings, numbers } from '@douyinfe/semi-foundation/navigation/constants';
+import {strings, numbers, cssClasses} from '@douyinfe/semi-foundation/navigation/constants';
 import { IconChevronDown, IconChevronUp, IconChevronRight } from '@douyinfe/semi-icons';
 
 import NavItem from './Item';
@@ -17,9 +17,8 @@ import Dropdown, { DropdownProps } from '../dropdown';
 import NavContext, { NavContextType } from './nav-context';
 
 import { times, get } from 'lodash';
-
-import SubNavTransition from './SubNavTransition';
-import OpenIconTransition from './OpenIconTransition';
+import Collapsible from "../collapsible"
+import CSSAnimation from "../_cssAnimation";
 
 export interface ToggleIcon {
     open?: string;
@@ -195,9 +194,13 @@ export default class SubNav extends BaseComponent<SubNavProps, SubNavState> {
         const isOpen = this.adapter.getIsOpen();
 
         const iconElem = React.isValidElement(icon) ? (withTransition ? (
+            <CSSAnimation animationState={isOpen?"enter":"leave"} startClassName={`${cssClasses.PREFIX}-icon-rotate-${isOpen?"0":"180"}`}>
+                {({animationClassName})=>{
+                    // @ts-ignore
+                    return React.cloneElement(icon, { size: iconSize,className:animationClassName })
+                }}
+            </CSSAnimation>
             // @ts-ignore
-            <OpenIconTransition isOpen={isOpen}>{React.cloneElement(icon, { size: iconSize })}</OpenIconTransition>
-            //@ts-ignore
         ) : React.cloneElement(icon, { size: iconSize })) : null;
 
         return <i key={key} className={className}>{iconElem}</i>;
@@ -282,20 +285,16 @@ export default class SubNav extends BaseComponent<SubNavProps, SubNavState> {
             [`${prefixCls}-sub-popover`]: isCollapsed || isHorizontal,
         });
 
-        const ulWithMotion = (
-            <SubNavTransition motion={subNavMotion} isCollapsed={isCollapsed} maxHeight={maxHeight}>
-                {!isCollapsed && isOpen
-                    ? (transitionStyle: any) => (
-                        <ul
-                            style={{ ...transitionStyle, visibility: isCollapsed ? 'hidden' : 'visible' }}
-                            className={subNavCls}
-                        >
-                            {children}
-                        </ul>
-                    )
-                    : null}
-            </SubNavTransition>
-        );
+        const ulWithMotion = <Collapsible motion={subNavMotion} isOpen={isOpen} keepDOM={false} fade={true}>
+            {
+                !isCollapsed ? <ul
+                    className={subNavCls}
+                >
+                    {children}
+                </ul>: null
+            }
+        </Collapsible>
+
 
         const finalDom = isHorizontal ? null : subNavMotion ? (
             ulWithMotion

+ 0 - 58
packages/semi-ui/navigation/SubNavTransition.tsx

@@ -1,58 +0,0 @@
-import { Transition } from '@douyinfe/semi-animation-react';
-import PropTypes from 'prop-types';
-import React, { useState } from 'react';
-import { Motion } from '@douyinfe/semi-foundation/utils/type';
-
-const ease = 'cubicBezier(.25,.1,.25,1)';
-
-const formatStyle = function formatStyle({ maxHeight, opacity }: { maxHeight: number; opacity: number }) {
-    return {
-        maxHeight,
-        opacity,
-    };
-};
-
-export interface SubNavTransitionProps {
-    children?: React.ReactNode | ((transitionProps?: any) => React.ReactNode);
-    isCollapsed?: boolean;
-    maxHeight?: number;
-    motion?: Motion;
-}
-
-function SubNavTransition(props: SubNavTransitionProps = {}) {
-    const { children, isCollapsed, maxHeight = 999 } = props;
-    // eslint-disable-next-line no-unused-vars
-    const [immediate, setImmediate] = useState(false);
-
-    // useEffect(() => {
-    //     setImmediate(isCollapsed);
-    // }, [isCollapsed]);
-
-    return (
-        <Transition
-            from={{ maxHeight: 0, opacity: 0 }}
-            enter={{
-                maxHeight: { val: maxHeight, easing: 'easeInQuad', duration: 250 },
-                opacity: { val: 1, duration: 200, easing: 'cubic-bezier(0.5, -0.1, 1, 0.4)' },
-            }}
-            leave={{
-                maxHeight: { val: 0, easing: ease, duration: 250 },
-                opacity: {
-                    val: 0,
-                    duration: isCollapsed ? 1 : 200, // Need to be fast and transparent when put away, otherwise there will be jumping
-                    easing: 'cubic-bezier(0.5, -0.1, 1, 0.4)',
-                },
-            }}
-            immediate={immediate}
-        >
-            {typeof children === 'function' ? (transitionStyle: { maxHeight: number; opacity: number }) => children(formatStyle(transitionStyle)) : children}
-        </Transition>
-    );
-}
-
-SubNavTransition.propTypes = {
-    children: PropTypes.any,
-    isCollapsed: PropTypes.bool,
-};
-
-export default SubNavTransition;

+ 1 - 3
packages/semi-ui/navigation/index.tsx

@@ -21,9 +21,7 @@ export type { CollapseButtonProps } from './CollapseButton';
 export type { NavFooterProps } from './Footer';
 export type { NavHeaderProps } from './Header';
 export type { NavItemProps } from './Item';
-export type { OpenIconTransitionProps } from './OpenIconTransition';
 export type { ToggleIcon, SubNavProps } from './SubNav';
-export type { SubNavTransitionProps } from './SubNavTransition';
 export type Mode = 'vertical' | 'horizontal';
 
 export interface OnSelectedData {
@@ -61,7 +59,7 @@ export interface NavProps extends BaseProps {
     prefixCls?: string;
     selectedKeys?: React.ReactText[];
     subNavCloseDelay?: number;
-    subNavMotion?: Motion;
+    subNavMotion?: boolean;
     subNavOpenDelay?: number;
     toggleIconPosition?: string;
     tooltipHideDelay?: number;

+ 7 - 6
packages/semi-ui/table/CustomExpandIcon.tsx

@@ -7,7 +7,7 @@ import { IconChevronRight, IconChevronDown, IconTreeTriangleDown, IconTreeTriang
 import { cssClasses } from '@douyinfe/semi-foundation/table/constants';
 import isEnterPress from '@douyinfe/semi-foundation/utils/isEnterPress';
 
-import Rotate from '../motions/Rotate';
+import CSSAnimation from "../_cssAnimation";
 
 export interface CustomExpandIconProps {
     expanded?: boolean;
@@ -57,11 +57,12 @@ export default function CustomExpandIcon(props: CustomExpandIconProps) {
     );
 
     if (motion) {
-        icon = (
-            <Rotate isOpen={expanded} enterDeg={90}>
-                {icon}
-            </Rotate>
-        );
+        const originIcon = icon;
+        icon = <CSSAnimation animationState={expanded?"enter":"leave"} startClassName={`${cssClasses.PREFIX}-expandedIcon-${expanded?'show':"hide"}`}>
+            {({animationClassName})=>{
+                return React.cloneElement(originIcon,{className:(originIcon.props.className||"")+" "+animationClassName})
+            }}
+        </CSSAnimation>
     }
 
     return (