Mobile

CalendarSheet

A month on a sheet that folds to a week by drag, with the day's agenda under it.

#mobile#interactive#gesture#calendar#agenda#tablet

Demo

Preview

November 2026

Today, Wednesday 4 November

Friday 6 November

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

Example

The code of the preview above.

demos/calendar_sheet.qwe
let calendar_sheet_demo_items = [    { id: 1, date: "2026-11-04", time: "09:30", title: "Dentist", where: "Rua Augusta 12", tone: "danger" },    { id: 2, date: "2026-11-04", title: "Ana's birthday", tone: "success" },    { id: 3, date: "2026-11-04", time: "13:00", title: "Lunch with Ana" },    { id: 4, date: "2026-11-06", time: "18:00", title: "Pick up keys", tone: "warning" },    { id: 5, date: "2026-11-12", time: "07:15", title: "Flight to Lisbon", where: "Gate B22", tone: "info" },    { id: 6, date: "2026-12-04", title: "Rent", tone: "warning" },]component CalendarSheetDemoLive() {    state log = ""    <div.demo-stack>        <div.demo-phone id="calendar-sheet-screen">            <CalendarSheet today="2026-11-04" items={calendar_sheet_demo_items}                           on_pick={(d) => { log = `pick ${d}` }} on_open={(m) => { log = `open ${m.id}` }} on_fold={(f) => { log = `fold ${f}` }}/>        </div>        <p.demo-note id="calendar-sheet-log">{log}</p>        // Sunday first, starting folded, and bad data, for the test to look at.        <div id="calendar-sheet-more" hidden="true"><CalendarSheet today="2026-02-10" week_start={7} folded={true} items={[null, 7, { date: "2026-02-30", title: "No" }, { date: "2026-02-10" }, { date: "2026-02-10", title: "Yes", time: "25:99" }]}/><CalendarSheet today="what" items="none" week_start={0}/></div>    </div>}view CalendarSheetDemo() = <div.demo-stack><CalendarSheetDemoLive/></div>

Source

ui/calendar_sheet.qwe
// CalendarSheet: a month on a sheet with the day's agenda under it, as a phone's calendar// has. Dragging the sheet up, by its handle or the days themselves, folds the month to the// week of the chosen day, giving the agenda the room, and dragging down opens it again. A// sideways swipe goes a month on, or a week when folded. The handle is a button, tapped or// used with the arrow keys, there are buttons for the month, and the days take the arrow keys,// Home, End, Page Up and Page Down. On a tablet the month and the agenda stand side by side,// and nothing folds; turned back, a folded month is folded still.////   component Diary() {//       <CalendarSheet today="2026-11-04" items={events}//                      on_pick={(d) => { day = d }} on_open={(item) => { show(item) }}/>//   }//// It fills the height it is given, the agenda scrolling inside it; with none set it is as tall as it needs.// today: "YYYY-MM-DD"; left out, the server's day when it renders. picked: the day chosen to// start with (today). items: each { id, date, title, time? ("HH:MM"), where?, tone? ("accent",// "success", "warning", "danger", "info") }; those without a good date or a title are left// out, and with no time it is all day. week_start: 1 for Monday to 7 for Sunday (1).// folded: starts as a week (false). agenda_days: how many days from the chosen one the agenda// runs to (7). label: its name for screen readers. empty_text: what a day with nothing says. class.// on_pick(date), on_open(item), on_fold(folded): inside one of your components.import ui/theme.qwe use ui_clslet calendar_sheet_css = css`.ui-calendar-sheet { --row: 46px; container-type: inline-size; display: flex; flex-direction: column; height: 100%; min-height: 0; color: var(--ui-fg); font: 15px/1.35 var(--ui-font); background: var(--ui-bg); }.ui-calendar-sheet-inner { display: flex; flex: 1; flex-direction: column; min-height: 0; }.ui-calendar-sheet-sheet { position: relative; z-index: 1; flex: none; padding-top: env(safe-area-inset-top); border-radius: 0 0 calc(var(--ui-radius) + 6px) calc(var(--ui-radius) + 6px); background: var(--ui-surface); box-shadow: var(--ui-shadow); }.ui-calendar-sheet-head { display: flex; align-items: center; gap: 2px; min-height: 46px; padding: 0 6px; }.ui-calendar-sheet-title { flex: 1; min-width: 0; margin: 0; padding-left: 8px; overflow: hidden; font-size: 17px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }.ui-calendar-sheet-btn { display: grid; flex: none; place-items: center; min-width: 44px; min-height: 44px; padding: 0 8px; border: 0; border-radius: var(--ui-radius-sm); background: transparent; color: var(--ui-fg); font: 500 14px/1 var(--ui-font); cursor: pointer; }.ui-calendar-sheet-btn:disabled { color: var(--ui-strong); cursor: default; }.ui-calendar-sheet-btn svg { width: 20px; height: 20px; }.ui-calendar-sheet-btn:focus-visible, .ui-calendar-sheet-day:focus-visible, .ui-calendar-sheet-handle:focus-visible, .ui-calendar-sheet-item:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-calendar-sheet-names { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); padding: 0 6px; color: var(--ui-muted); font-size: 12px; text-align: center; }/* Both ways of a drag are used here, so the page gets neither on the days. */.ui-calendar-sheet-clip { height: calc(var(--row) * var(--rows, 6)); margin: 0 6px; overflow: hidden; touch-action: none; -webkit-touch-callout: none; transition: height .24s cubic-bezier(.2, .8, .2, 1); }.ui-calendar-sheet-grid { transition: transform .24s cubic-bezier(.2, .8, .2, 1); }.ui-calendar-sheet-moving, .ui-calendar-sheet-moving .ui-calendar-sheet-clip, .ui-calendar-sheet-moving .ui-calendar-sheet-grid { transition: none; user-select: none; -webkit-user-select: none; }.ui-calendar-sheet-week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); height: var(--row); }.ui-calendar-sheet-day { display: grid; justify-items: center; align-content: center; gap: 1px; min-width: 0; height: var(--row); padding: 0; border: 0; border-radius: var(--ui-radius); background: transparent; color: var(--ui-fg); font: 500 15px/1 var(--ui-font); cursor: pointer; -webkit-tap-highlight-color: transparent; }.ui-calendar-sheet-day[data-out] { color: var(--ui-muted); font-weight: 400; }.ui-calendar-sheet-num { display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50%; }.ui-calendar-sheet-day[aria-current="date"] .ui-calendar-sheet-num { color: var(--ui-accent); font-weight: 700; }.ui-calendar-sheet-day[aria-pressed="true"] .ui-calendar-sheet-num { background: var(--ui-accent); color: var(--ui-accent-fg); }.ui-calendar-sheet-dots { display: flex; justify-content: center; gap: 2px; height: 5px; }.ui-calendar-sheet-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--ui-accent); }.ui-calendar-sheet-dot[data-tone="success"], .ui-calendar-sheet-bar[data-tone="success"] { background: var(--ui-success); }.ui-calendar-sheet-dot[data-tone="warning"], .ui-calendar-sheet-bar[data-tone="warning"] { background: var(--ui-warning); }.ui-calendar-sheet-dot[data-tone="danger"], .ui-calendar-sheet-bar[data-tone="danger"] { background: var(--ui-danger); }.ui-calendar-sheet-dot[data-tone="info"], .ui-calendar-sheet-bar[data-tone="info"] { background: var(--ui-info); }.ui-calendar-sheet-handle { display: grid; place-items: center; width: 100%; height: 44px; padding: 0; border: 0; border-radius: 0 0 var(--ui-radius) var(--ui-radius); background: transparent; cursor: grab; touch-action: none; -webkit-tap-highlight-color: transparent; }.ui-calendar-sheet-handle span { width: 40px; height: 5px; border-radius: 999px; background: var(--ui-strong); }.ui-calendar-sheet-agenda { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 10px 12px max(14px, env(safe-area-inset-bottom)); }.ui-calendar-sheet-dayhead { margin: 10px 0 4px; color: var(--ui-muted); font-size: 13px; font-weight: 600; }.ui-calendar-sheet-dayhead:first-child { margin-top: 0; }.ui-calendar-sheet-item { display: flex; align-items: stretch; gap: 10px; width: 100%; min-height: 52px; padding: 6px 8px; border: 0; border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: inherit; font: inherit; text-align: left; cursor: pointer; }.ui-calendar-sheet-item + .ui-calendar-sheet-item { margin-top: 4px; }.ui-calendar-sheet-bar { flex: none; width: 4px; border-radius: 2px; background: var(--ui-accent); }.ui-calendar-sheet-time { flex: none; width: 52px; color: var(--ui-muted); font-size: 13px; padding-top: 2px; }.ui-calendar-sheet-what { flex: 1; min-width: 0; }.ui-calendar-sheet-name { display: block; font-weight: 500; overflow-wrap: anywhere; }.ui-calendar-sheet-where { display: block; color: var(--ui-muted); font-size: 13px; overflow-wrap: anywhere; }.ui-calendar-sheet-none { margin: 0; padding: 10px 8px; color: var(--ui-muted); }/* Folded is for a narrow screen alone, so a tablet turned back finds it as it was. */@container (max-width: 639px) {  .ui-calendar-sheet-folded .ui-calendar-sheet-clip { height: var(--row); }  .ui-calendar-sheet-folded .ui-calendar-sheet-grid { transform: translateY(calc(var(--row) * var(--at, 0) * -1)); }}@container (min-width: 640px) {  .ui-calendar-sheet-inner { --row: 60px; flex-direction: row; gap: 16px; }  .ui-calendar-sheet-sheet { flex: 0 0 min(52%, 440px); align-self: flex-start; border-radius: calc(var(--ui-radius) + 6px); padding-bottom: 8px; }  .ui-calendar-sheet-handle { display: none; }  .ui-calendar-sheet-agenda { padding-top: 14px; }}@media (hover: hover) {  .ui-calendar-sheet-btn:hover:not(:disabled), .ui-calendar-sheet-day:hover, .ui-calendar-sheet-item:hover { background: var(--ui-subtle); }}@media (prefers-reduced-motion: reduce) {  .ui-calendar-sheet-clip, .ui-calendar-sheet-grid { transition: none; }}`fn calendar_sheet_first(d: str, week_start: int) -> str {    let back = (date.weekday(d) - week_start + 7) % 7    date.add_days(d, -back)}fn calendar_sheet_start_day(n: int) -> int = n >= 1 && n <= 7 ? n : 1// The weeks a month touches, each seven dates.fn calendar_sheet_weeks(day: str, week_start: int) -> list {    let month = date.start_of_month(day)    let first = calendar_sheet_first(month, week_start)    let gap = date.days_between(first, month)    let rows = int(ceil((gap + date.days_in_month(month)) / 7))    range(0, rows) | map((w) => range(0, 7) | map((i) => date.add_days(first, w * 7 + i)))}// Which of the weeks holds d.fn calendar_sheet_row(weeks: list, d: str) -> int {    let hits = enumerate(weeks) | filter((p) => contains(p[1], d))    len(hits) > 0 ? hits[0][0] : 0}fn calendar_sheet_time_ok(t) -> bool = type_of(t) == "str" && re.test(t, "^([01][0-9]|2[0-3]):[0-5][0-9]$")// What can be shown, in time order, the whole day first.fn calendar_sheet_clean(items) -> list {    if type_of(items) != "list" { return [] }    let good = enumerate(items) | filter((p) => {        let m = p[1]        type_of(m) == "map" && type_of(get(m, "date")) == "str" && date.valid(m.date, "date") && get(m, "title") != null    }) | map((p) => {        let t = get(p[1], "time")        ({ ...p[1], id: get(p[1], "id") != null ? str(p[1].id) : `_${p[0]}`, title: str(p[1].title), time: calendar_sheet_time_ok(t) ? t : null })    })    sort_by(good, (m) => `${m.date} ${m.time != null ? m.time : ""}`)}// The agenda from day d: that day always, the days after it that have something, up to `days` on.fn calendar_sheet_agenda(items: list, d: str, days: int) -> list {    let end = date.add_days(d, max([days, 1]) - 1)    let inside = items | filter((m) => m.date >= d && m.date <= end)    let others = unique(inside | map((m) => m.date)) | filter((x) => x != d)    ([d] + others) | map((x) => ({ date: x, items: inside | filter((m) => m.date == x) }))}fn calendar_sheet_head(d: str, today: str) -> str {    let n = date.days_between(today, d)    let word = n == 0 ? "Today, " : n == 1 ? "Tomorrow, " : n == -1 ? "Yesterday, " : ""    `${word}${date.format(d, "dddd D MMMM")}`}fn calendar_sheet_tone(t) = contains(["success", "warning", "danger", "info"], t) ? t : "accent"fn calendar_sheet_says(d: str, today: str, count: int) -> str {    let many = count == 0 ? "" : count == 1 ? ", 1 item" : `, ${count} items`    `${date.format(d, "dddd D MMMM")}${d == today ? ", today" : ""}${many}`}// The share of the month showing, 0 folded to 1 open, for a height.fn calendar_sheet_share(h: num, row: num, rows: int) -> num = max([0, min([1, (h - row) / (row * (rows - 1))])])// Open or folded after a release: a throw says, or else whichever is nearer. v is down positive.fn calendar_sheet_opens(share: num, v: num) -> bool {    if abs(v) >= 0.4 { return v > 0 }    share >= 0.5}// -1 back, 1 on, 0 stay, as for a page: x is the drag, left negative.fn calendar_sheet_turns(x: num, v: num, width: num) -> int {    if abs(v) >= 0.3 && abs(x) >= 24 { return v * x > 0 ? (x < 0 ? 1 : -1) : 0 }    if abs(x) >= width * 0.35 { return x < 0 ? 1 : -1 }    0}fn calendar_sheet_axis(dx: num, dy: num, slop: num) -> str {    if abs(dx) < slop && abs(dy) < slop { return "" }    abs(dx) > abs(dy) ? "x" : "y"}fn calendar_sheet_sample(s, pos: num, at: num) {    let dt = at - s.at    dt < 32 ? s : { pos, at, v: (pos - s.pos) / dt }}fn calendar_sheet_speed(s, pos: num, at: num, moved: num) -> num {    if at - moved > 80 { return 0 }    let dt = at - s.at    let since = dt >= 8 ? (pos - s.pos) / dt : 0    abs(since) > abs(s.v) ? since : s.v}fn calendar_sheet_key(k: str, d: str, week_start: int) = match k {    "ArrowLeft" => date.add_days(d, -1)    "ArrowRight" => date.add_days(d, 1)    "ArrowUp" => date.add_days(d, -7)    "ArrowDown" => date.add_days(d, 7)    "PageUp" => date.add_months(d, -1)    "PageDown" => date.add_months(d, 1)    "Home" => calendar_sheet_first(d, week_start)    "End" => date.add_days(calendar_sheet_first(d, week_start), 6)    _ => null}component CalendarSheet(today: str = "", picked: str = "", items = [], week_start: int = 1, folded: bool = false, agenda_days: int = 7, label: str = "Calendar",                        empty_text: str = "Nothing on this day.", on_pick = null, on_open = null, on_fold = null, class: str? = null) {    style global calendar_sheet_css    state day = date.valid(today, "date") ? today : date.today()    state sel = date.valid(picked, "date") ? picked : day    state mini = folded    state flying = false    state swiped = 0    state g = null    state root = null    state inner = null       // takes the class a drag sets by hand: the root's is written from state, which would drop it    state clip = null    state grid = null    let starts = calendar_sheet_start_day(week_start)    let list = calendar_sheet_clean(items)    let weeks = calendar_sheet_weeks(sel, starts)    let at = calendar_sheet_row(weeks, sel)    let month = slice(sel, 0, 7)    let names = weeks[0] | map((d) => date.format(d, "ddd"))    let agenda = calendar_sheet_agenda(list, sel, agenda_days)    let wide = () => root.offsetWidth >= 640    let pick = (d: str, focus: bool) => {        sel = d        on_pick && on_pick(d)        if focus {            js("requestAnimationFrame", (_t = null) => {                let b = root.querySelector(`[data-day="${d}"]:not([tabindex="-1"])`)                b && b.focus()            })        }    }    let fold = (to: bool) => {        if to != mini {            mini = to            on_fold && on_fold(to)        }    }    // Out to one side, the days change, and in from the other. `translate` leaves the fold's `transform` alone.    let turn = (way: int) => {        let w = clip.offsetWidth        let calm = js("matchMedia", "(prefers-reduced-motion: reduce)").matches        let from = grid.style.translate != "" ? grid.style.translate : "0px 0px"        flying = true        let out = grid.animate([{ translate: from }, { translate: `${-way * w}px 0px` }], { duration: calm ? 0 : 170, easing: "ease-in", fill: "forwards" })        out.addEventListener("finish", (_e = null) => {            pick(mini && !wide() ? date.add_days(sel, way * 7) : date.add_months(sel, way), false)            grid.style.translate = ""            out.cancel()            let back = grid.animate([{ translate: `${way * w}px 0px` }, { translate: "0px 0px" }], { duration: calm ? 0 : 200, easing: "ease-out" })            back.addEventListener("finish", (_f = null) => { flying = false })        })    }    let let_go = () => {        inner.classList.remove("ui-calendar-sheet-moving")        clip.style.height = ""        grid.style.transform = ""    }    let spring_x = () => {        inner.classList.remove("ui-calendar-sheet-moving")        let was = grid.style.translate        grid.style.translate = ""        if was != "" { grid.animate([{ translate: was }, { translate: "0px 0px" }], { duration: 200, easing: "ease-out" }) }    }    <section class={ui_cls(["ui-calendar-sheet", mini && "ui-calendar-sheet-folded", class])} aria-label={label} style={`--rows: ${len(weeks)}; --at: ${at}`}             @mount={(me) => {                 root = me                 inner = me.querySelector(".ui-calendar-sheet-inner")                 clip = me.querySelector(".ui-calendar-sheet-clip")                 grid = me.querySelector(".ui-calendar-sheet-grid")                 let base = day                 let local = (_x = null) => {                     let t = js_new("Date")                     date.of(t.getFullYear(), t.getMonth() + 1, t.getDate())                 }                 let started = local()                 let d = js("document")                 let back = (_e = null) => {                     if js("document.visibilityState") == "visible" {                         day = date.add_days(base, date.days_between(started, local()))                     }                 }                 d.addEventListener("visibilitychange", back)                 () => { d.removeEventListener("visibilitychange", back) }             }}>        <div.ui-calendar-sheet-inner>            <div.ui-calendar-sheet-sheet                 @pointerdown={(e) => {                     let on_it = e.target.closest(".ui-calendar-sheet-clip, .ui-calendar-sheet-handle") != null                     if on_it && e.isPrimary && e.button == 0 && !flying {                         let row = clip.querySelector(".ui-calendar-sheet-week").offsetHeight                         g = { pid: e.pointerId, x: e.clientX, y: e.clientY, on: "", at: 0, shift: 0, row, h: clip.offsetHeight, moved: e.timeStamp, s: { pos: e.clientY, at: e.timeStamp, v: 0 } }                     } else {                         g = null                     }                 }}                 @pointermove={(e) => {                     if g != null && e.pointerId == g.pid {                         let dx = e.clientX - g.x                         let dy = e.clientY - g.y                         if g.on == "" {                             let way = calendar_sheet_axis(dx, dy, 8)                             if way == "y" && wide() { g = null }                             if way != "" && g != null {                                 e.currentTarget.setPointerCapture(e.pointerId)                                 inner.classList.add("ui-calendar-sheet-moving")                                 g = { ...g, on: way, shift: way == "x" ? (dx > 0 ? 8 : -8) : (dy > 0 ? 8 : -8), s: { pos: way == "x" ? e.clientX : e.clientY, at: e.timeStamp, v: 0 } }                             }                         }                         if g != null && g.on == "x" {                             let x = dx - g.shift                             grid.style.translate = `${x}px 0px`                             g = { ...g, at: x, moved: e.timeStamp, s: calendar_sheet_sample(g.s, e.clientX, e.timeStamp) }                         }                         if g != null && g.on == "y" {                             let low = g.row                             let high = g.row * len(weeks)                             let h = max([low, min([high, g.h + dy - g.shift])])                             let share = calendar_sheet_share(h, g.row, len(weeks))                             clip.style.height = `${h}px`                             grid.style.transform = `translateY(${-at * g.row * (1 - share)}px)`                             g = { ...g, at: h, moved: e.timeStamp, s: calendar_sheet_sample(g.s, e.clientY, e.timeStamp) }                         }                     }                 }}                 @pointerup={(e) => {                     if g != null && e.pointerId == g.pid {                         let was = g                         g = null                         if was.on != "" { swiped = e.timeStamp }                         if was.on == "y" {                             let v = calendar_sheet_speed(was.s, e.clientY, e.timeStamp, was.moved)                             let open_it = calendar_sheet_opens(calendar_sheet_share(was.at, was.row, len(weeks)), v)                             let_go()                             fold(!open_it)                         }                         if was.on == "x" {                             let v = calendar_sheet_speed(was.s, e.clientX, e.timeStamp, was.moved)                             let way = calendar_sheet_turns(was.at, v, clip.offsetWidth)                             inner.classList.remove("ui-calendar-sheet-moving")                             if way != 0 { turn(way) } else { spring_x() }                         }                     }                 }}                 @pointercancel={(e) => {                     if g != null && e.pointerId == g.pid {                         let was = g                         g = null                         if was.on == "y" { let_go() }                         if was.on == "x" { spring_x() }                     }                 }}>                <div.ui-calendar-sheet-head>                    <h2.ui-calendar-sheet-title aria-live="polite">{date.format(sel, "MMMM YYYY")}</h2>                    <button.ui-calendar-sheet-btn type="button" disabled={sel == day ? true : null} @click={() => { pick(day, false) }}>Today</button>                    <button.ui-calendar-sheet-btn type="button" aria-label={mini ? "Week before" : "Month before"} @click={() => { if !flying { turn(-1) } }}>                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 6l-6 6 6 6"/></svg>                    </button>                    <button.ui-calendar-sheet-btn type="button" aria-label={mini ? "Week after" : "Month after"} @click={() => { if !flying { turn(1) } }}>                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg>                    </button>                </div>                <div.ui-calendar-sheet-names aria-hidden="true">{for n in names => <span>{n}</span>}</div>                <div.ui-calendar-sheet-clip                     @click={(e) => {                         let b = e.target.closest("[data-day]")                         if b != null && !flying && e.timeStamp - swiped > 350 { pick(b.dataset.day, false) }                     }}                     @keydown={(e) => {                         let d = calendar_sheet_key(e.key, sel, starts)                         if d != null {                             e.preventDefault()                             pick(d, true)                         }                     }}>                    <div.ui-calendar-sheet-grid>                        {for [i, w] in enumerate(weeks) => <div.ui-calendar-sheet-week role="group" inert={mini && i != at ? "true" : null}>                            {for d in w => <button.ui-calendar-sheet-day type="button" key={d} data-day={d} tabindex={d == sel ? "0" : "-1"} data-out={slice(d, 0, 7) != month ? "" : null}                                    aria-label={calendar_sheet_says(d, day, len(list | filter((m) => m.date == d)))} aria-pressed={d == sel ? "true" : "false"} aria-current={d == day ? "date" : null}>                                <span.ui-calendar-sheet-num aria-hidden="true">{int(slice(d, 8, 10))}</span>                                <span.ui-calendar-sheet-dots aria-hidden="true">{for m in take(list | filter((x) => x.date == d), 3) => <span.ui-calendar-sheet-dot data-tone={calendar_sheet_tone(get(m, "tone"))}></span>}</span>                            </button>}                        </div>}                    </div>                </div>                <button.ui-calendar-sheet-handle type="button" aria-expanded={mini ? "false" : "true"} aria-label={mini ? "Show the whole month" : "Fold to one week"}                         @click={(e) => { if e.timeStamp - swiped > 350 { fold(!mini) } }}                         @keydown={(e) => {                             if e.key == "ArrowUp" { e.preventDefault(); fold(true) }                             if e.key == "ArrowDown" { e.preventDefault(); fold(false) }                         }}><span aria-hidden="true"></span></button>            </div>            <div.ui-calendar-sheet-agenda tabindex="-1">                {for a in agenda => <div key={a.date}>                    <h3.ui-calendar-sheet-dayhead>{calendar_sheet_head(a.date, day)}</h3>                    {len(a.items) == 0 && <p.ui-calendar-sheet-none>{empty_text}</p>}                    {for m in a.items => <button.ui-calendar-sheet-item type="button" key={m.id} @click={() => { on_open && on_open(m) }}>                        <span.ui-calendar-sheet-bar data-tone={calendar_sheet_tone(get(m, "tone"))} aria-hidden="true"></span>                        <span.ui-calendar-sheet-time>{m.time != null ? m.time : "All day"}</span>                        <span.ui-calendar-sheet-what>                            <span.ui-calendar-sheet-name>{m.title}</span>                            {get(m, "where") != null && <span.ui-calendar-sheet-where>{m.where}</span>}                        </span>                    </button>}                </div>}            </div>        </div>    </section>}
Thread
34Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.