diff --git a/package.json b/package.json index baab1f1ced367..7de1dfd784382 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "esbuild": "^0.19.8", "framer-motion": "^10.12.16", "gray-matter": "^4.0.3", + "hast-util-to-jsx-runtime": "^2.3.2", "hastscript": "^8.0.0", "image-size": "^1.1.1", "js-cookie": "^3.0.5", @@ -79,7 +80,6 @@ "parse-numeric-range": "^1.3.0", "platformicons": "^6.0.3", "prism-sentry": "^1.0.2", - "prismjs": "^1.27.0", "query-string": "^6.13.1", "react": "^18", "react-dom": "^18", @@ -137,4 +137,4 @@ "node": "20.11.0", "yarn": "1.22.21" } -} \ No newline at end of file +} diff --git a/src/components/apiExamples/apiExamples.module.scss b/src/components/apiExamples/apiExamples.module.scss index 3a0a0213c0a88..90ea811ac4fb3 100644 --- a/src/components/apiExamples/apiExamples.module.scss +++ b/src/components/apiExamples/apiExamples.module.scss @@ -1,4 +1,6 @@ .api-block-example { + background-color: var(--code-background); + color: var(--white); border: none; border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; @@ -10,17 +12,6 @@ padding: 0.75rem; } -.api-block-example.request { - color: var(--white); - background-color: #2d2d2d; - border-radius: 4px; -} - -.api-block-example.response { - background: #2d2d2d; - color: var(--white); -} - .api-params dd { padding: 0; diff --git a/src/components/apiExamples/apiExamples.tsx b/src/components/apiExamples/apiExamples.tsx index dfa9253c996b0..cc5b026aeb005 100644 --- a/src/components/apiExamples/apiExamples.tsx +++ b/src/components/apiExamples/apiExamples.tsx @@ -1,74 +1,24 @@ 'use client'; -import {Fragment, useEffect, useRef, useState} from 'react'; +import {Fragment, useEffect, useState} from 'react'; +import {jsx, jsxs} from 'react/jsx-runtime'; +import {Clipboard} from 'react-feather'; +import {toJsxRuntime} from 'hast-util-to-jsx-runtime'; +import {Nodes} from 'hastscript/lib/create-h'; +import bash from 'refractor/lang/bash.js'; +import json from 'refractor/lang/json.js'; +import {refractor} from 'refractor/lib/core.js'; import {type API} from 'sentry-docs/build/resolveOpenAPI'; +import codeBlockStyles from '../codeBlock/code-blocks.module.scss'; import styles from './apiExamples.module.scss'; -type ExampleProps = { - api: API; - selectedResponse: number; - selectedTabView: number; -}; - -const requestStyles = `${styles['api-block-example']} ${styles.request}`; -const responseStyles = `${styles['api-block-example']} ${styles.response}`; - -// overwriting global code block font size -const jsonCodeBlockStyles = `!text-[0.8rem] language-json`; - -function Example({api, selectedTabView, selectedResponse}: ExampleProps) { - const ref = useRef(null); - let exampleJson: any; - if (api.responses[selectedResponse].content?.examples) { - exampleJson = Object.values( - api.responses[selectedResponse].content?.examples ?? {} - ).map(e => e.value)[0]; - } else if (api.responses[selectedResponse].content?.example) { - exampleJson = api.responses[selectedResponse].content?.example; - } - - // load prism dynamically for these codeblocks, - // otherwise the highlighting applies globally - useEffect(() => { - (async () => { - const {highlightAllUnder} = await import('prismjs'); - await import('prismjs/components/prism-json'); - if (ref.current) { - highlightAllUnder(ref.current); - } - })(); - }, [selectedResponse, selectedTabView]); +import {CodeBlock} from '../codeBlock'; +import {CodeTabs} from '../codeTabs'; - return ( -
- {selectedTabView === 0 && - (exampleJson ? ( -- ); -} +refractor.register(bash); +refractor.register(json); const strFormat = (str: string) => { const s = str.trim(); @@ -82,6 +32,10 @@ type Props = { api: API; }; +const codeToJsx = (code: string, lang = 'json') => { + return toJsxRuntime(refractor.highlight(code, lang) as Nodes, {Fragment, jsx, jsxs}); +}; + export function ApiExamples({api}: Props) { const apiExample = [ `curl https://sentry.io${api.apiPath}`, @@ -112,11 +66,43 @@ export function ApiExamples({api}: Props) { ? ['RESPONSE', 'SCHEMA'] : ['RESPONSE']; + const [showCopied, setShowCopied] = useState(false); + + // Show the copy button after js has loaded + // otherwise the copy button will not work + const [showCopyButton, setShowCopyButton] = useState(false); + useEffect(() => { + setShowCopyButton(true); + }, []); + async function copyCode(code: string) { + await navigator.clipboard.writeText(code); + setShowCopied(true); + setTimeout(() => setShowCopied(false), 1200); + } + + let exampleJson: any; + if (api.responses[selectedResponse].content?.examples) { + exampleJson = Object.values( + api.responses[selectedResponse].content?.examples ?? {} + ).map(e => e.value)[0]; + } else if (api.responses[selectedResponse].content?.example) { + exampleJson = api.responses[selectedResponse].content?.example; + } + + const codeToCopy = + selectedTabView === 0 + ? exampleJson + ? JSON.stringify(exampleJson, null, 2) + : strFormat(api.responses[selectedResponse].description) + : JSON.stringify(api.responses[selectedResponse].content?.schema, null, 2); + return (- ) : ( - strFormat(api.responses[selectedResponse].description) - ))} - {selectedTabView === 1 && ( -
- )} -
{apiExample.join(' \\\n')}-
{codeToJsx(apiExample.join(' \\\n'), 'bash')}+
++ Copied ++ {selectedTabView === 0 && + (exampleJson ? ( ++ {codeToJsx(JSON.stringify(exampleJson, null, 2), 'json')} +
+ ) : ( + strFormat(api.responses[selectedResponse].description) + ))} + {selectedTabView === 1 && ( ++ {codeToJsx( + JSON.stringify(api.responses[selectedResponse].content?.schema, null, 2), + 'json' + )} +
+ )} +