import { baseTheme } from '@toeverything/theme'; import { cssVarV2 } from '@toeverything/theme/v2'; import { css, html, LitElement, nothing, type TemplateResult, unsafeCSS, } from 'lit'; import { property, query } from 'lit/decorators.js'; /** * Default size is 32px, you can override it by setting `size` property. * For example, ``. * * You can also set `width` or `height` property to override the size. * * Set `text` property to show a text label. * * @example * ```ts * html` * ${UnlinkIcon} * ` * * html` * ${ExportToHTMLIcon} * ` * ``` */ export class IconButton extends LitElement { static override styles = css` :host { box-sizing: border-box; display: flex; justify-content: center; align-items: center; border: none; width: var(--button-width); height: var(--button-height); border-radius: 4px; background: transparent; cursor: pointer; user-select: none; font-family: ${unsafeCSS(baseTheme.fontSansFamily)}; color: var(--affine-text-primary-color); pointer-events: auto; padding: 4px; } // This media query can detect if the device has a hover capability @media (hover: hover) { :host(:hover) { background: var(--affine-hover-color); } } :host(:active) { background: transparent; } :host([disabled]), :host(:disabled) { background: transparent; color: var(--affine-text-disable-color); cursor: not-allowed; } /* You can add a 'hover' attribute to the button to show the hover style */ :host([hover='true']) { background: var(--affine-hover-color); } :host([hover='false']) { background: transparent; } :host(:active[active]) { background: transparent; } /* not supported "until-found" yet */ :host([hidden]) { display: none; } :host > .text-container { display: flex; flex-direction: column; overflow: hidden; } :host .text { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: var(--affine-font-sm); line-height: var(--affine-line-height); } :host .sub-text { font-size: var(--affine-font-xs); color: var( --light-textColor-textSecondaryColor, var(--textColor-textSecondaryColor, #8e8d91) ); line-height: var(--affine-line-height); white-space: nowrap; text-overflow: ellipsis; overflow: hidden; margin-top: -2px; } ::slotted(svg) { flex-shrink: 0; color: var(--svg-icon-color); } ::slotted([slot='suffix']) { margin-left: auto; } `; constructor() { super(); // Allow activate button by pressing Enter key this.addEventListener('keypress', event => { if (this.disabled) { return; } if (event.key === 'Enter' && !event.isComposing) { this.click(); } }); // Prevent click event when disabled this.addEventListener( 'click', event => { if (this.disabled === true) { event.preventDefault(); event.stopPropagation(); } }, { capture: true } ); } override connectedCallback() { super.connectedCallback(); this.tabIndex = 0; this.role = 'button'; const DEFAULT_SIZE = '28px'; if (this.size && (this.width || this.height)) { return; } let width = this.width ?? DEFAULT_SIZE; let height = this.height ?? DEFAULT_SIZE; if (this.size) { width = this.size; height = this.size; } this.style.setProperty( '--button-width', typeof width === 'string' ? width : `${width}px` ); this.style.setProperty( '--button-height', typeof height === 'string' ? height : `${height}px` ); } override render() { if (this.hidden) return nothing; if (this.disabled) { const disabledColor = cssVarV2('icon/disable'); this.style.setProperty('--svg-icon-color', disabledColor); this.dataset.testDisabled = 'true'; } else { this.dataset.testDisabled = 'false'; const iconColor = this.active ? cssVarV2('icon/activated') : cssVarV2('icon/primary'); this.style.setProperty('--svg-icon-color', iconColor); } const text = this.text ? // wrap a span around the text so we can ellipsis it automatically html`
${this.text}
` : nothing; const subText = this.subText ? html`
${this.subText}
` : nothing; const textContainer = this.text || this.subText ? html`
${text}${subText}
` : nothing; return html` ${textContainer} `; } @property({ attribute: true, type: Boolean }) accessor active: boolean = false; // Do not add `{ attribute: false }` option here, otherwise the `disabled` styles will not work @property({ attribute: true, type: Boolean }) accessor disabled: boolean | undefined = undefined; @property() accessor height: string | number | null = null; @property({ attribute: true, type: String }) accessor hover: 'true' | 'false' | undefined = undefined; @property() accessor size: string | number | null = null; @property() accessor subText: string | TemplateResult<1> | null = null; @property() accessor text: string | TemplateResult<1> | null = null; @query('.text-container .text') accessor textElement: HTMLDivElement | null = null; @property() accessor width: string | number | null = null; } declare global { interface HTMLElementTagNameMap { 'icon-button': IconButton; } }