diff --git a/packages/react-transition-presence/src/CrossFlow/CrossFlow.mdx b/packages/react-transition-presence/src/CrossFlow/CrossFlow.mdx new file mode 100644 index 0000000..a437012 --- /dev/null +++ b/packages/react-transition-presence/src/CrossFlow/CrossFlow.mdx @@ -0,0 +1,57 @@ +import { Meta, Canvas, Controls } from '@storybook/blocks'; +import * as stories from './CrossFlow.stories'; + + + +# CrossFlow + +`` is a component that allows you to defer the unmount lifecycle of a component. + +The callback function used in `useBeforeUnmount` in components that are rendered in the context of +`` will be called just before a component is unmounted. The promises that are created +using the callback are awaited before unmounting a component. In the CrossFlow component new +children are immediately rendered. + +The most common use-case for `` is animations, but it's not limited to this use case. + +## Props + +### Children + +`` accepts a single child, use a `Fragment` when you want to render multiple components +on the root level. New children are rendered when the component type for the children (`

` to +`

`) change, or when the key of the children changes (`

` to `null`). + +```tsx +// Transition between two instances + + {myBoolean ? : } + + +// Transition between component types (new instances are automatically created) + + {myBoolean ? : } + +``` + +### onChildrenMounted + +The `onChildrenMounted` is called when the new children are mounted. + +```tsx +onChildrenMounted?: () => void; + + console.log('onChildrenMounted')}> + ... + +``` + +## Demo + +### Basic + + + +### Basic with Fragments + + diff --git a/packages/react-transition-presence/src/CrossFlow/CrossFlow.stories.tsx b/packages/react-transition-presence/src/CrossFlow/CrossFlow.stories.tsx new file mode 100644 index 0000000..d577ac9 --- /dev/null +++ b/packages/react-transition-presence/src/CrossFlow/CrossFlow.stories.tsx @@ -0,0 +1,144 @@ +/* eslint-disable react/no-multi-comp, react/jsx-no-literals,no-console */ +// eslint-disable-next-line import/no-extraneous-dependencies +import { useAnimation } from '@mediamonks/react-animation'; +import type { Meta } from '@storybook/react'; +import gsap from 'gsap'; +import { Fragment, useEffect, useRef, useState, type ReactElement } from 'react'; +import { useBeforeUnmount } from '../useBeforeUnmount/useBeforeUnmount.js'; +import { CrossFlow } from './CrossFlow.js'; + +export default { + title: 'components/CrossFlow', + argTypes: { + children: { + description: 'ReactElement | null', + type: 'string', + }, + onChildrenMounted: { + description: '() => void | undefined', + type: 'string', + }, + }, +} satisfies Meta; + +type ChildProps = { + background: string; + duration?: number; + delayIn?: number; + onClick?(): void; +}; + +function Child({ background, onClick, duration = 1, delayIn = 0 }: ChildProps): ReactElement { + const ref = useRef(null); + + // show when mounted/unmounted + useEffect(() => { + console.log('mounted', background); + + return () => { + console.log('unmounted', background); + }; + }, [background]); + + useAnimation(() => { + console.log('animate-in', background); + return gsap.fromTo(ref.current, { opacity: 0 }, { opacity: 1, duration, delay: delayIn }); + }, []); + + // show visible animation during "before unmount" lifecycle + useBeforeUnmount(async (abortSignal) => { + console.log('animate-out', background); + + const animation = gsap.fromTo(ref.current, { opacity: 1 }, { opacity: 0, duration }); + + abortSignal.addEventListener('abort', () => { + animation.pause(0); + }); + + return animation; + }); + + return ( +