refactor(ui): reuse emoji icon helpers
This commit is contained in:
@@ -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
10
ui/src/ui/icons.ts
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user