@oslokommune/punkt-elements
Version:
Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo
140 lines (125 loc) • 4.33 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 { PktElement } from '@/base-elements/element'
import { PktSlotController } from '@/controllers/pkt-slot-controller'
import { ref } from 'lit/directives/ref.js'
import { Ref, createRef } from 'lit/directives/ref.js'
import { TPktSize } from '@/types/size'
import specs from 'componentSpecs/tag.json'
import '@/components/icon'
import { IAriaAttributes } from '@/types/aria'
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 = 'button' | 'reset' | 'submit'
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 PktElement<IPktTag> implements IPktTag {
slotController: PktSlotController
defaultSlot: Ref<HTMLElement> = createRef()
constructor() {
super()
this.slotController = new PktSlotController(this, this.defaultSlot)
this._isClosed = false
}
/**
* Element attributes
*/
closeTag: boolean = specs.props.closeTag.default
size: TPktSize = specs.props.size.default as TPktSize
skin: TTagSkin = specs.props.skin.default as TTagSkin
textStyle: string | null = null
iconName: string | undefined = undefined
type: TTagType = specs.props.type.default as TTagType
ariaLabel: string | null = null
/**
* 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.defaultSlot.value?.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: true, composed: true }),
)
// Historical support of old Vue implementations…
this.dispatchEvent(
new CustomEvent('on-close', { detail: { origin: event }, bubbles: true, 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}
=${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}></pkt-icon>`}
<span ${ref(this.defaultSlot)}></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}></pkt-icon>`}
<span ${ref(this.defaultSlot)}></span>
</span>
`
}
}
}