Scheduling

AgendaList

Events grouped by day with the date at the left, each with its time, title and place; today and tomorrow named.

#schedule#calendar#list#time#no-js

Demo

Preview
  1. Today 4 November

  2. Tomorrow 5 November

  3. Friday 6 November

  4. 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.qwe
main.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>}
Thread
66Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.