UNPKG

@cfpb/cfpb-design-system

Version:
169 lines (150 loc) 4.33 kB
import { html, LitElement, css, unsafeCSS } from 'lit'; import { classMap } from 'lit/directives/class-map.js'; import { ref, createRef } from 'lit/directives/ref.js'; import styles from './cfpb-button.component.scss'; import { CfpbIconText } from '../cfpb-icon-text'; // The variants are different color themes of the button. const VALID_VARIANTS = ['primary', 'secondary', 'warning']; // The types are a regular button, or submit/reset that are used in forms. const VALID_TYPES = ['button', 'submit', 'reset']; /** * * @element cfpb-button * @slot - The main content for the button. */ export class CfpbButton extends LitElement { static styles = css` ${unsafeCSS(styles)} `; /** * @property {string} type - The button type: button, submit, or reset. * @property {string} href - The URL to link to (makes the button a link). * @property {boolean} disabled - Whether the button is disabled or not. * @property {string} variant - The button variant: secondary and warning. * @property {boolean} fullOnMobile - Whether to be width 100% on mobile. * @property {boolean} flushLeft - Whether button is not rounded on left. * @property {boolean} flushRight - Whether button is not rounded on right. * @property {boolean} styleAsLink - Style the button as a link. * @returns {object} The map of properties. */ static get properties() { return { type: { type: String }, href: { type: String }, disabled: { type: Boolean, reflect: true }, variant: { type: String }, fullOnMobile: { type: Boolean, attribute: 'full-on-mobile', reflect: true, }, flushLeft: { type: Boolean, attribute: 'flush-left', reflect: true, }, flushRight: { type: Boolean, attribute: 'flush-right', reflect: true, }, styleAsLink: { type: Boolean, attribute: 'style-as-link', reflect: true, }, }; } // DOM references. #iconTextDom = createRef(); constructor() { super(); this.type = 'button'; this.variant = 'primary'; this.disabled = false; this.fullOnMobile = false; this.styleAsLink = false; } /** * @returns {boolean} True if it has an icon, false otherwise. */ hasIcon() { return this.#iconTextDom.value?.hasIcon(); } /** * Hide any icon in the slot. */ hideIcon() { this.#iconTextDom.value?.hideIcon(); } /** * Show any icon in the slot, if it was hidden. */ showIcon() { this.#iconTextDom.value?.showIcon(); } /** * Ensure the variant value is valid, and fall back to a default if not. * @returns {string} A valid variant value string. */ get #validVariant() { return VALID_VARIANTS.includes(this.variant) ? this.variant : 'primary'; } /** * Ensure the type value is valid, and fall back to a default if not. * @returns {string} A valid type value string. */ get #validType() { return VALID_TYPES.includes(this.type) ? this.type : 'button'; } /** * The classes added to the button. * @returns {object} A classmap of CSS class names. */ get #btnClass() { return { 'a-btn': true, [`a-btn--${this.#validVariant}`]: this.#validVariant !== 'primary', [`a-btn--link`]: this.styleAsLink === true, }; } #renderTextAndIcon() { return html` <cfpb-icon-text ${ref(this.#iconTextDom)} ?disabled=${this.disabled}> <slot></slot> </cfpb-icon-text> `; } render() { const classes = classMap(this.#btnClass); // Link button form. if (this.href) { return html` <a class=${classes} href=${this.disabled ? undefined : this.href} role="button" aria-disabled=${String(this.disabled)} tabindex=${this.disabled ? -1 : 0} > ${this.#renderTextAndIcon()} </a> `; } // Button form. return html` <button class=${classes} ?disabled=${this.disabled} type=${this.#validType} > ${this.#renderTextAndIcon()} </button> `; } static init() { CfpbIconText.init(); window.customElements.get('cfpb-button') || window.customElements.define('cfpb-button', CfpbButton); } }