ChatBubble
Theme-aware, responsive chat bubble component.
Usage
Components are auto-discovered. Use the component directly in any .wrn view:
<ChatBubble
size="default"
color="primary"
title="title"
description="description"
items="[]"
/>Legacy mount name: data-component="ChatBubble".
Props and attributes
| Prop | Type | Requirement | Default |
|---|---|---|---|
size | string | Optional | "default" |
color | string | Optional | "primary" |
title | string | Optional | "" |
description | string | Optional | "" |
items | string | Optional | [] |
oneSided | boolean | Optional | false |
showAvatars | boolean | Optional | false |
showMetadata | boolean | Optional | false |
ariaLabel | string | Optional | "Conversation" |
variant | string | Optional | "default" |
class | string | Optional | "" |
Slots
default
Events
actionmessageClickavatarClicklinkClick
Source contract
Installed source: components/ChatBubble.wrn
component ChatBubble {
props {
@event action = function
@event messageClick = function
@event avatarClick = function
@event linkClick = function
size = "default"
color = "primary"
title = ""
description = ""
items = []
oneSided = false
showAvatars = false
showMetadata = false
ariaLabel = "Conversation"
variant = "default"
class = ""
}
functions {
function messageDirection(item) {
return item.direction === "outgoing" ? "outgoing" : "incoming"
}
function selectMessage(item, index) {
$emit("messageClick", {
item: item,
index: index,
direction: messageDirection(item)
})
}
function selectAvatar(sourceEvent, item, index) {
sourceEvent.stopPropagation()
$emit("avatarClick", {
item: item,
index: index,
direction: messageDirection(item)
})
}
function selectLink(sourceEvent, link, item, index) {
sourceEvent.stopPropagation()
$emit("linkClick", {
link: link,
item: item,
index: index
})
}
function selectAction(sourceEvent, item, index) {
sourceEvent.stopPropagation()
$emit("action", {
action: item.action || "retry",
item: item,
index: index
})
}
}
view {
<section
{...attrs}
class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--chat-bubble wire-next--variant-{variant} {class}"
data-one-sided="{oneSided}"
role="log"
aria-label="{ariaLabel}"
aria-live="polite"
>
{#if title || description}
<header class="wire-next__chat-header">
{#if title}<h3>{title}</h3>{/if}
{#if description}<p>{description}</p>{/if}
</header>
{/if}
{#if items.length}
<div class="wire-next__chat-thread">
{#each items as item, index}
<article
class="wire-next__chat-message"
data-direction="{messageDirection(item)}"
aria-label="{messageDirection(item) === 'outgoing' ? 'Sent message' : 'Received message'}"
>
<div class="wire-next__chat-row">
{#if showAvatars && (item.avatarSrc || item.avatarFallback)}
<button
class="wire-next__chat-avatar"
type="button"
aria-label="{item.avatarLabel || item.author || 'Message author'}"
@click="selectAvatar(event, item, index)"
>
{#if item.avatarSrc}
<img src="{item.avatarSrc}" alt="{item.avatarAlt || ''}" />
{:else}
<span>{item.avatarFallback}</span>
{/if}
</button>
{/if}
<div
class="wire-next__chat-content"
role="button"
tabindex="0"
@click="selectMessage(item, index)"
@keydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); selectMessage(item, index) }"
>
{#if item.title}<h4>{item.title}</h4>{/if}
{#if item.text}<p>{item.text}</p>{/if}
{#if item.bullets && item.bullets.length}
<ul>
{#each item.bullets as bullet}<li>{bullet}</li>{/each}
</ul>
{/if}
{#if item.links && item.links.length}
<nav aria-label="Message links">
{#each item.links as link}
<a
href="{link.href || '#'}"
@click="selectLink(event, link, item, index)"
>{link.label}</a>
{/each}
</nav>
{/if}
</div>
</div>
{#if showMetadata && (item.timestamp || item.status || item.actionLabel)}
<footer class="wire-next__chat-meta" data-tone="{item.statusTone || 'muted'}">
{#if item.statusIcon}<span class="{item.statusIcon}" aria-hidden="true">
</span>{/if}
{#if item.status}<span>{item.status}</span>{/if}
{#if item.timestamp}<time datetime="{item.datetime || ''}">{item.timestamp}</time>{/if}
{#if item.actionLabel}
<button type="button" @click="selectAction(event, item, index)">
{item.actionLabel}
</button>
{/if}
</footer>
{/if}
</article>
{/each}
</div>
{/if}
<slot />
</section>
}
}