Repository: vercel/next.js
Pull request: [dev-overlay] disable copy button when clipboard is not available

### What

*Non-secure context (http)* except localhost will not have navigator.clipboard, for instance directly visiting local ip when serving localhost with default port. We're currently showing an error which might display on the DOM when you click the copy button (fig. 1). This is not an ideal presentation.

<img width="300" alt="image" src="https://github.com/user-attachments/assets/fa25712d-c18f-48b5-ac7a-833484022687" />


After this PR, we'll show the button as disabled so you won't trigger the error. Also changed the clipboard failure to a warning which will not show in the error overlay. It's a unavailabibility of Next.js dev only feature, showing up in error overlay will be bit distractive.

Closes NDX-1016

Changed files:
filename: packages/next/src/client/components/react-dev-overlay/ui/components/copy-button/index.tsx
patch: @@ -48,7 +48,7 @@ function useCopyLegacy(content: string) {
     if (!navigator.clipboard) {
       dispatch({
         type: 'error',
-        error: new Error('Copy to clipboard is not supported in this browser'),
+        error: 'Copy to clipboard is not supported in this browser',
       })
     } else {
       dispatch({ type: 'copying' })
@@ -94,9 +94,7 @@ function useCopyModern(content: string) {
         if (!navigator.clipboard) {
           return {
             state: 'error',
-            error: new Error(
-              'Copy to clipboard is not supported in this browser'
-            ),
+            error: 'Copy to clipboard is not supported in this browser',
           }
         }
         return navigator.clipboard.writeText(content).then(
@@ -153,8 +151,9 @@ export function CopyButton({
   const error = copyState.state === 'error' ? copyState.error : null
   React.useEffect(() => {
     if (error !== null) {
-      // Additional console.error to get the stack.
-      console.error(error)
+      // Only log warning in terminal to avoid showing in the error overlay.
+      // When it's errored, the copy button will be disabled.
+      console.warn(error)
     }
   }, [error])
   React.useEffect(() => {
@@ -168,7 +167,7 @@ export function CopyButton({
       }
     }
   }, [isPending, copyState.state, reset])
-  const isDisabled = isPending || disabled
+  const isDisabled = !navigator.clipboard || isPending || disabled || !!error
   const label = copyState.state === 'success' ? successLabel : actionLabel
 
   // Assign default icon
@@ -255,14 +254,18 @@ export const COPY_BUTTON_STYLES = `
       height: var(--size-16);
     }
   }
-  .nextjs-data-copy-button--initial:hover {
+  .nextjs-data-copy-button:disabled {
+    background-color: var(--color-gray-100);
+    cursor: not-allowed;
+  }
+  .nextjs-data-copy-button--initial:hover:not(:disabled) {
     cursor: pointer;
   }
-  .nextjs-data-copy-button--error,
-  .nextjs-data-copy-button--error:hover {
+  .nextjs-data-copy-button--error:not(:disabled),
+  .nextjs-data-copy-button--error:hover:not(:disabled) {
     color: var(--color-ansi-red);
   }
-  .nextjs-data-copy-button--success {
+  .nextjs-data-copy-button--success:not(:disabled) {
     color: var(--color-ansi-green);
   }
 `

filename: packages/next/src/client/components/react-dev-overlay/ui/container/errors.tsx
patch: @@ -250,17 +250,6 @@ export const styles = `
   .nextjs-toast-hide-button:hover {
     opacity: 1;
   }
-  .nextjs__container_errors_inspect_copy_button {
-    cursor: pointer;
-    background: none;
-    border: none;
-    color: var(--color-ansi-bright-white);
-    font-size: var(--size-24);
-    padding: 0;
-    margin: 0;
-    margin-left: 8px;
-    transition: opacity 0.25s ease;
-  }
   .nextjs__container_errors__error_title {
     display: flex;
     align-items: center;