Chat thread
betaThe canonical message bubble: incoming/outgoing, attachments inside, meta (avatar, name, time, delivery state, actions) below. Used by chats and the e-mail conversation view.
Canon 17 Oct 2026 (owner decision): bubble from prototype proto/mail-konverzace@6355a0399 (.kvb-*) replaces the K7-4 chat bubble (accent-tint, meta inside). Incoming --line-100, outgoing solid --accent; --radius-xl with --radius-xs sender-side corner; padding --sp-3 --sp-5; attachments inside under the text; meta below; max 80% (phone 85%). Voice/video outgoing stays on --accent-tint (follow-up). History: K7-4 (acrm !4447), AD review 2 Oct 2026, sending/failed states (!4514), published = meta text only (13 Oct 2026). Registry repaired 17 Oct 2026 (review F2): removed classes with no CSS definition and no TSX use on main: .cht-bubble, .cht-atts, .cht-att, .cht-who, .cht-link. Registry repaired 17 Oct 2026 (review F3): mapped existing Ladle stories.
Used in 2 files · 2 areas · 3× — components/mail, pages/komunikace
Default
export const Default = () => (
<div style={{ maxWidth: 420 }}>
<ChatThread ariaLabel="Konverzace s Janem Novákem">
<ChatBubble
text="Dobrý den, technici dorazí v pondělí kolem 7:30."
time="11:02"
out={false}
fromLabel="Jan Novák"
/>
<ChatBubble
text="Díky za potvrzení, budeme připraveni."
time="11:05"
out
fromLabel="Já"
state="read"
stateLabel="přečteno"
/>
</ChatThread>
</div>
);Variants
export const Variants = () => (
<div style={{ maxWidth: 420 }}>
<ChatThread ariaLabel="Varianty bubliny">
<ChatBubble
text="Příchozí — protistrana, vlevo, ostrý roh vlevo dole."
time="9:12"
out={false}
fromLabel="Jan Novák"
/>
<ChatBubble
text="Odchozí — naše strana, vpravo, ostrý roh vpravo dole."
time="9:13"
out
fromLabel="Já"
/>
<ChatBubble
text="S avatarem a jménem pod bublinou (skupina, e-mail)."
time="9:14"
out={false}
fromLabel="Karel Horák"
senderName="Karel Horák"
avatar={<Avatar name="Karel Horák" size="inline" />}
/>
<ChatBubble
text={
'Víceřádková zpráva zalamuje řádky.\nA dlouhé slovo bez mezer: Nejnepravděpodobnějšíhonadlouhého-nepřerušitelného-slova.'
}
time="10:02"
out={false}
fromLabel="Karel Horák"
/>
</ChatThread>
</div>
);Pending — no build carries this story yet.
States
export const States = () => (
<div style={{ maxWidth: 420 }}>
<ChatThread ariaLabel="Stavy doručení">
{(
[
['sending', 'Odesílá se…'],
['sent', 'odesláno'],
['delivered', 'doručeno'],
['read', 'přečteno'],
] as const
).map(([state, label]) => (
<ChatBubble
key={state}
text={`Posílám upravenou nabídku — ${label}.`}
time="9:14"
out
fromLabel="Já"
state={state}
stateLabel={label}
/>
))}
<ChatBubble
text="Posílám upravenou nabídku — neodesláno."
time="9:14"
out
fromLabel="Já"
state="failed"
stateLabel="Neodesláno — Telegram žádá pauzu, zkus to za 30 s."
errorText="Telegram žádá pauzu, zkus to za 30 s."
/>
</ChatThread>
</div>
);Pending — no build carries this story yet.
Pending — no build carries this story yet.
Composition
Dobrý den,
posíláme upravenou nabídku včetně harmonogramu. Potvrďte prosím do pátku.
Potvrzujeme. Termín nám vyhovuje.
Dlouhá zpráva se zkrátí na pevný počet řádků a zbytek je za přepínačem uvnitř bubliny. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Moje dlouhá odpověď má v modré bublině bílý podtržený přepínač. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
export const Composition = () => {
const [full, setFull] = useState(false);
const [fullOut, setFullOut] = useState(false);
const ref = useRef<HTMLDivElement>(null);
return (
<div style={{ maxWidth: 520 }}>
<ChatThread ariaLabel="Konverzace — e-mail">
<span className="cht-day">Pátek 16. 10.</span>
<ChatBubble
rich
text={
<>
<p>Dobrý den,</p>
<p>
posíláme <b>upravenou nabídku</b> včetně harmonogramu. Potvrďte prosím do pátku.
</p>
</>
}
time="16:40"
out={false}
fromLabel="Jan Novák"
senderName="Jan Novák"
avatar={<Avatar name="Jan Novák" size="inline" />}
attachments={<span className="cht-att">nabidka.pdf</span>}
actions={
<button type="button" className="cht-link">
Původní
</button>
}
/>
<ChatBubble
rich
text={
<>
<p>
<b>Potvrzujeme.</b> Termín nám vyhovuje.
</p>
</>
}
time="16:52"
out
fromLabel="Já"
state="sent"
stateLabel="odesláno"
actions={
<button type="button" className="cht-link">
Původní
</button>
}
/>
<ChatBubble
rich
textRef={ref}
clampLines={3}
expanded={full}
text={
<p>
Dlouhá zpráva se zkrátí na pevný počet řádků a zbytek je za přepínačem uvnitř bubliny.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
</p>
}
time="17:05"
out={false}
fromLabel="Karel Horák"
senderName="Karel Horák"
avatar={<Avatar name="Karel Horák" size="inline" />}
more={
<button
type="button"
className="cht-more"
aria-expanded={full}
onClick={() => setFull((v) => !v)}
>
{full ? 'Zobrazit méně' : 'Zobrazit vše'}
</button>
}
actions={
<button type="button" className="cht-link">
Původní
</button>
}
/>
<ChatBubble
rich
clampLines={2}
expanded={fullOut}
text={
<p>
Moje dlouhá odpověď má v modré bublině bílý podtržený přepínač. Lorem ipsum dolor sit
amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris.
</p>
}
time="17:20"
out
fromLabel="Já"
state="sent"
stateLabel="odesláno"
more={
<button
type="button"
className="cht-more"
aria-expanded={fullOut}
onClick={() => setFullOut((v) => !v)}
>
{fullOut ? 'Zobrazit méně' : 'Zobrazit vše'}
</button>
}
/>
</ChatThread>
</div>
);
};export const WithAttachments = () => (
<div style={{ maxWidth: 420 }}>
<ChatThread ariaLabel="Zprávy s přílohami">
<ChatBubble
text="Posílám smlouvu a přílohu."
time="14:20"
out={false}
fromLabel="Jan Novák"
attachments={
<>
<span className="cht-att">smlouva.pdf</span>
<span className="cht-att">cenik-2026.xlsx</span>
</>
}
/>
<ChatBubble
text="Díky, podepsané posílám zpět."
time="14:31"
out
fromLabel="Já"
state="delivered"
stateLabel="doručeno"
attachments={<Attachment name="smlouva-podepsana.pdf" size={182_400} />}
/>
<ChatBubble
text=""
time="14:32"
out={false}
fromLabel="Jan Novák"
attachments={<span className="cht-att">foto-stavby.jpg</span>}
/>
</ChatThread>
</div>
);