UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

194 lines (172 loc) 7.19 kB
import { classMap } from 'lit/directives/class-map.js' import { customElement, property, state } from 'lit/decorators.js' import { html, nothing } from 'lit' import { PktElement } from '@/base-elements/element' import { Ref, createRef, ref } from 'lit/directives/ref.js' import { styleMap } from 'lit/directives/style-map.js' import { uuidish } from '@/utils/stringutils' import '@/components/icon' import { TAriaLive } from '@/types/aria' export type TProgressbarRole = 'progressbar' | 'meter' export type TProgressbarSkin = 'dark-blue' | 'light-blue' | 'green' | 'red' export type TProgressbarStatusPlacement = 'center' | 'left' | 'following' export type TProgressbarStatusType = 'none' | 'percentage' | 'fraction' export type TProgressbarTitlePosition = 'left' | 'center' export interface IPktProgressbar { ariaLabel?: string | null ariaLabelledby?: string | null ariaLive?: TAriaLive | null ariaValueText?: string | null id?: string | null role?: TProgressbarRole skin?: TProgressbarSkin statusPlacement?: TProgressbarStatusPlacement statusType?: TProgressbarStatusType title?: string | null titlePosition?: TProgressbarTitlePosition valueCurrent: number valueMax?: number valueMin?: number } @customElement('pkt-progressbar') export class PktProgressbar extends PktElement implements IPktProgressbar { constructor() { super() } // Public properties @property({ type: String }) ariaLabel: string | null = null @property({ type: String, reflect: true }) ariaLabelledby: string | null = null @property({ type: String, reflect: true }) ariaValueText: string | null = null @property({ type: String }) ariaLive: TAriaLive = 'polite' @property({ type: String, reflect: true }) id: string = uuidish() @property({ type: String }) role: TProgressbarRole = 'progressbar' @property({ type: String }) skin: TProgressbarSkin = 'dark-blue' @property({ type: String }) statusPlacement: TProgressbarStatusPlacement = 'following' @property({ type: String }) statusType: TProgressbarStatusType = 'none' @property({ type: String, reflect: true }) title = '' @property({ type: String }) titlePosition: TProgressbarTitlePosition = 'left' @property({ type: Number, reflect: true }) valueCurrent = 0 @property({ type: Number }) valueMax = 100 @property({ type: Number }) valueMin = 0 // State @state() private labelWidth = 0 @state() private progressbarId: string = this.id @state() private computedAriaLabelledby: string | null = null @state() private computedAriaValueText = '' // Private refs private labelRef: Ref<HTMLSpanElement> = createRef() private progressBarRef: Ref<HTMLDivElement> = createRef() firstUpdated(changedProperties: Map<string | number | symbol, unknown>) { super.firstUpdated(changedProperties) this.setComputedValues() this.syncAttributes() } updated(changedProperties: Map<string | number | symbol, unknown>) { super.updated(changedProperties) if (changedProperties.has('valueCurrent') && this.labelRef.value) { this.labelWidth = this.labelRef.value.getBoundingClientRect().width || 0 } if (changedProperties.has('id') && this.id) { this.progressBarId = this.id } if ( changedProperties.has('statusType') || changedProperties.has('id') || changedProperties.has('ariaLabelledby') ) { this.progressbarId = this.id || uuidish() this.computedAriaLabelledby = this.ariaLabelledby || `${this.progressbarId}-title` } if ( changedProperties.has('ariaValueText') || changedProperties.has('valueCurrent') || changedProperties.has('valueMax') ) { this.computedAriaValueText = this.statusType === 'fraction' && !this.ariaValueText ? `${this.valueCurrent} av ${this.valueMax - this.valueMin}` : this.ariaValueText || '' } this.syncAttributes() } render() { const hasStatus = this.statusType !== 'none' const totalSteps = this.valueMax - this.valueMin const percentageOfSteps = (this.valueCurrent / totalSteps) * 100 const currentPercentage = this.statusType === 'fraction' ? Math.round(percentageOfSteps) : Math.round(((this.valueCurrent - this.valueMin) / (this.valueMax - this.valueMin)) * 100) const formattedTitle = `${this.valueCurrent} av ${totalSteps}` const barClasses = classMap({ 'pkt-progressbar__bar': true, [`pkt-progressbar__bar--${this.skin}`]: !!this.skin, }) const titleClasses = classMap({ 'pkt-progressbar__title': true, [`pkt-progressbar__title-center`]: this.titlePosition === 'center', }) const statusClasses = classMap({ 'pkt-progressbar__status': true, [`pkt-progressbar__status--center`]: this.statusPlacement === 'center', }) const placementClasses = classMap({ [`pkt-progressbar__status-placement--following`]: this.statusPlacement === 'following', [`pkt-progressbar__status-placement--center`]: this.statusPlacement === 'center', [`pkt-progressbar__status-placement--left`]: this.statusPlacement === 'left', }) return html` <div class="pkt-progressbar__container" .ref=${this.progressBarRef} style=${styleMap({ '--pkt-progress-label-width': `${this.labelWidth}px`, '--pkt-progress-width': `${currentPercentage}%`, })} > ${this.title ? html`<p id=${`${this.progressBarId}-title`} class=${titleClasses}>${this.title}</p>` : nothing} <div class="pkt-progressbar__bar-wrapper"> <div class=${barClasses}></div> </div> ${hasStatus ? html`<div class=${statusClasses}> <span class=${placementClasses} ${ref(this.labelRef)}> ${this.statusType === 'percentage' ? `${currentPercentage}%` : formattedTitle} </span> </div>` : nothing} </div>` } // methods private setComputedValues() { this.progressbarId = this.id || uuidish() this.computedAriaLabelledby = this.ariaLabelledby || `${this.progressbarId}-title` this.computedAriaValueText = this.statusType === 'fraction' && !this.ariaValueText ? `${this.valueCurrent} av ${this.valueMax - this.valueMin}` : this.ariaValueText || '' } private syncAttributes() { this._handleAttribute('aria-live', this.ariaLive) this._handleAttribute('aria-valuenow', this.valueCurrent) this._handleAttribute('aria-valuemin', this.valueMin) this._handleAttribute('aria-valuemax', this.valueMax) this._handleAttribute('aria-valuetext', this.computedAriaValueText) this._handleAttribute('aria-label', this.ariaLabel) this._handleAttribute('role', this.role) this._handleAttribute('aria-atomic', 'true') this._handleAttribute('id', this.progressbarId) if (!this.ariaLabel) { this._handleAttribute('aria-labelledby', this.computedAriaLabelledby) } } private _handleAttribute(name: string, value: string | number | null) { if (value == null || value === '') { this.removeAttribute(name) } else { this.setAttribute(name, String(value)) } } } export default PktProgressbar