Skip to content

Commit

Permalink
Allowing hiding the ReactDevOverlay (#37417)
Browse files Browse the repository at this point in the history
* Add hide button

* Update label

* Add data attributes for testing

* Add e2e tests

* update test

Co-authored-by: JJ Kasper <jj@jjsweb.site>
  • Loading branch information
JohnPhamous and ijjk committed Jun 6, 2022
1 parent 6914a25 commit be5dedf
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react'
import { CloseIcon } from '../../icons/CloseIcon'

export type LeftRightDialogHeaderProps = {
className?: string
Expand Down Expand Up @@ -147,34 +148,14 @@ const LeftRightDialogHeader: React.FC<LeftRightDialogHeaderProps> =
</nav>
{close ? (
<button
data-nextjs-errors-dialog-left-right-close-button
ref={buttonClose}
type="button"
onClick={close}
aria-label="Close"
>
<span aria-hidden="true">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18 6L6 18"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M6 6L18 18"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<CloseIcon />
</span>
</button>
) : null}
Expand Down
49 changes: 42 additions & 7 deletions packages/react-dev-overlay/src/internal/container/Errors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Toast } from '../components/Toast'
import { getErrorByType, ReadyRuntimeError } from '../helpers/getErrorByType'
import { isNodeError } from '../helpers/nodeStackFrames'
import { noop as css } from '../helpers/noop-template'
import { CloseIcon } from '../icons/CloseIcon'
import { RuntimeError } from './RuntimeError'

export type SupportedErrorEvent = {
Expand Down Expand Up @@ -136,7 +137,9 @@ export const Errors: React.FC<ErrorsProps> = function Errors({ errors }) {
}
}, [nextError])

const [isMinimized, setMinimized] = React.useState<boolean>(false)
const [displayState, setDisplayState] = React.useState<
'minimized' | 'fullscreen' | 'hidden'
>('fullscreen')
const [activeIdx, setActiveIndex] = React.useState<number>(0)
const previous = React.useCallback((e?: MouseEvent | TouchEvent) => {
e?.preventDefault()
Expand All @@ -162,19 +165,23 @@ export const Errors: React.FC<ErrorsProps> = function Errors({ errors }) {
React.useEffect(() => {
if (errors.length < 1) {
setLookups({})
setMinimized(false)
setDisplayState('hidden')
setActiveIndex(0)
}
}, [errors.length])

const minimize = React.useCallback((e?: MouseEvent | TouchEvent) => {
e?.preventDefault()
setMinimized(true)
setDisplayState('minimized')
}, [])
const reopen = React.useCallback(
const hide = React.useCallback((e?: MouseEvent | TouchEvent) => {
e?.preventDefault()
setDisplayState('hidden')
}, [])
const fullscreen = React.useCallback(
(e?: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
e?.preventDefault()
setMinimized(false)
setDisplayState('fullscreen')
},
[]
)
Expand All @@ -190,9 +197,13 @@ export const Errors: React.FC<ErrorsProps> = function Errors({ errors }) {
return <Overlay />
}

if (isMinimized) {
if (displayState === 'hidden') {
return null
}

if (displayState === 'minimized') {
return (
<Toast className="nextjs-toast-errors-parent" onClick={reopen}>
<Toast className="nextjs-toast-errors-parent" onClick={fullscreen}>
<div className="nextjs-toast-errors">
<svg
xmlns="http://www.w3.org/2000/svg"
Expand All @@ -212,6 +223,18 @@ export const Errors: React.FC<ErrorsProps> = function Errors({ errors }) {
<span>
{readyErrors.length} error{readyErrors.length > 1 ? 's' : ''}
</span>
<button
data-nextjs-toast-errors-hide-button
className="nextjs-toast-errors-hide-button"
type="button"
onClick={(e) => {
e.stopPropagation()
hide()
}}
aria-label="Hide Errors"
>
<CloseIcon />
</button>
</div>
</Toast>
)
Expand Down Expand Up @@ -320,4 +343,16 @@ export const styles = css`
.nextjs-toast-errors > svg {
margin-right: var(--size-gap);
}
.nextjs-toast-errors-hide-button {
margin-left: var(--size-gap-triple);
border: none;
background: none;
color: var(--color-ansi-bright-white);
padding: 0;
transition: opacity 0.25s ease;
opacity: 0.7;
}
.nextjs-toast-errors-hide-button:hover {
opacity: 1;
}
`
30 changes: 30 additions & 0 deletions packages/react-dev-overlay/src/internal/icons/CloseIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as React from 'react'

const CloseIcon = () => {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18 6L6 18"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M6 6L18 18"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}

export { CloseIcon }
1 change: 1 addition & 0 deletions packages/react-dev-overlay/src/internal/styles/Base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export function Base() {
--size-gap-half: 4px;
--size-gap: 8px;
--size-gap-double: 16px;
--size-gap-triple: 24px;
--size-gap-quad: 32px;
--size-font-small: 14px;
Expand Down
12 changes: 12 additions & 0 deletions test/development/client-dev-overlay/app/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react'

// Create a runtime error.
if ('window' in global) {
throw Error('example runtime error')
}

const Page = () => {
return <div>client-react-dev-overlay</div>
}

export default Page
75 changes: 75 additions & 0 deletions test/development/client-dev-overlay/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { createNext, FileRef } from 'e2e-utils'
import webdriver from 'next-webdriver'
import { NextInstance } from 'test/lib/next-modes/base'
import { join } from 'path'
import { BrowserInterface } from 'test/lib/browsers/base'
import { check } from 'next-test-utils'

describe('client-dev-overlay', () => {
let next: NextInstance
let browser: BrowserInterface

beforeAll(async () => {
next = await createNext({
files: {
pages: new FileRef(join(__dirname, 'app/pages')),
},
})
})
beforeEach(async () => {
browser = await webdriver(next.url, '')
})
afterAll(() => next.destroy())

// The `BrowserInterface.hasElementByCssSelector` cannot be used for elements inside a shadow DOM.
function elementExistsInNextJSPortalShadowDOM(selector: string) {
return browser.eval(
`!!document.querySelector('nextjs-portal').shadowRoot.querySelector('${selector}')`
) as any
}
const selectors = {
fullScreenDialog: '[data-nextjs-dialog]',
toast: '[data-nextjs-toast]',
minimizeButton: '[data-nextjs-errors-dialog-left-right-close-button]',
hideButton: '[data-nextjs-toast-errors-hide-button]',
}
function getToast() {
return browser.elementByCss(selectors.toast)
}
function getMinimizeButton() {
return browser.elementByCss(selectors.minimizeButton)
}
function getHideButton() {
return browser.elementByCss(selectors.hideButton)
}

it('should be able to fullscreen the minimized overlay', async () => {
await getMinimizeButton().click()
await getToast().click()

await check(async () => {
return (await elementExistsInNextJSPortalShadowDOM(
selectors.fullScreenDialog
))
? 'success'
: 'missing'
}, 'success')
})

it('should be able to minimize the fullscreen overlay', async () => {
await getMinimizeButton().click()
expect(await elementExistsInNextJSPortalShadowDOM(selectors.toast)).toBe(
true
)
})

it('should be able to hide the minimized overlay', async () => {
await getMinimizeButton().click()
await getHideButton().click()

await check(async () => {
const exists = await elementExistsInNextJSPortalShadowDOM('div')
return exists ? 'found' : 'success'
}, 'success')
})
})

0 comments on commit be5dedf

Please sign in to comment.