Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/replace-react-transition-group.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tiny-design/react": minor
---

Replace react-transition-group with a custom useTransition hook for CSS-driven animations. This removes the unmaintained dependency and prepares the library for React 19 compatibility.
2 changes: 0 additions & 2 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
"@tiny-design/icons": "workspace:*",
"@tiny-design/tokens": "workspace:*",
"classnames": "^2.3.1",
"react-transition-group": "^4.4.2",
"tslib": "^2.3.1"
},
"devDependencies": {
Expand All @@ -75,7 +74,6 @@
"@types/jest": "^29.0.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/react-transition-group": "^4.4.4",
"jest": "^29.0.0",
"jest-environment-jsdom": "^29.0.0",
"autoprefixer": "^10.4.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`<Alert /> should match the snapshot 1`] = `
<DocumentFragment>
<div
class="ty-alert ty-alert_info ty-undefined-appear ty-undefined-appear-active"
class="ty-alert ty-alert_info ty-undefined-enter"
role="alert"
>
<div>
Expand Down
7 changes: 4 additions & 3 deletions packages/react/src/drawer/drawer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useContext, useEffect, useId, useRef, useState } from 'react';
import classNames from 'classnames';
import { CSSTransition } from 'react-transition-group';
import Transition from '../transition';
import Overlay from '../overlay';
import { ConfigContext } from '../config-provider/config-context';
import { getPrefixCls } from '../_utils/general';
Expand Down Expand Up @@ -91,11 +91,12 @@ const Drawer = React.forwardRef<HTMLDivElement, DrawerProps>((props, ref) => {
}}
style={maskStyle}>
<div ref={ref} className={cls} style={{ ...style, ...sty }}>
<CSSTransition
<Transition
appear={true}
nodeRef={nodeRef}
in={drawerVisible}
timeout={0}
unmountOnExit={false}
classNames={`${prefixCls}__content_move`}>
<div
ref={nodeRef}
Expand All @@ -113,7 +114,7 @@ const Drawer = React.forwardRef<HTMLDivElement, DrawerProps>((props, ref) => {
<div className={`${prefixCls}__body`}>{children}</div>
{footer && <div className={`${prefixCls}__footer`}>{footer}</div>}
</div>
</CSSTransition>
</Transition>
</div>
</Overlay>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`<Message /> should match the snapshot 1`] = `
<DocumentFragment>
<div
class="ty-message ty-message_fade-slide-appear ty-message_fade-slide-appear-active"
class="ty-message ty-message_fade-slide-enter"
role="alert"
>
<svg
Expand Down
6 changes: 3 additions & 3 deletions packages/react/src/message/message.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState, useContext } from 'react';
import classNames from 'classnames';
import { CSSTransition } from 'react-transition-group';
import Transition from '../transition';
import { ConfigContext } from '../config-provider/config-context';
import { getPrefixCls } from '../_utils/general';
import {
Expand Down Expand Up @@ -72,13 +72,13 @@ const Message = (props: MessageProps): JSX.Element => {
}, [duration, willUnmount]);

return (
<CSSTransition nodeRef={ref} in={visible} appear={true} timeout={0} classNames={`${prefixCls}_fade-slide`}>
<Transition nodeRef={ref} in={visible} appear={true} timeout={0} unmountOnExit={false} classNames={`${prefixCls}_fade-slide`}>
<div role="alert" className={cls} style={style} ref={ref}>
{renderIcon()}
<span className={`${prefixCls}__content`}>{content}</span>
{extra && <div className={`${prefixCls}__extra`}>{extra}</div>}
</div>
</CSSTransition>
</Transition>
);
};

Expand Down
7 changes: 4 additions & 3 deletions packages/react/src/modal/modal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useContext, useEffect, useId, useRef, useState } from 'react';
import classNames from 'classnames';
import { CSSTransition } from 'react-transition-group';
import Transition from '../transition';
import Overlay from '../overlay';
import Button from '../button/button';
import Flex from '../flex/flex';
Expand Down Expand Up @@ -132,11 +132,12 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>((props, ref) => {
style={maskStyle}>
<div ref={ref} className={cls} style={{ top }}>
<div style={{ width, ...style }}>
<CSSTransition
<Transition
appear={true}
nodeRef={nodeRef}
in={modalVisible}
classNames={`${prefixCls}__content_${animation}`}
unmountOnExit={false}
timeout={0}>
<div
ref={nodeRef}
Expand All @@ -160,7 +161,7 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>((props, ref) => {
</div>
{renderFooter()}
</div>
</CSSTransition>
</Transition>
</div>
</div>
</Overlay>
Expand Down
6 changes: 3 additions & 3 deletions packages/react/src/overlay/overlay.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useContext, useEffect, useRef } from 'react';
import classNames from 'classnames';
import Portal from '../portal';
import { CSSTransition } from 'react-transition-group';
import Transition from '../transition';
import { ConfigContext } from '../config-provider/config-context';
import { getPrefixCls } from '../_utils/general';
import { OverlayProps } from './types';
Expand Down Expand Up @@ -47,7 +47,7 @@ const Overlay = (props: OverlayProps): JSX.Element => {

return (
<Portal>
<CSSTransition
<Transition
appear={true}
nodeRef={nodeRef}
onEnter={onEnter}
Expand All @@ -62,7 +62,7 @@ const Overlay = (props: OverlayProps): JSX.Element => {
<div ref={nodeRef} tabIndex={-1} className={cls} onClick={clickCallback} style={{ zIndex, ...style }}>
{children}
</div>
</CSSTransition>
</Transition>
</Portal>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`<Transition /> should match the snapshot 1`] = `
<DocumentFragment>
<div
class="ty-zoom-top-appear ty-zoom-top-appear-active"
class="ty-zoom-top-enter"
>
Content
</div>
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/transition/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Transition from './transition';

export type { AnimationName, TransitionProps } from './transition';
export { default as useTransition } from './use-transition';
export type { TransitionState, UseTransitionOptions, UseTransitionResult } from './use-transition';
export default Transition;
103 changes: 86 additions & 17 deletions packages/react/src/transition/transition.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { CSSTransition } from 'react-transition-group';
import { CSSTransitionProps } from 'react-transition-group/CSSTransition';
import useTransition, { TransitionState } from './use-transition';

export type AnimationName =
| 'zoom-center-top'
Expand All @@ -23,42 +22,112 @@ export type AnimationName =
| 'slide-down';

export type TransitionProps = {
in?: boolean;
timeout?: number | { enter: number; exit: number };
appear?: boolean;
unmountOnExit?: boolean;
mountOnEnter?: boolean;

/** Animation prefix */
prefix?: string;

/** Preset animation name */
animation?: AnimationName;

/** Custom class name base (overrides prefix + animation) */
classNames?: string;

/** Prevent the transition conflict with the inner component */
wrapper?: boolean;

nodeRef?: React.RefObject<HTMLElement | null>;

onEnter?: () => void;
onEntering?: () => void;
onEntered?: () => void;
onExit?: () => void;
onExiting?: () => void;
onExited?: () => void;

children?: React.ReactNode;
} & Partial<CSSTransitionProps<HTMLElement>>;
};

function getTransitionClasses(base: string, state: TransitionState): string {
switch (state) {
case 'enter':
return `${base}-enter`;
case 'entering':
return `${base}-enter ${base}-enter-active`;
case 'entered':
return `${base}-enter-done`;
case 'exit':
return `${base}-exit`;
case 'exiting':
return `${base}-exit ${base}-exit-active`;
case 'exited':
return `${base}-exit-done`;
default:
return '';
}
}

const Transition = (props: TransitionProps): React.ReactElement => {
const Transition = (props: TransitionProps): React.ReactElement | null => {
const {
in: inProp = false,
timeout = 300,
unmountOnExit = true,
mountOnEnter,
appear = true,
prefix = 'ty',
animation,
classNames,
classNames: classNamesProp,
nodeRef,
children,
wrapper,
...otherProps
onEnter,
onEntering,
onEntered,
onExit,
onExiting,
onExited,
} = props;

return (
<CSSTransition
{...(otherProps as CSSTransitionProps<HTMLElement>)}
timeout={timeout}
appear={appear}
unmountOnExit={unmountOnExit}
nodeRef={nodeRef}
classNames={classNames ? classNames : `${prefix}-${animation}`}>
{wrapper ? <div>{children}</div> : (children as React.ReactElement)}
</CSSTransition>
);
const { state, shouldMount } = useTransition({
in: inProp,
timeout,
appear,
unmountOnExit,
mountOnEnter,
onEnter,
onEntering,
onEntered,
onExit,
onExiting,
onExited,
nodeRef,
});

if (!shouldMount) {
return null;
}

const base = classNamesProp ? classNamesProp : `${prefix}-${animation}`;
const transitionClasses = getTransitionClasses(base, state);

const child = wrapper ? <div>{children}</div> : children;

if (React.isValidElement(child)) {
const existingClassName = (child.props as { className?: string }).className || '';
const mergedClassName = existingClassName
? `${existingClassName} ${transitionClasses}`.trim()
: transitionClasses;

return React.cloneElement(child as React.ReactElement<{ className?: string }>, {
className: mergedClassName || undefined,
});
}

return child as React.ReactElement;
};

Transition.displayName = 'Transition';
Expand Down
Loading
Loading