diff options
| author | John Henry Rudden <[email protected]> | 2025-07-15 16:56:33 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-07-15 17:56:33 -0400 |
| commit | 7c91f668d1b8faa7ec373145ce0e2e351118dbe8 (patch) | |
| tree | 813bea27c52d0273e724c830940fa6c386b0ccb0 /packages/web/src/components | |
| parent | 1af103d29e4c1e37533a85cc6f3f8333ca16eb2a (diff) | |
| download | opencode-7c91f668d1b8faa7ec373145ce0e2e351118dbe8.tar.gz opencode-7c91f668d1b8faa7ec373145ce0e2e351118dbe8.zip | |
docs: share add copy button to messages in web interface (#902)
Co-authored-by: Jay <[email protected]>
Diffstat (limited to 'packages/web/src/components')
5 files changed, 89 insertions, 0 deletions
diff --git a/packages/web/src/components/share/content-markdown.tsx b/packages/web/src/components/share/content-markdown.tsx index a083872e7..8efa5f9a9 100644 --- a/packages/web/src/components/share/content-markdown.tsx +++ b/packages/web/src/components/share/content-markdown.tsx @@ -2,6 +2,7 @@ import { marked } from "marked" import { codeToHtml } from "shiki" import markedShiki from "marked-shiki" import { createOverflow } from "./common" +import { CopyButton } from "./copy-button" import { createResource, createSignal } from "solid-js" import { transformerNotationDiff } from "@shikijs/transformers" import style from "./content-markdown.module.css" @@ -41,6 +42,7 @@ export function ContentMarkdown(props: Props) { class={style.root} data-highlight={props.highlight === true ? true : undefined} data-expanded={expanded() || props.expand === true ? true : undefined} + style={{ position: "relative" }} > <div data-slot="markdown" ref={overflow.ref} innerHTML={html()} /> @@ -54,6 +56,7 @@ export function ContentMarkdown(props: Props) { {expanded() ? "Show less" : "Show more"} </button> )} + <CopyButton text={props.text} /> </div> ) } diff --git a/packages/web/src/components/share/content-text.module.css b/packages/web/src/components/share/content-text.module.css index 203f0d06d..a3842275c 100644 --- a/packages/web/src/components/share/content-text.module.css +++ b/packages/web/src/components/share/content-text.module.css @@ -2,6 +2,7 @@ color: var(--sl-color-text); background-color: var(--sl-color-bg-surface); padding: 0.5rem calc(0.5rem + 3px); + padding-right: calc(1rem + 18px); border-radius: 0.25rem; display: flex; flex-direction: column; diff --git a/packages/web/src/components/share/content-text.tsx b/packages/web/src/components/share/content-text.tsx index c52e0dfcc..c898bd8d7 100644 --- a/packages/web/src/components/share/content-text.tsx +++ b/packages/web/src/components/share/content-text.tsx @@ -1,6 +1,7 @@ import style from "./content-text.module.css" import { createSignal } from "solid-js" import { createOverflow } from "./common" +import { CopyButton } from "./copy-button" interface Props { text: string @@ -16,6 +17,7 @@ export function ContentText(props: Props) { class={style.root} data-expanded={expanded() || props.expand === true ? true : undefined} data-compact={props.compact === true ? true : undefined} + style={{ position: "relative" }} > <pre data-slot="text" ref={overflow.ref}> {props.text} @@ -30,6 +32,7 @@ export function ContentText(props: Props) { {expanded() ? "Show less" : "Show more"} </button> )} + <CopyButton text={props.text} /> </div> ) } diff --git a/packages/web/src/components/share/copy-button.module.css b/packages/web/src/components/share/copy-button.module.css new file mode 100644 index 000000000..7494c2b81 --- /dev/null +++ b/packages/web/src/components/share/copy-button.module.css @@ -0,0 +1,47 @@ +.copyButtonWrapper { + position: absolute; + top: 0.5rem; + right: 0.5rem; + opacity: 0; + visibility: hidden; + transition: opacity 0.15s ease; +} + +.copyButton { + width: 18px; + cursor: pointer; + background: none; + border: none; + padding: 0; + color: var(--sl-color-text-secondary); + + svg { + display: block; + width: 16px; + height: 16px; + } + + &[data-copied="true"] { + color: var(--sl-color-green-high); + } +} + +/* Show copy button when parent is hovered */ +*:hover > .copyButtonWrapper { + opacity: 0.65; + visibility: visible; +} + +.copyTooltip { + position: absolute; + top: 50%; + left: calc(100% + 12px); + transform: translate(0, -50%); + padding: 0.375em 0.5em; + background: var(--sl-color-white); + color: var(--sl-color-text-invert); + font-size: 0.6875rem; + border-radius: 7px; + white-space: nowrap; + z-index: 11; +} diff --git a/packages/web/src/components/share/copy-button.tsx b/packages/web/src/components/share/copy-button.tsx new file mode 100644 index 000000000..a4434b1c5 --- /dev/null +++ b/packages/web/src/components/share/copy-button.tsx @@ -0,0 +1,35 @@ +import { createSignal } from "solid-js" +import { IconClipboard, IconCheckCircle } from "../icons" +import styles from "./copy-button.module.css" + +interface CopyButtonProps { + text: string +} + +export function CopyButton(props: CopyButtonProps) { + const [copied, setCopied] = createSignal(false) + + function handleCopyClick() { + if (props.text) { + navigator.clipboard.writeText(props.text).catch((err) => console.error("Copy failed", err)) + + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + } + + return ( + <div class={styles.copyButtonWrapper}> + <button + type="button" + class={styles.copyButton} + onClick={handleCopyClick} + data-copied={copied() ? true : undefined} + title="Copy content" + > + {copied() ? <IconCheckCircle width={16} height={16} /> : <IconClipboard width={16} height={16} />} + </button> + {copied() && <span class={styles.copyTooltip}>Copied!</span>} + </div> + ) +} |
