fix: better copy icon
This commit is contained in:
@@ -103,29 +103,48 @@
|
|||||||
|
|
||||||
.documentation-code-copy-button {
|
.documentation-code-copy-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: .4rem;
|
||||||
right: 8px;
|
right: .4rem;
|
||||||
border: 1px solid var(--doc-border);
|
border: 1px solid var(--doc-border);
|
||||||
border-radius: 6px;
|
border-radius: .25rem;
|
||||||
background: var(--doc-surface);
|
background: var(--doc-surface);
|
||||||
color: var(--doc-text);
|
color: var(--doc-muted);
|
||||||
width: 30px;
|
width: 1.8rem;
|
||||||
height: 30px;
|
height: 1.8rem;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
opacity: .88;
|
||||||
}
|
}
|
||||||
|
|
||||||
.documentation-code-copy-button:hover {
|
.documentation-code-copy-button:hover,
|
||||||
background: var(--doc-code-bg);
|
.documentation-code-copy-button:focus-visible {
|
||||||
|
opacity: 1;
|
||||||
|
color: var(--doc-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.documentation-code-block pre {
|
.documentation-code-block pre {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.documentation-code-block .code-copy-icon {
|
||||||
|
font-size: .95rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.documentation-code-block.code-copy-success .documentation-code-copy-button {
|
||||||
|
color: var(--pico-ins-color, rgb(53, 117, 56));
|
||||||
|
border-color: var(--pico-ins-color, rgb(53, 117, 56));
|
||||||
|
}
|
||||||
|
|
||||||
|
.documentation-code-block.code-copy-failed .documentation-code-copy-button {
|
||||||
|
color: var(--pico-del-color, rgb(183, 72, 72));
|
||||||
|
border-color: var(--pico-del-color, rgb(183, 72, 72));
|
||||||
|
}
|
||||||
|
|
||||||
.documentation-content.markdown-body blockquote {
|
.documentation-content.markdown-body blockquote {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
padding: 0 0 0 12px;
|
padding: 0 0 0 12px;
|
||||||
|
|||||||
@@ -103,25 +103,20 @@ export const DocumentationView: React.FC = () => {
|
|||||||
const preferredScrollContainer = scrollContainerRef.current;
|
const preferredScrollContainer = scrollContainerRef.current;
|
||||||
|
|
||||||
if (!articleElement) {
|
if (!articleElement) {
|
||||||
console.info('[DocumentationView] hash jump skipped', { href, targetId, reason: 'article-not-available' });
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetHeading = resolveTargetHeadingInArticle(articleElement, targetId);
|
const targetHeading = resolveTargetHeadingInArticle(articleElement, targetId);
|
||||||
|
|
||||||
if (!targetHeading) {
|
if (!targetHeading) {
|
||||||
console.info('[DocumentationView] hash jump skipped', { href, targetId, reason: 'target-not-found-or-outside-article' });
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollContainer = resolveScrollContainer(targetHeading, preferredScrollContainer);
|
const scrollContainer = resolveScrollContainer(targetHeading, preferredScrollContainer);
|
||||||
if (!scrollContainer) {
|
if (!scrollContainer) {
|
||||||
console.info('[DocumentationView] hash jump skipped', { href, targetId, reason: 'no-scroll-container' });
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const beforeTop = scrollContainer.scrollTop;
|
|
||||||
|
|
||||||
const containerRect = scrollContainer.getBoundingClientRect();
|
const containerRect = scrollContainer.getBoundingClientRect();
|
||||||
const headingRect = targetHeading.getBoundingClientRect();
|
const headingRect = targetHeading.getBoundingClientRect();
|
||||||
const targetTop = Math.max(0, scrollContainer.scrollTop + (headingRect.top - containerRect.top) - 12);
|
const targetTop = Math.max(0, scrollContainer.scrollTop + (headingRect.top - containerRect.top) - 12);
|
||||||
@@ -130,19 +125,6 @@ export const DocumentationView: React.FC = () => {
|
|||||||
scrollContainer.scrollTop = targetTop;
|
scrollContainer.scrollTop = targetTop;
|
||||||
window.location.hash = href;
|
window.location.hash = href;
|
||||||
|
|
||||||
articleElement.dataset.lastJumpTarget = targetId;
|
|
||||||
articleElement.dataset.lastJumpTop = String(targetTop);
|
|
||||||
articleElement.dataset.lastJumpContainer = scrollContainer.className || scrollContainer.tagName;
|
|
||||||
|
|
||||||
console.info('[DocumentationView] hash jump applied', {
|
|
||||||
href,
|
|
||||||
targetId,
|
|
||||||
beforeTop,
|
|
||||||
targetTop,
|
|
||||||
afterTop: scrollContainer.scrollTop,
|
|
||||||
container: scrollContainer.className || scrollContainer.tagName,
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -203,10 +185,14 @@ export const DocumentationView: React.FC = () => {
|
|||||||
<div className="documentation-code-block" key={codeBlockKey}>
|
<div className="documentation-code-block" key={codeBlockKey}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="documentation-code-copy-button"
|
className="code-copy-button documentation-code-copy-button"
|
||||||
aria-label={tr('docs.copyCode')}
|
aria-label={tr('docs.copyCode')}
|
||||||
title={tr('docs.copyCode')}
|
title={tr('docs.copyCode')}
|
||||||
onClick={() => {
|
onClick={(event) => {
|
||||||
|
const button = event.currentTarget;
|
||||||
|
const wrapper = button.closest('.documentation-code-block');
|
||||||
|
const icon = button.querySelector('.code-copy-icon');
|
||||||
|
|
||||||
const copyToClipboard = async () => {
|
const copyToClipboard = async () => {
|
||||||
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
||||||
await navigator.clipboard.writeText(sourceCode);
|
await navigator.clipboard.writeText(sourceCode);
|
||||||
@@ -225,13 +211,30 @@ export const DocumentationView: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
copyToClipboard()
|
copyToClipboard()
|
||||||
.then(() => undefined)
|
.then(() => {
|
||||||
|
wrapper?.classList.remove('code-copy-failed');
|
||||||
|
wrapper?.classList.remove('code-copy-success');
|
||||||
|
wrapper?.classList.add('code-copy-success');
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
icon.textContent = '✓';
|
||||||
|
setTimeout(() => {
|
||||||
|
icon.textContent = '⧉';
|
||||||
|
wrapper?.classList.remove('code-copy-success');
|
||||||
|
}, 1200);
|
||||||
|
}
|
||||||
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
wrapper?.classList.remove('code-copy-success');
|
||||||
|
wrapper?.classList.add('code-copy-failed');
|
||||||
|
setTimeout(() => {
|
||||||
|
wrapper?.classList.remove('code-copy-failed');
|
||||||
|
}, 1200);
|
||||||
console.error('Failed to copy documentation code block:', error);
|
console.error('Failed to copy documentation code block:', error);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
📋
|
<span className="code-copy-icon">⧉</span>
|
||||||
</button>
|
</button>
|
||||||
<pre>
|
<pre>
|
||||||
<code
|
<code
|
||||||
|
|||||||
@@ -80,9 +80,6 @@ describe('DocumentationView', () => {
|
|||||||
fireEvent.click(tocLink as HTMLAnchorElement);
|
fireEvent.click(tocLink as HTMLAnchorElement);
|
||||||
|
|
||||||
expect((scrollContainer as HTMLElement).scrollTop).toBe(238);
|
expect((scrollContainer as HTMLElement).scrollTop).toBe(238);
|
||||||
const article = container.querySelector('.documentation-article') as HTMLElement;
|
|
||||||
expect(article.dataset.lastJumpTarget).toBe('who-this-guide-is-for');
|
|
||||||
expect(article.dataset.lastJumpTop).toBe('238');
|
|
||||||
expect(window.location.hash).toBe('#who-this-guide-is-for');
|
expect(window.location.hash).toBe('#who-this-guide-is-for');
|
||||||
expect(window.open).not.toHaveBeenCalled();
|
expect(window.open).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -190,7 +187,6 @@ describe('DocumentationView', () => {
|
|||||||
|
|
||||||
expect(firstScroll.scrollTop).toBe(7);
|
expect(firstScroll.scrollTop).toBe(7);
|
||||||
expect(secondScroll.scrollTop).toBe(299);
|
expect(secondScroll.scrollTop).toBe(299);
|
||||||
expect(secondArticle.dataset.lastJumpTarget).toBe('using-macros');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('falls back to heading text slug when heading id does not match hash', async () => {
|
it('falls back to heading text slug when heading id does not match hash', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user