1
1
'use client' ;
2
2
3
- import { Fragment , useEffect , useRef , useState } from 'react' ;
3
+ import { Fragment , useEffect , useState } from 'react' ;
4
+ import { jsx , jsxs } from 'react/jsx-runtime' ;
5
+ import { Clipboard } from 'react-feather' ;
6
+ import { toJsxRuntime } from 'hast-util-to-jsx-runtime' ;
7
+ import { Nodes } from 'hastscript/lib/create-h' ;
8
+ import bash from 'refractor/lang/bash.js' ;
9
+ import json from 'refractor/lang/json.js' ;
10
+ import { refractor } from 'refractor/lib/core.js' ;
4
11
5
12
import { type API } from 'sentry-docs/build/resolveOpenAPI' ;
6
13
14
+ import codeBlockStyles from '../codeBlock/code-blocks.module.scss' ;
7
15
import styles from './apiExamples.module.scss' ;
8
16
9
- type ExampleProps = {
10
- api : API ;
11
- selectedResponse : number ;
12
- selectedTabView : number ;
13
- } ;
14
-
15
- const requestStyles = `${ styles [ 'api-block-example' ] } ${ styles . request } ` ;
16
- const responseStyles = `${ styles [ 'api-block-example' ] } ${ styles . response } ` ;
17
-
18
- // overwriting global code block font size
19
- const jsonCodeBlockStyles = `!text-[0.8rem] language-json` ;
20
-
21
- function Example ( { api, selectedTabView, selectedResponse} : ExampleProps ) {
22
- const ref = useRef ( null ) ;
23
- let exampleJson : any ;
24
- if ( api . responses [ selectedResponse ] . content ?. examples ) {
25
- exampleJson = Object . values (
26
- api . responses [ selectedResponse ] . content ?. examples ?? { }
27
- ) . map ( e => e . value ) [ 0 ] ;
28
- } else if ( api . responses [ selectedResponse ] . content ?. example ) {
29
- exampleJson = api . responses [ selectedResponse ] . content ?. example ;
30
- }
31
-
32
- // load prism dynamically for these codeblocks,
33
- // otherwise the highlighting applies globally
34
- useEffect ( ( ) => {
35
- ( async ( ) => {
36
- const { highlightAllUnder} = await import ( 'prismjs' ) ;
37
- await import ( 'prismjs/components/prism-json' ) ;
38
- if ( ref . current ) {
39
- highlightAllUnder ( ref . current ) ;
40
- }
41
- } ) ( ) ;
42
- } , [ selectedResponse , selectedTabView ] ) ;
17
+ import { CodeBlock } from '../codeBlock' ;
18
+ import { CodeTabs } from '../codeTabs' ;
43
19
44
- return (
45
- < pre className = { responseStyles } ref = { ref } >
46
- { selectedTabView === 0 &&
47
- ( exampleJson ? (
48
- < code
49
- className = { jsonCodeBlockStyles }
50
- dangerouslySetInnerHTML = { {
51
- __html : JSON . stringify ( exampleJson , null , 2 ) ,
52
- } }
53
- />
54
- ) : (
55
- strFormat ( api . responses [ selectedResponse ] . description )
56
- ) ) }
57
- { selectedTabView === 1 && (
58
- < code
59
- className = { jsonCodeBlockStyles }
60
- dangerouslySetInnerHTML = { {
61
- __html : JSON . stringify (
62
- api . responses [ selectedResponse ] . content ?. schema ,
63
- null ,
64
- 2
65
- ) ,
66
- } }
67
- />
68
- ) }
69
- </ pre >
70
- ) ;
71
- }
20
+ refractor . register ( bash ) ;
21
+ refractor . register ( json ) ;
72
22
73
23
const strFormat = ( str : string ) => {
74
24
const s = str . trim ( ) ;
@@ -82,6 +32,10 @@ type Props = {
82
32
api : API ;
83
33
} ;
84
34
35
+ const codeToJsx = ( code : string , lang = 'json' ) => {
36
+ return toJsxRuntime ( refractor . highlight ( code , lang ) as Nodes , { Fragment, jsx, jsxs} ) ;
37
+ } ;
38
+
85
39
export function ApiExamples ( { api} : Props ) {
86
40
const apiExample = [
87
41
`curl https://sentry.io${ api . apiPath } ` ,
@@ -112,11 +66,43 @@ export function ApiExamples({api}: Props) {
112
66
? [ 'RESPONSE' , 'SCHEMA' ]
113
67
: [ 'RESPONSE' ] ;
114
68
69
+ const [ showCopied , setShowCopied ] = useState ( false ) ;
70
+
71
+ // Show the copy button after js has loaded
72
+ // otherwise the copy button will not work
73
+ const [ showCopyButton , setShowCopyButton ] = useState ( false ) ;
74
+ useEffect ( ( ) => {
75
+ setShowCopyButton ( true ) ;
76
+ } , [ ] ) ;
77
+ async function copyCode ( code : string ) {
78
+ await navigator . clipboard . writeText ( code ) ;
79
+ setShowCopied ( true ) ;
80
+ setTimeout ( ( ) => setShowCopied ( false ) , 1200 ) ;
81
+ }
82
+
83
+ let exampleJson : any ;
84
+ if ( api . responses [ selectedResponse ] . content ?. examples ) {
85
+ exampleJson = Object . values (
86
+ api . responses [ selectedResponse ] . content ?. examples ?? { }
87
+ ) . map ( e => e . value ) [ 0 ] ;
88
+ } else if ( api . responses [ selectedResponse ] . content ?. example ) {
89
+ exampleJson = api . responses [ selectedResponse ] . content ?. example ;
90
+ }
91
+
92
+ const codeToCopy =
93
+ selectedTabView === 0
94
+ ? exampleJson
95
+ ? JSON . stringify ( exampleJson , null , 2 )
96
+ : strFormat ( api . responses [ selectedResponse ] . description )
97
+ : JSON . stringify ( api . responses [ selectedResponse ] . content ?. schema , null , 2 ) ;
98
+
115
99
return (
116
100
< Fragment >
117
- < div className = "api-block" >
118
- < pre className = { requestStyles } > { apiExample . join ( ' \\\n' ) } </ pre >
119
- </ div >
101
+ < CodeTabs >
102
+ < CodeBlock language = "bash" >
103
+ < pre > { codeToJsx ( apiExample . join ( ' \\\n' ) , 'bash' ) } </ pre >
104
+ </ CodeBlock >
105
+ </ CodeTabs >
120
106
< div className = "api-block" >
121
107
< div className = "api-block-header response" >
122
108
< div className = "tabs-group" >
@@ -149,12 +135,32 @@ export function ApiExamples({api}: Props) {
149
135
)
150
136
) }
151
137
</ div >
138
+
139
+ < button className = { styles . copy } onClick = { ( ) => copyCode ( codeToCopy ) } >
140
+ { showCopyButton && < Clipboard size = { 16 } /> }
141
+ </ button >
152
142
</ div >
153
- < Example
154
- api = { api }
155
- selectedTabView = { selectedTabView }
156
- selectedResponse = { selectedResponse }
157
- />
143
+ < pre className = { `${ styles [ 'api-block-example' ] } relative` } >
144
+ < div className = { codeBlockStyles . copied } style = { { opacity : showCopied ? 1 : 0 } } >
145
+ Copied
146
+ </ div >
147
+ { selectedTabView === 0 &&
148
+ ( exampleJson ? (
149
+ < code className = "!text-[0.8rem]" >
150
+ { codeToJsx ( JSON . stringify ( exampleJson , null , 2 ) , 'json' ) }
151
+ </ code >
152
+ ) : (
153
+ strFormat ( api . responses [ selectedResponse ] . description )
154
+ ) ) }
155
+ { selectedTabView === 1 && (
156
+ < code className = "!text-[0.8rem]" >
157
+ { codeToJsx (
158
+ JSON . stringify ( api . responses [ selectedResponse ] . content ?. schema , null , 2 ) ,
159
+ 'json'
160
+ ) }
161
+ </ code >
162
+ ) }
163
+ </ pre >
158
164
</ div >
159
165
</ Fragment >
160
166
) ;
0 commit comments