diff --git a/src/components/overrides/Head.astro b/src/components/overrides/Head.astro index 8656e131b..6622935f8 100644 --- a/src/components/overrides/Head.astro +++ b/src/components/overrides/Head.astro @@ -90,3 +90,5 @@ if (breadcrumbItems.length > 1) { }} /> )} + + diff --git a/src/scripts/copy-code.ts b/src/scripts/copy-code.ts new file mode 100644 index 000000000..17cf35e2d --- /dev/null +++ b/src/scripts/copy-code.ts @@ -0,0 +1,126 @@ +/** + * Code-block copy button + * + * Injects a copy-to-clipboard button into every `
` block.
+ * Uses the `copy` / `check` icons from the docs icon registry.
+ *
+ * Re-runs on View-Transition navigations via the `astro:page-load` event.
+ */
+
+const COPY_BUTTON_STYLE_ID = 'copy-code-button-styles';
+
+function ensureCopyButtonStyles(): void {
+  if (document.getElementById(COPY_BUTTON_STYLE_ID)) return;
+
+  const style = document.createElement('style');
+  style.id = COPY_BUTTON_STYLE_ID;
+  style.textContent = `
+    pre.astro-code.copy-code-host {
+      position: relative;
+    }
+
+    pre.astro-code.copy-code-host > .copy-btn {
+      position: absolute;
+      top: 0.5rem;
+      right: 0.5rem;
+      opacity: 0;
+      transition: opacity 0.15s ease;
+    }
+
+    pre.astro-code.copy-code-host:hover > .copy-btn,
+    pre.astro-code.copy-code-host:focus-within > .copy-btn {
+      opacity: 1;
+    }
+  `;
+
+  document.head.appendChild(style);
+}
+
+function initCopyButtons(): void {
+  ensureCopyButtonStyles();
+
+  document.querySelectorAll('pre.astro-code').forEach((pre) => {
+    if (pre.querySelector('.copy-btn')) return; // already injected
+
+    pre.classList.add('copy-code-host');
+
+    const btn = document.createElement('igc-icon-button') as HTMLElement;
+    btn.setAttribute('variant', 'flat');
+    btn.setAttribute('aria-label', 'Copy code');
+    btn.className = 'copy-btn';
+
+    const icon = document.createElement('igc-icon');
+    icon.setAttribute('name', 'copy');
+    icon.setAttribute('collection', 'docs');
+    btn.appendChild(icon);
+
+    btn.addEventListener('click', () => {
+      const code = pre.querySelector('code');
+      const text = code ? code.textContent ?? '' : pre.textContent ?? '';
+
+      const resetButtonState = () => {
+        icon.setAttribute('name', 'copy');
+        btn.setAttribute('aria-label', 'Copy code');
+      };
+
+      const showCopiedState = () => {
+        icon.setAttribute('name', 'check');
+        btn.setAttribute('aria-label', 'Copied!');
+        setTimeout(resetButtonState, 1500);
+      };
+
+      const showCopyFailedState = () => {
+        btn.setAttribute('aria-label', 'Copy failed');
+        setTimeout(resetButtonState, 1500);
+      };
+
+      const fallbackCopyText = (value: string): boolean => {
+        const textArea = document.createElement('textarea');
+        textArea.value = value;
+        textArea.setAttribute('readonly', '');
+        textArea.style.position = 'fixed';
+        textArea.style.opacity = '0';
+        textArea.style.pointerEvents = 'none';
+        document.body.appendChild(textArea);
+        textArea.select();
+        textArea.setSelectionRange(0, textArea.value.length);
+
+        try {
+          return document.execCommand('copy');
+        } catch {
+          return false;
+        } finally {
+          document.body.removeChild(textArea);
+        }
+      };
+
+      if (navigator.clipboard?.writeText) {
+        navigator.clipboard.writeText(text)
+          .then(() => {
+            showCopiedState();
+          })
+          .catch(() => {
+            if (fallbackCopyText(text)) {
+              showCopiedState();
+            } else {
+              showCopyFailedState();
+            }
+          });
+        return;
+      }
+
+      if (fallbackCopyText(text)) {
+        showCopiedState();
+      } else {
+        showCopyFailedState();
+      }
+    });
+
+    pre.appendChild(btn);
+  });
+}
+
+// Run immediately (module may load after astro:page-load has fired)
+// and on subsequent View-Transition navigations.
+initCopyButtons();
+document.addEventListener('astro:page-load', initCopyButtons);