-
Notifications
You must be signed in to change notification settings - Fork 117
Smarter, lazier, and more complete page default/api for express #893
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
Merged
Merged
Changes from 5 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
1716b1d
Add TopLevelRecallContextManager which inspects args to infer page de…
cpsievert 5fa9a52
WIP convert layout to ui
wch 19b8f44
Add remaining components to shiny.express.ui
wch fac3ca2
Install htmltools from github
wch d9361d2
Update examples
wch f03bb44
Better test error messages
wch 74412af
Update express UI components
wch 63e7307
Merge remote-tracking branch 'origin/main' into express-layout-to-ui
wch 9d75b26
Remove row and column from express.ui
wch d3e5f32
Merge branch 'main' into express-inspect-args
wch b552067
Move TopLevelRecallContextManager code to page_auto
wch 1a8935d
For multiple sidebars, require use of layout_sidebar()
wch 8afcc28
Add set_page_*, use_page_* functions
wch af25ce2
Update docstrings
wch 38472f0
Merge branch 'main' into express-layout-to-ui
wch 55b60fd
Add .tagify() method to RecallContextManager
wch 16d03f2
Add shiny.express.layout compatibility shim
wch 364cbad
Remove navset and navset_card
wch b3db4db
Fix example app
wch b084224
Fix exports
wch 675d015
Update docstrings
wch 0366460
Update test apps
wch 8df8d94
Import fixes in test apps
wch 2f801f7
Documentation updates
wch 1a7cdd8
Merge branch 'express-layout-to-ui' into express-inspect-args-2
wch a7b6664
Update known missing items
wch 31c8130
Update quartodoc entries
wch 06b31e0
API updates
wch c03f684
Update page_auto
wch cdf8a3e
Replace set_page() with page_opts()
wch cb7886e
Merge remote-tracking branch 'origin/main' into express-inspect-args
wch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,334 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from typing import Callable, Literal, Optional, cast | ||
|
|
||
| from htmltools import Tag, TagAttrValue, TagChild, TagList | ||
|
|
||
| from .. import ui | ||
| from ..types import MISSING, MISSING_TYPE | ||
| from ..ui._navs import NavMenu, NavPanel | ||
| from ..ui._sidebar import Sidebar | ||
| from ..ui.css import CssUnit | ||
| from ._recall_context import RecallContextManager | ||
| from ._run import get_top_level_recall_context_manager | ||
|
|
||
| __all__ = ( | ||
| "page_auto", | ||
| "page_auto_cm", | ||
| "set_page_title", | ||
| "set_page_lang", | ||
| "set_page_fillable", | ||
| "set_page_wider", | ||
| "use_page_fixed", | ||
| "use_page_fluid", | ||
| "use_page_fillable", | ||
| "use_page_sidebar", | ||
| "use_page_navbar", | ||
| ) | ||
|
|
||
|
|
||
| def page_auto_cm() -> RecallContextManager[Tag]: | ||
| return RecallContextManager( | ||
| page_auto, | ||
| kwargs={ | ||
| "_page_fn": None, | ||
| "_fillable": False, | ||
| "_wider": False, | ||
jcheng5 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }, | ||
| ) | ||
|
|
||
|
|
||
| def page_auto( | ||
| *args: object, | ||
| _page_fn: Callable[[object], Tag] | None, | ||
| _fillable: bool, | ||
| _wider: bool, | ||
| **kwargs: object, | ||
| ) -> Tag: | ||
| # 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, ui.Sidebar)] | ||
|
|
||
| nNavs = len(navs) | ||
| nSidebars = len(sidebars) | ||
|
|
||
| if _page_fn is None: | ||
| if nNavs == 0: | ||
| if nSidebars == 0: | ||
| if _fillable: | ||
| _page_fn = ( | ||
| ui.page_fillable | ||
| ) # pyright: ignore[reportGeneralTypeIssues] | ||
|
||
| elif _wider: | ||
| _page_fn = ui.page_fluid # pyright: ignore[reportGeneralTypeIssues] | ||
| else: | ||
| _page_fn = ui.page_fixed # pyright: ignore[reportGeneralTypeIssues] | ||
|
|
||
| elif nSidebars == 1: | ||
| # 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 = ui.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 nSidebars == 0: | ||
| # TODO: what do we do when nArgs != nNavs? Just let page_navbar handle it (i.e. error)? | ||
| _page_fn = ui.page_navbar # pyright: ignore[reportGeneralTypeIssues] | ||
|
|
||
| elif nSidebars == 1: | ||
| # TODO: change page_navbar() to remove `sidebar` and accept a sidebar as a | ||
| # *arg. | ||
| _page_fn = ui.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[[object], Tag], _page_fn) | ||
| return _page_fn(*args, **kwargs) | ||
|
|
||
|
|
||
| # ====================================================================================== | ||
| # Page attribute setters | ||
| # ====================================================================================== | ||
|
|
||
|
|
||
| def set_page_title(title: str) -> None: | ||
| get_top_level_recall_context_manager().kwargs["title"] = title | ||
|
|
||
|
|
||
| def set_page_fillable(fillable: bool) -> None: | ||
| get_top_level_recall_context_manager().kwargs["_fillable"] = fillable | ||
|
|
||
|
|
||
| def set_page_wider(wider: bool) -> None: | ||
| get_top_level_recall_context_manager().kwargs["_wider"] = wider | ||
|
|
||
|
|
||
| def set_page_lang(lang: str) -> None: | ||
| get_top_level_recall_context_manager().kwargs["lang"] = lang | ||
|
|
||
|
|
||
| # ====================================================================================== | ||
| # Page functions | ||
| # ====================================================================================== | ||
|
|
||
|
|
||
| def use_page_fixed( | ||
| *, | ||
| title: Optional[str] = None, | ||
| lang: Optional[str] = None, | ||
| **kwargs: str, | ||
| ) -> None: | ||
| """ | ||
| 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 ``<html>`` tag, as in ``<html lang="en">``. The | ||
| default, `None`, results in an empty string. | ||
| **kwargs | ||
| Attributes on the page level container. | ||
| """ | ||
| get_top_level_recall_context_manager().kwargs.update( | ||
| dict( | ||
| _page_fn=ui.page_fixed, | ||
| title=title, | ||
| lang=lang, | ||
| **kwargs, | ||
| ) | ||
| ) | ||
|
|
||
|
|
||
| def use_page_fluid( | ||
| *, | ||
| title: Optional[str] = None, | ||
| lang: Optional[str] = None, | ||
| **kwargs: str, | ||
| ) -> None: | ||
| """ | ||
| 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 ``<html>`` tag, as in ``<html lang="en">``. The | ||
| default, `None`, results in an empty string. | ||
| **kwargs | ||
| Attributes on the page level container. | ||
| """ | ||
| get_top_level_recall_context_manager().kwargs.update( | ||
| dict( | ||
| _page_fn=ui.page_fluid, | ||
| title=title, | ||
| lang=lang, | ||
| **kwargs, | ||
| ) | ||
| ) | ||
|
|
||
|
|
||
| def use_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, | ||
| ) -> None: | ||
| """ | ||
| Use 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 ``<html>`` tag, as in ``<html lang="en">``. The | ||
| default, `None`, results in an empty string. | ||
| """ | ||
| get_top_level_recall_context_manager().kwargs.update( | ||
| dict( | ||
| _page_fn=ui.page_fillable, | ||
| padding=padding, | ||
| gap=gap, | ||
| fillable_mobile=fillable_mobile, | ||
| title=title, | ||
| lang=lang, | ||
| **kwargs, | ||
| ) | ||
| ) | ||
|
|
||
|
|
||
| def use_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, | ||
| ) -> None: | ||
| """ | ||
| Create a page with a sidebar and a title. | ||
| This function wraps :func:`~shiny.ui.page_sidebar`. | ||
| Parameters | ||
| ---------- | ||
| sidebar | ||
| Content to display in the sidebar. | ||
| 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 ``<html>`` tag, as in ``<html lang="en">``. The | ||
| default, `None`, results in an empty string. | ||
| **kwargs | ||
| Additional attributes passed to :func:`~shiny.ui.layout_sidebar`. | ||
| Returns | ||
| ------- | ||
| : | ||
| A UI element. | ||
| """ | ||
| get_top_level_recall_context_manager().kwargs.update( | ||
| dict( | ||
| _page_fn=ui.page_sidebar, | ||
| title=title, | ||
| fillable=fillable, | ||
| fillable_mobile=fillable_mobile, | ||
| window_title=window_title, | ||
| lang=lang, | ||
| **kwargs, | ||
| ) | ||
| ) | ||
|
|
||
|
|
||
| def use_page_navbar( | ||
| *, | ||
| title: Optional[str | Tag | TagList] = None, | ||
| id: Optional[str] = None, | ||
| selected: Optional[str] = None, | ||
| sidebar: Optional[Sidebar] = None, | ||
| # Only page_navbar gets enhanced treatement for `fillable` | ||
| # If an `*args`'s `data-value` attr string is in `fillable`, then the component is fillable | ||
| 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, | ||
| ) -> None: | ||
| get_top_level_recall_context_manager().kwargs.update( | ||
| dict( | ||
| _page_fn=ui.page_navbar, | ||
| title=title, | ||
| id=id, | ||
| selected=selected, | ||
| sidebar=sidebar, | ||
| 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, | ||
| ) | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.