Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 65 additions & 8 deletions src/pages/ReadmeMaker/PreviewPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function calculateQuality({ formData, sectionState, selectedTechs, screenshots }

export default function PreviewPanel({ currentMd, formData, sectionState, selectedTechs, screenshots }) {
const toast = useToast();
const [copied, setCopied] = useState(false);
const [tab, setTabState] = useState('rendered');
const [zoom, setZoom] = useState(() => {
try {
Expand Down Expand Up @@ -102,13 +103,22 @@ export default function PreviewPanel({ currentMd, formData, sectionState, select
try {
await navigator.clipboard.writeText(currentMd);
toast('✓ Copied to clipboard!');
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {
const ta = document.createElement('textarea');
ta.value = currentMd;
ta.style.cssText = 'position:absolute;left:-9999px';
document.body.appendChild(ta);
ta.select();
try { document.execCommand('copy'); toast('✓ Copied!'); } catch { toast('Copy failed'); }
try {
document.execCommand('copy');
toast('✓ Copied!');
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {
toast('Copy failed');
}
document.body.removeChild(ta);
}
}, [currentMd, toast]);
Expand Down Expand Up @@ -158,11 +168,22 @@ export default function PreviewPanel({ currentMd, formData, sectionState, select
<button className="pbtn" onClick={zoomIn} disabled={zoom >= ZOOM_LEVELS[ZOOM_LEVELS.length - 1]} title="Zoom in (Ctrl +)">+</button>
<button className="pbtn" onClick={() => setZoom(1)} title="Reset zoom (Ctrl 0)">Reset</button>
</div>
<button className="pbtn green" onClick={copyMarkdown}>
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="9" y="9" width="13" height="13" rx="2" /><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
</svg>
Copy Markdown
<button className={`pbtn green${copied ? ' copied' : ''}`} onClick={copyMarkdown}>
{copied ? (
<>
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polyline points="20 6 9 17 4 12" />
</svg>
Copied!
</>
) : (
<>
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="9" y="9" width="13" height="13" rx="2" /><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
</svg>
Copy Markdown
</>
)}
</button>
<button className="pbtn" onClick={downloadMd}>
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
Expand Down Expand Up @@ -256,11 +277,47 @@ export default function PreviewPanel({ currentMd, formData, sectionState, select
</div>
</div>
) : tab === 'rendered' ? (
<div className="preview-zoom-wrap">
<div className="preview-zoom-wrap" style={{ position: 'relative' }}>
<button className={`copy-floating-btn${copied ? ' copied' : ''}`} onClick={copyMarkdown} title="Copy README Markdown">
{copied ? (
<>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="20 6 9 17 4 12" />
</svg>
<span>Copied!</span>
</>
) : (
<>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" />
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
</svg>
<span>Copy</span>
</>
)}
</button>
<div className="gh-preview" dangerouslySetInnerHTML={{ __html: md2html(currentMd) }} />
</div>
) : (
<div className="preview-zoom-wrap">
<div className="preview-zoom-wrap" style={{ position: 'relative' }}>
<button className={`copy-floating-btn${copied ? ' copied' : ''}`} onClick={copyMarkdown} title="Copy README Markdown">
{copied ? (
<>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="20 6 9 17 4 12" />
</svg>
<span>Copied!</span>
</>
) : (
<>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" />
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
</svg>
<span>Copy</span>
</>
)}
</button>
<div className="raw-view">{currentMd}</div>
</div>
)}
Expand Down
11 changes: 9 additions & 2 deletions src/pages/ReadmeMaker/ReadmeMaker.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useState } from 'react';

export default function ReadmeMaker() {
const toast = useToast();
const [copied, setCopied] = useState(false);

const {
formData, updateField,
Expand Down Expand Up @@ -41,7 +42,11 @@ export default function ReadmeMaker() {
function handleCopyMarkdown() {
if (!currentMd) { toast('Generate content first!'); return; }
navigator.clipboard.writeText(currentMd)
.then(() => toast('✓ Copied to clipboard!'))
.then(() => {
toast('✓ Copied to clipboard!');
setCopied(true);
setTimeout(() => setCopied(false), 2000);
})
.catch(() => toast('Copy failed'));
}

Expand Down Expand Up @@ -81,7 +86,9 @@ export default function ReadmeMaker() {
</a>
<button className="hbtn" onClick={handleClearSaved}>🗑 Clear Saved</button>
<button className="hbtn" onClick={handleResetAll}>↺ Reset All Fields</button>
<button className="hbtn" onClick={handleCopyMarkdown}>Copy Markdown</button>
<button className={`hbtn${copied ? ' copied' : ''}`} onClick={handleCopyMarkdown}>
{copied ? '✓ Copied!' : 'Copy Markdown'}
</button>
</div>
</header>

Expand Down
50 changes: 50 additions & 0 deletions src/styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -3401,3 +3401,53 @@ body.light-mode .gh-preview {
.faq-question { font-size: 14px; padding: 16px; }
.faq-answer { padding: 0 16px 16px; padding-top: 12px; }
}

/* ── Floating Copy Button inside Preview ── */
.copy-floating-btn {
position: absolute;
top: 16px;
right: 16px;
z-index: 50;
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background: var(--surface2);
border: 1px solid var(--border2);
border-radius: 8px;
color: var(--muted2);
font-family: 'Space Grotesk', sans-serif;
font-size: 12px;
font-weight: 600;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
user-select: none;
}

.copy-floating-btn:hover {
background: var(--surface3);
border-color: var(--accent);
color: var(--accent);
transform: translateY(-1px);
}

.copy-floating-btn.copied {
background: rgba(16, 185, 129, 0.15);
border-color: var(--green);
color: var(--green);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
}

.pbtn.green.copied {
background: rgba(16, 185, 129, 0.25);
border-color: var(--green);
color: var(--green);
}

.hbtn.copied {
background: rgba(16, 185, 129, 0.15);
border-color: var(--green);
color: var(--green);
}