Repository: facebook/react
Pull request: [playground] Allow accordion tabs to open on error

There was a bug where the other output passes (aside from the "Output" tab) were unable to open on compiler error. This PR still allows for the "Output" tab to automatically open on error, but also allows other tabs to be opened. 

https://github.com/user-attachments/assets/157bf5d6-c289-46fd-bafb-073c2e0ff52b


Changed files:
filename: compiler/apps/playground/components/AccordionWindow.tsx
patch: @@ -22,7 +22,6 @@ export default function AccordionWindow(props: {
   tabsOpen: Set<string>;
   setTabsOpen: (newTab: Set<string>) => void;
   changedPasses: Set<string>;
-  isFailure: boolean;
 }): React.ReactElement {
   return (
     <div className="flex-1 min-w-[550px] sm:min-w-0">
@@ -36,7 +35,6 @@ export default function AccordionWindow(props: {
               tabsOpen={props.tabsOpen}
               setTabsOpen={props.setTabsOpen}
               hasChanged={props.changedPasses.has(name)}
-              isFailure={props.isFailure}
             />
           );
         })}
@@ -51,7 +49,6 @@ function AccordionWindowItem({
   tabsOpen,
   setTabsOpen,
   hasChanged,
-  isFailure,
 }: {
   name: string;
   tabs: TabsRecord;
@@ -61,7 +58,7 @@ function AccordionWindowItem({
   isFailure: boolean;
 }): React.ReactElement {
   const id = useId();
-  const isShow = isFailure ? name === 'Output' : tabsOpen.has(name);
+  const isShow = tabsOpen.has(name);
 
   const transitionName = `accordion-window-item-${id}`;
 

filename: compiler/apps/playground/components/Editor/Output.tsx
patch: @@ -27,6 +27,8 @@ import {
   useState,
   Suspense,
   unstable_ViewTransition as ViewTransition,
+  unstable_addTransitionType as addTransitionType,
+  startTransition,
 } from 'react';
 import AccordionWindow from '../AccordionWindow';
 import TabbedWindow from '../TabbedWindow';
@@ -35,6 +37,7 @@ import {BabelFileResult} from '@babel/core';
 import {
   CONFIG_PANEL_TRANSITION,
   TOGGLE_INTERNALS_TRANSITION,
+  EXPAND_ACCORDION_TRANSITION,
 } from '../../lib/transitionTypes';
 import {LRUCache} from 'lru-cache';
 
@@ -265,8 +268,22 @@ function OutputContent({store, compilerOutput}: Props): JSX.Element {
    * Update the active tab back to the output or errors tab when the compilation state
    * changes between success/failure.
    */
-
+  const [previousOutputKind, setPreviousOutputKind] = useState(
+    compilerOutput.kind,
+  );
   const isFailure = compilerOutput.kind !== 'ok';
+
+  if (compilerOutput.kind !== previousOutputKind) {
+    setPreviousOutputKind(compilerOutput.kind);
+    if (isFailure) {
+      startTransition(() => {
+        addTransitionType(EXPAND_ACCORDION_TRANSITION);
+        setTabsOpen(prev => new Set(prev).add('Output'));
+        setActiveTab('Output');
+      });
+    }
+  }
+
   const changedPasses: Set<string> = new Set(['Output', 'HIR']); // Initial and final passes should always be bold
   let lastResult: string = '';
   for (const [passName, results] of compilerOutput.results) {
@@ -295,8 +312,6 @@ function OutputContent({store, compilerOutput}: Props): JSX.Element {
           tabs={tabs}
           activeTab={activeTab}
           onTabChange={setActiveTab}
-          // Display the Output tab on compilation failure
-          activeTabOverride={isFailure ? 'Output' : undefined}
         />
       </ViewTransition>
     );
@@ -315,7 +330,6 @@ function OutputContent({store, compilerOutput}: Props): JSX.Element {
         tabsOpen={tabsOpen}
         tabs={tabs}
         changedPasses={changedPasses}
-        isFailure={isFailure}
       />
     </ViewTransition>
   );

filename: compiler/apps/playground/components/TabbedWindow.tsx
patch: @@ -17,15 +17,11 @@ export default function TabbedWindow({
   tabs,
   activeTab,
   onTabChange,
-  activeTabOverride,
 }: {
   tabs: Map<string, React.ReactNode>;
   activeTab: string;
   onTabChange: (tab: string) => void;
-  activeTabOverride?: string;
 }): React.ReactElement {
-  const currentActiveTab = activeTabOverride ? activeTabOverride : activeTab;
-
   const id = useId();
   const transitionName = `tab-highlight-${id}`;
 
@@ -41,7 +37,7 @@ export default function TabbedWindow({
       <div className="flex flex-col h-full max-w-full">
         <div className="flex p-2 flex-shrink-0">
           {Array.from(tabs.keys()).map(tab => {
-            const isActive = currentActiveTab === tab;
+            const isActive = activeTab === tab;
             return (
               <button
                 key={tab}
@@ -77,7 +73,7 @@ export default function TabbedWindow({
           })}
         </div>
         <div className="flex-1 overflow-hidden w-full h-full">
-          {tabs.get(currentActiveTab)}
+          {tabs.get(activeTab)}
         </div>
       </div>
     </div>