Skip to content

Commit

Permalink
Merge pull request #208 from mediamonks/feature/#207-allow-any-target…
Browse files Browse the repository at this point in the history
…-in-expose-animation-hooks

#207 Allow any target in expose animation hooks
  • Loading branch information
leroykorterink authored Oct 13, 2023
2 parents fe71d46 + 8cfda40 commit 3b47d95
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 141 deletions.
1 change: 0 additions & 1 deletion packages/react-animation/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export * from './SplitTextWrapper/SplitTextWrapper.js';
export * from './getAnimation/getAnimation.js';
export * from './useAnimation/useAnimation.js';
export * from './useAnimationRef/useAnimationRef.js';
export * from './useExposeAnimation/useExposeAnimation.js';
export * from './useExposedAnimation/useExposedAnimation.js';
export * from './useExposedAnimations/useExposedAnimations.js';
Expand Down
39 changes: 0 additions & 39 deletions packages/react-animation/src/useAnimationRef/useAnimationRef.mdx

This file was deleted.

This file was deleted.

10 changes: 0 additions & 10 deletions packages/react-animation/src/useAnimationRef/useAnimationRef.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,19 @@ const UseExposeAnimationForwardRef = ensuredForwardRef<HTMLDivElement, unknown>(
);
```

## Using a string value as reference
## Using a value as reference

```tsx
const value = Symbol("myRef")

const UseExposeAnimationAnyValue = (): ReactElement => {
const ref = useRef('myRef');
const ref = useRef(value);

const animation = useAnimation(() => gsap.from({ value: 0 }, { value: 1 }), []);
useExposeAnimation(animation, ref);

useEffect(() => {
console.log(getAnimation('myRef'));
console.log(getAnimation(value));
}, []);

...
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { unref, type Unreffable } from '@mediamonks/react-hooks';
import { useEffect, type RefObject } from 'react';
import { animations } from '../animations.js';

Expand All @@ -6,15 +7,15 @@ import { animations } from '../animations.js';
*/
export function useExposeAnimation(
animation: RefObject<gsap.core.Animation | undefined>,
reference: RefObject<unknown>,
reference: Unreffable<unknown>,
): void {
useEffect(() => {
// eslint-disable-next-line no-underscore-dangle
const _reference = reference.current;
const _reference = unref(reference);
// eslint-disable-next-line no-underscore-dangle
const _animation = animation.current;

if (_animation) {
if (_animation && _reference) {
animations.set(_reference, _animation);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,5 @@ function Parent(): ReactElement {
return <Child animationRef={animationRef} />;
}
```

<Canvas of={stories.OptionalMultipleChildComponentAnimations} />
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import { ensuredForwardRef, useEventListener } from '@mediamonks/react-hooks';
import { ensuredForwardRef, useEventListener, useStaticValue } from '@mediamonks/react-hooks';
import type { Meta, StoryObj, StoryFn } from '@storybook/react';
import gsap from 'gsap';
import { useRef, type ReactElement, type RefObject } from 'react';
import { useRef, type ReactElement } from 'react';
import { useAnimation } from '../useAnimation/useAnimation.js';
import { useAnimationRef } from '../useAnimationRef/useAnimationRef.js';
import { useExposeAnimation } from '../useExposeAnimation/useExposeAnimation.js';
import { useExposedAnimation } from './useExposedAnimation.js';

export default {
const meta = {
title: 'hooks/useExposedAnimation',
};
} satisfies Meta;

export default meta;

type Story = StoryObj<typeof meta>;

type ChildProps = {
rotateAnimationRef?: RefObject<symbol>;
scaleAnimationRef?: RefObject<symbol>;
rotateAnimationReference?: symbol;
scaleAnimationReference?: symbol;
};

const Child = ensuredForwardRef<HTMLDivElement, ChildProps>(
({ rotateAnimationRef, scaleAnimationRef }, ref): ReactElement => {
(
{ rotateAnimationReference: rotateAnimationRef, scaleAnimationReference: scaleAnimationRef },
ref,
): ReactElement => {
const animation = useAnimation(
() =>
gsap.from(
Expand All @@ -31,32 +38,57 @@ const Child = ensuredForwardRef<HTMLDivElement, ChildProps>(
);

const rotateAnimation = useAnimation(
() => gsap.timeline({ paused: true }).fromTo(ref.current, { rotate: 0 }, { rotate: 360 }),
() =>
gsap
.timeline({ paused: true })
.to(ref.current, {
x: 20,
y: 20,
duration: 0.4,
ease: 'power2.inOut',
})
.to(ref.current, {
x: -20,
y: -20,
duration: 0.4,
ease: 'power2.out',
})
.to(ref.current, {
x: 0,
y: 0,
duration: 0.6,
ease: 'power2.out',
}),
[],
);

const scaleAnimation = useAnimation(
() =>
gsap
.timeline({ paused: true })
.fromTo(ref.current, { scale: 1 }, { scale: 0.5 })
.to(ref.current, { scale: 1 }),
.fromTo(ref.current, { scale: 1 }, { scale: 0.75, ease: 'power2.out' })
.to(ref.current, { scale: 1, ease: 'power2.out' }),
[],
);

useExposeAnimation(animation, ref);
useExposeAnimation(rotateAnimation, useAnimationRef(rotateAnimationRef));
useExposeAnimation(scaleAnimation, useAnimationRef(scaleAnimationRef));
useExposeAnimation(rotateAnimation, rotateAnimationRef);
useExposeAnimation(scaleAnimation, scaleAnimationRef);

return (
<div ref={ref} style={{ inlineSize: 'fit-content' }}>
Check the console to see the result
</div>
<div
ref={ref}
style={{
inlineSize: 200,
blockSize: 200,
background: `linear-gradient(to bottom right, red, blue)`,
}}
/>
);
},
);

export const UseExposedAnimation = {
export const UseExposedAnimation: Story = {
render(): ReactElement {
const ref = useRef<HTMLDivElement | null>(null);
const animation = useExposedAnimation(ref);
Expand All @@ -69,14 +101,22 @@ export const UseExposedAnimation = {
};

export const MultipleChildComponentAnimations = {
decorators: [
// eslint-disable-next-line @typescript-eslint/naming-convention
(Story: StoryFn): ReactElement => (
<div style={{ padding: 20 }}>
<Story />
</div>
),
],
render(): ReactElement {
const ref = useRef<HTMLDivElement | null>(null);

const rotateAnimationRef = useAnimationRef();
const rotateAnimation = useExposedAnimation(rotateAnimationRef);
const rotateAnimationReference = useStaticValue(() => Symbol('rotateAnimation'));
const scaleAnimationReference = useStaticValue(() => Symbol('scaleAnimation'));

const scaleAnimationRef = useAnimationRef();
const scaleAnimation = useExposedAnimation(scaleAnimationRef);
const rotateAnimation = useExposedAnimation(rotateAnimationReference);
const scaleAnimation = useExposedAnimation(scaleAnimationReference);

useEventListener(globalThis.document, 'keydown', (event) => {
if (event instanceof KeyboardEvent) {
Expand Down Expand Up @@ -105,10 +145,56 @@ export const MultipleChildComponentAnimations = {
<>
<Child
ref={ref}
rotateAnimationRef={rotateAnimationRef}
scaleAnimationRef={scaleAnimationRef}
rotateAnimationReference={rotateAnimationReference}
scaleAnimationReference={scaleAnimationReference}
/>
<p>Press `z` to rotate, press `x` to scale</p>
<p style={{ marginBlockStart: 40, marginBlockEnd: 0 }}>
Press `z` to wiggle, press `x` to scale
</p>
</>
);
},
};

export const OptionalMultipleChildComponentAnimations: Story = {
decorators: [
// eslint-disable-next-line @typescript-eslint/naming-convention
(Story: StoryFn): ReactElement => (
<div style={{ padding: 20 }}>
<Story />
</div>
),
],
render(): ReactElement {
const ref = useRef<HTMLDivElement | null>(null);
const rotateAnimationReference = useStaticValue(() => Symbol('rotateAnimation'));
const rotateAnimation = useExposedAnimation(rotateAnimationReference);

useEventListener(globalThis.document, 'keydown', (event) => {
if (event instanceof KeyboardEvent) {
switch (event.key) {
case 'z': {
rotateAnimation?.play(0);
break;
}
default: {
break;
}
}
}
});

const animation = useExposedAnimation(ref);

// eslint-disable-next-line no-console
console.log('useExposedAnimation', animation);

return (
<>
<Child ref={ref} rotateAnimationReference={rotateAnimationReference} />
<p style={{ marginBlockStart: 40, marginBlockEnd: 0 }}>
Press `z` to wiggle, the scale animation is not enabled
</p>
</>
);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { useEffect, useState, type RefObject } from 'react';
import { unref, type Unreffable } from '@mediamonks/react-hooks';
import { useEffect, useState } from 'react';
import { animations } from '../animations.js';

/**
* Hook to get animation from global animations map using given reference
*/
export function useExposedAnimation<T extends gsap.core.Animation>(
ref: RefObject<unknown>,
target: Unreffable<unknown>,
): T | undefined {
const [animation, setAnimation] = useState<T | undefined>();

useEffect(
() =>
animations.listen(() => {
setAnimation(animations.get(ref.current));
setAnimation(animations.get(unref(target)));
}),
[ref],
[target],
);

return animation;
Expand Down
Loading

0 comments on commit 3b47d95

Please sign in to comment.