Calendar · Months: a month view, a year view, event chips and cards
CalendarMonthView lays a month in week rows. An event across days lies over them as a bar in the first free lane, cut at the week's edge; the rest sit by the hour as chips, and a crowded day keeps the rest behind "N more". Arrows move a day, Page keys turn the month, and Enter or a double press opens the day; the cursor holds through the owner's echo of a turn. Days sit at sevenths of their row, as the bars do, so text never widens a column. YearView shows twelve small months; a day with something on it wears a dot in its first event's hue. EventChip is an event in a line and EventCard a block with its hours and place. MiniCalendar points to forms::Calendar. Palette::hue is now the one home for a chart color by index, for mail labels and events alike. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c1a5603184
commit
f316ab56ba
22 files changed
+2121
-20
No files matched your search
@@ -0,0 +1,89 @@
|
||||
use ely_gpui_component::{
|
||||
calendar::Event,
|
||||
theme::{ActiveTheme, TextSize},
|
||||
};
|
||||
use gpui::{AnyElement, App, IntoElement, ParentElement, SharedString, Styled, Window, div};
|
||||
use jiff::{Timestamp, ToSpan, civil::Date, tz::TimeZone};
|
||||
|
||||
mod months;
|
||||
|
||||
use super::Page;
|
||||
use crate::script::Step;
|
||||
|
||||
pub const PAGE: Page = Page {
|
||||
number: 30,
|
||||
slug: "calendar",
|
||||
title: "Calendar",
|
||||
summary: "Days, weeks, months and years, and what happens in them.",
|
||||
render,
|
||||
script: SCRIPT,
|
||||
};
|
||||
|
||||
const SCRIPT: &[Step] = &[
|
||||
Step::DownAt("calendar-month", 285.0, 230.0),
|
||||
Step::UpAt("calendar-month", 285.0, 230.0),
|
||||
Step::Key("right"),
|
||||
Step::Key("down"),
|
||||
Step::Wait(150),
|
||||
Step::Shot("month-cursor"),
|
||||
Step::Key("pagedown"),
|
||||
Step::Wait(200),
|
||||
Step::Shot("month-next"),
|
||||
Step::Key("pageup"),
|
||||
Step::DownAt("calendar-year", 150.0, 150.0),
|
||||
Step::UpAt("calendar-year", 150.0, 150.0),
|
||||
Step::Key("right"),
|
||||
Step::Wait(150),
|
||||
Step::Shot("year-cursor"),
|
||||
];
|
||||
|
||||
/// What a demo says it did, under it.
|
||||
fn quiet(text: SharedString, cx: &App) -> impl IntoElement + use<> {
|
||||
let theme = cx.theme();
|
||||
div()
|
||||
.text_size(theme.text_size(TextSize::Sm))
|
||||
.text_color(theme.colors.fg_muted)
|
||||
.child(text)
|
||||
}
|
||||
|
||||
/// The gallery's zone and today in it.
|
||||
fn here() -> (TimeZone, Date) {
|
||||
let zone = TimeZone::try_system().expect("the gallery reads the system time zone");
|
||||
let today = Timestamp::now().to_zoned(zone.clone()).date();
|
||||
(zone, today)
|
||||
}
|
||||
|
||||
/// A studio's weeks around today: meetings, a crowded day, an offsite, a trip past a weekend, a night shoot past midnight.
|
||||
fn events(today: Date, zone: &TimeZone) -> Vec<Event> {
|
||||
let day = |offset: i64| today.checked_add(offset.days()).expect("a day nearby");
|
||||
let at = |offset: i64, hour: i8, minute: i8| {
|
||||
day(offset)
|
||||
.at(hour, minute, 0, 0)
|
||||
.to_zoned(zone.clone())
|
||||
.expect("the zone holds the hour")
|
||||
.timestamp()
|
||||
};
|
||||
vec![
|
||||
Event::timed("standup", "Studio stand-up", at(0, 9, 30), at(0, 9, 45), 0),
|
||||
Event::timed("review", "Design review", at(0, 11, 0), at(0, 12, 0), 3).place("Model room"),
|
||||
Event::timed("lunch", "Lunch with Ana", at(0, 12, 30), at(0, 13, 30), 5),
|
||||
Event::timed("samples", "Plaster samples", at(0, 15, 0), at(0, 16, 0), 2),
|
||||
Event::timed("client", "Client call", at(0, 17, 0), at(0, 17, 30), 1),
|
||||
Event::timed("crit", "Crit", at(-3, 14, 0), at(-3, 15, 30), 3).place("Studio 2"),
|
||||
Event::timed("print", "Print run", at(-9, 10, 0), at(-9, 11, 0), 2),
|
||||
Event::all_day("offsite", "Studio offsite", day(2), day(4), 4),
|
||||
Event::timed("night", "Night shoot", at(5, 21, 0), at(6, 2, 0), 6).place("Atrium"),
|
||||
Event::all_day("trip", "Site visit · Lisbon", day(9), day(13), 7),
|
||||
Event::timed("models", "Model photos", at(11, 10, 0), at(11, 12, 0), 0),
|
||||
Event::all_day("launch", "Launch", day(18), day(18), 1),
|
||||
]
|
||||
}
|
||||
|
||||
fn render(window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
div()
|
||||
.child(months::month(window, cx))
|
||||
.child(months::mini(window, cx))
|
||||
.child(months::year(window, cx))
|
||||
.child(months::marks(cx))
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
use ely_gpui_component::{
|
||||
calendar::{CalendarMonthView, Event, EventCard, EventChip, YearView},
|
||||
forms::Calendar,
|
||||
};
|
||||
use gpui::{
|
||||
App, ElementId, Entity, IntoElement, ParentElement, SharedString, Styled, Window, div, px,
|
||||
};
|
||||
use jiff::civil::Date;
|
||||
|
||||
use super::{events, here, quiet};
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{change, keep, section, set},
|
||||
};
|
||||
|
||||
/// The day picked in the small calendar, the month shown, and what the last press said.
|
||||
#[derive(Clone)]
|
||||
struct Viewing {
|
||||
day: Date,
|
||||
month: Date,
|
||||
said: Option<SharedString>,
|
||||
}
|
||||
|
||||
/// The month shown and the day picked in the small calendar, shared by both sections.
|
||||
fn viewing(window: &mut Window, cx: &mut App) -> Entity<Viewing> {
|
||||
let (_, today) = here();
|
||||
keep(
|
||||
"calendar-viewing",
|
||||
|| Viewing {
|
||||
day: today,
|
||||
month: today.first_of_month(),
|
||||
said: None,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn month(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let (zone, today) = here();
|
||||
let viewing = viewing(window, cx);
|
||||
let now = viewing.read(cx).clone();
|
||||
let (turned, opened, pressed) = (viewing.clone(), viewing.clone(), viewing);
|
||||
let view = CalendarMonthView::new("calendar-month", now.month, events(today, &zone))
|
||||
.zone(zone)
|
||||
.on_month(move |first, _, cx| change(&turned, cx, |viewing| viewing.month = first))
|
||||
.on_day(move |day, _, cx| {
|
||||
let said = format!("Opened {}.", day.strftime("%A, %B %-d"));
|
||||
change(&opened, cx, |viewing| viewing.said = Some(said.into()))
|
||||
})
|
||||
.on_event(move |key, _, cx| {
|
||||
let said = format!("Pressed {key}.");
|
||||
change(&pressed, cx, |viewing| viewing.said = Some(said.into()))
|
||||
});
|
||||
section(
|
||||
"CalendarMonthView",
|
||||
"A month in week rows. What spans days lies across them, the rest sits by the hour, and a crowded day keeps the rest behind N more. Arrows move a day, Page keys turn the month, and Enter or a double press opens the day.",
|
||||
cx,
|
||||
)
|
||||
.child(probe(
|
||||
"calendar-month",
|
||||
div()
|
||||
.w(px(800.))
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.child(view)
|
||||
.children(now.said.map(|said| quiet(said, cx))),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn mini(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let viewing = viewing(window, cx);
|
||||
let day = viewing.read(cx).day;
|
||||
section(
|
||||
"MiniCalendar → forms::Calendar",
|
||||
"A month at a glance is forms::Calendar. Here a day picked in it turns the month view above to its month.",
|
||||
cx,
|
||||
)
|
||||
.child(probe(
|
||||
"calendar-mini",
|
||||
div().w(px(252.)).child(Calendar::new("calendar-mini").selected(day).on_pick(
|
||||
move |day, _, cx| {
|
||||
change(&viewing, cx, |viewing| {
|
||||
viewing.day = day;
|
||||
viewing.month = day.first_of_month();
|
||||
})
|
||||
},
|
||||
)),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn year(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let (zone, today) = here();
|
||||
let said = keep("calendar-year-said", || None::<SharedString>, window, cx);
|
||||
let shown = said.read(cx).clone();
|
||||
section(
|
||||
"YearView",
|
||||
"Twelve small months. A day with something on it wears a dot in its first event's hue; arrows move a day, Page keys a month, and Enter or a double press opens the day.",
|
||||
cx,
|
||||
)
|
||||
.child(probe(
|
||||
"calendar-year",
|
||||
div()
|
||||
.w(px(900.))
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.child(
|
||||
YearView::new("calendar-year", today.year(), events(today, &zone))
|
||||
.zone(zone)
|
||||
.on_day(move |day, _, cx| {
|
||||
let opened = format!("Opened {}.", day.strftime("%A, %B %-d"));
|
||||
set(&said, Some(opened.into()), cx)
|
||||
}),
|
||||
)
|
||||
.children(shown.map(|shown| quiet(shown, cx))),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn marks(cx: &mut App) -> impl IntoElement + use<> {
|
||||
let (zone, today) = here();
|
||||
let events = events(today, &zone);
|
||||
let event = |key: &str| -> Event {
|
||||
events
|
||||
.iter()
|
||||
.find(|event| event.key.as_ref() == key)
|
||||
.cloned()
|
||||
.expect("the demo holds the event")
|
||||
};
|
||||
let chip = |key: &str| {
|
||||
EventChip::new(
|
||||
(ElementId::from("calendar-chip"), key.to_string()),
|
||||
event(key),
|
||||
)
|
||||
.zone(zone.clone())
|
||||
};
|
||||
let card = |key: &str| {
|
||||
div().w(px(220.)).h(px(58.)).child(
|
||||
EventCard::new(
|
||||
(ElementId::from("calendar-card"), key.to_string()),
|
||||
event(key),
|
||||
)
|
||||
.zone(zone.clone()),
|
||||
)
|
||||
};
|
||||
section(
|
||||
"EventCard / EventChip",
|
||||
"An event in a line, by its hour or across whole days, and as a block with its hours and place, the way a time grid holds it.",
|
||||
cx,
|
||||
)
|
||||
.child(probe(
|
||||
"calendar-marks",
|
||||
div()
|
||||
.flex()
|
||||
.flex_wrap()
|
||||
.items_start()
|
||||
.gap_8()
|
||||
.child(
|
||||
div()
|
||||
.w(px(200.))
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.child(chip("review"))
|
||||
.child(chip("offsite"))
|
||||
.child(chip("night")),
|
||||
)
|
||||
.child(card("review"))
|
||||
.child(card("night"))
|
||||
.child(card("offsite")),
|
||||
))
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
mod agent;
|
||||
mod buttons;
|
||||
mod calendar;
|
||||
mod charts;
|
||||
mod chat;
|
||||
mod collab;
|
||||
@@ -73,6 +74,7 @@ pub const ALL: &[Page] = &[
|
||||
files::PAGE,
|
||||
messaging::PAGE,
|
||||
mail::PAGE,
|
||||
calendar::PAGE,
|
||||
theme::PAGE,
|
||||
];
|
||||
|
||||
|
||||
Reference in new issue
Block a user