[blog + docs] React Labs: View Transitions, Activity, and more (#7772)

* init

* Add new error

* Bunch of updates

* Add addTransitionType

* Add Activity

* Expand on Activity

* Expand more on Activity

* Start on vt examples

* wip

* wip

* wip 2

* bump deploy

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* init

* Add new error

* Bunch of updates

* Add addTransitionType

* Add Activity

* Expand on Activity

* Expand more on Activity

* Start on vt examples

* wip

* wip

* wip 2

* bump deploy

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update existing VT examples to use video items

* Suspense animation example

* Add customization example

* typos

* Remove TODOs -- we might still want these but cutting scope

* Address wp feedback

* Fix reorder example

* wip

* wip

* wip

* wip

* fix activity iframe resize

---------

Co-authored-by: Jack Pope <jackpope1@gmail.com>
This commit is contained in:
Ricky
2025-04-23 11:27:48 -04:00
committed by GitHub
parent 55ddaa4f13
commit 358d2be565
22 changed files with 31039 additions and 17 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import {memo} from 'react';
export const IconExperimental = memo<
JSX.IntrinsicElements['svg'] & {title?: string; size?: 's' | 'md'}
>(function IconCanary(
{className, title, size} = {
className: undefined,
title: undefined,
size: 'md',
}
) {
return (
<svg
className={className}
width={size === 's' ? '12px' : '20px'}
height={size === 's' ? '12px' : '20px'}
viewBox="0 0 20 20"
version="1.1"
xmlns="http://www.w3.org/2000/svg">
{title && <title>{title}</title>}
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g
id="noun-labs-1201738-(2)"
transform="translate(2, 0)"
fill="currentColor"
fillRule="nonzero">
<path
d="M10.2865804,5.55665262 L10.2865804,2.22331605 L10.8591544,2.22331605 C11.0103911,2.22244799 11.1551447,2.16342155 11.2617505,2.05914367 C11.3684534,1.95486857 11.4282767,1.81370176 11.4282767,1.66667106 L11.4282767,0.556642208 C11.4282767,0.40907262 11.3678934,0.26747526 11.2605218,0.16308627 C11.1531503,0.0587028348 11.0074938,0 10.8556998,0 L5.14338868,0 C4.9915947,0 4.84594391,0.0587028348 4.73856664,0.16308627 C4.63119507,0.267469704 4.57081178,0.40907262 4.57081178,0.556642208 L4.57081178,1.66667106 C4.57081178,1.81434899 4.63119507,1.95594912 4.73856664,2.06033811 C4.8459382,2.16472155 4.9915947,2.22331605 5.14338868,2.22331605 L5.71596273,2.22331605 L5.71596273,5.55665262 C5.71596273,8.38665538 2.97295619,9.88999017 0.651686904,15.5566623 C-0.0957823782,17.360053 -2.00560068,20 7.99951567,20 C18.004632,20 16.0948137,17.3600252 15.3507732,15.5566623 C13.0124432,9.88999017 10.2865804,8.38665538 10.2865804,5.55665262 Z M9.89570197,10.709991 C10.0921412,10.709991 10.2805515,10.7858383 10.4193876,10.9209301 C10.5583466,11.0559135 10.6363652,11.2390693 10.6363652,11.4300417 C10.6363652,11.6210141 10.5583466,11.8040698 10.4193876,11.9391533 C10.2805401,12.0741367 10.0921412,12.1499813 9.89570197,12.1499813 C9.6992627,12.1499813 9.51096673,12.074134 9.37201631,11.9391533 C9.23316875,11.8040615 9.15515307,11.6210141 9.15515307,11.4300417 C9.15515307,11.2390693 9.2331716,11.0559024 9.37201631,10.9209301 C9.57264221,10.7258996 9.61239426,10.709991 9.89570197,10.709991 Z M8.98919546,9.04212824 C9.09790709,9.14792278 9.15884755,9.29158681 9.1585213,9.44110085 C9.15829001,9.59073155 9.09678989,9.73407335 8.98763252,9.83954568 C8.87847514,9.945018 8.73069852,10.0039347 8.57678157,10.0033977 C8.42286747,10.0027392 8.27565088,9.94273467 8.16727355,9.83639845 C8.05900765,9.73006224 7.99873866,9.58628988 7.99963013,9.43664806 C8.00052304,9.28788403 8.0620221,9.14542556 8.17051087,9.04048101 C8.27911107,8.93555591 8.42599335,8.87663641 8.57913312,8.87663641 C8.73291864,8.87665585 8.88047525,8.93622535 8.98919546,9.04212824 Z M7.99965585,17.9999981 C4.91377349,17.9999981 3.29882839,17.7332867 2.51364277,17.4999976 C2.37780966,17.4476975 2.26954376,17.3439641 2.21396931,17.2125528 C2.15838628,17.0811499 2.16006066,16.9334692 2.21876871,16.8033858 C2.6144474,15.5921346 3.14916224,14.4280501 3.81316983,13.3333824 C5.980145,9.82337899 8.22941036,13.8867718 10.0980836,13.8867718 C11.9666996,13.8867718 11.4695868,12.1534924 12.1827971,13.3333824 C12.8511505,14.4269112 13.3916656,15.5896902 13.794259,16.8000524 C13.8533022,16.9322137 13.8537479,17.0822749 13.7952635,17.2147751 C13.7368889,17.3472613 13.6248314,17.4504531 13.4856467,17.5000531 C12.6833967,17.7332867 11.0855382,17.9999981 7.99965585,17.9999981 Z"
id="Shape"></path>
</g>
</g>
</svg>
);
});

View File

@@ -31,7 +31,7 @@ interface PageProps {
meta: {
title?: string;
titleForTitleTag?: string;
canary?: boolean;
version?: 'experimental' | 'canary';
description?: string;
};
section: 'learn' | 'reference' | 'community' | 'blog' | 'home' | 'unknown';
@@ -53,7 +53,7 @@ export function Page({
routeTree
);
const title = meta.title || route?.title || '';
const canary = meta.canary || false;
const version = meta.version;
const description = meta.description || route?.description || '';
const isHomePage = cleanedPath === '/';
const isBlogIndex = cleanedPath === '/blog';
@@ -70,7 +70,7 @@ export function Page({
)}>
<PageHeading
title={title}
canary={canary}
version={version}
description={description}
tags={route?.tags}
breadcrumbs={breadcrumbs}

View File

@@ -9,6 +9,7 @@ import * as React from 'react';
import cn from 'classnames';
import {IconNavArrow} from 'components/Icon/IconNavArrow';
import {IconCanary} from 'components/Icon/IconCanary';
import {IconExperimental} from 'components/Icon/IconExperimental';
import Link from 'next/link';
interface SidebarLinkProps {
@@ -16,7 +17,7 @@ interface SidebarLinkProps {
selected?: boolean;
title: string;
level: number;
version?: 'canary' | 'major';
version?: 'canary' | 'major' | 'experimental';
icon?: React.ReactNode;
isExpanded?: boolean;
hideArrow?: boolean;
@@ -84,7 +85,13 @@ export function SidebarLink({
)}
{version === 'canary' && (
<IconCanary
title=" - This feature is available in the latest Canary"
title=" - This feature is available in the latest Canary version of React"
className="ms-1 text-gray-30 dark:text-gray-60 inline-block w-3.5 h-3.5 align-[-3px]"
/>
)}
{version === 'experimental' && (
<IconExperimental
title=" - This feature is available in the latest Experimental version of React"
className="ms-1 text-gray-30 dark:text-gray-60 inline-block w-3.5 h-3.5 align-[-3px]"
/>
)}

View File

@@ -16,6 +16,7 @@ type CalloutVariants =
| 'note'
| 'wip'
| 'canary'
| 'experimental'
| 'major'
| 'rsc';
@@ -51,6 +52,15 @@ const variantMap = {
overlayGradient:
'linear-gradient(rgba(245, 249, 248, 0), rgba(245, 249, 248, 1)',
},
experimental: {
title: 'Experimental Feature',
Icon: IconCanary,
containerClasses:
'bg-green-5 dark:bg-green-60 dark:bg-opacity-20 text-primary dark:text-primary-dark text-lg',
textColor: 'text-green-60 dark:text-green-40',
overlayGradient:
'linear-gradient(rgba(245, 249, 248, 0), rgba(245, 249, 248, 1)',
},
pitfall: {
title: 'Pitfall',
Icon: IconPitfall,

View File

@@ -98,6 +98,10 @@ const Canary = ({children}: {children: React.ReactNode}) => (
<ExpandableCallout type="canary">{children}</ExpandableCallout>
);
const Experimental = ({children}: {children: React.ReactNode}) => (
<ExpandableCallout type="experimental">{children}</ExpandableCallout>
);
const NextMajor = ({children}: {children: React.ReactNode}) => (
<ExpandableCallout type="major">{children}</ExpandableCallout>
);
@@ -120,6 +124,20 @@ const CanaryBadge = ({title}: {title: string}) => (
</span>
);
const ExperimentalBadge = ({title}: {title: string}) => (
<span
title={title}
className={
'text-base font-display px-1 py-0.5 font-bold bg-gray-10 dark:bg-gray-60 text-gray-60 dark:text-gray-10 rounded'
}>
<IconCanary
size="s"
className={'inline me-1 mb-0.5 text-sm text-gray-60 dark:text-gray-10'}
/>
Experimental only
</span>
);
const NextMajorBadge = ({title}: {title: string}) => (
<span
title={title}
@@ -508,6 +526,8 @@ export const MDXComponents = {
MathI,
Note,
Canary,
Experimental,
ExperimentalBadge,
CanaryBadge,
NextMajor,
NextMajorBadge,

View File

@@ -8,10 +8,12 @@ import {H1} from './MDX/Heading';
import type {RouteTag, RouteItem} from './Layout/getRouteMeta';
import * as React from 'react';
import {IconCanary} from './Icon/IconCanary';
import {IconExperimental} from './Icon/IconExperimental';
interface PageHeadingProps {
title: string;
canary?: boolean;
version?: 'experimental' | 'canary';
experimental?: boolean;
status?: string;
description?: string;
tags?: RouteTag[];
@@ -21,19 +23,26 @@ interface PageHeadingProps {
function PageHeading({
title,
status,
canary,
version,
tags = [],
breadcrumbs,
}: PageHeadingProps) {
console.log('version', version);
return (
<div className="px-5 sm:px-12 pt-3.5">
<div className="max-w-4xl ms-0 2xl:mx-auto">
{breadcrumbs ? <Breadcrumbs breadcrumbs={breadcrumbs} /> : null}
<H1 className="mt-0 text-primary dark:text-primary-dark -mx-.5 break-words">
{title}
{canary && (
{version === 'canary' && (
<IconCanary
title=" - This feature is available in the latest Canary"
title=" - This feature is available in the latest Canary version of React"
className="ms-4 mt-1 text-gray-50 dark:text-gray-40 inline-block w-6 h-6 align-[-1px]"
/>
)}
{version === 'experimental' && (
<IconExperimental
title=" - This feature is available in the latest Experimental version of React"
className="ms-4 mt-1 text-gray-50 dark:text-gray-40 inline-block w-6 h-6 align-[-1px]"
/>
)}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,12 @@ You can also follow the [@react.dev](https://bsky.app/profile/react.dev) account
<div className="sm:-mx-5 flex flex-col gap-5 mt-12">
<BlogCard title="React Labs: View Transitions, Activity, and more" date="April 23, 2025" url="/blog/2025/04/23/react-labs-view-transitions-activity-and-more">
In React Labs posts, we write about projects in active research and development. In this post, we're sharing two new experimental features that are ready to try today, and sharing other areas we're working on now ...
</BlogCard>
<BlogCard title="React Compiler RC" date="April 21, 2025" url="/blog/2025/04/21/react-compiler-rc">
We are releasing the compiler's first Release Candidate (RC) today.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,182 @@
---
title: unstable_addTransitionType
version: experimental
---
<Experimental>
**This API is experimental and is not available in a stable version of React yet.**
You can try it by upgrading React packages to the most recent experimental version:
- `react@experimental`
- `react-dom@experimental`
- `eslint-plugin-react-hooks@experimental`
Experimental versions of React may contain bugs. Don't use them in production.
</Experimental>
<Intro>
`unstable_addTransitionType` lets you specify the cause of a transition.
```js
startTransition(() => {
unstable_addTransitionType('my-transition-type');
setState(newState);
});
```
</Intro>
<InlineToc />
---
## Reference {/*reference*/}
### `addTransitionType` {/*addtransitiontype*/}
#### Parameters {/*parameters*/}
- `type`: The type of transition to add. This can be any string.
#### Returns {/*returns*/}
`startTransition` does not return anything.
#### Caveats {/*caveats*/}
- If multiple transitions are combined, all Transition Types are collected. You can also add more than one type to a Transition.
- Transition Types are reset after each commit. This means a `<Suspense>` fallback will associate the types after a `startTransition`, but revealing the content does not.
---
## Usage {/*usage*/}
### Adding the cause of a transition {/*adding-the-cause-of-a-transition*/}
Call `addTransitionType` inside of `startTransition` to indicate the cause of a transition:
``` [[1, 6, "unstable_addTransitionType"], [2, 5, "startTransition", [3, 6, "'submit-click'"]]
import { startTransition, unstable_addTransitionType } from 'react';
function Submit({action) {
function handleClick() {
startTransition(() => {
unstable_addTransitionType('submit-click');
action();
});
}
return <button onClick={handleClick}>Click me</button>;
}
```
When you call <CodeStep step={1}>addTransitionType</CodeStep> inside the scope of <CodeStep step={2}>startTransition</CodeStep>, React will associate <CodeStep step={3}>submit-click</CodeStep> as one of the causes for the Transition.
Currently, Transition Types can be used to customize different animations based on what caused the Transition. You have three different ways to choose from for how to use them:
- [Customize animations using browser view transition types](#customize-animations-using-browser-view-transition-types)
- [Customize animations using `View Transition` Class](#customize-animations-using-view-transition-class)
- [Customize animations using `ViewTransition` events](#customize-animations-using-viewtransition-events)
In the future, we plan to support more use cases for using the cause of a transition.
---
### Customize animations using browser view transition types {/*customize-animations-using-browser-view-transition-types*/}
When a [`ViewTransition`](/reference/react/ViewTransition) activates from a transition, React adds all the Transition Types as browser [view transition types](https://www.w3.org/TR/css-view-transitions-2/#active-view-transition-pseudo-examples) to the element.
This allows you to customize different animations based on CSS scopes:
```js [11]
function Component() {
return (
<ViewTransition>
<div>Hello</div>
</ViewTransition>
);
}
startTransition(() => {
unstable_addTransitionType('my-transition-type');
setShow(true);
});
```
```css
:root:active-view-transition-type(my-transition-type) {
&::view-transition-...(...) {
...
}
}
```
---
### Customize animations using `View Transition` Class {/*customize-animations-using-view-transition-class*/}
You can customize animations for an activated `ViewTransition` based on type by passing an object to the View Transition Class:
```js
function Component() {
return (
<ViewTransition enter={{
'my-transition-type': 'my-transition-class',
}}>
<div>Hello</div>
</ViewTransition>
);
}
// ...
startTransition(() => {
unstable_addTransitionType('my-transition-type');
setState(newState);
});
```
If multiple types match, then they're joined together. If no types match then the special "default" entry is used instead. If any type has the value "none" then that wins and the ViewTransition is disabled (not assigned a name).
These can be combined with enter/exit/update/layout/share props to match based on kind of trigger and Transition Type.
```js
<ViewTransition enter={{
'navigation-back': 'enter-right',
'navigation-forward': 'enter-left',
}}
exit={{
'navigation-back': 'exit-right',
'navigation-forward': 'exit-left',
}}>
```
---
### Customize animations using `ViewTransition` events {/*customize-animations-using-viewtransition-events*/}
You can imperatively customize animations for an activated `ViewTransition` based on type using View Transition events:
```
<ViewTransition onUpdate={(inst, types) => {
if (types.includes('navigation-back')) {
...
} else if (types.includes('navigation-forward')) {
...
} else {
...
}
}}>
```
This allows you to pick different imperative Animations based on the cause.
---
## Troubleshooting {/*troubleshooting*/}
### TODO {/*todo2*/}

View File

@@ -1,8 +1,9 @@
---
title: experimental_taintObjectReference
version: experimental
---
<Wip>
<Experimental>
**This API is experimental and is not available in a stable version of React yet.**
@@ -16,7 +17,7 @@ Experimental versions of React may contain bugs. Don't use them in production.
This API is only available inside React Server Components.
</Wip>
</Experimental>
<Intro>

View File

@@ -1,8 +1,9 @@
---
title: experimental_taintUniqueValue
version: experimental
---
<Wip>
<Experimental>
**This API is experimental and is not available in a stable version of React yet.**
@@ -16,7 +17,7 @@ Experimental versions of React may contain bugs. Don't use them in production.
This API is only available inside [React Server Components](/reference/rsc/use-client).
</Wip>
</Experimental>
<Intro>

View File

@@ -1,8 +1,9 @@
---
title: experimental_useEffectEvent
version: experimental
---
<Wip>
<Experimental>
**This API is experimental and is not available in a stable version of React yet.**
@@ -14,7 +15,7 @@ You can try it by upgrading React packages to the most recent experimental versi
Experimental versions of React may contain bugs. Don't use them in production.
</Wip>
</Experimental>
<Intro>

View File

@@ -11,6 +11,13 @@
"path": "/blog",
"skipBreadcrumb": true,
"routes": [
{
"title": "React Labs: View Transitions, Activity, and more",
"titleForHomepage": "View Transitions and Activity",
"icon": "blog",
"date": "April 23, 2025",
"path": "/blog/2025/04/23/react-labs-view-transitions-activity-and-more"
},
{
"title": "React Compiler RC",
"titleForHomepage": "React Compiler RC",

View File

@@ -103,6 +103,16 @@
{
"title": "<Suspense>",
"path": "/reference/react/Suspense"
},
{
"title": "<Activity>",
"path": "/reference/react/Activity",
"version": "experimental"
},
{
"title": "<ViewTransition>",
"path": "/reference/react/ViewTransition",
"version": "experimental"
}
]
},
@@ -144,12 +154,17 @@
{
"title": "experimental_taintObjectReference",
"path": "/reference/react/experimental_taintObjectReference",
"version": "canary"
"version": "experimental"
},
{
"title": "experimental_taintUniqueValue",
"path": "/reference/react/experimental_taintUniqueValue",
"version": "canary"
"version": "experimental"
},
{
"title": "unstable_addTransitionType",
"path": "/reference/react/addTransitionType",
"version": "experimental"
}
]
},