diff --git a/develop-docs/sdk/getting-started/templates/saved-replies/index.mdx b/develop-docs/sdk/getting-started/templates/saved-replies/index.mdx index ad7c321c7a6dd5..7d1c51852cad3d 100644 --- a/develop-docs/sdk/getting-started/templates/saved-replies/index.mdx +++ b/develop-docs/sdk/getting-started/templates/saved-replies/index.mdx @@ -12,31 +12,46 @@ To set up saved replies go to [GitHub > Settings > Saved replies](https://github --- -## Open an issue first (behavior/refactor -> close) + Thanks for the contribution! We ask that behavioral changes and refactors have a linked issue so we can discuss the approach before a PR is opened. Could you open an issue describing the problem you're solving and your proposed approach? Closing this for now - happy to revisit once there's an issue to reference. -> -> Please also have a look at our CONTRIBUTING.md for more PR guidelines. +Please also have a look at our CONTRIBUTING.md for more PR guidelines.`}> ---- +Thanks for the contribution! We ask that behavioral changes and refactors have a linked issue so we can discuss the approach before a PR is opened. Could you open an issue describing the problem you're solving and your proposed approach? Closing this for now - happy to revisit once there's an issue to reference. -## Let's discuss the approach first (idea -> close) +Please also have a look at our CONTRIBUTING.md for more PR guidelines. -> This is an interesting idea! We'd like to align on the approach before reviewing code - could you open an issue describing the problem and your proposed solution? That way we can agree on direction first. Closing this PR for now. -> -> Please also have a look at our CONTRIBUTING.md for more PR guidelines. + ---- + + +This is an interesting idea! We'd like to align on the approach before reviewing code - could you open an issue describing the problem and your proposed solution? That way we can agree on direction first. Closing this PR for now. + +Please also have a look at our CONTRIBUTING.md for more PR guidelines. + + + + + +Thanks for the contribution! This PR needs some updates before we can review: + +- [ ] CI checks are passing +- [ ] PR description explains what and why +- [ ] Linked issue exists +- [ ] Tests are included + +We marked it as draft for now. Please update and we'll take another look. + +Please also have a look at our CONTRIBUTING.md for more PR guidelines. -## Not ready for review (request changes / mark as draft) - -> Thanks for the contribution! This PR needs some updates before we can review: -> -> - [ ] CI checks are passing -> - [ ] PR description explains what and why -> - [ ] Linked issue exists -> - [ ] Tests are included -> -> We marked it as draft for now. Please update and we'll take another look. -> -> Please also have a look at our CONTRIBUTING.md for more PR guidelines. + diff --git a/src/components/copyableCard.tsx b/src/components/copyableCard.tsx new file mode 100644 index 00000000000000..45112bd6c27e28 --- /dev/null +++ b/src/components/copyableCard.tsx @@ -0,0 +1,149 @@ +'use client'; + +import {Fragment, useEffect, useRef, useState} from 'react'; +import {createPortal} from 'react-dom'; +import {Clipboard} from 'react-feather'; + +import Chevron from 'sentry-docs/icons/Chevron'; + +interface CopyableCardProps { + children: React.ReactNode; + description: string; + title: string; +} + +export function CopyableCard({title, description, children}: CopyableCardProps) { + const [copiedItem, setCopiedItem] = useState<'title' | 'description' | null>(null); + const [isOpen, setIsOpen] = useState(false); + const [isMounted, setIsMounted] = useState(false); + const buttonRef = useRef(null); + const dropdownRef = useRef(null); + + useEffect(() => { + setIsMounted(true); + + const handleClickOutside = (event: MouseEvent) => { + if ( + buttonRef.current && + !buttonRef.current.contains(event.target as Node) && + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setIsOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + async function copyText(text: string, item: 'title' | 'description') { + try { + await navigator.clipboard.writeText(text.trim()); + setCopiedItem(item); + setIsOpen(false); + setTimeout(() => setCopiedItem(null), 1500); + } catch (error) { + // eslint-disable-next-line no-console + console.error('Failed to copy:', error); + } + } + + const getDropdownPosition = () => { + if (!buttonRef.current) { + return {top: 0, left: 0}; + } + const rect = buttonRef.current.getBoundingClientRect(); + return { + top: rect.bottom + 8, + left: rect.right - 160, + }; + }; + + const buttonClass = + 'inline-flex items-center text-nowrap h-full text-gray-700 dark:text-[var(--foreground)] bg-transparent border-none cursor-pointer transition-colors duration-150 hover:bg-gray-50 dark:hover:bg-[var(--gray-a4)] active:bg-gray-100 dark:active:bg-[var(--gray-5)] focus:bg-gray-50 dark:focus:bg-[var(--gray-a4)] outline-none'; + const dropdownItemClass = + 'w-full p-2 px-3 text-left text-sm bg-transparent border-none rounded-md transition-colors hover:bg-gray-100 dark:hover:bg-[var(--gray-a4)] font-sans text-gray-900 dark:text-[var(--foreground)] cursor-pointer'; + + const getButtonLabel = () => { + if (copiedItem === 'title') { + return 'Reply title copied!'; + } + if (copiedItem === 'description') { + return 'Reply description copied!'; + } + return 'Copy'; + }; + + return ( +
+
+

+ {title} +

+ + {isMounted && ( + +
+
+ + +
+ + +
+
+ + {isOpen && + createPortal( +
+
+ + +
+
, + document.body + )} + + )} +
+
+
{children}
+
+
+ ); +} diff --git a/src/mdxComponents.ts b/src/mdxComponents.ts index 508cc8c9b14448..29cae613ee3fb2 100644 --- a/src/mdxComponents.ts +++ b/src/mdxComponents.ts @@ -10,6 +10,7 @@ import {CommunitySupportedPlatforms} from './components/communitySupportedPlatfo import {ConfigKey} from './components/configKey'; import {ConfigValue} from './components/configValue'; import {ContentSeparator} from './components/contentSeparator'; +import {CopyableCard} from './components/copyableCard'; import {CreateGitHubAppForm} from './components/createGitHubAppForm'; import {DefinitionList} from './components/definitionList'; import {DevDocsCardGrid} from './components/devDocsCardGrid'; @@ -114,6 +115,7 @@ export function mdxComponents( OnboardingSteps, RelayMetrics, SandboxLink, + CopyableCard, SignInNote, SplitLayout, SplitSection,