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
72 changes: 72 additions & 0 deletions src/components/CodeBlockWithCopy/CodeBlockWithCopy.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useRef, useState } from 'react';
import PropTypes from 'prop-types';
import './CodeBlockWithCopy.scss';

export default function CodeBlockWithCopy({ children }) {
const preRef = useRef(null);
const [copyStatus, setCopyStatus] = useState('copy');

const handleCopy = async () => {
if (!preRef.current) return;

const codeElement = preRef.current.querySelector('code');
if (!codeElement) return;

const codeText = codeElement.textContent;
let successfulCopy = false;

// Try modern API (navigator.clipboard) -> as document.execCommand() deprecated
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(codeText);
successfulCopy = true;
}
} catch (err) {
console.log(err);
}

// If modern API failed, fall back to deprecated document.execCommand('copy')
if (!successfulCopy) {
const textarea = document.createElement('textarea');
textarea.value = codeText;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';

document.body.appendChild(textarea);
textarea.select();

try {
// This deprecated method is kept as a fallback for compatibility/iframe environments.
successfulCopy = document.execCommand('copy');
} catch (err) {
successfulCopy = false;
console.log(err);
}

document.body.removeChild(textarea);
}

setCopyStatus(successfulCopy ? 'copied' : 'error');
setTimeout(() => setCopyStatus('copy'), 2000);
};

return (
<div className="code-block-wrapper">
<button onClick={handleCopy} className={`copy-button ${copyStatus}`}>
{copyStatus === 'copied'
? 'Copied!'
: copyStatus === 'error'
? 'Error'
: 'Copy'}
</button>

<pre ref={preRef} className="code-block">
{children}
</pre>
</div>
);
}

CodeBlockWithCopy.propTypes = {
children: PropTypes.node.isRequired,
};
74 changes: 74 additions & 0 deletions src/components/CodeBlockWithCopy/CodeBlockWithCopy.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
.code-block-wrapper {
position: relative;
margin-bottom: 1.5rem;
}

.code-block {
background-color: #2d3748;
color: #e2e8f0;
padding: 1rem;
padding-right: 3.5rem;
border-radius: 0.5rem;
overflow-x: auto;
font-size: 0.875rem;
line-height: 1.5;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);

code {
font-family: monospace;
}
}

.copy-button {
position: absolute;
top: 0.32rem;
right: 0.5rem;
z-index: 10;

padding: 0.4rem 0.7rem;
border-radius: 0.35rem;

border: none;
cursor: pointer;

font-size: 0.75rem;
font-weight: 500;

/* Always visible */
opacity: 1;

background-color: #7c3aed;
color: #e2e8f0;

transition:
background-color 0.2s,
transform 0.1s;

&:hover {
background-color: #6d28d9;
}

/* Success */
&.copied {
background-color: #38a169;
}
&.copied:hover {
background-color: #2f855a;
}

/* Error */
&.error {
background-color: #e53e3e;
}
&.error:hover {
background-color: #c53030;
}

&:focus {
outline: none;
}

&:active {
transform: scale(0.95);
}
}
6 changes: 6 additions & 0 deletions src/content/concepts/output.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ contributors:
- EugeneHlushko
---

import CodeBlockWithCopy from '../../components/CodeBlockWithCopy/CodeBlockWithCopy'

Configuring the `output` configuration options tells webpack how to write the compiled files to disk. Note that, while there can be multiple `entry` points, only one `output` configuration is specified.

## Usage
Expand All @@ -17,6 +19,8 @@ The minimum requirement for the `output` property in your webpack configuration

**webpack.config.js**

<CodeBlockWithCopy>

```javascript
module.exports = {
output: {
Expand All @@ -25,6 +29,8 @@ module.exports = {
};
```

</CodeBlockWithCopy>

This configuration would output a single `bundle.js` file into the `dist` directory.

## Multiple Entry Points
Expand Down
2 changes: 2 additions & 0 deletions src/mdx-components.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import Badge from './components/Badge/Badge';
import LinkComponent from './components/mdxComponents/Link';
import StackBlitzPreview from './components/StackBlitzPreview/StackBlitzPreview';
import CodeBlockWithCopy from './components/CodeBlockWithCopy/CodeBlockWithCopy';

/** @returns {import('mdx/types.js').MDXComponents} */
export function useMDXComponents() {
return {
a: LinkComponent,
Badge: Badge,
StackBlitzPreview: StackBlitzPreview,
pre: CodeBlockWithCopy,
};
}
Loading