diff --git a/assets/scss/components/_sidebar.scss b/assets/scss/components/_sidebar.scss index 5e5863a6..7e5da3db 100644 --- a/assets/scss/components/_sidebar.scss +++ b/assets/scss/components/_sidebar.scss @@ -181,6 +181,38 @@ $sidebar-item-icon-column: calc(1.25em + #{$spacer * 0.25}); } } +// Hold the icon column open on rows that have no icon. +// +// A menu is free to mix the two - a "Home" entry among icon-bearing sections is +// the common case - and the row without an icon then starts its label at the +// padding edge while every neighbour starts one icon-width further right. The +// labels no longer form a column, and the odd row reads as misplaced rather +// than as merely unadorned. +// +// So such a row reserves the same column instead. `assets/sidebar.html` sets +// the class on a row that has no icon in a list where something else does, so +// the reservation follows the list it has to line up with: a list that uses no +// icons at all is untouched, and neither is a nested list of its own. +// +// This is the width alone - no `text-indent` to go with it, unlike the hanging +// indent above. There is no icon to pull the first line back past, and the +// wrapped lines of a row whose text starts at the padding edge already line up +// underneath it. Applies in both the collapsible and the plain sidebar, since +// what it corrects is the resting layout the two share; in icon-only mode the +// row has nothing left to show and the reserved column costs nothing. +// +// `!important` for the same reason the block above needs it: the base +// `.sidebar-item` padding carries one. +.sidebar-item.sidebar-item-icon-indent { + padding-left: calc(0.5rem + #{$sidebar-item-icon-column}) !important; +} + +// Group headers again sit on the wrapper's padding, so they reserve the column +// alone. They are not `.sidebar-item`, so this needs its own selector. +.sidebar-item-group > div > a.sidebar-item-icon-indent { + padding-left: $sidebar-item-icon-column; +} + .btn-toggle-group { padding: 0.25rem 0.5rem; font-weight: 600; diff --git a/layouts/_partials/assets/link.html b/layouts/_partials/assets/link.html index 8860b99f..3ccd3081 100644 --- a/layouts/_partials/assets/link.html +++ b/layouts/_partials/assets/link.html @@ -51,7 +51,20 @@ {{- else -}} {{- $destination = (strings.TrimPrefix (strings.TrimSuffix "/" site.BaseURL) $destination) -}} {{- end -}} - {{- if not $destination }}{{ $destination = "/" }}{{ end -}} + {{/* A destination that normalizes away is the site root. + + Stripping the base URL's path leaves nothing behind for a link to the site's own + home - "/" on a site rooted at "/", "/docs/" on one rooted at "/docs/" - and + `path.Clean` renders that empty string as ".". Both mean the same place, but only + the empty one is caught by an emptiness test: "." survives as a *relative* page + reference meaning "the page being rendered", so the lookup below resolves it to + the caller and the link quietly points at whatever page happened to render it. + + The sidebar is where that surfaces: its markup is cached per section against the + section root, so a menu entry linking to "/" comes out as a link to the section + root, then matches the current page in the client-side active pass and takes the + highlight from the entry that owns it. */}} + {{- if or (not $destination) (eq $destination ".") }}{{ $destination = "/" }}{{ end -}} {{/* cue and tab carry no schema default, so an absent value is nil and an explicit false is a real choice. "default" treats false as empty and would diff --git a/layouts/_partials/assets/sidebar.html b/layouts/_partials/assets/sidebar.html index 7fb29a66..b1917cad 100644 --- a/layouts/_partials/assets/sidebar.html +++ b/layouts/_partials/assets/sidebar.html @@ -79,11 +79,34 @@ {{- $args = merge $args (dict "menu" $menu) -}} {{- end -}} +{{/* + Does any entry in this list carry a leading icon? + + Icons are per entry, but alignment is a property of the list: as soon as one row leads + with one, every other row's label in that list starts an icon-width further left and the + column of labels breaks. The rows that need to reserve that column are therefore the ones + without an icon in a list that has them, which only the list knows - hence a single + lookup per sibling list, passed down to the rows as `iconColumn`. + + Scoped to the list rather than the whole sidebar on purpose: a nested list whose entries + are all icon-less is aligned already, and reserving a column there would indent it away + from the label it hangs under, changing every existing site that renders icons only on + its top level. +*/ -}} +{{- define "_partials/inline/sidebar/has-icons.html" -}} + {{- $found := false -}} + {{- range . -}} + {{- if .pre }}{{ $found = true }}{{ end -}} + {{- end -}} + {{- return $found -}} +{{- end -}} + {{- define "_partials/inline/sidebar/group-link.html" -}} {{- $page := .page -}} {{- $group := .group -}} {{- $baseURL := .baseURL -}} {{- $pre := $group.pre -}} + {{- $iconColumn := .iconColumn | default false -}} {{- $collapsible := .collapsible | default false -}} {{- $href := $group.link | default "" -}} {{- if and $href (not (hasPrefix $href "/")) -}} @@ -92,8 +115,10 @@ {{- if and $href (not (hasSuffix $href "/")) -}}{{- $href = printf "%s/" $href -}}{{- end -}} {{- $groupTitle := $group.title -}} {{- $groupTitle = partial "utilities/TitleCase.html" (dict "page" $page "text" $groupTitle) -}} + {{- $class := "sidebar-item text-decoration-none rounded w-100" -}} + {{- if and $iconColumn (not $pre) -}}{{- $class = printf "%s sidebar-item-icon-indent" $class -}}{{- end -}}