import { css, html } from 'lit'; import { property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { BootstrapElement, defineElement, type Variant } from '@bootstrap-wc/core'; /** * Numeric avatar size (matches the bootstrap-essentials `.avatar-{N}` scale). * Custom sizes can be set via the `size` attribute as a number (e.g. `40`) * or via the `width` / `height` attributes (any CSS length). */ export type AvatarSize = 16 | 24 | 32 | 48 | 64 | 96 | 128 | number; export type AvatarShape = 'circle' | 'rounded' | 'square'; /** * `` — display a user's profile picture, initials, or icon * inside a sized, optionally-rounded box. * * The component matches the avatar size scale from bootstrap-essentials * (`16/24/32/48/64/96/128`) plus arbitrary numeric sizes, and the three * BS5 shape variants (`circle`/`rounded`/`square`). When `src` is set the * image is rendered with `object-fit: cover`; otherwise the default slot * is shown (typically initials or a ``). * * @slot default - Placeholder content (initials, icon) when no `src`. * @slot status - A status indicator (e.g. online dot) absolutely positioned * in the bottom-right corner. Author can place a * `` or any element here. * * @csspart wrapper - The host's inner box (sized via the `size` attribute). * @csspart image - The `` element when `src` is set. * * @example * * JD * * * */ export class BsAvatar extends BootstrapElement { static override styles = css` :host { display: inline-block; vertical-align: middle; line-height: 1; position: relative; } [part='wrapper'] { display: inline-flex; align-items: center; justify-content: center; overflow: hidden; width: 100%; height: 100%; background: var(--bs-tertiary-bg, #e9ecef); color: var(--bs-body-color, #212529); font-weight: 600; user-select: none; border-radius: inherit; } [part='image'] { width: 100%; height: 100%; object-fit: cover; display: block; } [part='status'] { position: absolute; right: 0; bottom: 0; width: 28%; height: 28%; min-width: 8px; min-height: 8px; border: 2px solid var(--bs-body-bg, #fff); border-radius: 50%; box-sizing: border-box; } /* Variant tinting for initial-only avatars (no image). */ :host([variant]) [part='wrapper'] { background-color: var(--bs-tertiary-bg); } `; /** Size in pixels. Accepts the BSE numeric scale (16/24/32/48/64/96/128) or any number. */ @property({ type: Number }) size: AvatarSize = 32; /** Shape: circle, rounded (4px corners), or square (no rounding). */ @property({ type: String, reflect: true }) shape: AvatarShape = 'circle'; /** Image URL. When set, the `` is rendered instead of the default slot. */ @property({ type: String }) src?: string; /** Alt text for the image (required when `src` is set; recommended otherwise via `aria-label`). */ @property({ type: String }) alt = ''; /** Tints the wrapper with `bg-{variant}-subtle` + `text-{variant}-emphasis`. */ @property({ type: String }) variant?: Variant; /** Override the host's width (any CSS length). Takes precedence over `size`. */ @property({ type: String }) width?: string; /** Override the host's height (any CSS length). Takes precedence over `size`. */ @property({ type: String }) height?: string; /** * Apply size + shape directly to the host. We can't put dynamic values in * `static styles` (they're frozen at class-eval time), so write inline * styles on the host element via attributeChangedCallback / updated. */ override updated(changed: Map): void { super.updated(changed); if (changed.has('size') || changed.has('width') || changed.has('height')) { const px = `${this.size}px`; this.style.width = this.width ?? px; this.style.height = this.height ?? px; } if (changed.has('shape')) { this.style.borderRadius = this.shape === 'circle' ? '50%' : this.shape === 'square' ? '0' : 'var(--bs-border-radius, .375rem)'; } } override render() { const wrapperClasses = classMap({ [`bg-${this.variant}-subtle`]: !!this.variant, [`text-${this.variant}-emphasis`]: !!this.variant, }); return html` ${this.src ? html`${this.alt}` : html``} `; } } defineElement('bs-avatar', BsAvatar); declare global { interface HTMLElementTagNameMap { 'bs-avatar': BsAvatar; } }