From 7cba3053f4031c40030f959f624b0fe0fdc412ac Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 12 May 2026 16:10:37 +0300 Subject: [PATCH] feat: ViewTransitionWrapper shared component with stable react-dom fallback Wraps children in React's ViewTransition (canary API) when available, falling back to Fragment in environments where ViewTransition is undefined (test env, stable react-dom). Add react/canary to tsconfig types to expose the ViewTransition component type. --- src/shared/ui/ViewTransitionWrapper/index.ts | 1 + .../ui/ViewTransitionWrapper.test.tsx | 33 +++++++++++++++++++ .../ui/ViewTransitionWrapper.tsx | 26 +++++++++++++++ src/shared/ui/index.ts | 1 + tsconfig.json | 2 +- 5 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 src/shared/ui/ViewTransitionWrapper/index.ts create mode 100644 src/shared/ui/ViewTransitionWrapper/ui/ViewTransitionWrapper.test.tsx create mode 100644 src/shared/ui/ViewTransitionWrapper/ui/ViewTransitionWrapper.tsx diff --git a/src/shared/ui/ViewTransitionWrapper/index.ts b/src/shared/ui/ViewTransitionWrapper/index.ts new file mode 100644 index 0000000..548b11f --- /dev/null +++ b/src/shared/ui/ViewTransitionWrapper/index.ts @@ -0,0 +1 @@ +export { ViewTransitionWrapper } from './ui/ViewTransitionWrapper'; diff --git a/src/shared/ui/ViewTransitionWrapper/ui/ViewTransitionWrapper.test.tsx b/src/shared/ui/ViewTransitionWrapper/ui/ViewTransitionWrapper.test.tsx new file mode 100644 index 0000000..b34b133 --- /dev/null +++ b/src/shared/ui/ViewTransitionWrapper/ui/ViewTransitionWrapper.test.tsx @@ -0,0 +1,33 @@ +import { render, screen } from '@testing-library/react'; +import { ViewTransitionWrapper } from './ViewTransitionWrapper'; + +describe('ViewTransitionWrapper', () => { + it('renders children', () => { + render( + +

Hello

+
, + ); + expect(screen.getByText('Hello')).toBeInTheDocument(); + }); + + it('renders multiple children', () => { + render( + +

First

+

Second

+
, + ); + expect(screen.getByText('First')).toBeInTheDocument(); + expect(screen.getByText('Second')).toBeInTheDocument(); + }); + + it('does not add an extra wrapper DOM element', () => { + const { container } = render( + +

Content

+
, + ); + expect(container.firstChild?.nodeName).toBe('P'); + }); +}); diff --git a/src/shared/ui/ViewTransitionWrapper/ui/ViewTransitionWrapper.tsx b/src/shared/ui/ViewTransitionWrapper/ui/ViewTransitionWrapper.tsx new file mode 100644 index 0000000..3aad3d4 --- /dev/null +++ b/src/shared/ui/ViewTransitionWrapper/ui/ViewTransitionWrapper.tsx @@ -0,0 +1,26 @@ +import { Fragment, type ReactNode, ViewTransition as VT } from 'react'; + +/** + * VT is undefined in stable react-dom (test env / non-experimental builds). + * Fall back to Fragment so children render and the name prop is silently ignored. + */ +const Transition = (VT ?? Fragment) as typeof VT; + +type Props = { + /** + * Maps to the view-transition-name CSS property + */ + name: string; + /** + * Content to animate + */ + children: ReactNode; +}; + +/** + * Wraps children in React's ViewTransition when available, + * falling back to a Fragment in environments where ViewTransition is undefined. + */ +export function ViewTransitionWrapper({ name, children }: Props) { + return {children}; +} diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts index 45bda75..6dc448b 100644 --- a/src/shared/ui/index.ts +++ b/src/shared/ui/index.ts @@ -10,3 +10,4 @@ export { RichText } from './RichText'; export type { ContainerSize, SectionBackground } from './Section'; export { Container, Section } from './Section'; export { TechStackBrick, TechStackGrid } from './TechStack'; +export { ViewTransitionWrapper } from './ViewTransitionWrapper'; diff --git a/tsconfig.json b/tsconfig.json index d3c3b5c..de58819 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,7 @@ "name": "next" } ], - "types": ["vitest/globals"], + "types": ["vitest/globals", "react/canary"], "paths": { "@/*": ["./*"], "$shared/*": ["./src/shared/*"],