import { html, nothing } from 'lit'; import { property } from 'lit/decorators.js'; import { BootstrapElement, defineElement, type Variant } from '@bootstrap-wc/core'; /** * `` — Bootstrap card container. * * The host IS the `.card`. Bootstrap's `.card`, `.border-{variant}`, and * `.text-bg-{variant}` classes are mirrored onto the host (via `hostClasses`) * so that: * 1. The host fills its grid cell / parent like a native `
` * would (display, background, border, radius all picked up from * Bootstrap's own selectors). * 2. Bootstrap's `.card > .card-body`, `.card > .card-img-top`, etc. * child-combinator selectors continue to match across the shadow * boundary, because the shadow root has no wrapping element — every * shadow node and every projected child is a flat-tree child of the * host. * * @slot - Card body content. * @slot header - Rendered in `.card-header`. * @slot footer - Rendered in `.card-footer`. * @slot image - Image rendered above the body (`.card-img-top`). * @slot image-bottom - Image rendered after the footer (`.card-img-bottom`). * @slot img-overlay - Content rendered inside `.card-img-overlay` on top of the image. */ export class BsCard extends BootstrapElement { @property({ type: String }) variant?: Variant; @property({ type: String, attribute: 'text-variant' }) textVariant?: Variant; @property({ type: String, attribute: 'heading' }) heading?: string; @property({ type: String }) subtitle?: string; @property({ type: Boolean, attribute: 'no-body' }) noBody = false; @property({ type: Boolean }) horizontal = false; protected override hostClasses(): string { const parts = ['card']; if (this.variant) { parts.push(`border-${this.variant}`); parts.push(`text-bg-${this.variant}`); } if (this.textVariant) parts.push(`text-${this.textVariant}`); return parts.join(' '); } override render() { const hasImg = !!this.querySelector('[slot="image"]'); const hasImgBottom = !!this.querySelector('[slot="image-bottom"]'); const hasHeader = !!this.querySelector('[slot="header"]'); const hasFooter = !!this.querySelector('[slot="footer"]'); const hasOverlay = !!this.querySelector('[slot="img-overlay"]'); const body = this.noBody ? html`` : html`
${this.heading ? html`
${this.heading}
` : nothing} ${this.subtitle ? html`
${this.subtitle}
` : nothing}
`; if (this.horizontal) { // Row-based horizontal layout. Image slot becomes left column, body + header/footer stack on the right. return html`
${hasImg ? html`
` : nothing}
${hasHeader ? html`
` : nothing} ${body} ${hasFooter ? html`` : nothing}
`; } return html` ${hasImg ? html`` : nothing} ${hasOverlay ? html`
` : nothing} ${hasHeader ? html`
` : nothing} ${body} ${hasFooter ? html`` : nothing} ${hasImgBottom ? html`` : nothing} `; } } defineElement('bs-card', BsCard); declare global { interface HTMLElementTagNameMap { 'bs-card': BsCard; } }