Skip to content
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
d761a5d
Implement dynamic navs (i.e., nav_insert(), nav_remove(), nav_show(),…
cpsievert Apr 27, 2022
9048478
Use resolve_id instead of session.ns for id resolution in navs_dynamic
jcheng5 May 22, 2023
acde8d8
Explain how to prepend/append
jcheng5 May 22, 2023
f210d48
Merge branch 'main' into dynamic-navs
cpsievert Jul 16, 2025
2799518
Modernize message sending
cpsievert Jul 16, 2025
5d6cb93
Cleanup
cpsievert Jul 16, 2025
24765ad
Update shiny/ui/_navs_dynamic.py
elnelson575 Jul 23, 2025
b32d14b
First revisions
elnelson575 Jul 23, 2025
1902796
Merge remote-tracking branch 'origin/dynamic-navs' into dynamic-navs
elnelson575 Jul 23, 2025
01c3aad
Updating nav implementation and examples
elnelson575 Jul 24, 2025
bb5ffab
Express for nav_show works
elnelson575 Jul 24, 2025
06f0d0e
Fixing formatting
elnelson575 Jul 24, 2025
9414879
Update nav language to say nav_panel
elnelson575 Jul 24, 2025
019d619
Removing unused imports
elnelson575 Jul 24, 2025
6de5bde
Example app with dynamic tabs
elnelson575 Jul 24, 2025
78f89d4
Add more ergonomic nav_insert()
cpsievert Jul 24, 2025
7e57fce
Finishing visibility tests
elnelson575 Jul 25, 2025
6a3de97
Merge branch 'dynamic-navs' of github.com:posit-dev/py-shiny into dyn…
elnelson575 Jul 25, 2025
1fa58d5
PLAYWRIGHT WORKS
elnelson575 Jul 25, 2025
eff71c7
Removing timeout
elnelson575 Jul 25, 2025
2cf6331
Renaming
elnelson575 Jul 25, 2025
3cfa66b
Updated express nav_insert
elnelson575 Jul 25, 2025
b350733
Rename folders for api-examples to show up right in docs
elnelson575 Jul 25, 2025
f74e938
Initial untested module express example
elnelson575 Jul 25, 2025
4818091
Test navs
elnelson575 Jul 25, 2025
53e0bfe
Updating express test
elnelson575 Jul 29, 2025
79b0f67
Updating to update_nav_panel
elnelson575 Jul 29, 2025
1a2e769
Clarified test comments
elnelson575 Jul 29, 2025
4157551
Added string tests
elnelson575 Jul 29, 2025
3200c31
Isort
elnelson575 Jul 29, 2025
57e0540
Update shiny/api-examples/insert_nav_panel/app-core.py
elnelson575 Jul 29, 2025
3879029
Updating textpanels
elnelson575 Jul 29, 2025
238032b
Update reactive effect
elnelson575 Jul 29, 2025
036d38b
Updating reactive effects
elnelson575 Jul 29, 2025
7f0a796
updating description
elnelson575 Jul 29, 2025
206252b
Updating comments
elnelson575 Jul 29, 2025
eba940b
Update text
elnelson575 Jul 29, 2025
c0366a1
Update text
elnelson575 Jul 29, 2025
2455c31
Add example with update_navs
elnelson575 Jul 29, 2025
8d4f201
Update name
elnelson575 Jul 29, 2025
2086f4a
Updating all camelcase before .loc edits
elnelson575 Jul 29, 2025
5d2b391
Updating spacing
elnelson575 Jul 29, 2025
ea8518e
Aligning
elnelson575 Jul 29, 2025
41b2e5e
Add a note about menu divider/header
cpsievert Jul 29, 2025
559ab2a
Updating to remove top level text render
elnelson575 Jul 29, 2025
b20faac
Updating to remove top level
elnelson575 Jul 29, 2025
748d81b
Updated tests'
elnelson575 Jul 30, 2025
7d4434b
Merge branch 'dynamic-navs' of github.com:posit-dev/py-shiny into dyn…
elnelson575 Jul 30, 2025
0c9448c
Updating tests
elnelson575 Jul 30, 2025
1fff229
Updating examples
elnelson575 Jul 30, 2025
c29710b
Adding changelog
elnelson575 Jul 30, 2025
5a89e2c
Minor correction
elnelson575 Jul 30, 2025
78b5460
Adding notifications
elnelson575 Jul 30, 2025
1fced93
Minor changes
elnelson575 Jul 30, 2025
1c77316
Merge branch 'main' into dynamic-navs
elnelson575 Jul 30, 2025
7ec9191
Add to API reference
cpsievert Jul 30, 2025
49438c1
Make no example
cpsievert Jul 30, 2025
f2aad2e
adding python string
elnelson575 Jul 30, 2025
1deb81f
Merge branch 'dynamic-navs' of github.com:posit-dev/py-shiny into dyn…
elnelson575 Jul 30, 2025
d5c199e
Update docstrings
elnelson575 Jul 30, 2025
f28420e
Update CHANGELOG.md
elnelson575 Jul 31, 2025
8f0fb76
Update shiny/ui/_navs_dynamic.py
elnelson575 Jul 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### New features

* Added `ui.insert_nav_panel()`, `ui.remove_nav_panel()`, and `ui.update_nav_panel` to support dynamic navigation. (#90)

* Added support for python 3.13. (#1711)

* `ui.sidebar()` is now interactively resizable. (#2020)
Expand Down
3 changes: 3 additions & 0 deletions docs/_quartodoc-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ quartodoc:
- ui.navset_pill_list
- ui.navset_hidden
- ui.navbar_options
- ui.insert_nav_panel
- ui.remove_nav_panel
- ui.update_nav_panel
- title: UI panels
desc: Visually group together a section of UI components.
contents:
Expand Down
3 changes: 3 additions & 0 deletions docs/_quartodoc-express.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ quartodoc:
- express.ui.navset_pill_list
- express.ui.navset_hidden
- express.ui.navbar_options
- express.ui.insert_nav_panel
- express.ui.remove_nav_panel
- express.ui.update_nav_panel
- title: Chat interface
desc: Build a chatbot interface
contents:
Expand Down
52 changes: 52 additions & 0 deletions shiny/api-examples/insert_nav_panel/app-core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from shiny import App, Inputs, Outputs, Session, reactive, ui

app_ui = ui.page_sidebar(
ui.sidebar(
ui.input_action_button("add", "Add 'Dynamic' tab"),
ui.input_action_button("update_foo", "Add/Remove 'Foo' tab"),
),
ui.navset_tab(
ui.nav_panel("Hello", "This is the hello tab", value="Hello"),
ui.nav_panel("Foo", "This is the Foo tab", value="Foo"),
ui.nav_menu(
"Static",
ui.nav_panel("Static 1", "Static 1", value="s1"),
ui.nav_panel("Static 2", "Static 2", value="s2"),
value="Menu",
),
id="tabs",
),
)


def server(input: Inputs, output: Outputs, session: Session):

@reactive.effect
@reactive.event(input.update_foo)
def _():
if input.update_foo() % 2 == 0:
ui.insert_nav_panel(
"tabs",
ui.nav_panel("Foo", "Foo is back now", value="Foo"),
target="Menu",
position="before",
select=True,
)
else:
ui.remove_nav_panel("tabs", target="Foo")

@reactive.effect
@reactive.event(input.add)
def _():
id = "Dynamic-" + str(input.add())
ui.insert_nav_panel(
"tabs",
ui.nav_panel(id, id, value=id),
target="s2",
position="before",
)

ui.notification_show(f"Added tab to menu: {id}")


app = App(app_ui, server)
43 changes: 43 additions & 0 deletions shiny/api-examples/insert_nav_panel/app-express.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from shiny import reactive
from shiny.express import input, ui

with ui.sidebar():
ui.input_action_button("add", "Add 'Dynamic' tab")
ui.input_action_button("update_foo", "Add/Remove 'Foo' tab")


with ui.navset_tab(id="tabs"):
with ui.nav_panel("Hello", value="Hello"):
"This is the hello tab"
with ui.nav_panel("Foo", value="Foo"):
"This is the Foo tab"
with ui.nav_menu("Static", value="Menu"):
with ui.nav_panel("Static 1", value="s1"):
"Static 1"
with ui.nav_panel("Static 2", value="s2"):
"Static 2"


@reactive.effect
@reactive.event(input.update_foo)
def _():
if input.update_foo() % 2 == 0:
ui.insert_nav_panel(
"tabs",
"Foo",
"Foo is back now",
value="Foo",
target="Menu",
position="before",
select=True,
)
else:
ui.remove_nav_panel("tabs", target="Foo")


@reactive.effect
@reactive.event(input.add)
def _():
id = "Dynamic-" + str(input.add())
ui.insert_nav_panel("tabs", title=id, value=id, target="s2", position="before")
ui.notification_show(f"Added tab to menu: {id}")
52 changes: 52 additions & 0 deletions shiny/api-examples/update_nav_panel/app-core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from shiny import App, Inputs, Outputs, Session, reactive, ui

app_ui = ui.page_sidebar(
ui.sidebar(
"Home",
ui.input_action_button("hideTab", "Hide 'Foo' tab"),
ui.input_action_button("showTab", "Show 'Foo' tab"),
ui.input_action_button("hideMenu", "Hide 'More' nav_menu"),
ui.input_action_button("showMenu", "Show 'More' nav_menu"),
),
ui.navset_tab(
ui.nav_panel("Foo", "This is the foo tab", value="Foo"),
ui.nav_panel("Bar", "This is the bar tab", value="Bar"),
ui.nav_menu(
"More",
ui.nav_panel("Table", "Table page"),
ui.nav_panel("About", "About page"),
"------",
"Even more!",
ui.nav_panel("Email", "Email page"),
value="More",
),
id="tabs",
),
title="Navbar page",
id="sidebar",
)


def server(input: Inputs, output: Outputs, session: Session):
@reactive.effect
@reactive.event(input.hideTab)
def _():
ui.update_nav_panel("tabs", target="Foo", method="hide")

@reactive.effect
@reactive.event(input.showTab)
def _():
ui.update_nav_panel("tabs", target="Foo", method="show")

@reactive.effect
@reactive.event(input.hideMenu)
def _():
ui.update_nav_panel("tabs", target="More", method="hide")

@reactive.effect
@reactive.event(input.showMenu)
def _():
ui.update_nav_panel("tabs", target="More", method="show")


app = App(app_ui, server)
45 changes: 45 additions & 0 deletions shiny/api-examples/update_nav_panel/app-express.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from shiny import reactive
from shiny.express import input, ui

with ui.layout_sidebar():
with ui.sidebar(title="Navbar page", id="sidebar"):
"Home"
ui.input_action_button("hideTab", "Hide 'Foo' tab")
ui.input_action_button("showTab", "Show 'Foo' tab")
ui.input_action_button("hideMenu", "Hide 'More' nav_menu")
ui.input_action_button("showMenu", "Show 'More' nav_menu")

with ui.navset_tab(id="tabs"):
with ui.nav_panel("Foo", value="Foo"):
"This is the foo tab"
with ui.nav_panel("Bar", value="Bar"):
"This is the bar tab"
with ui.nav_menu(title="More", value="More"):
with ui.nav_panel("Table"):
"Table page"
with ui.nav_panel("About"):
"About page"
"------"
"Even more!"
with ui.nav_panel("Email"):
"Email page"

@reactive.effect
@reactive.event(input.hideTab)
def _():
ui.update_nav_panel("tabs", target="Foo", method="hide")

@reactive.effect
@reactive.event(input.showTab)
def _():
ui.update_nav_panel("tabs", target="Foo", method="show")

@reactive.effect
@reactive.event(input.hideMenu)
def _():
ui.update_nav_panel("tabs", target="More", method="hide")

@reactive.effect
@reactive.event(input.showMenu)
def _():
ui.update_nav_panel("tabs", target="More", method="show")
9 changes: 9 additions & 0 deletions shiny/express/ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@
notification_remove,
nav_spacer,
navbar_options,
remove_nav_panel,
update_nav_panel,
Progress,
Theme,
value_box_theme,
Expand Down Expand Up @@ -161,6 +163,10 @@
hold,
)

from ._insert import (
insert_nav_panel,
)

__all__ = (
# Imports from htmltools
"TagList",
Expand Down Expand Up @@ -289,6 +295,9 @@
"navset_hidden",
"navset_pill",
"navset_pill_list",
"update_nav_panel",
"insert_nav_panel",
"remove_nav_panel",
"navset_tab",
"navset_underline",
"navbar_options",
Expand Down
86 changes: 86 additions & 0 deletions shiny/express/ui/_insert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""
Shims for `ui.insert_*()`, `ui.update_*()`, etc. functions that lead to a more ergonomic
Express API.
These functions tend to have one issue in common: if they were re-exported verbatim from
Core to Express, they would want to take RecallContextManager(s) as input, which leads
to a somewhat awkward API. That's because, you'd have to know to use something like
@ui.hold() pass the UI as a value without displaying it.
"""

from typing import Literal, Optional

from htmltools import TagChild

from ..._docstring import add_example
from ...session import Session


@add_example()
def insert_nav_panel(
id: str,
title: TagChild,
*args: TagChild,
value: Optional[str] = None,
icon: TagChild = None,
target: Optional[str] = None,
position: Literal["after", "before"] = "after",
select: bool = False,
session: Optional[Session] = None,
) -> None:
"""
Create a new nav panel in an existing navset.

Parameters
----------
id
The id of the navset container to insert into.
title
A title for the inserted nav panel. Can be a character string or UI elements (i.e., tags).
*args
UI elements for the inserted nav panel.
value
The value of the panel. Use this value to determine whether the panel is active
(when an `id` is provided to the nav container) or to programmatically
select the item (e.g., :func:`~shiny.ui.update_navs`). You can also
provide the value to the `selected` argument of the navigation container
(e.g., :func:`~shiny.ui.navset_tab`).
icon
An icon to appear inline with the title.
target
The `value` of an existing :func:`shiny.ui.nav_panel`, next to which tab will
be added. Can also be `None`; see `position`.
position
The position of the new nav panel relative to the target. If
`target=None`, then `"before"` means the new panel should be inserted at
the head of the navlist, and `"after"` is the end.
select
Whether the nav panel should be selected upon insertion.
session
A :class:`~shiny.Session` instance. If not provided, it is inferred via
:func:`~shiny.session.get_current_session`.

Note
----
Unlike :func:`~shiny.ui.insert_nav_panel`, this function does not support inserting
of a heading/divider into an existing :func:`~shiny.ui.nav_menu`. To do so, use
:func:`~shiny.ui.insert_nav_panel` instead of this Express variant (i.e.,
`shiny.ui.insert_nav_panel("id", "Header")`).
"""

from ...ui import insert_nav_panel, nav_panel

panel = nav_panel(
title,
*args,
value=value,
icon=icon,
)

insert_nav_panel(
id=id,
nav_panel=panel,
target=target,
position=position,
select=select,
session=session,
)
8 changes: 8 additions & 0 deletions shiny/ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@
navset_tab,
navset_underline,
)
from ._navs_dynamic import (
update_nav_panel,
insert_nav_panel,
remove_nav_panel,
)
from ._notification import notification_remove, notification_show
from ._output import (
output_code,
Expand Down Expand Up @@ -297,6 +302,9 @@
"navset_pill_list",
"navset_hidden",
"navset_bar",
"insert_nav_panel",
"remove_nav_panel",
"update_nav_panel",
"navbar_options",
# _notification
"notification_show",
Expand Down
Loading
Loading