import { css, html } from "lit"; import { BaseElement } from "../../core/base-element.ts"; /** * CTTab - Individual tab button component used within ct-tab-list * * @element ct-tab * * @attr {string} value - Unique identifier for the tab * @attr {boolean} disabled - Whether the tab is disabled * @attr {boolean} selected - Whether the tab is currently selected * * @slot - Default slot for tab label content * * @fires tab-click - Fired when tab is clicked with detail: { tab } * * @example * Profile */ export class CTTab extends BaseElement { static override properties = { value: { type: String, reflect: true }, disabled: { type: Boolean }, selected: { type: Boolean }, }; static override styles = [ BaseElement.baseStyles, css` :host { display: inline-flex; } .tab { display: inline-flex; align-items: center; justify-content: center; white-space: nowrap; border-radius: var(--ct-theme-border-radius, var(--ct-border-radius-md)); padding: var(--ct-spacing-2) var(--ct-spacing-3); font-size: var(--ct-font-size-sm); font-weight: var(--ct-font-weight-medium); transition: all var(--ct-transition-duration-fast) var(--ct-transition-timing-ease); cursor: pointer; background: transparent; border: none; color: var(--ct-theme-color-text-muted, #6b7280); font-family: inherit; position: relative; } .tab:hover:not(:disabled) { color: var(--ct-theme-color-text, #111827); } .tab:focus-visible { outline: 2px solid var(--ct-theme-color-primary, var(--ct-colors-primary-500)); outline-offset: 2px; } .tab:disabled { opacity: 0.5; cursor: not-allowed; } .tab[data-selected="true"] { color: var(--ct-theme-color-text, #111827); } /* Indicator for selected state */ .tab[data-selected="true"]::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background-color: var( --ct-theme-color-primary, var(--ct-colors-primary-500) ); } /* Vertical orientation styles */ :host-context(ct-tab-list[orientation="vertical"]) .tab[data-selected="true"]::after { top: 0; bottom: 0; left: -1px; right: auto; width: 2px; height: auto; } `, ]; declare value: string; declare disabled: boolean; declare selected: boolean; private _button: HTMLButtonElement | null = null; constructor() { super(); this.value = ""; this.disabled = false; this.selected = false; } get button(): HTMLButtonElement | null { if (!this._button) { this._button = this.shadowRoot?.querySelector(".tab") as HTMLButtonElement || null; } return this._button; } override connectedCallback() { super.connectedCallback(); // Set ARIA attributes this.setAttribute("role", "tab"); this.updateAriaAttributes(); } override updated(changedProperties: Map) { super.updated(changedProperties); if ( changedProperties.has("selected") || changedProperties.has("disabled") ) { this.updateAriaAttributes(); } } override render() { return html` `; } private updateAriaAttributes(): void { this.setAttribute("aria-selected", String(this.selected)); this.setAttribute("aria-disabled", String(this.disabled)); this.setAttribute( "tabindex", this.selected && !this.disabled ? "0" : "-1", ); } private handleClick = (event: Event): void => { if (this.disabled) { event.preventDefault(); event.stopPropagation(); return; } // Emit custom event for parent tabs component this.emit("tab-click", { tab: this }); }; private handleKeydown = (event: KeyboardEvent): void => { if (event.key === " " || event.key === "Enter") { event.preventDefault(); this.click(); } }; /** * Focus the tab button */ override focus(): void { this.button?.focus(); } /** * Blur the tab button */ override blur(): void { this.button?.blur(); } } globalThis.customElements.define("ct-tab", CTTab);