Skip to content

Commit

Permalink
[dev-overlay] feat: dev indicator menu route info popover (#76153)
Browse files Browse the repository at this point in the history
### What?

This PR added a menu item's specific info popover for Route.

https://github.com/user-attachments/assets/63a3df65-f367-471a-a33a-30a7c6377485

| Static | Dynamic |
|--------|--------|
| ![CleanShot 2025-02-18 at 21 48 38@2x](https://github.com/user-attachments/assets/70e0d7c1-9390-43f2-9fd2-92f1477c8028) | ![CleanShot 2025-02-18 at 21 48 10@2x](https://github.com/user-attachments/assets/353404d4-2c9a-46f3-9beb-c8eb26bd0f79) | 

Closes NDX-815
Closes NDX-849
  • Loading branch information
devjiwonchoi authored Feb 19, 2025
1 parent 5b2de1b commit 0c946eb
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,43 +59,41 @@ const state: OverlayState = {
rootLayoutMissingTags: [],
versionInfo: mockVersionInfo,
notFound: false,
staticIndicator: false,
staticIndicator: true,
debugInfo: { devtoolsFrontendUrl: undefined },
}

export const NoErrors: Story = {
export const StaticRoute: Story = {
args: {
errorCount: 0,
state,
setIsErrorOverlayOpen: () => {},
},
}

export const SingleError: Story = {
export const DynamicRoute: Story = {
args: {
errorCount: 1,
state,
errorCount: 0,
state: {
...state,
staticIndicator: false,
},
setIsErrorOverlayOpen: () => {},
},
}

export const MultipleErrors: Story = {
export const SingleError: Story = {
args: {
errorCount: 3,
errorCount: 1,
state,
setIsErrorOverlayOpen: () => {},
},
}

export const WithStaticIndicator: Story = {
export const MultipleErrors: Story = {
args: {
errorCount: 3,
state: {
...state,
staticIndicator: true,
},
setIsErrorOverlayOpen: () => {
console.log('setIsErrorOverlayOpen called')
},
state,
setIsErrorOverlayOpen: () => {},
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useIsDevRendering } from './internal/dev-render-indicator'
import { useDelayedRender } from '../../../hooks/use-delayed-render'
import { noop as css } from '../../../helpers/noop-template'
import { TurbopackInfo } from './dev-tools-info/turbopack-info'
import { RouteInfo } from './dev-tools-info/route-info'

// TODO: add E2E tests to cover different scenarios

Expand Down Expand Up @@ -86,8 +87,11 @@ function DevToolsPopover({
const triggerRef = useRef<HTMLButtonElement | null>(null)
const turbopackRef = useRef<HTMLElement>(null)
const triggerTurbopackRef = useRef<HTMLButtonElement | null>(null)
const routeInfoRef = useRef<HTMLElement>(null)
const triggerRouteInfoRef = useRef<HTMLButtonElement | null>(null)
const [isMenuOpen, setIsMenuOpen] = useState(false)
const [isTurbopackInfoOpen, setIsTurbopackInfoOpen] = useState(false)
const [isRouteInfoOpen, setIsRouteInfoOpen] = useState(false)
const [selectedIndex, setSelectedIndex] = useState(-1)

// This hook lets us do an exit animation before unmounting the component
Expand All @@ -105,6 +109,11 @@ function DevToolsPopover({
enterDelay: 0,
exitDelay: ANIMATE_OUT_DURATION_MS,
})
const { mounted: routeInfoMounted, rendered: routeInfoRendered } =
useDelayedRender(isRouteInfoOpen, {
enterDelay: 0,
exitDelay: ANIMATE_OUT_DURATION_MS,
})

// Features to make the menu accessible
useFocusTrap(menuRef, triggerRef, isMenuOpen)
Expand All @@ -116,6 +125,13 @@ function DevToolsPopover({
isTurbopackInfoOpen,
closeTurbopackInfo
)
useFocusTrap(routeInfoRef, triggerRouteInfoRef, isRouteInfoOpen)
useClickOutside(
routeInfoRef,
triggerRouteInfoRef,
isRouteInfoOpen,
closeRouteInfo
)

function select(index: number | 'first' | 'last') {
if (index === 'first') {
Expand Down Expand Up @@ -214,6 +230,10 @@ function DevToolsPopover({
setIsTurbopackInfoOpen(false)
}

function closeRouteInfo() {
setIsRouteInfoOpen(false)
}

const [vertical, horizontal] = position.split('-', 2)

return (
Expand Down Expand Up @@ -245,6 +265,21 @@ function DevToolsPopover({
isDevRendering={useIsDevRendering()}
/>

{routeInfoMounted && (
<RouteInfo
ref={routeInfoRef}
routeType={isStaticRoute ? 'Static' : 'Dynamic'}
isOpen={isRouteInfoOpen}
setIsOpen={setIsRouteInfoOpen}
setPreviousOpen={setIsMenuOpen}
style={{
[vertical]: 'calc(100% + var(--size-gap))',
[horizontal]: 0,
}}
data-rendered={routeInfoRendered}
/>
)}

{turbopackInfoMounted && (
<TurbopackInfo
ref={turbopackRef}
Expand Down Expand Up @@ -298,14 +333,16 @@ function DevToolsPopover({
)}
<MenuItem
label="Route"
index={1}
value={isStaticRoute ? 'Static' : 'Dynamic'}
onClick={() => setIsRouteInfoOpen(true)}
data-nextjs-route-type={isStaticRoute ? 'static' : 'dynamic'}
/>
{isTurbopack ? (
<MenuItem label="Turbopack" value="Enabled" />
) : (
<MenuItem
index={1}
index={2}
label="Try Turbopack"
value={<ChevronRight />}
onClick={() => setIsTurbopackInfoOpen(true)}
Expand All @@ -319,7 +356,7 @@ function DevToolsPopover({
label="Hide Dev Tools"
value={<Cross color="var(--color-gray-900)" />}
onClick={hide}
index={isTurbopack ? 1 : 2}
index={isTurbopack ? 2 : 3}
/>
</div>
</Context.Provider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const DEV_TOOLS_INFO_STYLES = css`
overflow: hidden;
opacity: 0;
outline: 0;
min-width: 248px;
min-width: 350px;
transition: opacity var(--animate-out-duration-ms)
var(--animate-out-timing-function);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { DevToolsInfo } from './dev-tools-info'
import { noop as css } from '../../../../helpers/noop-template'

function StaticRouteContent() {
return (
<article className="dev-tools-info-article">
<p className="dev-tools-info-paragraph">
The path{' '}
<code className="dev-tools-info-code">{window.location.pathname}</code>{' '}
is marked as "static" since it will be prerendered during the build
time.
</p>
<p className="dev-tools-info-paragraph">
With Static Rendering, routes are rendered at build time, or in the
background after{' '}
<a
className="dev-tools-info-link"
href="https://nextjs.org/docs/app/building-your-application/data-fetching/incremental-static-regeneration"
target="_blank"
rel="noopener noreferrer"
>
data revalidation
</a>
.
</p>
<p className="dev-tools-info-paragraph">
Static rendering is useful when a route has data that is not
personalized to the user and can be known at build time, such as a
static blog post or a product page.
</p>
</article>
)
}

function DynamicRouteContent() {
return (
<article className="dev-tools-info-article">
<p className="dev-tools-info-paragraph">
The path{' '}
<code className="dev-tools-info-code">{window.location.pathname}</code>{' '}
is marked as "dynamic" since it will be rendered for each user at{' '}
<strong>request time</strong>.
</p>
<p className="dev-tools-info-paragraph">
Dynamic rendering is useful when a route has data that is personalized
to the user or has information that can only be known at request time,
such as cookies or the URL's search params.
</p>
<p className="dev-tools-info-paragraph">
During rendering, if a{' '}
<a
className="dev-tools-info-link"
href="https://nextjs.org/docs/app/building-your-application/rendering/server-components#dynamic-apis"
target="_blank"
rel="noopener noreferrer"
>
Dynamic API
</a>{' '}
or a{' '}
<a
className="dev-tools-info-link"
href="https://nextjs.org/docs/app/api-reference/functions/fetch"
target="_blank"
rel="noopener noreferrer"
>
fetch
</a>{' '}
option of{' '}
<code className="dev-tools-info-code">{`{ cache: 'no-store' }`}</code>{' '}
is discovered, Next.js will switch to dynamically rendering the whole
route.
</p>
</article>
)
}

export function RouteInfo({
routeType,
isOpen,
setIsOpen,
setPreviousOpen,
...props
}: {
routeType: 'Static' | 'Dynamic'
isOpen: boolean
setIsOpen: (isOpen: boolean) => void
setPreviousOpen: (isOpen: boolean) => void
style?: React.CSSProperties
ref?: React.RefObject<HTMLElement | null>
}) {
const isStaticRoute = routeType === 'Static'
const learnMoreLink = isStaticRoute
? 'https://nextjs.org/docs/app/building-your-application/rendering/server-components#static-rendering-default'
: 'https://nextjs.org/docs/app/building-your-application/rendering/server-components#dynamic-rendering'
return (
<DevToolsInfo
{...props}
title={`${routeType} Route`}
learnMoreLink={learnMoreLink}
setIsOpen={setIsOpen}
setPreviousOpen={setPreviousOpen}
>
{isStaticRoute ? <StaticRouteContent /> : <DynamicRouteContent />}
</DevToolsInfo>
)
}

export const DEV_TOOLS_INFO_ROUTE_INFO_STYLES = css`
.dev-tools-info-link {
}
`
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { EDITOR_LINK_STYLES } from '../components/terminal/editor-link'
import { ENVIRONMENT_NAME_LABEL_STYLES } from '../components/errors/environment-name-label/environment-name-label'
import { DEV_TOOLS_INFO_STYLES } from '../components/errors/dev-tools-indicator/dev-tools-info/dev-tools-info'
import { DEV_TOOLS_INFO_TURBOPACK_INFO_STYLES } from '../components/errors/dev-tools-indicator/dev-tools-info/turbopack-info'
import { DEV_TOOLS_INFO_ROUTE_INFO_STYLES } from '../components/errors/dev-tools-indicator/dev-tools-info/route-info'

export function ComponentStyles() {
return (
Expand All @@ -44,6 +45,7 @@ export function ComponentStyles() {
${DEV_TOOLS_INDICATOR_STYLES}
${DEV_TOOLS_INFO_STYLES}
${DEV_TOOLS_INFO_TURBOPACK_INFO_STYLES}
${DEV_TOOLS_INFO_ROUTE_INFO_STYLES}
`}
</style>
)
Expand Down

0 comments on commit 0c946eb

Please sign in to comment.