import { PktElementWithSlot } from '@/base-elements/element-with-slot' import { slotContent } from '@/directives/slot-content' import { PktIconName } from '@oslokommune/punkt-assets/dist/icons/icon' import type { Booleanish, THTMLButtonType } from 'shared-types' import { html, nothing, PropertyValues } from 'lit' import { property, customElement, state } from 'lit/decorators.js' import { classMap } from 'lit/directives/class-map.js' import { ifDefined } from 'lit/directives/if-defined.js' import '@/components/icon' const defaultAnimationPath = 'https://punkt-cdn.oslo.kommune.no/latest/animations/' // Allow global override of animation assets path if (typeof window !== 'undefined') { window.pktAnimationPath = window.pktAnimationPath || defaultAnimationPath } export type TPktButtonMode = 'light' | 'dark' export type TPktButtonSize = 'xsmall' | 'small' | 'medium' | 'large' export type TPktButtonColor = | 'blue' | 'blue-outline' | 'green' | 'green-outline' | 'green-dark' | 'green-dark-outline' | 'beige-light' | 'beige-dark-outline' | 'yellow' | 'yellow-outline' | 'red' | 'red-outline' export type TPktButtonSkin = 'primary' | 'secondary' | 'tertiary' export type TPktButtonVariant = | 'label-only' | 'icon-left' | 'icon-right' | 'icon-only' | 'icons-right-and-left' export type TPktButtonState = 'normal' | 'focus' | 'hover' | 'active' export type TPktButtonType = THTMLButtonType export interface IPktButton { iconName?: PktIconName secondIconName?: PktIconName iconPath?: string secondIconPath?: string mode?: TPktButtonMode size?: TPktButtonSize fullWidth?: Booleanish fullWidthOnMobile?: Booleanish color?: TPktButtonColor skin?: TPktButtonSkin variant?: TPktButtonVariant state?: TPktButtonState type?: TPktButtonType isLoading?: Booleanish disabled?: Booleanish loadingAnimationPath?: string } export class PktButton extends PktElementWithSlot implements IPktButton { // Properties @property({ type: String }) iconName: string = 'user' @property({ type: String }) secondIconName: string = 'user' @property({ type: String }) iconPath: string | undefined @property({ type: String }) secondIconPath: string | undefined @property({ type: String }) mode?: TPktButtonMode = 'light' @property({ type: String }) size: TPktButtonSize = 'medium' @property({ type: Boolean, attribute: 'full-width' }) fullWidth: Booleanish = false @property({ type: Boolean, attribute: 'full-width-on-mobile' }) fullWidthOnMobile: Booleanish = false @property({ type: String }) color?: TPktButtonColor @property({ type: String }) skin: TPktButtonSkin = 'primary' @property({ type: String }) variant: TPktButtonVariant = 'label-only' @property({ type: String, reflect: true }) state?: TPktButtonState = 'normal' @property({ type: String, reflect: true }) type: TPktButtonType = 'button' @property({ type: String }) form: string | undefined = undefined @property({ type: Boolean, reflect: true }) isLoading: Booleanish = false @property({ type: Boolean, reflect: true }) disabled: Booleanish = false @property({ type: String }) loadingAnimationPath: string | undefined = typeof window !== 'undefined' ? window.pktAnimationPath : defaultAnimationPath // Lifecycle connectedCallback(): void { super.connectedCallback() this.addEventListener( 'click', (e) => { if (this.disabled || this.hasAttribute('disabled') || this.isLoading) { e.preventDefault() e.stopImmediatePropagation() } }, true, ) this.addEventListener( 'keydown', (e) => { if (!(this.disabled || this.hasAttribute('disabled') || this.isLoading)) return if (e.key === 'Enter' || e.key === ' ') { e.preventDefault() e.stopImmediatePropagation() } }, true, ) } attributeChangedCallback(name: string, _old: string | null, value: string | null): void { super.attributeChangedCallback(name, _old, value) // Convert strings to booleans if (name === 'disabled' && value === 'false') { this.disabled = false } if ((name === 'isloading' || name === 'isLoading') && value === 'false') { this.isLoading = false } } private static readonly relocatedAriaAttributes = [ 'aria-haspopup', 'aria-expanded', 'aria-controls', ] as const @state() private relocatedAria: Record = {} protected willUpdate(changed: PropertyValues): void { super.willUpdate(changed) let moved = false const next = { ...this.relocatedAria } for (const name of PktButton.relocatedAriaAttributes) { const value = this.getAttribute(name) if (value !== null) { next[name] = value this.removeAttribute(name) moved = true } } if (moved) this.relocatedAria = next } protected firstUpdated(_changedProperties: PropertyValues): void { super.firstUpdated(_changedProperties) if (this.disabled === 'false') { this.disabled = false } if (this.isLoading === 'false') { this.isLoading = false } } // Render render() { const formId = this.form ?? this.getAttribute('form') ?? undefined const classes = { 'pkt-btn': true, [`pkt-btn--${this.size}`]: !!this.size, [`pkt-btn--${this.skin}`]: !!this.skin, [`pkt-btn--${this.variant}`]: !!this.variant, [`pkt-btn--${this.color}`]: !!this.color, [`pkt-btn--${this.state}`]: !!this.state, 'pkt-btn--full': !!this.fullWidth, 'pkt-btn--full-small': !!this.fullWidthOnMobile, 'pkt-btn--disabled': !!this.disabled, 'pkt-btn--isLoading': !!this.isLoading, } return html` ` } } export default PktButton try { customElement('pkt-button')(PktButton) } catch (e) { console.warn('Forsøker å definere , men den er allerede definert') }