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 ? (
-          
-        ) : (
-          strFormat(api.responses[selectedResponse].description)
-        ))}
-      {selectedTabView === 1 && (
-        
-      )}
-    
- ); -} +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 ( -
-
{apiExample.join(' \\\n')}
-
+ + +
{codeToJsx(apiExample.join(' \\\n'), 'bash')}
+
+
@@ -149,12 +135,32 @@ export function ApiExamples({api}: Props) { ) )}
+ +
- +
+          
+ 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' + )} + + )} +
); diff --git a/src/components/apiPage/styles.scss b/src/components/apiPage/styles.scss index f77c3c2ca01d0..7617585a324b8 100644 --- a/src/components/apiPage/styles.scss +++ b/src/components/apiPage/styles.scss @@ -7,6 +7,10 @@ box-shadow: rgba(0, 0, 0, 0.07) 0px 0px 0px 1px; margin-bottom: var(--paragraph-margin-bottom); } +.dark .api-block { + border: 1px solid var(--border-color); + box-shadow: none; +} .api-block-header { border-top-left-radius: 3px; @@ -31,7 +35,7 @@ } .api-block-header.response { - background: #2d2d2d; + background-color: var(--code-background); border-bottom: 1px solid #444; color: var(--white); display: flex; @@ -57,6 +61,8 @@ .response-status-btn-group { border-radius: 3px; + margin-left: auto; + margin-right: 1rem; } .response-status-btn { diff --git a/src/components/codeBlock/code-blocks.module.scss b/src/components/codeBlock/code-blocks.module.scss index aa002e3698412..dae2d1941bd5c 100644 --- a/src/components/codeBlock/code-blocks.module.scss +++ b/src/components/codeBlock/code-blocks.module.scss @@ -4,9 +4,8 @@ } pre { - background: #251f3d; - border: 1px solid #40364a; - border-radius: 0; + background-color: var(--code-background); + border-radius: 0 0 0.25rem 0.25rem; margin-top: 0; margin-bottom: 0; } diff --git a/src/components/codeTabs.tsx b/src/components/codeTabs.tsx index 7eb5d856f8dd2..7096f1fd8742f 100644 --- a/src/components/codeTabs.tsx +++ b/src/components/codeTabs.tsx @@ -136,7 +136,7 @@ const Container = styled('div')` `; const TabBar = styled('div')` - background: #251f3d; + background: var(--code-background); border-bottom: 1px solid #40364a; height: 36px; display: flex; diff --git a/src/components/docPage/type.scss b/src/components/docPage/type.scss index 387a079287593..30296e4458dd1 100644 --- a/src/components/docPage/type.scss +++ b/src/components/docPage/type.scss @@ -10,6 +10,7 @@ .prose { --heading-color: var(--darkPurple); --link-decoration: none; + --code-background: #251f3d; h1, h2, h3, @@ -174,9 +175,14 @@ } dt + dd { + margin-top: 0.25rem; margin-bottom: var(--paragraph-margin-bottom); } + dd > p { + margin-top: 0; + } + [data-onboarding-option].hidden { display: none; } diff --git a/yarn.lock b/yarn.lock index 4c2b25da6901a..60abe154a018f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6905,6 +6905,27 @@ hast-util-to-jsx-runtime@^2.0.0: unist-util-position "^5.0.0" vfile-message "^4.0.0" +hast-util-to-jsx-runtime@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz#6d11b027473e69adeaa00ca4cfb5bb68e3d282fa" + integrity sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + style-to-object "^1.0.0" + unist-util-position "^5.0.0" + vfile-message "^4.0.0" + hast-util-to-parse5@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz" @@ -10180,7 +10201,7 @@ prisma@^5.8.1: dependencies: "@prisma/engines" "5.12.1" -prismjs@^1.23.0, prismjs@^1.27.0: +prismjs@^1.23.0: version "1.29.0" resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz" integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==