Skip to content

Commit ec6cfde

Browse files
committed
add copy paste
1 parent 15ba41c commit ec6cfde

File tree

3 files changed

+87
-50
lines changed

3 files changed

+87
-50
lines changed

src/components/apiExamples/apiExamples.module.scss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,17 @@
1919
margin: 0;
2020
}
2121
}
22+
23+
.copied {
24+
position: absolute;
25+
right: 4px;
26+
top: 4px;
27+
margin: 0;
28+
padding: 0 6px;
29+
border-radius: 2px;
30+
font-size: 0.85rem;
31+
background: rgba(255, 255, 255, 0.25);
32+
border: none;
33+
color: var(--white);
34+
transition: opacity 150ms;
35+
}

src/components/apiExamples/apiExamples.tsx

Lines changed: 71 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
'use client';
22

3-
import {Fragment, useState} from 'react';
3+
import {Fragment, useEffect, useState} from 'react';
44
import {jsx, jsxs} from 'react/jsx-runtime';
5+
import {Clipboard} from 'react-feather';
56
import {toJsxRuntime} from 'hast-util-to-jsx-runtime';
67
import {Nodes} from 'hastscript/lib/create-h';
78
import bash from 'refractor/lang/bash.js';
@@ -10,6 +11,7 @@ import {refractor} from 'refractor/lib/core.js';
1011

1112
import {type API} from 'sentry-docs/build/resolveOpenAPI';
1213

14+
import codeBlockStyles from '../codeBlock/code-blocks.module.scss';
1315
import styles from './apiExamples.module.scss';
1416

1517
import {CodeBlock} from '../codeBlock';
@@ -18,50 +20,6 @@ import {CodeTabs} from '../codeTabs';
1820
refractor.register(bash);
1921
refractor.register(json);
2022

21-
type ExampleProps = {
22-
api: API;
23-
selectedResponse: number;
24-
selectedTabView: number;
25-
};
26-
27-
function Example({api, selectedTabView, selectedResponse}: ExampleProps) {
28-
let exampleJson: any;
29-
if (api.responses[selectedResponse].content?.examples) {
30-
exampleJson = Object.values(
31-
api.responses[selectedResponse].content?.examples ?? {}
32-
).map(e => e.value)[0];
33-
} else if (api.responses[selectedResponse].content?.example) {
34-
exampleJson = api.responses[selectedResponse].content?.example;
35-
}
36-
37-
return (
38-
<pre className={styles['api-block-example']}>
39-
{selectedTabView === 0 &&
40-
(exampleJson ? (
41-
<code className="!text-[0.8rem]">
42-
{toJsxRuntime(
43-
refractor.highlight(JSON.stringify(exampleJson, null, 2), 'json') as Nodes,
44-
{Fragment, jsx, jsxs}
45-
)}
46-
</code>
47-
) : (
48-
strFormat(api.responses[selectedResponse].description)
49-
))}
50-
{selectedTabView === 1 && (
51-
<code className="!text-[0.8rem]">
52-
{toJsxRuntime(
53-
refractor.highlight(
54-
JSON.stringify(api.responses[selectedResponse].content?.schema, null, 2),
55-
'json'
56-
) as Nodes,
57-
{Fragment, jsx, jsxs}
58-
)}
59-
</code>
60-
)}
61-
</pre>
62-
);
63-
}
64-
6523
const strFormat = (str: string) => {
6624
const s = str.trim();
6725
if (s.endsWith('.')) {
@@ -104,6 +62,36 @@ export function ApiExamples({api}: Props) {
10462
? ['RESPONSE', 'SCHEMA']
10563
: ['RESPONSE'];
10664

65+
const [showCopied, setShowCopied] = useState(false);
66+
67+
// Show the copy button after js has loaded
68+
// otherwise the copy button will not work
69+
const [showCopyButton, setShowCopyButton] = useState(false);
70+
useEffect(() => {
71+
setShowCopyButton(true);
72+
}, []);
73+
async function copyCode(code: string) {
74+
await navigator.clipboard.writeText(code);
75+
setShowCopied(true);
76+
setTimeout(() => setShowCopied(false), 1200);
77+
}
78+
79+
let exampleJson: any;
80+
if (api.responses[selectedResponse].content?.examples) {
81+
exampleJson = Object.values(
82+
api.responses[selectedResponse].content?.examples ?? {}
83+
).map(e => e.value)[0];
84+
} else if (api.responses[selectedResponse].content?.example) {
85+
exampleJson = api.responses[selectedResponse].content?.example;
86+
}
87+
88+
const codeToCopy =
89+
selectedTabView === 0
90+
? exampleJson
91+
? JSON.stringify(exampleJson, null, 2)
92+
: strFormat(api.responses[selectedResponse].description)
93+
: JSON.stringify(api.responses[selectedResponse].content?.schema, null, 2);
94+
10795
return (
10896
<Fragment>
10997
<CodeTabs>
@@ -152,12 +140,45 @@ export function ApiExamples({api}: Props) {
152140
)
153141
)}
154142
</div>
143+
144+
<button className={styles.copy} onClick={() => copyCode(codeToCopy)}>
145+
{showCopyButton && <Clipboard size={16} />}
146+
</button>
155147
</div>
156-
<Example
157-
api={api}
158-
selectedTabView={selectedTabView}
159-
selectedResponse={selectedResponse}
160-
/>
148+
<pre className={`${styles['api-block-example']} relative`}>
149+
<div className={codeBlockStyles.copied} style={{opacity: showCopied ? 1 : 0}}>
150+
Copied
151+
</div>
152+
{selectedTabView === 0 &&
153+
(exampleJson ? (
154+
<code className="!text-[0.8rem]">
155+
{toJsxRuntime(
156+
refractor.highlight(
157+
JSON.stringify(exampleJson, null, 2),
158+
'json'
159+
) as Nodes,
160+
{Fragment, jsx, jsxs}
161+
)}
162+
</code>
163+
) : (
164+
strFormat(api.responses[selectedResponse].description)
165+
))}
166+
{selectedTabView === 1 && (
167+
<code className="!text-[0.8rem]">
168+
{toJsxRuntime(
169+
refractor.highlight(
170+
JSON.stringify(
171+
api.responses[selectedResponse].content?.schema,
172+
null,
173+
2
174+
),
175+
'json'
176+
) as Nodes,
177+
{Fragment, jsx, jsxs}
178+
)}
179+
</code>
180+
)}
181+
</pre>
161182
</div>
162183
</Fragment>
163184
);

src/components/apiPage/styles.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@
6161

6262
.response-status-btn-group {
6363
border-radius: 3px;
64+
margin-left: auto;
65+
margin-right: 1rem;
6466
}
6567

6668
.response-status-btn {

0 commit comments

Comments
 (0)