diff --git a/docs/conf.py b/docs/conf.py index 967814f..fb638ce 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -74,8 +74,9 @@ # a list of builtin themes. # html_theme = "sphinx_book_theme" -# html_theme = "sphinx_rtd_theme" +# html_theme = "sphinx_rtd_theme" # These are just for testing # html_theme = "alabaster" +# html_theme = "furo" html_theme_options = { "repository_url": "https://github.com/executablebooks/sphinx-togglebutton", @@ -102,7 +103,6 @@ # # html_sidebars = {} - # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. diff --git a/docs/index.md b/docs/index.md index a622a3c..c4e8704 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,48 +1,58 @@ # `sphinx-togglebutton` A small sphinx extension to add "toggle button" elements to sections of your page. -This allows you to: +For example: -- Collapse admonitions (notes, warnings, etc) so that their content is hidden - until users click a toggle button. See {ref}`dropdown-admonitions`. -- Collapse arbitrary chunks of content on your page with a `toggle` directive. - See {ref}`toggle-directive`. +## Collapse admonitions -:::{admonition} For example, click the "+" button to the right: +You can collapse admonitions (notes, warnings, etc) so that their content is hidden until users click the admonition title. + +:::{admonition} Example: click this title to toggle the content +:class: dropdown +You can toggle any admonition to hide its content behind a user click! +Do so by adding a `dropdown` class to the admonition, like this: + +```` +```{note} :class: dropdown -Here's a toggled note! You can put all kinds of stuff in here! +Some content +``` +```` ::: -You can also add a toggle button to arbitrary chunks of content. -For example, click the toggle button to the right just below. +See {ref}`dropdown-admonitions` for more information. + +## Hide any content behind a toggle button + +You can also hide arbitrary content behind a toggle button. +When users press the button, they will see the content. +For example: ::::{toggle} -:::{admonition} Wow! -:class: tip -It's a code block! +This is a toggled content block! +It was added like this: -```python -a = "wow, very python" +```` +```{toggle} +This is a toggled content block! ``` -::: +```` + :::: -See {ref}`usage` for more information. +You can either do this with a `{toggle}` directive, or by adding a `toggle` CSS class to any elements you'd like hidden behind a toggle button. -:::{admonition} Check out sphinx-panels as well! +See [](use:css-selector) for more details. + +:::{admonition} Check out sphinx-design as well! :class: tip For a bootstrap-based "dropdown" directive that uses pure CSS, check out -[Sphinx Panels](https://sphinx-panels.readthedocs.io/en/latest/#dropdown-usage) +[Sphinx Design](https://sphinx-design.readthedocs.io/en/latest/dropdowns.html) ::: -```{toctree} -use -reference -``` - ## Installation You can install `sphinx-togglebutton` with `pip`: @@ -65,3 +75,10 @@ extensions = [ ``` See {ref}`usage` for information about how to use `sphinx-togglebutton`. + + +```{toctree} +:maxdepth: 2 +use +reference/index +``` \ No newline at end of file diff --git a/docs/reference.md b/docs/reference/index.md similarity index 100% rename from docs/reference.md rename to docs/reference/index.md diff --git a/docs/use.md b/docs/use.md index baa9d55..3df27fc 100644 --- a/docs/use.md +++ b/docs/use.md @@ -3,126 +3,98 @@ This page covers how to use and configure / customize `sphinx-togglebutton`. -There are two main ways to use `sphinx-togglebutton`: +There are three main ways to use `sphinx-togglebutton`: +- Wrap arbitrary objects in a toggle button via a CSS selector - Collapse admonitions with the `dropdown` class - Make arbitrary chunks of content "toggle-able" with the `toggle` directive -Both are described below +Each is described below -(dropdown-admonitions)= -## Collapse admonitions with the `dropdown` class - -Making dropdown admonitions allows you to insert extra information in your document -without forcing the user to see that content. For example: - -:::{admonition} What could be inside this warning? -:class: warning, dropdown +(use:css-selector)= +## Collapse a block of content with a CSS selector -A whale of a joke! +You can hide any content and display a toggle button to show it by using certain CSS classes. +`sphinx-togglebutton` will wrap elements with these classes in a `
` block like so: -```{image} https://media.giphy.com/media/FaKV1cVKlVRxC/giphy.gif +```html +
+ Click to show + +
``` -::: - -Create a dropdown admonition by adding the `dropdown` class to an admonition directive. -For example, like so: - -::::{tab-set} -:::{tab-item} MyST +:::{admonition} example +:class: tip +This MyST Markdown: ````md - -```{note} -:class: dropdown - -My note +```{image} https://media.giphy.com/media/FaKV1cVKlVRxC/giphy.gif +:class: toggle ``` ```` - -::: - -:::{tab-item} reStructuredText - -```rst -.. note:: - :class: dropdown - - My note +results in: +```{image} https://media.giphy.com/media/FaKV1cVKlVRxC/giphy.gif +:class: toggle ``` - ::: -:::: - -You can use a custom admonition title and apply the style of a "built-in" -admonition (e.g., `note`, `warning`, etc) with the `admonition::` directive: - -::::{tab-set} -:::{tab-item} MyST +### Configure the CSS selector used to insert toggle buttons -````md -```{admonition} Here's my title -:class: dropdown, warning +By default, `sphinx-togglebutton` will use this selector: -My note ``` -```` - -::: - -:::{tab-item} reStructuredText - -```rst -.. admonition:: Here's my title - :class: dropdown, warning - - My note +.toggle, .admonition.dropdown ``` -::: -:::: - -Creates: +However, you can customize this behavior with the `togglebutton_selector` configuration value. +To specify the selector to use, pass a valid CSS selector as a string: -:::{admonition} Here's my title -:class: dropdown, warning +:::{admonition} example +:class: tip +Configure `sphinx-togglebutton` to look for a `.toggle-this-element` class and an element with ID `#my-special-id` **instead of** `.toggle` and `.admonition.dropdown`. -My custom admonition! +```python +sphinx_togglebutton_selector = ".toggle-this-element, #my-special-id" +``` ::: -To show the content by default, add a `toggle-shown` class as well. - -:::{tab-set-code} +(dropdown-admonitions)= +## Collapse admonitions with the `dropdown` class -````markdown -```{note} -:class: dropdown, toggle-shown +`sphinx-togglebutton` treats admonitions as a special case if they are selected. +If a Sphinx admonition matches the toggle button selector, then its title will be displayed with a button to reveal its content. -This is my note. +:::{admonition} example +:class: tip +````md +```{admonition} This will be shown +:class: dropdown +And this will be hidden! ``` ```` - -```rst -.. note:: - :class: dropdown, toggle-shown - - This is my note. +results in +```{admonition} This will be shown +:class: dropdown +And this will be hidden! ``` - ::: -This will generate the following block: +This works for any kind of Sphinx admoniton: :::{note} -:class: dropdown, toggle-shown +:class: dropdown +A note! +::: -This is my note. +:::{warning} +:class: dropdown +A warning! ::: + (toggle-directive)= -## Toggle any content with the toggle directive +## Use the `{toggle}` directive to toggle blocks of content To add toggle-able content, use the **toggle directive**. This directive will wrap its content in a toggle-able container. You can call it like so: @@ -169,12 +141,37 @@ It results in the following: Here is my toggle-able content! ::: -## Control the togglebutton hover text +## Change the button hint text -You can control the "hint" text that is displayed next to togglebuttons when -their content is collapsed. To do so, use the following configuration variable -in your `conf.py` file: +You can control the "hint" text that is displayed next to togglebuttons. +To do so, use the following configuration variable in your `conf.py` file: ```python -togglebutton_hint = "My text" +togglebutton_hint = "Displayed when the toggle is closed." +togglebutton_hint_hide = "Displayed when the toggle is open." +``` + +## Change the toggle icon color + +You can apply some extra styles to the toggle button to achieve the look you want. +This is particularly useful if the color of the toggle button does not contrast with the background of an admonition. + +To style the toggle button, [add a custom CSS file to your documentation](https://docs.readthedocs.io/en/stable/guides/adding-custom-css.html) and include a custom CSS selector like so: + +```scss +// Turn the color red... +// ...with admonition toggle buttons +button.toggle-button { + color: red; +} + +// ...with content block toggle buttons +.toggle-button summary { + color: red; +} ``` + +## Printing behavior with toggle buttons + +When you print the screen while using `sphinx-togglebutton`, the toggle-able content will not show up. +To reveal it for printing, you must manually un-toggle the items and then print. diff --git a/setup.py b/setup.py index 751d10b..cafd6ff 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ license="MIT License", packages=find_packages(), package_data={ - "sphinx_togglebutton": ["_static/togglebutton.css_t", "_static/togglebutton.js"] + "sphinx_togglebutton": ["_static/togglebutton.css", "_static/togglebutton.js", "_static/togglebutton-chevron.svg"] }, install_requires=["setuptools", "wheel", "sphinx", "docutils"], extras_require={"sphinx": ["matplotlib", "myst_nb", "sphinx_book_theme", "sphinx_design"]}, diff --git a/sphinx_togglebutton/__init__.py b/sphinx_togglebutton/__init__.py index 4172aba..6cbeba9 100644 --- a/sphinx_togglebutton/__init__.py +++ b/sphinx_togglebutton/__init__.py @@ -11,9 +11,11 @@ def st_static_path(app): app.config.html_static_path.append(static_path) -def add_to_context(app, config): +def initialize_js_assets(app, config): # Update the global context - config.html_context.update({"togglebutton_hint": config.togglebutton_hint}) + app.add_js_file(None, body=f"let toggleHintShow = '{config.togglebutton_hint}';") + app.add_js_file(None, body=f"let toggleHintHide = '{config.togglebutton_hint_hide}';") + app.add_js_file("togglebutton.js") # This function reads in a variable and inserts it into JavaScript @@ -56,11 +58,11 @@ def setup(app): # Tell Sphinx about this configuration variable app.add_config_value("togglebutton_selector", ".toggle, .admonition.dropdown", "html") app.add_config_value("togglebutton_hint", "Click to show", "html") - app.add_js_file("togglebutton.js") + app.add_config_value("togglebutton_hint_hide", "Click to hide", "html") # Run the function after the builder is initialized app.connect("builder-inited", insert_custom_selection_config) - app.connect("config-inited", add_to_context) + app.connect("config-inited", initialize_js_assets) app.add_directive("toggle", Toggle) return { "version": __version__, diff --git a/sphinx_togglebutton/_static/togglebutton.css b/sphinx_togglebutton/_static/togglebutton.css new file mode 100644 index 0000000..2c86bb4 --- /dev/null +++ b/sphinx_togglebutton/_static/togglebutton.css @@ -0,0 +1,117 @@ +/** + * Admonition-based toggles + */ + +/* Visibility of the target */ +.admonition.toggle .admonition-title ~ * { + transition: opacity .3s, height .3s; +} + +/* Overrides for admonition toggles */ + +/* Toggle buttons inside admonitions so we see the title */ +.toggle.admonition { + position: relative; +} + +/* Titles should cut off earlier to avoid overlapping w/ button */ +.toggle.admonition .admonition-title { + padding-right: 25%; + cursor: pointer; +} + +/* hides all the content of a page until de-toggled */ +.toggle-hidden.admonition .admonition-title ~ * { + height: 0; + margin: 0; + float: left; /* so they overlap when hidden */ + opacity: 0; + visibility: hidden; +} + +/* General button style and position*/ +button.toggle-button { + /** + * Background and shape. By default there's no background + * but users can style as they wish + */ + background: none; + border: none; + outline: none; + + /* Positioning just inside the amonition title */ + margin-right: 0.5em; + position: absolute; + right: 0em; + top: .5em; + float: right; + padding: 0px; + display: flex; +} + +/* Display the toggle hint on wide screens */ +@media (min-width: 768px) { + button.toggle-button.toggle-button-hidden:before { + content: attr(data-toggle-hint); /* This will be filled in by JS */ + position: absolute; + font-size: .8em; + left: -6.5em; + bottom: .4em; + } +} + +/* Icon behavior */ +.tb-icon { + transition: transform .2s ease-out; + height: 1.5em; + width: 1.5em; + stroke: currentColor; /* So that we inherit the color of other text */ +} + +details.toggle-details .tb-icon { + height: 1.3em; + width: 1.3em; +} + +.toggle-button-hidden .tb-icon { + transform: rotate(90deg); +} + +/** + * Details-based toggles. + * In this case, we wrap elements with `.toggle` in a details block. + */ + +/* Details blocks */ +details.toggle-details { + margin: 1em 0; +} + +details.toggle-details summary { + display: flex; + align-items: center; + width: fit-content; + list-style: none; + border-radius: .4em; + border: 1px solid #ccc; + background: #f8f8f8; + padding: 0.4em 1em 0.4em 0.5em; /* Less padding on left because the SVG has left margin */ + font-size: .9em; +} + +details.toggle-details[open] summary { + margin-bottom: .5em; +} + +details.toggle-details[open] summary .tb-icon { + transform: rotate(90deg); +} + +details.toggle-details[open] summary ~ * { + animation: toggle-fade-in .3s ease-out; +} + +@keyframes toggle-fade-in { + from {opacity: 0%;} + to {opacity: 100%;} +} diff --git a/sphinx_togglebutton/_static/togglebutton.css_t b/sphinx_togglebutton/_static/togglebutton.css_t deleted file mode 100644 index aa1e443..0000000 --- a/sphinx_togglebutton/_static/togglebutton.css_t +++ /dev/null @@ -1,95 +0,0 @@ -/* Visibility of the target */ -.toggle, .admonition.toggle .admonition-title ~ * { - transition: opacity .5s, height .5s; -} - -.toggle-hidden:not(.admonition) { - visibility: hidden; - opacity: 0; - height: 1.5em; - margin: 0px; - padding: 0px; -} - -/* Overrides for admonition toggles */ - -/* Titles should cut off earlier to avoid overlapping w/ button */ -.admonition.toggle p.admonition-title { - padding-right: 25%; -} - -/* hides all the content of a page until de-toggled */ -.admonition.toggle-hidden .admonition-title ~ * { - height: 0; - margin: 0; - float: left; /* so they overlap when hidden */ - opacity: 0; - visibility: hidden; -} - -/* Toggle buttons inside admonitions so we see the title */ -.toggle.admonition { - position: relative; -} - -/* Clicking the title will toggle the admonition, so a pointer makes this clearer */ -.toggle.admonition .admonition-title { - cursor: pointer; -} - -.toggle.admonition.admonition-title:after { - content: "" !important; -} - -/* Note, we'll over-ride this in sphinx-book-theme */ -.toggle.admonition button.toggle-button { - margin-right: 0.5em; - right: 0em; - position: absolute; - top: .2em; -} - -/* General button style */ -button.toggle-button { - background: #999; - border: none; - z-index: 100; - right: -2.5em; - margin-left: -2.5em; /* A hack to keep code blocks from being pushed left */ - position: relative; - float: right; - border-radius: 100%; - width: 1.5em; - height: 1.5em; - padding: 0px; -} - -@media (min-width: 768px) { - button.toggle-button.toggle-button-hidden:before { - content: "{{ togglebutton_hint }}"; - position: absolute; - font-size: .8em; - left: -6.5em; - bottom: .4em; - } -} - - -/* Plus / minus toggles */ -.toggle-button .bar { - background-color: white; - position: absolute; - left: 15%; - top: 43%; - width: 16px; - height: 3px; -} - -.toggle-button .vertical { - transition: all 0.25s ease-in-out; - transform-origin: center; -} - -.toggle-button-hidden .vertical { - transform: rotate(-90deg); -} diff --git a/sphinx_togglebutton/_static/togglebutton.js b/sphinx_togglebutton/_static/togglebutton.js index 41b77f9..1ce6047 100644 --- a/sphinx_togglebutton/_static/togglebutton.js +++ b/sphinx_togglebutton/_static/togglebutton.js @@ -1,47 +1,89 @@ +/** + * Add Toggle Buttons to elements + */ + +let toggleChevron = ` + + + +`; + var initToggleItems = () => { var itemsToToggle = document.querySelectorAll(togglebuttonSelector); console.log(`[togglebutton]: Adding toggle buttons to ${itemsToToggle.length} items`) // Add the button to each admonition and hook up a callback to toggle visibility itemsToToggle.forEach((item, index) => { - // Generate unique IDs for this item - var toggleID = `toggle-${index}`; - var buttonID = `button-${toggleID}`; - - item.setAttribute('id', toggleID); - if (!item.classList.contains("toggle")){ - item.classList.add("toggle"); - } + if (item.classList.contains("admonition")) { + // If it's an admonition block, then we'll add a button inside + // Generate unique IDs for this item + var toggleID = `toggle-${index}`; + var buttonID = `button-${toggleID}`; - // This is the button that will be added to each item to trigger the toggle - var collapseButton = ` - `; + item.setAttribute('id', toggleID); + if (!item.classList.contains("toggle")){ + item.classList.add("toggle"); + } + // This is the button that will be added to each item to trigger the toggle + var collapseButton = ` + `; - // Add the button HTML to this element and assign it as a variable to use later - if (item.classList.contains("admonition")) { - // If it's an admonition block, then we'll add the button inside item.insertAdjacentHTML("afterbegin", collapseButton); + thisButton = document.getElementById(buttonID); + + // Add click handlers for the button + admonition title (if admonition) + thisButton.addEventListener('click', toggleClickHandler); + admonitionTitle = document.querySelector(`#${toggleID} > .admonition-title`) + if (admonitionTitle) { + admonitionTitle.addEventListener('click', toggleClickHandler); + admonitionTitle.dataset.target = toggleID + admonitionTitle.dataset.button = buttonID + } + + // Now hide the item for this toggle button unless explicitly noted to show + if (!item.classList.contains("toggle-shown")) { + toggleHidden(thisButton); + } } else { - item.insertAdjacentHTML('beforebegin', collapseButton); - } - thisButton = document.getElementById(buttonID); - - // Add click handlers for the button + admonition title (if admonition) - thisButton.addEventListener('click', toggleClickHandler); + // If not an admonition, wrap the block in a
block + // Define the structure of the details block and insert it as a sibling + var detailsBlock = ` +
+ + ${toggleChevron} + ${toggleHintShow} + +
`; + item.insertAdjacentHTML("beforebegin", detailsBlock); - // If admonition has a single direct-child title make it clickable. - admonitionTitle = document.querySelector(`#${toggleID} > .admonition-title`) - if (admonitionTitle) { - admonitionTitle.addEventListener('click', toggleClickHandler); - admonitionTitle.dataset.target = toggleID - admonitionTitle.dataset.button = buttonID - } + // Now move the toggle-able content inside of the details block + details = item.previousElementSibling + details.appendChild(item) - // Now hide the item for this toggle button unless explicitly noted to show - if (!item.classList.contains("toggle-shown")) { - toggleHidden(thisButton); + // Set up a click trigger to change the text as needed + details.addEventListener('click', (click) => { + let parent = click.target.parentElement; + if (parent.tagName.toLowerCase() == "details") { + summary = parent.querySelector("summary"); + details = parent; + } else { + summary = parent; + details = parent.parentElement; + } + // Update the inner text for the proper hint + if (details.open) { + summary.querySelector("span").innerText = toggleHintShow; + } else { + summary.querySelector("span").innerText = toggleHintHide; + } + + }); + + // If we have a toggle-shown class, open details block should be open + if (item.classList.contains("toggle-shown")) { + details.click(); + } } }) }; @@ -60,8 +102,15 @@ var toggleHidden = (button) => { } var toggleClickHandler = (click) => { - button = document.getElementById(click.target.dataset['button']); - toggleHidden(button); + if (click.target.classList.contains("admonition-title")) { + // If it's an admonition title, the button will be just before + button = click.target.previousElementSibling; + } else { + // If not, we've clicked the button itself or its content, so search upwards + button = click.currentTarget; + } + target = document.getElementById(button.dataset['button']); + toggleHidden(target); } // If we want to blanket-add toggle classes to certain cells diff --git a/tox.ini b/tox.ini index c9d307c..5539595 100644 --- a/tox.ini +++ b/tox.ini @@ -11,6 +11,11 @@ recreate = false description = Build the documentation extras = sphinx +deps = + -e. + sphinx_rtd_theme + furo + alabaster commands = sphinx-build \ -n -b {posargs:html} docs/ docs/_build/{posargs:html} @@ -18,7 +23,11 @@ commands = [testenv:docs-live] description = Auto-build and preview the documentation in the browser deps = + -e. sphinx-autobuild + sphinx_rtd_theme + furo + alabaster extras = sphinx commands =