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.
- {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/PKG-INFO +18 -4
- {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/README.md +16 -2
- {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs/plugin.py +2 -10
- mkdocs_nested_tabs-0.2.0/mkdocs_nested_tabs/static/nested-tabs.css +93 -0
- mkdocs_nested_tabs-0.2.0/mkdocs_nested_tabs/static/nested-tabs.js +177 -0
- {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/PKG-INFO +18 -4
- {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/pyproject.toml +2 -2
- {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/tests/test_plugin.py +6 -12
- mkdocs_nested_tabs-0.1.0/mkdocs_nested_tabs/static/nested-tabs.css +0 -91
- mkdocs_nested_tabs-0.1.0/mkdocs_nested_tabs/static/nested-tabs.js +0 -142
- {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/LICENSE +0 -0
- {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs/__init__.py +0 -0
- {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/SOURCES.txt +0 -0
- {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/dependency_links.txt +0 -0
- {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/entry_points.txt +0 -0
- {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/requires.txt +0 -0
- {mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/top_level.txt +0 -0
- {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.
|
|
4
|
-
Summary:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
29
|
-
#
|
|
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.
|
|
4
|
-
Summary:
|
|
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
|
-
|
|
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
|
|
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.
|
|
8
|
-
description = "
|
|
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
|
|
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
|
|
37
|
-
|
|
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
|
|
36
|
+
assert content
|
|
41
37
|
|
|
42
|
-
#
|
|
43
|
-
#
|
|
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
|
-
})();
|
|
File without changes
|
|
File without changes
|
{mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/requires.txt
RENAMED
|
File without changes
|
{mkdocs_nested_tabs-0.1.0 → mkdocs_nested_tabs-0.2.0}/mkdocs_nested_tabs.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|