Social

Hashtag

A #tag that links to its page, with an optional count, and HashtagList, the trending tags ranked with their post counts.

#social#link#list#no-js

Demo

Preview

Shipped the new tables today #quillo #SQLite310 #release notes

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/hashtag.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/hashtag.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/hashtag.qwe
view HashtagDemo() =    <div.demo-stack>        <p.demo-note id="hashtag-demo-line" style="margin: 0; color: var(--ui-fg); font-size: 14px">            Shipped the new tables today <Hashtag tag="quillo"/> <Hashtag tag="SQLite" count={310}/> <Hashtag tag="release notes" tone="neutral" size="sm"/>        </p>        <div.demo-narrow>            <HashtagList title="Trending" base="#hashtag-" tags={[                { tag: "quillo", count: 2400, trend: "up" }, { tag: "sqlite", count: 1200000 },                { tag: "rust", count: 980, trend: "down" }, { tag: "postgres", count: 310, href: "#hashtag" }]}/>        </div>    </div>

Source

ui/hashtag.qwe
// Hashtag: a #tag as a link to its page, and HashtagList, the tags trending now, each// with how many posts.////   <Hashtag tag="quillo"/>                                    #quillo → /tags/quillo//   <Hashtag tag="Rust" base="/explore/" count={1200}/>         #Rust · 1.2K//   <HashtagList title="Trending" tags={[{ tag: "quillo", count: 2400 }, { tag: "sqlite", count: 310 }]}/>//// tag: the word, without the #. base: the start of its link ("/tags/"); href: the whole// link instead. count: how many posts carry it, shown after it (1200 reads 1.2K).// tone: "accent" (default) or "neutral". size: "sm" or "md".// HashtagList: tags is a list of { tag, count?, href?, trend? ("up", "down") }, shown// in order with their rank; title: a heading above them; base as for Hashtag; label:// what screen readers call the list (the title, or "Hashtags").import ui/theme.qwe use ui_clslet hashtag_css = css`.ui-hashtag { display: inline-flex; align-items: baseline; gap: 5px; color: var(--ui-accent); font: 500 14px/1.4 var(--ui-font); text-decoration: none; border-radius: 3px; }.ui-hashtag:hover { text-decoration: underline; }.ui-hashtag:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-hashtag-neutral { color: var(--ui-fg); }.ui-hashtag-count { color: var(--ui-muted); font-size: .85em; font-weight: 400; font-variant-numeric: tabular-nums; }.ui-hashtag-sm { font-size: 13px; }.ui-hashtag-list { display: grid; gap: 4px; min-width: 0; padding: 14px 16px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-hashtag-list h3 { margin: 0 0 6px; font-size: 15px; font-weight: 700; }.ui-hashtag-list ol { margin: 0; padding: 0; list-style: none; counter-reset: ui-hashtag; }.ui-hashtag-item { display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; align-items: center; gap: 8px; padding: 6px 0; border-top: 1px solid var(--ui-border); counter-increment: ui-hashtag; }.ui-hashtag-item:first-child { border-top: 0; }.ui-hashtag-item::before { content: counter(ui-hashtag); color: var(--ui-muted); font-size: 12px; font-variant-numeric: tabular-nums; }.ui-hashtag-item .ui-hashtag { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.ui-hashtag-posts { color: var(--ui-muted); font-size: 12px; white-space: nowrap; }.ui-hashtag-trend { display: inline-block; width: 12px; height: 12px; margin-right: 3px; vertical-align: -2px; }.ui-hashtag-up { color: var(--ui-success); }.ui-hashtag-down { color: var(--ui-danger); }`// A count as it reads beside a tag: 950 is "950", 1200 is "1.2K", 2500000 is "2.5M".fn hashtag_count(n: int) -> str {    if n < 1000 { return str(n) }    let unit = n < 1000000 ? 1000 : 1000000    let tenths = floor(n * 10 / unit)    `${floor(tenths / 10)}${tenths % 10 == 0 ? "" : `.${tenths % 10}`}${unit == 1000 ? "K" : "M"}`}view Hashtag(tag: str, base: str = "/tags/", href: str? = null, count: int? = null, tone: str = "accent",             size: str = "md", class: str? = null) {    style global hashtag_css    <a class={ui_cls(["ui-hashtag", tone != "accent" && `ui-hashtag-${tone}`, size == "sm" && "ui-hashtag-sm", class])}       href={href ?? `${base}${url_encode(tag)}`}>        #{tag}        {count != null && <span.ui-hashtag-count aria-label={`${count} posts`}>{hashtag_count(count)}</span>}    </a>}view HashtagList(tags: list, title: str? = null, base: str = "/tags/", label: str? = null, class: str? = null) {    style global hashtag_css    <nav class={ui_cls(["ui-hashtag-list", class])} aria-label={label ?? title ?? "Hashtags"}>        {title != null && <h3>{title}</h3>}        <ol>            {for t in tags => <li.ui-hashtag-item>                <Hashtag tag={t.tag} base={base} href={t.href}/>                {t.count != null && <span.ui-hashtag-posts>                    {t.trend == "up" && <svg.ui-hashtag-trend.ui-hashtag-up viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" role="img" aria-label="rising"><path d="M2 9l3.5-3.5 2 2L10 4"/><path d="M7 4h3v3"/></svg>}                    {t.trend == "down" && <svg.ui-hashtag-trend.ui-hashtag-down viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" role="img" aria-label="falling"><path d="M2 3l3.5 3.5 2-2L10 8"/><path d="M7 8h3V5"/></svg>}                    {hashtag_count(t.count)} posts                </span>}            </li>}        </ol>    </nav>}
Thread
68Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.