Repository: vercel/next.js
Pull request: wrap devtools stories in context providers

Wraps stories in context providers, and replaces any old arguments that were being passed before the props -> context refactor in devtools

Closes NEXT-4642

Changed files:
filename: packages/next/.storybook/preview.tsx
patch: @@ -1,5 +1,6 @@
 import type { Preview } from '@storybook/react'
 import { useInsertionEffect } from 'react'
+import { withDevOverlayContexts } from '../src/next-devtools/dev-overlay/storybook/with-dev-overlay-contexts'
 
 function CreatePortalNode() {
   useInsertionEffect(() => {
@@ -63,6 +64,7 @@ const preview: Preview = {
     },
   },
   decorators: [
+    withDevOverlayContexts(),
     (Story) => (
       <>
         <CreatePortalNode />

filename: packages/next/src/next-devtools/dev-overlay/components/devtools-indicator/devtools-indicator.stories.tsx
patch: @@ -1,9 +1,9 @@
 import type { Meta, StoryObj } from '@storybook/react'
-import type { OverlayState } from '../../shared'
 
 import { DevToolsIndicator } from './devtools-indicator'
-import { INITIAL_OVERLAY_STATE } from '../../shared'
 import { withShadowPortal } from '../../storybook/with-shadow-portal'
+import { withDevOverlayContexts } from '../../storybook/with-dev-overlay-contexts'
+import { INITIAL_OVERLAY_STATE, type OverlayState } from '../../shared'
 
 const meta: Meta<typeof DevToolsIndicator> = {
   component: DevToolsIndicator,
@@ -35,35 +35,31 @@ const meta: Meta<typeof DevToolsIndicator> = {
     ),
   ],
 }
-
-export default meta
-type Story = StoryObj<typeof DevToolsIndicator>
-
 const state: OverlayState = {
   ...INITIAL_OVERLAY_STATE,
   routerType: 'app',
   isErrorOverlayOpen: false,
 }
 
-export const Default: Story = {
-  args: {
-    state,
-    dispatch: () => {},
-  },
-}
+export default meta
+type Story = StoryObj<typeof DevToolsIndicator>
+
+export const Default: Story = {}
 
 export const SingleError: Story = {
-  args: {
-    errorCount: 1,
-    state,
-    dispatch: () => {},
-  },
+  decorators: [
+    withDevOverlayContexts({
+      state,
+      totalErrorCount: 1,
+    }),
+  ],
 }
 
 export const MultipleErrors: Story = {
-  args: {
-    errorCount: 3,
-    state,
-    dispatch: () => {},
-  },
+  decorators: [
+    withDevOverlayContexts({
+      state,
+      totalErrorCount: 3,
+    }),
+  ],
 }

filename: packages/next/src/next-devtools/dev-overlay/components/devtools-indicator/next-logo.stories.tsx
patch: @@ -1,19 +1,10 @@
 import type { Meta, StoryObj } from '@storybook/react'
 import { NextLogo } from './next-logo'
 import { withShadowPortal } from '../../storybook/with-shadow-portal'
-// TODO: NEXT-4642
-const StoryBookNextLogo = (
-  _: {
-    issueCount: number
-    isDevBuilding: boolean
-    isDevRendering: boolean
-  } & Record<string, unknown>
-) => {
-  return <NextLogo onTriggerClick={() => {}} />
-}
+import { withDevOverlayContexts } from '../../storybook/with-dev-overlay-contexts'
 
-const meta: Meta<typeof StoryBookNextLogo> = {
-  component: StoryBookNextLogo,
+const meta: Meta<typeof NextLogo> = {
+  component: NextLogo,
   parameters: {
     layout: 'centered',
   },
@@ -24,68 +15,100 @@ const meta: Meta<typeof StoryBookNextLogo> = {
 }
 
 export default meta
-type Story = StoryObj<typeof StoryBookNextLogo>
+type Story = StoryObj<typeof NextLogo>
 
 export const NoIssues: Story = {
-  args: {
-    issueCount: 0,
-    isDevBuilding: false,
-    isDevRendering: false,
-  },
+  decorators: [
+    withDevOverlayContexts({
+      totalErrorCount: 0,
+      state: {
+        buildingIndicator: false,
+        renderingIndicator: false,
+      },
+    }),
+  ],
 }
 
 export const SingleIssue: Story = {
-  args: {
-    issueCount: 1,
-    isDevBuilding: false,
-    isDevRendering: false,
-  },
+  decorators: [
+    withDevOverlayContexts({
+      totalErrorCount: 1,
+      state: {
+        buildingIndicator: false,
+        renderingIndicator: false,
+      },
+    }),
+  ],
 }
 
 export const MultipleIssues: Story = {
-  args: {
-    issueCount: 5,
-    isDevBuilding: false,
-    isDevRendering: false,
-  },
+  decorators: [
+    withDevOverlayContexts({
+      totalErrorCount: 5,
+      state: {
+        buildingIndicator: false,
+        renderingIndicator: false,
+      },
+    }),
+  ],
 }
 
 export const ManyIssues: Story = {
-  args: {
-    issueCount: 99,
-    isDevBuilding: false,
-    isDevRendering: false,
-  },
+  decorators: [
+    withDevOverlayContexts({
+      totalErrorCount: 99,
+      state: {
+        buildingIndicator: false,
+        renderingIndicator: false,
+      },
+    }),
+  ],
 }
 
 export const Building: Story = {
-  args: {
-    issueCount: 0,
-    isDevBuilding: true,
-    isDevRendering: false,
-  },
+  decorators: [
+    withDevOverlayContexts({
+      totalErrorCount: 0,
+      state: {
+        buildingIndicator: true,
+        renderingIndicator: false,
+      },
+    }),
+  ],
 }
 
 export const BuildingWithError: Story = {
-  args: {
-    issueCount: 1,
-    isDevBuilding: true,
-    isDevRendering: false,
-  },
+  decorators: [
+    withDevOverlayContexts({
+      totalErrorCount: 1,
+      state: {
+        buildingIndicator: true,
+        renderingIndicator: false,
+      },
+    }),
+  ],
 }
 
 export const Rendering: Story = {
-  args: {
-    issueCount: 0,
-    isDevBuilding: false,
-    isDevRendering: true,
-  },
+  decorators: [
+    withDevOverlayContexts({
+      totalErrorCount: 0,
+      state: {
+        buildingIndicator: false,
+        renderingIndicator: true,
+      },
+    }),
+  ],
 }
 
 export const RenderingWithError: Story = {
-  args: {
-    issueCount: 1,
-    isDevBuilding: false,
-    isDevRendering: true,
-  },
+  decorators: [
+    withDevOverlayContexts({
+      totalErrorCount: 1,
+      state: {
+        buildingIndicator: false,
+        renderingIndicator: true,
+      },
+    }),
+  ],
 }

filename: packages/next/src/next-devtools/dev-overlay/storybook/with-dev-overlay-contexts.tsx
patch: @@ -0,0 +1,67 @@
+import { useRef, useState, type Dispatch, type SetStateAction } from 'react'
+import { DevOverlayContext } from '../../dev-overlay.browser'
+import { RenderErrorContext } from '../dev-overlay'
+import { PanelRouterContext, type PanelStateKind } from '../menu/context'
+import { INITIAL_OVERLAY_STATE } from '../shared'
+import type { OverlayState, DispatcherEvent } from '../shared'
+import type { ReadyRuntimeError } from '../utils/get-error-by-type'
+
+interface WithDevOverlayContextsProps {
+  state?: Partial<OverlayState>
+  dispatch?: (action: DispatcherEvent) => void
+  runtimeErrors?: ReadyRuntimeError[]
+  totalErrorCount?: number
+  panel?: PanelStateKind | null
+  setPanel?: Dispatch<SetStateAction<PanelStateKind | null>>
+  selectedIndex?: number
+  setSelectedIndex?: Dispatch<SetStateAction<number>>
+}
+
+export const withDevOverlayContexts =
+  (props?: WithDevOverlayContextsProps) => (Story: any) => {
+    const [panel, setPanel] = useState<PanelStateKind | null>(
+      props?.panel ?? null
+    )
+    const [selectedIndex, setSelectedIndex] = useState(
+      props?.selectedIndex ?? -1
+    )
+    const triggerRef = useRef<HTMLButtonElement>(null)
+
+    const defaultState: OverlayState = {
+      ...INITIAL_OVERLAY_STATE,
+      routerType: 'app',
+      isErrorOverlayOpen: false,
+      ...props?.state,
+    }
+
+    const defaultDispatch = props?.dispatch || (() => {})
+
+    return (
+      <DevOverlayContext.Provider
+        value={{
+          state: defaultState,
+          dispatch: defaultDispatch,
+          getSquashedHydrationErrorDetails: () => null,
+        }}
+      >
+        <RenderErrorContext.Provider
+          value={{
+            runtimeErrors: props?.runtimeErrors ?? [],
+            totalErrorCount: props?.totalErrorCount ?? 0,
+          }}
+        >
+          <PanelRouterContext.Provider
+            value={{
+              panel,
+              setPanel: props?.setPanel ?? setPanel,
+              triggerRef,
+              selectedIndex,
+              setSelectedIndex: props?.setSelectedIndex ?? setSelectedIndex,
+            }}
+          >
+            <Story />
+          </PanelRouterContext.Provider>
+        </RenderErrorContext.Provider>
+      </DevOverlayContext.Provider>
+    )
+  }

filename: pnpm-lock.yaml
patch: @@ -3461,12 +3461,6 @@ packages:
     peerDependencies:
       eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
 
-  '@eslint-community/eslint-utils@4.5.1':
-    resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    peerDependencies:
-      eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
-
   '@eslint-community/eslint-utils@4.7.0':
     resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -8946,10 +8940,6 @@ packages:
     resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
 
-  eslint-visitor-keys@4.2.0:
-    resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
-    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
   eslint-visitor-keys@4.2.1:
     resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -14014,6 +14004,7 @@ packages:
     engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
     deprecated: |-
       You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.
+
       (For a CapTP with native promises, see @endo/eventual-send and @endo/captp)
 
   qs@6.11.0:
@@ -15126,6 +15117,7 @@ packages:
   source-map@0.8.0-beta.0:
     resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
     engines: {node: '>= 8'}
+    deprecated: The work that was done in this beta branch won't be included in future versions
 
   sourcemap-codec@1.4.8:
     resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
@@ -15490,7 +15482,7 @@ packages:
   superagent@3.8.3:
     resolution: {integrity: sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==}
     engines: {node: '>= 4.0'}
-    deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net
+    deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net
 
   superstruct@1.0.3:
     resolution: {integrity: sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==}
@@ -18704,11 +18696,6 @@ snapshots:
       eslint: 9.12.0
       eslint-visitor-keys: 3.4.3
 
-  '@eslint-community/eslint-utils@4.5.1(eslint@9.12.0)':
-    dependencies:
-      eslint: 9.12.0
-      eslint-visitor-keys: 3.4.3
-
   '@eslint-community/eslint-utils@4.7.0(eslint@9.12.0)':
     dependencies:
       eslint: 9.12.0
@@ -22923,7 +22910,7 @@ snapshots:
     dependencies:
       '@babel/runtime': 7.27.0
       cosmiconfig: 7.1.0
-      resolve: 1.22.8
+      resolve: 1.22.10
 
   babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.26.10):
     dependencies:
@@ -25751,8 +25738,6 @@ snapshots:
 
   eslint-visitor-keys@3.4.3: {}
 
-  eslint-visitor-keys@4.2.0: {}
-
   eslint-visitor-keys@4.2.1: {}
 
   eslint@7.32.0:
@@ -25888,7 +25873,7 @@ snapshots:
 
   eslint@9.12.0:
     dependencies:
-      '@eslint-community/eslint-utils': 4.5.1(eslint@9.12.0)
+      '@eslint-community/eslint-utils': 4.7.0(eslint@9.12.0)
       '@eslint-community/regexpp': 4.11.1
       '@eslint/config-array': 0.18.0
       '@eslint/core': 0.6.0
@@ -25906,7 +25891,7 @@ snapshots:
       debug: 4.4.0
       escape-string-regexp: 4.0.0
       eslint-scope: 8.1.0
-      eslint-visitor-keys: 4.2.0
+      eslint-visitor-keys: 4.2.1
       espree: 10.2.0
       esquery: 1.6.0
       esutils: 2.0.3
@@ -25930,7 +25915,7 @@ snapshots:
     dependencies:
       acorn: 8.14.0
       acorn-jsx: 5.3.2(acorn@8.14.0)
-      eslint-visitor-keys: 4.2.0
+      eslint-visitor-keys: 4.2.1
 
   espree@7.3.1:
     dependencies: