Repository: vercel/next.js
Pull request: [devtools] port devtools-indicator position to dispatcher

### Why?

Since the DevTools Panel and the DevTools Indicator are decoupled, use the dispatcher to communicate the indicator position from the DevTools Panel (Settings tab).

Changed files:
filename: packages/next/src/next-devtools/dev-overlay/components/devtools-indicator/devtools-indicator.tsx
patch: @@ -15,8 +15,8 @@ import {
   ACTION_ERROR_OVERLAY_CLOSE,
   STORAGE_KEY_POSITION,
   ACTION_DEVTOOLS_PANEL_CLOSE,
+  ACTION_DEVTOOLS_POSITION,
 } from '../../shared'
-import { getInitialPosition } from '../errors/dev-tools-indicator/dev-tools-info/preferences'
 import { Draggable } from '../errors/dev-tools-indicator/draggable'
 
 const INDICATOR_PADDING = 20
@@ -35,9 +35,8 @@ export function DevToolsIndicator({
   scale: DevToolsScale
 }) {
   const [open, setOpen] = useState(false)
-  const [position, setPosition] = useState(getInitialPosition())
 
-  const [vertical, horizontal] = position.split('-', 2)
+  const [vertical, horizontal] = state.devToolsPosition.split('-', 2)
 
   const toggleErrorOverlay = () => {
     dispatch({ type: ACTION_DEVTOOLS_PANEL_CLOSE })
@@ -66,10 +65,13 @@ export function DevToolsIndicator({
       <Draggable
         padding={INDICATOR_PADDING}
         onDragStart={() => setOpen(false)}
-        position={position}
+        position={state.devToolsPosition}
         setPosition={(p) => {
+          dispatch({
+            type: ACTION_DEVTOOLS_POSITION,
+            devToolsPosition: p,
+          })
           localStorage.setItem(STORAGE_KEY_POSITION, p)
-          setPosition(p)
         }}
       >
         {/* Trigger */}

filename: packages/next/src/next-devtools/dev-overlay/components/errors/dev-tools-indicator/dev-tools-indicator.stories.tsx
patch: @@ -61,6 +61,7 @@ const state: OverlayState = {
   isErrorOverlayOpen: false,
   // TODO: This will be handled on the next stack——with proper story.
   isDevToolsPanelOpen: false,
+  devToolsPosition: 'bottom-left',
 }
 
 export const StaticRoute: Story = {

filename: packages/next/src/next-devtools/dev-overlay/dev-overlay.stories.tsx
patch: @@ -7,6 +7,8 @@ import imgApp from './app.png'
 import { useReducer } from 'react'
 import { DevOverlay } from './dev-overlay'
 import {
+  ACTION_DEVTOOLS_POSITION,
+  ACTION_DEVTOOLS_PANEL_CLOSE,
   ACTION_DEVTOOLS_PANEL_TOGGLE,
   ACTION_ERROR_OVERLAY_CLOSE,
   ACTION_ERROR_OVERLAY_OPEN,
@@ -96,6 +98,7 @@ const initialState: OverlayState = {
   },
   isErrorOverlayOpen: false,
   isDevToolsPanelOpen: false,
+  devToolsPosition: 'bottom-left',
 }
 
 function useOverlayReducer() {
@@ -114,6 +117,12 @@ function useOverlayReducer() {
         case ACTION_DEVTOOLS_PANEL_TOGGLE: {
           return { ...state, isDevToolsPanelOpen: !state.isDevToolsPanelOpen }
         }
+        case ACTION_DEVTOOLS_PANEL_CLOSE: {
+          return { ...state, isDevToolsPanelOpen: false }
+        }
+        case ACTION_DEVTOOLS_POSITION: {
+          return { ...state, devToolsPosition: action.devToolsPosition }
+        }
         default: {
           return state
         }

filename: packages/next/src/next-devtools/dev-overlay/shared.ts
patch: @@ -8,6 +8,8 @@ import type { DevIndicatorServerState } from '../../server/dev/dev-indicator-ser
 import { parseStack } from '../../server/lib/parse-stack'
 import { isConsoleError } from '../shared/console-error'
 
+export type Corners = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
+
 type FastRefreshState =
   /** No refresh in progress. */
   | { type: 'idle' }
@@ -30,6 +32,7 @@ export interface OverlayState {
   routerType: 'pages' | 'app'
   isErrorOverlayOpen: boolean
   isDevToolsPanelOpen: boolean
+  devToolsPosition: Corners
 }
 export type OverlayDispatch = React.Dispatch<DispatcherEvent>
 
@@ -57,6 +60,8 @@ export const ACTION_DEVTOOLS_PANEL_OPEN = 'devtools-panel-open'
 export const ACTION_DEVTOOLS_PANEL_CLOSE = 'devtools-panel-close'
 export const ACTION_DEVTOOLS_PANEL_TOGGLE = 'devtools-panel-toggle'
 
+export const ACTION_DEVTOOLS_POSITION = 'devtools-position'
+
 export const STORAGE_KEY_THEME = '__nextjs-dev-tools-theme'
 export const STORAGE_KEY_POSITION = '__nextjs-dev-tools-position'
 export const STORAGE_KEY_SCALE = '__nextjs-dev-tools-scale'
@@ -138,6 +143,11 @@ export interface DevToolsPanelToggleAction {
   type: typeof ACTION_DEVTOOLS_PANEL_TOGGLE
 }
 
+export interface DevToolsIndicatorPositionAction {
+  type: typeof ACTION_DEVTOOLS_POSITION
+  devToolsPosition: Corners
+}
+
 export type DispatcherEvent =
   | BuildOkAction
   | BuildErrorAction
@@ -159,6 +169,7 @@ export type DispatcherEvent =
   | DevToolsPanelOpenAction
   | DevToolsPanelCloseAction
   | DevToolsPanelToggleAction
+  | DevToolsIndicatorPositionAction
 
 const REACT_ERROR_STACK_BOTTOM_FRAME_REGEX =
   // 1st group: v8
@@ -198,6 +209,7 @@ export const INITIAL_OVERLAY_STATE: Omit<
   versionInfo: { installed: '0.0.0', staleness: 'unknown' },
   debugInfo: { devtoolsFrontendUrl: undefined },
   isDevToolsPanelOpen: false,
+  devToolsPosition: 'bottom-left',
 }
 
 function getInitialState(
@@ -369,6 +381,9 @@ export function useErrorOverlayReducer(
         case ACTION_DEVTOOLS_PANEL_TOGGLE: {
           return { ...state, isDevToolsPanelOpen: !state.isDevToolsPanelOpen }
         }
+        case ACTION_DEVTOOLS_POSITION: {
+          return { ...state, devToolsPosition: action.devToolsPosition }
+        }
         default: {
           return state
         }