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.
This commit is contained in:
Ilia Mashkov
2026-05-12 16:10:37 +03:00
parent 0090718869
commit 7cba3053f4
5 changed files with 62 additions and 1 deletions
@@ -0,0 +1 @@
export { ViewTransitionWrapper } from './ui/ViewTransitionWrapper';
@@ -0,0 +1,33 @@
import { render, screen } from '@testing-library/react';
import { ViewTransitionWrapper } from './ViewTransitionWrapper';
describe('ViewTransitionWrapper', () => {
it('renders children', () => {
render(
<ViewTransitionWrapper name="test">
<p>Hello</p>
</ViewTransitionWrapper>,
);
expect(screen.getByText('Hello')).toBeInTheDocument();
});
it('renders multiple children', () => {
render(
<ViewTransitionWrapper name="test">
<p>First</p>
<p>Second</p>
</ViewTransitionWrapper>,
);
expect(screen.getByText('First')).toBeInTheDocument();
expect(screen.getByText('Second')).toBeInTheDocument();
});
it('does not add an extra wrapper DOM element', () => {
const { container } = render(
<ViewTransitionWrapper name="test">
<p>Content</p>
</ViewTransitionWrapper>,
);
expect(container.firstChild?.nodeName).toBe('P');
});
});
@@ -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 <Transition name={name}>{children}</Transition>;
}
+1
View File
@@ -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';
+1 -1
View File
@@ -18,7 +18,7 @@
"name": "next"
}
],
"types": ["vitest/globals"],
"types": ["vitest/globals", "react/canary"],
"paths": {
"@/*": ["./*"],
"$shared/*": ["./src/shared/*"],