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);