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:
@@ -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>;
|
||||||
|
}
|
||||||
@@ -10,3 +10,4 @@ export { RichText } from './RichText';
|
|||||||
export type { ContainerSize, SectionBackground } from './Section';
|
export type { ContainerSize, SectionBackground } from './Section';
|
||||||
export { Container, Section } from './Section';
|
export { Container, Section } from './Section';
|
||||||
export { TechStackBrick, TechStackGrid } from './TechStack';
|
export { TechStackBrick, TechStackGrid } from './TechStack';
|
||||||
|
export { ViewTransitionWrapper } from './ViewTransitionWrapper';
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
"name": "next"
|
"name": "next"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"types": ["vitest/globals"],
|
"types": ["vitest/globals", "react/canary"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"],
|
"@/*": ["./*"],
|
||||||
"$shared/*": ["./src/shared/*"],
|
"$shared/*": ["./src/shared/*"],
|
||||||
|
|||||||
Reference in New Issue
Block a user