Skip to content

Commit 276ea40

Browse files
Fix: updated docs and added CodeBlock component
1 parent 13a1396 commit 276ea40

File tree

3 files changed

+248
-0
lines changed

3 files changed

+248
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import React, { useState, useRef, useEffect } from 'react';
2+
import PropTypes from 'prop-types';
3+
import './CodeBlock.scss';
4+
5+
const CodeBlock = ({ children, ...props }) => {
6+
const [copied, setCopied] = useState(false);
7+
const preRef = useRef(null);
8+
const timeoutRef = useRef(null);
9+
10+
// Extract the code content from the children
11+
const getCodeContent = () => {
12+
if (!preRef.current) return '';
13+
14+
const codeElement = preRef.current.querySelector('code');
15+
if (codeElement) {
16+
return codeElement.textContent || codeElement.innerText || '';
17+
}
18+
19+
// Fallback: get text from pre element
20+
return preRef.current.textContent || preRef.current.innerText || '';
21+
};
22+
23+
const handleCopy = async () => {
24+
const codeContent = getCodeContent();
25+
26+
if (!codeContent) return;
27+
28+
try {
29+
await navigator.clipboard.writeText(codeContent);
30+
setCopied(true);
31+
32+
// Clear any existing timeout
33+
if (timeoutRef.current) {
34+
clearTimeout(timeoutRef.current);
35+
}
36+
37+
// Reset the copied state after 2 seconds
38+
timeoutRef.current = setTimeout(() => {
39+
setCopied(false);
40+
}, 2000);
41+
} catch (err) {
42+
// Fallback for browsers that don't support clipboard API
43+
console.error('Failed to copy code:', err);
44+
45+
// Try the fallback method
46+
const textArea = document.createElement('textarea');
47+
textArea.value = codeContent;
48+
textArea.style.position = 'fixed';
49+
textArea.style.left = '-999999px';
50+
document.body.appendChild(textArea);
51+
textArea.select();
52+
53+
try {
54+
document.execCommand('copy');
55+
setCopied(true);
56+
if (timeoutRef.current) {
57+
clearTimeout(timeoutRef.current);
58+
}
59+
timeoutRef.current = setTimeout(() => {
60+
setCopied(false);
61+
}, 2000);
62+
} catch (fallbackErr) {
63+
console.error('Fallback copy failed:', fallbackErr);
64+
}
65+
66+
document.body.removeChild(textArea);
67+
}
68+
};
69+
70+
// Cleanup timeout on unmount
71+
useEffect(() => {
72+
return () => {
73+
if (timeoutRef.current) {
74+
clearTimeout(timeoutRef.current);
75+
}
76+
};
77+
}, []);
78+
79+
return (
80+
<div className="code-block-wrapper">
81+
<pre ref={preRef} {...props}>
82+
{children}
83+
</pre>
84+
<button
85+
type="button"
86+
className="code-block-copy-button"
87+
onClick={handleCopy}
88+
aria-label={copied ? 'Copied!' : 'Copy code'}
89+
title={copied ? 'Copied!' : 'Copy code'}
90+
>
91+
<svg
92+
className="code-block-copy-icon"
93+
width="16"
94+
height="16"
95+
viewBox="0 0 16 16"
96+
fill="none"
97+
xmlns="http://www.w3.org/2000/svg"
98+
>
99+
{copied ? (
100+
// Checkmark icon
101+
<path
102+
d="M13.5 4.5L6 12L2.5 8.5"
103+
stroke="currentColor"
104+
strokeWidth="2"
105+
strokeLinecap="round"
106+
strokeLinejoin="round"
107+
fill="none"
108+
/>
109+
) : (
110+
// Copy icon - two overlapping squares
111+
<>
112+
<rect
113+
x="4"
114+
y="4"
115+
width="8"
116+
height="8"
117+
rx="1"
118+
stroke="currentColor"
119+
strokeWidth="1.5"
120+
fill="none"
121+
/>
122+
<rect
123+
x="6"
124+
y="6"
125+
width="8"
126+
height="8"
127+
rx="1"
128+
stroke="currentColor"
129+
strokeWidth="1.5"
130+
fill="none"
131+
/>
132+
</>
133+
)}
134+
</svg>
135+
<span className="code-block-copy-text">
136+
{copied ? 'Copied!' : 'Copy'}
137+
</span>
138+
</button>
139+
</div>
140+
);
141+
};
142+
143+
CodeBlock.propTypes = {
144+
children: PropTypes.node.isRequired,
145+
};
146+
147+
export default CodeBlock;
148+
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
@import 'vars';
2+
@import 'functions';
3+
4+
.code-block-wrapper {
5+
position: relative;
6+
margin: 1em 0; // Match the margin from .markdown pre
7+
8+
// Ensure pre has relative positioning for absolute button
9+
pre {
10+
position: relative;
11+
margin: 0; // Remove margin from pre since wrapper has it
12+
}
13+
}
14+
15+
.code-block-copy-button {
16+
position: absolute;
17+
top: 8px;
18+
right: 8px;
19+
display: flex;
20+
align-items: center;
21+
gap: 6px;
22+
padding: 6px 12px;
23+
background-color: transparentize(getColor(elephant), 0.2);
24+
border: 1px solid transparentize(getColor(white), 0.9);
25+
border-radius: 4px;
26+
color: getColor(malibu);
27+
font-size: 12px;
28+
font-family: $font-stack-body;
29+
cursor: pointer;
30+
transition: all 200ms ease;
31+
z-index: 10;
32+
33+
// Subtle by default, more visible on hover
34+
opacity: 0.7;
35+
36+
.code-block-wrapper:hover & {
37+
opacity: 1;
38+
background-color: transparentize(getColor(elephant), 0.1);
39+
border-color: transparentize(getColor(white), 0.8);
40+
}
41+
42+
&:hover {
43+
background-color: transparentize(getColor(elephant), 0.05);
44+
border-color: transparentize(getColor(white), 0.75);
45+
color: lighten(getColor(malibu), 10%);
46+
opacity: 1;
47+
}
48+
49+
&:active {
50+
transform: scale(0.98);
51+
}
52+
53+
&:focus {
54+
outline: 2px solid getColor(malibu);
55+
outline-offset: 2px;
56+
opacity: 1;
57+
}
58+
}
59+
60+
.code-block-copy-icon {
61+
width: 16px;
62+
height: 16px;
63+
flex-shrink: 0;
64+
}
65+
66+
.code-block-copy-text {
67+
white-space: nowrap;
68+
font-weight: 500;
69+
}
70+
71+
// Dark theme support
72+
[data-theme='dark'] {
73+
.code-block-copy-button {
74+
background-color: transparentize(#131b1f, 0.3);
75+
border-color: transparentize(getColor(white), 0.95);
76+
color: #69a8ee;
77+
opacity: 0.7;
78+
79+
.code-block-wrapper:hover & {
80+
opacity: 1;
81+
background-color: transparentize(#131b1f, 0.15);
82+
border-color: transparentize(getColor(white), 0.9);
83+
}
84+
85+
&:hover {
86+
background-color: transparentize(#131b1f, 0.05);
87+
border-color: transparentize(getColor(white), 0.8);
88+
color: #82b7f6;
89+
opacity: 1;
90+
}
91+
92+
&:focus {
93+
outline-color: #69a8ee;
94+
opacity: 1;
95+
}
96+
}
97+
}
98+

src/mdx-components.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import Badge from './components/Badge/Badge';
22
import LinkComponent from './components/mdxComponents/Link';
33
import StackBlitzPreview from './components/StackBlitzPreview/StackBlitzPreview';
4+
import CodeBlock from './components/CodeBlock/CodeBlock';
45

56
/** @returns {import('mdx/types.js').MDXComponents} */
67
export function useMDXComponents() {
78
return {
89
a: LinkComponent,
910
Badge: Badge,
1011
StackBlitzPreview: StackBlitzPreview,
12+
pre: CodeBlock,
1113
};
1214
}

0 commit comments

Comments
 (0)