refactor(ui): reuse emoji icon helpers

This commit is contained in:
Peter Steinberger
2026-01-21 02:58:51 +00:00
parent 03916ed10e
commit 14d3d72bcc
2 changed files with 39 additions and 17 deletions

View File

@@ -1,4 +1,5 @@
import { html, type TemplateResult } from "lit"; import { html, type TemplateResult } from "lit";
import { renderEmojiIcon, setEmojiIcon } from "../icons";
const COPIED_FOR_MS = 1500; const COPIED_FOR_MS = 1500;
const ERROR_FOR_MS = 2000; const ERROR_FOR_MS = 2000;
@@ -9,6 +10,11 @@ const COPY_ICON = "📋";
const COPIED_ICON = "✓"; const COPIED_ICON = "✓";
const ERROR_ICON = "!"; const ERROR_ICON = "!";
type CopyButtonOptions = {
text: () => string;
label?: string;
};
async function copyTextToClipboard(text: string): Promise<boolean> { async function copyTextToClipboard(text: string): Promise<boolean> {
if (!text) return false; if (!text) return false;
@@ -20,13 +26,19 @@ async function copyTextToClipboard(text: string): Promise<boolean> {
} }
} }
export function renderCopyAsMarkdownButton(markdown: string): TemplateResult { function setButtonLabel(button: HTMLButtonElement, label: string) {
button.title = label;
button.setAttribute("aria-label", label);
}
function createCopyButton(options: CopyButtonOptions): TemplateResult {
const idleLabel = options.label ?? COPY_LABEL;
return html` return html`
<button <button
class="chat-copy-btn" class="chat-copy-btn"
type="button" type="button"
title=${COPY_LABEL} title=${idleLabel}
aria-label=${COPY_LABEL} aria-label=${idleLabel}
@click=${async (e: Event) => { @click=${async (e: Event) => {
const btn = e.currentTarget as HTMLButtonElement | null; const btn = e.currentTarget as HTMLButtonElement | null;
const icon = btn?.querySelector( const icon = btn?.querySelector(
@@ -39,7 +51,7 @@ export function renderCopyAsMarkdownButton(markdown: string): TemplateResult {
btn.setAttribute("aria-busy", "true"); btn.setAttribute("aria-busy", "true");
btn.disabled = true; btn.disabled = true;
const copied = await copyTextToClipboard(markdown); const copied = await copyTextToClipboard(options.text());
if (!btn.isConnected) return; if (!btn.isConnected) return;
delete btn.dataset.copying; delete btn.dataset.copying;
@@ -48,35 +60,35 @@ export function renderCopyAsMarkdownButton(markdown: string): TemplateResult {
if (!copied) { if (!copied) {
btn.dataset.error = "1"; btn.dataset.error = "1";
btn.title = ERROR_LABEL; setButtonLabel(btn, ERROR_LABEL);
btn.setAttribute("aria-label", ERROR_LABEL); setEmojiIcon(icon, ERROR_ICON);
if (icon) icon.textContent = ERROR_ICON;
window.setTimeout(() => { window.setTimeout(() => {
if (!btn.isConnected) return; if (!btn.isConnected) return;
delete btn.dataset.error; delete btn.dataset.error;
btn.title = COPY_LABEL; setButtonLabel(btn, idleLabel);
btn.setAttribute("aria-label", COPY_LABEL); setEmojiIcon(icon, COPY_ICON);
if (icon) icon.textContent = COPY_ICON;
}, ERROR_FOR_MS); }, ERROR_FOR_MS);
return; return;
} }
btn.dataset.copied = "1"; btn.dataset.copied = "1";
btn.title = COPIED_LABEL; setButtonLabel(btn, COPIED_LABEL);
btn.setAttribute("aria-label", COPIED_LABEL); setEmojiIcon(icon, COPIED_ICON);
if (icon) icon.textContent = COPIED_ICON;
window.setTimeout(() => { window.setTimeout(() => {
if (!btn.isConnected) return; if (!btn.isConnected) return;
delete btn.dataset.copied; delete btn.dataset.copied;
btn.title = COPY_LABEL; setButtonLabel(btn, idleLabel);
btn.setAttribute("aria-label", COPY_LABEL); setEmojiIcon(icon, COPY_ICON);
if (icon) icon.textContent = COPY_ICON;
}, COPIED_FOR_MS); }, COPIED_FOR_MS);
}} }}
> >
<span class="chat-copy-btn__icon" aria-hidden="true">${COPY_ICON}</span> ${renderEmojiIcon(COPY_ICON, "chat-copy-btn__icon")}
</button> </button>
`; `;
} }
export function renderCopyAsMarkdownButton(markdown: string): TemplateResult {
return createCopyButton({ text: () => markdown, label: COPY_LABEL });
}

10
ui/src/ui/icons.ts Normal file
View File

@@ -0,0 +1,10 @@
import { html, type TemplateResult } from "lit";
export function renderEmojiIcon(icon: string, className: string): TemplateResult {
return html`<span class=${className} aria-hidden="true">${icon}</span>`;
}
export function setEmojiIcon(target: HTMLElement | null, icon: string): void {
if (!target) return;
target.textContent = icon;
}