Skip to content

Commit 83f2e33

Browse files
committed
NEW: Add headers
1 parent af6277a commit 83f2e33

14 files changed

Lines changed: 368 additions & 35 deletions

File tree

docs/_templates/sections/header.html

Lines changed: 0 additions & 2 deletions
This file was deleted.

docs/conf.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,54 @@
127127
"so double-check your custom CSS rules!⚠️"
128128
),
129129
# For testing
130+
"header": {
131+
"brand": {
132+
"type": "image",
133+
"src": "https://executablebooks.org/en/latest/_static/logo.svg",
134+
"url": "https://sphinx-book-theme.readthedocs.io",
135+
},
136+
"start": [
137+
{
138+
"type": "text",
139+
"content": "Jupyter Book",
140+
"url": "https://jupyterbook.org",
141+
},
142+
{
143+
"type": "dropdown",
144+
"content": "EBP Projects",
145+
"items": [
146+
{"content": "google", "url": "https://google.com"},
147+
{"content": "jupyter", "url": "https://jupyter.org"},
148+
],
149+
},
150+
{
151+
"type": "dropdown",
152+
"content": "MyST Markdown",
153+
"items": [
154+
{"content": "google", "url": "https://google.com"},
155+
{"content": "jupyter", "url": "https://jupyter.org"},
156+
],
157+
},
158+
],
159+
"end": [
160+
{"type": "button", "content": "end", "url": "https://google.com"},
161+
{
162+
"type": "icon-links",
163+
"icons": [
164+
{
165+
"url": "https://twitter.com/executablebooks",
166+
"name": "Twitter",
167+
"icon": "fab fa-twitter-square",
168+
},
169+
{
170+
"url": "https://github.com/orgs/executablebooks/discussions",
171+
"name": "Forum",
172+
"icon": "fas fa-comments",
173+
},
174+
],
175+
},
176+
],
177+
}
130178
# "use_fullscreen_button": False,
131179
# "home_page_in_toc": True,
132180
# "single_page": True,

src/sphinx_book_theme/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .header_buttons import prep_header_buttons, add_header_buttons
1515
from .header_buttons.launch import add_launch_buttons
1616
from ._transforms import HandleFootnoteTransform
17+
from ._components import COMPONENT_FUNCS
1718

1819
__version__ = "0.3.2"
1920
"""sphinx-book-theme version"""
@@ -62,6 +63,26 @@ def add_metadata_to_page(app, pagename, templatename, context, doctree):
6263
context.get("theme_search_bar_text", "Search the docs ...")
6364
)
6465

66+
# Define the function render the above
67+
def render_component(component):
68+
component_copy = component.copy()
69+
70+
# We use `type` to denote different kinds of components
71+
kind = component_copy.pop("type")
72+
if kind not in COMPONENT_FUNCS:
73+
SPHINX_LOGGER.warn(f"Unknown component type: {kind}")
74+
return
75+
try:
76+
output = COMPONENT_FUNCS[kind](app, context, **component_copy)
77+
except Exception as exc:
78+
msg = f"Component render failure for:\n{component}\n\n"
79+
msg += f"Exception: {exc}"
80+
SPHINX_LOGGER.warn(msg)
81+
return
82+
return output
83+
84+
context["theme_render_component"] = render_component
85+
6586

6687
@lru_cache(maxsize=None)
6788
def _gen_hash(path: str) -> str:
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""Functions to compile HTML components to be placed on a page.
2+
3+
These are meant to be used by Jinja templates via the Sphinx HTML context.
4+
5+
A dictionary defines the components that are available to the theme.
6+
Keys of this dictionary should be the `"type"` values that users provide in
7+
their configuration.
8+
The remaining values in the user configuration are passed as kwargs to the func.
9+
"""
10+
from sphinx.util import logging
11+
12+
SPHINX_LOGGER = logging.getLogger(__name__)
13+
14+
15+
# Add functions to render header components
16+
def component_text(app, context, content="", url="", classes=[]):
17+
classes = " ".join(classes)
18+
html = f"<span class='component-text {classes}'>{content}</span>"
19+
if url:
20+
html = f'<a href="{url}" class="link-primary">{html}</a>'
21+
return html
22+
23+
24+
def component_button(app, context, content="", url="", onclick="", classes=[]):
25+
if url and onclick:
26+
raise Exception("Button component cannot have both url and onclick specified.")
27+
classes = " ".join(classes)
28+
if onclick:
29+
onclick = ' onclick="{onclick}"'
30+
31+
classes = " ".join(classes)
32+
html = f"""
33+
<button class="btn btn-outline-primary {classes}"{onclick} type="button">
34+
{content}
35+
</button>
36+
"""
37+
if url:
38+
html = f'<a href="{url}">{html}</a>'
39+
40+
return html
41+
42+
43+
def component_image(app, context, src="", url="", classes=[]):
44+
if not src.startswith("http"):
45+
src = context["pathto"](src, 1)
46+
html = f"""
47+
<img src={src}>
48+
"""
49+
if url:
50+
html = f"<a href={url}>{html}</a>"
51+
return html
52+
53+
54+
def component_html(app, context, html=""):
55+
return html
56+
57+
58+
def component_dropdown(app, context, content="", items=[]):
59+
dropdown_items = []
60+
for component in items:
61+
link = f"""
62+
<a href="{component['url']}" class="dropdown-item">{component['content']}</a>
63+
"""
64+
dropdown_items.append(link)
65+
dropdown_items = "\n".join(dropdown_items)
66+
html = f"""
67+
<div class="dropdown">
68+
<button class="btn dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
69+
{content}
70+
</button>
71+
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
72+
{dropdown_items}
73+
</div>
74+
</div>
75+
""" # noqa
76+
return html
77+
78+
79+
def component_icon_links(app, context, icons, classes=[]):
80+
context = {"theme_icon_links": icons}
81+
# Add the pydata theme icon-links macro as a function we can re-use
82+
return app.builder.templates.render("icon-links.html", context)
83+
84+
85+
COMPONENT_FUNCS = {
86+
"text": component_text,
87+
"button": component_button,
88+
"html": component_html,
89+
"image": component_image,
90+
"icon-links": component_icon_links,
91+
"dropdown": component_dropdown,
92+
}

src/sphinx_book_theme/assets/styles/abstracts/_variables.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ $zindex-offcanvas: 1100; // We increase this to be over the tooltips
2222
$header-article-height: 3em;
2323
$leftbar-width-mobile: 75%;
2424
$leftbar-width-wide: 275px;
25+
$leftbar-padding: 1rem 1rem 0 1.5rem;
2526
$toc-width-mobile: 75%;
2627
// Main content, to leave room for the margin
2728
$content-max-width: 70%;

src/sphinx_book_theme/assets/styles/components/_buttons.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
}
4242

4343
.menu-dropdown__content {
44-
// Hide by default, we'll show on hover
44+
// Hide by default, we'll show on click
4545
position: absolute;
4646
visibility: hidden;
4747
opacity: 0;

src/sphinx_book_theme/assets/styles/index.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
@import "sections/article";
2121
@import "sections/footer-article";
2222
@import "sections/footer-content";
23+
@import "sections/header-announcement";
2324
@import "sections/header-article";
24-
@import "sections/headers";
25+
@import "sections/header-primary";
2526
@import "sections/sidebar-primary";
2627
@import "sections/sidebar-secondary";
2728
@import "sections/sidebars-toggle";
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.announcement {
2+
width: 100%;
3+
text-align: center;
4+
background-color: #616161;
5+
color: white;
6+
padding: 0.4em 12.5%; // Horizontal padding so the width is 75%
7+
8+
@media (max-width: $breakpoint-md) {
9+
// Announcements can take a bit more width on mobile
10+
padding: 0.4em 2%;
11+
}
12+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* A few different header CSS rules
3+
*/
4+
@mixin max-height-header() {
5+
// Fix max height so our header items don't spill over
6+
max-height: 4rem;
7+
@media (max-width: $breakpoint-md) {
8+
max-height: 3rem;
9+
}
10+
}
11+
12+
// The parent div that will expand / contract
13+
.header {
14+
display: flex;
15+
border-bottom: $border-thin;
16+
17+
// On narrow screens, cap the height unless you click the hamburger menu
18+
@include max-height-header;
19+
@media (max-width: $breakpoint-md) {
20+
transition: max-height $animation-time ease-out;
21+
overflow-y: hidden;
22+
}
23+
24+
// Navigation links
25+
a,
26+
div.dropdown button {
27+
color: $non-content-grey;
28+
29+
&:hover {
30+
color: rgba(var(--pst-color-link), 1);
31+
text-decoration: none;
32+
}
33+
}
34+
}
35+
36+
// Header content should take up the whole remaining horizontal space
37+
.header__content {
38+
flex-grow: 1;
39+
}
40+
41+
.header__content,
42+
.header__brand,
43+
.header__start,
44+
.header__end {
45+
display: flex;
46+
flex-wrap: nowrap;
47+
gap: 0.5rem;
48+
49+
// On narrow screens, the header items show flow vertically and snap to left
50+
@media (max-width: $breakpoint-md) {
51+
flex-direction: column;
52+
}
53+
}
54+
55+
// Ensure the header items don't touch the screen edge
56+
.header__start {
57+
padding-left: 1rem;
58+
}
59+
.header__end {
60+
padding-right: 1rem;
61+
}
62+
63+
@media (max-width: $breakpoint-md) {
64+
// Only apply on narrow screens since we want it centered on sidebar on wide
65+
.header__brand,
66+
.header__end {
67+
padding-left: 1rem;
68+
padding-right: 0;
69+
}
70+
}
71+
72+
// Only show up on narrow screens, and push to the far right
73+
.headerbtn.header-toggle-button {
74+
display: none;
75+
@media (max-width: $breakpoint-md) {
76+
display: block;
77+
margin-top: 0.7em; // HACK: to make this vertically-centered w/o using flexbox
78+
margin-left: auto;
79+
padding-right: 0; // Override headerbtn padding so header has same right/left pad
80+
}
81+
}
82+
83+
input#__header {
84+
// Inputs never display, only used to control behavior
85+
display: none;
86+
87+
// On narrow screens, checking the button opens the header
88+
&:checked ~ div.header {
89+
@media (max-width: $breakpoint-md) {
90+
max-height: 20em;
91+
}
92+
}
93+
}
94+
95+
// Header content contains the components
96+
.header__content {
97+
flex-grow: 1;
98+
}
99+
100+
// This is the logo or a bold docs title
101+
.header__brand {
102+
font-size: 1.5em;
103+
104+
// Wide screens: header start has same width as the sidebar + center over logo
105+
@media (min-width: $breakpoint-md) {
106+
width: $leftbar-width-wide;
107+
justify-content: center;
108+
}
109+
}
110+
111+
.header__end {
112+
@media (min-width: $breakpoint-md) {
113+
// Header end is right-justified
114+
margin-left: auto;
115+
}
116+
}
117+
118+
ul#navbar-icon-links {
119+
flex-direction: row;
120+
gap: 0.5em;
121+
}
122+
123+
.header-content-item {
124+
display: flex;
125+
align-items: center;
126+
127+
// Spacing is horizontal on wide, vertical on narrow
128+
// Items should snap to left on mobile
129+
@media (max-width: $breakpoint-md) {
130+
align-items: start;
131+
}
132+
133+
img {
134+
padding: 0.4rem 0em;
135+
@include max-height-header;
136+
}
137+
138+
.dropdown-menu {
139+
z-index: $zindex-sticky + 1;
140+
}
141+
}

src/sphinx_book_theme/assets/styles/sections/_headers.scss

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)