summaryrefslogtreecommitdiff
path: root/Omni/Jr
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-11-27 18:57:44 -0500
committerBen Sima <ben@bensima.com>2025-11-27 18:57:44 -0500
commitebc281f90389da3b064bb14888e7c4f81ae4df17 (patch)
treecd538b0755b1aa687ab19fdf86585e376826204f /Omni/Jr
parentb5ac1e5984ee69b54414ef3c98ece1f143021de5 (diff)
Click badge to show inline dropdown options
The implementation is complete. The changes I made: 1. **Web.hs** - Replaced the `statusBadgeWithForm` function from a `<sel - `statusBadgeWithForm` now creates a container div with the clickabl - `clickableBadge` renders a badge that, when clicked, toggles the "o - `statusDropdownOptions` renders all the status options as badge-sty - `statusOption` renders each status option as an HTMX form that upda 2. **Style.hs** - Added CSS styles for the new dropdown component: - `.status-badge-dropdown` - Container with relative positioning - `.status-badge-clickable` - Clickable badge styling with cursor poi - `.dropdown-arrow` - Arrow indicator styling - `.status-dropdown-menu` - Hidden dropdown that appears when parent - `.status-option-form` and `.status-dropdown-option` - Styling for o - Dark mode support for the dropdown menu Task-Id: t-157.2
Diffstat (limited to 'Omni/Jr')
-rw-r--r--Omni/Jr/Web.hs71
-rw-r--r--Omni/Jr/Web/Style.hs56
2 files changed, 91 insertions, 36 deletions
diff --git a/Omni/Jr/Web.hs b/Omni/Jr/Web.hs
index ce77bca..c7e2e44 100644
--- a/Omni/Jr/Web.hs
+++ b/Omni/Jr/Web.hs
@@ -276,31 +276,62 @@ multiColorProgressBar stats =
statusBadgeWithForm :: (Monad m) => TaskCore.Status -> Text -> Lucid.HtmlT m ()
statusBadgeWithForm status tid =
- let badgeClass = case status of
- TaskCore.Open -> "status-badge-select badge-open"
- TaskCore.InProgress -> "status-badge-select badge-inprogress"
- TaskCore.Review -> "status-badge-select badge-review"
- TaskCore.Approved -> "status-badge-select badge-approved"
- TaskCore.Done -> "status-badge-select badge-done"
- in Lucid.select_
- [ Lucid.id_ "status-badge-container",
- Lucid.name_ "status",
- Lucid.class_ badgeClass,
+ Lucid.div_
+ [ Lucid.id_ "status-badge-container",
+ Lucid.class_ "status-badge-dropdown"
+ ]
+ <| do
+ clickableBadge status tid
+ statusDropdownOptions status tid
+
+clickableBadge :: (Monad m) => TaskCore.Status -> Text -> Lucid.HtmlT m ()
+clickableBadge status _tid =
+ let (cls, label) = case status of
+ TaskCore.Open -> ("badge badge-open status-badge-clickable", "Open" :: Text)
+ TaskCore.InProgress -> ("badge badge-inprogress status-badge-clickable", "In Progress")
+ TaskCore.Review -> ("badge badge-review status-badge-clickable", "Review")
+ TaskCore.Approved -> ("badge badge-approved status-badge-clickable", "Approved")
+ TaskCore.Done -> ("badge badge-done status-badge-clickable", "Done")
+ in Lucid.span_
+ [ Lucid.class_ cls,
+ Lucid.makeAttribute "onclick" "this.parentElement.classList.toggle('open')"
+ ]
+ <| do
+ Lucid.toHtml label
+ Lucid.span_ [Lucid.class_ "dropdown-arrow"] " ▾"
+
+statusDropdownOptions :: (Monad m) => TaskCore.Status -> Text -> Lucid.HtmlT m ()
+statusDropdownOptions currentStatus tid =
+ Lucid.div_ [Lucid.class_ "status-dropdown-menu"] <| do
+ statusOption TaskCore.Open currentStatus tid
+ statusOption TaskCore.InProgress currentStatus tid
+ statusOption TaskCore.Review currentStatus tid
+ statusOption TaskCore.Approved currentStatus tid
+ statusOption TaskCore.Done currentStatus tid
+
+statusOption :: (Monad m) => TaskCore.Status -> TaskCore.Status -> Text -> Lucid.HtmlT m ()
+statusOption opt currentStatus tid =
+ let (cls, label) = case opt of
+ TaskCore.Open -> ("badge badge-open", "Open" :: Text)
+ TaskCore.InProgress -> ("badge badge-inprogress", "In Progress")
+ TaskCore.Review -> ("badge badge-review", "Review")
+ TaskCore.Approved -> ("badge badge-approved", "Approved")
+ TaskCore.Done -> ("badge badge-done", "Done")
+ isSelected = opt == currentStatus
+ optClass = cls <> " status-dropdown-option" <> if isSelected then " selected" else ""
+ in Lucid.form_
+ [ Lucid.class_ "status-option-form",
Lucid.makeAttribute "hx-post" ("/tasks/" <> tid <> "/status"),
Lucid.makeAttribute "hx-target" "#status-badge-container",
Lucid.makeAttribute "hx-swap" "outerHTML"
]
<| do
- statusOptionHtmx TaskCore.Open status
- statusOptionHtmx TaskCore.InProgress status
- statusOptionHtmx TaskCore.Review status
- statusOptionHtmx TaskCore.Approved status
- statusOptionHtmx TaskCore.Done status
- where
- statusOptionHtmx :: (Monad m2) => TaskCore.Status -> TaskCore.Status -> Lucid.HtmlT m2 ()
- statusOptionHtmx opt current =
- let attrs = [Lucid.value_ (tshow opt)] <> [Lucid.selected_ "selected" | opt == current]
- in Lucid.option_ attrs (Lucid.toHtml (tshow opt))
+ Lucid.input_ [Lucid.type_ "hidden", Lucid.name_ "status", Lucid.value_ (tshow opt)]
+ Lucid.button_
+ [ Lucid.type_ "submit",
+ Lucid.class_ optClass
+ ]
+ (Lucid.toHtml label)
renderTaskCard :: (Monad m) => TaskCore.Task -> Lucid.HtmlT m ()
renderTaskCard t =
diff --git a/Omni/Jr/Web/Style.hs b/Omni/Jr/Web/Style.hs
index e768cda..594fb21 100644
--- a/Omni/Jr/Web/Style.hs
+++ b/Omni/Jr/Web/Style.hs
@@ -519,25 +519,46 @@ statusBadges = do
".badge-done" ? do
backgroundColor "#d1fae5"
color "#065f46"
- ".status-badge-select" ? do
- Stylesheet.key "-webkit-appearance" ("none" :: Text)
- Stylesheet.key "-moz-appearance" ("none" :: Text)
- Stylesheet.key "appearance" ("none" :: Text)
+ ".status-badge-dropdown" ? do
+ position relative
display inlineBlock
- padding (px 2) (px 18) (px 2) (px 6)
- borderRadius (px 2) (px 2) (px 2) (px 2)
- fontSize (px 11)
- fontWeight (weight 500)
- whiteSpace nowrap
+ ".status-badge-clickable" ? do
+ cursor pointer
+ Stylesheet.key "user-select" ("none" :: Text)
+ ".status-badge-clickable" # hover ? do
+ opacity 0.85
+ ".dropdown-arrow" ? do
+ fontSize (px 8)
+ marginLeft (px 2)
+ opacity 0.7
+ ".status-dropdown-menu" ? do
+ display none
+ position absolute
+ left (px 0)
+ top (pct 100)
+ marginTop (px 2)
+ backgroundColor white
+ borderRadius (px 4) (px 4) (px 4) (px 4)
+ Stylesheet.key "box-shadow" ("0 2px 8px rgba(0,0,0,0.15)" :: Text)
+ zIndex 100
+ padding (px 4) (px 4) (px 4) (px 4)
+ minWidth (px 100)
+ ".status-badge-dropdown.open" |> ".status-dropdown-menu" ? do
+ display block
+ ".status-option-form" ? do
+ margin (px 0) (px 0) (px 0) (px 0)
+ padding (px 0) (px 0) (px 0) (px 0)
+ ".status-dropdown-option" ? do
+ display block
+ width (pct 100)
+ textAlign (alignSide sideLeft)
+ margin (px 2) (px 0) (px 2) (px 0)
border (px 0) none transparent
cursor pointer
- Stylesheet.key "background-image" ("url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Cpath fill='currentColor' d='M0 2l4 4 4-4z'/%3E%3C/svg%3E\")" :: Text)
- Stylesheet.key "background-repeat" ("no-repeat" :: Text)
- Stylesheet.key "background-position" ("right 4px center" :: Text)
- Stylesheet.key "background-size" ("8px" :: Text)
- ".status-badge-select" # hover ? do
- opacity 0.8
- ".status-badge-select" # focus ? do
+ transition "opacity" (ms 150) ease (sec 0)
+ ".status-dropdown-option" # hover ? do
+ opacity 0.7
+ ".status-dropdown-option.selected" ? do
Stylesheet.key "outline" ("2px solid #0066cc" :: Text)
Stylesheet.key "outline-offset" ("1px" :: Text)
@@ -1066,6 +1087,9 @@ darkModeStyles =
Stylesheet.key "box-shadow" ("0 2px 8px rgba(0,0,0,0.3)" :: Text)
".navbar-dropdown-item" ? color "#d1d5db"
".navbar-dropdown-item" # hover ? backgroundColor "#374151"
+ ".status-dropdown-menu" ? do
+ backgroundColor "#1f2937"
+ Stylesheet.key "box-shadow" ("0 2px 8px rgba(0,0,0,0.3)" :: Text)
".hamburger-line" ? backgroundColor "#d1d5db"
".nav-brand" ? color "#f3f4f6"
"h2" <> "h3" ? color "#d1d5db"