Files
WRNexusJS/packages/ui/components/ChatBubble.wrn
T
2026-07-29 12:51:10 +05:30

148 lines
4.7 KiB
Plaintext

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>
}
}