Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Move navbar_options into single argument with helper navbar_options() #1822

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions docs/_quartodoc-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ quartodoc:
- ui.navset_card_underline
- ui.navset_pill_list
- ui.navset_hidden
- ui.navbar_options
- title: UI panels
desc: Visually group together a section of UI components.
contents:
Expand Down
1 change: 1 addition & 0 deletions docs/_quartodoc-express.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ quartodoc:
- express.ui.navset_underline
- express.ui.navset_pill_list
- express.ui.navset_hidden
- express.ui.navbar_options
- title: Chat interface
desc: Build a chatbot interface
contents:
Expand Down
36 changes: 36 additions & 0 deletions shiny/api-examples/navbar_options/app-core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from shiny import App, render, ui

app_ui = ui.page_fluid(
ui.navset_bar(
ui.nav_panel("A", "Panel A content"),
ui.nav_panel("B", "Panel B content"),
ui.nav_panel("C", "Panel C content"),
ui.nav_menu(
"Other links",
ui.nav_panel("D", "Panel D content"),
"----",
"Description:",
ui.nav_control(
ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
),
),
id="selected_navset_bar",
title="Navset Bar",
navbar_options=ui.navbar_options(
bg="#B73A85",
theme="dark",
underline=False,
),
),
ui.h5("Selected:"),
ui.output_code("selected"),
)


def server(input, output, session):
@render.code
def selected():
return input.selected_navset_bar()


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

with ui.navset_bar(
title="Navset Bar",
id="selected_navset_bar",
navbar_options=ui.navbar_options(
bg="#B73A85",
theme="dark",
underline=False,
),
):
with ui.nav_panel("A"):
"Panel A content"

with ui.nav_panel("B"):
"Panel B content"

with ui.nav_panel("C"):
"Panel C content"

with ui.nav_menu("Other links"):
with ui.nav_panel("D"):
"Page D content"

"----"
"Description:"
with ui.nav_control():
ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
ui.h5("Selected:")


@render.code
def _():
return input.selected_navset_bar()
2 changes: 2 additions & 0 deletions shiny/express/ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
notification_show,
notification_remove,
nav_spacer,
navbar_options,
Progress,
Theme,
value_box_theme,
Expand Down Expand Up @@ -282,6 +283,7 @@
"navset_pill_list",
"navset_tab",
"navset_underline",
"navbar_options",
"value_box",
"panel_well",
"panel_conditional",
Expand Down
75 changes: 51 additions & 24 deletions shiny/express/ui/_cm_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,19 @@

from ... import ui
from ..._docstring import add_example, no_example
from ...types import MISSING, MISSING_TYPE
from ...types import DEPRECATED, MISSING, MISSING_TYPE, MaybeMissing
from ...ui._accordion import AccordionPanel
from ...ui._card import CardItem
from ...ui._layout_columns import BreakpointsUser
from ...ui._navs import NavMenu, NavPanel, NavSet, NavSetBar, NavSetCard
from ...ui._navs import (
NavbarOptions,
NavbarOptionsPositionT,
NavMenu,
NavPanel,
NavSet,
NavSetBar,
NavSetCard,
)
from ...ui._sidebar import SidebarOpenSpec, SidebarOpenValue
from ...ui.css import CssUnit
from .._recall_context import RecallContextManager
Expand Down Expand Up @@ -1067,17 +1075,16 @@ def navset_bar(
fillable: bool | list[str] = True,
gap: Optional[CssUnit] = None,
padding: Optional[CssUnit | list[CssUnit]] = None,
position: Literal[
"static-top", "fixed-top", "fixed-bottom", "sticky-top"
] = "static-top",
header: TagChild = None,
footer: TagChild = None,
bg: Optional[str] = None,
# TODO: default to 'auto', like we have in R (parse color via webcolors?)
inverse: bool = False,
underline: bool = True,
collapsible: bool = True,
navbar_options: Optional[NavbarOptions] = None,
fluid: bool = True,
# Deprecated ----
position: MaybeMissing[NavbarOptionsPositionT] = DEPRECATED,
bg: MaybeMissing[str | None] = DEPRECATED,
inverse: MaybeMissing[bool] = DEPRECATED,
underline: MaybeMissing[bool] = DEPRECATED,
collapsible: MaybeMissing[bool] = DEPRECATED,
) -> RecallContextManager[NavSetBar]:
"""
Context manager for a set of nav items as a tabset inside a card container.
Expand All @@ -1095,16 +1102,15 @@ def navset_bar(
Choose a particular nav item to select by default value (should match it's
``value``).
sidebar
A :class:`~shiny.ui.Sidebar` component to display on every
:func:`~shiny.ui.nav_panel` page.
A :class:`~shiny.ui.Sidebar` component to display on every :func:`~shiny.ui.nav_panel` page.
fillable
Whether or not to allow fill items to grow/shrink to fit the browser window. If
`True`, all `nav()` pages are fillable. A character vector, matching the value
of `nav()`s to be filled, may also be provided. Note that, if a `sidebar` is
provided, `fillable` makes the main content portion fillable.
gap
A CSS length unit defining the gap (i.e., spacing) between elements provided to
`*args`.
`*args`. This value is only used when the navbar is `fillable`.
padding
Padding to use for the body. This can be a numeric vector (which will be
interpreted as pixels) or a character vector with valid CSS lengths. The length
Expand All @@ -1113,26 +1119,45 @@ def navset_bar(
the second value will be used for left and right. If three, then the first will
be used for top, the second will be left and right, and the third will be
bottom. If four, then the values will be interpreted as top, right, bottom, and
left respectively.
left respectively. This value is only used when the navbar is `fillable`.
header
UI to display above the selected content.
footer
UI to display below the selected content.
fluid
``True`` to use fluid layout; ``False`` to use fixed layout.
navbar_options
Configure the appearance and behavior of the navbar using
:func:`~shiny.ui.navbar_options` to set properties like position, background
color, and more.

`navbar_options` was added in v1.3.0 and replaces deprecated arguments
`position`, `bg`, `inverse`, `collapsible`, and `underline`.
position
Deprecated in v1.3.0. Please use `navbar_options` instead; see
:func:`~shiny.ui.navbar_options` for details.

Determines whether the navbar should be displayed at the top of the page with
normal scrolling behavior ("static-top"), pinned at the top ("fixed-top"), or
pinned at the bottom ("fixed-bottom"). Note that using "fixed-top" or
"fixed-bottom" will cause the navbar to overlay your body content, unless you
add padding (e.g., ``tags.style("body {padding-top: 70px;}")``).
header
UI to display above the selected content.
footer
UI to display below the selected content.
bg
Deprecated in v1.3.0. Please use `navbar_options` instead; see
:func:`~shiny.ui.navbar_options` for details.

Background color of the navbar (a CSS color).
inverse
Deprecated in v1.3.0. Please use `navbar_options` instead; see
:func:`~shiny.ui.navbar_options` for details.

Either ``True`` for a light text color or ``False`` for a dark text color.
collapsible
``True`` to automatically collapse the navigation elements into an expandable
menu on mobile devices or narrow window widths.
fluid
``True`` to use fluid layout; ``False`` to use fixed layout.
Deprecated in v1.3.0. Please use `navbar_options` instead; see
:func:`~shiny.ui.navbar_options` for details.

``True`` to automatically collapse the elements into an expandable menu on
mobile devices or narrow window widths.
"""
return RecallContextManager(
ui.navset_bar,
Expand All @@ -1144,14 +1169,16 @@ def navset_bar(
fillable=fillable,
gap=gap,
padding=padding,
position=position,
header=header,
footer=footer,
fluid=fluid,
navbar_options=navbar_options,
# Deprecated -- v1.3.0 2025-01 ----
position=position,
bg=bg,
inverse=inverse,
underline=underline,
collapsible=collapsible,
fluid=fluid,
),
)

Expand Down
12 changes: 9 additions & 3 deletions shiny/types.py
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@schloerke Can you review the changes in this file for me? I wanted an object that operates like MISSING but gives a better hint to users in the function signature, so I created a new sentinel DEPRECATED object. Look at page_navbar() or navset_bar() (in _navs.py, which might be hidden for being a large diff) to see how this is used.

Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,30 @@
from htmltools import TagChild

from ._docstring import add_example
from ._typing_extensions import NotRequired, TypedDict
from ._typing_extensions import NotRequired, TypedDict, TypeIs

if TYPE_CHECKING:
from matplotlib.figure import Figure

T = TypeVar("T")


# Sentinel value - indicates a missing value in a function call.
class MISSING_TYPE:
pass


MISSING: MISSING_TYPE = MISSING_TYPE()
DEPRECATED: MISSING_TYPE = MISSING_TYPE() # A MISSING that communicates deprecation
MaybeMissing = Union[T, MISSING_TYPE]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find bg: MaybeMissing[str | None] = MISSING a bit more readable than bg: str | None | MISSING_TYPE = MISSING, so this just provides a bit of sugar around the common pattern.



T = TypeVar("T")
ListOrTuple = Union[List[T], Tuple[T, ...]]


def is_missing(x: Any) -> TypeIs[MISSING_TYPE]:
return x is MISSING or isinstance(x, MISSING_TYPE)
Comment on lines +54 to +55
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL about the difference between TypeGuard and TypeIs. (TypeGuard only narrows the type scope in the positive case [if it is, it is, otherwise it might still be], while TypeIs narrows the type on both sides [it either is or it isn't].)

Anyway, this could also just be isinstance(x, MISSING_TYPE) but it's a little more succinct when used.



# Information about a single file, with a structure like:
# {'name': 'mtcars.csv', 'size': 1303, 'type': 'text/csv', 'datapath: '/...../mtcars.csv'}
# The incoming data doesn't include 'datapath'; that field is added by the
Expand Down
2 changes: 2 additions & 0 deletions shiny/ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
nav_menu,
nav_panel,
nav_spacer,
navbar_options,
navset_bar,
navset_card_pill,
navset_card_tab,
Expand Down Expand Up @@ -291,6 +292,7 @@
"navset_pill_list",
"navset_hidden",
"navset_bar",
"navbar_options",
# _notification
"notification_show",
"notification_remove",
Expand Down
Loading
Loading