component ChatBubble { outputs { action(payload: { action: boolean; item: string | number | boolean | null | object; index: number }) messageClick(payload: { item: string | number | boolean | null | object; index: number; direction: string }) avatarClick(payload: { item: string | number | boolean | null | object; index: number; direction: string }) linkClick(payload: { link: string | number | boolean | null | object; item: string | number | boolean | null | object; index: number }) } props { size: string = "default" color: string = "primary" title: string = "" description: string = "" items: unknown[] = [] oneSided: boolean = false showAvatars: boolean = false showMetadata: boolean = false ariaLabel: string = "Conversation" variant: string = "default" class: string = "" } functions { shared function messageDirection(item) { return item.direction === "outgoing" ? "outgoing" : "incoming" } // Delivery receipt from item.receipt, or from a status label that names one. shared function receipt(item, value) { value = String(item.receipt || item.status || "").toLowerCase() if (value === "seen") { return "read" } if (value === "sending" || value === "sent" || value === "delivered" || value === "read" || value === "failed" || value === "error") { return value } return "" } shared function receiptLabel(item, value) { value = receipt(item) if (value === "sending") { return "Sending" } if (value === "sent") { return "Sent" } if (value === "delivered") { return "Delivered" } if (value === "read") { return "Read" } if (value === "failed") { return "Not delivered" } if (value === "error") { return "Error" } return "" } client function selectMessage(item, index) { output.messageClick({ item: item, index: index, direction: messageDirection(item) }) } client function selectAvatar(sourceEvent, item, index) { sourceEvent.stopPropagation() output.avatarClick({ item: item, index: index, direction: messageDirection(item) }) } client function selectLink(sourceEvent, link, item, index) { sourceEvent.stopPropagation() output.linkClick({ link: link, item: item, index: index }) } client function selectAction(sourceEvent, item, index) { sourceEvent.stopPropagation() output.action({ action: item.action || "retry", item: item, index: index }) } } view {
{#if title || description}
{#if title}

{title}

{/if} {#if description}

{description}

{/if}
{/if} {#if items.length}
{#each items as item, index}
{#if showAvatars && (item.avatarSrc || item.avatarFallback)} {/if}
{#if item.title}

{item.title}

{/if} {#if item.text}

{item.text}

{/if} {#if item.bullets && item.bullets.length}
    {#each item.bullets as bullet}
  • {bullet}
  • {/each}
{/if} {#if item.links && item.links.length} {/if}
{#if showMetadata && (item.timestamp || item.status || item.receipt || item.actionLabel)}
{#if item.statusIcon}{/if} {#if item.status && (!receipt(item) || receipt(item) === "failed" || receipt(item) === "error")}{item.status}{/if} {#if item.timestamp}{/if} {#if receipt(item)} {#if receipt(item) === "sending"} {:else if receipt(item) === "sent"} {:else if receipt(item) === "delivered" || receipt(item) === "read"} {:else} {/if} {/if} {#if item.actionLabel} {/if}
{/if}
{/each}
{/if}
} style { .wrn-next--chat-bubble.wrn-component--size-sm { --wrn-chat-padding: 0.75rem; font-size: 0.875rem; } .wrn-next--chat-bubble.wrn-component--size-lg { --wrn-chat-padding: 1.25rem; font-size: 1.0625rem; } .wrn-next--chat-bubble { --wrn-chat-color: var(--wrn-component-color, var(--wrn-color-primary)); --wrn-chat-padding: 1rem; display: grid; width: 100%; min-width: 0; gap: 1rem; color: var(--wrn-color-text); } .wrn-next__chat-header h3, .wrn-next__chat-header p, .wrn-next__chat-content h4, .wrn-next__chat-content p { margin: 0; } .wrn-next__chat-header p { margin-top: 0.35rem; color: var(--wrn-color-text-muted); } .wrn-next__chat-thread { display: grid; min-width: 0; gap: 0.875rem; } .wrn-next__chat-message { display: grid; width: min(78%, 34rem); min-width: 0; justify-self: start; gap: 0.3rem; } .wrn-next__chat-message[data-direction="outgoing"] { justify-self: end; } .wrn-next--chat-bubble[data-one-sided="true"] .wrn-next__chat-message { justify-self: start; } .wrn-next__chat-row { display: flex; min-width: 0; align-items: flex-end; gap: 0.5rem; } .wrn-next__chat-message[data-direction="outgoing"] .wrn-next__chat-row { flex-direction: row-reverse; } .wrn-next--chat-bubble[data-one-sided="true"] .wrn-next__chat-message[data-direction="outgoing"] .wrn-next__chat-row { flex-direction: row; } .wrn-next__chat-content { min-width: 0; padding: 0.625rem 0.875rem; border: 1px solid var(--wrn-color-border); border-radius: 1.125rem 1.125rem 1.125rem 0.25rem; background: var(--wrn-color-surface-raised, var(--wrn-color-surface)); box-shadow: var(--wrn-shadow-1); color: var(--wrn-color-text); font-size: 0.875rem; line-height: 1.5; cursor: pointer; } .wrn-next__chat-content:focus-visible { outline: 2px solid var(--wrn-chat-color); outline-offset: 2px; } .wrn-next__chat-message[data-direction="outgoing"] .wrn-next__chat-content { border-color: color-mix(in srgb, var(--wrn-chat-color) 82%, black); border-radius: 1.125rem 1.125rem 0.25rem 1.125rem; background: linear-gradient(180deg, color-mix(in srgb, var(--wrn-chat-color) 94%, white), var(--wrn-chat-color)); box-shadow: var(--wrn-highlight-edge, none), 0 1px 2px color-mix(in srgb, var(--wrn-chat-color) 25%, transparent); color: var(--wrn-color-primary-contrast, #fff); } .wrn-next__chat-message[data-direction="outgoing"] .wrn-next__chat-content :is(h4, p, li, span) { color: inherit; } .wrn-next__chat-content h4 { margin-bottom: 0.65rem; font-size: 1em; } .wrn-next__chat-content p + p, .wrn-next__chat-content p + nav { margin-top: 0.75rem; } .wrn-next__chat-content ul { display: grid; margin: 0.55rem 0 0; padding-inline-start: 1.25rem; gap: 0.35rem; } .wrn-next__chat-content nav { display: grid; margin-top: 0.75rem; gap: 0.3rem; } .wrn-next__chat-content a { color: var(--wrn-color-primary-text, var(--wrn-chat-color)); font-weight: 600; text-decoration: none; } .wrn-next__chat-message[data-direction="outgoing"] .wrn-next__chat-content a { color: inherit; text-decoration: underline; } .wrn-next__chat-avatar { display: grid; flex: 0 0 2rem; width: 2rem; min-height: 2rem; height: 2rem; overflow: hidden; padding: 0; place-items: center; border: 1px solid var(--wrn-color-border); border-radius: 999px; background: color-mix(in srgb, var(--wrn-chat-color) 12%, var(--wrn-color-surface)); color: var(--wrn-color-primary-text, var(--wrn-chat-color)); font: inherit; font-size: 0.72rem; font-weight: 650; cursor: pointer; } .wrn-next__chat-avatar img { width: 100%; height: 100%; object-fit: cover; } .wrn-next__chat-avatar:focus-visible { outline: 2px solid var(--wrn-chat-color); outline-offset: 2px; } .wrn-next__chat-meta { display: flex; align-items: center; gap: 0.3rem; padding-inline: 0.25rem; color: var(--wrn-color-text-subtle, var(--wrn-color-text-muted)); font-size: 0.6875rem; font-variant-numeric: tabular-nums; line-height: 1; } .wrn-next__chat-message[data-direction="outgoing"] .wrn-next__chat-meta { justify-content: flex-end; } .wrn-next__chat-meta[data-tone="danger"] { color: var(--wrn-color-danger-text, var(--wrn-color-danger)); } .wrn-next__chat-meta button { padding: 0; border: 0; background: transparent; color: var(--wrn-color-danger-text, var(--wrn-color-danger)); font: inherit; font-weight: 650; cursor: pointer; text-decoration: underline; text-underline-offset: 0.2em; } @media (max-width: 640px) { .wrn-next__chat-message { width: min(92%, 34rem); } .wrn-next__chat-avatar { flex-basis: 2rem; width: 2rem; min-height: 2rem; } .wrn-next__chat-meta { padding-inline: 0; } } /* Shared component foundation. */ .wrn-component { --wrn-component-color: var(--wrn-color-primary); --wrn-component-scale: 1; margin: 0; color: var(--wrn-color-text); font-family: var(--wrn-font-sans, inherit); } .wrn-component--color-primary { --wrn-component-color: var(--wrn-color-primary); } .wrn-component--color-secondary { --wrn-component-color: var(--wrn-color-secondary); } .wrn-component--color-success { --wrn-component-color: var(--wrn-color-success); } .wrn-component--color-warning { --wrn-component-color: var(--wrn-color-warning); } .wrn-component--color-danger { --wrn-component-color: var(--wrn-color-danger); } .wrn-component--color-info { --wrn-component-color: var(--wrn-color-info); } .wrn-component--size-xs { --wrn-component-scale: 0.78; } .wrn-component--size-sm { --wrn-component-scale: 0.88; } .wrn-component--size-default, .wrn-component--size-md { --wrn-component-scale: 1; } .wrn-component--size-lg { --wrn-component-scale: 1.14; } .wrn-component--size-xl { --wrn-component-scale: 1.28; } .wrn-component:not(.wrn-btn) { font-size: calc(1em * var(--wrn-component-scale)); } .wrn-component :is(a, button, input, select, textarea):focus-visible { outline-color: var(--wrn-component-color); } .wrn-component p { margin: 0; color: var(--wrn-color-text-muted, var(--wrn-color-muted)); line-height: 1.6; } .wrn-next--chat-bubble[data-avatars="true"] .wrn-next__chat-meta { padding-inline: calc(2rem + 0.5rem + 0.25rem); } .wrn-next__chat-receipt { display: inline-flex; align-items: center; color: var(--wrn-color-text-subtle, var(--wrn-color-text-muted)); } .wrn-next__chat-receipt[data-receipt="read"] { color: var(--wrn-color-info, var(--wrn-color-primary)); } .wrn-next__chat-receipt:is([data-receipt="failed"], [data-receipt="error"]) { color: var(--wrn-color-danger); } .wrn-next__chat-receipt[data-receipt="sending"] svg { animation: wrn-chat-sending 1.6s linear infinite; } @keyframes wrn-chat-sending { to { transform: rotate(360deg); } } @media (prefers-reduced-motion: reduce) { .wrn-next__chat-receipt[data-receipt="sending"] svg { animation: none; } } } }