@oslokommune/punkt-elements
Version:
Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo
150 lines (135 loc) • 4.52 kB
text/typescript
import { classMap } from 'lit/directives/class-map.js'
import { customElement, property, state } from 'lit/decorators.js'
import { html, PropertyValues } from 'lit'
import { PktElementWithSlot } from '@/base-elements/element-with-slot'
import { slotContent } from '@/directives/slot-content'
import { TPktSize } from '@/types/size'
import specs from 'componentSpecs/tag.json'
import '@/components/icon'
import type { IAriaAttributes, THTMLButtonType } from 'shared-types'
import { PktIconName } from '@oslokommune/punkt-assets/dist/icons/icon'
import { ifDefined } from 'lit/directives/if-defined.js'
export type TTagSkin =
| 'blue'
| 'blue-dark'
| 'blue-light'
| 'green'
| 'red'
| 'yellow'
| 'beige'
| 'gray'
| 'grey'
export type TTagType = THTMLButtonType
export interface IPktTag {
closeTag?: boolean
size?: TPktSize
skin?: TTagSkin
textStyle?: string | null
iconName?: PktIconName
type?: TTagType
ariaLabel?: IAriaAttributes['aria-label'] | null
}
export class PktTag extends PktElementWithSlot<IPktTag> implements IPktTag {
constructor() {
super()
this._isClosed = false
}
/**
* Element attributes
*/
({ type: Boolean, reflect: true }) closeTag: boolean = specs.props.closeTag.default
({ type: String, reflect: true }) size: TPktSize = specs.props.size.default as TPktSize
({ type: String, reflect: true }) skin: TTagSkin = specs.props.skin.default as TTagSkin
({ type: String, reflect: true }) textStyle: string | null = null
({ type: String, reflect: true }) iconName: string | undefined = undefined
({ type: String }) type: TTagType = specs.props.type.default as TTagType
({ type: String }) ariaLabel: string | null = null
({ type: Number, attribute: 'button-tabindex' }) buttonTabindex: number | undefined =
undefined
/**
* Element state
*/
() _isClosed: boolean = false
() _ariaDescription: string | null = null
/**
* Lifecycle
*/
protected firstUpdated(_changedProperties: PropertyValues): void {
super.firstUpdated(_changedProperties)
if (this.closeTag && !this.ariaLabel) {
const label = this.querySelector('button span, .pkt-tag > span')?.textContent?.trim()
if (label) {
this._ariaDescription = `Klikk for å fjerne ${label}`
}
}
}
/**
* Element functions
*/
private close = (event: MouseEvent) => {
this._isClosed = true
this.dispatchEvent(
new CustomEvent('close', { detail: { origin: event }, bubbles: false, composed: true }),
)
// Historical support of old Vue implementations…
this.dispatchEvent(
new CustomEvent('on-close', { detail: { origin: event }, bubbles: false, composed: true }),
)
}
render() {
const classes = {
'pkt-tag': true,
[`pkt-tag--${this.size}`]: !!this.size,
[`pkt-tag--${this.skin}`]: !!this.skin,
[`pkt-tag--${this.textStyle}`]: !!this.textStyle,
}
const btnClasses = {
'pkt-tag': true,
'pkt-btn': true,
'pkt-btn--tertiary': true,
[`pkt-tag--${this.textStyle}`]: !!this.textStyle,
[`pkt-tag--${this.size}`]: !!this.size,
[`pkt-tag--${this.skin}`]: !!this.skin,
'pkt-btn--icons-right-and-left': this.closeTag && !!this.iconName,
'pkt-hide': this._isClosed,
}
if (this.closeTag) {
return html`
<button
class=${classMap(btnClasses)}
type=${this.type}
tabindex=${ifDefined(this.buttonTabindex)}
@click=${this.close}
aria-label=${ifDefined(this.ariaLabel || undefined)}
aria-description=${ifDefined(this._ariaDescription || undefined)}
>
${this.iconName &&
html`<pkt-icon
class="pkt-tag__icon"
name=${this.iconName}
aria-hidden="true"
></pkt-icon>`}
<span>${slotContent(this)}</span>
<pkt-icon class="pkt-tag__close-btn" name="close"></pkt-icon>
</button>
`
} else {
return html`
<span class=${classMap(classes)}>
${this.iconName &&
html`<pkt-icon
class="pkt-tag__icon"
name=${this.iconName}
aria-hidden="true"
></pkt-icon>`}
<span>${slotContent(this)}</span>
</span>
`
}
}
}
try {
customElement('pkt-tag')(PktTag)
} catch (e) {
console.warn('Forsøker å definere <pkt-tag>, men den er allerede definert')
}