@mediamonks/react-kit 1.0.0-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/_hooks/useStorybookLog.d.ts +21 -0
- package/dist/_hooks/useStorybookLog.js +29 -0
- package/dist/_utils/childrenAreEqual.d.ts +2 -0
- package/dist/_utils/childrenAreEqual.js +16 -0
- package/dist/_utils/getId.d.ts +1 -0
- package/dist/_utils/getId.js +4 -0
- package/dist/_utils/isNonNullableRecord/isNonNullableRecord.d.ts +9 -0
- package/dist/_utils/isNonNullableRecord/isNonNullableRecord.js +6 -0
- package/dist/_utils/trimEnd/trimEnd.d.ts +4 -0
- package/dist/_utils/trimEnd/trimEnd.js +9 -0
- package/dist/components/AutoFill/AutoFill.d.ts +13 -0
- package/dist/components/AutoFill/AutoFill.js +33 -0
- package/dist/gsap/animations.d.ts +11 -0
- package/dist/gsap/animations.js +35 -0
- package/dist/gsap/components/SplitTextWrapper/SplitTextWrapper.d.ts +24 -0
- package/dist/gsap/components/SplitTextWrapper/SplitTextWrapper.js +28 -0
- package/dist/gsap/hooks/useAnimation/useAnimation.d.ts +6 -0
- package/dist/gsap/hooks/useAnimation/useAnimation.js +19 -0
- package/dist/gsap/hooks/useExposeAnimation/useExposeAnimation.d.ts +6 -0
- package/dist/gsap/hooks/useExposeAnimation/useExposeAnimation.js +28 -0
- package/dist/gsap/hooks/useExposedAnimation/useExposedAnimation.d.ts +5 -0
- package/dist/gsap/hooks/useExposedAnimation/useExposedAnimation.js +13 -0
- package/dist/gsap/hooks/useExposedAnimations/useExposedAnimations.d.ts +5 -0
- package/dist/gsap/hooks/useExposedAnimations/useExposedAnimations.js +32 -0
- package/dist/gsap/hooks/useFlip/useFlip.d.ts +3 -0
- package/dist/gsap/hooks/useFlip/useFlip.js +18 -0
- package/dist/gsap/hooks/useScrollAnimation/useScrollAnimation.d.ts +9 -0
- package/dist/gsap/hooks/useScrollAnimation/useScrollAnimation.js +26 -0
- package/dist/gsap/utils/getAnimation/getAnimation.d.ts +4 -0
- package/dist/gsap/utils/getAnimation/getAnimation.js +7 -0
- package/dist/hocs/ensuredForwardRef/ensuredForwardRef.d.ts +11 -0
- package/dist/hocs/ensuredForwardRef/ensuredForwardRef.js +31 -0
- package/dist/hooks/useClientSideValue/useClientSideValue.d.ts +11 -0
- package/dist/hooks/useClientSideValue/useClientSideValue.js +19 -0
- package/dist/hooks/useEventListener/useEventListener.d.ts +2 -0
- package/dist/hooks/useEventListener/useEventListener.js +17 -0
- package/dist/hooks/useForceRerender/useForceRerender.d.ts +11 -0
- package/dist/hooks/useForceRerender/useForceRerender.js +18 -0
- package/dist/hooks/useHasFocus/useHasFocus.d.ts +9 -0
- package/dist/hooks/useHasFocus/useHasFocus.js +18 -0
- package/dist/hooks/useIntersectionObserver/useIntersectionObserver.d.ts +13 -0
- package/dist/hooks/useIntersectionObserver/useIntersectionObserver.js +38 -0
- package/dist/hooks/useInterval/useInterval.d.ts +8 -0
- package/dist/hooks/useInterval/useInterval.js +25 -0
- package/dist/hooks/useMediaDuration/useMediaDuration.d.ts +5 -0
- package/dist/hooks/useMediaDuration/useMediaDuration.js +20 -0
- package/dist/hooks/useMediaQuery/useMediaQuery.d.ts +33 -0
- package/dist/hooks/useMediaQuery/useMediaQuery.js +32 -0
- package/dist/hooks/useMutationObserver/useMutationObserver.d.ts +16 -0
- package/dist/hooks/useMutationObserver/useMutationObserver.js +36 -0
- package/dist/hooks/useRafCallback/useRafCallback.d.ts +5 -0
- package/dist/hooks/useRafCallback/useRafCallback.js +21 -0
- package/dist/hooks/useRefValue/useRefValue.d.ts +11 -0
- package/dist/hooks/useRefValue/useRefValue.js +15 -0
- package/dist/hooks/useRefs/useRefs.d.ts +5 -0
- package/dist/hooks/useRefs/useRefs.js +17 -0
- package/dist/hooks/useRefs/useRefs.types.d.ts +15 -0
- package/dist/hooks/useRefs/useRefs.types.js +1 -0
- package/dist/hooks/useRefs/utils/assertAndUnwrapRefs/assertAndUnwrapRefs.d.ts +14 -0
- package/dist/hooks/useRefs/utils/assertAndUnwrapRefs/assertAndUnwrapRefs.js +19 -0
- package/dist/hooks/useRefs/utils/unwrapRefs/unwrapRefs.d.ts +6 -0
- package/dist/hooks/useRefs/utils/unwrapRefs/unwrapRefs.js +12 -0
- package/dist/hooks/useRefs/utils/unwrapRefs/unwrapRefs.types.d.ts +7 -0
- package/dist/hooks/useRefs/utils/unwrapRefs/unwrapRefs.types.js +1 -0
- package/dist/hooks/useRefs/utils/validateAndUnwrapRefs/validateAndUnwrapRefs.d.ts +12 -0
- package/dist/hooks/useRefs/utils/validateAndUnwrapRefs/validateAndUnwrapRefs.js +17 -0
- package/dist/hooks/useResizeObserver/useResizeObserver.d.ts +9 -0
- package/dist/hooks/useResizeObserver/useResizeObserver.js +22 -0
- package/dist/hooks/useStaticValue/useStaticValue.d.ts +6 -0
- package/dist/hooks/useStaticValue/useStaticValue.js +11 -0
- package/dist/hooks/useToggle/useToggle.d.ts +16 -0
- package/dist/hooks/useToggle/useToggle.js +23 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +46 -0
- package/dist/lifecycle/components/CrossFlow/CrossFlow.d.ts +3 -0
- package/dist/lifecycle/components/CrossFlow/CrossFlow.js +55 -0
- package/dist/lifecycle/components/TransitionPresence/TransitionPresence.context.d.ts +4 -0
- package/dist/lifecycle/components/TransitionPresence/TransitionPresence.context.js +2 -0
- package/dist/lifecycle/components/TransitionPresence/TransitionPresence.d.ts +13 -0
- package/dist/lifecycle/components/TransitionPresence/TransitionPresence.js +61 -0
- package/dist/lifecycle/hooks/useBeforeMount/useBeforeMount.d.ts +11 -0
- package/dist/lifecycle/hooks/useBeforeMount/useBeforeMount.js +21 -0
- package/dist/lifecycle/hooks/useBeforeUnmount/useBeforeUnmount.d.ts +11 -0
- package/dist/lifecycle/hooks/useBeforeUnmount/useBeforeUnmount.js +39 -0
- package/dist/lifecycle/hooks/useIsMounted/useIsMounted.d.ts +13 -0
- package/dist/lifecycle/hooks/useIsMounted/useIsMounted.js +34 -0
- package/dist/lifecycle/hooks/useIsMountedState/useIsMountedState.d.ts +9 -0
- package/dist/lifecycle/hooks/useIsMountedState/useIsMountedState.js +21 -0
- package/dist/lifecycle/hooks/useMount/useMount.d.ts +12 -0
- package/dist/lifecycle/hooks/useMount/useMount.js +16 -0
- package/dist/lifecycle/hooks/useUnmount/useUnmount.d.ts +12 -0
- package/dist/lifecycle/hooks/useUnmount/useUnmount.js +19 -0
- package/dist/nextjs/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.d.ts +18 -0
- package/dist/nextjs/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.js +18 -0
- package/dist/utils/arrayRef/arrayRef.d.ts +8 -0
- package/dist/utils/arrayRef/arrayRef.js +35 -0
- package/dist/utils/createTimeout/createTimeout.d.ts +5 -0
- package/dist/utils/createTimeout/createTimeout.js +9 -0
- package/dist/utils/isRefObject/isRefObject.d.ts +5 -0
- package/dist/utils/isRefObject/isRefObject.js +6 -0
- package/dist/utils/unref/unref.d.ts +13 -0
- package/dist/utils/unref/unref.js +7 -0
- package/package.json +131 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Media.Monks
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/@mediamonks/react-kit)
|
|
2
|
+
[](https://www.npmjs.com/package/@mediamonks/react-kit)
|
|
3
|
+
|
|
4
|
+
# @mediamonks/react-kit
|
|
5
|
+
|
|
6
|
+
Collection of commonly used React hooks.
|
|
7
|
+
|
|
8
|
+
## Getting started
|
|
9
|
+
|
|
10
|
+
### Installing
|
|
11
|
+
|
|
12
|
+
Add `@mediamonks/react-kit` to your project:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm i @mediamonks/react-kit
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Example
|
|
19
|
+
|
|
20
|
+
Use a hook inside a component:
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { useToggle } from '@mediamonks/react-kit';
|
|
24
|
+
|
|
25
|
+
function DemoComponent() {
|
|
26
|
+
const [state, toggle] = useToggle(false);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div>
|
|
30
|
+
<div>{state} </div>
|
|
31
|
+
<button onClick={() => toggle()}>Toggle</button>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Docs
|
|
38
|
+
|
|
39
|
+
[https://mediamonks.github.io/react-kit/](https://mediamonks.github.io/react-kit/)
|
|
40
|
+
|
|
41
|
+
## Development
|
|
42
|
+
|
|
43
|
+
The information below should help you develop new hooks in this library.
|
|
44
|
+
|
|
45
|
+
Run `npm run test -- --watch` to run all unit tests in watch mode.
|
|
46
|
+
|
|
47
|
+
Run `npm run storybook` to preview your stories and documentation.
|
|
48
|
+
|
|
49
|
+
### Folder Structure
|
|
50
|
+
|
|
51
|
+
`useHookName`
|
|
52
|
+
|
|
53
|
+
- `useHookName.ts` – The Hook itself
|
|
54
|
+
- `useHookName.stories.tsx` – To showcase the hook with a working UI, also used for dom testing
|
|
55
|
+
- `useHookName.stories.mdx` – Documentation about the hook
|
|
56
|
+
- `useHookName.test.tsx` – Unit tests for the hook
|
|
57
|
+
|
|
58
|
+
### Steps for adding a new Hook:
|
|
59
|
+
|
|
60
|
+
Run the `plop` script and enter your hook name starting with `use`.
|
|
61
|
+
|
|
62
|
+
```shell
|
|
63
|
+
npm run plop
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Which will execute the following steps, where you need to fill in the content.
|
|
67
|
+
|
|
68
|
+
- Create a new folder and a new `ts` file with the hook
|
|
69
|
+
- Use the `use` prefix for the name of the hook
|
|
70
|
+
- Use named exports to export the hook
|
|
71
|
+
- Enter JSDoc for description and parameters
|
|
72
|
+
- Re-export the hook in the `index.ts`
|
|
73
|
+
- Add a markdown file documenting the hook
|
|
74
|
+
- General description
|
|
75
|
+
- Reference for types, parameters, return type
|
|
76
|
+
- Simple and extended use cases
|
|
77
|
+
- Add a story file to test out the hook
|
|
78
|
+
- Add an instructions banner at the top of the story
|
|
79
|
+
- Create a type for the StoryArgs that match the template, so it can be used when rendering the
|
|
80
|
+
Story inside tests.
|
|
81
|
+
- Add unit tests for the hook
|
|
82
|
+
|
|
83
|
+
## Writing Unit test
|
|
84
|
+
|
|
85
|
+
Hooks can be tested using the `renderHook` function that now exists in `@testing-library/react`.
|
|
86
|
+
|
|
87
|
+
At the time of writing, this method is undocumented. It can be used as follows:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { renderHook } from '@testing-library/react';
|
|
91
|
+
|
|
92
|
+
// init the hook
|
|
93
|
+
const { result, rerender, unmount } = renderHook(useToggle, {
|
|
94
|
+
// values passed to your hook
|
|
95
|
+
initialProps: { foo: 'bar' },
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// inspect the response of the hook
|
|
99
|
+
console.log(result.current);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Run Component Lifecycle
|
|
103
|
+
|
|
104
|
+
To interact with your hook, you must use the `act` function.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { act, renderHook } from '@testing-library/react';
|
|
108
|
+
|
|
109
|
+
// init the hook
|
|
110
|
+
const { result, rerender, unmount } = renderHook(useToggle, {
|
|
111
|
+
// values passed to your hook
|
|
112
|
+
initialProps: { foo: 'bar' },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// inspect the response of the hook
|
|
116
|
+
console.log(result.current);
|
|
117
|
+
|
|
118
|
+
act(() => {
|
|
119
|
+
// interact with your hook
|
|
120
|
+
result.current[1]();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// inspect the updated value of the hook
|
|
124
|
+
console.log(result.current);
|
|
125
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* This hook is an internal utility function to visually "log" messages inside your story UI.
|
|
4
|
+
*
|
|
5
|
+
* ```
|
|
6
|
+
* const { getLog, log } = useStorybookLog();
|
|
7
|
+
*
|
|
8
|
+
* return (
|
|
9
|
+
* <div>
|
|
10
|
+
* <div>{getLog()}</div>
|
|
11
|
+
* <button onClick={() => log('clicked')}>Click me</button>
|
|
12
|
+
* </div>
|
|
13
|
+
* )
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @param logRef The Ref that will hold the logs.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useStorybookLog(): {
|
|
19
|
+
getLog(): ReactNode;
|
|
20
|
+
log(message: string): void;
|
|
21
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useState } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* This hook is an internal utility function to visually "log" messages inside your story UI.
|
|
5
|
+
*
|
|
6
|
+
* ```
|
|
7
|
+
* const { getLog, log } = useStorybookLog();
|
|
8
|
+
*
|
|
9
|
+
* return (
|
|
10
|
+
* <div>
|
|
11
|
+
* <div>{getLog()}</div>
|
|
12
|
+
* <button onClick={() => log('clicked')}>Click me</button>
|
|
13
|
+
* </div>
|
|
14
|
+
* )
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @param logRef The Ref that will hold the logs.
|
|
18
|
+
*/
|
|
19
|
+
export function useStorybookLog() {
|
|
20
|
+
const [logs, setLogs] = useState([]);
|
|
21
|
+
const log = useCallback((message) => {
|
|
22
|
+
setLogs((oldLogs) => [...oldLogs, message]);
|
|
23
|
+
setTimeout(() => {
|
|
24
|
+
setLogs([]);
|
|
25
|
+
}, 2000);
|
|
26
|
+
}, []);
|
|
27
|
+
const getLog = useCallback(() => (_jsx(_Fragment, { children: logs.map((message) => (_jsx("div", { className: "alert alert-dismissible alert-info", children: message }, message))) })), [logs]);
|
|
28
|
+
return { getLog, log };
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function childrenAreEqual(previousChildren, nextChildren) {
|
|
2
|
+
if (previousChildren === nextChildren) {
|
|
3
|
+
return true;
|
|
4
|
+
}
|
|
5
|
+
// React reconciler will create a new instance when children type changes
|
|
6
|
+
if ((previousChildren !== null && 'type' in previousChildren && previousChildren.type) !==
|
|
7
|
+
(nextChildren !== null && 'type' in nextChildren && nextChildren.type)) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
// React reconciler will create a new instance when children key changes
|
|
11
|
+
if ((previousChildren !== null && 'key' in previousChildren && previousChildren.key) !==
|
|
12
|
+
(nextChildren !== null && 'key' in nextChildren && nextChildren.key)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getId(): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type UnknownRecord = Record<string | number | symbol, unknown>;
|
|
2
|
+
export type NonNullableRecord<T extends UnknownRecord> = {
|
|
3
|
+
[K in keyof T]: NonNullable<T[K]>;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Check if any value in record is nullable
|
|
7
|
+
*/
|
|
8
|
+
export declare function isNonNullableRecord<T extends UnknownRecord>(record: T): record is NonNullableRecord<T>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ReactElement, type RefCallback } from 'react';
|
|
2
|
+
type AutoFillChildrenProps = {
|
|
3
|
+
ref: RefCallback<unknown>;
|
|
4
|
+
};
|
|
5
|
+
type AutoFillProps = {
|
|
6
|
+
children: ReactElement<AutoFillChildrenProps> | ReadonlyArray<ReactElement<AutoFillChildrenProps>>;
|
|
7
|
+
axis?: 'x' | 'y';
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Repeats children to fill the parent element in given axis.
|
|
11
|
+
*/
|
|
12
|
+
export declare function AutoFill({ children, axis }: AutoFillProps): ReactElement;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children, Fragment, cloneElement, isValidElement, useCallback, useEffect, useRef, useState, } from 'react';
|
|
3
|
+
import { useEventListener } from '../../hooks/useEventListener/useEventListener.js';
|
|
4
|
+
import { useRafCallback } from '../../index.js';
|
|
5
|
+
import { arrayRef } from '../../utils/arrayRef/arrayRef.js';
|
|
6
|
+
/**
|
|
7
|
+
* Repeats children to fill the parent element in given axis.
|
|
8
|
+
*/
|
|
9
|
+
export function AutoFill({ children, axis = 'x' }) {
|
|
10
|
+
const childrenRef = useRef([]);
|
|
11
|
+
const [repeatCount, setRepeatCount] = useState(0);
|
|
12
|
+
const updateRepeatCount = useCallback(() => {
|
|
13
|
+
const lastChild = childrenRef.current?.at(-1);
|
|
14
|
+
if (!(lastChild instanceof HTMLElement) || !lastChild.parentElement) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const { offsetTop, offsetLeft, clientWidth, clientHeight, parentElement } = lastChild;
|
|
18
|
+
const { clientWidth: parentClientWidth, clientHeight: parentClientHeight } = parentElement;
|
|
19
|
+
if (axis === 'x') {
|
|
20
|
+
setRepeatCount(Math.ceil(parentClientWidth / (offsetLeft + clientWidth)));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
setRepeatCount(Math.ceil(parentClientHeight / (offsetTop + clientHeight)));
|
|
24
|
+
}
|
|
25
|
+
}, [axis]);
|
|
26
|
+
useEffect(updateRepeatCount, [updateRepeatCount, children]);
|
|
27
|
+
useEventListener(globalThis.window, 'resize', useRafCallback(updateRepeatCount));
|
|
28
|
+
return (_jsxs(_Fragment, { children: [Children.map(children, (child, index) => isValidElement(child) &&
|
|
29
|
+
cloneElement(child, {
|
|
30
|
+
key: child.key,
|
|
31
|
+
ref: arrayRef(childrenRef, index),
|
|
32
|
+
})), Array.from({ length: repeatCount }, (_, index) => (_jsx(Fragment, { children: children }, index)))] }));
|
|
33
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare class AnimationsMap extends Map {
|
|
2
|
+
private readonly callbacks;
|
|
3
|
+
private updateQueued;
|
|
4
|
+
set(key: unknown, value: gsap.core.Animation): this;
|
|
5
|
+
listen(callback: () => void): () => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Global map of animations that can be accessed by reference
|
|
9
|
+
*/
|
|
10
|
+
export declare const animations: AnimationsMap;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class AnimationsMap extends Map {
|
|
2
|
+
callbacks = new Set();
|
|
3
|
+
updateQueued = false;
|
|
4
|
+
set(key, value) {
|
|
5
|
+
if (key === null || key === undefined) {
|
|
6
|
+
throw new Error('Cannot set animation with null or undefined key. Make sure the ref you pass correctly has its value set.');
|
|
7
|
+
}
|
|
8
|
+
// skip if value is the same, mostly for optimisation
|
|
9
|
+
if (value === this.get(key)) {
|
|
10
|
+
return this;
|
|
11
|
+
}
|
|
12
|
+
const result = super.set(key, value);
|
|
13
|
+
if (this.updateQueued) {
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
this.updateQueued = true;
|
|
17
|
+
queueMicrotask(() => {
|
|
18
|
+
for (const callback of this.callbacks) {
|
|
19
|
+
callback();
|
|
20
|
+
}
|
|
21
|
+
this.updateQueued = false;
|
|
22
|
+
});
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
listen(callback) {
|
|
26
|
+
this.callbacks.add(callback);
|
|
27
|
+
return () => {
|
|
28
|
+
this.callbacks.delete(callback);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Global map of animations that can be accessed by reference
|
|
34
|
+
*/
|
|
35
|
+
export const animations = new AnimationsMap();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import SplitText from 'gsap/SplitText';
|
|
2
|
+
import { type ComponentProps, type ReactElement, type RefAttributes } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Allowed as prop values
|
|
5
|
+
*/
|
|
6
|
+
type KnownTarget = Exclude<keyof JSX.IntrinsicElements, symbol | object>;
|
|
7
|
+
type SplitTextWrapperProps<T extends KnownTarget> = {
|
|
8
|
+
/**
|
|
9
|
+
* The SplitText variables
|
|
10
|
+
* @link https://greensock.com/docs/v3/Plugins/SplitText
|
|
11
|
+
*/
|
|
12
|
+
variables?: SplitText.Vars;
|
|
13
|
+
/**
|
|
14
|
+
* The element type to render, the default is `div`
|
|
15
|
+
*/
|
|
16
|
+
as?: T;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Polymorphic component type, necessary to get all the attributes/props for the
|
|
20
|
+
* as prop component
|
|
21
|
+
*/
|
|
22
|
+
type SplitTextWrapperComponent = <T extends KnownTarget = 'div'>(props: SplitTextWrapperProps<T> & Omit<ComponentProps<T>, keyof SplitTextWrapperProps<T> | 'ref'> & RefAttributes<SplitText | null>) => ReactElement;
|
|
23
|
+
export declare const SplitTextWrapper: SplitTextWrapperComponent;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import gsap from 'gsap';
|
|
3
|
+
import SplitText from 'gsap/SplitText';
|
|
4
|
+
import { renderToString } from 'react-dom/server';
|
|
5
|
+
import { ensuredForwardRef } from '../../../hocs/ensuredForwardRef/ensuredForwardRef.js';
|
|
6
|
+
if (typeof window !== 'undefined') {
|
|
7
|
+
gsap.registerPlugin(SplitText);
|
|
8
|
+
}
|
|
9
|
+
// @ts-expect-error polymorphic type is not compatible with ensuredForwardRef function factory
|
|
10
|
+
export const SplitTextWrapper = ensuredForwardRef(({ variables = {}, as, children, ...props }, ref) => {
|
|
11
|
+
/**
|
|
12
|
+
* Not using useCallback on purpose so that a new SplitText instance is
|
|
13
|
+
* created whenever this component rerenders the children
|
|
14
|
+
*/
|
|
15
|
+
const onRef = async (element) => {
|
|
16
|
+
if (ref.current && 'isSplit' in ref.current && ref.current.isSplit) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
ref.current = new SplitText(element, variables);
|
|
20
|
+
};
|
|
21
|
+
const Component = (as ?? 'div');
|
|
22
|
+
return (_jsx(Component, { ...props, dangerouslySetInnerHTML: props.dangerouslySetInnerHTML ?? {
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention, react/jsx-no-useless-fragment
|
|
24
|
+
__html: renderToString(_jsx(_Fragment, { children: children })),
|
|
25
|
+
},
|
|
26
|
+
// eslint-disable-next-line react/jsx-no-bind
|
|
27
|
+
ref: onRef }));
|
|
28
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Create gsap animation via a callback, animation is killed when component is
|
|
4
|
+
* unmounted or when a new callback function is provided.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useAnimation<T extends gsap.core.Animation | undefined>(callback: () => T, dependencies: ReadonlyArray<unknown>): RefObject<T | undefined>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Create gsap animation via a callback, animation is killed when component is
|
|
4
|
+
* unmounted or when a new callback function is provided.
|
|
5
|
+
*/
|
|
6
|
+
export function useAnimation(callback, dependencies) {
|
|
7
|
+
const animation = useRef();
|
|
8
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps, no-underscore-dangle
|
|
9
|
+
const _callback = useCallback(callback, dependencies);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
12
|
+
const _animation = _callback();
|
|
13
|
+
animation.current = _animation;
|
|
14
|
+
return () => {
|
|
15
|
+
_animation?.kill();
|
|
16
|
+
};
|
|
17
|
+
}, [_callback]);
|
|
18
|
+
return animation;
|
|
19
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import { type Unreffable } from '../../../utils/unref/unref.js';
|
|
3
|
+
/**
|
|
4
|
+
* Hook to store animation using a reference in global animations map
|
|
5
|
+
*/
|
|
6
|
+
export declare function useExposeAnimation(animation: RefObject<gsap.core.Animation | undefined>, reference: Unreffable<unknown>): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { unref } from '../../../utils/unref/unref.js';
|
|
3
|
+
import { animations } from '../../animations.js';
|
|
4
|
+
/**
|
|
5
|
+
* Hook to store animation using a reference in global animations map
|
|
6
|
+
*/
|
|
7
|
+
export function useExposeAnimation(animation, reference) {
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
10
|
+
const _reference = unref(reference);
|
|
11
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
12
|
+
const _animation = animation.current;
|
|
13
|
+
if (_animation && _reference) {
|
|
14
|
+
animations.set(_reference, _animation);
|
|
15
|
+
}
|
|
16
|
+
return () => {
|
|
17
|
+
animations.delete(_reference);
|
|
18
|
+
};
|
|
19
|
+
// TODO: We currently rely on the Component where this hook is used,
|
|
20
|
+
// and we know that animation will get a new ref.current assigned
|
|
21
|
+
// as part of useAnimation, if that has dependencies.
|
|
22
|
+
// If that updates (due to a re-render), we should also update
|
|
23
|
+
// the animation in the global map.
|
|
24
|
+
// This feels a bit flaky, but I can't think of a better way to do this currently.
|
|
25
|
+
// We should probably have these hooks more integrated with each other.
|
|
26
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
27
|
+
}, [animation, animation.current, reference]);
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { unref } from '../../../index.js';
|
|
3
|
+
import { animations } from '../../animations.js';
|
|
4
|
+
/**
|
|
5
|
+
* Hook to get animation from global animations map using given reference
|
|
6
|
+
*/
|
|
7
|
+
export function useExposedAnimation(target) {
|
|
8
|
+
const [animation, setAnimation] = useState();
|
|
9
|
+
useEffect(() => animations.listen(() => {
|
|
10
|
+
setAnimation(animations.get(unref(target)));
|
|
11
|
+
}), [target]);
|
|
12
|
+
return animation;
|
|
13
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { unref } from '../../../index.js';
|
|
3
|
+
import { animations } from '../../animations.js';
|
|
4
|
+
/**
|
|
5
|
+
* Hook to get animation from global animations map using given reference
|
|
6
|
+
*/
|
|
7
|
+
export function useExposedAnimations(target) {
|
|
8
|
+
const [exposedAnimations, setExposedAnimations] = useState([]);
|
|
9
|
+
useEffect(() => animations.listen(() => {
|
|
10
|
+
const array = unref(target);
|
|
11
|
+
if (array) {
|
|
12
|
+
const newAnimations = array.map((ref) => animations.get(ref));
|
|
13
|
+
// this should only be done when the refs have been updated, otherwise we're returning
|
|
14
|
+
// a new array ref with the same values, which will cause a re-render
|
|
15
|
+
setExposedAnimations((currentAnimations) => areArraysEqual(newAnimations, currentAnimations) ? currentAnimations : newAnimations);
|
|
16
|
+
}
|
|
17
|
+
}),
|
|
18
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
19
|
+
[target]);
|
|
20
|
+
return exposedAnimations;
|
|
21
|
+
}
|
|
22
|
+
function areArraysEqual(a, b) {
|
|
23
|
+
if (a.length !== b.length) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
for (const [index, element] of a.entries()) {
|
|
27
|
+
if (element !== b[index]) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import gsap from 'gsap';
|
|
2
|
+
import Flip from 'gsap/Flip';
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
import { unref } from '../../../utils/unref/unref.js';
|
|
5
|
+
gsap.registerPlugin(Flip);
|
|
6
|
+
export function useFlip(ref, flipStateVariables = {}) {
|
|
7
|
+
const flipStateRef = useRef();
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
9
|
+
if (globalThis.window !== undefined) {
|
|
10
|
+
flipStateRef.current = Flip.getState(unref(ref));
|
|
11
|
+
}
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (flipStateRef.current) {
|
|
14
|
+
Flip.from(flipStateRef.current, flipStateVariables);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
return flipStateRef;
|
|
18
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Hook to create animation that make use of ScrollTrigger, ScrollTrigger is refreshed
|
|
4
|
+
* when the global animations map is updated.
|
|
5
|
+
*
|
|
6
|
+
* Note: do not scrub a React component's root because React won't know what element to
|
|
7
|
+
* unmount when the component with a pinned animation is removed from the vDOM.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useScrollAnimation<T extends gsap.core.Animation>(callback: () => T | undefined, dependencies: ReadonlyArray<unknown>): RefObject<T | undefined>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import gsap from 'gsap';
|
|
2
|
+
import ScrollTrigger from 'gsap/ScrollTrigger';
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
import { animations } from '../../animations.js';
|
|
5
|
+
import { useAnimation } from '../useAnimation/useAnimation.js';
|
|
6
|
+
import { useExposeAnimation } from '../useExposeAnimation/useExposeAnimation.js';
|
|
7
|
+
if (typeof window !== 'undefined') {
|
|
8
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Hook to create animation that make use of ScrollTrigger, ScrollTrigger is refreshed
|
|
12
|
+
* when the global animations map is updated.
|
|
13
|
+
*
|
|
14
|
+
* Note: do not scrub a React component's root because React won't know what element to
|
|
15
|
+
* unmount when the component with a pinned animation is removed from the vDOM.
|
|
16
|
+
*/
|
|
17
|
+
export function useScrollAnimation(callback, dependencies) {
|
|
18
|
+
const animation = useAnimation(callback, dependencies);
|
|
19
|
+
// Expose animation so that we can leverage the listener from the global animations map
|
|
20
|
+
const ref = useRef(Symbol('useScrollAnimation'));
|
|
21
|
+
useExposeAnimation(animation, ref);
|
|
22
|
+
useEffect(() => animations.listen(() => {
|
|
23
|
+
ScrollTrigger.refresh();
|
|
24
|
+
}), []);
|
|
25
|
+
return animation;
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ForwardRefExoticComponent, type MutableRefObject, type PropsWithoutRef, type ReactElement, type RefAttributes } from 'react';
|
|
2
|
+
export interface EnsuredForwardRefRenderFunction<T, P = Record<string | number | symbol, unknown>> {
|
|
3
|
+
(props: P, ref: MutableRefObject<T | null>): ReactElement | null;
|
|
4
|
+
displayName?: string | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* defaultProps are not supported on render functions
|
|
7
|
+
*/
|
|
8
|
+
defaultProps?: never;
|
|
9
|
+
propTypes?: never;
|
|
10
|
+
}
|
|
11
|
+
export declare function ensuredForwardRef<T, P = Record<string | number | symbol, unknown>>(Component: EnsuredForwardRefRenderFunction<T, P>): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;
|