diff --git a/documentation/src/theme/DocItem/Layout/CopyPageButton.module.css b/documentation/src/theme/DocItem/Layout/CopyPageButton.module.css new file mode 100644 index 0000000000..801374de8c --- /dev/null +++ b/documentation/src/theme/DocItem/Layout/CopyPageButton.module.css @@ -0,0 +1,98 @@ +/* Copy Page Button Styles */ +.copyButton { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + background-color: var(--ifm-color-emphasis-200); + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: 6px; + color: var(--ifm-color-content); + font-size: 14px; + cursor: pointer; + transition: all 0.2s ease; + font-family: var(--ifm-font-family-base); + line-height: 1; +} + +.copyButton:hover { + background-color: var(--ifm-color-emphasis-300); +} + +.copyButton:focus { + outline: 2px solid var(--ifm-color-primary); + outline-offset: 2px; +} + +.copyButton:active { + transform: translateY(1px); +} + +.copyButton:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.copyButton:disabled:hover { + background-color: var(--ifm-color-emphasis-200); + transform: none; +} + +.copyIcon { + width: 16px; + height: 16px; + flex-shrink: 0; +} + +/* Header layout styles */ +.headerWithButton { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1rem; +} + +.headerTitle { + margin: 0; + flex: 1; +} + +.buttonContainer { + display: flex; + justify-content: flex-end; + margin-bottom: 1rem; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .copyButton { + font-size: 12px; + padding: 4px 8px; + gap: 4px; + } + + .copyIcon { + width: 14px; + height: 14px; + } + + .headerWithButton { + flex-direction: column; + align-items: stretch; + gap: 0.5rem; + } + + .buttonContainer { + margin-bottom: 0.5rem; + } +} + +/* Dark mode adjustments */ +[data-theme='dark'] .copyButton { + background-color: var(--ifm-color-emphasis-200); + border-color: var(--ifm-color-emphasis-300); +} + +[data-theme='dark'] .copyButton:hover { + background-color: var(--ifm-color-emphasis-300); +} diff --git a/documentation/src/theme/DocItem/Layout/index.tsx b/documentation/src/theme/DocItem/Layout/index.tsx index 91749457d9..fe5a19cd5d 100644 --- a/documentation/src/theme/DocItem/Layout/index.tsx +++ b/documentation/src/theme/DocItem/Layout/index.tsx @@ -15,14 +15,30 @@ import DocBreadcrumbs from '@theme/DocBreadcrumbs'; import ContentVisibility from '@theme/ContentVisibility'; import Heading from '@theme/Heading'; import MDXContent from '@theme/MDXContent'; +import styles from './CopyPageButton.module.css'; type Props = WrapperProps; +// Constants for better maintainability +const COPY_FEEDBACK_DURATION = 2000; + // Component for the Copy Page button function CopyPageButton(): ReactNode { const [copied, setCopied] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); const handleCopy = async () => { + // Check if clipboard API is available + if (!navigator.clipboard) { + setError('Clipboard not supported in this browser'); + setTimeout(() => setError(null), COPY_FEEDBACK_DURATION); + return; + } + + setIsLoading(true); + setError(null); + try { // For now, just copy a placeholder text // In Phase 2, we'll copy the actual markdown content @@ -31,54 +47,51 @@ function CopyPageButton(): ReactNode { await navigator.clipboard.writeText(textToCopy); setCopied(true); - // Reset the "Copied" state after 2 seconds + // Reset the "Copied" state after timeout setTimeout(() => { setCopied(false); - }, 2000); + }, COPY_FEEDBACK_DURATION); } catch (err) { + setError('Failed to copy. Please try again.'); + setTimeout(() => setError(null), COPY_FEEDBACK_DURATION); console.error('Failed to copy text: ', err); + } finally { + setIsLoading(false); } }; + // Display error message if there's an error + if (error) { + return ( +
+ {error} +
+ ); + } + return ( ); } @@ -86,17 +99,6 @@ function CopyPageButton(): ReactNode { // Hook to determine if we should show the copy button function useShouldShowCopyButton(): boolean { const {metadata} = useDoc(); - - // Debug: Log metadata to understand the structure - React.useEffect(() => { - console.log('DocItem Metadata for Copy Button:', { - source: metadata?.source, - permalink: metadata?.permalink, - isGeneratedIndex: metadata?.isGeneratedIndex, - type: metadata?.type, - frontMatter: metadata?.frontMatter, - }); - }, [metadata]); // Show copy button only on actual content pages (not category/index pages) // A content page should have a source file (.md file) @@ -108,17 +110,7 @@ function useShouldShowCopyButton(): boolean { // Don't show on category pages (they typically have /category/ in the permalink) const isNotCategoryPage = !metadata?.permalink?.includes('/category/'); - const shouldShow = hasSource && isNotGeneratedIndex && isNotCategoryPage; - - console.log('Copy button decision:', { - hasSource, - isNotGeneratedIndex, - isNotCategoryPage, - shouldShow, - permalink: metadata?.permalink - }); - - return shouldShow; + return hasSource && isNotGeneratedIndex && isNotCategoryPage; } /** @@ -157,22 +149,13 @@ function CustomDocItemContent({children}: {children: ReactNode}): ReactNode { return (
{syntheticTitle && ( -
- {syntheticTitle} +
+ {syntheticTitle} {shouldShowCopyButton && }
)} {!syntheticTitle && shouldShowCopyButton && ( -
+
)}