diff --git a/examples/visualization/src/app/react-dom/FadeIn/page.tsx b/examples/visualization/src/app/react-dom/FadeIn/page.tsx
index 461aa47c8..833169b75 100644
--- a/examples/visualization/src/app/react-dom/FadeIn/page.tsx
+++ b/examples/visualization/src/app/react-dom/FadeIn/page.tsx
@@ -40,18 +40,33 @@ export default function Page() {
clientOnly
fallback={
- {skeleton}
+ {(fadeIn) => (
+
+ )}
}
>
{({ data: user }) => (
-
- {user.username}
- {user.userAgent}
- {user.age}
- {user.maidenName}
-
+
+ {(fadeIn) => (
+
+
{user.username}
+
{user.userAgent}
+
{user.age}
+
{user.maidenName}
+
+
+ )}
)}
@@ -62,14 +77,3 @@ export default function Page() {
)
}
-
-const skeleton = (
-
-)
diff --git a/packages/react-dom/src/FadeIn.spec.tsx b/packages/react-dom/src/FadeIn.spec.tsx
new file mode 100644
index 000000000..0ed9ce558
--- /dev/null
+++ b/packages/react-dom/src/FadeIn.spec.tsx
@@ -0,0 +1,24 @@
+import { render, screen } from '@testing-library/react'
+import { FadeIn } from './FadeIn'
+import { mockAllIsIntersecting } from './test-utils'
+
+describe('', () => {
+ it('renders children with correct styles when in view', () => {
+ mockAllIsIntersecting(false)
+ const mockChild = vi.fn()
+ render(
+
+ {(fadeIn) => (
+
+ {mockChild(fadeIn.style)}
+
+ )}
+
+ )
+ expect(mockChild).toHaveBeenCalledWith({ opacity: 0, willChange: 'opacity', transition: 'opacity 300ms ease-in' })
+ mockAllIsIntersecting(true)
+ const child = screen.getByTestId('fade-in-child')
+ expect(child).toHaveStyle({ opacity: '1', willChange: 'opacity', transition: 'opacity 300ms ease-in' })
+ expect(mockChild).toHaveBeenCalledWith({ opacity: 1, willChange: 'opacity', transition: 'opacity 300ms ease-in' })
+ })
+})
diff --git a/packages/react-dom/src/FadeIn.test-d.tsx b/packages/react-dom/src/FadeIn.test-d.tsx
index 15af2837b..fc30a2927 100644
--- a/packages/react-dom/src/FadeIn.test-d.tsx
+++ b/packages/react-dom/src/FadeIn.test-d.tsx
@@ -1,25 +1,10 @@
-import type { CSSProperties, ComponentProps } from 'react'
-import { FadeIn } from './FadeIn'
-import type { OmitKeyof } from './utility-types'
-
-const Example1 = ({}: { x: string }) => <>>
-const Example2 = () => <>>
+import type { ComponentProps } from 'react'
+import type { FadeIn } from './FadeIn'
describe('', () => {
it('type check', () => {
- // @ts-expect-error ts(2322)
- ;(() => )()
- ;(() => )()
- ;(() => )()
- ;(() => )()
- // @ts-expect-error ts(2322)
- ;(() => )()
-
- expectTypeOf>>().toEqualTypeOf<
- 'root' | 'rootMargin' | 'threshold' | 'triggerOnce' | 'delay' | 'style' | 'as' | 'duration' | 'timingFunction'
- >()
- expectTypeOf>['style']>().toEqualTypeOf<
- OmitKeyof | undefined
+ expectTypeOf>().toEqualTypeOf<
+ 'root' | 'rootMargin' | 'threshold' | 'triggerOnce' | 'delay' | 'children' | 'duration' | 'timingFunction'
>()
})
})
diff --git a/packages/react-dom/src/FadeIn.tsx b/packages/react-dom/src/FadeIn.tsx
index a7d8b3d9c..8a2ddb6dd 100644
--- a/packages/react-dom/src/FadeIn.tsx
+++ b/packages/react-dom/src/FadeIn.tsx
@@ -1,67 +1,22 @@
-import { type CSSProperties, type ComponentPropsWithoutRef, type ElementType } from 'react'
-import { InView } from './InView'
-import { type InViewOptions } from './useInView'
-import { type OmitKeyof, type Override } from './utility-types'
-
-type FadeInProps = Override<
- Override<
- ComponentPropsWithoutRef,
- OmitKeyof
- >,
- {
- /**
- * The element type to render.
- * @default 'div'
- */
- as?: TAs
- /**
- * The style of the element.
- */
- style?: OmitKeyof
- /**
- * The duration in milliseconds of the animation.
- * @default 200
- */
- duration?: number
- /**
- * The timing function of the animation.
- * @default 'linear'
- */
- timingFunction?: CSSProperties['animationTimingFunction']
- }
->
+import { type ReactNode } from 'react'
+import { type FadeInOptions, useFadeIn } from './useFadeIn'
+type FadeInProps = FadeInOptions & {
+ children: (fadeInResult: ReturnType) => ReactNode
+}
/**
* A component that fades in when it comes into view.
*/
-export function FadeIn({
- as,
- style,
- // fadeIn Options
+export function FadeIn({
duration = 200,
timingFunction = 'linear',
- // inView options
delay,
+ root,
rootMargin,
threshold,
triggerOnce,
- ...restProps
-}: FadeInProps) {
- const Component = as ?? 'div'
- return (
-
- {({ inView, ref }) => (
-
- )}
-
- )
+ children,
+}: FadeInProps) {
+ const result = useFadeIn({ delay, duration, root, rootMargin, threshold, timingFunction, triggerOnce })
+ return <>{children(result)}>
}
diff --git a/packages/react-dom/src/index.ts b/packages/react-dom/src/index.ts
index 0e0c494a3..e6b6f0a6f 100644
--- a/packages/react-dom/src/index.ts
+++ b/packages/react-dom/src/index.ts
@@ -1,3 +1,4 @@
export { InView } from './InView'
export { useInView } from './useInView'
export { FadeIn } from './FadeIn'
+export { useFadeIn } from './useFadeIn'
diff --git a/packages/react-dom/src/useFadeIn.ts b/packages/react-dom/src/useFadeIn.ts
new file mode 100644
index 000000000..f817e4f7c
--- /dev/null
+++ b/packages/react-dom/src/useFadeIn.ts
@@ -0,0 +1,48 @@
+import { type CSSProperties, useMemo } from 'react'
+import { type InViewOptions, useInView } from './useInView'
+import type { OmitKeyof } from './utility-types'
+
+export type FadeInOptions = OmitKeyof<
+ InViewOptions,
+ 'fallbackInView' | 'initialInView' | 'skip' | 'onChange' | 'trackVisibility'
+> & {
+ /**
+ * The duration in milliseconds of the animation.
+ * @default 200
+ */
+ duration?: number
+ /**
+ * The timing function of the animation.
+ * @default 'linear'
+ */
+ timingFunction?: CSSProperties['transitionTimingFunction']
+}
+type FadeInResult = Pick, 'ref'> & {
+ style: {
+ opacity: 0 | 1
+ willChange: 'opacity'
+ transition: `opacity ${number}ms ${Required['transitionTimingFunction']}`
+ }
+}
+export function useFadeIn({
+ duration = 200,
+ timingFunction = 'linear',
+ delay,
+ root,
+ rootMargin,
+ threshold,
+ triggerOnce,
+}: FadeInOptions): FadeInResult {
+ const { inView, ref } = useInView({ delay, root, rootMargin, threshold, triggerOnce })
+ return useMemo(
+ () => ({
+ ref,
+ style: {
+ opacity: inView ? 1 : 0,
+ willChange: 'opacity',
+ transition: `opacity ${duration}ms ${timingFunction}` as const,
+ },
+ }),
+ [inView, duration, timingFunction]
+ )
+}
diff --git a/packages/react-dom/src/utility-types/Override.ts b/packages/react-dom/src/utility-types/Override.ts
deleted file mode 100644
index edfa44ee6..000000000
--- a/packages/react-dom/src/utility-types/Override.ts
+++ /dev/null
@@ -1 +0,0 @@
-export type Override = Omit & TObject
diff --git a/packages/react-dom/src/utility-types/index.ts b/packages/react-dom/src/utility-types/index.ts
index 6765a418a..e439d3d4c 100644
--- a/packages/react-dom/src/utility-types/index.ts
+++ b/packages/react-dom/src/utility-types/index.ts
@@ -1,2 +1 @@
export type { OmitKeyof } from './OmitKeyof'
-export type { Override } from './Override'