diff --git a/packages/next/src/client/components/react-dev-overlay/internal/components/copy-button/index.tsx b/packages/next/src/client/components/react-dev-overlay/internal/components/copy-button/index.tsx new file mode 100644 index 0000000000000..8ec622dcd63e0 --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/internal/components/copy-button/index.tsx @@ -0,0 +1,79 @@ +import { useState } from 'react' + +enum CopyState { + Initial = 0, + Copied = 1, + Error = 2, +} + +export function CopyButton({ + label, + successLabel, + content, + ...props +}: React.HTMLProps & { + label: string + successLabel: string + content: string +}) { + const [copied, setCopied] = useState(CopyState.Initial) + const isDisabled = copied === CopyState.Error + const title = isDisabled ? '' : copied ? successLabel : label + return ( + { + if (isDisabled) return + if (!navigator.clipboard) { + setCopied(CopyState.Error) + return + } + navigator.clipboard.writeText(content).then(() => { + if (copied) return + setCopied(CopyState.Copied) + setTimeout(() => setCopied(CopyState.Initial), 2000) + }) + }} + > + {copied ? : } + + ) +} + +function CopyIcon() { + return ( + + + + + ) +} + +function CopySuccessIcon() { + return ( + + + + ) +} diff --git a/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/GroupedStackFrames.tsx b/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/GroupedStackFrames.tsx index 542a6fac880e4..1a6ea345b6188 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/GroupedStackFrames.tsx +++ b/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/GroupedStackFrames.tsx @@ -27,12 +27,9 @@ function FrameworkGroup({ export function GroupedStackFrames({ groupedStackFrames, - show, }: { groupedStackFrames: StackFramesGroup[] - show: boolean }) { - if (!show) return return ( <> {groupedStackFrames.map((stackFramesGroup, groupIndex) => { diff --git a/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/index.tsx b/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/index.tsx index f68ac1582b067..a50ad51bc11ad 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/index.tsx +++ b/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/index.tsx @@ -4,6 +4,7 @@ import type { ReadyRuntimeError } from '../../helpers/getErrorByType' import { noop as css } from '../../helpers/noop-template' import { groupStackFramesByFramework } from '../../helpers/group-stack-frames-by-framework' import { GroupedStackFrames } from './GroupedStackFrames' +import { CopyButton } from '../../components/copy-button' export type RuntimeErrorProps = { error: ReadyRuntimeError } @@ -67,7 +68,6 @@ export function RuntimeError({ error }: RuntimeErrorProps) {

Source

-

Call Stack

+

+ Call Stack + {error.error.stack && ( + + )} +

+ ) : undefined} @@ -116,6 +126,27 @@ export const styles = css` margin-bottom: var(--size-gap-double); } + [data-nextjs-data-runtime-error-copy-stack] { + position: relative; + margin-left: var(--size-gap); + } + [data-nextjs-data-runtime-error-copy-stack] > svg { + vertical-align: middle; + } + [data-nextjs-data-runtime-error-copy-stack][aria-disabled], + [data-nextjs-data-runtime-error-copy-stack][aria-disabled]:hover { + cursor: pointer; + color: var(--color-ansi-red); + opacity: 0.3; + cursor: not-allowed; + } + [data-nextjs-data-runtime-error-copy-stack-success] { + color: var(--color-ansi-green); + } + [data-nextjs-data-runtime-error-copy-stack]:hover { + cursor: pointer; + } + [data-nextjs-call-stack-frame] > h3, [data-nextjs-component-stack-frame] > h3 { margin-top: 0; @@ -141,7 +172,6 @@ export const styles = css` height: var(--size-font-small); margin-left: var(--size-gap); flex-shrink: 0; - display: none; }