diff --git a/packages/react-animation/src/index.ts b/packages/react-animation/src/index.ts index 9a3e5e2..ce8fcb3 100644 --- a/packages/react-animation/src/index.ts +++ b/packages/react-animation/src/index.ts @@ -5,3 +5,4 @@ export * from './useExposeAnimation/useExposeAnimation.js'; export * from './useExposedAnimation/useExposedAnimation.js'; export * from './useExposedAnimations/useExposedAnimations.js'; export * from './useScrollAnimation/useScrollAnimation.js'; +export * from './useFlip/useFlip.js'; diff --git a/packages/react-animation/src/useFlip/useFlip.mdx b/packages/react-animation/src/useFlip/useFlip.mdx new file mode 100644 index 0000000..5f2a6d0 --- /dev/null +++ b/packages/react-animation/src/useFlip/useFlip.mdx @@ -0,0 +1,46 @@ +import { Meta, Canvas } from '@storybook/blocks'; +import * as stories from './useFlip.stories'; + + + +# useFlip + +Use to flip a components state from one value to another. + +```tsx +function MyComponent(): ReactElement { + const [isFlipped, toggle] = useToggle(false); + + const divRef = useRef(null); + useFlip(divRef, flipOptions); + + useEventListener(globalThis.document, 'click', () => { + toggle(); + }); + + return ( +
+ ); +} +``` + +### Demo + + diff --git a/packages/react-animation/src/useFlip/useFlip.stories.tsx b/packages/react-animation/src/useFlip/useFlip.stories.tsx new file mode 100644 index 0000000..351b97c --- /dev/null +++ b/packages/react-animation/src/useFlip/useFlip.stories.tsx @@ -0,0 +1,63 @@ +/* eslint-disable react/jsx-no-literals */ +import { useEventListener, useToggle } from '@mediamonks/react-hooks'; +import type { Meta, StoryObj } from '@storybook/react'; +import { useRef, type CSSProperties, type ReactElement } from 'react'; +import { useFlip } from './useFlip.js'; + +const meta = { + title: 'hooks/useFlip', +} as Meta; + +export default meta; + +type Story = StoryObj; + +const flipOptions = { + ease: 'power2.inOut', +} satisfies Flip.FromToVars; + +const styles = { + backgroundColor: 'royalblue', +} satisfies CSSProperties; + +export const Default: Story = { + render(): ReactElement { + const [isFlipped, toggle] = useToggle(false); + const div1Ref = useRef(null); + const div2Ref = useRef(null); + + useFlip(div1Ref, flipOptions); + useFlip(div2Ref, flipOptions); + + useEventListener(globalThis.document, 'click', () => { + toggle(); + }); + + return ( + <> +

+ Click to rerender, the box will fill the right side of the screen using position absolute. +

+
+

The element renders inline

+ + ); + }, +}; diff --git a/packages/react-animation/src/useFlip/useFlip.ts b/packages/react-animation/src/useFlip/useFlip.ts new file mode 100644 index 0000000..0400221 --- /dev/null +++ b/packages/react-animation/src/useFlip/useFlip.ts @@ -0,0 +1,26 @@ +import { unref, type Unreffable } from '@mediamonks/react-hooks'; +import gsap from 'gsap'; +import Flip from 'gsap/Flip'; +import { useEffect, useRef, type MutableRefObject } from 'react'; + +gsap.registerPlugin(Flip); + +export function useFlip( + ref: Unreffable, + flipStateVariables: Flip.FromToVars = {}, +): MutableRefObject { + const flipStateRef = useRef(); + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (globalThis.window !== undefined) { + flipStateRef.current = Flip.getState(unref(ref)); + } + + useEffect(() => { + if (flipStateRef.current) { + Flip.from(flipStateRef.current, flipStateVariables); + } + }); + + return flipStateRef; +}