Revert "feat(calendar-events): add view toggle, source filters, detail dialog, error banner, loading indicator"

This reverts commit d8e280df2a.
This commit is contained in:
Florian Schroedl
2026-03-29 11:20:55 +02:00
parent d8e280df2a
commit 7f1a679b74
7 changed files with 182 additions and 915 deletions

View File

@@ -318,14 +318,14 @@
(form/form-input {:type :email :error true :value "invalid-email"}))]))
(def sample-calendar-events
[{:title "Team standup" :date "2026-03-29" :time-start "09:00" :time-end "09:30" :color :accent :tags ["work"] :source "Work"}
{:title "Lunch with Alex" :date "2026-03-29" :time-start "12:00" :time-end "13:00" :color :success :tags ["social"] :source "Personal"}
{:title "Deploy v2.0" :date "2026-03-29" :time-start "15:00" :color :danger :tags ["ops" "urgent"] :source "Work"}
{:title "Design review" :date "2026-03-30" :time-start "10:00" :color :warning :tags ["design"] :source "Work"}
{:title "All-day planning" :date "2026-03-31" :color nil :done? true :source "Work"}
{:title "Sprint retro" :date "2026-04-01" :time-start "14:00" :time-end "15:00" :color :accent :source "Work"}
{:title "1:1 with manager" :date "2026-04-02" :time-start "11:00" :color :success :source "Work"}
{:title "Release party" :date "2026-04-03" :time-start "17:00" :color :danger :tags ["social"] :source "Personal"}])
[{:title "Team standup" :date "2026-03-29" :time-start "09:00" :time-end "09:30" :color :accent}
{:title "Lunch with Alex" :date "2026-03-29" :time-start "12:00" :time-end "13:00" :color :success}
{:title "Deploy v2.0" :date "2026-03-29" :time-start "15:00" :color :danger}
{:title "Design review" :date "2026-03-30" :time-start "10:00" :color :warning}
{:title "All-day planning" :date "2026-03-31" :color nil :done? true}
{:title "Sprint retro" :date "2026-04-01" :time-start "14:00" :time-end "15:00" :color :accent}
{:title "1:1 with manager" :date "2026-04-02" :time-start "11:00" :color :success}
{:title "Release party" :date "2026-04-03" :time-start "17:00" :color :danger}])
(defn calendar-demo []
(section "Calendar"
@@ -335,25 +335,12 @@
:selected-date "2026-03-29"})
(calendar/calendar {:year 2026 :month 4 :today-str "2026-03-29"})]
[:h5 "Full Calendar App"]
;; Error banner
(cal-events/error-banner {:message "Could not fetch events (demo)"})
;; Source toggles
(cal-events/source-toggles
{:sources [{:name "Work" :color :accent :active? true}
{:name "Personal" :color :success :active? true}
{:name "Inbox" :color :warning :active? false}]})
;; Event grid with view toggle in header
[:h5 "Event Grid"]
(cal-events/calendar-event-grid {:year 2026 :month 3 :today-str "2026-03-29"
:selected-date "2026-03-29"
:events sample-calendar-events
:header-actions (cal-events/view-toggle {:view :month})})
:events sample-calendar-events})
;; Ticker + Agenda
[:h5 "Agenda View"]
[:h5 "Day Ticker"]
(cal-events/ticker-strip {:days [{:date "2026-03-27" :day-num 27 :day-label "Fr"}
{:date "2026-03-28" :day-num 28 :day-label "Sa"}
{:date "2026-03-29" :day-num 29 :day-label "Su"}
@@ -365,19 +352,15 @@
:today-str "2026-03-29"
:selected "2026-03-29"
:events sample-calendar-events})
[:h5 "Agenda List"]
(cal-events/agenda-list {:days [{:date "2026-03-29" :label "Today"}
{:date "2026-03-30" :label "Tomorrow"}
{:date "2026-03-31" :label "Tue"}
{:date "2026-04-01" :label "Wed"}
{:date "2026-04-02" :label "Thu"}
{:date "2026-04-03" :label "Fri"}]
:events sample-calendar-events})
;; Event detail dialog (static demo)
[:h5 "Event Detail Dialog"]
(cal-events/event-detail-dialog
{:event {:title "Deploy v2.0" :date "2026-03-29" :time-start "15:00"
:color :danger :tags ["ops" "urgent"] :source "Work"}})))
:events sample-calendar-events})))
;; ── Pages ───────────────────────────────────────────────────────────
@@ -430,9 +413,9 @@
(defn calendar-page []
[:div
(page-header "Calendar" "Date picker, event grid, ticker strip, and agenda list.")
(calendar-demo)
(into [:div {:class "md-docs"}]
(markdown/markdown->hiccup (slurp "src/ui/calendar.md")))])
(markdown/markdown->hiccup (slurp "src/ui/calendar.md")))
(calendar-demo)])
(defn icons-page []
[:div

View File

@@ -280,80 +280,75 @@
(form/form-field {:label "Email" :error "Please enter a valid email address."}
(form/form-input {:type :email :error true :value "invalid-email"}))]))
(defonce !cal-state (atom {:year 2026 :month 3 :selected-date nil
:view :month :selected-event nil :error nil}))
(defonce !cal-state (atom {:year 2026 :month 3 :selected-date nil}))
(def sample-calendar-events
[{:title "Team standup" :date "2026-03-29" :time-start "09:00" :time-end "09:30" :color :accent :tags ["work"] :source "Work"}
{:title "Lunch with Alex" :date "2026-03-29" :time-start "12:00" :time-end "13:00" :color :success :tags ["social"] :source "Personal"}
{:title "Deploy v2.0" :date "2026-03-29" :time-start "15:00" :color :danger :tags ["ops" "urgent"] :source "Work"}
{:title "Design review" :date "2026-03-30" :time-start "10:00" :color :warning :tags ["design"] :source "Work"}
{:title "All-day planning" :date "2026-03-31" :color nil :done? true :source "Work"}
{:title "Sprint retro" :date "2026-04-01" :time-start "14:00" :time-end "15:00" :color :accent :source "Work"}
{:title "1:1 with manager" :date "2026-04-02" :time-start "11:00" :color :success :source "Work"}
{:title "Release party" :date "2026-04-03" :time-start "17:00" :color :danger :tags ["social"] :source "Personal"}])
[{:title "Team standup" :date "2026-03-29" :time-start "09:00" :time-end "09:30" :color :accent}
{:title "Lunch with Alex" :date "2026-03-29" :time-start "12:00" :time-end "13:00" :color :success}
{:title "Deploy v2.0" :date "2026-03-29" :time-start "15:00" :color :danger}
{:title "Design review" :date "2026-03-30" :time-start "10:00" :color :warning}
{:title "All-day planning" :date "2026-03-31" :color nil :done? true}
{:title "Sprint retro" :date "2026-04-01" :time-start "14:00" :time-end "15:00" :color :accent}
{:title "1:1 with manager" :date "2026-04-02" :time-start "11:00" :color :success}
{:title "Release party" :date "2026-04-03" :time-start "17:00" :color :danger}])
(defn calendar-demo []
(let [{:keys [year month selected-date view selected-event error]} @!cal-state
today-str "2026-03-29"
prev-month! (fn [_] (let [[ny nm] (calendar/prev-month year month)]
(swap! !cal-state assoc :year ny :month nm)))
next-month! (fn [_] (let [[ny nm] (calendar/next-month year month)]
(swap! !cal-state assoc :year ny :month nm)))]
(let [{:keys [year month selected-date]} @!cal-state
today-str "2026-03-29"]
(section "Calendar"
[:h5 "Date Picker"]
(calendar/calendar {:year year :month month
:today-str today-str
:selected-date selected-date
:on-select (fn [d] (swap! !cal-state assoc :selected-date d))
:on-prev-month prev-month!
:on-next-month next-month!})
[:h5 "Date Picker (interactive)"]
[:div {:style {:display "flex" :gap "1.5rem" :flex-wrap "wrap"}}
(calendar/calendar {:year year :month month
:today-str today-str
:selected-date selected-date
:on-select (fn [d] (swap! !cal-state assoc :selected-date d))
:on-prev-month (fn [_]
(let [[ny nm] (calendar/prev-month year month)]
(swap! !cal-state assoc :year ny :month nm)))
:on-next-month (fn [_]
(let [[ny nm] (calendar/next-month year month)]
(swap! !cal-state assoc :year ny :month nm)))})]
(when selected-date
[:p {:style {:color "var(--fg-1)" :font-size "var(--font-sm)"}}
(str "Selected: " selected-date)])
[:h5 "Full Calendar App"]
(cal-events/error-banner {:message error
:on-dismiss (fn [_] (swap! !cal-state assoc :error nil))})
[:h5 "Event Grid"]
(cal-events/calendar-event-grid {:year year :month month
:today-str today-str
:selected-date selected-date
:events sample-calendar-events
:on-select (fn [d] (swap! !cal-state assoc :selected-date d))
:on-prev-month (fn [_]
(let [[ny nm] (calendar/prev-month year month)]
(swap! !cal-state assoc :year ny :month nm)))
:on-next-month (fn [_]
(let [[ny nm] (calendar/next-month year month)]
(swap! !cal-state assoc :year ny :month nm)))
:on-event-click (fn [evt] (js/console.log "Event clicked:" (:title evt)))})
(cal-events/source-toggles
{:sources [{:name "Work" :color :accent :active? true}
{:name "Personal" :color :success :active? true}]
:on-toggle (fn [s] (js/console.log "Toggle source:" s))})
[:h5 "Day Ticker"]
(cal-events/ticker-strip {:days [{:date "2026-03-27" :day-num 27 :day-label "Fr"}
{:date "2026-03-28" :day-num 28 :day-label "Sa"}
{:date "2026-03-29" :day-num 29 :day-label "Su"}
{:date "2026-03-30" :day-num 30 :day-label "Mo"}
{:date "2026-03-31" :day-num 31 :day-label "Tu"}
{:date "2026-04-01" :day-num 1 :day-label "We"}
{:date "2026-04-02" :day-num 2 :day-label "Th"}
{:date "2026-04-03" :day-num 3 :day-label "Fr"}]
:today-str today-str
:selected (or selected-date today-str)
:events sample-calendar-events
:on-select (fn [d] (swap! !cal-state assoc :selected-date d))})
(if (= view :agenda)
[:div
(cal-events/ticker-strip {:days [{:date "2026-03-27" :day-num 27 :day-label "Fr"}
{:date "2026-03-28" :day-num 28 :day-label "Sa"}
{:date "2026-03-29" :day-num 29 :day-label "Su"}
{:date "2026-03-30" :day-num 30 :day-label "Mo"}
{:date "2026-03-31" :day-num 31 :day-label "Tu"}
{:date "2026-04-01" :day-num 1 :day-label "We"}
{:date "2026-04-02" :day-num 2 :day-label "Th"}
{:date "2026-04-03" :day-num 3 :day-label "Fr"}]
:today-str today-str
:selected (or selected-date today-str)
:events sample-calendar-events
:on-select (fn [d] (swap! !cal-state assoc :selected-date d))})
(cal-events/agenda-list {:days [{:date "2026-03-29" :label "Today"}
{:date "2026-03-30" :label "Tomorrow"}
{:date "2026-03-31" :label "Tue"}
{:date "2026-04-01" :label "Wed"}
{:date "2026-04-02" :label "Thu"}
{:date "2026-04-03" :label "Fri"}]
:events sample-calendar-events
:on-event-click (fn [evt] (swap! !cal-state assoc :selected-event evt))})]
(cal-events/calendar-event-grid {:year year :month month
:today-str today-str
:selected-date selected-date
:events sample-calendar-events
:on-select (fn [d] (swap! !cal-state assoc :selected-date d))
:on-prev-month prev-month!
:on-next-month next-month!
:on-event-click (fn [evt] (swap! !cal-state assoc :selected-event evt))
:header-actions (cal-events/view-toggle
{:view view
:on-change (fn [v] (swap! !cal-state assoc :view v))})}))
(cal-events/event-detail-dialog {:event selected-event
:on-close (fn [_] (swap! !cal-state assoc :selected-event nil))}))))
[:h5 "Agenda List"]
(cal-events/agenda-list {:days [{:date "2026-03-29" :label "Today"}
{:date "2026-03-30" :label "Tomorrow"}
{:date "2026-03-31" :label "Tue"}
{:date "2026-04-01" :label "Wed"}
{:date "2026-04-02" :label "Thu"}
{:date "2026-04-03" :label "Fri"}]
:events sample-calendar-events
:on-event-click (fn [evt] (js/console.log "Agenda event:" (:title evt)))}))))
;; ── Pages ───────────────────────────────────────────────────────────
@@ -402,9 +397,9 @@
(defn calendar-page []
[:div
(page-header "Calendar" "Date picker, event grid, ticker strip, and agenda list.")
(calendar-demo)
(into [:div {:class ["md-docs"]}]
(markdown/markdown->hiccup calendar-docs-md))])
(markdown/markdown->hiccup calendar-docs-md))
(calendar-demo)])
(defn icons-page []
[:div

View File

@@ -297,99 +297,85 @@
(form/form-field {:label "Email" :error "Please enter a valid email address."}
(form/form-input {:type "email" :error true :value "invalid-email"}))]))
(def !cal-state (atom {:year 2026 :month 3 :selected-date nil
:view "month" :selected-event nil :error nil}))
(def !cal-state (atom {:year 2026 :month 3 :selected-date nil}))
(def sample-calendar-events
[{:title "Team standup" :date "2026-03-29" :time-start "09:00" :time-end "09:30" :color "accent" :tags ["work"] :source "Work"}
{:title "Lunch with Alex" :date "2026-03-29" :time-start "12:00" :time-end "13:00" :color "success" :tags ["social"] :source "Personal"}
{:title "Deploy v2.0" :date "2026-03-29" :time-start "15:00" :color "danger" :tags ["ops" "urgent"] :source "Work"}
{:title "Design review" :date "2026-03-30" :time-start "10:00" :color "warning" :tags ["design"] :source "Work"}
{:title "All-day planning" :date "2026-03-31" :color nil :done? true :source "Work"}
{:title "Sprint retro" :date "2026-04-01" :time-start "14:00" :time-end "15:00" :color "accent" :source "Work"}
{:title "1:1 with manager" :date "2026-04-02" :time-start "11:00" :color "success" :source "Work"}
{:title "Release party" :date "2026-04-03" :time-start "17:00" :color "danger" :tags ["social"] :source "Personal"}])
(defn- nav! [key val]
(fn [_] (swap! !cal-state assoc key val) (render!)))
(defn- prev-month! [_]
(let [{:keys [year month]} @!cal-state
[ny nm] (calendar/prev-month year month)]
(swap! !cal-state assoc :year ny :month nm)
(render!)))
(defn- next-month! [_]
(let [{:keys [year month]} @!cal-state
[ny nm] (calendar/next-month year month)]
(swap! !cal-state assoc :year ny :month nm)
(render!)))
[{:title "Team standup" :date "2026-03-29" :time-start "09:00" :time-end "09:30" :color "accent"}
{:title "Lunch with Alex" :date "2026-03-29" :time-start "12:00" :time-end "13:00" :color "success"}
{:title "Deploy v2.0" :date "2026-03-29" :time-start "15:00" :color "danger"}
{:title "Design review" :date "2026-03-30" :time-start "10:00" :color "warning"}
{:title "All-day planning" :date "2026-03-31" :color nil :done? true}
{:title "Sprint retro" :date "2026-04-01" :time-start "14:00" :time-end "15:00" :color "accent"}
{:title "1:1 with manager" :date "2026-04-02" :time-start "11:00" :color "success"}
{:title "Release party" :date "2026-04-03" :time-start "17:00" :color "danger"}])
(defn calendar-demo []
(let [{:keys [year month selected-date view selected-event error]} @!cal-state
(let [{:keys [year month selected-date]} @!cal-state
today-str "2026-03-29"]
(section "Calendar"
[:h5 "Date Picker"]
(calendar/calendar {:year year :month month
:today-str today-str
:selected-date selected-date
:on-select (nav! :selected-date)
:on-prev-month prev-month!
:on-next-month next-month!})
[:h5 "Date Picker (interactive)"]
[:div {:style {"display" "flex" "gap" "1.5rem" "flex-wrap" "wrap"}}
(calendar/calendar {:year year :month month
:today-str today-str
:selected-date selected-date
:on-select (fn [d]
(swap! !cal-state assoc :selected-date d)
(render!))
:on-prev-month (fn [_]
(let [[ny nm] (calendar/prev-month year month)]
(swap! !cal-state assoc :year ny :month nm)
(render!)))
:on-next-month (fn [_]
(let [[ny nm] (calendar/next-month year month)]
(swap! !cal-state assoc :year ny :month nm)
(render!)))})]
(when selected-date
[:p {:style {"color" "var(--fg-1)" "font-size" "var(--font-sm)"}}
(str "Selected: " selected-date)])
[:h5 "Full Calendar App"]
;; Error banner
(cal-events/error-banner {:message error
:on-dismiss (nav! :error nil)})
[:h5 "Event Grid"]
(cal-events/calendar-event-grid {:year year :month month
:today-str today-str
:selected-date selected-date
:events sample-calendar-events
:on-select (fn [d]
(swap! !cal-state assoc :selected-date d)
(render!))
:on-prev-month (fn [_]
(let [[ny nm] (calendar/prev-month year month)]
(swap! !cal-state assoc :year ny :month nm)
(render!)))
:on-next-month (fn [_]
(let [[ny nm] (calendar/next-month year month)]
(swap! !cal-state assoc :year ny :month nm)
(render!)))
:on-event-click (fn [evt] (js/console.log "Event clicked:" (:title evt)))})
;; Source toggles
(cal-events/source-toggles
{:sources [{:name "Work" :color "accent" :active? true}
{:name "Personal" :color "success" :active? true}]
:on-toggle (fn [s] (js/console.log "Toggle source:" s))})
[:h5 "Day Ticker"]
(cal-events/ticker-strip {:days [{:date "2026-03-27" :day-num 27 :day-label "Fr"}
{:date "2026-03-28" :day-num 28 :day-label "Sa"}
{:date "2026-03-29" :day-num 29 :day-label "Su"}
{:date "2026-03-30" :day-num 30 :day-label "Mo"}
{:date "2026-03-31" :day-num 31 :day-label "Tu"}
{:date "2026-04-01" :day-num 1 :day-label "We"}
{:date "2026-04-02" :day-num 2 :day-label "Th"}
{:date "2026-04-03" :day-num 3 :day-label "Fr"}]
:today-str today-str
:selected (or selected-date today-str)
:events sample-calendar-events
:on-select (fn [d]
(swap! !cal-state assoc :selected-date d)
(render!))})
;; View toggle + event grid / agenda
(if (= view "agenda")
[:div
(cal-events/ticker-strip {:days [{:date "2026-03-27" :day-num 27 :day-label "Fr"}
{:date "2026-03-28" :day-num 28 :day-label "Sa"}
{:date "2026-03-29" :day-num 29 :day-label "Su"}
{:date "2026-03-30" :day-num 30 :day-label "Mo"}
{:date "2026-03-31" :day-num 31 :day-label "Tu"}
{:date "2026-04-01" :day-num 1 :day-label "We"}
{:date "2026-04-02" :day-num 2 :day-label "Th"}
{:date "2026-04-03" :day-num 3 :day-label "Fr"}]
:today-str today-str
:selected (or selected-date today-str)
:events sample-calendar-events
:on-select (nav! :selected-date)})
(cal-events/agenda-list {:days [{:date "2026-03-29" :label "Today"}
{:date "2026-03-30" :label "Tomorrow"}
{:date "2026-03-31" :label "Tue"}
{:date "2026-04-01" :label "Wed"}
{:date "2026-04-02" :label "Thu"}
{:date "2026-04-03" :label "Fri"}]
:events sample-calendar-events
:on-event-click (fn [evt]
(swap! !cal-state assoc :selected-event evt)
(render!))})]
(cal-events/calendar-event-grid {:year year :month month
:today-str today-str
:selected-date selected-date
:events sample-calendar-events
:on-select (nav! :selected-date)
:on-prev-month prev-month!
:on-next-month next-month!
:on-event-click (fn [evt]
(swap! !cal-state assoc :selected-event evt)
(render!))
:header-actions (cal-events/view-toggle
{:view view
:on-change (nav! :view)})}))
;; Event detail dialog
(cal-events/event-detail-dialog {:event selected-event
:on-close (fn [] (swap! !cal-state assoc :selected-event nil) (render!))}))))
[:h5 "Agenda List"]
(cal-events/agenda-list {:days [{:date "2026-03-29" :label "Today"}
{:date "2026-03-30" :label "Tomorrow"}
{:date "2026-03-31" :label "Tue"}
{:date "2026-04-01" :label "Wed"}
{:date "2026-04-02" :label "Thu"}
{:date "2026-04-03" :label "Fri"}]
:events sample-calendar-events
:on-event-click (fn [evt] (js/console.log "Agenda event:" (:title evt)))}))))
;; ── Pages ───────────────────────────────────────────────────────────
@@ -462,10 +448,10 @@
(load-calendar-docs!)
[:div
(page-header "Calendar" "Date picker, event grid, ticker strip, and agenda list.")
(calendar-demo)
(when-let [md @!calendar-docs]
(into [:div {:class "md-docs"}]
(markdown/markdown->hiccup md)))])
(markdown/markdown->hiccup md)))
(calendar-demo)])
(defn icons-page []
[:div

View File

@@ -75,60 +75,6 @@ Vertical list of events grouped by day.
:on-event-click (fn [event-map] ...)})
```
## View Toggle
A segmented control to switch between Grid and Agenda views:
```clojure
(cal-events/view-toggle
{:view :month ;; :month or :agenda
:on-change (fn [new-view] ...)})
```
## Source Filter Toggles
Colored pill buttons to show/hide event sources:
```clojure
(cal-events/source-toggles
{:sources [{:name "Work" :color :accent :active? true}
{:name "Personal" :color :success :active? false}]
:on-toggle (fn [source-name] ...)})
```
## Event Detail Dialog
An overlay dialog showing event details (date, time, tags, source):
```clojure
(cal-events/event-detail-dialog
{:event {:title "Meeting" :date "2026-03-29" :time-start "10:00"
:color :accent :tags ["work"] :source "Work Calendar"}
:on-close (fn [] ...)})
```
## Error Banner
A dismissible error bar at the top of the calendar:
```clojure
(cal-events/error-banner
{:message "Failed to fetch events"
:on-dismiss (fn [_] ...)})
```
## Loading Indicator
A pulsing dot for loading state, placed inline in the header:
```clojure
(cal-events/loading-indicator)
```
## Event Colors
Colors map to the theme's semantic tokens and support dark mode automatically: `:accent`, `:danger`, `:success`, `:warning`. Pass `nil` for the default gray.
## Event Data Format
Events are plain maps:
@@ -139,11 +85,13 @@ Events are plain maps:
:time-start "09:00" ;; HH:MM or nil
:time-end "09:30" ;; HH:MM or nil
:color :accent ;; :accent :danger :success :warning or nil
:done? false
:tags ["work" "daily"] ;; optional, shown in detail dialog
:source "Work Calendar"} ;; optional, shown in detail dialog
:done? false}
```
## Event Colors
Colors map to the theme's semantic tokens and support dark mode automatically: `:accent`, `:danger`, `:success`, `:warning`. Pass `nil` for the default gray.
## Date Utilities
All date math is pure (no JS Date dependency) and works on all targets:

View File

@@ -1,9 +1,7 @@
(ns ui.calendar-events
"Event-aware calendar components. See src/ui/calendar.md for full documentation."
(:require [clojure.string :as str]
[ui.calendar :as cal]
[ui.button :as button]
[ui.icon :as icon]))
[ui.calendar :as cal]))
;; In squint, keywords are strings — name is identity
#?(:squint (defn- kw-name [s] s)
@@ -18,8 +16,6 @@
;; :time-end - "HH:MM" string or nil
;; :color - :accent, :danger, :success, :warning, or nil (default)
;; :done? - boolean
;; :tags - vector of strings (optional)
;; :source - string (optional, e.g. file/calendar name)
(def event-colors #{"accent" "danger" "success" "warning"})
@@ -98,260 +94,8 @@
[opts]
(str/join " " (agenda-event-class-list opts)))
(defn view-toggle-class-list
"Returns a vector of CSS class strings for a view toggle button.
Options:
:active? - boolean"
[{:keys [active?]}]
(cond-> ["cal-view-btn"]
active? (conj "cal-view-btn-active")))
(defn view-toggle-classes [opts]
(str/join " " (view-toggle-class-list opts)))
(defn source-toggle-class-list
"Returns a vector of CSS class strings for a source filter toggle.
Options:
:color - event color keyword
:active? - boolean (visible)"
[{:keys [color active?]}]
(cond-> ["cal-source-toggle" (event-color-class color)]
(not active?) (conj "cal-source-inactive")))
(defn source-toggle-classes [opts]
(str/join " " (source-toggle-class-list opts)))
;; ── Components ──────────────────────────────────────────────────────
;; ── View Toggle ─────────────────────────────────────────────────────
(defn view-toggle
"Render a Grid/Agenda view toggle.
Props:
:view - current view (:month or :agenda)
:on-change - callback fn, receives the new view keyword
:class - additional CSS classes
:attrs - additional HTML attributes"
[{:keys [view on-change class attrs]}]
(let [v (kw-name (or view #?(:squint "month" :cljs :month :clj :month)))]
#?(:squint
(let [classes (cond-> "cal-view-toggle" class (str " " class))
base-attrs (merge {:class classes} attrs)]
[:div base-attrs
[:button {:class (view-toggle-classes {:active? (= v "month")})
:on-click (when on-change (fn [_] (on-change "month")))}
"Grid"]
[:button {:class (view-toggle-classes {:active? (= v "agenda")})
:on-click (when on-change (fn [_] (on-change "agenda")))}
"Agenda"]])
:cljs
(let [classes (cond-> ["cal-view-toggle"] class (conj class))
base-attrs (merge {:class classes} attrs)]
[:div base-attrs
[:button {:class (view-toggle-class-list {:active? (= v "month")})
:on (when on-change {:click (fn [_] (on-change :month))})}
"Grid"]
[:button {:class (view-toggle-class-list {:active? (= v "agenda")})
:on (when on-change {:click (fn [_] (on-change :agenda))})}
"Agenda"]])
:clj
(let [classes (cond-> "cal-view-toggle" class (str " " class))
base-attrs (merge {:class classes} attrs)]
[:div base-attrs
[:button {:class (view-toggle-classes {:active? (= v "month")})} "Grid"]
[:button {:class (view-toggle-classes {:active? (= v "agenda")})} "Agenda"]]))))
;; ── Loading Indicator ───────────────────────────────────────────────
(defn loading-indicator
"Render a small pulsing dot to indicate loading state."
[]
#?(:squint [:div {:class "cal-loading"}]
:cljs [:div {:class ["cal-loading"]}]
:clj [:div {:class "cal-loading"}]))
;; ── Error Banner ────────────────────────────────────────────────────
(defn error-banner
"Render an error banner at the top of the calendar.
Props:
:message - error message string
:on-dismiss - callback to dismiss the error"
[{:keys [message on-dismiss]}]
(when message
#?(:squint
[:div {:class "cal-error-banner"}
[:span (str "Error: " message)]
(when on-dismiss
[:button {:class "cal-error-dismiss" :on-click on-dismiss} "\u00d7"])]
:cljs
[:div {:class ["cal-error-banner"]}
[:span (str "Error: " message)]
(when on-dismiss
[:button {:class ["cal-error-dismiss"]
:on {:click on-dismiss}} "\u00d7"])]
:clj
[:div {:class "cal-error-banner"}
[:span (str "Error: " message)]
[:button {:class "cal-error-dismiss"} "\u00d7"]])))
;; ── Source Filter Toggles ───────────────────────────────────────────
(defn source-toggles
"Render a row of source filter toggle buttons.
Props:
:sources - vector of {:name :color :active?} maps
:on-toggle - callback fn, receives source name string
:class - additional CSS classes
:attrs - additional HTML attributes"
[{:keys [sources on-toggle class attrs]}]
(when (seq sources)
#?(:squint
(let [classes (cond-> "cal-source-toggles" class (str " " class))
base-attrs (merge {:class classes} attrs)]
(into [:div base-attrs]
(map (fn [src]
[:button {:class (source-toggle-classes {:color (:color src) :active? (:active? src)})
:on-click (when on-toggle (fn [_] (on-toggle (:name src))))}
(:name src)])
sources)))
:cljs
(let [classes (cond-> ["cal-source-toggles"] class (conj class))
base-attrs (merge {:class classes} attrs)]
(into [:div base-attrs]
(map (fn [src]
[:button {:class (source-toggle-class-list {:color (:color src) :active? (:active? src)})
:on (when on-toggle {:click (fn [_] (on-toggle (:name src)))})}
(:name src)])
sources)))
:clj
(let [classes (cond-> "cal-source-toggles" class (str " " class))
base-attrs (merge {:class classes} attrs)]
(into [:div base-attrs]
(map (fn [src]
[:button {:class (source-toggle-classes {:color (:color src) :active? (:active? src)})}
(:name src)])
sources))))))
;; ── Event Detail Dialog ─────────────────────────────────────────────
(defn event-detail-dialog
"Render an event detail overlay/dialog.
Props:
:event - event map (nil to hide)
:on-close - callback to close the dialog
:class - additional CSS classes
:attrs - additional HTML attributes"
[{:keys [event on-close class attrs]}]
(when event
(let [title (:title event)
done? (:done? event)
tags (or (:tags event) [])
source (:source event)
color (:color event)
time-str (event-time-display event)
date (:date event)]
#?(:squint
[:div {:class "cal-detail-overlay"
:on-click on-close}
[:div {:class (str "cal-detail " (event-color-class color)
(when class (str " " class)))
:on-click (fn [e] (.stopPropagation e))}
[:div {:class "cal-detail-header"}
[:div {:class "cal-detail-title"}
(when done? [:span {:class "cal-detail-badge"} "DONE"])
title]
[:button {:class "cal-detail-close" :on-click on-close} "\u00d7"]]
[:div {:class "cal-detail-body"}
(when date
[:div {:class "cal-detail-row"}
[:span {:class "cal-detail-label"} "Date"]
[:span {:class "cal-detail-value"} (str/replace date "-" "/")]])
(when time-str
[:div {:class "cal-detail-row"}
[:span {:class "cal-detail-label"} "Time"]
[:span {:class "cal-detail-value"} time-str]])
(when (seq tags)
[:div {:class "cal-detail-row"}
[:span {:class "cal-detail-label"} "Tags"]
(into [:div {:class "cal-detail-tags"}]
(map (fn [t] [:span {:class "cal-detail-tag"} t]) tags))])
(when source
[:div {:class "cal-detail-row"}
[:span {:class "cal-detail-label"} "Source"]
[:span {:class "cal-detail-value"} source]])]]]
:cljs
[:div {:class ["cal-detail-overlay"]
:on {:click on-close}}
[:div {:class [(str "cal-detail " (event-color-class color))
(when class class)]
:on {:click (fn [e] (.stopPropagation e))}}
[:div {:class ["cal-detail-header"]}
[:div {:class ["cal-detail-title"]}
(when done? [:span {:class ["cal-detail-badge"]} "DONE"])
title]
[:button {:class ["cal-detail-close"]
:on {:click on-close}} "\u00d7"]]
[:div {:class ["cal-detail-body"]}
(when date
[:div {:class ["cal-detail-row"]}
[:span {:class ["cal-detail-label"]} "Date"]
[:span {:class ["cal-detail-value"]} (str/replace date "-" "/")]])
(when time-str
[:div {:class ["cal-detail-row"]}
[:span {:class ["cal-detail-label"]} "Time"]
[:span {:class ["cal-detail-value"]} time-str]])
(when (seq tags)
[:div {:class ["cal-detail-row"]}
[:span {:class ["cal-detail-label"]} "Tags"]
(into [:div {:class ["cal-detail-tags"]}]
(map (fn [t] [:span {:class ["cal-detail-tag"]} t]) tags))])
(when source
[:div {:class ["cal-detail-row"]}
[:span {:class ["cal-detail-label"]} "Source"]
[:span {:class ["cal-detail-value"]} source]])]]]
:clj
[:div {:class "cal-detail-overlay"}
[:div {:class (str "cal-detail " (event-color-class color)
(when class (str " " class)))}
[:div {:class "cal-detail-header"}
[:div {:class "cal-detail-title"}
(when done? [:span {:class "cal-detail-badge"} "DONE"])
title]
[:button {:class "cal-detail-close"} "\u00d7"]]
[:div {:class "cal-detail-body"}
(when date
[:div {:class "cal-detail-row"}
[:span {:class "cal-detail-label"} "Date"]
[:span {:class "cal-detail-value"} (str/replace date "-" "/")]])
(when time-str
[:div {:class "cal-detail-row"}
[:span {:class "cal-detail-label"} "Time"]
[:span {:class "cal-detail-value"} time-str]])
(when (seq tags)
[:div {:class "cal-detail-row"}
[:span {:class "cal-detail-label"} "Tags"]
(into [:div {:class "cal-detail-tags"}]
(map (fn [t] [:span {:class "cal-detail-tag"} t]) tags))])
(when source
[:div {:class "cal-detail-row"}
[:span {:class "cal-detail-label"} "Source"]
[:span {:class "cal-detail-value"} source]])]]]))))
;; ── Event Pill ──────────────────────────────────────────────────────
(defn event-pill
"Render a small event chip for use inside calendar day cells.
@@ -389,8 +133,6 @@
[:span {:class "cal-event-time"} time-str])
[:span {:class "cal-event-title"} title]])))
;; ── Event Day Cell ──────────────────────────────────────────────────
(defn event-day-cell
"Render a day cell with event pills for the calendar event grid.
@@ -451,8 +193,6 @@
(when (pos? overflow)
[[:div {:class "cal-event-more"} (str "+" overflow " more")]])))])))
;; ── Calendar Event Grid ─────────────────────────────────────────────
(defn calendar-event-grid
"Render a month grid calendar with events displayed in day cells.
@@ -467,32 +207,19 @@
:on-next-month - callback for next month nav
:on-event-click - callback for event click
:max-visible - max events per cell (default 3)
:loading? - show loading indicator
:header-actions - extra hiccup to render in header right side
:class - additional CSS classes
:attrs - additional HTML attributes"
[{:keys [year month today-str selected-date events on-select
on-prev-month on-next-month on-event-click max-visible
loading? header-actions class attrs]}]
class attrs]}]
(let [days (cal/calendar-days year month)]
#?(:squint
(let [classes (cond-> "cal cal-has-events" class (str " " class))
base-attrs (merge {:class classes} attrs)]
[:div base-attrs
[:div {:class "cal-nav"}
(button/button {:variant "ghost" :icon "chevron-left" :size "sm"
:on-click on-prev-month
:class "cal-nav-btn"
:attrs {:aria-label "Previous month"}})
[:div {:class "cal-nav-title"}
(str (cal/month-name month) " " year)
(when loading? (loading-indicator))]
[:div {:class "cal-nav-actions"}
header-actions
(button/button {:variant "ghost" :icon "chevron-right" :size "sm"
:on-click on-next-month
:class "cal-nav-btn"
:attrs {:aria-label "Next month"}})]]
(cal/calendar-header {:year year :month month
:on-prev-month on-prev-month
:on-next-month on-next-month})
(cal/calendar-weekdays {})
(into [:div {:class "cal-grid cal-grid-events"}]
(map (fn [day-info]
@@ -510,20 +237,9 @@
classes (cond-> cls class (conj class))
base-attrs (merge {:class classes} attrs)]
[:div base-attrs
[:div {:class ["cal-nav"]}
(button/button {:variant :ghost :icon :chevron-left :size :sm
:on-click on-prev-month
:class "cal-nav-btn"
:attrs {:aria-label "Previous month"}})
[:div {:class ["cal-nav-title"]}
(str (cal/month-name month) " " year)
(when loading? (loading-indicator))]
[:div {:class ["cal-nav-actions"]}
header-actions
(button/button {:variant :ghost :icon :chevron-right :size :sm
:on-click on-next-month
:class "cal-nav-btn"
:attrs {:aria-label "Next month"}})]]
(cal/calendar-header {:year year :month month
:on-prev-month on-prev-month
:on-next-month on-next-month})
(cal/calendar-weekdays {})
(into [:div {:class ["cal-grid" "cal-grid-events"]}]
(map (fn [day-info]
@@ -540,18 +256,7 @@
(let [classes (cond-> "cal cal-has-events" class (str " " class))
base-attrs (merge {:class classes} attrs)]
[:div base-attrs
[:div {:class "cal-nav"}
(button/button {:variant :ghost :icon :chevron-left :size :sm
:class "cal-nav-btn"
:attrs {:aria-label "Previous month"}})
[:div {:class "cal-nav-title"}
(str (cal/month-name month) " " year)
(when loading? (loading-indicator))]
[:div {:class "cal-nav-actions"}
header-actions
(button/button {:variant :ghost :icon :chevron-right :size :sm
:class "cal-nav-btn"
:attrs {:aria-label "Next month"}})]]
(cal/calendar-header {:year year :month month})
(cal/calendar-weekdays {})
(into [:div {:class "cal-grid cal-grid-events"}]
(map (fn [day-info]
@@ -616,8 +321,12 @@
(map (fn [evt] (ticker-dot {:event evt}))
(take 4 day-evts)))])))
(def ^:private weekday-short-names
["Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun"])
(defn ticker-strip
"Render a horizontal scrollable day ticker strip.
Shows days from the given list with event dot indicators.
Props:
:days - vector of {:date :day-num :day-label} maps
@@ -715,21 +424,18 @@
"Render a day group in the agenda list with header and event rows.
Props:
:date - YYYY-MM-DD string
:label - display label (e.g. 'Today', 'Tomorrow', 'Mon')
:events - all events (filtered internally)
:on-event-click - callback for event click
:day-actions - extra hiccup to render in the day header (e.g. add button)"
[{:keys [date label events on-event-click day-actions]}]
:date - YYYY-MM-DD string
:label - display label (e.g. 'Today', 'Tomorrow', 'Mon')
:events - all events (filtered internally)
:on-event-click - callback for event click"
[{:keys [date label events on-event-click]}]
(let [day-evts (events-for-date events date)]
(when (seq day-evts)
#?(:squint
[:div {:class "cal-agenda-day-group"}
[:div {:class "cal-agenda-day-header"}
[:div {:class "cal-agenda-day-header-left"}
[:span {:class "cal-agenda-day-label"} label]
[:span {:class "cal-agenda-day-date"} (str/replace date "-" "/")]]
(when day-actions day-actions)]
[:span {:class "cal-agenda-day-label"} label]
[:span {:class "cal-agenda-day-date"} (str/replace date "-" "/")]]
(into [:div {:class "cal-agenda-day-events"}]
(map (fn [evt]
(agenda-event-row {:event evt :on-click on-event-click}))
@@ -738,10 +444,8 @@
:cljs
[:div {:class ["cal-agenda-day-group"]}
[:div {:class ["cal-agenda-day-header"]}
[:div {:class ["cal-agenda-day-header-left"]}
[:span {:class ["cal-agenda-day-label"]} label]
[:span {:class ["cal-agenda-day-date"]} (str/replace date "-" "/")]]
(when day-actions day-actions)]
[:span {:class ["cal-agenda-day-label"]} label]
[:span {:class ["cal-agenda-day-date"]} (str/replace date "-" "/")]]
(into [:div {:class ["cal-agenda-day-events"]}]
(map (fn [evt]
(agenda-event-row {:event evt :on-click on-event-click}))
@@ -750,10 +454,8 @@
:clj
[:div {:class "cal-agenda-day-group"}
[:div {:class "cal-agenda-day-header"}
[:div {:class "cal-agenda-day-header-left"}
[:span {:class "cal-agenda-day-label"} label]
[:span {:class "cal-agenda-day-date"} (str/replace date "-" "/")]]
(when day-actions day-actions)]
[:span {:class "cal-agenda-day-label"} label]
[:span {:class "cal-agenda-day-date"} (str/replace date "-" "/")]]
(into [:div {:class "cal-agenda-day-events"}]
(map (fn [evt]
(agenda-event-row {:event evt}))
@@ -766,17 +468,14 @@
:days - vector of {:date :label} maps for days to show
:events - all events
:on-event-click - callback for event click
:day-actions-fn - (fn [date-str] hiccup) — optional, renders action slot per day header
:class - additional CSS classes
:attrs - additional HTML attributes"
[{:keys [days events on-event-click day-actions-fn class attrs]}]
[{:keys [days events on-event-click class attrs]}]
(let [groups (keep (fn [d]
(agenda-day-group {:date (:date d)
:label (:label d)
:events events
:on-event-click on-event-click
:day-actions (when day-actions-fn
(day-actions-fn (:date d)))}))
:on-event-click on-event-click}))
days)
empty? (not (seq groups))]
#?(:squint

View File

@@ -439,292 +439,6 @@
font-size: var(--font-sm);
}
/* ── Nav Actions (header right side slot) ────────────────────────── */
.cal-nav-actions {
display: flex;
align-items: center;
gap: var(--size-1);
}
/* ── View Toggle ─────────────────────────────────────────────────── */
.cal-view-toggle {
display: flex;
background: var(--bg-2);
border-radius: var(--radius-sm);
padding: var(--size-1);
gap: var(--size-1);
}
.cal-view-btn {
appearance: none;
border: none;
background: transparent;
color: var(--fg-2);
font-family: inherit;
font-size: var(--font-xs);
font-weight: 600;
padding: var(--size-1) var(--size-2);
border-radius: var(--radius-sm);
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.cal-view-btn:hover {
color: var(--fg-1);
}
.cal-view-btn-active {
background: var(--bg-1);
color: var(--fg-0);
box-shadow: var(--shadow-0);
}
/* ── Loading Indicator ───────────────────────────────────────────── */
.cal-loading {
width: var(--size-2);
height: var(--size-2);
border-radius: 9999px;
background: var(--accent);
animation: cal-pulse 0.9s ease-in-out infinite;
flex-shrink: 0;
display: inline-block;
margin-left: var(--size-2);
vertical-align: middle;
}
@keyframes cal-pulse {
0%, 100% { opacity: 0.3; transform: scale(0.8); }
50% { opacity: 1; transform: scale(1.2); }
}
/* ── Error Banner ────────────────────────────────────────────────── */
.cal-error-banner {
background: var(--danger-100);
border-bottom: 1px solid var(--danger-200);
color: var(--danger-800);
padding: var(--size-2) var(--size-4);
display: flex;
align-items: center;
justify-content: space-between;
font-size: var(--font-xs);
font-weight: 500;
}
.cal-error-dismiss {
appearance: none;
border: none;
background: none;
color: var(--danger-800);
cursor: pointer;
font-size: var(--font-md);
padding: 0 var(--size-1);
opacity: 0.6;
line-height: 1;
}
.cal-error-dismiss:hover {
opacity: 1;
}
[data-theme="dark"] .cal-error-banner {
background: var(--danger-950);
border-bottom-color: var(--danger-900);
color: var(--danger-200);
}
[data-theme="dark"] .cal-error-dismiss {
color: var(--danger-200);
}
/* ── Source Filter Toggles ───────────────────────────────────────── */
.cal-source-toggles {
display: flex;
gap: var(--size-1);
padding: var(--size-2) var(--size-4);
border-bottom: var(--border-0);
background: var(--bg-1);
flex-wrap: wrap;
}
.cal-source-toggle {
appearance: none;
border: none;
padding: var(--size-1) var(--size-2);
border-radius: 9999px;
font-family: inherit;
font-size: var(--font-xs);
font-weight: 600;
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.cal-source-toggle:hover {
filter: brightness(0.95);
}
.cal-source-inactive {
background: var(--bg-2) !important;
color: var(--fg-2) !important;
border-left-color: transparent !important;
}
/* ── Agenda Day Header (with action slot) ────────────────────────── */
.cal-agenda-day-header-left {
display: flex;
align-items: baseline;
gap: var(--size-2);
flex: 1;
}
.cal-agenda-day-header {
justify-content: space-between;
}
/* ── Event Detail Dialog ─────────────────────────────────────────── */
.cal-detail-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
padding: var(--size-4);
animation: cal-fade-in 0.15s ease;
}
@keyframes cal-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.cal-detail {
background: var(--bg-1);
border-radius: var(--radius-md);
width: 100%;
max-width: 28rem;
box-shadow: var(--shadow-3);
border-left: 4px solid transparent;
animation: cal-dialog-in 0.15s ease;
overflow: hidden;
}
@keyframes cal-dialog-in {
from { opacity: 0; transform: scale(0.96) translateY(var(--size-2)); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.cal-detail-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--size-4);
padding: var(--size-4) var(--size-4) var(--size-2);
}
.cal-detail-title {
font-size: var(--font-base);
font-weight: 600;
line-height: 1.35;
display: flex;
align-items: baseline;
gap: var(--size-2);
flex: 1;
min-width: 0;
word-break: break-word;
}
.cal-detail-badge {
font-size: var(--font-xs);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: var(--size-1) var(--size-1);
border-radius: var(--radius-sm);
background: var(--bg-2);
color: var(--fg-1);
flex-shrink: 0;
white-space: nowrap;
}
.cal-detail-close {
appearance: none;
border: none;
background: none;
color: var(--fg-2);
cursor: pointer;
font-size: var(--font-lg);
line-height: 1;
padding: 0;
flex-shrink: 0;
transition: color 0.15s;
}
.cal-detail-close:hover {
color: var(--fg-0);
}
.cal-detail-body {
padding: 0 var(--size-4) var(--size-4);
display: flex;
flex-direction: column;
gap: var(--size-2);
}
.cal-detail-row {
display: flex;
align-items: baseline;
gap: var(--size-3);
}
.cal-detail-label {
font-size: var(--font-xs);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--fg-2);
width: var(--size-16);
flex-shrink: 0;
}
.cal-detail-value {
font-size: var(--font-sm);
color: var(--fg-0);
font-weight: 400;
}
.cal-detail-tags {
display: flex;
flex-wrap: wrap;
gap: var(--size-1);
}
.cal-detail-tag {
font-size: var(--font-xs);
font-weight: 600;
padding: var(--size-1) var(--size-2);
border-radius: 9999px;
background: var(--bg-2);
color: var(--fg-1);
}
[data-theme="dark"] .cal-detail {
box-shadow: var(--shadow-3);
}
[data-theme="dark"] .cal-detail-overlay {
background: rgba(0, 0, 0, 0.5);
}
/* ── Responsive ──────────────────────────────────────────────────── */
@media (max-width: 768px) {

View File

@@ -115,61 +115,3 @@
:label "Wed"
:events sample-events})]
(is (nil? result)))))
;; ── New component tests ─────────────────────────────────────────────
(deftest view-toggle-class-list-test
(testing "active"
(is (= ["cal-view-btn" "cal-view-btn-active"]
(cal-events/view-toggle-class-list {:active? true}))))
(testing "inactive"
(is (= ["cal-view-btn"]
(cal-events/view-toggle-class-list {:active? false})))))
(deftest source-toggle-class-list-test
(testing "active source"
(is (= ["cal-source-toggle" "cal-event-accent"]
(cal-events/source-toggle-class-list {:color :accent :active? true}))))
(testing "inactive source"
(is (= ["cal-source-toggle" "cal-event-danger" "cal-source-inactive"]
(cal-events/source-toggle-class-list {:color :danger :active? false})))))
(deftest view-toggle-component-test
(testing "renders view toggle (clj target)"
(let [result (cal-events/view-toggle {:view :month})]
(is (= :div (first result)))
(is (= "cal-view-toggle" (get-in result [1 :class]))))))
(deftest loading-indicator-test
(testing "renders loading indicator"
(let [result (cal-events/loading-indicator)]
(is (= :div (first result)))
(is (= "cal-loading" (get-in result [1 :class]))))))
(deftest error-banner-test
(testing "renders error with message"
(let [result (cal-events/error-banner {:message "Something broke"})]
(is (some? result))
(is (= :div (first result)))))
(testing "returns nil with no message"
(is (nil? (cal-events/error-banner {:message nil})))))
(deftest source-toggles-test
(testing "renders source toggles"
(let [result (cal-events/source-toggles
{:sources [{:name "Work" :color :accent :active? true}
{:name "Personal" :color :success :active? false}]})]
(is (some? result))
(is (= :div (first result)))))
(testing "returns nil with no sources"
(is (nil? (cal-events/source-toggles {:sources []})))))
(deftest event-detail-dialog-test
(testing "renders detail dialog with event"
(let [evt {:title "Meeting" :date "2026-03-29" :color :accent
:time-start "10:00" :tags ["work"] :source "cal:Work"}
result (cal-events/event-detail-dialog {:event evt})]
(is (some? result))
(is (= :div (first result)))))
(testing "returns nil with no event"
(is (nil? (cal-events/event-detail-dialog {:event nil})))))