diff --git a/lerna.json b/lerna.json index 77fccd94894bd..db4ae4c9a6585 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "15.4.2-canary.8" + "version": "15.4.2-canary.9" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 149dc8de75c40..357e92f6ed750 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index c6fb182b68a9c..64142c2219991 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "description": "ESLint configuration used by Next.js.", "main": "index.js", "license": "MIT", @@ -10,7 +10,7 @@ }, "homepage": "https://nextjs.org/docs/app/api-reference/config/eslint", "dependencies": { - "@next/eslint-plugin-next": "15.4.2-canary.8", + "@next/eslint-plugin-next": "15.4.2-canary.9", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index 2e8f7ddc39309..cd7e78398e31d 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,7 +1,7 @@ { "name": "@next/eslint-plugin-internal", "private": true, - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "description": "ESLint plugin for working on Next.js.", "exports": { ".": "./src/eslint-plugin-internal.js" diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index e10a89177c3b2..829eb68107691 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -1,6 +1,6 @@ { "name": "@next/eslint-plugin-next", - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "description": "ESLint plugin for Next.js.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/font/package.json b/packages/font/package.json index c1667408a518b..761b105acfeca 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,7 +1,7 @@ { "name": "@next/font", "private": true, - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "repository": { "url": "vercel/next.js", "directory": "packages/font" diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index fbc69e66a534c..859912709ec93 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index cd8785ac2b90b..a55be364098a7 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 18a73cdd6ed68..092f6c363e9b2 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 0e3e07f72d093..33dd4a8fb59b6 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 28546e10ea5e3..5acbd4779acc2 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index 5f2fce7df52e2..bd478a0e5881a 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index 544a9407f97d7..bec097ae0106b 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-rspack/package.json b/packages/next-rspack/package.json index f16c532e8846e..cf9cdfead72c1 100644 --- a/packages/next-rspack/package.json +++ b/packages/next-rspack/package.json @@ -1,6 +1,6 @@ { "name": "next-rspack", - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "repository": { "url": "vercel/next.js", "directory": "packages/next-rspack" diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index ef8446073cfed..0fadde22f9257 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "private": true, "files": [ "native/" diff --git a/packages/next/package.json b/packages/next/package.json index 125e9cf812818..9bb9b72f3e668 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "15.4.2-canary.8", + "version": "15.4.2-canary.9", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -100,7 +100,7 @@ ] }, "dependencies": { - "@next/env": "15.4.2-canary.8", + "@next/env": "15.4.2-canary.9", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -163,11 +163,11 @@ "@jest/types": "29.5.0", "@mswjs/interceptors": "0.23.0", "@napi-rs/triples": "1.2.0", - "@next/font": "15.4.2-canary.8", - "@next/polyfill-module": "15.4.2-canary.8", - "@next/polyfill-nomodule": "15.4.2-canary.8", - "@next/react-refresh-utils": "15.4.2-canary.8", - "@next/swc": "15.4.2-canary.8", + "@next/font": "15.4.2-canary.9", + "@next/polyfill-module": "15.4.2-canary.9", + "@next/polyfill-nomodule": "15.4.2-canary.9", + "@next/react-refresh-utils": "15.4.2-canary.9", + "@next/swc": "15.4.2-canary.9", "@opentelemetry/api": "1.6.0", "@playwright/test": "1.51.1", "@rspack/core": "1.4.5", diff --git a/packages/next/src/build/templates/app-page.ts b/packages/next/src/build/templates/app-page.ts index d5024b99fc42a..e04c13e0185f4 100644 --- a/packages/next/src/build/templates/app-page.ts +++ b/packages/next/src/build/templates/app-page.ts @@ -1075,6 +1075,16 @@ export async function handler( // should also be the case for a resume request because it's completed // as a server render (rather than a static render). if (!didPostpone || minimalMode) { + // If we're in test mode, we should add a sentinel chunk to the response + // that's between the static and dynamic parts so we can compare the + // chunks and add assertions. + if (process.env.__NEXT_TEST_MODE && minimalMode && isRoutePPREnabled) { + // As we're in minimal mode, the static part would have already been + // streamed first. The only part that this streams is the dynamic part + // so we should FIRST stream the sentinel and THEN the dynamic part. + body.unshift(createPPRBoundarySentinel()) + } + return sendRenderResult({ req, res, @@ -1093,7 +1103,7 @@ export async function handler( if (isDebugStaticShell || isDebugDynamicAccesses) { // Since we're not resuming the render, we need to at least add the // closing body and html tags to create valid HTML. - body.chain( + body.push( new ReadableStream({ start(controller) { controller.enqueue(ENCODED_TAGS.CLOSED.BODY_AND_HTML) @@ -1113,11 +1123,18 @@ export async function handler( }) } + // If we're in test mode, we should add a sentinel chunk to the response + // that's between the static and dynamic parts so we can compare the + // chunks and add assertions. + if (process.env.__NEXT_TEST_MODE) { + body.push(createPPRBoundarySentinel()) + } + // This request has postponed, so let's create a new transformer that the // dynamic data can pipe to that will attach the dynamic data to the end // of the response. const transformer = new TransformStream() - body.chain(transformer.readable) + body.push(transformer.readable) // Perform the render again, but this time, provide the postponed state. // We don't await because we want the result to start streaming now, and @@ -1208,3 +1225,20 @@ export async function handler( throw err } } + +// TODO: omit this from production builds, only test builds should include it +/** + * Creates a readable stream that emits a PPR boundary sentinel. + * + * @returns A readable stream that emits a PPR boundary sentinel. + */ +function createPPRBoundarySentinel() { + return new ReadableStream({ + start(controller) { + controller.enqueue( + new TextEncoder().encode('') + ) + controller.close() + }, + }) +} diff --git a/packages/next/src/next-devtools/dev-overlay/components/dialog/dialog.tsx b/packages/next/src/next-devtools/dev-overlay/components/dialog/dialog.tsx index 8a03d61fce311..4ed779c64d743 100644 --- a/packages/next/src/next-devtools/dev-overlay/components/dialog/dialog.tsx +++ b/packages/next/src/next-devtools/dev-overlay/components/dialog/dialog.tsx @@ -87,6 +87,7 @@ const Dialog: React.FC = function Dialog({ ref={dialogRef} tabIndex={-1} data-nextjs-dialog + data-nextjs-scrollable-content role={role} aria-labelledby={ariaLabelledBy} aria-describedby={ariaDescribedBy} diff --git a/packages/next/src/next-devtools/dev-overlay/components/dialog/styles.ts b/packages/next/src/next-devtools/dev-overlay/components/dialog/styles.ts index 260e86d8a9a57..60d45e2262890 100644 --- a/packages/next/src/next-devtools/dev-overlay/components/dialog/styles.ts +++ b/packages/next/src/next-devtools/dev-overlay/components/dialog/styles.ts @@ -41,36 +41,6 @@ export const styles = css` outline: 0; } - [data-nextjs-dialog], - [data-nextjs-dialog] * { - &::-webkit-scrollbar { - width: 6px; - height: 6px; - border-radius: 0 0 1rem 1rem; - margin-bottom: 1rem; - } - - &::-webkit-scrollbar-button { - display: none; - } - - &::-webkit-scrollbar-track { - border-radius: 0 0 1rem 1rem; - background-color: var(--color-background-100); - } - - &::-webkit-scrollbar-thumb { - border-radius: 1rem; - background-color: var(--color-gray-500); - } - } - - /* Place overflow: hidden on this so we can break out from [data-nextjs-dialog] */ - [data-nextjs-dialog-sizer] { - overflow: hidden; - border-radius: inherit; - } - [data-nextjs-dialog-backdrop] { opacity: 0; transition: opacity var(--transition-duration) var(--timing-overlay); diff --git a/packages/next/src/next-devtools/dev-overlay/components/errors/dev-tools-indicator/dev-tools-info/user-preferences.tsx b/packages/next/src/next-devtools/dev-overlay/components/errors/dev-tools-indicator/dev-tools-info/user-preferences.tsx index 37e8c13e9927f..114785b688b86 100644 --- a/packages/next/src/next-devtools/dev-overlay/components/errors/dev-tools-indicator/dev-tools-info/user-preferences.tsx +++ b/packages/next/src/next-devtools/dev-overlay/components/errors/dev-tools-indicator/dev-tools-info/user-preferences.tsx @@ -17,6 +17,7 @@ import { type DevToolsScale, } from './preferences' import { ShortcutRecorder } from './shortcut-recorder' +import { useRestartServer } from '../../error-overlay-toolbar/use-restart-server' export function UserPreferences({ hide, @@ -69,6 +70,7 @@ export function UserPreferencesBody({ scale: DevToolsScale setScale: (value: DevToolsScale) => void }) { + const { restartServer, isPending } = useRestartServer() const [theme, setTheme] = useState(getInitialTheme()) const handleThemeChange = (e: React.ChangeEvent) => { @@ -105,21 +107,6 @@ export function UserPreferencesBody({ setScale(value) } - function handleRestartDevServer(invalidatePersistentCache: boolean) { - let endpoint = '/__nextjs_restart_dev' - - if (invalidatePersistentCache) { - endpoint = '/__nextjs_restart_dev?invalidatePersistentCache' - } - - fetch(endpoint, { - method: 'POST', - }).then(() => { - // TODO: poll server status and reload when the server is back up. - // https://github.com/vercel/next.js/pull/80005 - }) - } - return ( <>

General

@@ -253,8 +240,9 @@ export function UserPreferencesBody({ data-restart-dev-server className="action-button" onClick={() => - handleRestartDevServer(/*invalidatePersistentCache*/ false) + restartServer({ invalidatePersistentCache: false }) } + disabled={isPending} > Restart @@ -279,8 +267,9 @@ export function UserPreferencesBody({ data-reset-bundler-cache className="action-button" onClick={() => - handleRestartDevServer(/*invalidatePersistentCache*/ true) + restartServer({ invalidatePersistentCache: true }) } + disabled={isPending} > Reset Cache @@ -408,6 +397,11 @@ export const DEV_TOOLS_INFO_USER_PREFERENCES_STYLES = css` } } + .preference-section button:disabled { + opacity: 0.6; + cursor: not-allowed; + } + :global(.icon) { width: 18px; height: 18px; diff --git a/packages/next/src/next-devtools/dev-overlay/components/errors/error-overlay-toolbar/restart-server-button.tsx b/packages/next/src/next-devtools/dev-overlay/components/errors/error-overlay-toolbar/restart-server-button.tsx index 72312d32960f6..b4758bfc29b24 100644 --- a/packages/next/src/next-devtools/dev-overlay/components/errors/error-overlay-toolbar/restart-server-button.tsx +++ b/packages/next/src/next-devtools/dev-overlay/components/errors/error-overlay-toolbar/restart-server-button.tsx @@ -5,6 +5,8 @@ import { type OverlayDispatch, } from '../../../shared' import type { SupportedErrorEvent } from '../../../container/runtime-error/render-error' +import { useRestartServer } from './use-restart-server' +import { css } from '../../../utils/css' /** * When the Turbopack persistent cache is enabled, and the user reloads on a @@ -14,28 +16,29 @@ import type { SupportedErrorEvent } from '../../../container/runtime-error/rende * telemetry on how often this is used. */ export function RestartServerButton({ showButton }: { showButton: boolean }) { + const { restartServer, isPending } = useRestartServer() + if (!showButton) { return null } - function handleClick() { - // TODO: Use Client Action for transition indicator when DevTools is isolated. - fetch('/__nextjs_restart_dev?invalidatePersistentCache', { - method: 'POST', - }).then(() => { - // TODO: poll server status and reload when the server is back up. - // https://github.com/vercel/next.js/pull/80005 - }) - } - return ( ) } @@ -84,7 +87,7 @@ export function usePersistentCacheErrorDetection({ }, [errors, dispatch]) } -export const RESTART_SERVER_BUTTON_STYLES = ` +export const RESTART_SERVER_BUTTON_STYLES = css` .restart-dev-server-button { display: flex; justify-content: center; @@ -105,4 +108,18 @@ export const RESTART_SERVER_BUTTON_STYLES = ` font-weight: 500; line-height: var(--size-16); } + + .restart-dev-server-button:disabled { + opacity: 0.6; + cursor: not-allowed; + } + + @keyframes refresh-clock-wise-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } ` diff --git a/packages/next/src/next-devtools/dev-overlay/components/errors/error-overlay-toolbar/use-restart-server.ts b/packages/next/src/next-devtools/dev-overlay/components/errors/error-overlay-toolbar/use-restart-server.ts new file mode 100644 index 0000000000000..74b5684d76c87 --- /dev/null +++ b/packages/next/src/next-devtools/dev-overlay/components/errors/error-overlay-toolbar/use-restart-server.ts @@ -0,0 +1,92 @@ +import { useState } from 'react' + +export function useRestartServer() { + const [isPending, setIsPending] = useState(false) + + const restartServer = async ({ + invalidatePersistentCache, + }: { + invalidatePersistentCache: boolean + }): Promise => { + setIsPending(true) + + const url = invalidatePersistentCache + ? '/__nextjs_restart_dev?invalidatePersistentCache=1' + : '/__nextjs_restart_dev' + + let serverRestarted = false + + try { + const curId = await fetch('/__nextjs_server_status') + .then((res) => res.json()) + .then((data) => data.executionId as number) + .catch((error) => { + console.log( + '[Next.js DevTools] Failed to fetch server status while restarting dev server.', + error + ) + return null + }) + + if (!curId) { + console.log( + '[Next.js DevTools] Failed to get the current server execution ID while restarting dev server.' + ) + return + } + + const restartRes = await fetch(url, { + method: 'POST', + }) + + if (!restartRes.ok) { + // Use console log to avoid spamming the error overlay which users can't control. + console.log( + '[Next.js DevTools] Failed to fetch restart server endpoint. Status:', + restartRes.status + ) + return + } + + // Poll for server restart confirmation. + for (let i = 0; i < 10; i++) { + // generous 1 second delay for large apps. + await new Promise((resolveTimeout) => setTimeout(resolveTimeout, 1_000)) + + try { + const nextId = await fetch('/__nextjs_server_status') + .then((res) => res.json()) + .then((data) => data.executionId as number) + + // If the execution ID has changed, the server has restarted successfully. + if (curId !== nextId) { + serverRestarted = true + // Reload the page to ensure the connection to the new server. + window.location.reload() + return + } + } catch (e) { + continue + } + } + + console.log( + '[Next.js DevTools] Failed to restart server. Exhausted all polling attempts.' + ) + return + } catch (error) { + console.log('[Next.js DevTools] Failed to restart server.', error) + return + } finally { + // If server restarted, don't reset isPending since the page will reload. + if (!serverRestarted) { + setIsPending(false) + } + } + } + + return { + restartServer, + isPending, + } +} diff --git a/packages/next/src/next-devtools/dev-overlay/components/overview/segment-explorer.tsx b/packages/next/src/next-devtools/dev-overlay/components/overview/segment-explorer.tsx index bf8d532e4aaba..65062c242a5c1 100644 --- a/packages/next/src/next-devtools/dev-overlay/components/overview/segment-explorer.tsx +++ b/packages/next/src/next-devtools/dev-overlay/components/overview/segment-explorer.tsx @@ -96,26 +96,18 @@ function SegmentExplorerFooter({ ) } -export function PageSegmentTree({ - isAppRouter, - page, -}: { - isAppRouter: boolean - page: string -}) { +export function PageSegmentTree({ page }: { page: string }) { const tree = useSegmentTree() // Count active boundaries for the badge const activeBoundariesCount = useMemo(() => { - return isAppRouter ? countActiveBoundaries(tree) : 0 - }, [tree, isAppRouter]) + return countActiveBoundaries(tree) + }, [tree]) // Global reset handler const handleGlobalReset = useCallback(() => { - if (isAppRouter) { - traverseTreeAndResetBoundaries(tree) - } - }, [tree, isAppRouter]) + traverseTreeAndResetBoundaries(tree) + }, [tree]) return (
- {isAppRouter && } +
- {isAppRouter ? ( - - ) : ( -

Route Info currently is only available for the App Router.

- )} +
- {isAppRouter && ( - - )} +
) } diff --git a/packages/next/src/next-devtools/dev-overlay/menu/panel-router.css b/packages/next/src/next-devtools/dev-overlay/menu/panel-router.css new file mode 100644 index 0000000000000..736aae84b69af --- /dev/null +++ b/packages/next/src/next-devtools/dev-overlay/menu/panel-router.css @@ -0,0 +1,19 @@ +/* Panel content padding styles */ +.panel-content { + padding: 16px; + padding-top: 8px; + overflow: hidden; +} + +/* User preferences wrapper styles */ +.user-preferences-wrapper { + padding: 20px; + padding-top: 8px; + overflow: hidden; +} + +/* Panel route base styles */ +.panel-route { + opacity: var(--panel-opacity); + transition: var(--panel-transition); +} diff --git a/packages/next/src/next-devtools/dev-overlay/menu/panel-router.tsx b/packages/next/src/next-devtools/dev-overlay/menu/panel-router.tsx index 4ed52300053b7..bf06161a6157a 100644 --- a/packages/next/src/next-devtools/dev-overlay/menu/panel-router.tsx +++ b/packages/next/src/next-devtools/dev-overlay/menu/panel-router.tsx @@ -29,11 +29,14 @@ import { UserPreferencesBody } from '../components/errors/dev-tools-indicator/de import { useHideShortcutStorage } from '../components/errors/dev-tools-indicator/dev-tools-info/preferences' import { useShortcuts } from '../hooks/use-shortcuts' import { useUpdateAllPanelPositions } from '../components/devtools-indicator/devtools-indicator' +import './panel-router.css' const MenuPanel = () => { const { setPanel, setSelectedIndex } = usePanelRouterContext() const { state, dispatch } = useDevOverlayContext() const { totalErrorCount } = useRenderErrorContext() + const isAppRouter = state.routerType === 'app' + return ( { value: , onClick: () => setPanel('turbo-info'), }, - !!process.env.__NEXT_DEVTOOL_SEGMENT_EXPLORER && { - label: 'Route Info', - value: , - onClick: () => setPanel('segment-explorer'), - attributes: { - 'data-segment-explorer': true, + !!process.env.__NEXT_DEVTOOL_SEGMENT_EXPLORER && + isAppRouter && { + label: 'Route Info', + value: , + onClick: () => setPanel('segment-explorer'), + attributes: { + 'data-segment-explorer': true, + }, }, - }, { label: 'Preferences', value: , @@ -137,6 +141,7 @@ export const PanelRouter = () => { const { state } = useDevOverlayContext() const { triggerRef } = usePanelRouterContext() const toggleDevtools = useToggleDevtoolsVisibility() + const isAppRouter = state.routerType === 'app' const [hideShortcut, setHideShortcut] = useHideShortcutStorage() useShortcuts( @@ -185,12 +190,7 @@ export const PanelRouter = () => { /> } > -
+
{ - {process.env.__NEXT_DEVTOOL_SEGMENT_EXPLORER && ( + {process.env.__NEXT_DEVTOOL_SEGMENT_EXPLORER && isAppRouter && ( { }} header={} > - + )} @@ -245,12 +242,7 @@ export const PanelRouter = () => { closeOnClickOutside header={} > -
+
@@ -287,12 +279,7 @@ const UserPreferencesWrapper = ({ const updateAllPanelPositions = useUpdateAllPanelPositions() return ( -
+
{children}
diff --git a/packages/next/src/next-devtools/dev-overlay/panel/dynamic-panel.css b/packages/next/src/next-devtools/dev-overlay/panel/dynamic-panel.css new file mode 100644 index 0000000000000..6e9fb02c19677 --- /dev/null +++ b/packages/next/src/next-devtools/dev-overlay/panel/dynamic-panel.css @@ -0,0 +1,34 @@ +/* Panel container base styles with dynamic positioning and sizing */ +.dynamic-panel-container { + position: fixed; + z-index: 2147483646; + outline: none; + top: var(--panel-top, auto); + bottom: var(--panel-bottom, auto); + left: var(--panel-left, auto); + right: var(--panel-right, auto); + width: var(--panel-width); + height: var(--panel-height); + min-width: var(--panel-min-width); + min-height: var(--panel-min-height); + max-width: var(--panel-max-width); + max-height: var(--panel-max-height); +} + +/* Panel content container styles */ +.panel-content-container { + position: relative; + width: 100%; + height: 100%; + border: 1px solid var(--color-gray-alpha-400); + border-radius: var(--rounded-xl); + background: var(--color-background-100); + display: flex; + flex-direction: column; +} + +/* Draggable content area styles */ +.draggable-content { + flex: 1; + overflow: auto; +} diff --git a/packages/next/src/next-devtools/dev-overlay/panel/dynamic-panel.tsx b/packages/next/src/next-devtools/dev-overlay/panel/dynamic-panel.tsx index fe6ed3cd2834e..99551d7e38145 100644 --- a/packages/next/src/next-devtools/dev-overlay/panel/dynamic-panel.tsx +++ b/packages/next/src/next-devtools/dev-overlay/panel/dynamic-panel.tsx @@ -19,6 +19,7 @@ import { STORE_KEY_SHARED_PANEL_SIZE, } from '../shared' import { getIndicatorOffset } from '../utils/indicator-metrics' +import './dynamic-panel.css' function resolveCSSValue( value: string | number, @@ -235,23 +236,30 @@ export function DynamicPanel({
+ } >
{header} -
{children}
+
+ {children} +
{isResizable && ( <> diff --git a/packages/next/src/next-devtools/dev-overlay/styles/component-styles.tsx b/packages/next/src/next-devtools/dev-overlay/styles/component-styles.tsx index e22167196d144..1c4fb78c12a7d 100644 --- a/packages/next/src/next-devtools/dev-overlay/styles/component-styles.tsx +++ b/packages/next/src/next-devtools/dev-overlay/styles/component-styles.tsx @@ -25,6 +25,7 @@ import { CALL_STACK_STYLES } from '../components/call-stack/call-stack' import { ISSUE_FEEDBACK_BUTTON_STYLES } from '../components/errors/error-overlay-toolbar/issue-feedback-button' import { ERROR_CONTENT_SKELETON_STYLES } from '../container/runtime-error/error-content-skeleton' import { SHORTCUT_RECORDER_STYLES } from '../components/errors/dev-tools-indicator/dev-tools-info/shortcut-recorder' + export function ComponentStyles() { return (