11'use client' ;
22
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' ;
411
512import { type API } from 'sentry-docs/build/resolveOpenAPI' ;
613
14+ import codeBlockStyles from '../codeBlock/code-blocks.module.scss' ;
715import styles from './apiExamples.module.scss' ;
816
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' ;
4319
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 ) ;
7222
7323const strFormat = ( str : string ) => {
7424 const s = str . trim ( ) ;
@@ -82,6 +32,10 @@ type Props = {
8232 api : API ;
8333} ;
8434
35+ const codeToJsx = ( code : string , lang = 'json' ) => {
36+ return toJsxRuntime ( refractor . highlight ( code , lang ) as Nodes , { Fragment, jsx, jsxs} ) ;
37+ } ;
38+
8539export function ApiExamples ( { api} : Props ) {
8640 const apiExample = [
8741 `curl https://sentry.io${ api . apiPath } ` ,
@@ -112,11 +66,43 @@ export function ApiExamples({api}: Props) {
11266 ? [ 'RESPONSE' , 'SCHEMA' ]
11367 : [ 'RESPONSE' ] ;
11468
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+
11599 return (
116100 < 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 >
120106 < div className = "api-block" >
121107 < div className = "api-block-header response" >
122108 < div className = "tabs-group" >
@@ -149,12 +135,32 @@ export function ApiExamples({api}: Props) {
149135 )
150136 ) }
151137 </ div >
138+
139+ < button className = { styles . copy } onClick = { ( ) => copyCode ( codeToCopy ) } >
140+ { showCopyButton && < Clipboard size = { 16 } /> }
141+ </ button >
152142 </ 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 >
158164 </ div >
159165 </ Fragment >
160166 ) ;
0 commit comments