UNPKG

@cfpb/cfpb-design-system

Version:
142 lines (115 loc) 3.75 kB
import { html, css, unsafeCSS } from 'lit'; import { ref, createRef } from 'lit/directives/ref.js'; import { CfpbIconText } from '../../cfpb-icon-text'; import styles from './styles.component.scss?inline'; export const MixinLink = (superClass) => class extends superClass { static styles = css` ${unsafeCSS(styles)} `; #anchorRef = createRef(); /** * @property {string} linkText - The text of the slotted link. * @property {object} linkAttributes - The attributes on the slotted link. * @property {string} linkVariant - The configuration of the link. * @returns {object} The map of properties. */ static properties = { linkText: { type: String, state: true }, linkAttributes: { type: Object, state: true }, linkVariant: { type: String, reflect: true, attribute: 'link-variant' }, noTopBorder: { type: Boolean, attribute: 'no-top-border', reflect: true, }, inline: { type: Boolean, reflect: true }, }; static variants = new Set([ 'external', 'download', 'nav-left', 'nav-right', ]); constructor() { super(); this.linkText = ''; this.linkAttributes = {}; } willUpdate(changed) { if (changed.has('linkVariant')) { if (!this.constructor.variants.has(this.linkVariant)) { // eslint-disable-next-line no-console console.warn(`Invalid link variant "${this.linkVariant}"`); this.linkVariant = undefined; } } } firstUpdated(changedProperties) { super.firstUpdated?.(changedProperties); this.#extractAnchorData(); CfpbIconText.init(); } get #slottedAnchor() { const slot = this.renderRoot.querySelector('slot'); const assigned = slot?.assignedElements({ flatten: true }) ?? []; return assigned.find((elm) => elm instanceof HTMLAnchorElement) ?? null; } #extractAnchorData() { const anchor = this.#slottedAnchor; if (!anchor) { // this.tagName will be retrieved from the class implementing the mixin. // eslint-disable-next-line no-console console.warn(`${this.tagName.toLowerCase()} expects a slotted <a>`); return; } this.linkText = anchor.textContent.trim(); const blocked = ['class', 'style', 'id', 'slot']; this.linkAttributes = Object.fromEntries( [...anchor.attributes] .filter((attr) => !blocked.includes(attr.name)) .map((attr) => [attr.name, attr.value]), ); } get #iconLeft() { return this.linkVariant === 'nav-left' ? 'left' : ''; } get #iconRight() { switch (this.linkVariant) { case 'nav-right': return 'right'; case 'download': return 'download'; case 'external': return 'external-link'; } return ''; } get #underline() { if (!this.linkAttributes.href) return ''; return this.inline ? 'all' : 'tablet-up'; } renderLink() { // Empty slot is fallback content. return html` <slot></slot> <a ${ref(this.#anchorRef)}> <cfpb-icon-text iconLeft=${this.#iconLeft} iconRight=${this.#iconRight} .underline=${this.#underline} ?mobile-icon-align-end=${this.linkVariant === 'nav-right'} ?inline=${this.inline} >${this.linkText}</cfpb-icon-text > </a> `; } updated() { const anchor = this.#anchorRef.value; if (!anchor) return; Object.entries(this.linkAttributes).forEach(([name, value]) => { anchor.setAttribute(name, value); }); } };