Demo
November 2026
Today, Wednesday 4 November
Friday 6 November
February 2026
Today, Tuesday 10 February
November 2026
Today, Monday 9 November
Nothing on this day.
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.qwemain.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>}Related
WeekStripDays in a swiped strip with dots for what is on them, today, and a jump to any day.2 views · 8 downloadsActionSheetA list of actions rising from the bottom, with a destructive style and Cancel: swipe down or the phone's back gesture closes it; on a tablet, a card by what opened it.3 views · 9 downloadsAlertInboxAlerts by severity, swiped to acknowledge or snooze, with buttons and keys to the same, and a badge that stays right as snoozes run out.3 views · 8 downloadsAlphaRailListA long list with sticky letter headers and an A to Z rail scrubbed by finger, with a bubble showing the letter and a haptic per step.1 view · 6 downloadsApprovalQueueOne request at a time, swiped to approve or reject, with a required reason, undo and a count of what is left.3 views · 6 downloadsBookSpreadReader pages, one column upright and a two-page spread turned, by swipe or edge tap, the place kept by text and not page number.2 views · 7 downloads
Thread
Thread 0 posts
On CalendarSheet: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.