- Convert admin layout, sidebar, and navigation to Tailwind classes - Convert dashboard components (StatusCard, DiagnosticRow, etc.) to Tailwind - Convert all ~48 admin page files from admin CSS classes to Tailwind - Remove unused admin CSS from globals.css (973 → 712 lines) - Convert developers page badges to Tailwind - Remove <style jsx> block from OnlineUsersWidget
70 lines
1.9 KiB
TypeScript
70 lines
1.9 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
|
|
/**
|
|
* Read-only copy box for an embed snippet. Renders the code inside a
|
|
* <textarea readOnly> so staff can select/copy it, plus a one-click copy
|
|
* button (with clipboard fallback to selecting the text).
|
|
*/
|
|
export function EmbedSnippet({
|
|
title,
|
|
description,
|
|
snippet,
|
|
}: {
|
|
title: string;
|
|
description?: string;
|
|
snippet: string;
|
|
}) {
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
async function copy() {
|
|
try {
|
|
await navigator.clipboard.writeText(snippet);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 1500);
|
|
} catch {
|
|
// Clipboard API unavailable (e.g. insecure context) — fall back to
|
|
// selecting the textarea so the user can copy manually.
|
|
const el = document.getElementById(
|
|
`snippet-${title.replace(/\s+/g, '-').toLowerCase()}`,
|
|
) as HTMLTextAreaElement | null;
|
|
el?.select();
|
|
}
|
|
}
|
|
|
|
const id = `snippet-${title.replace(/\s+/g, '-').toLowerCase()}`;
|
|
const lines = snippet.split('\n').length;
|
|
|
|
return (
|
|
<section className="mt-6">
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
|
<h2 style={{ flex: 1, margin: 0 }}>{title}</h2>
|
|
<button type="button" className="btn btn-primary" onClick={copy}>
|
|
{copied ? 'Copied!' : 'Copy'}
|
|
</button>
|
|
</div>
|
|
{description ? (
|
|
<p className="muted" style={{ marginTop: '0.4rem' }}>
|
|
{description}
|
|
</p>
|
|
) : null}
|
|
<textarea
|
|
id={id}
|
|
readOnly
|
|
value={snippet}
|
|
rows={Math.min(Math.max(lines + 1, 3), 14)}
|
|
onFocus={(e) => e.currentTarget.select()}
|
|
spellCheck={false}
|
|
style={{
|
|
width: '100%',
|
|
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',
|
|
fontSize: '0.85rem',
|
|
whiteSpace: 'pre',
|
|
resize: 'vertical',
|
|
}}
|
|
/>
|
|
</section>
|
|
);
|
|
}
|