Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 49 additions & 54 deletions src/components/expandable/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import {ReactNode, useEffect, useRef, useState} from 'react';
import {ReactNode, useCallback, useEffect, useRef, useState} from 'react';
import {ChevronDownIcon, ChevronRightIcon} from '@radix-ui/react-icons';
import * as Sentry from '@sentry/nextjs';

Expand Down Expand Up @@ -38,7 +38,6 @@ export function Expandable({

const [isExpanded, setIsExpanded] = useState(false);
const [copied, setCopied] = useState(false);
const [showCopyButton, setShowCopyButton] = useState(false);
const contentRef = useRef<HTMLDivElement>(null);

// Ensure we scroll to the element if the URL hash matches
Expand Down Expand Up @@ -67,55 +66,52 @@ export function Expandable({
};
}, [id]);

useEffect(() => {
if (copy) {
setShowCopyButton(true);
}
}, [copy]);
const copyContentOnClick = useCallback(
async (event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation(); // Prevent the details element from toggling
event.preventDefault(); // Prevent default summary click behavior

async function copyContentOnClick(event: React.MouseEvent<HTMLButtonElement>) {
event.stopPropagation(); // Prevent the details element from toggling
event.preventDefault(); // Prevent default summary click behavior

if (contentRef.current === null) {
return;
}
if (contentRef.current === null) {
return;
}

// Attempt to get text from markdown code blocks if they exist
const codeBlocks = contentRef.current.querySelectorAll('code');
let contentToCopy = '';

if (codeBlocks.length > 0) {
// If there are code blocks, concatenate their text content
codeBlocks.forEach(block => {
// Exclude code elements within other code elements (e.g. inline code in a block)
if (!block.closest('code')?.parentElement?.closest('code')) {
contentToCopy += (block.textContent || '') + '\n';
}
});
contentToCopy = contentToCopy.trim();
}
// Attempt to get text from markdown code blocks if they exist
const codeBlocks = contentRef.current.querySelectorAll('code');
let contentToCopy = '';

if (codeBlocks.length > 0) {
// If there are code blocks, concatenate their text content
codeBlocks.forEach(block => {
// Exclude code elements within other code elements (e.g. inline code in a block)
if (!block.closest('code')?.parentElement?.closest('code')) {
contentToCopy += (block.textContent || '') + '\n';
}
});
contentToCopy = contentToCopy.trim();
}

// Fallback to the whole content if no code blocks or if they are empty
if (!contentToCopy && contentRef.current.textContent) {
contentToCopy = contentRef.current.textContent.trim();
}
// Fallback to the whole content if no code blocks or if they are empty
if (!contentToCopy && contentRef.current.textContent) {
contentToCopy = contentRef.current.textContent.trim();
}

if (!contentToCopy) {
// if there is no content to copy (e.g. only images), do nothing.
return;
}
if (!contentToCopy) {
// if there is no content to copy (e.g. only images), do nothing.
return;
}

try {
setCopied(false);
await navigator.clipboard.writeText(contentToCopy);
setCopied(true);
setTimeout(() => setCopied(false), 1200);
} catch (error) {
Sentry.captureException(error);
setCopied(false);
}
}
try {
setCopied(false);
await navigator.clipboard.writeText(contentToCopy);
setCopied(true);
setTimeout(() => setCopied(false), 1200);
} catch (error) {
Sentry.captureException(error);
setCopied(false);
}
},
[]
);

function toggleIsExpanded(event: React.MouseEvent<HTMLDetailsElement>) {
const newVal = event.currentTarget.open;
Expand All @@ -140,12 +136,11 @@ export function Expandable({
>
<summary className={`${styles['expandable-header']} callout-header`}>
<div className={styles['expandable-title-container']}>
<ChevronDownIcon
className={`${styles['expandable-icon-expanded']} callout-icon`}
/>
<ChevronRightIcon
className={`${styles['expandable-icon-collapsed']} callout-icon`}
/>
{isExpanded ? (
<ChevronDownIcon className="callout-icon" />
) : (
<ChevronRightIcon className="callout-icon" />
)}
<div>{title}</div>
</div>
{copy && (
Expand All @@ -154,8 +149,8 @@ export function Expandable({
onClick={copyContentOnClick}
type="button" // Important for buttons in summaries
>
{showCopyButton && !copied && 'Copy Rules'}
{showCopyButton && copied && 'Copied!'}
{!copied && 'Copy Rules'}
{copied && 'Copied!'}
</button>
)}
</summary>
Expand Down
12 changes: 7 additions & 5 deletions src/components/expandable/style.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,24 @@
}

.expandable-title-container {
flex: 1;
display: flex;
align-items: center;
gap: 0.7rem;
}

.copy-button {
background-color: var(--code-block-background);
color: var(--text-default);
border: 1px solid var(--gray-200);
padding: 0.25rem 0.75rem;
border-radius: var(--border-radius-medium);
border-radius: 0.25rem;
cursor: pointer;
font-size: var(--font-size-small);
font-size: 0.75rem;
font-weight: 500;
line-height: 1.2;
transition: background-color 150ms linear, border-color 150ms linear, color 150ms linear;
transition:
background-color 150ms linear,
border-color 150ms linear,
color 150ms linear;

&:hover {
background-color: var(--gray-100);
Expand Down
Loading