Social

TypingIndicator

"Ana is typing…" with pulsing dots, naming one, two or more people, and nothing at all when nobody is.

#social#chat#status#no-js

Demo

Preview
Ana is typing…
Ana Silva, Ben Okafor and Lee Chen are typing…
Support
Sam is looking into it…

Get it

It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.

$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/typing_indicator.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/typing_indicator.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/typing_indicator.qwe
component TypingIndicatorDemoLive() {    state who = ["Ana Silva"]    let all = ["Ana Silva", "Ben Okafor", "Lee Chen", "Mia Rossi"]    <div.demo-stack>        <div id="typing-indicator-demo-live"><TypingIndicator names={who} first_names={true}/></div>        <div.demo-row>            {for p in all => <label key={p} style="display: inline-flex; gap: 6px; align-items: center; font-size: 13px">                <input type="checkbox" checked={contains(who, p)} value={p}                       @change={(e) => { who = e.checked ? all | filter((x) => x == p || contains(who, x)) : who | filter((x) => x != p) }}/>                {p}            </label>}        </div>    </div>}view TypingIndicatorDemo() =    <div.demo-stack>        <TypingIndicatorDemoLive/>        <div id="typing-indicator-demo-plain"><TypingIndicator names={["Ana Silva", "Ben Okafor", "Lee Chen"]}/></div>        <div.demo-note id="typing-indicator-demo-inline">Support <TypingIndicator names={["Sam"]} dots={false} text="is looking into it…"/></div>    </div>

Source

ui/typing_indicator.qwe
// TypingIndicator: "Ana is typing…" with three dots that pulse, for the bottom of a// chat. With nobody typing it shows nothing and takes no room, so it can stay in the// page and be given the names as they come.////   <TypingIndicator names={["Ana Silva"]}/>                      Ana Silva is typing…//   <TypingIndicator names={typing} first_names={true}/>           Ana and Ben are typing…//   <TypingIndicator names={["Ana", "Ben", "Lee", "Mia"]}/>        Ana, Ben and 2 others are typing…//// names: who is typing (an empty list shows nothing). first_names: true shows each// person's first word only. dots: false drops the dots (the text alone, inline).// text: the words after the names ("is typing…" / "are typing…" by default; give one// to say something else, "is recording…"). It is a status for screen readers, so// they hear it change.import ui/theme.qwe use ui_clslet typing_indicator_css = css`.ui-typing-indicator { display: flex; align-items: center; gap: 8px; min-height: 26px; color: var(--ui-muted); font: 12px/1.3 var(--ui-font); }.ui-typing-indicator:empty { min-height: 0; }.ui-typing-indicator-dots { display: inline-flex; gap: 3px; padding: 7px 10px; border-radius: 12px; background: var(--ui-subtle); }.ui-typing-indicator-dots i { width: 6px; height: 6px; border-radius: 50%; background: var(--ui-muted); animation: ui-typing-indicator-dot 1.2s infinite ease-in-out; }.ui-typing-indicator-dots i:nth-child(2) { animation-delay: .15s; }.ui-typing-indicator-dots i:nth-child(3) { animation-delay: .3s; }.ui-typing-indicator-inline { display: inline-flex; min-height: 0; }@keyframes ui-typing-indicator-dot { 0%, 60%, 100% { opacity: .35; transform: none; } 30% { opacity: 1; transform: translateY(-2px); } }@media (prefers-reduced-motion: reduce) { .ui-typing-indicator-dots i { animation: none; opacity: .7; } }`// "Ana", "Ana and Ben", "Ana, Ben and Lee", "Ana, Ben and 2 others".fn typing_indicator_who(names: list) -> str {    let n = len(names)    if n == 0 { return "" }    if n == 1 { return names[0] }    if n == 2 { return `${names[0]} and ${names[1]}` }    if n == 3 { return `${names[0]}, ${names[1]} and ${names[2]}` }    `${names[0]}, ${names[1]} and ${n - 2} others`}view TypingIndicator(names: list = [], first_names: bool = false, dots: bool = true, text: str? = null, class: str? = null) {    style global typing_indicator_css    let shown = first_names ? names | map((nm) => split(trim(nm), " ")[0]) : names    let words = text ?? (len(names) == 1 ? "is typing…" : "are typing…")    <div class={ui_cls(["ui-typing-indicator", !dots && "ui-typing-indicator-inline", class])} role="status" aria-live="polite">        {len(names) > 0 && [            dots && <span.ui-typing-indicator-dots aria-hidden="true"><i></i><i></i><i></i></span>,            <span>{typing_indicator_who(shown)} {words}</span>,        ]}    </div>}
Thread
66Useful? Like it, or add to its thread below.

Thread 0 posts

On TypingIndicator: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.