Skip to content

Commit

Permalink
Remove react-transition-group dependency (#2391)
Browse files Browse the repository at this point in the history
  • Loading branch information
r100-stack authored Jan 28, 2025
1 parent ad6b0ce commit 55cc09e
Show file tree
Hide file tree
Showing 24 changed files with 283 additions and 312 deletions.
5 changes: 5 additions & 0 deletions .changeset/rude-books-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@itwin/itwinui-css': minor
---

Animation related classes are now deprecated: `.iui-enter`, `.iui-enter-active`, `.iui-exit`, `.iui-exit-active`.
7 changes: 7 additions & 0 deletions .changeset/tidy-geckos-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@itwin/itwinui-react': minor
---

Removed dependency on `react-transition-group`. Notable changes in components:
* `useToaster`: Animations have been reworked to directly use the web animations API.
* `Dialog` and `Modal`: Exit animations have been temporarily removed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions apps/react-workshop/src/Toasts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ describe('Toasts', () => {
cy.get('#ladle-root').within(() => {
cy.get('button').first().click();
});

// Wait for entry animation to complete
cy.wait(240);
cy.compareSnapshot(testName);
});
});
Expand Down
31 changes: 0 additions & 31 deletions packages/itwinui-css/src/mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,37 +41,6 @@
}
}

/// Classes for react-transition-group
/// Used for expand/collapse transitions. Needs height/width to be set in JS.
@mixin iui-transition-group {
$transition-rule:
opacity var(--iui-duration-1) ease-out,
width var(--iui-duration-1) ease-out,
height var(--iui-duration-1) ease-out;

&.iui-enter {
opacity: 0;
}

&.iui-enter-active {
opacity: 1;
@media (prefers-reduced-motion: no-preference) {
transition: $transition-rule;
}
}

&.iui-exit {
opacity: 1;
}

&.iui-exit-active {
opacity: 0;
@media (prefers-reduced-motion: no-preference) {
transition: $transition-rule;
}
}
}

@mixin safari-only {
@supports (-apple-pay-button-style: inherit) {
@content;
Expand Down
7 changes: 0 additions & 7 deletions packages/itwinui-css/src/side-navigation/side-navigation.scss
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,6 @@ $iui-side-navigation-icon-margins: calc(1.5 * var(--iui-size-m));
background-color: var(--iui-color-background);
border-inline-end: 1px solid var(--iui-color-border);

@include mixins.iui-transition-group;

&.iui-enter-active,
&.iui-exit-active {
display: flex;
}

&-content {
padding-block: 0 var(--iui-size-s);
padding-inline: var(--iui-size-s);
Expand Down
1 change: 0 additions & 1 deletion packages/itwinui-css/src/table/base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,6 @@
border-inline-end: 1px solid transparent;
border-block-end: 1px solid var(--iui-color-border);
flex-shrink: 0;
@include mixins.iui-transition-group;
}

// #region Selection
Expand Down
4 changes: 1 addition & 3 deletions packages/itwinui-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,7 @@
"@tanstack/react-virtual": "^3.8.2",
"classnames": "^2.3.2",
"jotai": "^2.8.0",
"react-table": "^7.8.0",
"react-transition-group": "^4.4.5"
"react-table": "^7.8.0"
},
"devDependencies": {
"@swc/cli": "^0.5.1",
Expand All @@ -121,7 +120,6 @@
"@types/node": "*",
"@types/react": "*",
"@types/react-dom": "*",
"@types/react-transition-group": "^4.4.10",
"@vitest/coverage-v8": "^1.2.1",
"eslint": "^8",
"eslint-config-prettier": "^8.8.0",
Expand Down
7 changes: 0 additions & 7 deletions packages/itwinui-react/src/core/Dialog/Dialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,6 @@ it('should not stay in the DOM when isOpen=false', () => {

rerender(<Component isOpen={false} />);

// Should be there in the DOM until the exit animation is finished
dialogWrapper = container.querySelector('.iui-dialog-wrapper') as HTMLElement;
expect(dialogWrapper).toBeTruthy();

// Since timeout for the exit animation is 600ms
act(() => vi.advanceTimersByTime(600));

dialogWrapper = container.querySelector('.iui-dialog-wrapper') as HTMLElement;
expect(dialogWrapper).toBeFalsy();
});
61 changes: 29 additions & 32 deletions packages/itwinui-react/src/core/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ import { DialogButtonBar } from './DialogButtonBar.js';
import { DialogMain } from './DialogMain.js';
import { useMergedRefs, Box, Portal } from '../../utils/index.js';
import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
import { Transition } from 'react-transition-group';

type DialogProps = {
/**
* Dialog content.
*/
children: React.ReactNode;
} & Omit<DialogContextProps, 'dialogRootRef'>;
} & DialogContextProps;

const DialogComponent = React.forwardRef((props, ref) => {
const {
Expand All @@ -42,37 +41,35 @@ const DialogComponent = React.forwardRef((props, ref) => {
} = props;

const dialogRootRef = React.useRef<HTMLDivElement>(null);
const mergedRefs = useMergedRefs(ref, dialogRootRef);

return (
<Transition in={isOpen} timeout={{ exit: 600 }} mountOnEnter unmountOnExit>
<DialogContext.Provider
value={{
isOpen,
onClose,
closeOnEsc,
closeOnExternalClick,
isDismissible,
preventDocumentScroll,
trapFocus,
setFocus,
isDraggable,
isResizable,
relativeTo,
dialogRootRef,
placement,
}}
>
<Portal portal={portal}>
<Box
className={cx('iui-dialog-wrapper', className)}
data-iui-relative={relativeTo === 'container'}
ref={useMergedRefs(ref, dialogRootRef)}
{...rest}
/>
</Portal>
</DialogContext.Provider>
</Transition>
);
return isOpen ? (
<DialogContext.Provider
value={{
isOpen,
onClose,
closeOnEsc,
closeOnExternalClick,
isDismissible,
preventDocumentScroll,
trapFocus,
setFocus,
isDraggable,
isResizable,
relativeTo,
placement,
}}
>
<Portal portal={portal}>
<Box
className={cx('iui-dialog-wrapper', className)}
data-iui-relative={relativeTo === 'container'}
ref={mergedRefs}
{...rest}
/>
</Portal>
</DialogContext.Provider>
) : null;
}) as PolymorphicForwardRefComponent<'div', DialogProps>;
if (process.env.NODE_ENV === 'development') {
DialogComponent.displayName = 'Dialog';
Expand Down
7 changes: 5 additions & 2 deletions packages/itwinui-react/src/core/Dialog/DialogBackdrop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { Backdrop } from '../Backdrop/Backdrop.js';
import type { BackdropProps } from '../Backdrop/Backdrop.js';
import { useMergedRefs } from '../../utils/index.js';
import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
import { useDialogContext } from './DialogContext.js';
import type { DialogContextProps } from './DialogContext.js';
import { useDialogContext, type DialogContextProps } from './DialogContext.js';
import { useDialogMainContext } from './DialogMainContext.js';
import cx from 'classnames';

type DialogBackdropProps = BackdropProps &
Expand All @@ -25,6 +25,8 @@ type DialogBackdropProps = BackdropProps &
*/
export const DialogBackdrop = React.forwardRef((props, ref) => {
const dialogContext = useDialogContext();
const dialogMainContext = useDialogMainContext();

const {
isVisible = dialogContext.isOpen,
isDismissible = dialogContext.isDismissible,
Expand All @@ -47,6 +49,7 @@ export const DialogBackdrop = React.forwardRef((props, ref) => {
return;
}
if (isDismissible && closeOnExternalClick && onClose) {
dialogMainContext?.beforeClose();
onClose(event);
}
onMouseDown?.(event);
Expand Down
76 changes: 44 additions & 32 deletions packages/itwinui-react/src/core/Dialog/DialogMain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import {
import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
import { useDialogContext } from './DialogContext.js';
import type { DialogContextProps } from './DialogContext.js';
import { Transition } from 'react-transition-group';
import { DialogDragContext } from './DialogDragContext.js';
import { useDragAndDrop } from '../../utils/hooks/useDragAndDrop.js';
import { DialogMainContext } from './DialogMainContext.js';

export type DialogMainProps = {
/**
Expand Down Expand Up @@ -73,14 +73,16 @@ export const DialogMain = React.forwardRef((props, ref) => {
...rest
} = props;

const [style, setStyle] = React.useState<React.CSSProperties>();
const { dialogRootRef } = dialogContext;

const dialogRef = React.useRef<HTMLDivElement>(null);
const hasBeenResized = React.useRef(false);
const previousFocusedElement = React.useRef<HTMLElement | null>();

const [style, setStyle] = React.useState<React.CSSProperties>();
const hasBeenResized = React.useRef(false);

const originalBodyOverflow = React.useRef('');
React.useEffect(() => {
useLayoutEffect(() => {
if (isOpen) {
originalBodyOverflow.current = document.body.style.overflow;
}
Expand All @@ -107,7 +109,7 @@ export const DialogMain = React.forwardRef((props, ref) => {
return () => {
ownerDocument.body.style.overflow = originalBodyOverflow.current;
};
}, [isOpen, preventDocumentScroll]);
}, [dialogRef, isOpen, preventDocumentScroll]);

const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.altKey) {
Expand All @@ -116,14 +118,15 @@ export const DialogMain = React.forwardRef((props, ref) => {
// Prevents React from resetting its properties
event.persist();
if (isDismissible && closeOnEsc && event.key === 'Escape' && onClose) {
beforeClose();
onClose(event);
}
onKeyDown?.(event);
};

const { onPointerDown, transform } = useDragAndDrop(
dialogRef,
dialogContext.dialogRootRef,
dialogRootRef,
isDraggable,
);
const handlePointerDown = React.useCallback(
Expand All @@ -149,7 +152,7 @@ export const DialogMain = React.forwardRef((props, ref) => {
insetBlockStart: dialogRef.current?.offsetTop,
transform: `translate(${translateX}px,${translateY}px)`,
}));
}, [isDraggable, isOpen]);
}, [dialogRef, isDraggable, isOpen]);

const setResizeStyle = React.useCallback((newStyle: React.CSSProperties) => {
setStyle((oldStyle) => ({
Expand All @@ -158,6 +161,35 @@ export const DialogMain = React.forwardRef((props, ref) => {
}));
}, []);

/** Focuses dialog when opened. */
const onEnter = React.useCallback(() => {
previousFocusedElement.current = dialogRef.current?.ownerDocument
.activeElement as HTMLElement;
if (setFocus) {
dialogRef.current?.focus({ preventScroll: true });
}
}, [dialogRef, previousFocusedElement, setFocus]);

/** Brings back focus to the previously focused element when closed. */
const beforeClose = React.useCallback(() => {
if (
dialogRef.current?.contains(
dialogRef.current?.ownerDocument.activeElement,
)
) {
previousFocusedElement.current?.focus();
}
}, [dialogRef, previousFocusedElement]);

const mountRef = React.useCallback(
(element: HTMLElement | null) => {
if (element) {
onEnter();
}
},
[onEnter],
);

const content = (
<Box
className={cx(
Expand All @@ -171,7 +203,7 @@ export const DialogMain = React.forwardRef((props, ref) => {
className,
)}
role='dialog'
ref={useMergedRefs(dialogRef, ref)}
ref={useMergedRefs(dialogRef, mountRef, ref)}
onKeyDown={handleKeyDown}
tabIndex={-1}
data-iui-placement={placement}
Expand All @@ -187,7 +219,7 @@ export const DialogMain = React.forwardRef((props, ref) => {
{isResizable && (
<Resizer
elementRef={dialogRef}
containerRef={dialogContext.dialogRootRef}
containerRef={dialogRootRef}
onResizeStart={() => {
if (!hasBeenResized.current) {
hasBeenResized.current = true;
Expand All @@ -204,34 +236,14 @@ export const DialogMain = React.forwardRef((props, ref) => {
);

return (
<Transition
in={isOpen}
appear={true}
timeout={{ exit: 600 }}
// Focuses dialog when opened
onEntered={() => {
previousFocusedElement.current = dialogRef.current?.ownerDocument
.activeElement as HTMLElement;
setFocus && dialogRef.current?.focus({ preventScroll: true });
}}
// Brings back focus to the previously focused element when closed
onExit={() => {
if (
dialogRef.current?.contains(
dialogRef.current?.ownerDocument.activeElement,
)
) {
previousFocusedElement.current?.focus();
}
}}
unmountOnExit={true}
nodeRef={dialogRef}
<DialogMainContext.Provider
value={React.useMemo(() => ({ beforeClose }), [beforeClose])}
>
<DialogDragContext.Provider value={{ onPointerDown: handlePointerDown }}>
{trapFocus && <FocusTrap>{content}</FocusTrap>}
{!trapFocus && content}
</DialogDragContext.Provider>
</Transition>
</DialogMainContext.Provider>
);
}) as PolymorphicForwardRefComponent<'div', DialogMainProps>;
if (process.env.NODE_ENV === 'development') {
Expand Down
Loading

0 comments on commit 55cc09e

Please sign in to comment.