UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

268 lines (243 loc) 8.2 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 { PktElementWithSlot } from '@/base-elements/element-with-slot' import { slotContent } from '@/directives/slot-content' import { IPktHeading } from '../heading' import specs from 'componentSpecs/card.json' import '@/components/icon' import '@/components/tag' import '@/components/heading' import type { IAriaAttributes, TCardSkin, TLayout } from 'shared-types' export type { TCardSkin, TLayout } 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 })[] } export class PktCard extends PktElementWithSlot implements IPktCard { // 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, converter: { fromAttribute: (value: string | null): TCardSkin => { const validSkins = specs.props.skin.type as TCardSkin[] if (value && validSkins.includes(value as TCardSkin)) { return value as TCardSkin } else { if (value && !validSkins.includes(value as TCardSkin)) { // eslint-disable-next-line no-console -- Acceptable to log a warning for invalid skin values console.warn( `Invalid skin value "${value}". Using default skin "${specs.props.skin.default}".`, ) } return specs.props.skin.default as TCardSkin } }, toAttribute: (value: TCardSkin): string => value, }, }) 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 weight="regular" > ${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" weight="regular" 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)} > <span>${tag.text}</span> </pkt-tag> `, )} </div> ` : nothing} ` } renderSlot() { return html`<section class="pkt-card__content">${slotContent(this)}</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} ` } } try { customElement('pkt-card')(PktCard) } catch (e) { console.warn('Forsøker å definere <pkt-card>, men den er allerede definert') }