UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

253 lines (228 loc) 7.61 kB
import { classMap } from 'lit/directives/class-map.js' import { ifDefined } from 'lit/directives/if-defined.js' import { customElement, property } from 'lit/decorators.js' import { html, nothing } from 'lit' import { IPktTag } from '@/components/tag' import { PktElement } from '@/base-elements/element' import { PktSlotController } from '@/controllers/pkt-slot-controller' import { ref, createRef, Ref } from 'lit/directives/ref.js' import { IPktHeading } from '../heading' import specs from 'componentSpecs/card.json' import '@/components/icon' import '@/components/tag' import { IAriaAttributes } from '@/types/aria' export type TCardSkin = 'outlined' | 'outlined-beige' | 'gray' | 'beige' | 'green' | 'blue' export type TLayout = 'vertical' | 'horizontal' export type TCardPadding = 'none' | 'default' export type TCardImageShape = 'square' | 'round' export type TCardTagPosition = 'top' | 'bottom' export interface IPktCard { ariaLabel?: IAriaAttributes['aria-label'] metaLead?: string | null metaTrail?: string | null layout?: TLayout heading?: string headingLevel?: IPktHeading['level'] image?: { src: string; alt: string } imageShape?: TCardImageShape clickCardLink?: string | null openLinkInNewTab?: boolean | null borderOnHover?: boolean | null padding?: TCardPadding skin?: TCardSkin subheading?: string tagPosition?: TCardTagPosition tags?: (Omit<IPktTag, 'closeTag'> & { text: string })[] } @customElement('pkt-card') export class PktCard extends PktElement implements IPktCard { // Refs defaultSlot: Ref<HTMLElement> = createRef() //Constructor constructor() { super() this.slotController = new PktSlotController(this, this.defaultSlot) } // Properties @property({ type: String }) ariaLabel: string = '' @property({ type: String }) metaLead: string | null = null @property({ type: Boolean }) borderOnHover: boolean = true @property({ type: String, reflect: true }) clickCardLink: IPktCard['clickCardLink'] = null @property({ type: String }) metaTrail: string | null = null @property({ type: String }) layout: TLayout = specs.props.layout.default as TLayout @property({ type: String }) heading: string = '' @property({ type: Number }) headinglevel: IPktHeading['level'] = 3 @property({ type: Object }) image: { src: string; alt: string } = { src: '', alt: '', } @property({ type: String }) imageShape: TCardImageShape = 'square' @property({ type: Boolean }) openLinkInNewTab: boolean = false @property({ type: String }) padding: TCardPadding = specs.props.padding.default as TCardPadding @property({ type: String }) skin: TCardSkin = specs.props.skin.default as TCardSkin @property({ type: String }) subheading: string = '' @property({ type: String }) tagPosition: 'top' | 'bottom' = 'top' @property({ type: Array }) tags: (Omit<IPktTag, 'closeTag'> & { text: string })[] = [] connectedCallback() { super.connectedCallback() } // Render methods // Main render method // prettier-ignore render() { const classes = { 'pkt-card': true, [`pkt-card--${this.skin}`]: this.skin, [`pkt-card--${this.layout}`]: this.layout, [`pkt-card--padding-${this.padding}`]: this.padding, [`pkt-card--border-on-hover`]: this.borderOnHover, } // const ariaLabelLenke = this.ariaLabel?.trim() || (this.heading ? `${this.heading} lenkekort` : 'lenkekort') const ariaLabelVanlig = this.ariaLabel?.trim() || (this.heading ? this.heading : 'kort') return html` <article class=${classMap(classes)} aria-label=${ifDefined(this.clickCardLink ? ariaLabelLenke : ariaLabelVanlig)} > ${this.renderImage()} <div class="pkt-card__wrapper"> ${this.tagPosition === 'top' ? this.renderTags() : nothing} ${this.renderHeader()} ${this.renderSlot()} ${this.tagPosition === 'bottom' ? this.renderTags() : nothing} ${this.renderMetadata()} </div> </article> ` } // Render methods for different parts of the card renderImage() { const imageClasses = { 'pkt-card__image': true, [`pkt-card__image-${this.imageShape}`]: this.imageShape, } return html` ${this.image.src && html` <div class=${classMap(imageClasses)}> <img src=${this.image.src} alt=${this.image.alt || ''} /> </div> `} ` } // Do not render heading if link is present, render link heading instead // Combine the rendering for headings into a renderHeader method renderHeading() { return html` ${this.heading && !this.clickCardLink ? html` <pkt-heading class="pkt-card__heading" .level=${this.headinglevel || 3} size="medium" nospacing > ${this.heading} </pkt-heading> ` : nothing} ` } renderLinkHeading() { return html` ${this.clickCardLink ? html` <pkt-heading class="pkt-card__link-heading pkt-card__heading" .level=${this.headinglevel || 3} size="medium" nospacing > <a class="pkt-card__link" href=${this.clickCardLink} target=${this.openLinkInNewTab ? '_blank' : '_self'} >${this.heading}</a > </pkt-heading> ` : nothing} ` } renderSubheading() { return html` ${this.subheading ? html` <p class="pkt-card__subheading ">${this.subheading}</p> ` : nothing} ` } // Render header // prettier-ignore renderHeader() { const hasHeading = !!this.heading || !!this.subheading return html` ${hasHeading ? html` <header class="pkt-card__header"> ${this.renderHeading()} ${this.renderLinkHeading()} ${this.renderSubheading()} </header> ` : nothing} ` } renderTags() { const tagClasses = { 'pkt-card__tags': true, [`pkt-card__tags-${this.tagPosition}`]: this.tagPosition, } return html` ${this.tags.length > 0 ? html` <div class=${classMap(tagClasses)} role="list" aria-label=${this.tags.length > 1 ? 'merkelapper' : 'merkelapp'} > ${this.tags.map( (tag) => html` <pkt-tag role="listitem" textStyle="normal-text" size="medium" skin=${ifDefined(tag.skin)} iconName=${ifDefined(tag.iconName)} > ${tag.text} </pkt-tag> `, )} </div> ` : nothing} ` } renderSlot() { return html` ${this.defaultSlot && html`<section class="pkt-card__content" ${ref(this.defaultSlot)}></section>`} ` } renderMetadata() { return html` ${this.metaLead || this.metaTrail ? html` <footer class="pkt-card__metadata"> ${this.metaLead ? html`<span class="pkt-card__metadata-lead">${this.metaLead}</span>` : nothing} ${this.metaTrail ? html`<span class="pkt-card__metadata-trail">${this.metaTrail}</span>` : nothing} </footer> ` : nothing} ` } }