[playground] ViewTransition on config expand (#34595)

<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

Introduced `<ViewTransition>` to the React Compiler Playground. Added an
initial animation on the config panel opening/closing to allow for a
smoother visual experience. Previously, the panel would flash in and out
of the screen upon open/close.

## How did you test this change?

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->




https://github.com/user-attachments/assets/9dc77a6b-d4a5-4a7a-9d81-007ebb55e8d2
This commit is contained in:
Eugene Choi 2025-09-29 14:09:37 -04:00 committed by GitHub
parent d15d7fd79e
commit 319a7867d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 238 additions and 137 deletions

View File

@ -23,7 +23,8 @@ function formatPrint(data: Array<string>): Promise<string> {
async function expandConfigs(page: Page): Promise<void> {
const expandButton = page.locator('[title="Expand config editor"]');
expandButton.click();
await expandButton.click();
await page.waitForSelector('.monaco-editor-config', {state: 'visible'});
}
const TEST_SOURCE = `export default function TestComponent({ x }) {

View File

@ -18,6 +18,7 @@ export default function AccordionWindow(props: {
changedPasses: Set<string>;
}): React.ReactElement {
return (
<div className="flex-1 min-w-[550px] sm:min-w-0">
<div className="flex flex-row h-full">
{Array.from(props.tabs.keys()).map(name => {
return (
@ -32,6 +33,7 @@ export default function AccordionWindow(props: {
);
})}
</div>
</div>
);
}

View File

@ -9,12 +9,19 @@ import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
import {PluginOptions} from 'babel-plugin-react-compiler';
import type {editor} from 'monaco-editor';
import * as monaco from 'monaco-editor';
import React, {useState, useRef} from 'react';
import React, {
useState,
useRef,
unstable_ViewTransition as ViewTransition,
unstable_addTransitionType as addTransitionType,
startTransition,
} from 'react';
import {Resizable} from 're-resizable';
import {useStore, useStoreDispatch} from '../StoreContext';
import {monacoOptions} from './monacoOptions';
import {IconChevron} from '../Icons/IconChevron';
import prettyFormat from 'pretty-format';
import {CONFIG_PANEL_TRANSITION} from '../../lib/transitionTypes';
// @ts-expect-error - webpack asset/source loader handles .d.ts files as strings
import compilerTypeDefs from 'babel-plugin-react-compiler/dist/index.d.ts';
@ -36,7 +43,12 @@ export default function ConfigEditor({
display: isExpanded ? 'block' : 'none',
}}>
<ExpandedEditor
onToggle={setIsExpanded}
onToggle={() => {
startTransition(() => {
addTransitionType(CONFIG_PANEL_TRANSITION);
setIsExpanded(false);
});
}}
appliedOptions={appliedOptions}
/>
</div>
@ -44,7 +56,14 @@ export default function ConfigEditor({
style={{
display: !isExpanded ? 'block' : 'none',
}}>
<CollapsedEditor onToggle={setIsExpanded} />
<CollapsedEditor
onToggle={() => {
startTransition(() => {
addTransitionType(CONFIG_PANEL_TRANSITION);
setIsExpanded(true);
});
}}
/>
</div>
</>
);
@ -54,7 +73,7 @@ function ExpandedEditor({
onToggle,
appliedOptions,
}: {
onToggle: (expanded: boolean) => void;
onToggle: () => void;
appliedOptions: PluginOptions | null;
}): React.ReactElement {
const store = useStore();
@ -111,6 +130,8 @@ function ExpandedEditor({
: 'Invalid configs';
return (
<ViewTransition
update={{[CONFIG_PANEL_TRANSITION]: 'slide-in', default: 'none'}}>
<Resizable
minWidth={300}
maxWidth={600}
@ -120,7 +141,7 @@ function ExpandedEditor({
<div
className="absolute w-8 h-16 bg-blue-10 rounded-r-full flex items-center justify-center z-[2] cursor-pointer border border-l-0 border-gray-300"
title="Minimize config editor"
onClick={() => onToggle(false)}
onClick={onToggle}
style={{
top: '50%',
marginTop: '-32px',
@ -188,13 +209,14 @@ function ExpandedEditor({
</div>
</div>
</Resizable>
</ViewTransition>
);
}
function CollapsedEditor({
onToggle,
}: {
onToggle: (expanded: boolean) => void;
onToggle: () => void;
}): React.ReactElement {
return (
<div
@ -203,7 +225,7 @@ function CollapsedEditor({
<div
className="absolute w-10 h-16 bg-blue-10 hover:translate-x-2 transition-transform rounded-r-full flex items-center justify-center z-[2] cursor-pointer border border-gray-300"
title="Expand config editor"
onClick={() => onToggle(true)}
onClick={onToggle}
style={{
top: '50%',
marginTop: '-32px',

View File

@ -343,14 +343,10 @@ export default function Editor(): JSX.Element {
<ConfigEditor appliedOptions={appliedOptions} />
</div>
<div className="flex flex-1 min-w-0">
<div className="flex-1 min-w-[550px] sm:min-w-0">
<Input language={language} errors={errors} />
</div>
<div className="flex-1 min-w-[550px] sm:min-w-0">
<Output store={deferredStore} compilerOutput={mergedOutput} />
</div>
</div>
</div>
</>
);
}

View File

@ -13,11 +13,17 @@ import {
import invariant from 'invariant';
import type {editor} from 'monaco-editor';
import * as monaco from 'monaco-editor';
import {useEffect, useState} from 'react';
import {
useEffect,
useState,
unstable_ViewTransition as ViewTransition,
} from 'react';
import {renderReactCompilerMarkers} from '../../lib/reactCompilerMonacoDiagnostics';
import {useStore, useStoreDispatch} from '../StoreContext';
import TabbedWindow from '../TabbedWindow';
import {monacoOptions} from './monacoOptions';
import {CONFIG_PANEL_TRANSITION} from '../../lib/transitionTypes';
// @ts-expect-error TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
import React$Types from '../../node_modules/@types/react/index.d.ts';
@ -155,9 +161,13 @@ export default function Input({errors, language}: Props): JSX.Element {
const [activeTab, setActiveTab] = useState('Input');
return (
<div className="relative flex flex-col flex-none border-r border-gray-200">
<div className="!h-[calc(100vh_-_3.5rem)]">
<div className="flex flex-col h-full">
<ViewTransition
update={{
[CONFIG_PANEL_TRANSITION]: 'container',
default: 'none',
}}>
<div className="flex-1 min-w-[550px] sm:min-w-0">
<div className="flex flex-col h-full !h-[calc(100vh_-_3.5rem)] border-r border-gray-200">
<TabbedWindow
tabs={tabs}
activeTab={activeTab}
@ -165,6 +175,6 @@ export default function Input({errors, language}: Props): JSX.Element {
/>
</div>
</div>
</div>
</ViewTransition>
);
}

View File

@ -20,11 +20,19 @@ import parserBabel from 'prettier/plugins/babel';
import * as prettierPluginEstree from 'prettier/plugins/estree';
import * as prettier from 'prettier/standalone';
import {type Store} from '../../lib/stores';
import {memo, ReactNode, use, useState, Suspense} from 'react';
import {
memo,
ReactNode,
use,
useState,
Suspense,
unstable_ViewTransition as ViewTransition,
} from 'react';
import AccordionWindow from '../AccordionWindow';
import TabbedWindow from '../TabbedWindow';
import {monacoOptions} from './monacoOptions';
import {BabelFileResult} from '@babel/core';
import {CONFIG_PANEL_TRANSITION} from '../../lib/transitionTypes';
import {LRUCache} from 'lru-cache';
const MemoizedOutput = memo(Output);
@ -280,15 +288,26 @@ function OutputContent({store, compilerOutput}: Props): JSX.Element {
if (!store.showInternals) {
return (
<ViewTransition
update={{
[CONFIG_PANEL_TRANSITION]: 'container',
default: 'none',
}}>
<TabbedWindow
tabs={tabs}
activeTab={activeTab}
onTabChange={setActiveTab}
/>
</ViewTransition>
);
}
return (
<ViewTransition
update={{
[CONFIG_PANEL_TRANSITION]: 'accordion-container',
default: 'none',
}}>
<AccordionWindow
defaultTab={store.showInternals ? 'HIR' : 'Output'}
setTabsOpen={setTabsOpen}
@ -296,6 +315,7 @@ function OutputContent({store, compilerOutput}: Props): JSX.Element {
tabs={tabs}
changedPasses={changedPasses}
/>
</ViewTransition>
);
}

View File

@ -17,6 +17,7 @@ export default function TabbedWindow({
onTabChange: (tab: string) => void;
}): React.ReactElement {
return (
<div className="flex-1 min-w-[550px] sm:min-w-0">
<div className="flex flex-col h-full max-w-full">
<div className="flex p-2 flex-shrink-0">
{Array.from(tabs.keys()).map(tab => {
@ -39,5 +40,6 @@ export default function TabbedWindow({
{tabs.get(activeTab)}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,8 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export const CONFIG_PANEL_TRANSITION = 'config-panel';

View File

@ -11,6 +11,7 @@ const path = require('path');
const nextConfig = {
experimental: {
reactCompiler: true,
viewTransition: true,
},
reactStrictMode: true,
webpack: (config, options) => {

View File

@ -69,3 +69,42 @@
scrollbar-width: none; /* Firefox */
}
}
::view-transition-old(.slide-in) {
animation-name: slideOutLeft;
}
::view-transition-new(.slide-in) {
animation-name: slideInLeft;
}
::view-transition-group(.slide-in) {
z-index: 1;
}
@keyframes slideOutLeft {
from {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}
@keyframes slideInLeft {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
::view-transition-old(.container),
::view-transition-new(.container) {
height: 100%;
}
::view-transition-old(.accordion-container),
::view-transition-new(.accordion-container) {
height: 100%;
object-fit: none;
object-position: left;
}