Add configurable time increment toggle for event creation

- Add toggle button (15/30 minutes) in calendar header next to navigation arrows
- Implement circular frosted styling consistent with nav buttons
- Add configurable snapping for drag-to-create events in week view
- Persist time increment setting across browser sessions using localStorage
- Update snap function to accept configurable increment instead of hardcoded 15 minutes

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Connor Johnstone
2025-08-29 12:17:31 -04:00
parent 4fbef8a5dc
commit edd209238f
4 changed files with 95 additions and 12 deletions

View File

@@ -22,6 +22,8 @@ pub struct WeekViewProps {
pub on_create_event: Option<Callback<(NaiveDate, NaiveDateTime, NaiveDateTime)>>,
#[prop_or_default]
pub context_menus_open: bool,
#[prop_or_default]
pub time_increment: u32,
}
#[derive(Clone, PartialEq)]
@@ -122,6 +124,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
let onmousedown = {
let drag_state = drag_state_clone.clone();
let context_menus_open = props.context_menus_open;
let time_increment = props.time_increment;
Callback::from(move |e: MouseEvent| {
// Don't start drag if any context menu is open
if context_menus_open {
@@ -138,8 +141,8 @@ pub fn week_view(props: &WeekViewProps) -> Html {
let relative_y = e.layer_y() as f64;
let relative_y = if relative_y > 0.0 { relative_y } else { e.offset_y() as f64 };
// Snap to 15-minute increments
let snapped_y = snap_to_15_minutes(relative_y);
// Snap to increment
let snapped_y = snap_to_increment(relative_y, time_increment);
drag_state.set(Some(DragState {
is_dragging: true,
@@ -153,6 +156,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
let onmousemove = {
let drag_state = drag_state_clone.clone();
let time_increment = props.time_increment;
Callback::from(move |e: MouseEvent| {
if let Some(mut current_drag) = (*drag_state).clone() {
if current_drag.is_dragging {
@@ -160,8 +164,8 @@ pub fn week_view(props: &WeekViewProps) -> Html {
let relative_y = e.layer_y() as f64;
let relative_y = if relative_y > 0.0 { relative_y } else { e.offset_y() as f64 };
// Snap to 15-minute increments
let snapped_y = snap_to_15_minutes(relative_y);
// Snap to increment
let snapped_y = snap_to_increment(relative_y, time_increment);
current_drag.current_y = snapped_y;
drag_state.set(Some(current_drag));
@@ -214,7 +218,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
{onmousemove}
{onmouseup}
>
// Time slot backgrounds - 24 full hour slots + 1 boundary slot
// Time slot backgrounds - 24 hour slots to represent full day
{
(0..24).map(|_hour| {
html! {
@@ -225,8 +229,11 @@ pub fn week_view(props: &WeekViewProps) -> Html {
}
}).collect::<Html>()
}
// Final boundary slot to match the final time label
<div class="time-slot boundary-slot"></div>
// Final boundary slot to complete the 24-hour visual grid - make it interactive like other slots
<div class="time-slot boundary-slot">
<div class="time-slot-half"></div>
<div class="time-slot-half"></div>
</div>
// Events positioned absolutely based on their actual times
<div class="events-container">
@@ -398,9 +405,9 @@ fn get_weekday_name(weekday: Weekday) -> &'static str {
// Calculate the pixel position of an event based on its time
// Each hour is 60px, so we convert time to pixels
// Snap pixel position to 15-minute increments (15px = 15 minutes since 60px = 60 minutes)
fn snap_to_15_minutes(pixels: f64) -> f64 {
let increment = 15.0; // 15px = 15 minutes
(pixels / increment).round() * increment
fn snap_to_increment(pixels: f64, increment: u32) -> f64 {
let increment_px = increment as f64; // Convert to pixels (1px = 1 minute)
(pixels / increment_px).round() * increment_px
}
// Convert pixel position to time (inverse of time to pixels)
@@ -410,7 +417,12 @@ fn pixels_to_time(pixels: f64) -> NaiveTime {
let hours = (total_minutes / 60.0) as u32;
let minutes = (total_minutes % 60.0) as u32;
// Clamp to valid time range
// Handle midnight boundary - if we're at exactly 1440 pixels (24:00), return midnight
if total_minutes >= 1440.0 {
return NaiveTime::from_hms_opt(0, 0, 0).unwrap();
}
// Clamp to valid time range for within-day times
let hours = hours.min(23);
let minutes = minutes.min(59);