import { css, html } from "lit"; import { BaseElement } from "../../core/base-element.ts"; /** * CTCopyButton - Copy to clipboard button with automatic visual feedback * * @element ct-copy-button * * @attr {string} text - Text to copy to clipboard (required) * @attr {string} variant - Button style variant (default: "secondary") * Options: "primary" | "secondary" | "destructive" | "outline" | "ghost" | "link" | "pill" * @attr {string} size - Button size (default: "default") * Options: "default" | "sm" | "lg" | "icon" | "md" * @attr {boolean} disabled - Disable the button * @attr {number} feedback-duration - Success feedback duration in ms (default: 2000) * @attr {boolean} icon-only - Only show icon, no text (default: false) * * @fires ct-copy-success - Fired when copy succeeds * Detail: { text: string, length: number } * @fires ct-copy-error - Fired when copy fails * Detail: { error: Error, text: string } * * @slot - Button label text (optional, defaults based on state) * * @example * // Basic usage * Copy * * // Icon only * * * // Custom styling * 📋 Copy List * * // With event handler (in pattern) * Copy */ export class CTCopyButton extends BaseElement { static override styles = [ BaseElement.baseStyles, css` /* Ensure icon-only buttons maintain square aspect ratio */ :host([icon-only]) ct-button { min-width: 2.25rem; display: inline-flex; } /* Adjust for different sizes when icon-only */ :host([icon-only]) ct-button::part(button) { aspect-ratio: 1; min-width: fit-content; } `, ]; static override properties = { text: { type: String }, variant: { type: String }, size: { type: String }, disabled: { type: Boolean, reflect: true }, feedbackDuration: { type: Number, attribute: "feedback-duration" }, iconOnly: { type: Boolean, attribute: "icon-only", reflect: true }, }; declare text: string; declare variant?: | "primary" | "secondary" | "destructive" | "outline" | "ghost" | "link" | "pill"; declare size?: "default" | "sm" | "lg" | "icon" | "md"; declare disabled: boolean; declare feedbackDuration: number; declare iconOnly: boolean; private _copied = false; private _resetTimeout?: number; constructor() { super(); this.text = ""; this.variant = "secondary"; this.size = "default"; this.disabled = false; this.feedbackDuration = 2000; this.iconOnly = false; } private async _handleClick(e: Event) { e.preventDefault(); e.stopPropagation(); if (this.disabled || !this.text) return; try { await navigator.clipboard.writeText(this.text); this._copied = true; this.requestUpdate(); this.emit("ct-copy-success", { text: this.text, length: this.text.length, }); // Reset copied state after duration if (this._resetTimeout) { clearTimeout(this._resetTimeout); } this._resetTimeout = setTimeout(() => { this._copied = false; this.requestUpdate(); }, this.feedbackDuration); } catch (error) { this.emit("ct-copy-error", { error: error as Error, text: this.text, }); } } override disconnectedCallback() { super.disconnectedCallback(); if (this._resetTimeout) { clearTimeout(this._resetTimeout); } } override render() { const title = this._copied ? "Copied!" : "Copy to clipboard"; const ariaLabel = this._copied ? "Copied to clipboard" : "Copy to clipboard"; return html` ${this.iconOnly ? html` ${this._copied ? "✓" : "📋"} ` : html` ${this._copied ? "✓ Copied!" : "📋 Copy"} `} `; } } globalThis.customElements.define("ct-copy-button", CTCopyButton);