diff --git a/docs/platforms/javascript/common/index.mdx b/docs/platforms/javascript/common/index.mdx index e958ea1865f092..f4b744eea489df 100644 --- a/docs/platforms/javascript/common/index.mdx +++ b/docs/platforms/javascript/common/index.mdx @@ -2,6 +2,8 @@ + + Check out the other SDKs we support in the left-hand dropdown. diff --git a/docs/platforms/javascript/common/logs/index.mdx b/docs/platforms/javascript/common/logs/index.mdx index 4188d931776ee4..1fec59e2743b66 100644 --- a/docs/platforms/javascript/common/logs/index.mdx +++ b/docs/platforms/javascript/common/logs/index.mdx @@ -10,6 +10,8 @@ notSupported: - javascript.capacitor --- + + With Sentry Structured Logs, you can send text based log information from your applications to Sentry. Once in Sentry, these logs can be viewed alongside relevant errors, searched by text-string, or searched using their individual attributes. diff --git a/docs/platforms/javascript/guides/nextjs/index.mdx b/docs/platforms/javascript/guides/nextjs/index.mdx index 269453e27751ca..c07e37dfc4e72a 100644 --- a/docs/platforms/javascript/guides/nextjs/index.mdx +++ b/docs/platforms/javascript/guides/nextjs/index.mdx @@ -9,6 +9,8 @@ categories: - server-node --- + + ## Step 1: Install diff --git a/docs/platforms/javascript/guides/node/index.mdx b/docs/platforms/javascript/guides/node/index.mdx index 5362436799bdd1..dd015a665b96dc 100644 --- a/docs/platforms/javascript/guides/node/index.mdx +++ b/docs/platforms/javascript/guides/node/index.mdx @@ -11,4 +11,6 @@ categories: + + diff --git a/docs/platforms/javascript/guides/react/index.mdx b/docs/platforms/javascript/guides/react/index.mdx index afef4a7ea5d168..e422ea03a34a2f 100644 --- a/docs/platforms/javascript/guides/react/index.mdx +++ b/docs/platforms/javascript/guides/react/index.mdx @@ -7,6 +7,8 @@ categories: - browser --- + + ## Step 1: Install diff --git a/platform-includes/llm-rules-logs/javascript.nextjs.mdx b/platform-includes/llm-rules-logs/javascript.nextjs.mdx new file mode 100644 index 00000000000000..ab95d391cf70fa --- /dev/null +++ b/platform-includes/llm-rules-logs/javascript.nextjs.mdx @@ -0,0 +1,68 @@ + + + +````markdown {filename:rules.md} + +# Logs + +- Where logs are used, ensure Sentry is imported using `import * as Sentry from "@sentry/nextjs"` +- Reference the logger using `const { logger } = Sentry` +- Sentry initialization needs to be updated to include the `logger` integration. +- Sentry offers a `consoleLoggingIntegration` that can be used to log specific console error types automatically without instrumenting the individual logger calls + +## Configuration + +The Sentry initialization needs to be updated to enable the logs feature. + +### Baseline + +```javascript +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + + _experiments: { + enableLogs: true, + }, + +}); +``` + +### Logger Integration + +```javascript +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + integrations: [ + // send console.log, console.error, and console.warn calls as logs to Sentry + Sentry.consoleLoggingIntegration({ levels: ["log", "error", "warn"] }), + ], +}); +``` + +## Logger Examples + +```javascript +import * as Sentry from "@sentry/nextjs"; + +const { logger } = Sentry; + +logger.trace("Starting database connection", { database: "users" }); +logger.debug("Cache miss for user", { userId: 123 }); +logger.info("Updated profile", { profileId: 345 }); +logger.warn("Rate limit reached for endpoint", { + endpoint: "/api/results/", + isEnterprise: false, +}); +logger.error("Failed to process payment", { + orderId: "order_123", + amount: 99.99, +}); +logger.fatal("Database connection pool exhausted", { + database: "users", + activeConnections: 100, +}); +``` +```` + \ No newline at end of file diff --git a/platform-includes/llm-rules-logs/javascript.node.mdx b/platform-includes/llm-rules-logs/javascript.node.mdx new file mode 100644 index 00000000000000..460452d40179a4 --- /dev/null +++ b/platform-includes/llm-rules-logs/javascript.node.mdx @@ -0,0 +1,68 @@ + + + +````markdown {filename:rules.md} + +# Logs + +- Where logs are used, ensure Sentry is imported using `import * as Sentry from "@sentry/node"` +- Reference the logger using `const { logger } = Sentry` +- Sentry initialization needs to be updated to include the `logger` integration. +- Sentry offers a `consoleLoggingIntegration` that can be used to log specific console error types automatically without instrumenting the individual logger calls + +## Configuration + +The Sentry initialization needs to be updated to enable the logs feature. + +### Baseline + +```javascript +import * as Sentry from "@sentry/node"; + +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + + _experiments: { + enableLogs: true, + }, + +}); +``` + +### Logger Integration + +```javascript +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + integrations: [ + // send console.log, console.error, and console.warn calls as logs to Sentry + Sentry.consoleLoggingIntegration({ levels: ["log", "error", "warn"] }), + ], +}); +``` + +## Logger Examples + +```javascript +import * as Sentry from "@sentry/node"; + +const { logger } = Sentry; + +logger.trace("Starting database connection", { database: "users" }); +logger.debug("Cache miss for user", { userId: 123 }); +logger.info("Updated profile", { profileId: 345 }); +logger.warn("Rate limit reached for endpoint", { + endpoint: "/api/results/", + isEnterprise: false, +}); +logger.error("Failed to process payment", { + orderId: "order_123", + amount: 99.99, +}); +logger.fatal("Database connection pool exhausted", { + database: "users", + activeConnections: 100, +}); +``` +```` + \ No newline at end of file diff --git a/platform-includes/llm-rules-logs/javascript.react.mdx b/platform-includes/llm-rules-logs/javascript.react.mdx new file mode 100644 index 00000000000000..71987c14fe12fd --- /dev/null +++ b/platform-includes/llm-rules-logs/javascript.react.mdx @@ -0,0 +1,66 @@ + + + +````markdown {filename:rules.md} + +# Logs + +- Where logs are used, ensure Sentry is imported using `import * as Sentry from "@sentry/react"` +- Reference the logger using `const { logger } = Sentry` +- Sentry initialization needs to be updated to include the `logger` integration. +- Sentry offers a `consoleLoggingIntegration` that can be used to log specific console error types automatically without instrumenting the individual logger calls + +## Configuration + +The Sentry initialization needs to be updated to enable the logs feature. + +### Baseline + +```javascript +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + + _experiments: { + enableLogs: true, + }, + +}); +``` + +### Logger Integration + +```javascript +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + integrations: [ + // send console.log, console.error, and console.warn calls as logs to Sentry + Sentry.consoleLoggingIntegration({ levels: ["log", "error", "warn"] }), + ], +}); +``` + +## Logger Examples + +```javascript +import * as Sentry from "@sentry/react"; + +const { logger } = Sentry; + +logger.trace("Starting database connection", { database: "users" }); +logger.debug("Cache miss for user", { userId: 123 }); +logger.info("Updated profile", { profileId: 345 }); +logger.warn("Rate limit reached for endpoint", { + endpoint: "/api/results/", + isEnterprise: false, +}); +logger.error("Failed to process payment", { + orderId: "order_123", + amount: 99.99, +}); +logger.fatal("Database connection pool exhausted", { + database: "users", + activeConnections: 100, +}); +``` +```` + \ No newline at end of file diff --git a/platform-includes/llm-rules-platform/_default.mdx b/platform-includes/llm-rules-platform/_default.mdx new file mode 100644 index 00000000000000..57d240d6f712d4 --- /dev/null +++ b/platform-includes/llm-rules-platform/_default.mdx @@ -0,0 +1,144 @@ + + +Sentry provides a set of rules you can use to help your LLM use Sentry correctly. Copy this file and add it to your projects rules configuration. + +````markdown {filename:rules.md} + +These examples should be used as guidance when configuring Sentry functionlaity within a project. + +# Error / Exception Tracking + +- Use `Sentry.captureException(error)` to capture an exception and log the error in Sentry. +- Use this in try catch blocks or areas where exceptions are expected + +# Logs + +- Where logs are used, ensure Sentry is imported using `import * as Sentry from "@sentry/browser"` +- Reference the logger using `const { logger } = Sentry` +- Sentry initialization needs to be updated to include the `logger` integration. +- Sentry offers a `consoleLoggingIntegration` that can be used to log specific console error types automatically without instrumenting the individual logger calls + +## Configuration + +### Baseline + +```javascript +import * as Sentry from "@sentry/browser"; + +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + + _experiments: { + enableLogs: true, + }, + +}); +``` + +### Logger Integration + +```javascript +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + integrations: [ + // send console.log, console.error, and console.warn calls as logs to Sentry + Sentry.consoleLoggingIntegration({ levels: ["log", "error", "warn"] }), + ], +}); +``` + +## Logger Examples + +```javascript +import * as Sentry from "@sentry/browser"; + +const { logger } = Sentry; + +logger.trace("Starting database connection", { database: "users" }); +logger.debug("Cache miss for user", { userId: 123 }); +logger.info("Updated profile", { profileId: 345 }); +logger.warn("Rate limit reached for endpoint", { + endpoint: "/api/results/", + isEnterprise: false, +}); +logger.error("Failed to process payment", { + orderId: "order_123", + amount: 99.99, +}); +logger.fatal("Database connection pool exhausted", { + database: "users", + activeConnections: 100, +}); +``` + +# Tracing Examples + +- Spans should be created for meaningful actions within an applications like button clicks, API calls, and function calls +- Use the `Sentry.startSpan` function to create a span +- Child spans can exist within a parent span + +## Custom Span instrumentation in component actions + +- Name custom spans with meaningful names and operations. +- Attach attributes based on relevant information and metrics from the request + +```javascript +function TestComponent() { + const handleTestButtonClick = () => { + // Create a transaction/span to measure performance + Sentry.startSpan({ + op: "ui.click", + name: "Test Button Click" + }, async (span) => { + + const value = "some config" + const metric = "some metric" + + // Metrics can be added to the span + span.setAttribute("config", value) + span.setAttribute("metric", metric) + + doSomething(); + }); + }; + + return ( + + ); +} +``` + +## Custom span instrumentation in API calls + +- Name custom spans with meaningful names and operations. +- Attach attributes based on relevant information and metrics from the request + +```javascript +async function fetchUserData(userId) { + return Sentry.startSpan( + { + op: "http.client", + name: `GET /api/users/${userId}`, + }, + async () => { + try { + const response = await fetch(`/api/users/${userId}`); + const data = await response.json(); + return data; + } catch (error) { + // Capture error with the current span + Sentry.captureException(error); + throw error; + } + } + ); +} +``` +```` + + \ No newline at end of file diff --git a/platform-includes/llm-rules-platform/javascript.nextjs.mdx b/platform-includes/llm-rules-platform/javascript.nextjs.mdx new file mode 100644 index 00000000000000..e535d0819efc12 --- /dev/null +++ b/platform-includes/llm-rules-platform/javascript.nextjs.mdx @@ -0,0 +1,142 @@ + + +Sentry provides a set of rules you can use to help your LLM use Sentry correctly. Copy this file and add it to your projects rules configuration. + +````markdown {filename:rules.md} + +These examples should be used as guidance when configuring Sentry functionality within a project. + +# Exception Catching + +- Use `Sentry.captureException(error)` to capture an exception and log the error in Sentry. +- Use this in try catch blocks or areas where exceptions are expected + +# Logs + +- Where logs are used, ensure Sentry is imported using `import * as Sentry from "@sentry/nextjs"` +- Reference the logger using `const { logger } = Sentry` +- Sentry initialization needs to be updated to include the `logger` integration. +- Sentry offers a consoleLoggingIntegration that can be used to log specific console error types automatically without instrumenting the individual logger calls + +## Configuration + +- In NextJS the client side Sentry initialization is in `instrumentation-client.ts`, the server initialization is in `sentry.edge.config.ts` and the edge initialization is in `sentry.server.config.ts` +- Initialization does not need to be repeated in other files, it only needs to happen the files mentioned above. You should use `import * as Sentry from "@sentry/nextjs"` to reference Sentry functionality + +### Baseline + +```javascript +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + + _experiments: { + enableLogs: true, + }, + +}); +``` + +### Logger Integration +```javascript +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + integrations: [ + // send console.log, console.error, and console.warn calls as logs to Sentry + Sentry.consoleLoggingIntegration({ levels: ["log", "error", "warn"] }), + ], +}); +``` + +## Logger Examples + +```javascript +logger.trace("Starting database connection", { database: "users" }); +logger.debug("Cache miss for user", { userId: 123 }); +logger.info("Updated profile", { profileId: 345 }); +logger.warn("Rate limit reached for endpoint", { + endpoint: "/api/results/", + isEnterprise: false, +}); +logger.error("Failed to process payment", { + orderId: "order_123", + amount: 99.99, +}); +logger.fatal("Database connection pool exhausted", { + database: "users", + activeConnections: 100, +}); +``` + +# Tracing Examples + +- Spans should be created for meaningful actions within an applications like button clicks, API calls, and function calls +- Use the `Sentry.startSpan` function to create a span +- Child spans can exist within a parent span + +## Custom Span instrumentation in component actions + +- The `name` and `op` properties should be meaninful for the activities in the call. +- Attach attributes based on relevant information and metrics from the request + +```javascript +function TestComponent() { + const handleTestButtonClick = () => { + // Create a transaction/span to measure performance + Sentry.startSpan({ + op: "ui.click", + name: "Test Button Click" + }, async (span) => { + + const value = "some config" + const metric = "some metric" + + // Metrics can be added to the span + span.setAttribute("config", value) + span.setAttribute("metric", metric) + + doSomething(); + }); + }; + + return ( + + ); +} +``` + +## Custom span instrumentation in API calls + +- The `name` and `op` properties should be meaninful for the activities in the call. +- Attach attributes based on relevant information and metrics from the request + +```javascript +async function fetchUserData(userId) { + return Sentry.startSpan( + { + op: "http.client", + name: `GET /api/users/${userId}`, + }, + async () => { + try { + const response = await fetch(`/api/users/${userId}`); + const data = await response.json(); + return data; + } catch (error) { + // Capture error with the current span + Sentry.captureException(error); + throw error; + } + } + ); +} +``` +```` + + \ No newline at end of file diff --git a/platform-includes/llm-rules-platform/javascript.node.mdx b/platform-includes/llm-rules-platform/javascript.node.mdx new file mode 100644 index 00000000000000..f48305b14a48bb --- /dev/null +++ b/platform-includes/llm-rules-platform/javascript.node.mdx @@ -0,0 +1,137 @@ + + +Sentry provides a set of rules you can use to help your LLM use Sentry correctly. Copy this file and add it to your projects rules configuration. + +````markdown {filename:rules.md} + +These examples should be used as guidance when configuring Sentry functionlaity within a project. + +# Error / Exception Tracking + +- Use `Sentry.captureException(error)` to capture an exception and log the error in Sentry. +- Use this in try catch blocks or areas where exceptions are expected + +# Logs + +- Where logs are used, ensure they are imported using `import * as Sentry from "@sentry/node"` +- Reference the logger using `const { logger } = Sentry` +- Sentry initialization needs to be updated to include the `logger` integration. +- Sentry offers a consoleLoggingIntegration that can be used to log specific console error types automatically without instrumenting the individual logger calls + +## Configuration + +- In Node.js the Sentry initialization is typically in `instrumentation.ts` + +### Baseline + +```javascript +import * as Sentry from "@sentry/node"; + +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + + _experiments: { + enableLogs: true, + }, + +}); +``` + +### Logger Integration + +```javascript +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + integrations: [ + // send console.log, console.error, and console.warn calls as logs to Sentry + Sentry.consoleLoggingIntegration({ levels: ["log", "error", "warn"] }), + ], +}); +``` + +## Logger Examples + +```javascript +logger.trace("Starting database connection", { database: "users" }); +logger.debug("Cache miss for user", { userId: 123 }); +logger.info("Updated profile", { profileId: 345 }); +logger.warn("Rate limit reached for endpoint", { + endpoint: "/api/results/", + isEnterprise: false, +}); +logger.error("Failed to process payment", { + orderId: "order_123", + amount: 99.99, +}); +logger.fatal("Database connection pool exhausted", { + database: "users", + activeConnections: 100, +}); +``` + +# Tracing Examples + +- Spans should be created for meaningful actions within an applications like button clicks, API calls, and function calls +- Ensure you are creating custom spans with meaningful names and operations +- Use the `Sentry.startSpan` function to create a span +- Child spans can exist within a parent span + +## Custom Span instrumentation in component actions + +```javascript +function TestComponent() { + const handleTestButtonClick = () => { + // Create a transaction/span to measure performance + Sentry.startSpan({ + op: "ui.click", + name: "Test Button Click" + }, async (span) => { + + const value = "some config" + const metric = "some metric" + + // Metrics can be added to the span + span.setAttribute("config", value) + span.setAttribute("metric", metric) + + doSomething(); + }); + }; + + return ( + + ); +} +``` + +## Custom span instrumentation in API calls + +```javascript +async function fetchUserData(userId) { + return Sentry.startSpan( + { + op: "http.client", + name: `GET /api/users/${userId}`, + }, + async () => { + try { + const response = await fetch(`/api/users/${userId}`); + const data = await response.json(); + return data; + } catch (error) { + // Capture error with the current span + Sentry.captureException(error); + throw error; + } + } + ); +} +``` +```` + + \ No newline at end of file diff --git a/platform-includes/llm-rules-platform/javascript.react.mdx b/platform-includes/llm-rules-platform/javascript.react.mdx new file mode 100644 index 00000000000000..d0eb6206286995 --- /dev/null +++ b/platform-includes/llm-rules-platform/javascript.react.mdx @@ -0,0 +1,135 @@ + + +Sentry provides a set of rules you can use to help your LLM use Sentry correctly. Copy this file and add it to your projects rules configuration. + +````markdown {filename:rules.md} + +These examples should be used as guidance when configuring Sentry functionlaity within a project. + +# Error / Exception Tracking + +- Use `Sentry.captureException(error)` to capture an exception and log the error in Sentry. +- Use this in try catch blocks or areas where exceptions are expected + +# Logs + +- Where logs are used, ensure Sentry is imported using `import * as Sentry from "@sentry/react"` +- Reference the logger using `const { logger } = Sentry` +- Sentry initialization needs to be updated to include the `logger` integration. +- Sentry offers a consoleLoggingIntegration that can be used to log specific console error types automatically without instrumenting the individual logger calls + +## Configuration + +### Baseline + +```javascript +import * as Sentry from "@sentry/react"; + +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + + _experiments: { + enableLogs: true, + }, + +}); +``` + +### Logger Integration + +```javascript +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + integrations: [ + // send console.log, console.error, and console.warn calls as logs to Sentry + Sentry.consoleLoggingIntegration({ levels: ["log", "error", "warn"] }), + ], +}); +``` + +## Logger Examples + +```javascript +logger.trace("Starting database connection", { database: "users" }); +logger.debug("Cache miss for user", { userId: 123 }); +logger.info("Updated profile", { profileId: 345 }); +logger.warn("Rate limit reached for endpoint", { + endpoint: "/api/results/", + isEnterprise: false, +}); +logger.error("Failed to process payment", { + orderId: "order_123", + amount: 99.99, +}); +logger.fatal("Database connection pool exhausted", { + database: "users", + activeConnections: 100, +}); +``` + +# Tracing Examples + +- Spans should be created for meaningful actions within an applications like button clicks, API calls, and function calls +- Ensure you are creating custom spans with meaningful names and operations +- Use the `Sentry.startSpan` function to create a span +- Child spans can exist within a parent span + +## Custom Span instrumentation in component actions + +```javascript +function TestComponent() { + const handleTestButtonClick = () => { + // Create a transaction/span to measure performance + Sentry.startSpan({ + op: "ui.click", + name: "Test Button Click" + }, async (span) => { + + const value = "some config" + const metric = "some metric" + + // Metrics can be added to the span + span.setAttribute("config", value) + span.setAttribute("metric", metric) + + doSomething(); + }); + }; + + return ( + + ); +} +``` + +## Custom span instrumentation in API calls + +```javascript +async function fetchUserData(userId) { + return Sentry.startSpan( + { + op: "http.client", + name: `GET /api/users/${userId}`, + }, + async () => { + try { + const response = await fetch(`/api/users/${userId}`); + const data = await response.json(); + return data; + } catch (error) { + // Capture error with the current span + Sentry.captureException(error); + throw error; + } + } + ); +} +``` +```` + + \ No newline at end of file diff --git a/src/components/expandable/index.tsx b/src/components/expandable/index.tsx index 34941445336dc0..ab65d8166d7337 100644 --- a/src/components/expandable/index.tsx +++ b/src/components/expandable/index.tsx @@ -1,7 +1,8 @@ 'use client'; -import {ReactNode, useEffect, useState} from 'react'; +import {ReactNode, useEffect, useRef, useState} from 'react'; import {ChevronDownIcon, ChevronRightIcon} from '@radix-ui/react-icons'; +import * as Sentry from '@sentry/nextjs'; // explicitly not usig CSS modules here // because there's some prerendered content that depends on these exact class names @@ -11,6 +12,7 @@ import styles from './style.module.scss'; type Props = { children: ReactNode; title: string; + copy?: boolean; /** If defined, the expandable will be grouped with other expandables that have the same group. */ group?: string; level?: 'info' | 'warning' | 'success'; @@ -24,10 +26,20 @@ function slugify(str: string) { .replace(/[^a-z0-9-]/g, ''); } -export function Expandable({title, level = 'info', children, permalink, group}: Props) { +export function Expandable({ + title, + level = 'info', + children, + permalink, + group, + copy, +}: Props) { const id = permalink ? slugify(title) : undefined; const [isExpanded, setIsExpanded] = useState(false); + const [copied, setCopied] = useState(false); + const [showCopyButton, setShowCopyButton] = useState(false); + const contentRef = useRef(null); // Ensure we scroll to the element if the URL hash matches useEffect(() => { @@ -55,6 +67,56 @@ export function Expandable({title, level = 'info', children, permalink, group}: }; }, [id]); + useEffect(() => { + if (copy) { + setShowCopyButton(true); + } + }, [copy]); + + async function copyContentOnClick(event: React.MouseEvent) { + event.stopPropagation(); // Prevent the details element from toggling + event.preventDefault(); // Prevent default summary click behavior + + if (contentRef.current === null) { + return; + } + + // Attempt to get text from markdown code blocks if they exist + const codeBlocks = contentRef.current.querySelectorAll('code'); + let contentToCopy = ''; + + if (codeBlocks.length > 0) { + // If there are code blocks, concatenate their text content + codeBlocks.forEach(block => { + // Exclude code elements within other code elements (e.g. inline code in a block) + if (!block.closest('code')?.parentElement?.closest('code')) { + contentToCopy += (block.textContent || '') + '\n'; + } + }); + contentToCopy = contentToCopy.trim(); + } + + // Fallback to the whole content if no code blocks or if they are empty + if (!contentToCopy && contentRef.current.textContent) { + contentToCopy = contentRef.current.textContent.trim(); + } + + if (!contentToCopy) { + // if there is no content to copy (e.g. only images), do nothing. + return; + } + + try { + setCopied(false); + await navigator.clipboard.writeText(contentToCopy); + setCopied(true); + setTimeout(() => setCopied(false), 1200); + } catch (error) { + Sentry.captureException(error); + setCopied(false); + } + } + function toggleIsExpanded(event: React.MouseEvent) { const newVal = event.currentTarget.open; @@ -79,15 +141,30 @@ export function Expandable({title, level = 'info', children, permalink, group}: id={id} > - - -
{title}
+
+ + +
{title}
+
+ {copy && ( + + )}
-
+
{children}
diff --git a/src/components/expandable/style.module.scss b/src/components/expandable/style.module.scss index 54bd3ef6632c35..d9d6ffd5df8772 100644 --- a/src/components/expandable/style.module.scss +++ b/src/components/expandable/style.module.scss @@ -3,6 +3,38 @@ cursor: pointer; gap: 0.7rem; margin-bottom: 0.3rem; + justify-content: space-between; + align-items: center; +} + +.expandable-title-container { + display: flex; + align-items: center; + gap: 0.7rem; +} + +.copy-button { + background-color: var(--code-block-background); + color: var(--text-default); + border: 1px solid var(--gray-200); + padding: 0.25rem 0.75rem; + border-radius: var(--border-radius-medium); + cursor: pointer; + font-size: var(--font-size-small); + font-weight: 500; + line-height: 1.2; + transition: background-color 150ms linear, border-color 150ms linear, color 150ms linear; + + &:hover { + background-color: var(--gray-100); + border-color: var(--gray-300); + color: #333333; + } + + &:active { + background-color: var(--gray-200); + color: #333333; + } } .expandable-body {