diff --git a/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel-footer.tsx b/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel-footer.tsx new file mode 100644 index 0000000000000..7812924af4661 --- /dev/null +++ b/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel-footer.tsx @@ -0,0 +1,123 @@ +import type { OverlayState } from '../../shared' + +import { DevToolsPanelVersionInfo } from './devtools-panel-version-info' +import { QuestionIcon } from '../../icons/question' +import { BugIcon } from '../../icons/bug' +import { css } from '../../utils/css' + +export function DevToolsPanelFooter({ + versionInfo, +}: { + versionInfo: OverlayState['versionInfo'] +}) { + const bundlerName = ( + process.env.__NEXT_BUNDLER || 'WEBPACK' + ).toUpperCase() as 'WEBPACK' | 'TURBOPACK' | 'RSPACK' + return ( +
+
+ +
+ {/* TODO: The details may change, follow up. */} + + {bundlerName} + + + enabled + +
+
+
+ {/* TODO: Add help feature, details TBD */} + + {/* TODO: Add debugging/report GitHub issue feature, details TBD */} + +
+
+ ) +} + +export const DEVTOOLS_PANEL_FOOTER_STYLES = css` + [data-nextjs-devtools-panel-footer] { + background-color: var(--color-background-200); + display: flex; + justify-content: space-between; + align-items: center; + margin-top: auto; + border-top: 1px solid var(--color-gray-400); + border-radius: 0 0 var(--rounded-xl) var(--rounded-xl); + } + + [data-nextjs-devtools-panel-footer-tab-group] { + display: flex; + align-items: center; + } + + [data-nextjs-devtools-panel-footer-tab] { + display: flex; + align-items: center; + padding: 12px; + gap: 8px; + align-self: stretch; + border-right: 1px solid var(--color-gray-400); + + color: var(--color-gray-900); + font-size: 12px; + font-family: var(--font-stack-monospace); + } + + [data-nextjs-devtools-panel-footer-tab-bundler-name='TURBOPACK'] { + background: linear-gradient( + to right, + var(--color-turbopack-text-red) 0%, + var(--color-turbopack-text-blue) 100% + ); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + } + + [data-nextjs-devtools-panel-footer-action-button-group] { + display: flex; + align-items: center; + gap: 8px; + padding-right: 8px; + } + + [data-nextjs-devtools-panel-footer-action-button] { + display: flex; + justify-content: center; + align-items: center; + + padding: 4px; + background: var(--color-background-100); + background-clip: padding-box; + border: 1px solid var(--color-gray-alpha-400); + box-shadow: var(--shadow-small); + border-radius: var(--rounded-full); + color: var(--color-gray-800); + + &:focus { + outline: var(--focus-ring); + } + + &:not(:disabled):hover { + background: var(--color-gray-alpha-100); + } + + &:not(:disabled):active { + background: var(--color-gray-alpha-200); + } + + &:disabled { + background-color: var(--color-gray-100); + cursor: not-allowed; + } + } +` diff --git a/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel-version-info.stories.tsx b/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel-version-info.stories.tsx new file mode 100644 index 0000000000000..60a8f861dd27c --- /dev/null +++ b/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel-version-info.stories.tsx @@ -0,0 +1,95 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { DevToolsPanelVersionInfo } from './devtools-panel-version-info' +import { withShadowPortal } from '../../storybook/with-shadow-portal' + +const meta: Meta = { + component: DevToolsPanelVersionInfo, + parameters: { + layout: 'centered', + }, + decorators: [withShadowPortal], +} + +export default meta +type Story = StoryObj + +// Mock version info for different scenarios +const mockVersionInfo = { + fresh: { + installed: '15.0.0', + expected: '15.0.0', + staleness: 'fresh' as const, + }, + stalePatch: { + installed: '15.0.0', + expected: '15.0.1', + staleness: 'stale-patch' as const, + }, + staleMinor: { + installed: '15.0.0', + expected: '15.1.0', + staleness: 'stale-minor' as const, + }, + staleMajor: { + installed: '14.0.0', + expected: '15.0.0', + staleness: 'stale-major' as const, + }, + stalePrerelease: { + installed: '15.0.0-canary.0', + expected: '15.0.0-canary.1', + staleness: 'stale-prerelease' as const, + }, + newerThanNpm: { + installed: '15.0.0-canary.1', + expected: '15.0.0-canary.0', + staleness: 'newer-than-npm' as const, + }, + unknown: { + installed: '15.0.0', + expected: '15.0.0', + staleness: 'unknown' as const, + }, +} + +export const Fresh: Story = { + args: { + versionInfo: mockVersionInfo.fresh, + }, +} + +export const StalePatch: Story = { + args: { + versionInfo: mockVersionInfo.stalePatch, + }, +} + +export const StaleMinor: Story = { + args: { + versionInfo: mockVersionInfo.staleMinor, + }, +} + +export const StaleMajor: Story = { + args: { + versionInfo: mockVersionInfo.staleMajor, + }, +} + +export const StalePrerelease: Story = { + args: { + versionInfo: mockVersionInfo.stalePrerelease, + }, +} + +export const NewerThanNpm: Story = { + args: { + versionInfo: mockVersionInfo.newerThanNpm, + }, +} + +export const Unknown: Story = { + args: { + versionInfo: mockVersionInfo.unknown, + }, +} diff --git a/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel-version-info.tsx b/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel-version-info.tsx new file mode 100644 index 0000000000000..c4cdadc323b11 --- /dev/null +++ b/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel-version-info.tsx @@ -0,0 +1,57 @@ +import type { OverlayState } from '../../shared' + +import { EclipseIcon } from '../../icons/eclipse' +import { getStaleness } from '../../../shared/version-staleness' +import { css } from '../../utils/css' + +export function DevToolsPanelVersionInfo({ + versionInfo, +}: { + versionInfo: OverlayState['versionInfo'] +}) { + const { staleness } = versionInfo + const { text, indicatorClass, title } = getStaleness(versionInfo) + const shouldBeLink = staleness.startsWith('stale') + + if (shouldBeLink) { + return ( + // TODO: Make it button-like and display radix-ui Tooltip on hover. + + + {text} + + ) + } + + return ( +
+ + {text} +
+ ) +} + +export const DEVTOOLS_PANEL_VERSION_INFO_STYLES = css` + [data-nextjs-version-staleness-indicator='fresh'] { + fill: var(--color-green-800); + stroke: var(--color-green-300); + } + [data-nextjs-version-staleness-indicator='stale'] { + fill: var(--color-amber-800); + stroke: var(--color-amber-300); + } + [data-nextjs-version-staleness-indicator='outdated'] { + fill: var(--color-red-800); + stroke: var(--color-red-300); + } + [data-nextjs-version-staleness-indicator='unknown'] { + fill: var(--color-gray-800); + stroke: var(--color-gray-300); + } +` diff --git a/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel.stories.tsx b/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel.stories.tsx index d25c91afe7bae..9578637b3b7c0 100644 --- a/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel.stories.tsx +++ b/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel.stories.tsx @@ -22,6 +22,11 @@ const state: OverlayState = { routerType: 'app', isErrorOverlayOpen: false, isDevToolsPanelOpen: true, + versionInfo: { + installed: '15.0.0', + expected: '15.0.0', + staleness: 'fresh', + }, } export const Default: Story = { @@ -39,3 +44,35 @@ export const WithIssues: Story = { issueCount: 3, }, } + +export const Turbopack: Story = { + beforeEach: () => { + process.env.__NEXT_BUNDLER = 'Turbopack' + + // clean up callback function + return () => { + delete process.env.__NEXT_BUNDLER + } + }, + args: { + state, + dispatch: () => {}, + issueCount: 0, + }, +} + +export const Rspack: Story = { + beforeEach: () => { + process.env.__NEXT_BUNDLER = 'Rspack' + + // clean up callback function + return () => { + delete process.env.__NEXT_BUNDLER + } + }, + args: { + state, + dispatch: () => {}, + issueCount: 0, + }, +} diff --git a/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel.tsx b/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel.tsx index 576d501b640f0..d95d188593115 100644 --- a/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel.tsx +++ b/packages/next/src/next-devtools/dev-overlay/components/devtools-panel/devtools-panel.tsx @@ -2,6 +2,7 @@ import type { OverlayDispatch, OverlayState } from '../../shared' import { useState } from 'react' +import { DevToolsPanelFooter } from './devtools-panel-footer' import { Dialog, DialogContent, DialogHeader, DialogBody } from '../dialog' import { Overlay } from '../overlay/overlay' import { @@ -61,61 +62,64 @@ export function DevToolsPanel({ }) }} > - - - -
-
- - - + <> + + + +
+
+ + + +
+
+ {/* TODO: Currently no-op, will add fullscreen toggle. */} + + +
-
- {/* TODO: Currently no-op, will add fullscreen toggle. */} - - -
-
- - - -
+ + + + + + ) 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 13d0968a544be..1df46f9264c7c 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 @@ -17,6 +17,7 @@ const CSS_SELECTORS_TO_EXCLUDE_ON_CLICK_OUTSIDE = [ '[data-nextjs-error-overlay-nav]', '[data-info-popover]', '[data-nextjs-devtools-panel-overlay]', + '[data-nextjs-devtools-panel-footer]', ] const Dialog: React.FC = function Dialog({ diff --git a/packages/next/src/next-devtools/dev-overlay/components/version-staleness-info/version-staleness-info.tsx b/packages/next/src/next-devtools/dev-overlay/components/version-staleness-info/version-staleness-info.tsx index 6b8869dd08d9d..f5bff6e9eb0f6 100644 --- a/packages/next/src/next-devtools/dev-overlay/components/version-staleness-info/version-staleness-info.tsx +++ b/packages/next/src/next-devtools/dev-overlay/components/version-staleness-info/version-staleness-info.tsx @@ -1,6 +1,7 @@ import type { VersionInfo } from '../../../../server/dev/parse-version-info' import { getStaleness } from '../../../shared/version-staleness' import { cx } from '../../utils/cx' +import { EclipseIcon } from '../../icons/eclipse' export function VersionStalenessInfo({ versionInfo, @@ -23,7 +24,7 @@ export function VersionStalenessInfo({ rel="noopener noreferrer" href="https://nextjs.org/docs/messages/version-staleness" > - @@ -38,7 +39,9 @@ export function VersionStalenessInfo({ return ( - + {text} @@ -110,17 +113,3 @@ export const styles = ` -webkit-text-fill-color: transparent; } ` - -function Eclipse({ className }: { className: string }) { - return ( - - - - ) -} diff --git a/packages/next/src/next-devtools/dev-overlay/icons/bug.tsx b/packages/next/src/next-devtools/dev-overlay/icons/bug.tsx new file mode 100644 index 0000000000000..c949d666cb543 --- /dev/null +++ b/packages/next/src/next-devtools/dev-overlay/icons/bug.tsx @@ -0,0 +1,17 @@ +export function BugIcon(props: React.SVGProps) { + return ( + + + + ) +} diff --git a/packages/next/src/next-devtools/dev-overlay/icons/eclipse.tsx b/packages/next/src/next-devtools/dev-overlay/icons/eclipse.tsx new file mode 100644 index 0000000000000..60f34ca0757cb --- /dev/null +++ b/packages/next/src/next-devtools/dev-overlay/icons/eclipse.tsx @@ -0,0 +1,14 @@ +export function EclipseIcon(props: React.SVGProps) { + return ( + + + + ) +} diff --git a/packages/next/src/next-devtools/dev-overlay/icons/question.tsx b/packages/next/src/next-devtools/dev-overlay/icons/question.tsx new file mode 100644 index 0000000000000..7520406f7f99c --- /dev/null +++ b/packages/next/src/next-devtools/dev-overlay/icons/question.tsx @@ -0,0 +1,17 @@ +export function QuestionIcon(props: React.SVGProps) { + return ( + + + + ) +} 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 29a7d148c51eb..7b584f11306ab 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,8 @@ import { DEV_TOOLS_INFO_RENDER_FILES_STYLES } from '../components/overview/segme import { FADER_STYLES } from '../components/fader' import { RESTART_SERVER_BUTTON_STYLES } from '../components/errors/error-overlay-toolbar/restart-server-button' import { DEVTOOLS_PANEL_STYLES } from '../components/devtools-panel/devtools-panel' +import { DEVTOOLS_PANEL_FOOTER_STYLES } from '../components/devtools-panel/devtools-panel-footer' +import { DEVTOOLS_PANEL_VERSION_INFO_STYLES } from '../components/devtools-panel/devtools-panel-version-info' export function ComponentStyles() { return ( @@ -56,6 +58,8 @@ export function ComponentStyles() { ${DEV_TOOLS_INFO_RENDER_FILES_STYLES} ${FADER_STYLES} ${DEVTOOLS_PANEL_STYLES} + ${DEVTOOLS_PANEL_FOOTER_STYLES} + ${DEVTOOLS_PANEL_VERSION_INFO_STYLES} `} )