All files / src/components/Card index.ts

53.84% Statements 14/26
46.15% Branches 6/13
40% Functions 2/5
40% Lines 6/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123                                                                                90x             90x           90x           90x           29x           29x                                                                                                      
import { LitElement, html, unsafeCSS } from "lit"
import { customElement, property } from "lit/decorators.js"
import { classMap } from "lit/directives/class-map.js"
import { ifDefined } from "lit/directives/if-defined.js"
 
import style from "./style.scss?inline"
 
/**
 * Available visual styles for {@link VgCard}.
 */
export type CardVariant = "elevated" | "outlined" | "subtle"
 
/**
 * Payload emitted by the {@link VgCard | card}'s `action` event when the card is interactive.
 */
export interface CardActionDetail {
	/**
	 * Original keyboard or pointer event initiating the action.
	 */
	readonly originalEvent: MouseEvent | KeyboardEvent
}
 
 
/**
 * Theme-aware content container with optional header/footer slots.
 *
 * @tag vg-card
 * @tagname vg-card
 * @summary The Card component, used for displaying content in a structured container.
 *
 * @slot header - Optional content rendered before the body, often containing a title or metadata
 * @slot - Main card body content
 * @slot footer - Optional actions or supporting content rendered after the body
 *
 * @csspart card - Allows you to style the card container element
 *
 * @fires {CardActionDetail} vg-action - Fired when an interactive card is triggered
 *
 */
@customElement("vg-card")
export class VgCard extends LitElement {
	static styles = unsafeCSS(style)
 
	/**
	 * Display heading rendered above the default slot when provided.
	 */
	@property({ type: String })
	public heading: string | null = null
 
	/**
	 * Visual variant for the card container.
	 */
	@property({ type: String })
	public variant: CardVariant = "elevated"
 
	/**
	 * Enables hover/press affordances and makes the card keyboard activatable.
	 */
	@property({ type: Boolean, reflect: true })
	public interactive = false
 
	/**
	 * @inheritdoc
	 */
	protected render() {
		const classes = {
			card: true,
			[this.variant]: true,
			interactive: this.interactive,
		}
 
		return html`
			<article
				class=${classMap(classes)}
				role=${ifDefined(this.interactive ? "button" : "group")}
				tabindex=${ifDefined(this.interactive ? "0" : undefined)}
				@click=${this.handleClick}
				@keydown=${this.handleKeydown}
			>
				${this.heading ? html`<header class="card__header">${this.heading}</header>` : null}
				<slot name="header"></slot>
				<div class="card__body">
					<slot></slot>
				</div>
				<slot name="footer"></slot>
			</article>
		`
	}
 
	private handleClick(event: MouseEvent) {
		Iif (I!this.interactive) {
			return
		}
 
		this.dispatchAction(event)
	}
 
	private handleKeydown(event: KeyboardEvent) {
		Iif (I!this.interactive) {
			return
		}
 
		Iif (event.key === "Enter" || event.key === " ") {
			event.preventDefault()
			this.dispatchAction(event)
		}
	}
 
	private dispatchAction(event: MouseEvent | KeyboardEvent) {
		this.dispatchEvent(new CustomEvent<CardActionDetail>("vg-action", {
			detail: { originalEvent: event },
			// bubbles: true,
			composed: true,
		}))
	}
}
 
declare global {
	interface HTMLElementTagNameMap {
		"vg-card": VgCard
	}
}