///|
/// The right-aligned bubble of a sent message. Plain text by default;
/// pass `children` to compose rich content (inline media tags and text
/// spans share the same baseline flow). Long messages can be clamped to
/// `collapse_lines` behind a fade with an Expand/Collapse toggle — the
/// caller owns `collapsed`. `on_copy` reveals a copy action below the
/// bubble on hover.
///
/// ```moonbit nocheck
/// chat_bubble(text="Continue")
/// chat_bubble(children=[
/// media_tag(kind=Image, variant=Inline, thumbnail=thumb),
/// @html.text("Add more detail"),
/// ])
/// chat_bubble(text=long, collapsible=true, collapsed=c, on_toggle=set_c.map(v => _ => !v))
/// ```
pub fn chat_bubble(
text? : String,
children? : Array[@html.Html],
collapsible? : Bool = false,
collapsed? : Bool = true,
on_toggle? : @cmd.Cmd,
collapse_lines? : Int = 9,
on_copy? : @cmd.Cmd,
aria_label? : String,
id? : String,
class? : String,
attrs? : @html.Attrs,
style? : Array[String] = [],
) -> @html.Html {
let element_attrs = @internal.sumi_attrs(attrs).data_set(
"slot", "chat-bubble",
)
if collapsible {
ignore(
element_attrs.data_set(
"state",
if collapsed {
"collapsed"
} else {
"expanded"
},
),
)
}
if aria_label is Some(label_text) {
ignore(element_attrs.aria_label(label_text))
}
let body_children : Array[@html.Html] = []
if children is Some(rich) {
body_children.push(
@html.span(
style=[
"display:flex;flex-wrap:wrap;align-items:center;column-gap:8px;overflow:clip;font-size:14px;line-height:24px;color:var(--sumi-text,#ffffff)",
],
attrs=@html.Attrs::build().data_set("slot", "chat-bubble-rich"),
rich,
),
)
} else if text is Some(content) {
let text_style = if collapsible && collapsed {
"margin:0;font-size:14px;line-height:24px;color:var(--sumi-text,#ffffff);white-space:pre-wrap;word-break:break-word;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:\{collapse_lines};overflow:hidden"
} else {
"margin:0;font-size:14px;line-height:24px;color:var(--sumi-text,#ffffff);white-space:pre-wrap;word-break:break-word"
}
let text_children : Array[@html.Html] = [
@html.p(style=[text_style], [@html.text(content)]),
]
if collapsible && collapsed {
text_children.push(
@html.span(
style=[
"position:absolute;left:0;right:0;bottom:0;height:48px;background:linear-gradient(to top,var(--sumi-elevated,#222222) 50%,transparent);pointer-events:none",
],
attrs=@html.Attrs::build()
.data_set("slot", "chat-bubble-fade")
.aria_hidden("true"),
([] : Array[@html.Html]),
),
)
}
body_children.push(
@html.span(style=["position:relative;display:block"], text_children),
)
}
if collapsible && on_toggle is Some(toggle) {
let toggle_children : Array[@html.Html] = [
@html.text(if collapsed { "Expand" } else { "Collapse" }),
]
if !collapsed {
toggle_children.push(@primitives.icon_chevron_up_fill(size=14))
}
body_children.push(
@html.button(
style=[
@internal.SumiBoxSizing,
@internal.SumiFontSans,
@internal.SumiTextRendering,
@internal.SumiTransition,
"display:inline-flex;align-items:center;gap:4px;margin:0;padding:0;border:none;background:transparent;font-size:14px;line-height:22px;color:var(--sumi-text-secondary,rgba(255,255,255,0.7));cursor:pointer;appearance:none;-webkit-appearance:none;outline:none",
],
type_="button",
on_click=toggle,
attrs=@html.Attrs::build().data_set("slot", "chat-bubble-toggle"),
toggle_children,
),
)
}
let outer_children : Array[@html.Html] = [
@html.span(
style=[
"display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:8px;max-width:640px;padding:12px 16px;border-radius:16px;background:var(--sumi-block,rgba(255,255,255,0.08))",
],
attrs=@html.Attrs::build().data_set("slot", "chat-bubble-body"),
body_children,
),
]
if on_copy is Some(copy) {
outer_children.push(
@html.button(
style=[
@internal.SumiBoxSizing,
@internal.SumiTextRendering,
@internal.SumiTransition,
"position:absolute;bottom:-40px;right:0;display:flex;align-items:center;justify-content:center;width:36px;height:36px;margin:0;padding:0;border:none;border-radius:8px;background:transparent;color:var(--sumi-text-placeholder,rgba(255,255,255,0.35));cursor:pointer;appearance:none;-webkit-appearance:none;outline:none;opacity:0;pointer-events:none",
],
type_="button",
on_click=copy,
attrs=@html.Attrs::build()
.data_set("slot", "chat-bubble-copy")
.aria_label("Copy message"),
[@primitives.icon_copy(size=16)],
),
)
}
@html.div(
style=@internal.sumi_styles(
[
@internal.SumiBoxSizing,
@internal.SumiFontSans,
@internal.SumiTextRendering,
"position:relative;display:flex;flex-direction:column;align-items:flex-end;gap:4px;width:100%",
],
style,
),
id?,
class?,
attrs=element_attrs,
outer_children,
)
}