diff --git a/docs/_quartodoc.yml b/docs/_quartodoc.yml index 86b0c6738..7d1ec56ec 100644 --- a/docs/_quartodoc.yml +++ b/docs/_quartodoc.yml @@ -19,6 +19,8 @@ quartodoc: - ui.page_fluid - ui.page_fixed - ui.page_bootstrap + - ui.page_auto + - ui.page_output - title: UI Layouts desc: Control the layout of multiple UI components. contents: @@ -290,7 +292,6 @@ quartodoc: desc: "" flatten: true contents: - - express.ui.set_page - express.ui.sidebar - express.ui.layout_sidebar - express.ui.layout_column_wrap @@ -315,11 +316,15 @@ quartodoc: - express.ui.panel_conditional - express.ui.panel_fixed - express.ui.panel_absolute - - express.ui.page_fluid - - express.ui.page_fixed - - express.ui.page_fillable - - express.ui.page_sidebar - - express.ui.page_navbar + contents: + - kind: page + path: PageFunctions + summary: + name: "Page functions" + desc: "" + flatten: true + contents: + - express.ui.page_opts - title: Deprecated desc: "" contents: diff --git a/examples/express/plot_app.py b/examples/express/plot_app.py index 05d8643f4..605fe6e62 100644 --- a/examples/express/plot_app.py +++ b/examples/express/plot_app.py @@ -1,8 +1,8 @@ import matplotlib.pyplot as plt import numpy as np -from shiny import render, ui -from shiny.express import input +from shiny import render +from shiny.express import input, ui ui.input_slider("n", "N", 1, 100, 50) diff --git a/shiny/express/_recall_context.py b/shiny/express/_recall_context.py index dc636d6a0..e491f733c 100644 --- a/shiny/express/_recall_context.py +++ b/shiny/express/_recall_context.py @@ -19,12 +19,10 @@ def __init__( self, fn: Callable[..., R], *, - default_page: RecallContextManager[Tag] | None = None, args: tuple[object, ...] | None = None, kwargs: Mapping[str, object] | None = None, ): self.fn = fn - self.default_page = default_page if args is None: args = tuple() if kwargs is None: @@ -33,11 +31,6 @@ def __init__( self.kwargs: dict[str, object] = dict(kwargs) def __enter__(self) -> None: - if self.default_page is not None: - from . import _run - - _run.replace_top_level_recall_context_manager(self.default_page) - self._prev_displayhook = sys.displayhook # Collect each of the "printed" values in the args list. sys.displayhook = wrap_displayhook_handler(self.args.append) diff --git a/shiny/express/_run.py b/shiny/express/_run.py index e843e558c..34c934d6e 100644 --- a/shiny/express/_run.py +++ b/shiny/express/_run.py @@ -8,7 +8,6 @@ from htmltools import Tag, TagList -from .. import ui from .._app import App from ..session import Inputs, Outputs, Session from ._recall_context import RecallContextManager @@ -20,8 +19,6 @@ __all__ = ("wrap_express_app",) -_DEFAULT_PAGE_FUNCTION = ui.page_fixed - def wrap_express_app(file: Path) -> App: """Wrap a Shiny Express mode app into a Shiny `App` object. @@ -133,64 +130,14 @@ def set_result(x: object): _top_level_recall_context_manager: RecallContextManager[Tag] -_top_level_recall_context_manager_has_been_replaced = False def reset_top_level_recall_context_manager() -> None: + from .ui._page import page_auto_cm + global _top_level_recall_context_manager - global _top_level_recall_context_manager_has_been_replaced - _top_level_recall_context_manager = RecallContextManager(_DEFAULT_PAGE_FUNCTION) - _top_level_recall_context_manager_has_been_replaced = False + _top_level_recall_context_manager = page_auto_cm() def get_top_level_recall_context_manager() -> RecallContextManager[Tag]: return _top_level_recall_context_manager - - -def replace_top_level_recall_context_manager( - cm: RecallContextManager[Tag], - force: bool = False, -) -> RecallContextManager[Tag]: - """ - Replace the current top level RecallContextManager with another one. - - This transfers the `args` and `kwargs` from the previous RecallContextManager to the - new one. Normally it will only have an effect the first time it's run; it only - replace the previous one if has not already been replaced. To override this - behavior, this use `force=True`. - - Parameters - ---------- - cm - The RecallContextManager to replace the previous one. - force - If `False` (the default) and the top level RecallContextManager has already been - replaced, return with no chnages. If `True`, this will aways replace. - - Returns - ------- - : - The previous top level RecallContextManager. - """ - global _top_level_recall_context_manager - global _top_level_recall_context_manager_has_been_replaced - - old_cm = _top_level_recall_context_manager - - if force is False and _top_level_recall_context_manager_has_been_replaced: - return old_cm - - args = old_cm.args.copy() - args.extend(cm.args) - cm.args = args - - kwargs = old_cm.kwargs.copy() - kwargs.update(cm.kwargs) - cm.kwargs = kwargs - - old_cm.__exit__(BaseException, None, None) - cm.__enter__() - _top_level_recall_context_manager = cm - _top_level_recall_context_manager_has_been_replaced = True - - return old_cm diff --git a/shiny/express/ui/__init__.py b/shiny/express/ui/__init__.py index f33cab3e2..b016b9c70 100644 --- a/shiny/express/ui/__init__.py +++ b/shiny/express/ui/__init__.py @@ -107,7 +107,6 @@ ) from ._cm_components import ( - set_page, sidebar, layout_sidebar, layout_column_wrap, @@ -134,11 +133,10 @@ panel_conditional, panel_fixed, panel_absolute, - page_fluid, - page_fixed, - page_fillable, - page_sidebar, - page_navbar, +) + +from ._page import ( + page_opts, ) __all__ = ( @@ -245,7 +243,6 @@ "output_data_frame", "value_box_theme", # Imports from ._cm_components - "set_page", "sidebar", "layout_sidebar", "layout_column_wrap", @@ -272,11 +269,8 @@ "panel_conditional", "panel_fixed", "panel_absolute", - "page_fluid", - "page_fixed", - "page_fillable", - "page_sidebar", - "page_navbar", + # Imports from ._page + "page_opts", ) @@ -290,6 +284,12 @@ "navset_pill_card", # Deprecated "navset_tab_card", # Deprecated "page_bootstrap", + "page_fixed", + "page_sidebar", + "page_fillable", + "page_navbar", + "page_fluid", + "page_auto", "page_output", "panel_main", # Deprecated "panel_sidebar", # Deprecated @@ -301,5 +301,5 @@ "tooltip", ), # Items from shiny.express.ui that don't have a counterpart in shiny.ui - "shiny.express.ui": ("set_page",), + "shiny.express.ui": ("page_opts",), } diff --git a/shiny/express/ui/_cm_components.py b/shiny/express/ui/_cm_components.py index 4772cae7f..de4e251d2 100644 --- a/shiny/express/ui/_cm_components.py +++ b/shiny/express/ui/_cm_components.py @@ -13,11 +13,9 @@ from ...ui._layout_columns import BreakpointsUser from ...ui._navs import NavMenu, NavPanel, NavSet, NavSetBar, NavSetCard from ...ui.css import CssUnit -from .. import _run from .._recall_context import RecallContextManager __all__ = ( - "set_page", "sidebar", "layout_sidebar", "layout_column_wrap", @@ -34,22 +32,9 @@ "panel_conditional", "panel_fixed", "panel_absolute", - "page_fluid", - "page_fixed", - "page_fillable", - "page_sidebar", - "page_navbar", ) -# ====================================================================================== -# Page functions -# ====================================================================================== -def set_page(page_fn: RecallContextManager[Tag]) -> None: - """Set the page function for the current Shiny express app.""" - _run.replace_top_level_recall_context_manager(page_fn, force=True) - - # ====================================================================================== # Shiny layout components # ====================================================================================== @@ -125,7 +110,6 @@ def sidebar( """ return RecallContextManager( ui.sidebar, - default_page=page_sidebar(), kwargs=dict( width=width, position=position, @@ -287,7 +271,6 @@ def layout_column_wrap( """ return RecallContextManager( ui.layout_column_wrap, - default_page=page_fillable(), kwargs=dict( width=width, fixed_width=fixed_width, @@ -469,9 +452,6 @@ def card( ) -ui.card_header - - def card_header( *args: TagChild | TagAttrs, container: TagFunction = ui.tags.div, @@ -1420,272 +1400,3 @@ def panel_absolute( **kwargs, ), ) - - -# ====================================================================================== -# Page components -# ====================================================================================== -def page_fluid( - *, - title: Optional[str] = None, - lang: Optional[str] = None, - **kwargs: str, -) -> RecallContextManager[Tag]: - """ - Create a fluid page. - - This function wraps :func:`~shiny.ui.page_fluid`. - - Parameters - ---------- - title - The browser window title (defaults to the host URL of the page). Can also be set - as a side effect via :func:`~shiny.ui.panel_title`. - lang - ISO 639-1 language code for the HTML page, such as ``"en"`` or ``"ko"``. This - will be used as the lang in the ```` tag, as in ````. The - default, `None`, results in an empty string. - **kwargs - Attributes on the page level container. - """ - return RecallContextManager( - ui.page_fluid, - kwargs=dict( - title=title, - lang=lang, - **kwargs, - ), - ) - - -def page_fixed( - *, - title: Optional[str] = None, - lang: Optional[str] = None, - **kwargs: str, -) -> RecallContextManager[Tag]: - """ - Create a fixed page. - - This function wraps :func:`~shiny.ui.page_fixed`. - - Parameters - ---------- - title - The browser window title (defaults to the host URL of the page). Can also be set - as a side effect via :func:`~shiny.ui.panel_title`. - lang - ISO 639-1 language code for the HTML page, such as ``"en"`` or ``"ko"``. This - will be used as the lang in the ```` tag, as in ````. The - default, `None`, results in an empty string. - **kwargs - Attributes on the page level container. - """ - return RecallContextManager( - ui.page_fixed, - kwargs=dict( - title=title, - lang=lang, - **kwargs, - ), - ) - - -def page_fillable( - *, - padding: Optional[CssUnit | list[CssUnit]] = None, - gap: Optional[CssUnit] = None, - fillable_mobile: bool = False, - title: Optional[str] = None, - lang: Optional[str] = None, - **kwargs: TagAttrValue, -) -> RecallContextManager[Tag]: - """ - Creates a fillable page. - - This function wraps :func:`~shiny.ui.page_fillable`. - - Parameters - ---------- - padding - Padding to use for the body. See :func:`~shiny.ui.css_unit.as_css_padding` - for more details. - fillable_mobile - Whether or not the page should fill the viewport's height on mobile devices - (i.e., narrow windows). - gap - A CSS length unit passed through :func:`~shiny.ui.css_unit.as_css_unit` - defining the `gap` (i.e., spacing) between elements provided to `*args`. - title - The browser window title (defaults to the host URL of the page). Can also be set - as a side effect via :func:`~shiny.ui.panel_title`. - lang - ISO 639-1 language code for the HTML page, such as ``"en"`` or ``"ko"``. This - will be used as the lang in the ```` tag, as in ````. The - default, `None`, results in an empty string. - - """ - return RecallContextManager( - ui.page_fillable, - kwargs=dict( - padding=padding, - gap=gap, - fillable_mobile=fillable_mobile, - title=title, - lang=lang, - **kwargs, - ), - ) - - -def page_sidebar( - *, - title: Optional[str | Tag | TagList] = None, - fillable: bool = True, - fillable_mobile: bool = False, - window_title: str | MISSING_TYPE = MISSING, - lang: Optional[str] = None, - **kwargs: TagAttrValue, -) -> RecallContextManager[Tag]: - """ - Create a page with a sidebar and a title. - - This function wraps :func:`~shiny.ui.page_sidebar`. - - Parameters - ---------- - title - A title to display at the top of the page. - fillable - Whether or not the main content area should be considered a fillable - (i.e., flexbox) container. - fillable_mobile - Whether or not ``fillable`` should apply on mobile devices. - window_title - The browser's window title (defaults to the host URL of the page). Can also be - set as a side effect via :func:`~shiny.ui.panel_title`. - lang - ISO 639-1 language code for the HTML page, such as ``"en"`` or ``"ko"``. This - will be used as the lang in the ```` tag, as in ````. The - default, `None`, results in an empty string. - **kwargs - Additional attributes passed to :func:`~shiny.ui.layout_sidebar`. - """ - # sidebar - # Content to display in the sidebar. - - return RecallContextManager( - ui.page_sidebar, - kwargs=dict( - title=title, - fillable=fillable, - fillable_mobile=fillable_mobile, - window_title=window_title, - lang=lang, - **kwargs, - ), - ) - - -# TODO: Figure out sidebar arg for ui.page_navbar -def page_navbar( - *, - title: Optional[str | Tag | TagList] = None, - id: Optional[str] = None, - selected: Optional[str] = None, - fillable: bool | list[str] = True, - fillable_mobile: bool = False, - gap: Optional[CssUnit] = None, - padding: Optional[CssUnit | list[CssUnit]] = None, - position: Literal["static-top", "fixed-top", "fixed-bottom"] = "static-top", - header: Optional[TagChild] = None, - footer: Optional[TagChild] = None, - bg: Optional[str] = None, - inverse: bool = False, - underline: bool = True, - collapsible: bool = True, - fluid: bool = True, - window_title: str | MISSING_TYPE = MISSING, - lang: Optional[str] = None, -) -> RecallContextManager[Tag]: - """ - Create a page with a navbar and a title. - - This function wraps :func:`~shiny.ui.page_navbar`. - - Parameters - ---------- - title - The browser window title (defaults to the host URL of the page). Can also be set - as a side effect via :func:`~shiny.ui.panel_title`. - id - If provided, will create an input value that holds the currently selected nav - item. - selected - Choose a particular nav item to select by default value (should match it's - ``value``). - fillable - Whether or not the main content area should be considered a fillable - (i.e., flexbox) container. - fillable_mobile - Whether or not ``fillable`` should apply on mobile devices. - position - 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 - Background color of the navbar (a CSS color). - inverse - Either ``True`` for a light text color or ``False`` for a dark text color. - collapsible - ``True`` to automatically collapse the elements into an expandable menu on mobile devices or narrow window widths. - fluid - ``True`` to use fluid layout; ``False`` to use fixed layout. - window_title - The browser's window title (defaults to the host URL of the page). Can also be - set as a side effect via :func:`~shiny.ui.panel_title`. - lang - ISO 639-1 language code for the HTML page, such as ``"en"`` or ``"ko"``. This - will be used as the lang in the ```` tag, as in ````. The - default, `None`, results in an empty string. - - See Also - ------- - * :func:`~shiny.ui.nav` - * :func:`~shiny.ui.nav_menu` - * :func:`~shiny.ui.navset_bar` - * :func:`~shiny.ui.page_fluid` - - Example - ------- - See :func:`~shiny.ui.nav`. - """ - - return RecallContextManager( - ui.page_navbar, - kwargs=dict( - title=title, - id=id, - selected=selected, - fillable=fillable, - fillable_mobile=fillable_mobile, - gap=gap, - padding=padding, - position=position, - header=header, - footer=footer, - bg=bg, - inverse=inverse, - underline=underline, - collapsible=collapsible, - fluid=fluid, - window_title=window_title, - lang=lang, - ), - ) diff --git a/shiny/express/ui/_page.py b/shiny/express/ui/_page.py new file mode 100644 index 000000000..f06d549f4 --- /dev/null +++ b/shiny/express/ui/_page.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from typing import Callable + +from htmltools import Tag + +from ... import ui +from ...types import MISSING, MISSING_TYPE +from .._recall_context import RecallContextManager +from .._run import get_top_level_recall_context_manager + +__all__ = ("page_opts",) + + +def page_auto_cm() -> RecallContextManager[Tag]: + return RecallContextManager(ui.page_auto) + + +def page_opts( + *, + title: str | MISSING_TYPE = MISSING, + lang: str | MISSING_TYPE = MISSING, + page_fn: Callable[..., Tag] | None | MISSING_TYPE = MISSING, + fillable: bool | MISSING_TYPE = MISSING, + full_width: bool | MISSING_TYPE = MISSING, +) -> None: + """ + Set page-level options for the current app. + + title + The browser window title (defaults to the host URL of the page). + lang + ISO 639-1 language code for the HTML page, such as ``"en"`` or ``"ko"``. This + will be used as the lang in the ```` tag, as in ````. The + default, `None`, results in an empty string. + fillable + If there is a top-level sidebar or nav, then the value is passed through to the + :func:`~shiny.ui.page_sidebar` or :func:`~shiny.ui.page_navbar` function. + Otherwise, if ``True``, use :func:`~shiny.ui.page_fillable`, where the content + fills the window; if ``False`` (the default), the value of ``full_width`` will + determine which page function is used. + full_width + This has an effect only if there are no sidebars or top-level navs, and + ``fillable`` is ``False``. If this is ``False`` (the default), use use + :func:`~shiny.ui.page_fixed`; if ``True``, use :func:`~shiny.ui.page_fillable`. + page_fn + The page function to use. If ``None`` (the default), will automatically choose + one based on the arguments provided. If not ``None``, this will override all + heuristics for choosing page functions. + """ + cm = get_top_level_recall_context_manager() + + if not isinstance(title, MISSING_TYPE): + cm.kwargs["title"] = title + if not isinstance(lang, MISSING_TYPE): + cm.kwargs["lang"] = lang + if not isinstance(page_fn, MISSING_TYPE): + cm.kwargs["page_fn"] = page_fn + if not isinstance(fillable, MISSING_TYPE): + cm.kwargs["fillable"] = fillable + if not isinstance(full_width, MISSING_TYPE): + cm.kwargs["full_width"] = full_width diff --git a/shiny/ui/__init__.py b/shiny/ui/__init__.py index 39a9f4832..49a725aab 100644 --- a/shiny/ui/__init__.py +++ b/shiny/ui/__init__.py @@ -121,6 +121,7 @@ page_fluid, page_fixed, page_bootstrap, + page_auto, page_output, ) from ._progress import Progress @@ -298,7 +299,6 @@ "output_text_verbatim", "output_table", "output_ui", - "page_output", # _page "page_sidebar", "page_navbar", @@ -306,6 +306,8 @@ "page_fluid", "page_fixed", "page_bootstrap", + "page_auto", + "page_output", # _popover "popover", # _valuebox diff --git a/shiny/ui/_page.py b/shiny/ui/_page.py index 207bd1ab0..c7cd08980 100644 --- a/shiny/ui/_page.py +++ b/shiny/ui/_page.py @@ -7,10 +7,11 @@ "page_fluid", "page_fixed", "page_bootstrap", + "page_auto", "page_output", ) -from typing import Literal, Optional, Sequence +from typing import Callable, Literal, Optional, Sequence, cast from htmltools import ( MetadataNode, @@ -31,7 +32,7 @@ from ._html_deps_external import bootstrap_deps from ._html_deps_py_shiny import page_output_dependency from ._html_deps_shinyverse import components_dependency -from ._navs import navset_bar +from ._navs import NavMenu, NavPanel, navset_bar from ._sidebar import Sidebar, layout_sidebar from ._tag import consolidate_attrs from ._utils import get_window_title @@ -443,6 +444,121 @@ def page_bootstrap( ) +def page_auto( + *args: TagChild | TagAttrs, + title: str | MISSING_TYPE = MISSING, + lang: str | MISSING_TYPE = MISSING, + fillable: bool | MISSING_TYPE = MISSING, + full_width: bool = False, + page_fn: Callable[..., Tag] | None = None, + **kwargs: object, +) -> Tag: + """ + A page container which automatically decides which page function to use. + + If there is a top-level nav, this will use :func:`~shiny.ui.page_navbar`. If not, + and there is a top-level sidebar, this will use :func:`~shiny.ui.page_sidebar`. + + If there are neither top-level navs nor sidebars, this will use the ``fillable`` and + ``full_width`` arguments to determine which page function to use. + + Parameters + ---------- + *args + UI elements. These are used to determine which page function to use, and they + are also passed along to that page function. + title + The browser window title (defaults to the host URL of the page). + lang + ISO 639-1 language code for the HTML page, such as ``"en"`` or ``"ko"``. This + will be used as the lang in the ```` tag, as in ````. The + default, `None`, results in an empty string. + fillable + If there is a top-level sidebar or nav, then the value is passed through to the + :func:`~shiny.ui.page_sidebar` or :func:`~shiny.ui.page_navbar` function. + Otherwise, if ``True``, use :func:`~shiny.ui.page_fillable`, where the content + fills the window; if ``False`` (the default), the value of ``full_width`` will + determine which page function is used. + full_width + This has an effect only if there are no sidebars or top-level navs, and + ``fillable`` is ``False``. If this is ``False`` (the default), use use + :func:`~shiny.ui.page_fixed`; if ``True``, use :func:`~shiny.ui.page_fillable`. + page_fn + The page function to use. If ``None`` (the default), will automatically choose + one based on the arguments provided. If not ``None``, this will override all + heuristics for choosing page functions. + **kwargs + Additional arguments, which are passed to the page function. + + Returns + ------- + : + A UI element. + """ + if not isinstance(title, MISSING_TYPE): + kwargs["title"] = title + if not isinstance(lang, MISSING_TYPE): + kwargs["lang"] = lang + + # Presence of a top-level nav items and/or sidebar determines the page function + navs = [x for x in args if isinstance(x, (NavPanel, NavMenu))] + sidebars = [x for x in args if isinstance(x, Sidebar)] + + nNavs = len(navs) + nSidebars = len(sidebars) + if page_fn is None: + if nNavs == 0: + if nSidebars == 0: + if isinstance(fillable, MISSING_TYPE): + fillable = False + + if fillable: + page_fn = page_fillable # pyright: ignore[reportGeneralTypeIssues] + elif full_width: + page_fn = page_fluid # pyright: ignore[reportGeneralTypeIssues] + else: + page_fn = page_fixed # pyright: ignore[reportGeneralTypeIssues] + + elif nSidebars == 1: + if not isinstance(fillable, MISSING_TYPE): + kwargs["fillable"] = fillable + + # page_sidebar() needs sidebar to be the first arg + # TODO: Change page_sidebar() to remove `sidebar` and accept a sidebar as a + # *arg. + page_fn = page_sidebar # pyright: ignore[reportGeneralTypeIssues] + args = tuple(sidebars + [x for x in args if x not in sidebars]) + + else: + raise NotImplementedError( + "Multiple top-level sidebars not allowed. Did you meant to wrap each one in layout_sidebar()?" + ) + + # At least one nav + else: + if not isinstance(fillable, MISSING_TYPE): + kwargs["fillable"] = fillable + + if nSidebars == 0: + # TODO: what do we do when nArgs != nNavs? Just let page_navbar handle it (i.e. error)? + page_fn = page_navbar # pyright: ignore[reportGeneralTypeIssues] + + elif nSidebars == 1: + # TODO: change page_navbar() to remove `sidebar` and accept a sidebar as a + # *arg. + page_fn = page_navbar # pyright: ignore[reportGeneralTypeIssues] + kwargs["sidebar"] = sidebars[0] + + else: + raise NotImplementedError( + "Multiple top-level sidebars not allowed in combination with top-level navs." + ) + + # If we got here, _page_fn is not None, but the type checker needs a little help. + page_fn = cast(Callable[..., Tag], page_fn) + return page_fn(*args, **kwargs) + + def page_output(id: str) -> Tag: """ Create a page container where the entire body is a UI output. diff --git a/tests/playwright/shiny/shiny-express/page_fillable/app.py b/tests/playwright/shiny/shiny-express/page_fillable/app.py index 9c2013b04..94e8b2a99 100644 --- a/tests/playwright/shiny/shiny-express/page_fillable/app.py +++ b/tests/playwright/shiny/shiny-express/page_fillable/app.py @@ -1,7 +1,7 @@ from shiny import render from shiny.express import input, ui -ui.set_page(ui.page_fillable()) +ui.page_opts(fillable=True) with ui.card(id="card"): ui.input_slider("a", "A", 1, 100, 50) diff --git a/tests/playwright/shiny/shiny-express/page_fluid/app.py b/tests/playwright/shiny/shiny-express/page_fluid/app.py index 0422113b8..54fd50c64 100644 --- a/tests/playwright/shiny/shiny-express/page_fluid/app.py +++ b/tests/playwright/shiny/shiny-express/page_fluid/app.py @@ -1,7 +1,7 @@ from shiny import render from shiny.express import input, ui -ui.set_page(ui.page_fluid()) +ui.page_opts(full_width=True) with ui.card(id="card"): ui.input_slider("a", "A", 1, 100, 50) diff --git a/tests/playwright/shiny/shiny-express/page_sidebar/__init__.py b/tests/playwright/shiny/shiny-express/page_sidebar/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/playwright/shiny/shiny-express/page_sidebar/app.py b/tests/playwright/shiny/shiny-express/page_sidebar/app.py index fac04a612..8168abf73 100644 --- a/tests/playwright/shiny/shiny-express/page_sidebar/app.py +++ b/tests/playwright/shiny/shiny-express/page_sidebar/app.py @@ -1,7 +1,7 @@ from shiny import render from shiny.express import input, ui -ui.set_page(ui.page_sidebar(title="PageTitle")) +ui.page_opts(title="PageTitle") with ui.sidebar(id="sidebar", title="SidebarTitle"): "Sidebar Content" diff --git a/tests/pytest/test_express_ui.py b/tests/pytest/test_express_ui.py index ee99effdc..eee54b246 100644 --- a/tests/pytest/test_express_ui.py +++ b/tests/pytest/test_express_ui.py @@ -10,7 +10,8 @@ def test_express_ui_is_complete(): """ Make sure shiny.express.ui covers everything that shiny.ui does, or explicitly lists - the item in _known_missing. + the item in `_known_missing`. + These entries are in `_known_missing` in shiny/express/ui/__init__.py """ from shiny import ui