diff options
| author | Ben Sima <ben@bensima.com> | 2025-11-27 18:57:44 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-11-27 18:57:44 -0500 |
| commit | ebc281f90389da3b064bb14888e7c4f81ae4df17 (patch) | |
| tree | cd538b0755b1aa687ab19fdf86585e376826204f /Omni | |
| parent | b5ac1e5984ee69b54414ef3c98ece1f143021de5 (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')
| -rw-r--r-- | Omni/Jr/Web.hs | 71 | ||||
| -rw-r--r-- | Omni/Jr/Web/Style.hs | 56 |
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" |
