Add breadcrumbs navigation below navbar, above page content. Especially useful for navigating deeply nested task hierarchies (epics > subtasks > sub-subtasks).
| Page | Breadcrumbs |
|------|-------------|
| / (homepage) | *none* |
| /tasks | Jr > Tasks |
| /tasks/t-123 (no parent) | Jr > Tasks > t-123 |
| /tasks/t-123 (parent: t-100) | Jr > Tasks > t-100 > t-123 |
| /tasks/t-50 (epic with children t-100, t-101) | Jr > Tasks > t-50 |
| /tasks/t-123 (grandparent: t-50 > t-100 > t-123) | Jr > Tasks > t-50 > t-100 > t-123 |
| /ready | Jr > Ready Queue |
| /blocked | Jr > Blocked |
| /intervention | Jr > Needs Intervention |
| /stats | Jr > Stats |
| /kb | Jr > Knowledge Base |
| /kb/42 | Jr > Knowledge Base > Fact #42 |
| /epics | Jr > Epics |
| /tasks/t-50/review | Jr > Tasks > t-50 > Review |
data Breadcrumb = Breadcrumb
{ crumbLabel :: Text
, crumbHref :: Maybe Text -- Nothing for current page (last crumb)
}
type Breadcrumbs = [Breadcrumb]
-- Build breadcrumbs for a task, including parent chain
taskBreadcrumbs :: [TaskCore.Task] -> TaskCore.Task -> Breadcrumbs
taskBreadcrumbs allTasks task =
let ancestors = getAncestors allTasks task -- returns [grandparent, parent, task]
taskCrumbs = map (\t -> Breadcrumb (TaskCore.taskId t) (Just ("/tasks/" <> TaskCore.taskId t))) (init ancestors)
currentCrumb = Breadcrumb (TaskCore.taskId task) Nothing
in [ Breadcrumb "Jr" (Just "/")
, Breadcrumb "Tasks" (Just "/tasks")
] ++ taskCrumbs ++ [currentCrumb]
getAncestors :: [TaskCore.Task] -> TaskCore.Task -> [TaskCore.Task]
getAncestors allTasks task =
case TaskCore.taskParent task of
Nothing -> [task]
Just pid -> case TaskCore.findTask pid allTasks of
Nothing -> [task]
Just parent -> getAncestors allTasks parent ++ [task]
renderBreadcrumbs :: (Monad m) => Breadcrumbs -> Lucid.HtmlT m ()
renderBreadcrumbs [] = pure ()
renderBreadcrumbs crumbs =
Lucid.nav_ [Lucid.class_ "breadcrumbs", Lucid.makeAttribute "aria-label" "Breadcrumb"] <| do
Lucid.ol_ [Lucid.class_ "breadcrumb-list"] <| do
traverse_ renderCrumb (zip [0..] crumbs)
where
renderCrumb (idx, Breadcrumb label mHref) = do
let isLast = idx == length crumbs - 1
Lucid.li_ [Lucid.class_ "breadcrumb-item"] <| do
when (idx > 0) <| Lucid.span_ [Lucid.class_ "breadcrumb-sep"] ">"
case mHref of
Just href -> Lucid.a_ [Lucid.href_ href] (Lucid.toHtml label)
Nothing -> Lucid.span_ [Lucid.class_ "breadcrumb-current"] (Lucid.toHtml label)
Modify pageBody or create wrapper that accepts optional breadcrumbs:
pageBodyWithCrumbs :: (Monad m) => Breadcrumbs -> Lucid.HtmlT m () -> Lucid.HtmlT m ()
pageBodyWithCrumbs crumbs content =
Lucid.body_ <| do
navbar
unless (null crumbs) <| do
Lucid.div_ [Lucid.class_ "breadcrumb-container"] <| do
Lucid.div_ [Lucid.class_ "container"] <| renderBreadcrumbs crumbs
Lucid.main_ [Lucid.class_ "page-content"] content
breadcrumbStyles :: Css
breadcrumbStyles = do
".breadcrumb-container" ? do
backgroundColor "#f9fafb"
borderBottom (px 1) solid "#e5e7eb"
padding (px 6) (px 0) (px 6) (px 0)
".breadcrumb-list" ? do
display flex
alignItems center
flexWrap Flexbox.wrap
Stylesheet.key "gap" ("4px" :: Text)
margin (px 0) (px 0) (px 0) (px 0)
padding (px 0) (px 0) (px 0) (px 0)
listStyleType none
fontSize (px 12)
".breadcrumb-item" ? do
display flex
alignItems center
Stylesheet.key "gap" ("4px" :: Text)
".breadcrumb-sep" ? do
color "#9ca3af"
userSelect none
".breadcrumb-current" ? do
color "#6b7280"
fontWeight (weight 500)
(".breadcrumb-list" ** a) ? do
color "#0066cc"
textDecoration none
(".breadcrumb-list" ** a) # hover ? textDecoration underline
Dark mode:
".breadcrumb-container" ? do
backgroundColor "#1f2937"
borderBottomColor "#374151"
".breadcrumb-sep" ? color "#6b7280"
".breadcrumb-current" ? color "#9ca3af"
1. Omni/Jr/Web.hs:
Breadcrumb type and Breadcrumbs aliasrenderBreadcrumbs functiontaskBreadcrumbs, getAncestors helperspageBodyWithCrumbs or modify existing pagespageBody (no crumbs), others use pageBodyWithCrumbs2. Omni/Jr/Web/Style.hs:
breadcrumbStyles functionstylesheet list1. Navigate to /tasks - see "Jr > Tasks" 2. Click a task with no parent - see "Jr > Tasks > t-xxx" 3. Click a task with parent - see full chain 4. Click a task with grandparent - see full chain 5. Click breadcrumb links - navigate correctly 6. Check on mobile - wraps gracefully 7. Check dark mode
No activity yet.