AgendaList
Events grouped by day with the date at the left, each with its time, title and place; today and tomorrow named.
Demo
Today 4 November
Tomorrow 5 November
Friday 6 November
Saturday 7 November
Nothing planned
Press an event.
No events this week.
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/agenda_list.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/agenda_list.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/agenda_list.qwe
let agenda_list_demo_events = [ { id: 3, date: "2026-11-06", start: "16:00", end: "17:00", title: "Release", place: "Online", href: "#agenda_list", tone: "success" }, { id: 1, date: "2026-11-04", start: "09:00", end: "09:15", title: "Stand-up" }, { id: 2, date: "2026-11-04", title: "Ana out", tone: "neutral" }, { id: 4, date: "2026-11-04", start: "14:00", end: "15:30", title: "Design review", place: "Room 2", tone: "warning" }, { id: 5, date: "2026-11-05", start: "11:00", title: "Dentist", place: "Rua Augusta 10", tone: "danger" },]component AgendaListDemoLive() { state opened = null <div.demo-stack> <AgendaList today="2026-11-04" days={["2026-11-07"]} events={agenda_list_demo_events} on_open={(id) => { opened = id }}/> <p.demo-note>{opened == null ? "Press an event." : `Opened event ${opened}.`}</p> </div>}view AgendaListDemo() = <div.demo-stack> <AgendaListDemoLive/> <div id="agenda-list-demo-empty"><AgendaList events={[]} empty="No events this week."/></div> </div>Source
ui/agenda_list.qwe
// AgendaList: what is coming, as a list grouped by day: each day a heading with its// date, and under it the events in time order with their time, title and place.// Today is marked, and a day with nothing can be shown as such.//// <AgendaList today="2026-11-04" events={[// { id: 1, date: "2026-11-04", start: "09:00", end: "09:15", title: "Stand-up" },// { id: 2, date: "2026-11-04", title: "Ana out" },// { id: 3, date: "2026-11-06", start: "16:00", title: "Release", place: "Online", href: "/events/3", tone: "success" },// ]}/>//// events: a list of { date ("YYYY-MM-DD"), title, start? ("HH:MM"), end?, id?, place?,// tone?, href? }; one with no start is all day. They are grouped by date and sorted.// tone: "accent" (default), "success", "warning", "danger", "info" or "neutral", the// colour of the mark before the event. href makes the event a link; otherwise, inside// one of your components, on_open(id) is called with its id when it is pressed.// today: the day marked "Today" (tomorrow is "Tomorrow"). days: dates to show even with// nothing on them ("Nothing planned"). empty: what it says with no events at all.// label: what screen readers call the list ("Agenda").import ui/theme.qwe use ui_clslet agenda_list_css = css`.ui-agenda-list { display: grid; gap: 18px; min-width: 0; margin: 0; padding: 0; list-style: none; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-agenda-list-day { display: grid; grid-template-columns: 56px minmax(0, 1fr); gap: 0 14px; }.ui-agenda-list-date { display: grid; justify-items: center; align-content: start; gap: 1px; padding-top: 2px; color: var(--ui-muted); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; }.ui-agenda-list-date b { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%; color: var(--ui-fg); font-size: 18px; font-weight: 600; letter-spacing: 0; }.ui-agenda-list-today .ui-agenda-list-date b { background: var(--ui-accent); color: var(--ui-accent-fg); }.ui-agenda-list-today .ui-agenda-list-date { color: var(--ui-accent); }.ui-agenda-list-events { display: grid; gap: 4px; margin: 0; padding: 0; list-style: none; }.ui-agenda-list-title { grid-column: 2; margin: 0 0 6px; font-size: 13px; font-weight: 600; }.ui-agenda-list-title span { color: var(--ui-muted); font-weight: 400; }.ui-agenda-list-event { display: grid; grid-template-columns: 96px minmax(0, 1fr); align-items: start; gap: 10px; width: 100%; padding: 8px 10px; border: 0; border-radius: var(--ui-radius-sm); background: var(--ui-subtle); color: var(--ui-fg); font: inherit; text-align: left; text-decoration: none; --t: var(--ui-accent);}a.ui-agenda-list-event, button.ui-agenda-list-event { cursor: pointer; }a.ui-agenda-list-event:hover, button.ui-agenda-list-event:hover { background: color-mix(in srgb, var(--t) 12%, var(--ui-subtle)); }a.ui-agenda-list-event:focus-visible, button.ui-agenda-list-event:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-agenda-list-time { display: flex; align-items: center; gap: 8px; color: var(--ui-muted); font-size: 13px; font-variant-numeric: tabular-nums; white-space: nowrap; }.ui-agenda-list-time::before { content: ""; flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--t); }.ui-agenda-list-what { display: grid; gap: 1px; min-width: 0; }.ui-agenda-list-what b { font-weight: 600; }.ui-agenda-list-place { color: var(--ui-muted); font-size: 13px; }.ui-agenda-list-success { --t: var(--ui-success); }.ui-agenda-list-warning { --t: var(--ui-warning); }.ui-agenda-list-danger { --t: var(--ui-danger); }.ui-agenda-list-info { --t: var(--ui-info); }.ui-agenda-list-neutral { --t: var(--ui-strong); }.ui-agenda-list-none { padding: 8px 10px; color: var(--ui-muted); font-size: 13px; }.ui-agenda-list-empty { margin: 0; color: var(--ui-muted); }@media (max-width: 480px) { .ui-agenda-list-event { grid-template-columns: 1fr; gap: 2px; } }`view AgendaList(events: list = [], today: str? = null, days: list = [], empty: str = "Nothing planned", label: str = "Agenda", on_open = null, class: str? = null) { style global agenda_list_css let dates = (events | map((e) => e.date)) + days | unique | sort let tomorrow = today != null ? date.add_days(today, 1) : null len(dates) == 0 ? <p class={ui_cls(["ui-agenda-list-empty", class])}>{empty}</p> : <ol class={ui_cls(["ui-agenda-list", class])} aria-label={label}> {for d in dates => { let mine = events | filter((e) => e.date == d) | sort_by((e) => e.start ?? "") let word = d == today ? "Today" : (d == tomorrow ? "Tomorrow" : null) <li key={d} class={ui_cls(["ui-agenda-list-day", d == today && "ui-agenda-list-today"])}> <div.ui-agenda-list-date aria-hidden="true"><span>{date.format(d, "ddd")}</span><b>{date.parts(d).day}</b></div> <h3.ui-agenda-list-title>{word ?? date.format(d, "dddd")} <span>{date.format(d, "D MMMM")}</span></h3> {len(mine) == 0 ? <div.ui-agenda-list-none>{empty}</div> : <ul.ui-agenda-list-events> {for e in mine => { let when = e.start != null ? (e.end != null ? `${e.start}–${e.end}` : e.start) : "All day" let cls = ui_cls(["ui-agenda-list-event", e.tone != null && e.tone != "accent" && `ui-agenda-list-${e.tone}`]) let body = [<span.ui-agenda-list-time>{when}</span>, <span.ui-agenda-list-what><b>{e.title}</b>{e.place != null && <span.ui-agenda-list-place>{e.place}</span>}</span>] <li key={e.id ?? e.title}> {e.href != null ? <a class={cls} href={e.href} data-id={e.id}>{body}</a> : (on_open != null ? <button class={cls} type="button" data-id={e.id} @click={() => { on_open(e.id) }}>{body}</button> : <div class={cls} data-id={e.id}>{body}</div>)} </li> }} </ul>} </li> }} </ol>}Related
DayViewOne day as a column of hours with its events drawn at their times, side by side when they overlap, and all-day events above.7 views · 7 downloadsEventCardAn event as a card: a date block, title, time, place, a line about it, an optional picture and badge, and a button.7 views · 6 downloadsWeekViewA week of hour columns with events drawn at their times, side by side when they overlap, and all-day events across the top.5 views · 6 downloadsAvailabilityGridDays across and hours down: click or drag across the cells to mark when you are available; the slots go in a hidden input.7 views · 7 downloadsOpeningHoursA week's opening hours with today marked and whether it is open now, closing soon, or when it opens next.6 views · 6 downloadsRelativeTime"3 minutes ago" or "in 2 hours" from an ISO time, kept up to date in the browser, with the full date on hover.6 views · 4 downloads
Thread
Thread 0 posts
On AgendaList: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.