diff --git a/plop-templates/hook/{{camelCase hookName}}.mdx.hbs b/plop-templates/hook/{{camelCase hookName}}.mdx.hbs index c442f74..248a7fd 100644 --- a/plop-templates/hook/{{camelCase hookName}}.mdx.hbs +++ b/plop-templates/hook/{{camelCase hookName}}.mdx.hbs @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # {{camelCase hookName}} diff --git a/plop-templates/hook/{{camelCase hookName}}.stories.tsx.hbs b/plop-templates/hook/{{camelCase hookName}}.stories.tsx.hbs index ecd2445..9ba7548 100644 --- a/plop-templates/hook/{{camelCase hookName}}.stories.tsx.hbs +++ b/plop-templates/hook/{{camelCase hookName}}.stories.tsx.hbs @@ -1,10 +1,12 @@ -import type { StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import type { ReactElement } from 'react'; import { {{camelCase hookName}} } from './{{camelCase hookName}}.js'; -export default { - title: 'hooks/{{camelCase hookName}}', -}; +const meta = { + title: 'Hooks / {{camelCase hookName}}', +} satisfies Meta; + +type Story = StoryObj; function DemoComponent(): ReactElement { {{camelCase hookName}}(); @@ -28,7 +30,7 @@ function DemoComponent(): ReactElement { ); } -export const Demo: StoryObj = { +export const Demo: Story = { render() { return ; } diff --git a/src/_utils/childrenAreEqual.ts b/src/_utils/childrenAreEqual.ts new file mode 100644 index 0000000..3ccc305 --- /dev/null +++ b/src/_utils/childrenAreEqual.ts @@ -0,0 +1,28 @@ +import { type ReactElement, type ReactFragment } from 'react'; + +export function childrenAreEqual( + previousChildren: ReactElement | ReactFragment | null, + nextChildren: ReactElement | ReactFragment | null, +): boolean { + if (previousChildren === nextChildren) { + return true; + } + + // React reconciler will create a new instance when children type changes + if ( + (previousChildren !== null && 'type' in previousChildren && previousChildren.type) !== + (nextChildren !== null && 'type' in nextChildren && nextChildren.type) + ) { + return false; + } + + // React reconciler will create a new instance when children key changes + if ( + (previousChildren !== null && 'key' in previousChildren && previousChildren.key) !== + (nextChildren !== null && 'key' in nextChildren && nextChildren.key) + ) { + return false; + } + + return true; +} diff --git a/src/_utils/getId.ts b/src/_utils/getId.ts new file mode 100644 index 0000000..e54ba36 --- /dev/null +++ b/src/_utils/getId.ts @@ -0,0 +1,5 @@ +let id = 0; + +export function getId(): string { + return (id++ % Number.MAX_SAFE_INTEGER).toString(); +} diff --git a/src/components/AutoFill/AutoFill.mdx b/src/components/AutoFill/AutoFill.mdx index 1a1700e..c607b23 100644 --- a/src/components/AutoFill/AutoFill.mdx +++ b/src/components/AutoFill/AutoFill.mdx @@ -1,7 +1,7 @@ import { Canvas, Meta } from '@storybook/blocks'; import * as stories from './AutoFill.stories'; - + # AutoFill diff --git a/src/components/AutoFill/AutoFill.stories.tsx b/src/components/AutoFill/AutoFill.stories.tsx index 8ed983a..636d925 100644 --- a/src/components/AutoFill/AutoFill.stories.tsx +++ b/src/components/AutoFill/AutoFill.stories.tsx @@ -1,10 +1,9 @@ -/* eslint-disable react/jsx-no-literals, react/no-multi-comp */ +/* eslint-disable react/jsx-no-literals */ import { type Meta, type StoryObj } from '@storybook/react'; -import type { ReactElement } from 'react'; import { AutoFill } from './AutoFill.js'; const meta = { - title: 'components/AutoFill', + title: 'Components / AutoFill', component: AutoFill, } satisfies Meta; @@ -12,8 +11,8 @@ type Story = StoryObj; export default meta; -export const Horizontal = { - render(): ReactElement { +export const Horizontal: Story = { + render() { return (
, }, -} satisfies Story; +}; -export const Vertical = { - render(): ReactElement { +export const Vertical: Story = { + render() { return (
, }, -} satisfies Story; +}; diff --git a/src/hocs/ensuredForwardRef/ensuredForwardRef.mdx b/src/hocs/ensuredForwardRef/ensuredForwardRef.mdx index 740d0ff..673a60b 100644 --- a/src/hocs/ensuredForwardRef/ensuredForwardRef.mdx +++ b/src/hocs/ensuredForwardRef/ensuredForwardRef.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # ensuredForwardRef diff --git a/src/hooks/useClientSideValue/useClientSideValue.mdx b/src/hooks/useClientSideValue/useClientSideValue.mdx index ec6f7b1..c70bf4b 100644 --- a/src/hooks/useClientSideValue/useClientSideValue.mdx +++ b/src/hooks/useClientSideValue/useClientSideValue.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # useClientSideValue diff --git a/src/hooks/useClientSideValue/useClientSideValue.stories.tsx b/src/hooks/useClientSideValue/useClientSideValue.stories.tsx index e5ce0e9..af0767e 100644 --- a/src/hooks/useClientSideValue/useClientSideValue.stories.tsx +++ b/src/hooks/useClientSideValue/useClientSideValue.stories.tsx @@ -1,11 +1,15 @@ /* eslint-disable react-hooks/rules-of-hooks */ /* eslint-disable react/jsx-no-literals */ -import type { StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import { useClientSideValue } from './useClientSideValue.js'; -export default { - title: 'hooks/useClientSideValue', -}; +const meta = { + title: 'Hooks / useClientSideValue', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; const note = (
@@ -17,7 +21,7 @@ const note = (
); -export const Demo: StoryObj = { +export const Demo: Story = { render() { const value = useClientSideValue(Date.now, 0); @@ -33,7 +37,7 @@ export const Demo: StoryObj = { }, }; -export const Nullable: StoryObj = { +export const Nullable: Story = { render() { const value = useClientSideValue(Date.now, null); diff --git a/src/hooks/useClientSideValue/useClientSideValue.ts b/src/hooks/useClientSideValue/useClientSideValue.ts index c8d6399..c1a2f4a 100644 --- a/src/hooks/useClientSideValue/useClientSideValue.ts +++ b/src/hooks/useClientSideValue/useClientSideValue.ts @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { useMount } from '../useMount/useMount.js'; +import { useMount } from '../../lifecycle/hooks/useMount/useMount.js'; /** * Hook that returns the value returned by a callback function that is only called on client-side. diff --git a/src/hooks/useEventListener/useEventListener.mdx b/src/hooks/useEventListener/useEventListener.mdx index b97f73b..6d4e0d1 100644 --- a/src/hooks/useEventListener/useEventListener.mdx +++ b/src/hooks/useEventListener/useEventListener.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # useEventListener diff --git a/src/hooks/useEventListener/useEventListener.stories.tsx b/src/hooks/useEventListener/useEventListener.stories.tsx index 4e847bd..2b0af7b 100644 --- a/src/hooks/useEventListener/useEventListener.stories.tsx +++ b/src/hooks/useEventListener/useEventListener.stories.tsx @@ -1,11 +1,15 @@ /* eslint-disable react/jsx-no-literals */ -import type { StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import { useState, type ReactElement } from 'react'; import { useEventListener } from './useEventListener.js'; -export default { - title: 'hooks/useEventListener', -}; +const meta = { + title: 'Hooks / useEventListener', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; function DemoComponent(): ReactElement { const [text, setText] = useState>([]); @@ -40,7 +44,7 @@ function DemoComponent(): ReactElement { ); } -export const Demo: StoryObj = { +export const Demo: Story = { render() { return ; }, diff --git a/src/hooks/useForceRerender/useForceRerender.mdx b/src/hooks/useForceRerender/useForceRerender.mdx index 6d5b6e0..3dd0cd2 100644 --- a/src/hooks/useForceRerender/useForceRerender.mdx +++ b/src/hooks/useForceRerender/useForceRerender.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # useForceRerender diff --git a/src/hooks/useForceRerender/useForceRerender.stories.tsx b/src/hooks/useForceRerender/useForceRerender.stories.tsx index 1e7d16d..39f33d5 100644 --- a/src/hooks/useForceRerender/useForceRerender.stories.tsx +++ b/src/hooks/useForceRerender/useForceRerender.stories.tsx @@ -1,10 +1,14 @@ /* eslint-disable react/jsx-no-literals,react/jsx-handler-names */ -import type { StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import { useForceRerender } from './useForceRerender.js'; -export default { - title: 'hooks/lifecycle/useForceRerender', -}; +const meta = { + title: 'Hooks / useForceRerender', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; function DemoComponent(): JSX.Element { const forceRerender = useForceRerender(); @@ -28,7 +32,7 @@ function DemoComponent(): JSX.Element { ); } -export const Demo: StoryObj = { +export const Demo: Story = { render() { return ; }, diff --git a/src/hooks/useHasFocus/useHasFocus.mdx b/src/hooks/useHasFocus/useHasFocus.mdx index 42de9d6..27578d2 100644 --- a/src/hooks/useHasFocus/useHasFocus.mdx +++ b/src/hooks/useHasFocus/useHasFocus.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # useHasFocus diff --git a/src/hooks/useHasFocus/useHasFocus.stories.tsx b/src/hooks/useHasFocus/useHasFocus.stories.tsx index 0705cb1..eb404fc 100644 --- a/src/hooks/useHasFocus/useHasFocus.stories.tsx +++ b/src/hooks/useHasFocus/useHasFocus.stories.tsx @@ -1,11 +1,15 @@ /* eslint-disable react/jsx-no-literals */ -import type { StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import { useEffect, useRef, useState, type ReactElement } from 'react'; import { useHasFocus } from './useHasFocus.js'; -export default { - title: 'hooks/useHasFocus', -}; +const meta = { + title: 'Hooks / useHasFocus', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; function DemoComponent(): ReactElement { const ref = useRef(null); @@ -37,7 +41,7 @@ function DemoComponent(): ReactElement { ); } -export const Demo: StoryObj = { +export const Demo: Story = { render() { return ; }, diff --git a/src/hooks/useIntersectionObserver/useIntersectionObserver.mdx b/src/hooks/useIntersectionObserver/useIntersectionObserver.mdx index c4ee78e..9aec9d1 100644 --- a/src/hooks/useIntersectionObserver/useIntersectionObserver.mdx +++ b/src/hooks/useIntersectionObserver/useIntersectionObserver.mdx @@ -1,7 +1,7 @@ import { Meta, Canvas } from '@storybook/blocks'; import * as stories from './useIntersectionObserver.stories'; - + # useIntersectionObserver diff --git a/src/hooks/useIntersectionObserver/useIntersectionObserver.stories.tsx b/src/hooks/useIntersectionObserver/useIntersectionObserver.stories.tsx index cc5d21a..57c09db 100644 --- a/src/hooks/useIntersectionObserver/useIntersectionObserver.stories.tsx +++ b/src/hooks/useIntersectionObserver/useIntersectionObserver.stories.tsx @@ -7,7 +7,7 @@ import { arrayRef } from '../../index.js'; import { useIntersectionObserver } from './useIntersectionObserver.js'; const meta = { - title: 'hooks/useIntersectionObserver', + title: 'Hooks / useIntersectionObserver', } satisfies Meta; export default meta; diff --git a/src/hooks/useInterval/useInterval.mdx b/src/hooks/useInterval/useInterval.mdx index fb5a15e..6fd11ab 100644 --- a/src/hooks/useInterval/useInterval.mdx +++ b/src/hooks/useInterval/useInterval.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # `useInterval` diff --git a/src/hooks/useInterval/useInterval.stories.tsx b/src/hooks/useInterval/useInterval.stories.tsx index 66f1b44..27754e9 100644 --- a/src/hooks/useInterval/useInterval.stories.tsx +++ b/src/hooks/useInterval/useInterval.stories.tsx @@ -4,7 +4,7 @@ import { useRef, useState, type ReactElement } from 'react'; import { useInterval } from './useInterval.js'; const meta = { - title: 'hooks/useInterval', + title: 'Hooks / useInterval', } satisfies Meta; type Story = StoryObj; diff --git a/src/hooks/useMediaDuration/useMediaDuration.stories.mdx b/src/hooks/useMediaDuration/useMediaDuration.stories.mdx index cc7d5e8..35ff4c9 100644 --- a/src/hooks/useMediaDuration/useMediaDuration.stories.mdx +++ b/src/hooks/useMediaDuration/useMediaDuration.stories.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # useMediaDuration diff --git a/src/hooks/useMediaDuration/useMediaDuration.stories.tsx b/src/hooks/useMediaDuration/useMediaDuration.stories.tsx index 5b07a88..a3a3f34 100644 --- a/src/hooks/useMediaDuration/useMediaDuration.stories.tsx +++ b/src/hooks/useMediaDuration/useMediaDuration.stories.tsx @@ -1,13 +1,17 @@ /* eslint-disable react/jsx-no-literals */ -import type { StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import type { ReactElement } from 'react'; import { useRefs } from '../useRefs/useRefs.js'; import type { MutableRefs } from '../useRefs/useRefs.types.js'; import { useMediaDuration } from './useMediaDuration.js'; -export default { - title: 'hooks/useMediaDuration', -}; +const meta = { + title: 'Hooks / useMediaDuration', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; export type DemoComponentRefs = MutableRefs<{ video: HTMLVideoElement; @@ -54,7 +58,7 @@ function DemoComponent(): ReactElement { ); } -export const Demo: StoryObj = { +export const Demo: Story = { render() { return ; }, diff --git a/src/hooks/useMediaQuery/useMediaQuery.mdx b/src/hooks/useMediaQuery/useMediaQuery.mdx index ad464c4..ae4c965 100644 --- a/src/hooks/useMediaQuery/useMediaQuery.mdx +++ b/src/hooks/useMediaQuery/useMediaQuery.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # useMediaQuery diff --git a/src/hooks/useMediaQuery/useMediaQuery.stories.tsx b/src/hooks/useMediaQuery/useMediaQuery.stories.tsx index f08da49..5277d93 100644 --- a/src/hooks/useMediaQuery/useMediaQuery.stories.tsx +++ b/src/hooks/useMediaQuery/useMediaQuery.stories.tsx @@ -1,11 +1,15 @@ /* eslint-disable react/jsx-no-literals */ -import type { StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import type { ReactElement } from 'react'; import { useMediaQuery } from './useMediaQuery.js'; -export default { - title: 'hooks/useMediaQuery', -}; +const meta = { + title: 'Hooks / useMediaQuery', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; const css = ` :root { @@ -32,7 +36,7 @@ function DemoComponent(): ReactElement { ); } -export const Demo: StoryObj = { +export const Demo: Story = { render() { return ; }, diff --git a/src/hooks/useMutationObserver/useMutationObserver.mdx b/src/hooks/useMutationObserver/useMutationObserver.mdx index 76322a4..eae1741 100644 --- a/src/hooks/useMutationObserver/useMutationObserver.mdx +++ b/src/hooks/useMutationObserver/useMutationObserver.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # useMutationObserver diff --git a/src/hooks/useMutationObserver/useMutationObserver.stories.tsx b/src/hooks/useMutationObserver/useMutationObserver.stories.tsx index b24c960..01a6418 100644 --- a/src/hooks/useMutationObserver/useMutationObserver.stories.tsx +++ b/src/hooks/useMutationObserver/useMutationObserver.stories.tsx @@ -1,11 +1,15 @@ /* eslint-disable react/jsx-no-literals */ -import type { StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import { type ReactElement, useRef, useState, useCallback } from 'react'; import { useMutationObserver } from './useMutationObserver.js'; -export default { - title: 'hooks/useMutationObserver', -}; +const meta = { + title: 'Hooks / useMutationObserver', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; function DemoComponent(): ReactElement { const [count, setCount] = useState(0); @@ -58,7 +62,7 @@ function DemoComponent(): ReactElement { ); } -export const Demo: StoryObj = { +export const Demo: Story = { render() { return ; }, diff --git a/src/hooks/useRafCallback/useRafCallback.mdx b/src/hooks/useRafCallback/useRafCallback.mdx index 91aa29a..73082f1 100644 --- a/src/hooks/useRafCallback/useRafCallback.mdx +++ b/src/hooks/useRafCallback/useRafCallback.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # useRafCallback diff --git a/src/hooks/useRafCallback/useRafCallback.stories.tsx b/src/hooks/useRafCallback/useRafCallback.stories.tsx index c59aa5d..ec31250 100644 --- a/src/hooks/useRafCallback/useRafCallback.stories.tsx +++ b/src/hooks/useRafCallback/useRafCallback.stories.tsx @@ -1,13 +1,17 @@ /* eslint-disable react-hooks/rules-of-hooks, react/jsx-no-literals */ -import type { StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import { useState } from 'react'; import { useRafCallback } from './useRafCallback.js'; -export default { - title: 'hooks/useRafCallback', -}; +const meta = { + title: 'Hooks / useRafCallback', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; -export const Demo: StoryObj = { +export const Demo: Story = { render() { const [time, setTime] = useState(0); const onUpdate = useRafCallback(setTime); diff --git a/src/hooks/useRafCallback/useRafCallback.ts b/src/hooks/useRafCallback/useRafCallback.ts index f8bf154..fadaa2b 100644 --- a/src/hooks/useRafCallback/useRafCallback.ts +++ b/src/hooks/useRafCallback/useRafCallback.ts @@ -1,6 +1,6 @@ import { useCallback, useRef } from 'react'; +import { useUnmount } from '../../lifecycle/hooks/useUnmount/useUnmount.js'; import { useRefValue } from '../useRefValue/useRefValue.js'; -import { useUnmount } from '../useUnmount/useUnmount.js'; /** * Hook that returns a function that will be called on the next animation frame. diff --git a/src/hooks/useRefValue/useRefValue.mdx b/src/hooks/useRefValue/useRefValue.mdx index 44f0b86..31812ce 100644 --- a/src/hooks/useRefValue/useRefValue.mdx +++ b/src/hooks/useRefValue/useRefValue.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # useRefValue diff --git a/src/hooks/useRefs/useRefs.mdx b/src/hooks/useRefs/useRefs.mdx index 2b565cb..f9961b0 100644 --- a/src/hooks/useRefs/useRefs.mdx +++ b/src/hooks/useRefs/useRefs.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # useRefs diff --git a/src/hooks/useRefs/useRefs.stories.tsx b/src/hooks/useRefs/useRefs.stories.tsx index 2e8c155..8f28cad 100644 --- a/src/hooks/useRefs/useRefs.stories.tsx +++ b/src/hooks/useRefs/useRefs.stories.tsx @@ -1,14 +1,18 @@ /* eslint-disable react/jsx-no-bind, react/no-multi-comp, react/jsx-no-literals */ -import type { StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import { shuffle } from 'lodash-es'; import { useEffect, useState, type ReactElement } from 'react'; import { arrayRef } from '../../utils/arrayRef/arrayRef.js'; import { useRefs } from './useRefs.js'; import type { MutableRefs } from './useRefs.types.js'; -export default { - title: 'hooks/useRefs', -}; +const meta = { + title: 'Hooks / useRefs', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; type MyRefs = MutableRefs<{ item1: HTMLDivElement; @@ -161,7 +165,7 @@ function DemoComponent(): ReactElement { ); } -export const Demo: StoryObj = { +export const Demo: Story = { name: 'Demo', render() { return ; diff --git a/src/hooks/useRefs/utils/assertAndUnwrapRefs/assertAndUnwrapRefs.mdx b/src/hooks/useRefs/utils/assertAndUnwrapRefs/assertAndUnwrapRefs.mdx index f7c951e..cc13a59 100644 --- a/src/hooks/useRefs/utils/assertAndUnwrapRefs/assertAndUnwrapRefs.mdx +++ b/src/hooks/useRefs/utils/assertAndUnwrapRefs/assertAndUnwrapRefs.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # assertAndUnwrapRefs diff --git a/src/hooks/useRefs/utils/unwrapRefs/unwrapRefs.mdx b/src/hooks/useRefs/utils/unwrapRefs/unwrapRefs.mdx index 98ba36d..5d41f00 100644 --- a/src/hooks/useRefs/utils/unwrapRefs/unwrapRefs.mdx +++ b/src/hooks/useRefs/utils/unwrapRefs/unwrapRefs.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # unwrapRefs diff --git a/src/hooks/useRefs/utils/validateAndUnwrapRefs/validateAndUnwrapRefs.mdx b/src/hooks/useRefs/utils/validateAndUnwrapRefs/validateAndUnwrapRefs.mdx index c7fa003..fc9ec26 100644 --- a/src/hooks/useRefs/utils/validateAndUnwrapRefs/validateAndUnwrapRefs.mdx +++ b/src/hooks/useRefs/utils/validateAndUnwrapRefs/validateAndUnwrapRefs.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # validateAndUnwrapRefs diff --git a/src/hooks/useRegisterRef/useRegisterRef.mdx b/src/hooks/useRegisterRef/useRegisterRef.mdx index 20925b1..e4f6cd7 100644 --- a/src/hooks/useRegisterRef/useRegisterRef.mdx +++ b/src/hooks/useRegisterRef/useRegisterRef.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # useRegisterRef diff --git a/src/hooks/useRegisterRef/useRegisterRef.stories.tsx b/src/hooks/useRegisterRef/useRegisterRef.stories.tsx index be0ddc1..8610d95 100644 --- a/src/hooks/useRegisterRef/useRegisterRef.stories.tsx +++ b/src/hooks/useRegisterRef/useRegisterRef.stories.tsx @@ -1,12 +1,16 @@ /* eslint-disable react/jsx-no-bind, react/no-multi-comp, react/jsx-no-literals */ -import type { StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import { shuffle } from 'lodash-es'; import { useState, type ReactElement } from 'react'; import { useRegisterRef } from './useRegisterRef.js'; -export default { - title: 'hooks/useRegisterRef', -}; +const meta = { + title: 'Hooks / useRegisterRef', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; type Refs = { item1: HTMLDivElement | null; @@ -131,7 +135,7 @@ function DemoComponent(): ReactElement { ); } -export const Demo: StoryObj = { +export const Demo: Story = { name: 'Demo', render() { return ; diff --git a/src/hooks/useResizeObserver/useResizeObserver.mdx b/src/hooks/useResizeObserver/useResizeObserver.mdx index 5a42709..3ebe40b 100644 --- a/src/hooks/useResizeObserver/useResizeObserver.mdx +++ b/src/hooks/useResizeObserver/useResizeObserver.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # useResizeObserver diff --git a/src/hooks/useResizeObserver/useResizeObserver.stories.tsx b/src/hooks/useResizeObserver/useResizeObserver.stories.tsx index fd03dcf..ab258cc 100644 --- a/src/hooks/useResizeObserver/useResizeObserver.stories.tsx +++ b/src/hooks/useResizeObserver/useResizeObserver.stories.tsx @@ -1,10 +1,14 @@ -import type { StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import { type ReactElement, useRef, useState } from 'react'; import { useResizeObserver } from './useResizeObserver.js'; -export default { - title: 'hooks/useResizeObserver', -}; +const meta = { + title: 'Hooks / useResizeObserver', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; function DemoComponent(): ReactElement { const elementRef = useRef(null); @@ -28,7 +32,7 @@ function DemoComponent(): ReactElement { ); } -export const Demo: StoryObj = { +export const Demo: Story = { render() { return ; }, diff --git a/src/hooks/useStaticValue/useStaticValue.mdx b/src/hooks/useStaticValue/useStaticValue.mdx index afc0cd8..afb90cb 100644 --- a/src/hooks/useStaticValue/useStaticValue.mdx +++ b/src/hooks/useStaticValue/useStaticValue.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # useStaticValue diff --git a/src/hooks/useToggle/useToggle.mdx b/src/hooks/useToggle/useToggle.mdx index c1a4440..ee2b2f0 100644 --- a/src/hooks/useToggle/useToggle.mdx +++ b/src/hooks/useToggle/useToggle.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/blocks'; - + # useToggle diff --git a/src/hooks/useToggle/useToggle.stories.tsx b/src/hooks/useToggle/useToggle.stories.tsx index 08b7003..543c8bc 100644 --- a/src/hooks/useToggle/useToggle.stories.tsx +++ b/src/hooks/useToggle/useToggle.stories.tsx @@ -1,11 +1,15 @@ /* eslint-disable react/jsx-no-bind, react/no-multi-comp, react/jsx-no-literals */ -import type { StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import type { ReactElement } from 'react'; import { useToggle } from './useToggle.js'; -export default { - title: 'hooks/useToggle', -}; +const meta = { + title: 'Hooks / useToggle', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; type DemoComponentProps = { initialValue?: boolean; @@ -58,7 +62,7 @@ function DemoComponent({ initialValue = false }: DemoComponentProps): ReactEleme ); } -export const Demo: StoryObj = { +export const Demo: Story = { name: 'Demo', render(_arguments: DemoComponentProps) { return ; diff --git a/src/index.ts b/src/index.ts index 6594510..45634b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,19 +1,14 @@ /* PLOP_ADD_EXPORT */ export * from './components/AutoFill/AutoFill.js'; export * from './hocs/ensuredForwardRef/ensuredForwardRef.js'; -export * from './hooks/useBeforeMount/useBeforeMount.js'; export * from './hooks/useClientSideValue/useClientSideValue.js'; export * from './hooks/useEventListener/useEventListener.js'; export * from './hooks/useForceRerender/useForceRerender.js'; export * from './hooks/useHasFocus/useHasFocus.js'; export * from './hooks/useIntersectionObserver/useIntersectionObserver.js'; export * from './hooks/useInterval/useInterval.js'; -export * from './hooks/useIsMounted/useIsMounted.js'; -export * from './hooks/useIsMountedState/useIsMountedState.js'; -export * from './hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.js'; export * from './hooks/useMediaDuration/useMediaDuration.js'; export * from './hooks/useMediaQuery/useMediaQuery.js'; -export * from './hooks/useMount/useMount.js'; export * from './hooks/useMutationObserver/useMutationObserver.js'; export * from './hooks/useRafCallback/useRafCallback.js'; export * from './hooks/useRefValue/useRefValue.js'; @@ -27,8 +22,22 @@ export * from './hooks/useRegisterRef/useRegisterRef.js'; export * from './hooks/useResizeObserver/useResizeObserver.js'; export * from './hooks/useStaticValue/useStaticValue.js'; export * from './hooks/useToggle/useToggle.js'; -export * from './hooks/useUnmount/useUnmount.js'; +export * from './lifecycle/hooks/useBeforeMount/useBeforeMount.js'; +export * from './lifecycle/hooks/useIsMounted/useIsMounted.js'; +export * from './lifecycle/hooks/useIsMountedState/useIsMountedState.js'; +export * from './lifecycle/hooks/useMount/useMount.js'; +export * from './lifecycle/hooks/useUnmount/useUnmount.js'; +export * from './nextjs/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.js'; export * from './utils/arrayRef/arrayRef.js'; export * from './utils/createTimeout/createTimeout.js'; export * from './utils/isRefObject/isRefObject.js'; export * from './utils/unref/unref.js'; + +// Custom exports for external use only +export { CrossFlow } from './lifecycle/components/CrossFlow/CrossFlow.js'; +export { TransitionPresenceContext } from './lifecycle/components/TransitionPresence/TransitionPresence.context.js'; +export { TransitionPresence } from './lifecycle/components/TransitionPresence/TransitionPresence.js'; +export { + useBeforeUnmount, + type BeforeUnmountCallback, +} from './lifecycle/hooks/useBeforeUnmount/useBeforeUnmount.js'; diff --git a/src/lifecycle/components/CrossFlow/CrossFlow.mdx b/src/lifecycle/components/CrossFlow/CrossFlow.mdx new file mode 100644 index 0000000..153e091 --- /dev/null +++ b/src/lifecycle/components/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/src/lifecycle/components/CrossFlow/CrossFlow.stories.tsx b/src/lifecycle/components/CrossFlow/CrossFlow.stories.tsx new file mode 100644 index 0000000..1a8d9c8 --- /dev/null +++ b/src/lifecycle/components/CrossFlow/CrossFlow.stories.tsx @@ -0,0 +1,139 @@ +/* eslint-disable react/no-multi-comp, react/jsx-no-literals, react-hooks/rules-of-hooks */ +import type { Meta, StoryObj } from '@storybook/react'; +import { Fragment, useRef, useState, type ReactElement } from 'react'; +import { useBeforeUnmount } from '../../hooks/useBeforeUnmount/useBeforeUnmount.js'; +import { CrossFlow } from './CrossFlow.js'; + +const meta = { + title: 'Lifecycle / components/CrossFlow', + argTypes: { + children: { + description: 'ReactElement | null', + type: 'string', + }, + onChildrenMounted: { + description: '() => void | undefined', + type: 'string', + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +type ChildProps = { + background: string; + duration?: number; + onClick?(): void; +}; + +function Child({ background, onClick, duration = 1000 }: ChildProps): ReactElement { + const ref = useRef(null); + + // show visible animation during "before unmount" lifecycle + useBeforeUnmount(async (abortSignal) => { + if (!ref.current) { + return; + } + + const animation = ref.current.animate([{ opacity: 1 }, { opacity: 0 }], { + duration, + fill: 'forwards', + }); + + abortSignal.addEventListener('abort', () => { + animation.cancel(); + }); + + await animation.finished; + }); + + return ( +