mkdocs-nested-tabs 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (18) hide show
  1. {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/PKG-INFO +18 -4
  2. {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/README.md +16 -2
  3. {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs/plugin.py +2 -10
  4. mkdocs_nested_tabs-0.2.0/mkdocs_nested_tabs/static/nested-tabs.css +93 -0
  5. mkdocs_nested_tabs-0.2.0/mkdocs_nested_tabs/static/nested-tabs.js +177 -0
  6. {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/PKG-INFO +18 -4
  7. {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/pyproject.toml +2 -2
  8. {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/tests/test_plugin.py +6 -12
  9. mkdocs_nested_tabs-0.1.0/mkdocs_nested_tabs/static/nested-tabs.css +0 -91
  10. mkdocs_nested_tabs-0.1.0/mkdocs_nested_tabs/static/nested-tabs.js +0 -142
  11. {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/LICENSE +0 -0
  12. {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs/__init__.py +0 -0
  13. {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/SOURCES.txt +0 -0
  14. {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/dependency_links.txt +0 -0
  15. {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/entry_points.txt +0 -0
  16. {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/requires.txt +0 -0
  17. {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/top_level.txt +0 -0
  18. {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/setup.cfg +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs-nested-tabs
3
- Version: 0.1.0
4
- Summary: Display two levels of navigation.tabs instead of Material's hover-only dropdown.
3
+ Version: 0.2.0
4
+ Summary: For Material for mkdocs, display two levels of navigation.tabs instead of a hover-only dropdown.
5
5
  Author-email: Luka Sherman <lukawritecode@gmail.com>
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/luka-sherman/mkdocs-nested-tabs
@@ -29,12 +29,12 @@ tab dropdown (one category's children at a time).
29
29
 
30
30
  ## Status
31
31
 
32
- Early scaffold, ported and generalized from a working implementation. Not yet published.
32
+ Published on PyPI. Ported and generalized from a working implementation.
33
33
 
34
34
  ## Install
35
35
 
36
36
  ```bash
37
- pip install mkdocs-nested-tabs # not yet published
37
+ pip install mkdocs-nested-tabs
38
38
  ```
39
39
 
40
40
  ```yaml
@@ -52,6 +52,10 @@ Requires `navigation.tabs` to be enabled — this plugin replaces that
52
52
  feature's tab bar on desktop widths (≥76.234375em, matching Material's own
53
53
  breakpoint), it doesn't work alongside a site with tabs disabled.
54
54
 
55
+ Compatible with `navigation.indexes` — a category label whose own index page
56
+ Material merges in becomes a real link to that page, with correct
57
+ active/current-page state.
58
+
55
59
  ## How it works
56
60
 
57
61
  Reads Material's own primary sidebar nav at runtime (which already contains
@@ -82,8 +86,18 @@ so it looks reasonable on any palette out of the box. Override via:
82
86
 
83
87
  ```css
84
88
  :root {
89
+ /* Default (inactive) label/link color */
85
90
  --md-nested-tabs-label-color: ...;
86
91
  --md-nested-tabs-link-color: ...;
92
+
93
+ /* Active label/link color — the label gets .nested-tabs__label--active
94
+ when one of its own pages is active, not just the page link itself */
95
+ --md-nested-tabs-label-active-color: ...;
96
+ --md-nested-tabs-link-active-color: ...;
97
+
98
+ /* Hover/focus label/link color */
99
+ --md-nested-tabs-label-hover-color: ...;
100
+ --md-nested-tabs-link-hover-color: ...;
87
101
  }
88
102
  ```
89
103
 
@@ -6,12 +6,12 @@ tab dropdown (one category's children at a time).
6
6
 
7
7
  ## Status
8
8
 
9
- Early scaffold, ported and generalized from a working implementation. Not yet published.
9
+ Published on PyPI. Ported and generalized from a working implementation.
10
10
 
11
11
  ## Install
12
12
 
13
13
  ```bash
14
- pip install mkdocs-nested-tabs # not yet published
14
+ pip install mkdocs-nested-tabs
15
15
  ```
16
16
 
17
17
  ```yaml
@@ -29,6 +29,10 @@ Requires `navigation.tabs` to be enabled — this plugin replaces that
29
29
  feature's tab bar on desktop widths (≥76.234375em, matching Material's own
30
30
  breakpoint), it doesn't work alongside a site with tabs disabled.
31
31
 
32
+ Compatible with `navigation.indexes` — a category label whose own index page
33
+ Material merges in becomes a real link to that page, with correct
34
+ active/current-page state.
35
+
32
36
  ## How it works
33
37
 
34
38
  Reads Material's own primary sidebar nav at runtime (which already contains
@@ -59,8 +63,18 @@ so it looks reasonable on any palette out of the box. Override via:
59
63
 
60
64
  ```css
61
65
  :root {
66
+ /* Default (inactive) label/link color */
62
67
  --md-nested-tabs-label-color: ...;
63
68
  --md-nested-tabs-link-color: ...;
69
+
70
+ /* Active label/link color — the label gets .nested-tabs__label--active
71
+ when one of its own pages is active, not just the page link itself */
72
+ --md-nested-tabs-label-active-color: ...;
73
+ --md-nested-tabs-link-active-color: ...;
74
+
75
+ /* Hover/focus label/link color */
76
+ --md-nested-tabs-label-hover-color: ...;
77
+ --md-nested-tabs-link-hover-color: ...;
64
78
  }
65
79
  ```
66
80
 
@@ -15,22 +15,14 @@ class NestedTabsPlugin(BasePlugin):
15
15
  """
16
16
 
17
17
  config_scheme = (
18
- # v0.1: static breakpoint/behavior, matching Material's own tabs
19
- # breakpoint (76.234375em). Making these configurable needs the
20
- # static JS/CSS to be templated per-build rather than served as
21
- # fixed package assets — planned, not yet implemented.
22
18
  ("enabled", config_options.Type(bool, default=True)),
23
19
  )
24
20
 
25
21
  def on_files(self, files, config):
26
22
  if not self.config["enabled"]:
27
23
  return files
28
- # File(path, src_dir, dest_dir, ...) mirrors the same relative `path`
29
- # under both src_dir and dest_dir — keeping the assets flat (no
30
- # subfolder) in mkdocs_nested_tabs/static/ avoids any src/dest
31
- # mismatch, at the cost of sitting at the built site's root rather
32
- # than under its own assets/ subfolder. Confirmed working against
33
- # tests/fixture_site (see tests/test_plugin.py).
24
+ # Keeping the assets flat (no subfolder) means src_dir and dest_dir
25
+ # share the same relative path, so File() needs no extra mapping.
34
26
  for name in ASSET_NAMES:
35
27
  files.append(
36
28
  File(name, src_dir=STATIC_DIR, dest_dir=config["site_dir"], use_directory_urls=False)
@@ -0,0 +1,93 @@
1
+ /* Matches Material's own navigation.tabs breakpoint. Below this width,
2
+ Material's native tabs (or the hamburger drawer) apply unmodified. */
3
+ @media screen and (min-width: 76.234375em) {
4
+ .md-tabs {
5
+ display: none;
6
+ }
7
+ }
8
+
9
+ /* Transparent so .md-header's own background shows through, same as .md-tabs. */
10
+ .nested-tabs {
11
+ display: none;
12
+ }
13
+
14
+ @media screen and (min-width: 76.234375em) {
15
+ .nested-tabs {
16
+ display: block;
17
+ background-color: transparent;
18
+ }
19
+
20
+ .nested-tabs__list {
21
+ display: flex;
22
+ list-style: none;
23
+ margin: 0 0.2rem;
24
+ padding: 0.5rem 0;
25
+ gap: 1.4rem;
26
+ }
27
+
28
+ .nested-tabs__group {
29
+ display: flex;
30
+ flex-direction: column;
31
+ gap: 0.15rem;
32
+ }
33
+
34
+ .nested-tabs__label {
35
+ font-size: 0.64rem;
36
+ font-weight: 700;
37
+ text-transform: uppercase;
38
+ letter-spacing: 0.03em;
39
+ color: var(--md-nested-tabs-label-color, var(--md-default-fg-color--light));
40
+ }
41
+
42
+ /* Set on a flat group's label when one of its own pages is the active
43
+ page (see nested-tabs.js) — lets a consumer color the category name
44
+ itself, not just the active page link beneath it. Falls back to the
45
+ same accent color .nested-tabs__link--active uses below, but exposed as
46
+ its own variable since a consumer may want the label's active color to
47
+ differ from the link's (e.g. a distinct "active parent" tone). */
48
+ .nested-tabs__label--active {
49
+ color: var(--md-nested-tabs-label-active-color, var(--md-accent-fg-color));
50
+ font-weight: 700;
51
+ }
52
+
53
+ /* Fallback-link groups need hover/active states; the normal <span> labels don't. */
54
+ .nested-tabs__label--link {
55
+ text-decoration: none;
56
+ }
57
+
58
+ .nested-tabs__label--link:hover,
59
+ .nested-tabs__label--link:focus-visible {
60
+ color: var(--md-nested-tabs-label-hover-color, var(--md-accent-fg-color));
61
+ text-decoration: underline;
62
+ }
63
+
64
+ .nested-tabs__pages {
65
+ display: flex;
66
+ list-style: none;
67
+ margin: 0;
68
+ padding: 0;
69
+ gap: 0.7rem;
70
+ }
71
+
72
+ .nested-tabs__link {
73
+ font-size: 0.7rem;
74
+ color: var(--md-nested-tabs-link-color, var(--md-default-fg-color));
75
+ text-decoration: none;
76
+ }
77
+
78
+ .nested-tabs__link:hover,
79
+ .nested-tabs__link:focus-visible {
80
+ color: var(--md-nested-tabs-link-hover-color, var(--md-accent-fg-color));
81
+ text-decoration: underline;
82
+ }
83
+
84
+ /* Also reused on a fallback-link label (.nested-tabs__label--link) when its
85
+ own overview page is active — see nested-tabs.js. Named to match
86
+ --md-nested-tabs-label-active-color above rather than hardcoding
87
+ --md-accent-fg-color directly, so a consumer can override the two
88
+ independently if their active-page and active-label colors differ. */
89
+ .nested-tabs__link--active {
90
+ color: var(--md-nested-tabs-link-active-color, var(--md-accent-fg-color));
91
+ font-weight: 700;
92
+ }
93
+ }
@@ -0,0 +1,177 @@
1
+ (function () {
2
+ // Reads Material's own primary sidebar nav, which already lists every
3
+ // top-level category regardless of which page is active, so this stays
4
+ // in sync with mkdocs.yml's nav: block with no separate config needed.
5
+ // A category with a third nesting level doesn't fit the "label + page
6
+ // list" shape this renders, so it falls back to a single link.
7
+
8
+ function buildNestedTabs() {
9
+ const primaryNav = document.querySelector(
10
+ '[data-md-component="sidebar"][data-md-type="navigation"] .md-nav--primary'
11
+ );
12
+ if (!primaryNav) return null;
13
+
14
+ const topList = primaryNav.querySelector(":scope > ul.md-nav__list");
15
+ if (!topList) return null;
16
+
17
+ const nav = document.createElement("nav");
18
+ nav.className = "nested-tabs";
19
+ nav.setAttribute("aria-label", "Categories");
20
+
21
+ // Matches .md-tabs's own .md-grid wrapper so this row aligns with the
22
+ // rest of the header instead of running edge-to-edge.
23
+ const grid = document.createElement("div");
24
+ grid.className = "md-grid nested-tabs__grid";
25
+
26
+ const list = document.createElement("ul");
27
+ list.className = "nested-tabs__list";
28
+
29
+ topList.querySelectorAll(":scope > li.md-nav__item--nested").forEach(function (section) {
30
+ // With navigation.indexes enabled, Material wraps the link version in
31
+ // an extra <div class="md-nav__container"> instead of putting the <a>
32
+ // directly on the <li> — without that third alternative here, labelEl
33
+ // comes back null and the whole section silently disappears from this
34
+ // row, not just its active-state handling.
35
+ const labelEl = section.querySelector(
36
+ ":scope > label.md-nav__link, :scope > a.md-nav__link, :scope > .md-nav__container > a.md-nav__link"
37
+ );
38
+ const nestedNav = section.querySelector(":scope > nav.md-nav");
39
+ if (!labelEl || !nestedNav) return;
40
+
41
+ const categoryLabel = labelEl.querySelector(".md-ellipsis")
42
+ ? labelEl.querySelector(".md-ellipsis").textContent.trim()
43
+ : labelEl.textContent.trim();
44
+
45
+ // With navigation.indexes enabled, Material merges a section's own
46
+ // index page into its label — labelEl becomes a real <a> (not a
47
+ // <label> toggle) pointing at that page, and drops it from the child
48
+ // list entirely. So the label itself, not just one of its children,
49
+ // can be the active page.
50
+ const labelIsLink = labelEl.tagName === "A";
51
+ const labelIsActive = labelIsLink && labelEl.classList.contains("md-nav__link--active");
52
+
53
+ const childItems = Array.from(nestedNav.querySelectorAll(":scope > ul.md-nav__list > li.md-nav__item"));
54
+ const pageLinks = [];
55
+ let flat = true;
56
+ for (const pageItem of childItems) {
57
+ // A sub-category has no direct link, only its own nested nav.
58
+ const link = pageItem.querySelector(":scope > a.md-nav__link");
59
+ if (!link) {
60
+ flat = false;
61
+ break;
62
+ }
63
+ pageLinks.push(link);
64
+ }
65
+
66
+ const group = document.createElement("li");
67
+ group.className = "nested-tabs__group";
68
+
69
+ if (flat && pageLinks.length > 0) {
70
+ // labelIsLink means navigation.indexes merged this section's own
71
+ // index page into the label — render it as a real, clickable <a>
72
+ // (same shape as the fallback branch's label--link below) rather
73
+ // than an inert <span>, so that page stays reachable from here.
74
+ const label = document.createElement(labelIsLink ? "a" : "span");
75
+ label.className = labelIsLink ? "nested-tabs__label nested-tabs__label--link" : "nested-tabs__label";
76
+ if (labelIsLink) {
77
+ label.href = labelEl.getAttribute("href");
78
+ }
79
+ label.textContent = categoryLabel;
80
+ group.appendChild(label);
81
+
82
+ const pages = document.createElement("ul");
83
+ pages.className = "nested-tabs__pages";
84
+ let groupHasActive = labelIsActive;
85
+ pageLinks.forEach(function (link) {
86
+ const item = document.createElement("li");
87
+ const a = document.createElement("a");
88
+ a.className = "nested-tabs__link";
89
+ a.href = link.getAttribute("href");
90
+ a.textContent = link.querySelector(".md-ellipsis")
91
+ ? link.querySelector(".md-ellipsis").textContent.trim()
92
+ : link.textContent.trim();
93
+ if (link.classList.contains("md-nav__link--active")) {
94
+ a.classList.add("nested-tabs__link--active");
95
+ a.setAttribute("aria-current", "page");
96
+ groupHasActive = true;
97
+ }
98
+ item.appendChild(a);
99
+ pages.appendChild(item);
100
+ });
101
+ group.appendChild(pages);
102
+ // Lets a consumer style the category label itself (e.g. "Flow") when
103
+ // one of its own pages — or, with navigation.indexes, the label's
104
+ // own merged index page — is the active one, without reaching for a
105
+ // :has() selector from outside — see nested-tabs.css.
106
+ if (groupHasActive) {
107
+ label.classList.add("nested-tabs__label--active");
108
+ // Only when the label's own page is the active one, not merely a
109
+ // descendant's — aria-current="page" would misrepresent a parent
110
+ // category as literally being the current page otherwise.
111
+ if (labelIsActive) {
112
+ label.setAttribute("aria-current", "page");
113
+ }
114
+ }
115
+ } else {
116
+ // navigation.indexes merges a section's own index page into its own
117
+ // label (see labelIsLink above) before this branch ever sees its
118
+ // children — so when that happened, labelEl already has everything
119
+ // needed (href + active state) and searching descendants for an
120
+ // "overview" page would find nothing, dropping the whole section.
121
+ // Only fall back to that descendant search for the case with no
122
+ // navigation.indexes merge, where a plain child page (e.g. an
123
+ // explicit "All" entry) serves as the overview link instead.
124
+ let overviewHref;
125
+ let overviewIsActive;
126
+ if (labelIsLink) {
127
+ overviewHref = labelEl.getAttribute("href");
128
+ overviewIsActive = labelIsActive;
129
+ } else {
130
+ const overviewLink = nestedNav.querySelector(
131
+ ":scope > ul.md-nav__list > li.md-nav__item > a.md-nav__link"
132
+ );
133
+ if (!overviewLink) return;
134
+ overviewHref = overviewLink.getAttribute("href");
135
+ overviewIsActive = overviewLink.classList.contains("md-nav__link--active");
136
+ }
137
+
138
+ const label = document.createElement("a");
139
+ label.className = "nested-tabs__label nested-tabs__label--link";
140
+ label.href = overviewHref;
141
+ label.textContent = categoryLabel;
142
+ if (overviewIsActive) {
143
+ label.classList.add("nested-tabs__link--active");
144
+ label.setAttribute("aria-current", "page");
145
+ }
146
+ group.appendChild(label);
147
+ }
148
+
149
+ list.appendChild(group);
150
+ });
151
+
152
+ if (!list.children.length) return null;
153
+
154
+ grid.appendChild(list);
155
+ nav.appendChild(grid);
156
+ return nav;
157
+ }
158
+
159
+ // .md-tabs lives inside .md-header, so inserting after it makes this row
160
+ // a header child too and it inherits the header's sticky positioning for free.
161
+ function render() {
162
+ const existing = document.querySelector(".nested-tabs");
163
+ if (existing) existing.remove();
164
+
165
+ const nestedTabs = buildNestedTabs();
166
+ const tabs = document.querySelector(".md-tabs");
167
+ if (nestedTabs && tabs) {
168
+ tabs.insertAdjacentElement("afterend", nestedTabs);
169
+ }
170
+ }
171
+
172
+ if (window.document$) {
173
+ window.document$.subscribe(render);
174
+ } else {
175
+ document.addEventListener("DOMContentLoaded", render);
176
+ }
177
+ })();
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs-nested-tabs
3
- Version: 0.1.0
4
- Summary: Display two levels of navigation.tabs instead of Material's hover-only dropdown.
3
+ Version: 0.2.0
4
+ Summary: For Material for mkdocs, display two levels of navigation.tabs instead of a hover-only dropdown.
5
5
  Author-email: Luka Sherman <lukawritecode@gmail.com>
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/luka-sherman/mkdocs-nested-tabs
@@ -29,12 +29,12 @@ tab dropdown (one category's children at a time).
29
29
 
30
30
  ## Status
31
31
 
32
- Early scaffold, ported and generalized from a working implementation. Not yet published.
32
+ Published on PyPI. Ported and generalized from a working implementation.
33
33
 
34
34
  ## Install
35
35
 
36
36
  ```bash
37
- pip install mkdocs-nested-tabs # not yet published
37
+ pip install mkdocs-nested-tabs
38
38
  ```
39
39
 
40
40
  ```yaml
@@ -52,6 +52,10 @@ Requires `navigation.tabs` to be enabled — this plugin replaces that
52
52
  feature's tab bar on desktop widths (≥76.234375em, matching Material's own
53
53
  breakpoint), it doesn't work alongside a site with tabs disabled.
54
54
 
55
+ Compatible with `navigation.indexes` — a category label whose own index page
56
+ Material merges in becomes a real link to that page, with correct
57
+ active/current-page state.
58
+
55
59
  ## How it works
56
60
 
57
61
  Reads Material's own primary sidebar nav at runtime (which already contains
@@ -82,8 +86,18 @@ so it looks reasonable on any palette out of the box. Override via:
82
86
 
83
87
  ```css
84
88
  :root {
89
+ /* Default (inactive) label/link color */
85
90
  --md-nested-tabs-label-color: ...;
86
91
  --md-nested-tabs-link-color: ...;
92
+
93
+ /* Active label/link color — the label gets .nested-tabs__label--active
94
+ when one of its own pages is active, not just the page link itself */
95
+ --md-nested-tabs-label-active-color: ...;
96
+ --md-nested-tabs-link-active-color: ...;
97
+
98
+ /* Hover/focus label/link color */
99
+ --md-nested-tabs-label-hover-color: ...;
100
+ --md-nested-tabs-link-hover-color: ...;
87
101
  }
88
102
  ```
89
103
 
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mkdocs-nested-tabs"
7
- version = "0.1.0"
8
- description = "Display two levels of navigation.tabs instead of Material's hover-only dropdown."
7
+ version = "0.2.0"
8
+ description = "For Material for mkdocs, display two levels of navigation.tabs instead of a hover-only dropdown."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
11
11
  license = { text = "MIT" }
@@ -1,7 +1,4 @@
1
- """Builds tests/fixture_site (a tiny navigation.tabs project with a flat
2
- 2-page category and a nested-subcategory category) and checks the plugin's
3
- actual effect on the built output — not just that on_config/on_files run
4
- without raising."""
1
+ """Builds tests/fixture_site and checks the plugin's effect on the built output."""
5
2
 
6
3
  import subprocess
7
4
  import sys
@@ -33,13 +30,10 @@ def test_assets_are_built_and_referenced(tmp_path):
33
30
 
34
31
 
35
32
  def test_flat_category_lists_both_pages():
36
- """Section A (Page A1, Page A2) has no third nesting level, so it should
37
- render as a label with both page links underneath — the "flat" branch
38
- in nested-tabs.js."""
33
+ """Section A has no third nesting level, so it should render as a label
34
+ with both page links underneath — the "flat" branch in nested-tabs.js."""
39
35
  content = (Path(__file__).parent / "fixture_site" / "docs" / "index.md").read_text()
40
- assert content # placeholder for the DOM-level assertion below
36
+ assert content
41
37
 
42
- # The rendering itself happens client-side (nested-tabs.js reads the
43
- # built primary-sidebar nav at runtime), so this needs a real browser —
44
- # see the manual verification steps in the README until a Playwright
45
- # tier is added here to match python-field-guide's own test setup.
38
+ # nested-tabs.js renders client-side by reading the built sidebar nav at
39
+ # runtime, so this needs a real browser — see README for manual steps.
@@ -1,91 +0,0 @@
1
- /* True desktop only (matches Material's own navigation.tabs breakpoint):
2
- .mdx-nested-tabs replaces the native tab bar there — each category's name
3
- is already its own column header in this row, so keeping both would show
4
- every category name twice, stacked on top of itself. Below this width,
5
- Material's native tabs (or the hamburger drawer) still apply unmodified. */
6
- @media screen and (min-width: 76.234375em) {
7
- .md-tabs {
8
- display: none;
9
- }
10
- }
11
-
12
- /* .mdx-nested-tabs is inserted as a DOM child of .md-header, right after
13
- .md-tabs (see nested-tabs.js) — so it's a genuine part of the header, not
14
- a separate bar bolted on below: it rides along with .md-header's own
15
- sticky positioning and background automatically. Transparent background
16
- for the same reason .md-tabs is transparent: lets the header's own
17
- background/artwork show through instead of drawing a second, visually
18
- separate box. */
19
- .mdx-nested-tabs {
20
- display: none;
21
- }
22
-
23
- @media screen and (min-width: 76.234375em) {
24
- .mdx-nested-tabs {
25
- display: block;
26
- background-color: transparent;
27
- }
28
-
29
- .mdx-nested-tabs__list {
30
- display: flex;
31
- list-style: none;
32
- margin: 0 0.2rem;
33
- padding: 0.5rem 0;
34
- gap: 1.4rem;
35
- }
36
-
37
- .mdx-nested-tabs__group {
38
- display: flex;
39
- flex-direction: column;
40
- gap: 0.15rem;
41
- }
42
-
43
- .mdx-nested-tabs__label {
44
- font-size: 0.64rem;
45
- font-weight: 700;
46
- text-transform: uppercase;
47
- letter-spacing: 0.03em;
48
- /* Falls back to Material's own faded-foreground variable so this reads
49
- reasonably on any palette out of the box; a consumer can override
50
- --md-nested-tabs-label-color directly without touching this file. */
51
- color: var(--md-nested-tabs-label-color, var(--md-default-fg-color--light));
52
- }
53
-
54
- /* A category with a third nesting level (see nested-tabs.js) renders as a
55
- single link instead of a label + page list — needs its own hover/active
56
- state, unlike the other groups' inert <span> labels. */
57
- .mdx-nested-tabs__label--link {
58
- text-decoration: none;
59
- }
60
-
61
- .mdx-nested-tabs__label--link:hover,
62
- .mdx-nested-tabs__label--link:focus-visible {
63
- color: var(--md-accent-fg-color);
64
- text-decoration: underline;
65
- }
66
-
67
- .mdx-nested-tabs__pages {
68
- display: flex;
69
- list-style: none;
70
- margin: 0;
71
- padding: 0;
72
- gap: 0.7rem;
73
- }
74
-
75
- .mdx-nested-tabs__link {
76
- font-size: 0.7rem;
77
- color: var(--md-nested-tabs-link-color, var(--md-default-fg-color));
78
- text-decoration: none;
79
- }
80
-
81
- .mdx-nested-tabs__link:hover,
82
- .mdx-nested-tabs__link:focus-visible {
83
- color: var(--md-accent-fg-color);
84
- text-decoration: underline;
85
- }
86
-
87
- .mdx-nested-tabs__link--active {
88
- color: var(--md-accent-fg-color);
89
- font-weight: 700;
90
- }
91
- }
@@ -1,142 +0,0 @@
1
- (function () {
2
- // Desktop-only nav row that replaces Material's native tab bar: every
3
- // navigation.tabs category shown at once with all of its child pages
4
- // listed underneath, instead of only being reachable via Material's
5
- // hover-triggered tab dropdown (a gap explicitly declined upstream —
6
- // see squidfunk/mkdocs-material#4765). Built by reading Material's own
7
- // primary sidebar nav, which already contains the full site tree (every
8
- // section, not just the active one — Material only flags the active
9
- // top-level section with --section; every other one just gets --nested,
10
- // regardless of which page you're on) rather than requiring a
11
- // hand-maintained config list, so this stays in sync with mkdocs.yml's
12
- // nav: block automatically.
13
- //
14
- // A category with a third nesting level (a sub-category with its own
15
- // children, rather than a flat list of pages) doesn't fit the
16
- // "category + pages" shape this row renders — those fall back to a
17
- // single link, using the first leaf page found inside them as the
18
- // target (usually an "index"/"All" overview page).
19
-
20
- function buildNestedTabs() {
21
- const primaryNav = document.querySelector(
22
- '[data-md-component="sidebar"][data-md-type="navigation"] .md-nav--primary'
23
- );
24
- if (!primaryNav) return null;
25
-
26
- const topList = primaryNav.querySelector(":scope > ul.md-nav__list");
27
- if (!topList) return null;
28
-
29
- const nav = document.createElement("nav");
30
- nav.className = "mdx-nested-tabs";
31
- nav.setAttribute("aria-label", "Categories");
32
-
33
- // .md-tabs wraps its own list in a bare .md-grid div for the same
34
- // centered max-width alignment as .md-header__inner (logo/title/search)
35
- // — match that structure so this row lines up with the rest of the
36
- // header instead of running edge-to-edge.
37
- const grid = document.createElement("div");
38
- grid.className = "md-grid mdx-nested-tabs__grid";
39
-
40
- const list = document.createElement("ul");
41
- list.className = "mdx-nested-tabs__list";
42
-
43
- topList.querySelectorAll(":scope > li.md-nav__item--nested").forEach(function (section) {
44
- const labelEl = section.querySelector(":scope > label.md-nav__link, :scope > a.md-nav__link");
45
- const nestedNav = section.querySelector(":scope > nav.md-nav");
46
- if (!labelEl || !nestedNav) return;
47
-
48
- const categoryLabel = labelEl.querySelector(".md-ellipsis")
49
- ? labelEl.querySelector(".md-ellipsis").textContent.trim()
50
- : labelEl.textContent.trim();
51
-
52
- const childItems = Array.from(nestedNav.querySelectorAll(":scope > ul.md-nav__list > li.md-nav__item"));
53
- const pageLinks = [];
54
- let flat = true;
55
- for (const pageItem of childItems) {
56
- // A real page item links directly to itself; a further-nested
57
- // sub-category only has a label + its own nested nav, same shape
58
- // as this top-level section.
59
- const link = pageItem.querySelector(":scope > a.md-nav__link");
60
- if (!link) {
61
- flat = false;
62
- break;
63
- }
64
- pageLinks.push(link);
65
- }
66
-
67
- const group = document.createElement("li");
68
- group.className = "mdx-nested-tabs__group";
69
-
70
- if (flat && pageLinks.length > 0) {
71
- const label = document.createElement("span");
72
- label.className = "mdx-nested-tabs__label";
73
- label.textContent = categoryLabel;
74
- group.appendChild(label);
75
-
76
- const pages = document.createElement("ul");
77
- pages.className = "mdx-nested-tabs__pages";
78
- pageLinks.forEach(function (link) {
79
- const item = document.createElement("li");
80
- const a = document.createElement("a");
81
- a.className = "mdx-nested-tabs__link";
82
- a.href = link.getAttribute("href");
83
- a.textContent = link.querySelector(".md-ellipsis")
84
- ? link.querySelector(".md-ellipsis").textContent.trim()
85
- : link.textContent.trim();
86
- if (link.classList.contains("md-nav__link--active")) {
87
- a.classList.add("mdx-nested-tabs__link--active");
88
- a.setAttribute("aria-current", "page");
89
- }
90
- item.appendChild(a);
91
- pages.appendChild(item);
92
- });
93
- group.appendChild(pages);
94
- } else {
95
- const overviewLink = nestedNav.querySelector(
96
- ":scope > ul.md-nav__list > li.md-nav__item > a.md-nav__link"
97
- );
98
- if (!overviewLink) return;
99
-
100
- const label = document.createElement("a");
101
- label.className = "mdx-nested-tabs__label mdx-nested-tabs__label--link";
102
- label.href = overviewLink.getAttribute("href");
103
- label.textContent = categoryLabel;
104
- if (overviewLink.classList.contains("md-nav__link--active")) {
105
- label.classList.add("mdx-nested-tabs__link--active");
106
- label.setAttribute("aria-current", "page");
107
- }
108
- group.appendChild(label);
109
- }
110
-
111
- list.appendChild(group);
112
- });
113
-
114
- if (!list.children.length) return null;
115
-
116
- grid.appendChild(list);
117
- nav.appendChild(grid);
118
- return nav;
119
- }
120
-
121
- // .md-tabs lives inside .md-header (not below it, despite how it reads
122
- // visually), so inserting right after it via insertAdjacentElement makes
123
- // this row a header child too — it rides along with .md-header's own
124
- // sticky positioning and decorative background for free, no separate
125
- // sticky/offset math needed here.
126
- function render() {
127
- const existing = document.querySelector(".mdx-nested-tabs");
128
- if (existing) existing.remove();
129
-
130
- const nestedTabs = buildNestedTabs();
131
- const tabs = document.querySelector(".md-tabs");
132
- if (nestedTabs && tabs) {
133
- tabs.insertAdjacentElement("afterend", nestedTabs);
134
- }
135
- }
136
-
137
- if (window.document$) {
138
- window.document$.subscribe(render);
139
- } else {
140
- document.addEventListener("DOMContentLoaded", render);
141
- }
142
- })();