From 5e8a50332dcd42da5f2f7eafa5168de874c9583c Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 19 Jan 2024 17:09:33 -0500 Subject: [PATCH 01/19] Add python helpers to build preview and express/core code windows --- docs/helpers.py | 104 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/docs/helpers.py b/docs/helpers.py index ee119e70..519db3cc 100644 --- a/docs/helpers.py +++ b/docs/helpers.py @@ -1,6 +1,8 @@ import glob from pathlib import Path -from typing import Any, Iterable, List, Optional, Sequence +from typing import Any, Iterable, List, Literal, Optional, Sequence + +import shinylive class QuartoPrint(List[str]): @@ -18,6 +20,45 @@ def append_file(self, file_path: str, file_name: Optional[str] = None): app_contents = app_file.read() self.append(app_contents) + def append_shinylive_chunk( + self, files: list[str] | str, language: str = "py", **kwargs + ): + if isinstance(files, str): + app_file = files + else: + app_file = files.pop(0) + + bundle = shinylive._url.create_shinylive_bundle_file(app_file, files, language) + contents = shinylive._url.create_shinylive_chunk_contents(bundle) + + self.append(shinylive_chunk(contents, **kwargs)) + + +def shinylive_chunk( + contents: list[str] | str, + components: Sequence[str] = ("editor", "viewer"), + viewer_height: str = "400", + layout: Literal["horizontal", "vertical"] = "horizontal", +): + block = QuartoPrint( + [ + "```{shinylive-python}", + "#| standalone: true", + f"#| components: [{', '.join(components)}]", + f"#| layout: {layout}", + f"#| viewerHeight: {viewer_height}", + "", + ] + ) + + if isinstance(contents, str): + block.append(contents.strip()) + else: + block.extend(contents) + + block.append("```") + return block + def list_files(path: str) -> List[str]: files = glob.glob(path + "/**", recursive=True) @@ -135,3 +176,64 @@ def express_editor_tabs(path: str, viewer_height: str = "400px") -> None: block.append(":::") print(block) + + +def shinylive_app_preview( + files: list[str] | str, + viewer_height: str | int = 400, + div_attrs="", + **kwargs, +) -> None: + block = QuartoPrint([f"::: {{.app-preview {div_attrs}}}"]) + + block.append_shinylive_chunk( + files, + viewer_height=str(viewer_height), + components=["viewer"], + **kwargs, + ) + + block.append(":::") + print(block) + + +def express_core_preview( + app_express: str | None = None, + app_core: str | None = None, + files: list[str] | str | None = None, + div_attrs=".shiny-mode-tabset", + group="shiny-app-mode", + language="py", + **kwargs, +) -> None: + if app_express is None and app_core is None: + return + + if files is None: + files = [] + elif isinstance(files, str): + files = [files] + + header_attrs = ".panel-tabset" + header_attrs += " " + div_attrs if div_attrs else "" + header_attrs += f" group='{group}'" if group else "" + + block = QuartoPrint(["::: {" + header_attrs + "}"]) + + apps = zip([app_express, app_core], ["Express", "Core"]) + + for app_file, tab_name in apps: + if app_file is None: + continue + + shinylive_url = shinylive.encode_shinylive_url(app_file, files, language) + + block.append("### " + tab_name) + block.append( + '```{.python .code-overflow-scroll shinylive="' + shinylive_url + '"}' + ) + block.append_file(app_file) + block.extend(["```", ""]) + + block.append(":::") + print(block) From 6b40574ba9a79b08a1cb260be92d22ff5a03ad39 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 19 Jan 2024 17:11:08 -0500 Subject: [PATCH 02/19] fix shiny-mode-tabset css --- layouts/_partials/layouts.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/layouts/_partials/layouts.css b/layouts/_partials/layouts.css index 5bfd63cb..d349cd45 100644 --- a/layouts/_partials/layouts.css +++ b/layouts/_partials/layouts.css @@ -43,7 +43,7 @@ .component-list-header { display:flex; justify-content: space-between; - align-items: center; + align-items: center; margin-bottom: 0px; } @@ -81,7 +81,7 @@ a .component-link-icon:after { transition: all 0.3s !important; } -a:hover .component-link-icon:after, +a:hover .component-link-icon:after, a.component-list-header-text:hover ~ div.component-list-icon p a i.component-link-icon:after { content: "\F133"; font-family: "bootstrap-icons"; @@ -214,7 +214,7 @@ pre.console code { /* Tab styling */ -.panel-tabset .nav-tabs { +.panel-tabset:not(.shiny-mode-tabset) .nav-tabs { display: flex !important; align-items: flex-end !important; justify-content: flex-end !important; From a41f098a940f45b244a9ad3ee13783b50b550e6b Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 19 Jan 2024 17:11:30 -0500 Subject: [PATCH 03/19] minor edits --- layouts/arrange.qmd | 51 ++++++++++++++++++++++++--------------------- layouts/index.qmd | 41 +++++++++++++++++++----------------- 2 files changed, 49 insertions(+), 43 deletions(-) diff --git a/layouts/arrange.qmd b/layouts/arrange.qmd index 9e9c7c3f..942e7791 100644 --- a/layouts/arrange.qmd +++ b/layouts/arrange.qmd @@ -1,29 +1,32 @@ --- title: "Arrange Elements" -description: "Shiny's premade layouts, such as ui.page_sidebar() and ui.page_navbar() make use of Shiny's lower-level grid layout functions." +description: > + Shiny's premade layouts, such as ui.page_sidebar() and ui.page_navbar() make use of + Shiny's lower-level grid layout functions. --- + Shiny's premade layouts, such as [ui.layout_sidebar](https://shiny.posit.co/py/api/ui.layout_sidebar.html) and [ui.page_navbar](https://shiny.posit.co/py/api/ui.page_navbar.html) make use of Shiny's lower-level grid layout functions: * Rows are created by the `ui.row()` function and include columns. -* Columns are defined by the `ui.column()` function. Column widths are based on the [Bootstrap](https://getbootstrap.com/) 12-wide grid system, so column widths should add up to 12 within a row. +* Columns are defined by the `ui.column()` function. Column widths are based on the [Bootstrap](https://getbootstrap.com/) 12-wide grid system, so column widths should add up to 12 within a row. You can use these functions directly to create your own layout. Grid layouts can be used within a page, panel, or card and can even be nested within each other. ## Relevant functions -- [ui.row](https://shiny.posit.co/py/api/ui.row.html) +- [ui.row](https://shiny.posit.co/py/api/ui.row.html) `ui.row(*args, **kwargs)` -- [ui.column](https://shiny.posit.co/py/api/ui.column.html) +- [ui.column](https://shiny.posit.co/py/api/ui.column.html) `ui.column(width, *args, offset=0, **kwargs)` -- [ui.page_fluid](https://shiny.posit.co/py/api/ui.page_fluid.html) +- [ui.page_fluid](https://shiny.posit.co/py/api/ui.page_fluid.html) `ui.page_fluid(*args, title=None, lang=None, **kwargs)` -- [ui.page_fixed](https://shiny.posit.co/py/api/ui.page_fixed.html) +- [ui.page_fixed](https://shiny.posit.co/py/api/ui.page_fixed.html) `ui.page_fixed(*args, title=None, lang=None, **kwargs)` -- [ui.page_fillable](https://shiny.posit.co/py/api/ui.page_fillable.html) +- [ui.page_fillable](https://shiny.posit.co/py/api/ui.page_fillable.html) `ui.page_fillable(*args, padding=None, gap=None, fillable_mobile=False, title=None, lang=None, **kwargs)` :::{.border-bottom .blue .mt-6 .mb-5} @@ -36,7 +39,7 @@ To create a layout using the grid system, first build a grid by interlacing call :::{.column-screen-inset-right style="max-width:800px;"} + @@ -49,7 +52,7 @@ app_ui = ui.page_fluid( ), #<< ui.row( #<< ui.column(6, ui.card("Card 2")), #<< - ui.column(6, ui.card("Card 3")) #<< + ui.column(6, ui.card("Card 3")) #<< ) #<< ) @@ -64,11 +67,11 @@ app = App(app_ui, server) Notice three things about the app: -1. You always create rows before columns, and then embed columns within the rows. +1. You always create rows before columns, and then embed columns within the rows. -2. Rows are always as wide as the container they appear in, but columns take a `width` parameter. `width` should be an integer from 1 to 12. Bootstrap uses `width` to determine the width of the column relative to the row that contains it: +2. Rows are always as wide as the container they appear in, but columns take a `width` parameter. `width` should be an integer from 1 to 12. Bootstrap uses `width` to determine the width of the column relative to the row that contains it: - - A column of `width=12` will span the entire row. + - A column of `width=12` will span the entire row. - A column of `width=6` will span half the row. - A column of `width=3` will span a quarter of the row. - And so on. @@ -83,7 +86,7 @@ In addition to setting a column's width, you can also set its offset with the `o + @@ -112,7 +115,7 @@ app = App(app_ui, server)

Edit in Shinylive

::: - + :::{.border-bottom .blue .mt-6 .mb-5} ::: @@ -127,7 +130,7 @@ Consider this page layout: + @@ -138,7 +141,7 @@ from shiny import App, render, ui app_ui = ui.page_fluid( ui.row( - ui.column(12, + ui.column(12, ui.card("Width 12", ui.row( ui.column(6, @@ -164,22 +167,22 @@ app = App(app_ui, server)

Edit in Shinylive

::: - + :::{.border-bottom .blue .mt-6 .mb-5} ::: ## Controlling for page width and height There are multiple types of Shiny grids: fluid, fixed, and fillable. The fluid grid system is -recommended for most applications and is the default for Shiny functions like `ui.page_navbar()` and `ui.page_sidebar()`. So far, all the examples on this page have used the fluid grid system. +recommended for most applications and is the default for Shiny functions like `ui.page_navbar()` and `ui.page_sidebar()`. So far, all the examples on this page have used the fluid grid system. Each grid system uses a flexibly sub-dividable 12-column grid for layout. They differ in how they interact with the viewer's browser window: -* The fluid system always occupies the full width of the web page and re-sizes its components dynamically as the size of the page changes. +* The fluid system always occupies the full width of the web page and re-sizes its components dynamically as the size of the page changes. -* The fixed system occupies a fixed width of 940 pixels by default and may assume other widths when Bootstrap's responsive layout kicks in (e.g., when on a tablet). +* The fixed system occupies a fixed width of 940 pixels by default and may assume other widths when Bootstrap's responsive layout kicks in (e.g., when on a tablet). -* The fillable system always occupies the full width _and height_ of the web page and re-sizes its components dynamically as the size of the page changes. +* The fillable system always occupies the full width _and height_ of the web page and re-sizes its components dynamically as the size of the page changes. !["Differences between the three systems"](/images/page-control.png){.pt-4} @@ -190,7 +193,7 @@ Each grid system uses a flexibly sub-dividable 12-column grid for layout. They d + @@ -235,7 +238,7 @@ To make a fluid grid that adapts to the width of the user's viewing window, buil + @@ -284,7 +287,7 @@ The main benefit of a fixed grid is that it provides stronger guarantees about h + diff --git a/layouts/index.qmd b/layouts/index.qmd index 67078ef7..83f6d4fb 100644 --- a/layouts/index.qmd +++ b/layouts/index.qmd @@ -2,14 +2,17 @@ pagetitle: "Shiny Layouts" anchor-sections: false sidebar: false -pagedescription: Frameworks that will allow the simplest and most complicated app to be useable and scalable. Is it bursting at the seams with content? Quickly change the layout for a fresh start. +pagedescription: > + Frameworks that will allow the simplest and most complicated app to be useable and + scalable. Is it bursting at the seams with content? Quickly change the layout for a + fresh start. toc: false page-layout: article resources: - _partials/layouts.lottie format: html: - css: + css: - _partials/layouts.css - _partials/layouts-list.css code-overflow: wrap @@ -27,7 +30,7 @@ Shiny Layouts :::: {.g-col-md-5 .g-col-10 .g-start-4 .components-hero-img-container}
- +
@@ -44,10 +47,10 @@ Shiny Layouts :::: {.g-col-xl-3 .g-col-12} ::: {.sticky-xl-top .pt-4} -

+

Navbars - +

A navbar adds a navigation bar to your app, allowing users to easily navigate your app. @@ -106,10 +109,10 @@ A navbar adds a navigation bar to your app, allowing users to easily navigate yo ::: {.sticky-xl-top .pt-4} -

+

Sidebars - +

A sidebar layout creates a sidebar, typically used for inputs, and a large main area, typically used for outputs. @@ -157,7 +160,7 @@ A sidebar layout creates a sidebar, typically used for inputs, and a large main - +
@@ -204,10 +207,10 @@ A sidebar layout creates a sidebar, typically used for inputs, and a large main ::: {.sticky-xl-top .pt-4} -

+

Tabs - +

Tabs and navigation allow you to create apps that have multiple pages. @@ -241,7 +244,7 @@ Tabs and navigation allow you to create apps that have multiple pages.
@@ -398,10 +401,10 @@ Use panels and cards to define areas of related content. :::: {.g-col-xl-3 .g-col-12} ::: {.sticky-xl-top .pt-4} -

+

Arrange Elements - +

Use rows and columns to create your own layout for every device size. @@ -446,11 +449,11 @@ Use rows and columns to create your own layout for every device size.

- +
- +
@@ -475,4 +478,4 @@ Use rows and columns to create your own layout for every device size. :::::: :::::::: - \ No newline at end of file + From b5a185186acefaaee24e42b03246c2ff4cac5a95 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 19 Jan 2024 17:11:48 -0500 Subject: [PATCH 04/19] adjust styles of .app-preview --- quarto-style.scss | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/quarto-style.scss b/quarto-style.scss index 9adc317b..305f37af 100644 --- a/quarto-style.scss +++ b/quarto-style.scss @@ -1705,15 +1705,23 @@ btn.action-button:hover { } } +.app-preview { + padding: 1em; + border: 1px solid rgba(233,236,239,.9); + border-radius: 0.5em; + margin-block: 1em; +} + .app-preview .sourceCode { padding: 0; box-shadow: none; - margin-block: 1em; + margin: 0; + border: none; } .app-preview .shinylive-wrapper { margin: 0; - padding: 1em; + padding: 0; background-color: var(--bs-body-bg); } From 7fe37eabbec024ecd18dff839fcd70122826d6c3 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 19 Jan 2024 17:11:55 -0500 Subject: [PATCH 05/19] fixup require dev shinylive --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 240dc187..c46a4196 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ jupyter jupyter_client < 8.0.0 tabulate -shinylive==0.1.3 +shinylive @ git+https://github.com/posit-dev/py-shinylive.git@main matplotlib==3.8.1 shiny seaborn==0.13.0 From b2619d6f070e46f1fdd3bc2ae8eef36fe632027b Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 19 Jan 2024 17:14:12 -0500 Subject: [PATCH 06/19] expressify layout/navbars --- _quarto.yml | 4 +- layouts/index.qmd | 16 +-- layouts/navbars.qmd | 108 ------------------- layouts/navbars/app-navbar-bottom-express.py | 21 ++++ layouts/navbars/app-navbar-bottom.py | 17 +++ layouts/navbars/app-navbar-top-express.py | 20 ++++ layouts/navbars/app-navbar-top.py | 16 +++ layouts/navbars/index.qmd | 82 ++++++++++++++ 8 files changed, 166 insertions(+), 118 deletions(-) delete mode 100644 layouts/navbars.qmd create mode 100644 layouts/navbars/app-navbar-bottom-express.py create mode 100644 layouts/navbars/app-navbar-bottom.py create mode 100644 layouts/navbars/app-navbar-top-express.py create mode 100644 layouts/navbars/app-navbar-top.py create mode 100644 layouts/navbars/index.qmd diff --git a/_quarto.yml b/_quarto.yml index 07aedfa9..c2172999 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -164,9 +164,9 @@ website: - section: "![](/images/navbars-blue.svg){.sidebar-icon .sidebar-subtitle}__Navbars__" contents: - text: "Navbar at Top" - href: "/layouts/navbars.html#navbar-at-top" + href: "/layouts/navbars/index.html#navbar-at-top" - text: "Navbar at Bottom" - href: "/layouts/navbars.html#navbar-at-bottom" + href: "/layouts/navbars/index.html#navbar-at-bottom" - section: "![](/images/sidebars-blue.svg){.sidebar-icon .sidebar-subtitle}__Sidebars__" contents: - text: "Sidebar on Left" diff --git a/layouts/index.qmd b/layouts/index.qmd index 83f6d4fb..ebdb0d01 100644 --- a/layouts/index.qmd +++ b/layouts/index.qmd @@ -48,14 +48,14 @@ Shiny Layouts ::: {.sticky-xl-top .pt-4}

- + Navbars

A navbar adds a navigation bar to your app, allowing users to easily navigate your app. -Learn Navbars +Learn Navbars ::: :::: :::: {.g-col-xl-9 .g-col-12 .pt-3 .pt-xl-5 .ps-0 .ps-xl-5 .ms-0 .ms-xl-4} @@ -63,17 +63,17 @@ A navbar adds a navigation bar to your app, allowing users to easily navigate yo @@ -81,17 +81,17 @@ A navbar adds a navigation bar to your app, allowing users to easily navigate yo diff --git a/layouts/navbars.qmd b/layouts/navbars.qmd deleted file mode 100644 index 02911535..00000000 --- a/layouts/navbars.qmd +++ /dev/null @@ -1,108 +0,0 @@ ---- -title: "Navbars" -description: "A navbar adds a navigation bar, allowing users to easily navigate your Shiny app." ---- - -A navbar adds a navigation bar to your app, allowing users to easily navigate your app. - -## Relevant functions - -- [ui.page_navbar](https://shiny.posit.co/py/api/ui.page_navbar.html) - `ui.page_navbar(*args, title=None, id=None, selected=None, sidebar=None, fillable=True, fillable_mobile=False, gap=None, padding=None, position='static-top', header=None, footer=None, bg=None, inverse=False, underline=True, collapsible=True, fluid=True, window_title=MISSING, lang=None)` -- [ui.nav](https://shiny.posit.co/py/api/ui.nav.html) - `ui.nav_panel(title, *args, value=None, icon=None)` - -:::{.border-bottom .blue .mt-6 .mb-5} -::: - - -## Navbar at top - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - - - -```{.python filename="app.py" } -from shiny import App, ui - -app_ui = ui.page_navbar( #<< - ui.nav_panel("A", "Page A content"), - ui.nav_panel("B", "Page B content"), - ui.nav_panel("C", "Page C content"), - title="App with navbar", #<< - id="navbar" #<< -) #<< - -def server(input, output, session): - pass - -app = App(app_ui, server) -``` - - -

Edit in Shinylive

- -::: - -Follow these steps to add a navbar to the top of your app: - - 1. Define a navbar page layout with `ui.page_navbar()`. - - 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.page_navbar()` to control the items displayed in the navbar. - - 3. Set the `title` argument of `ui.page_navbar()` to set the browser window title. - - 4. _Optional:_ Pass a string to the `id` argument of `ui.page_navbar()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`. - -:::{.border-bottom .blue .my-5} -::: - -## Navbar at bottom - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - - - - - -```{.python filename="app.py" } -from shiny import App, ui - -app_ui = ui.page_navbar( #<< - ui.nav_panel("A", "Page A content"), #<< - ui.nav_panel("B", "Page B content"), #<< - ui.nav_panel("C", "Page C content"), #<< - title="App with navbar", #<< - id="navbar", #<< - position="fixed-bottom", #<< -) #<< - -def server(input, output, session): - pass - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - -Follow these steps to add a navbar to the bottom of your app: - - 1. Define a navbar page layout with `ui.page_navbar()`. - - 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.page_navbar()` to control the items displayed in the navbar. - - 3. Set the `position` parameter of `ui.page_navbar()` to `"fixed-bottom"` to pin the navbar to the bottom of the app. By default, `position` is `"static-top"`, which causes the navbar to display at the top with normal scrolling behavior. You can also pin the navbar to the top (`position="fixed-top"`). - - 4. Set the `title` argument of `ui.page_navbar()` to set the browser window title. - - 5. _Optional:_ Pass a string to the `id` argument of `ui.page_navbar()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`. - diff --git a/layouts/navbars/app-navbar-bottom-express.py b/layouts/navbars/app-navbar-bottom-express.py new file mode 100644 index 00000000..4e9310f2 --- /dev/null +++ b/layouts/navbars/app-navbar-bottom-express.py @@ -0,0 +1,21 @@ +from shiny.express import ui +from shiny.ui import page_navbar + +ui.page_opts( + title="App with navbar", # << + page_fn=lambda *args, **kwargs: page_navbar( + *args, + id="page", + position="fixed-bottom", # << + **kwargs, + ), # << +) + +with ui.nav_panel("A"): # << + "Page A content" + +with ui.nav_panel("B"): # << + "Page B content" + +with ui.nav_panel("C"): # << + "Page C content" diff --git a/layouts/navbars/app-navbar-bottom.py b/layouts/navbars/app-navbar-bottom.py new file mode 100644 index 00000000..5c5f6f95 --- /dev/null +++ b/layouts/navbars/app-navbar-bottom.py @@ -0,0 +1,17 @@ +from shiny import App, ui + +app_ui = ui.page_navbar( # << + ui.nav_panel("A", "Page A content"), # << + ui.nav_panel("B", "Page B content"), # << + ui.nav_panel("C", "Page C content"), # << + title="App with navbar", # << + id="page", # << + position="fixed-bottom", # << +) # << + + +def server(input, output, session): + pass + + +app = App(app_ui, server) diff --git a/layouts/navbars/app-navbar-top-express.py b/layouts/navbars/app-navbar-top-express.py new file mode 100644 index 00000000..8e2b79eb --- /dev/null +++ b/layouts/navbars/app-navbar-top-express.py @@ -0,0 +1,20 @@ +from shiny.express import ui +from shiny.ui import page_navbar + +ui.page_opts( + title="App with navbar", # << + page_fn=lambda *args, **kwargs: page_navbar( + *args, + id="page", # << + **kwargs, + ), # << +) + +with ui.nav_panel("A"): # << + "Page A content" + +with ui.nav_panel("B"): # << + "Page B content" + +with ui.nav_panel("C"): # << + "Page C content" diff --git a/layouts/navbars/app-navbar-top.py b/layouts/navbars/app-navbar-top.py new file mode 100644 index 00000000..2484f935 --- /dev/null +++ b/layouts/navbars/app-navbar-top.py @@ -0,0 +1,16 @@ +from shiny import App, ui + +app_ui = ui.page_navbar( # << + ui.nav_panel("A", "Page A content"), # << + ui.nav_panel("B", "Page B content"), # << + ui.nav_panel("C", "Page C content"), # << + title="App with navbar", # << + id="page", # << +) # << + + +def server(input, output, session): + pass + + +app = App(app_ui, server) diff --git a/layouts/navbars/index.qmd b/layouts/navbars/index.qmd new file mode 100644 index 00000000..b538bc9b --- /dev/null +++ b/layouts/navbars/index.qmd @@ -0,0 +1,82 @@ +--- +title: "Navbars" +description: "A navbar adds a navigation bar, allowing users to easily navigate your Shiny app." +--- + +```{python} +#| include: false +import shinylive + +import sys +sys.path.append("../..") + +from docs.helpers import express_core_preview, shinylive_app_preview +``` + +A navbar adds a navigation bar to your app, allowing users to easily navigate your app. + +## Relevant functions + +- [ui.page_navbar](https://shiny.posit.co/py/api/ui.page_navbar.html) + `ui.page_navbar(*args, title=None, id=None, selected=None, sidebar=None, fillable=True, fillable_mobile=False, gap=None, padding=None, position='static-top', header=None, footer=None, bg=None, inverse=False, underline=True, collapsible=True, fluid=True, window_title=MISSING, lang=None)` +- [ui.nav](https://shiny.posit.co/py/api/ui.nav.html) + `ui.nav_panel(title, *args, value=None, icon=None)` + +:::{.border-bottom .blue .mt-6 .mb-5} +::: + + +## Navbar at top + +:::{.column-screen-inset-right style="max-width:800px;"} + +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-navbar-top.py", viewer_height=150, div_attrs=".p-0") +express_core_preview("app-navbar-top-express.py", "app-navbar-top.py") +``` + +::: + +Follow these steps to add a navbar to the top of your app: + + 1. Define a navbar page layout with `ui.page_navbar()`. + + 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.page_navbar()` to control the items displayed in the navbar. + + 3. Set the `title` argument of `ui.page_navbar()` to set the browser window title. + + 4. _Optional:_ Pass a string to the `id` argument of `ui.page_navbar()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`. + +:::{.border-bottom .blue .my-5} +::: + +## Navbar at bottom + +:::{.column-screen-inset-right style="max-width:800px;"} + +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-navbar-bottom.py", viewer_height=150, div_attrs=".p-0") +express_core_preview("app-navbar-bottom-express.py", "app-navbar-bottom.py") +``` + +::: + + +Follow these steps to add a navbar to the bottom of your app: + + 1. Define a navbar page layout with `ui.page_navbar()`. + + 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.page_navbar()` to control the items displayed in the navbar. + + 3. Set the `position` parameter of `ui.page_navbar()` to `"fixed-bottom"` to pin the navbar to the bottom of the app. By default, `position` is `"static-top"`, which causes the navbar to display at the top with normal scrolling behavior. You can also pin the navbar to the top (`position="fixed-top"`). + + 4. Set the `title` argument of `ui.page_navbar()` to set the browser window title. + + 5. _Optional:_ Pass a string to the `id` argument of `ui.page_navbar()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`. + From af16de96c3b2dbf695214650e5f4c60743f8a6cd Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 22 Jan 2024 14:07:38 -0500 Subject: [PATCH 07/19] Use functools.partial() instead of a lambda --- layouts/navbars/app-navbar-bottom-express.py | 9 +++------ layouts/navbars/app-navbar-top-express.py | 8 +++----- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/layouts/navbars/app-navbar-bottom-express.py b/layouts/navbars/app-navbar-bottom-express.py index 4e9310f2..e8fc860e 100644 --- a/layouts/navbars/app-navbar-bottom-express.py +++ b/layouts/navbars/app-navbar-bottom-express.py @@ -1,14 +1,11 @@ +from functools import partial + from shiny.express import ui from shiny.ui import page_navbar ui.page_opts( title="App with navbar", # << - page_fn=lambda *args, **kwargs: page_navbar( - *args, - id="page", - position="fixed-bottom", # << - **kwargs, - ), # << + page_fn=partial(page_navbar, id="page", position="fixed-bottom"), # << ) with ui.nav_panel("A"): # << diff --git a/layouts/navbars/app-navbar-top-express.py b/layouts/navbars/app-navbar-top-express.py index 8e2b79eb..fc11ba3b 100644 --- a/layouts/navbars/app-navbar-top-express.py +++ b/layouts/navbars/app-navbar-top-express.py @@ -1,13 +1,11 @@ +from functools import partial + from shiny.express import ui from shiny.ui import page_navbar ui.page_opts( title="App with navbar", # << - page_fn=lambda *args, **kwargs: page_navbar( - *args, - id="page", # << - **kwargs, - ), # << + page_fn=partial(page_navbar, id="page"), # << ) with ui.nav_panel("A"): # << From c4218adfac3211e13e594c31fa82d86fc25070aa Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 22 Jan 2024 15:36:57 -0500 Subject: [PATCH 08/19] use latest iteration of shinylive package --- docs/helpers.py | 9 ++++----- layouts/navbars/index.qmd | 2 -- requirements.txt | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/docs/helpers.py b/docs/helpers.py index 519db3cc..ebd413c4 100644 --- a/docs/helpers.py +++ b/docs/helpers.py @@ -28,10 +28,9 @@ def append_shinylive_chunk( else: app_file = files.pop(0) - bundle = shinylive._url.create_shinylive_bundle_file(app_file, files, language) - contents = shinylive._url.create_shinylive_chunk_contents(bundle) + bundle = shinylive.url_encode(app_file, files, language) - self.append(shinylive_chunk(contents, **kwargs)) + self.append(bundle.chunk(**kwargs)) def shinylive_chunk( @@ -226,11 +225,11 @@ def express_core_preview( if app_file is None: continue - shinylive_url = shinylive.encode_shinylive_url(app_file, files, language) + sl_app = shinylive.url_encode(app_file, files, language) block.append("### " + tab_name) block.append( - '```{.python .code-overflow-scroll shinylive="' + shinylive_url + '"}' + '```{.python .code-overflow-scroll shinylive="' + sl_app.url() + '"}' ) block.append_file(app_file) block.extend(["```", ""]) diff --git a/layouts/navbars/index.qmd b/layouts/navbars/index.qmd index b538bc9b..e1586ef5 100644 --- a/layouts/navbars/index.qmd +++ b/layouts/navbars/index.qmd @@ -5,8 +5,6 @@ description: "A navbar adds a navigation bar, allowing users to easily navigate ```{python} #| include: false -import shinylive - import sys sys.path.append("../..") diff --git a/requirements.txt b/requirements.txt index c46a4196..6c060859 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ jupyter jupyter_client < 8.0.0 tabulate -shinylive @ git+https://github.com/posit-dev/py-shinylive.git@main +shinylive @ git+https://github.com/posit-dev/py-shinylive.git@shinylive-bundle-class matplotlib==3.8.1 shiny seaborn==0.13.0 From 3860a9a7b6533a65e4bf4e8b8fc66723c6f9c238 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 22 Jan 2024 15:37:15 -0500 Subject: [PATCH 09/19] expressify layouts/sidebars --- _quarto.yml | 8 +- layouts/index.qmd | 28 +-- layouts/sidebars.qmd | 169 ------------------ layouts/sidebars/app-sidebar-card-core.py | 17 ++ layouts/sidebars/app-sidebar-card-express.py | 8 + .../sidebars/app-sidebar-collapsed-core.py | 13 ++ .../sidebars/app-sidebar-collapsed-express.py | 6 + layouts/sidebars/app-sidebar-left-core.py | 13 ++ layouts/sidebars/app-sidebar-left-express.py | 6 + layouts/sidebars/app-sidebar-right-core.py | 13 ++ layouts/sidebars/app-sidebar-right-express.py | 6 + layouts/sidebars/index.qmd | 131 ++++++++++++++ 12 files changed, 231 insertions(+), 187 deletions(-) delete mode 100644 layouts/sidebars.qmd create mode 100644 layouts/sidebars/app-sidebar-card-core.py create mode 100644 layouts/sidebars/app-sidebar-card-express.py create mode 100644 layouts/sidebars/app-sidebar-collapsed-core.py create mode 100644 layouts/sidebars/app-sidebar-collapsed-express.py create mode 100644 layouts/sidebars/app-sidebar-left-core.py create mode 100644 layouts/sidebars/app-sidebar-left-express.py create mode 100644 layouts/sidebars/app-sidebar-right-core.py create mode 100644 layouts/sidebars/app-sidebar-right-express.py create mode 100644 layouts/sidebars/index.qmd diff --git a/_quarto.yml b/_quarto.yml index c2172999..62d7815e 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -170,13 +170,13 @@ website: - section: "![](/images/sidebars-blue.svg){.sidebar-icon .sidebar-subtitle}__Sidebars__" contents: - text: "Sidebar on Left" - href: "/layouts/sidebars.html#sidebar-on-the-left" + href: "/layouts/sidebars/index.html#sidebar-on-the-left" - text: "Sidebar on Right" - href: "/layouts/sidebars.html#sidebar-on-the-right" + href: "/layouts/sidebars/index.html#sidebar-on-the-right" - text: "Sidebar Within a Card" - href: "/layouts/sidebars.html#sidebar-within-a-card" + href: "/layouts/sidebars/index.html#sidebar-within-a-card" - text: "Collapsed Sidebar" - href: "/layouts/sidebars.html#collapsed-sidebar" + href: "/layouts/sidebars/index.html#collapsed-sidebar" - section: "![](/images/tabs-blue.svg){.sidebar-icon .sidebar-subtitle}__Tabs__" contents: - text: "Tabset with Pill Navigation" diff --git a/layouts/index.qmd b/layouts/index.qmd index ebdb0d01..d70c843a 100644 --- a/layouts/index.qmd +++ b/layouts/index.qmd @@ -110,14 +110,14 @@ A navbar adds a navigation bar to your app, allowing users to easily navigate yo

- + Sidebars

A sidebar layout creates a sidebar, typically used for inputs, and a large main area, typically used for outputs. -Learn Sidebars +Learn Sidebars ::: :::: @@ -128,17 +128,17 @@ A sidebar layout creates a sidebar, typically used for inputs, and a large main @@ -146,17 +146,17 @@ A sidebar layout creates a sidebar, typically used for inputs, and a large main @@ -164,34 +164,34 @@ A sidebar layout creates a sidebar, typically used for inputs, and a large main diff --git a/layouts/sidebars.qmd b/layouts/sidebars.qmd deleted file mode 100644 index 9ed316f0..00000000 --- a/layouts/sidebars.qmd +++ /dev/null @@ -1,169 +0,0 @@ ---- -title: "Sidebars" -description: "A sidebar layout creates a sidebar in your Shiny app, typically used for inputs, and a large main area, typically used for outputs." ---- - -A sidebar layout creates a sidebar, typically used for inputs, and a large main area, typically used for outputs. - -## Relevant functions - -- [ui.layout_sidebar](https://shiny.posit.co/py/api/ui.layout_sidebar.html) - `ui.layout_sidebar(sidebar, *args, fillable=True, fill=True, bg=None, fg=None, border=None, border_radius=None, border_color=None, gap=None, padding=None, height=None, **kwargs)` - -- [ui.sidebar](https://shiny.posit.co/py/api/ui.sidebar.html#shiny.ui.sidebar) - `ui.sidebar(*args, width=250, position='left', open='desktop', id=None, title=None, bg=None, fg=None, class_=None, max_height_mobile=None, gap=None, padding=None)` - - -:::{.border-bottom .blue .mt-6 .mb-5} -::: - -## Sidebar on the left - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - -```{.python filename="app.py" } -from shiny import App, ui - -app_ui = ui.page_sidebar( #<< - ui.sidebar("Sidebar", bg="#f8f8f8"), #<< - "Main content" -) #<< - - -def server(input, output, session): - pass - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - -Follow these steps to add a left-side sidebar to your app: - - 1. Add `ui.layout_sidebar()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.layout_sidebar()` creates a sidebar layout. - - 2. Pass `ui.sidebar()`as the first argument of `ui.layout_sidebar()` to create the sidebar. By default, the `position` parameter of `ui.sidebar()` is "left" and the sidebar will appear on the left. - - 3. Supply additional components (output components, cards, text, etc.) to `ui.layout_sidebar()` to define the contents of the main content area. - -:::{.border-bottom .blue .my-5} -::: - -## Sidebar on the right - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - -```{.python filename="app.py" } -from shiny import App, ui - -app_ui = ui.page_sidebar ( #<< - ui.sidebar("Sidebar", position="right", bg="#f8f8f8"), #<< - "Main content" -) #<< - -def server(input, output, session): - pass - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - -Follow these steps to add a right-side sidebar to your app: - - 1. Add `ui.layout_sidebar()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.layout_sidebar()` creates a sidebar layout. - - 2. Pass `ui.sidebar()`as the first argument of `ui.layout_sidebar()` to create the sidebar. - - 3. Pass `position="right"` to `ui.sidebar()`. The `position` argument controls where the sidebar appears relative to the main content. - - 3. Supply components (e.g., inputs) to `ui.sidebar()` to define the sidebar's contents. Supply additional components (e.g., output components, cards, etc.) to `ui.layout_sidebar()` to define the contents of the main content area. - -:::{.border-bottom .blue .my-5} -::: - -## Sidebar within a card - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - -```{.python filename="app.py" } -from shiny import App, ui - -app_ui = ui.page_fluid( - ui.card( #<< - ui.layout_sidebar( #<< - ui.sidebar("Sidebar", bg="#f8f8f8"), #<< - "Card content", - ), #<< - ) #<< -) - -def server(input, output, session): - pass - - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - -Follow these steps to add a sidebar within a card to your app: - - 1. Add `ui.card()` to the UI of your app to create a card. - 2. Pass `ui.layout_sidebar()` to `ui.card()` to define a sidebar layout within the card. - 3. Add `ui.sidebar()` and additional elements to `ui.layout_sidebar()` to define the sidebar and main content as usual. - 4. Add inputs or other components as desired to `ui.sidebar()` to define the sidebar's contents. - -:::{.border-bottom .blue .my-5} -::: - -## Collapsed sidebar - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - -```{.python filename="app.py" } -from shiny import App, ui - -app_ui = ui.page_sidebar( - ui.sidebar("Sidebar", bg="#f8f8f8", open="closed"), #<< - "Main content" -) - - -def server(input, output, session): - pass - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - - -`ui.sidebar()` has an `open` parameter that defines whether the sidebar appears open or closed when the app launches. To create a sidebar that is initially closed, set the `open` parameter to `"closed"`. - -The other options for `open` are: - - * `"desktop"`: The default. The sidebar starts open on a desktop screen and closed on mobile. - * `"open"`: The sidebar starts open and can be closed. - * `"always"`: The sidebar is always open and cannot be closed. - diff --git a/layouts/sidebars/app-sidebar-card-core.py b/layouts/sidebars/app-sidebar-card-core.py new file mode 100644 index 00000000..7ccdeb43 --- /dev/null +++ b/layouts/sidebars/app-sidebar-card-core.py @@ -0,0 +1,17 @@ +from shiny import App, ui + +app_ui = ui.page_fluid( + ui.card( # << + ui.layout_sidebar( # << + ui.sidebar("Sidebar", bg="#f8f8f8"), # << + "Card content", + ), # << + ) # << +) + + +def server(input, output, session): + pass + + +app = App(app_ui, server) diff --git a/layouts/sidebars/app-sidebar-card-express.py b/layouts/sidebars/app-sidebar-card-express.py new file mode 100644 index 00000000..7d60cc67 --- /dev/null +++ b/layouts/sidebars/app-sidebar-card-express.py @@ -0,0 +1,8 @@ +from shiny.express import ui + +with ui.card(): + with ui.layout_sidebar(): + with ui.sidebar(bg="#f8f8f8"): + "Sidebar" + + "Card content" diff --git a/layouts/sidebars/app-sidebar-collapsed-core.py b/layouts/sidebars/app-sidebar-collapsed-core.py new file mode 100644 index 00000000..237cdae1 --- /dev/null +++ b/layouts/sidebars/app-sidebar-collapsed-core.py @@ -0,0 +1,13 @@ +from shiny import App, ui + +app_ui = ui.page_sidebar( + ui.sidebar("Sidebar", bg="#f8f8f8", open="closed"), # << + "Main content", +) + + +def server(input, output, session): + pass + + +app = App(app_ui, server) diff --git a/layouts/sidebars/app-sidebar-collapsed-express.py b/layouts/sidebars/app-sidebar-collapsed-express.py new file mode 100644 index 00000000..bf2d0b14 --- /dev/null +++ b/layouts/sidebars/app-sidebar-collapsed-express.py @@ -0,0 +1,6 @@ +from shiny.express import ui + +with ui.sidebar(open="closed", bg="#f8f8f8"): + "Sidebar" + +"Main content" diff --git a/layouts/sidebars/app-sidebar-left-core.py b/layouts/sidebars/app-sidebar-left-core.py new file mode 100644 index 00000000..aff93f81 --- /dev/null +++ b/layouts/sidebars/app-sidebar-left-core.py @@ -0,0 +1,13 @@ +from shiny import App, ui + +app_ui = ui.page_sidebar( # << + ui.sidebar("Sidebar", bg="#f8f8f8"), # << + "Main content", +) # << + + +def server(input, output, session): + pass + + +app = App(app_ui, server) diff --git a/layouts/sidebars/app-sidebar-left-express.py b/layouts/sidebars/app-sidebar-left-express.py new file mode 100644 index 00000000..e5d0e145 --- /dev/null +++ b/layouts/sidebars/app-sidebar-left-express.py @@ -0,0 +1,6 @@ +from shiny.express import ui + +with ui.sidebar(bg="#f8f8f8"): # << + "Sidebar" # << + +"Main content" diff --git a/layouts/sidebars/app-sidebar-right-core.py b/layouts/sidebars/app-sidebar-right-core.py new file mode 100644 index 00000000..ae5c68fb --- /dev/null +++ b/layouts/sidebars/app-sidebar-right-core.py @@ -0,0 +1,13 @@ +from shiny import App, ui + +app_ui = ui.page_sidebar( # << + ui.sidebar("Sidebar", position="right", bg="#f8f8f8"), # << + "Main content", +) # << + + +def server(input, output, session): + pass + + +app = App(app_ui, server) diff --git a/layouts/sidebars/app-sidebar-right-express.py b/layouts/sidebars/app-sidebar-right-express.py new file mode 100644 index 00000000..d43bf43b --- /dev/null +++ b/layouts/sidebars/app-sidebar-right-express.py @@ -0,0 +1,6 @@ +from shiny.express import ui + +with ui.sidebar(position="right", bg="#f8f8f8"): # << + "Sidebar" # << + +"Main content" diff --git a/layouts/sidebars/index.qmd b/layouts/sidebars/index.qmd new file mode 100644 index 00000000..137e6374 --- /dev/null +++ b/layouts/sidebars/index.qmd @@ -0,0 +1,131 @@ +--- +title: "Sidebars" +description: > + A sidebar layout creates a sidebar in your Shiny app, typically used for inputs, and a + large main area, typically used for outputs. +--- + +```{python} +#| include: false +import sys +sys.path.append("../..") + +from docs.helpers import express_core_preview, shinylive_app_preview +``` + +A sidebar layout creates a sidebar, typically used for inputs, and a large main area, typically used for outputs. + +## Relevant functions + +- [ui.layout_sidebar](https://shiny.posit.co/py/api/ui.layout_sidebar.html) + `ui.layout_sidebar(sidebar, *args, fillable=True, fill=True, bg=None, fg=None, border=None, border_radius=None, border_color=None, gap=None, padding=None, height=None, **kwargs)` + +- [ui.sidebar](https://shiny.posit.co/py/api/ui.sidebar.html#shiny.ui.sidebar) + `ui.sidebar(*args, width=250, position='left', open='desktop', id=None, title=None, bg=None, fg=None, class_=None, max_height_mobile=None, gap=None, padding=None)` + + +:::{.border-bottom .blue .mt-6 .mb-5} +::: + +## Sidebar on the left + +:::{.column-screen-inset-right style="max-width:800px;"} + +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-sidebar-left-core.py", viewer_height=125, div_attrs=".p-0") +express_core_preview("app-sidebar-left-express.py", "app-sidebar-left-core.py") +``` + +::: + +Follow these steps to add a left-side sidebar to your app: + + 1. Add `ui.layout_sidebar()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.layout_sidebar()` creates a sidebar layout. + + 2. Pass `ui.sidebar()`as the first argument of `ui.layout_sidebar()` to create the sidebar. By default, the `position` parameter of `ui.sidebar()` is "left" and the sidebar will appear on the left. + + 3. Supply additional components (output components, cards, text, etc.) to `ui.layout_sidebar()` to define the contents of the main content area. + +:::{.border-bottom .blue .my-5} +::: + +## Sidebar on the right + +:::{.column-screen-inset-right style="max-width:800px;"} + +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-sidebar-right-core.py", viewer_height=125, div_attrs=".p-0") +express_core_preview("app-sidebar-right-express.py", "app-sidebar-right-core.py") +``` + +::: + + +Follow these steps to add a right-side sidebar to your app: + + 1. Add `ui.layout_sidebar()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.layout_sidebar()` creates a sidebar layout. + + 2. Pass `ui.sidebar()`as the first argument of `ui.layout_sidebar()` to create the sidebar. + + 3. Pass `position="right"` to `ui.sidebar()`. The `position` argument controls where the sidebar appears relative to the main content. + + 3. Supply components (e.g., inputs) to `ui.sidebar()` to define the sidebar's contents. Supply additional components (e.g., output components, cards, etc.) to `ui.layout_sidebar()` to define the contents of the main content area. + +:::{.border-bottom .blue .my-5} +::: + +## Sidebar within a card + +:::{.column-screen-inset-right style="max-width:800px;"} + +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-sidebar-card-core.py", viewer_height=125, div_attrs=".p-0") +express_core_preview("app-sidebar-card-express.py", "app-sidebar-card-core.py") +``` + +::: + + +Follow these steps to add a sidebar within a card to your app: + + 1. Add `ui.card()` to the UI of your app to create a card. + 2. Pass `ui.layout_sidebar()` to `ui.card()` to define a sidebar layout within the card. + 3. Add `ui.sidebar()` and additional elements to `ui.layout_sidebar()` to define the sidebar and main content as usual. + 4. Add inputs or other components as desired to `ui.sidebar()` to define the sidebar's contents. + +:::{.border-bottom .blue .my-5} +::: + +## Collapsed sidebar + +:::{.column-screen-inset-right style="max-width:800px;"} + +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-sidebar-collapsed-core.py", viewer_height=125, div_attrs=".p-0") +express_core_preview("app-sidebar-collapsed-express.py", "app-sidebar-collapsed-core.py") +``` + +::: + + + +`ui.sidebar()` has an `open` parameter that defines whether the sidebar appears open or closed when the app launches. To create a sidebar that is initially closed, set the `open` parameter to `"closed"`. + +The other options for `open` are: + + * `"desktop"`: The default. The sidebar starts open on a desktop screen and closed on mobile. + * `"open"`: The sidebar starts open and can be closed. + * `"always"`: The sidebar is always open and cannot be closed. + From 4a4be67d2f29a63fb64fd8368b50320de78f70b1 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 22 Jan 2024 16:48:31 -0500 Subject: [PATCH 10/19] expressify layouts/tabs --- _quarto.yml | 12 +- layouts/index.qmd | 40 +-- layouts/tabs.qmd | 330 ------------------- layouts/tabs/app-accordion-core.py | 18 + layouts/tabs/app-accordion-express.py | 17 + layouts/tabs/app-tabset-pill-card-core.py | 26 ++ layouts/tabs/app-tabset-pill-card-express.py | 22 ++ layouts/tabs/app-tabset-pill-list-core.py | 26 ++ layouts/tabs/app-tabset-pill-list-express.py | 20 ++ layouts/tabs/app-tabset-pills-core.py | 26 ++ layouts/tabs/app-tabset-pills-express.py | 20 ++ layouts/tabs/app-tabset-tab-card-core.py | 26 ++ layouts/tabs/app-tabset-tab-card-express.py | 22 ++ layouts/tabs/app-tabset-tab-core.py | 26 ++ layouts/tabs/app-tabset-tab-express.py | 20 ++ layouts/tabs/index.qmd | 190 +++++++++++ 16 files changed, 485 insertions(+), 356 deletions(-) delete mode 100644 layouts/tabs.qmd create mode 100644 layouts/tabs/app-accordion-core.py create mode 100644 layouts/tabs/app-accordion-express.py create mode 100644 layouts/tabs/app-tabset-pill-card-core.py create mode 100644 layouts/tabs/app-tabset-pill-card-express.py create mode 100644 layouts/tabs/app-tabset-pill-list-core.py create mode 100644 layouts/tabs/app-tabset-pill-list-express.py create mode 100644 layouts/tabs/app-tabset-pills-core.py create mode 100644 layouts/tabs/app-tabset-pills-express.py create mode 100644 layouts/tabs/app-tabset-tab-card-core.py create mode 100644 layouts/tabs/app-tabset-tab-card-express.py create mode 100644 layouts/tabs/app-tabset-tab-core.py create mode 100644 layouts/tabs/app-tabset-tab-express.py create mode 100644 layouts/tabs/index.qmd diff --git a/_quarto.yml b/_quarto.yml index 62d7815e..686ac95d 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -180,17 +180,17 @@ website: - section: "![](/images/tabs-blue.svg){.sidebar-icon .sidebar-subtitle}__Tabs__" contents: - text: "Tabset with Pill Navigation" - href: "/layouts/tabs.html#tabset-with-pill-navigation" + href: "/layouts/tabs/index.html#tabset-with-pill-navigation" - text: "Tabset with Pill List Navigation" - href: "/layouts/tabs.html#tabset-with-pill-list-navigation" + href: "/layouts/tabs/index.html#tabset-with-pill-list-navigation" - text: "Tabset with Tab Navigation" - href: "/layouts/tabs.html#tabset-with-tab-navigation" + href: "/layouts/tabs/index.html#tabset-with-tab-navigation" - text: "Card with a Tabbed Tabset" - href: "/layouts/tabs.html#card-with-a-tabbed-tabset" + href: "/layouts/tabs/index.html#card-with-a-tabbed-tabset" - text: "Card with a Pill Tabset" - href: "/layouts/tabs.html#card-with-a-pill-tabset" + href: "/layouts/tabs/index.html#card-with-a-pill-tabset" - text: "Vertically Collapsing Accordions" - href: "/layouts/tabs.html#vertically-collapsing-accordion-panels" + href: "/layouts/tabs/index.html#vertically-collapsing-accordion-panels" - section: "![](/images/cards-blue.svg){.sidebar-icon .sidebar-subtitle}__Panels & Cards__" contents: - text: "Main Image w/ Floating Panel" diff --git a/layouts/index.qmd b/layouts/index.qmd index d70c843a..d6080e3d 100644 --- a/layouts/index.qmd +++ b/layouts/index.qmd @@ -208,14 +208,14 @@ A sidebar layout creates a sidebar, typically used for inputs, and a large main

- + Tabs

Tabs and navigation allow you to create apps that have multiple pages. -Learn Tabs +Learn Tabs ::: :::: :::: {.g-col-xl-9 .g-col-12 .pt-3 .pt-xl-5 .ps-0 .ps-xl-5 .ms-0 .ms-xl-4} @@ -225,17 +225,17 @@ Tabs and navigation allow you to create apps that have multiple pages. @@ -243,34 +243,34 @@ Tabs and navigation allow you to create apps that have multiple pages. @@ -278,18 +278,18 @@ Tabs and navigation allow you to create apps that have multiple pages. @@ -297,34 +297,34 @@ Tabs and navigation allow you to create apps that have multiple pages. diff --git a/layouts/tabs.qmd b/layouts/tabs.qmd deleted file mode 100644 index b4cf7180..00000000 --- a/layouts/tabs.qmd +++ /dev/null @@ -1,330 +0,0 @@ ---- -title: "Tabs" -description: "Tabs and navigation allow you to create Shiny apps with multiple pages." ---- - -Tabs and navigation allow you to create apps that have multiple pages. - -## Relevant functions - -- [ui.accordion](https://shiny.posit.co/py/api/ui.accordion.html#shiny.ui.accordion) - `ui.accordion(*args, id=None, open=None, multiple=True, class_=None, width=None, height=None, **kwargs)` - -- [ui.accordion_panel](https://shiny.posit.co/py/api/ui.accordion_panel.html) - `ui.accordion_panel(title, *args, value=MISSING, icon=None, **kwargs)` - -- [ui.navset_card_tab](https://shiny.posit.co/py/api/ui.navset_card_tab.html) - `ui.navset_card_tab(*args, id=None, selected=None, title=None, sidebar=None, header=None, footer=None)` - -- [ui.navset_card_pill](https://shiny.posit.co/py/api/ui.navset_card_pill.html) - `ui.navset_card_pill(*args, id=None, selected=None, title=None, sidebar=None, header=None, footer=None, placement='above')` - -- [ui.navset_pill](https://shiny.posit.co/py/api/ui.navset_pill.html) - `ui.navset_pill(*args, id=None, selected=None, header=None, footer=None)` - -- [ui.navset_pill_list](https://shiny.posit.co/py/api/ui.navset_pill_list.html) - `ui.navset_pill_list(*args, id=None, selected=None, header=None, footer=None, well=True, widths=(4, 8))` - -- [ui.navset_tab](https://shiny.posit.co/py/api/ui.navset_tab.html) - `ui.navset_tab(*args, id=None, selected=None, header=None, footer=None)` - -:::{.border-bottom .blue .mt-6 .mb-5} -::: - -## Tabset with pill navigation - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - -```{.python filename="app.py" } -from shiny import App, ui - -app_ui = ui.page_fluid( - ui.navset_pill( #<< - ui.nav_panel("A", "Page A content"), - ui.nav_panel("B", "Page B content"), - ui.nav_panel("C", "Page C content"), - ui.nav_menu( - "Other links", - ui.nav_panel("D", "Page D content"), - "----", #<< - "Description:", - ui.nav_control( - ui.a("Weblink", href="URL", target="_blank") #<< - ), - ), - id = "tab" #<< - ) #<< -) - -def server(input, output, session): - pass - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - -Follow these steps to create an app with a tabset with pill navigation layout: - - 1. Add `ui.navset_pill()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_pill()` creates a pillset. - - 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_pill()` to set the items displayed in the navset. - - 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active. - - 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_pill()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`. - -:::{.border-bottom .blue .my-5} -::: - -## Tabset with pill list navigation - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - -```{.python filename="app.py" } -from shiny import App, ui - -app_ui = ui.page_fluid( - ui.navset_pill_list( #<< - ui.nav_panel("A", "Page A content"), - ui.nav_panel("B", "Page B content"), - ui.nav_panel("C", "Page C content"), - ui.nav_menu( - "Other links", - ui.nav_panel("D", "Page D content"), - "----", - "Description:", - ui.nav_control( - ui.a("Weblink", href="URL", target="_blank") - ), - ), - id = "tab" #<< - ) #<< -) - -def server(input, output, session): - pass - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - -Follow these steps to create an app with a pill list navigation layout. A pill list is a vertical pillset navigation. - - 1. Add `ui.navset_pill_list()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_pill()` creates a pill list. - - 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_pill_list()` to set the items displayed in the pillset. - - 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active. - - 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_pill_list()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`. - -:::{.border-bottom .blue .my-5} -::: - -## Tabset with tab navigation - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - -```{.python filename="app.py" } - -from shiny import App, ui - -app_ui = ui.page_fluid( - ui.navset_tab( #<< - ui.nav_panel("A", "Page A content"), - ui.nav_panel("B", "Page B content"), - ui.nav_panel("C", "Page C content"), - ui.nav_menu( - "Other links", - ui.nav_panel("D", "Page D content"), - "----", - "Description:", - ui.nav_control( - ui.a("Weblink", href="URL", target="_blank") - ), - ), - id = "tab" #<< - ) #<< -) - -def server(input, output, session): - pass - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - -Follow these steps to create an app with a tab navigation layout: - - 1. Add `ui.navset_tab()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_tab()` creates a tabset. - - 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_tab()` to set the items displayed in the tabset. - - 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active. - - 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_tab()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`. - -:::{.border-bottom .blue .my-5} -::: - -## Card with a tabbed tabset - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - -```{.python filename="app.py" } -from shiny import App, ui - -app_ui = ui.page_fluid( - ui.navset_card_tab( #<< - ui.nav_panel("A", "Page A content"), - ui.nav_panel("B", "Page B content"), - ui.nav_panel("C", "Page C content"), - ui.nav_menu( - "Other links", - ui.nav_panel("D", "Page D content"), - "----", - "Description:", - ui.nav_control( - ui.a("Weblink", href="URL", target="_blank") - ), - ), - id = "tab" #<< - ) #<< -) - -def server(input, output, session): - pass - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - -Follow these steps to add a card with a tabbed tabset to your app: - - 1. Add `ui.navset_card_tab()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_card_tab()` creates a tabset inside a card. - - 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_card_tab()` to set the items displayed in the tabset inside the card. - - 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active. - - 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_card_tab()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`. - -:::{.border-bottom .blue .my-5} -::: - -## Card with a pill tabset - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - -```{.python filename="app.py" } -from shiny import App, ui - -app_ui = ui.page_fluid( - ui.navset_card_pill( #<< - ui.nav_panel("A", "Page A content"), - ui.nav_panel("B", "Page B content"), - ui.nav_panel("C", "Page C content"), - ui.nav_menu( - "Other links", - ui.nav_panel("D", "Page D content"), - "----", - "Description:", - ui.nav_control( - ui.a("Weblink", href="URL", target="_blank") - ), - ), - id = "tab" #<< - ) #<< -) - -def server(input, output, session): - pass - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - -Follow these steps to add a card with a pill tabset to your app: - - 1. Add `ui.navset_card_pill()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_card_pill()` creates a pillset inside a card. - - 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_card_pill()` to set the items displayed in the pillset inside the card. - - 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active. - - 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_card_pill()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`. - -:::{.border-bottom .blue .my-5} -::: - -## Vertically collapsing accordion panels - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - -```{.python filename="app.py" } -from shiny import App, ui - -app_ui = ui.page_fluid( - ui.accordion( #<< - ui.accordion_panel("Section A", "Section A content"), #<< - ui.accordion_panel("Section B", "Section B content"), #<< - ui.accordion_panel("Section C", "Section C content"), #<< - ui.accordion_panel("Section D", "Section D content"), #<< - ui.accordion_panel("Section E", "Section E content"), #<< - id="acc", #<< - open = "Section A" #<< - ), #<< -) - -def server(input, output, session): - pass - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - -Follow these steps to add vertically collapsing accordion panels to your app: - - 1. Add `ui.accordion()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.accordion()` creates a vertically collapsing accordion. - - 2. Pass accordion panel items to `ui.accordion()` using calls to `ui.accordion_panel()`. Each call to `ui.accordion_panel()` creates one accordion panel. - - 3. Pass arguments to the accordion panels to control each panel's title, appearance, and associated content. The `*args` argument of `ui.accordion_panel()` controls the contents of the accordion panel. - - 4. _Optional:_ Pass a string to the `id` argument of `ui.accordian()`. This will create an input value that represents the currently active accordion panels. For example, `id = "panel"` would create a reactive value accessible as `input.panel()`. diff --git a/layouts/tabs/app-accordion-core.py b/layouts/tabs/app-accordion-core.py new file mode 100644 index 00000000..db0985ef --- /dev/null +++ b/layouts/tabs/app-accordion-core.py @@ -0,0 +1,18 @@ +from shiny import App, ui + +app_ui = ui.page_fluid( + ui.accordion( + ui.accordion_panel("Section A", "Section A content"), + ui.accordion_panel("Section B", "Section B content"), + ui.accordion_panel("Section C", "Section C content"), + ui.accordion_panel("Section D", "Section D content"), + ui.accordion_panel("Section E", "Section E content"), + id="acc", + open = "Section A" + ), +) + +def server(input, output, session): + pass + +app = App(app_ui, server) \ No newline at end of file diff --git a/layouts/tabs/app-accordion-express.py b/layouts/tabs/app-accordion-express.py new file mode 100644 index 00000000..e50b8819 --- /dev/null +++ b/layouts/tabs/app-accordion-express.py @@ -0,0 +1,17 @@ +from shiny.express import ui + +with ui.accordion(id="acc", open="Section A"): + with ui.accordion_panel("Section A"): + "Section A content" + + with ui.accordion_panel("Section B"): + "Section B content" + + with ui.accordion_panel("Section C"): + "Section C content" + + with ui.accordion_panel("Section D"): + "Section D content" + + with ui.accordion_panel("Section E"): + "Section E content" diff --git a/layouts/tabs/app-tabset-pill-card-core.py b/layouts/tabs/app-tabset-pill-card-core.py new file mode 100644 index 00000000..f7b647bb --- /dev/null +++ b/layouts/tabs/app-tabset-pill-card-core.py @@ -0,0 +1,26 @@ +from shiny import App, ui + +app_ui = ui.page_fillable( + ui.navset_card_pill( # << + 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="tab", + ) +) + + +def server(input, output, session): + pass + + +app = App(app_ui, server) diff --git a/layouts/tabs/app-tabset-pill-card-express.py b/layouts/tabs/app-tabset-pill-card-express.py new file mode 100644 index 00000000..f1ac6e32 --- /dev/null +++ b/layouts/tabs/app-tabset-pill-card-express.py @@ -0,0 +1,22 @@ +from shiny.express import ui + +ui.page_opts(fillable=True) + +with ui.navset_card_pill(id="tab"): # << + 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") diff --git a/layouts/tabs/app-tabset-pill-list-core.py b/layouts/tabs/app-tabset-pill-list-core.py new file mode 100644 index 00000000..f6734988 --- /dev/null +++ b/layouts/tabs/app-tabset-pill-list-core.py @@ -0,0 +1,26 @@ +from shiny import App, ui + +app_ui = ui.page_fluid( + ui.navset_pill_list( + 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="tab", + ) +) + + +def server(input, output, session): + pass + + +app = App(app_ui, server) diff --git a/layouts/tabs/app-tabset-pill-list-express.py b/layouts/tabs/app-tabset-pill-list-express.py new file mode 100644 index 00000000..38b96159 --- /dev/null +++ b/layouts/tabs/app-tabset-pill-list-express.py @@ -0,0 +1,20 @@ +from shiny.express import ui + +with ui.navset_pill_list(id="tab"): + 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") diff --git a/layouts/tabs/app-tabset-pills-core.py b/layouts/tabs/app-tabset-pills-core.py new file mode 100644 index 00000000..4be31b11 --- /dev/null +++ b/layouts/tabs/app-tabset-pills-core.py @@ -0,0 +1,26 @@ +from shiny import App, ui + +app_ui = ui.page_fluid( + ui.navset_pill( + 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="tab", + ) +) + + +def server(input, output, session): + pass + + +app = App(app_ui, server) diff --git a/layouts/tabs/app-tabset-pills-express.py b/layouts/tabs/app-tabset-pills-express.py new file mode 100644 index 00000000..b367d507 --- /dev/null +++ b/layouts/tabs/app-tabset-pills-express.py @@ -0,0 +1,20 @@ +from shiny.express import ui + +with ui.navset_pill(id="tab"): + 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") diff --git a/layouts/tabs/app-tabset-tab-card-core.py b/layouts/tabs/app-tabset-tab-card-core.py new file mode 100644 index 00000000..09ecfedc --- /dev/null +++ b/layouts/tabs/app-tabset-tab-card-core.py @@ -0,0 +1,26 @@ +from shiny import App, ui + +app_ui = ui.page_fillable( + ui.navset_card_tab( # << + 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="tab", + ) +) + + +def server(input, output, session): + pass + + +app = App(app_ui, server) diff --git a/layouts/tabs/app-tabset-tab-card-express.py b/layouts/tabs/app-tabset-tab-card-express.py new file mode 100644 index 00000000..6df58f28 --- /dev/null +++ b/layouts/tabs/app-tabset-tab-card-express.py @@ -0,0 +1,22 @@ +from shiny.express import ui + +ui.page_opts(fillable=True) + +with ui.navset_card_tab(id="tab"): # << + 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") diff --git a/layouts/tabs/app-tabset-tab-core.py b/layouts/tabs/app-tabset-tab-core.py new file mode 100644 index 00000000..bbabfb32 --- /dev/null +++ b/layouts/tabs/app-tabset-tab-core.py @@ -0,0 +1,26 @@ +from shiny import App, ui + +app_ui = ui.page_fluid( + ui.navset_tab( + 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="tab", + ) +) + + +def server(input, output, session): + pass + + +app = App(app_ui, server) diff --git a/layouts/tabs/app-tabset-tab-express.py b/layouts/tabs/app-tabset-tab-express.py new file mode 100644 index 00000000..b367d507 --- /dev/null +++ b/layouts/tabs/app-tabset-tab-express.py @@ -0,0 +1,20 @@ +from shiny.express import ui + +with ui.navset_pill(id="tab"): + 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") diff --git a/layouts/tabs/index.qmd b/layouts/tabs/index.qmd new file mode 100644 index 00000000..e6cdcff9 --- /dev/null +++ b/layouts/tabs/index.qmd @@ -0,0 +1,190 @@ +--- +title: "Tabs" +description: > + Tabs and navigation allow you to create Shiny apps with multiple pages. +--- + +```{python} +#| include: false +import sys +sys.path.append("../..") + +from docs.helpers import express_core_preview, shinylive_app_preview +``` + +Tabs and navigation allow you to create apps that have multiple pages. + +## Relevant functions + +- [ui.accordion](https://shiny.posit.co/py/api/ui.accordion.html#shiny.ui.accordion) + `ui.accordion(*args, id=None, open=None, multiple=True, class_=None, width=None, height=None, **kwargs)` + +- [ui.accordion_panel](https://shiny.posit.co/py/api/ui.accordion_panel.html) + `ui.accordion_panel(title, *args, value=MISSING, icon=None, **kwargs)` + +- [ui.navset_card_tab](https://shiny.posit.co/py/api/ui.navset_card_tab.html) + `ui.navset_card_tab(*args, id=None, selected=None, title=None, sidebar=None, header=None, footer=None)` + +- [ui.navset_card_pill](https://shiny.posit.co/py/api/ui.navset_card_pill.html) + `ui.navset_card_pill(*args, id=None, selected=None, title=None, sidebar=None, header=None, footer=None, placement='above')` + +- [ui.navset_pill](https://shiny.posit.co/py/api/ui.navset_pill.html) + `ui.navset_pill(*args, id=None, selected=None, header=None, footer=None)` + +- [ui.navset_pill_list](https://shiny.posit.co/py/api/ui.navset_pill_list.html) + `ui.navset_pill_list(*args, id=None, selected=None, header=None, footer=None, well=True, widths=(4, 8))` + +- [ui.navset_tab](https://shiny.posit.co/py/api/ui.navset_tab.html) + `ui.navset_tab(*args, id=None, selected=None, header=None, footer=None)` + +:::{.border-bottom .blue .mt-6 .mb-5} +::: + +## Tabset with pill navigation + +:::{.column-screen-inset-right style="max-width:800px;"} + +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-tabset-pills-core.py", viewer_height=190, div_attrs=".p-0") +express_core_preview("app-tabset-pills-express.py", "app-tabset-pills-core.py") +``` + +::: + +Follow these steps to create an app with a tabset with pill navigation layout: + + 1. Add `ui.navset_pill()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_pill()` creates a pillset. + + 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_pill()` to set the items displayed in the navset. + + 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active. + + 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_pill()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`. + +:::{.border-bottom .blue .my-5} +::: + +## Tabset with pill list navigation + +:::{.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-tabset-pill-list-core.py", viewer_height=320, div_attrs=".p-0") +express_core_preview("app-tabset-pill-list-express.py", "app-tabset-pill-list-core.py") +``` +::: + +Follow these steps to create an app with a pill list navigation layout. A pill list is a vertical pillset navigation. + + 1. Add `ui.navset_pill_list()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_pill()` creates a pill list. + + 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_pill_list()` to set the items displayed in the pillset. + + 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active. + + 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_pill_list()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`. + +:::{.border-bottom .blue .my-5} +::: + +## Tabset with tab navigation + +:::{.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-tabset-tab-core.py", viewer_height=200, div_attrs=".p-0") +express_core_preview("app-tabset-tab-express.py", "app-tabset-tab-core.py") +``` +::: + +Follow these steps to create an app with a tab navigation layout: + + 1. Add `ui.navset_tab()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_tab()` creates a tabset. + + 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_tab()` to set the items displayed in the tabset. + + 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active. + + 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_tab()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`. + +:::{.border-bottom .blue .my-5} +::: + +## Card with a tabbed tabset + +:::{.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-tabset-tab-card-core.py", viewer_height=250, div_attrs=".p-0") +express_core_preview("app-tabset-tab-card-express.py", "app-tabset-tab-card-core.py") +``` +::: + +Follow these steps to add a card with a tabbed tabset to your app: + + 1. Add `ui.navset_card_tab()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_card_tab()` creates a tabset inside a card. + + 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_card_tab()` to set the items displayed in the tabset inside the card. + + 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active. + + 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_card_tab()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`. + +:::{.border-bottom .blue .my-5} +::: + +## Card with a pill tabset + +:::{.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-tabset-pill-card-core.py", viewer_height=250, div_attrs=".p-0") +express_core_preview("app-tabset-pill-card-express.py", "app-tabset-pill-card-core.py") +``` +::: + +Follow these steps to add a card with a pill tabset to your app: + + 1. Add `ui.navset_card_pill()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_card_pill()` creates a pillset inside a card. + + 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_card_pill()` to set the items displayed in the pillset inside the card. + + 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active. + + 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_card_pill()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`. + +:::{.border-bottom .blue .my-5} +::: + +## Vertically collapsing accordion panels + +:::{.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-accordion-core.py", viewer_height=350, div_attrs=".p-0") +express_core_preview("app-accordion-express.py", "app-accordion-core.py") +``` +::: + +Follow these steps to add vertically collapsing accordion panels to your app: + + 1. Add `ui.accordion()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.accordion()` creates a vertically collapsing accordion. + + 2. Pass accordion panel items to `ui.accordion()` using calls to `ui.accordion_panel()`. Each call to `ui.accordion_panel()` creates one accordion panel. + + 3. Pass arguments to the accordion panels to control each panel's title, appearance, and associated content. The `*args` argument of `ui.accordion_panel()` controls the contents of the accordion panel. + + 4. _Optional:_ Pass a string to the `id` argument of `ui.accordian()`. This will create an input value that represents the currently active accordion panels. For example, `id = "panel"` would create a reactive value accessible as `input.panel()`. From 76572e9ea8f633af82e45d5e5e2ea1b0e79cf2f4 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 22 Jan 2024 17:05:19 -0500 Subject: [PATCH 11/19] expressify layouts/panels-cards --- _quarto.yml | 4 +- layouts/index.qmd | 16 +-- layouts/panels-cards.qmd | 131 ------------------ .../panels-cards/app-panel-absolute-core.py | 21 +++ .../app-panel-absolute-express.py | 11 ++ layouts/panels-cards/app-two-cards-core.py | 23 +++ layouts/panels-cards/app-two-cards-express.py | 14 ++ layouts/panels-cards/index.qmd | 93 +++++++++++++ 8 files changed, 172 insertions(+), 141 deletions(-) delete mode 100644 layouts/panels-cards.qmd create mode 100644 layouts/panels-cards/app-panel-absolute-core.py create mode 100644 layouts/panels-cards/app-panel-absolute-express.py create mode 100644 layouts/panels-cards/app-two-cards-core.py create mode 100644 layouts/panels-cards/app-two-cards-express.py create mode 100644 layouts/panels-cards/index.qmd diff --git a/_quarto.yml b/_quarto.yml index 686ac95d..3980dbe1 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -194,9 +194,9 @@ website: - section: "![](/images/cards-blue.svg){.sidebar-icon .sidebar-subtitle}__Panels & Cards__" contents: - text: "Main Image w/ Floating Panel" - href: "/layouts/panels-cards.html#main-image-with-panel-floating-above" + href: "/layouts/panels-cards/index.html#main-image-with-panel-floating-above" - text: "Content Divided by Cards" - href: "/layouts/panels-cards.html#content-divided-by-cards" + href: "/layouts/panels-cards/index.html#content-divided-by-cards" - section: "![](/images/arrange-blue.svg){.sidebar-icon .sidebar-subtitle}__Arrange Elements__" contents: - text: "Grid Layouts" diff --git a/layouts/index.qmd b/layouts/index.qmd index d6080e3d..4cacf5db 100644 --- a/layouts/index.qmd +++ b/layouts/index.qmd @@ -339,14 +339,14 @@ Tabs and navigation allow you to create apps that have multiple pages. ::: {.sticky-xl-top .pt-4}

- + Panels & Cards

Use panels and cards to define areas of related content. -Learn Panels & Cards +Learn Panels & Cards ::: :::: :::: {.g-col-xl-9 .g-col-12 .pt-3 .pt-xl-5 .ps-0 .ps-xl-5 .ms-0 .ms-xl-4} @@ -356,17 +356,17 @@ Use panels and cards to define areas of related content. @@ -374,17 +374,17 @@ Use panels and cards to define areas of related content. diff --git a/layouts/panels-cards.qmd b/layouts/panels-cards.qmd deleted file mode 100644 index 54857f55..00000000 --- a/layouts/panels-cards.qmd +++ /dev/null @@ -1,131 +0,0 @@ ---- -title: "Panels and cards" -description: "Use panels and cards to define areas of related content in your Shiny app." ---- -Use panels and cards to define areas of related content. - -## Relevant functions - -- [ui.card](https://shiny.posit.co/py/api/ui.card.html) - `ui.card(*args, full_screen=False, height=None, max_height=None, min_height=None, fill=True, class_=None, **kwargs)` - -- [ui.card_footer](https://shiny.posit.co/py/api/ui.card_footer.html) - `ui.card_footer(*args, **kwargs)` - -- [ui.card_header](https://shiny.posit.co/py/api/ui.card_header.html) - `ui.card_header(*args, container=tags.div, **kwargs)` - -- [ui.panel_absolute](https://shiny.posit.co/py/api/ui.panel_absolute.html) - `ui.panel_absolute(*args, top=None, left=None, right=None, bottom=None, width=None, height=None, draggable=False, fixed=False, cursor='auto', **kwargs)` - -- [ui.panel_fixed](https://shiny.posit.co/py/api/ui.panel_fixed.html) - `ui.panel_fixed(*args, **kwargs)` - -- [ui.panel_well](https://shiny.posit.co/py/api/ui.panel_well.html) - `ui.panel_well(*args, **kwargs)` - -:::{.border-bottom .blue .mt-6 .mb-5} -::: - -## Main image with panel floating above - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - -```{.python filename="app.py" } -from shiny import App, ui - -app_ui = ui.page_fluid( - ui.img( #<< - src="https://source.unsplash.com/a-view-of-a-mountain-range-with-stars-in-the-sky-8QiI0ueYm3Q", #<< - width="100%",#<< - heigth="100%"#<< - ),#<< - ui.panel_absolute(#<< - ui.panel_well( - ui.panel_title("Draggable panel"), - "Add elements here" - ), #<< - width="300px", #<< - right="50px", #<< - top="50px", #<< - draggable=True #<< - ), #<< -) - -def server(input, output, session): - pass - - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - -Follow these steps to create an app that has a panel floating over a main image. - -First, to create the image: - - 1. Pass `ui.img()` to any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.img()` creates an image. - - 2. Pass the path or URL of your desired image to `ui.img()`'s `src` parameter. Set additional parameters to control the appearance of the image (e.g., `width` and `height`). - -Then, to create the floating panel: - - 1. Pass `ui.panel_absolute()` as the second argument of your Shiny UI page method, after `ui.img()`. Pass elements that you want to appear inside the panel to `ui.panel_absolute()`. - - 2. Position the panel using the `top`, `bottom`, `left`, and/or `right` parameters. Set the size of the panel using the `height` and/or `width` parameters. - - 3. If you want the panel to be draggable, set the `draggable` parameter to `True`. - -See also: [`ui.panel_fixed()`](https://shiny.posit.co/py/api/ui.panel_fixed.html). `ui.panel_fixed()` is equivalent to calling `ui.panel_absolute()` with `fixed=True` (i.e., the panel does not scroll with the rest of the page). - -:::{.border-bottom .blue .my-5} -::: - -## Content divided by cards - -:::{.column-screen-inset-right style="max-width:800px;"} - - - -```{.python filename="app.py" } -from shiny import App, ui, Inputs - -app_ui = ui.page_fluid( - ui.card( #<< - ui.card_header("Card 1 header"), #<< - ui.p("Card 1 body"), #<< - ui.input_slider("slider", "Slider", 0, 10, 5), #<< - ), #<< - ui.card( #<< - ui.card_header("Card 2 header"), #<< - ui.p("Card 2 body"), #<< - ui.input_text("text", "Add text", "") #<< - ), #<< -) - -def server(input, output, session): - pass - -app = App(app_ui, server) -``` - -

Edit in Shinylive

- -::: - -Cards are general purpose containers used to group related UI elements together with a border and optional padding. - -Follow these steps to create an app with content separated into cards: - - 1. Add `ui.card()` inside any Shiny UI page method (e.g., `ui.page_fluid()`) to create a card. Add additional cards by adding additional calls to `ui.card()`. - - 2. Define the contents of each card. Inside `ui.card()`, pass elements that you would like to appear inside the card (e.g., inputs, text). You can also use `ui.card_header()` and `ui.card_footer()` to create a card header and footer. - - 3. Control the appearance and functionality of each card by passing additional arguments to `ui.card()`. For example, you can set `full_screen` to `True` to allow the user to expand the card to fullscreen, or control the height of the card with the `height` parameter. diff --git a/layouts/panels-cards/app-panel-absolute-core.py b/layouts/panels-cards/app-panel-absolute-core.py new file mode 100644 index 00000000..cef090a5 --- /dev/null +++ b/layouts/panels-cards/app-panel-absolute-core.py @@ -0,0 +1,21 @@ +from shiny import App, ui + +app_ui = ui.page_fluid( + ui.panel_absolute( # << + ui.panel_well( + ui.panel_title("Draggable panel"), + "Move this panel anywhere you want.", + ), + width="300px", # << + right="50px", # << + top="50px", # << + draggable=True, # << + ), # << +) + + +def server(input, output, session): + pass + + +app = App(app_ui, server) diff --git a/layouts/panels-cards/app-panel-absolute-express.py b/layouts/panels-cards/app-panel-absolute-express.py new file mode 100644 index 00000000..1a6dd230 --- /dev/null +++ b/layouts/panels-cards/app-panel-absolute-express.py @@ -0,0 +1,11 @@ +from shiny.express import ui + +with ui.panel_absolute( + width="300px", # << + right="50px", # << + top="50px", # << + draggable=True, # << +): # << + with ui.panel_well(): # << + ui.h2("Draggable panel") # << + "Move this panel anywhere you want." # << diff --git a/layouts/panels-cards/app-two-cards-core.py b/layouts/panels-cards/app-two-cards-core.py new file mode 100644 index 00000000..208ae601 --- /dev/null +++ b/layouts/panels-cards/app-two-cards-core.py @@ -0,0 +1,23 @@ +from shiny import App, ui + +app_ui = ui.page_fillable( + ui.layout_columns( # << + ui.card( # << + ui.card_header("Card 1 header"), + ui.p("Card 1 body"), + ui.input_slider("slider", "Slider", 0, 10, 5), + ), # << + ui.card( # << + ui.card_header("Card 2 header"), + ui.p("Card 2 body"), + ui.input_text("text", "Add text", ""), + ), # << + ) # << +) + + +def server(input, output, session): + pass + + +app = App(app_ui, server) diff --git a/layouts/panels-cards/app-two-cards-express.py b/layouts/panels-cards/app-two-cards-express.py new file mode 100644 index 00000000..2f832e92 --- /dev/null +++ b/layouts/panels-cards/app-two-cards-express.py @@ -0,0 +1,14 @@ +from shiny import ui + +ui.page_opts(fillable=True) + +with ui.layout_columns(): # << + with ui.card(): # << + ui.card_header("Card 1 header") + ui.p("Card 1 body") + ui.input_slider("slider", "Slider", 0, 10, 5) + + with ui.card(): # << + ui.card_header("Card 2 header") + ui.p("Card 2 body") + ui.input_text("text", "Add text", "") diff --git a/layouts/panels-cards/index.qmd b/layouts/panels-cards/index.qmd new file mode 100644 index 00000000..e22f23cb --- /dev/null +++ b/layouts/panels-cards/index.qmd @@ -0,0 +1,93 @@ +--- +title: "Panels and cards" +description: > + Use panels and cards to define areas of related content in your Shiny app. +--- + +```{python} +#| include: false +import sys +sys.path.append("../..") + +from docs.helpers import express_core_preview, shinylive_app_preview +``` + +Use panels and cards to define areas of related content. + +## Relevant functions + +- [ui.card](https://shiny.posit.co/py/api/ui.card.html) + `ui.card(*args, full_screen=False, height=None, max_height=None, min_height=None, fill=True, class_=None, **kwargs)` + +- [ui.card_footer](https://shiny.posit.co/py/api/ui.card_footer.html) + `ui.card_footer(*args, **kwargs)` + +- [ui.card_header](https://shiny.posit.co/py/api/ui.card_header.html) + `ui.card_header(*args, container=tags.div, **kwargs)` + +- [ui.panel_absolute](https://shiny.posit.co/py/api/ui.panel_absolute.html) + `ui.panel_absolute(*args, top=None, left=None, right=None, bottom=None, width=None, height=None, draggable=False, fixed=False, cursor='auto', **kwargs)` + +- [ui.panel_fixed](https://shiny.posit.co/py/api/ui.panel_fixed.html) + `ui.panel_fixed(*args, **kwargs)` + +- [ui.panel_well](https://shiny.posit.co/py/api/ui.panel_well.html) + `ui.panel_well(*args, **kwargs)` + +:::{.border-bottom .blue .mt-6 .mb-5} +::: + +## Main image with panel floating above + +:::{.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-panel-absolute-core.py", viewer_height=320, div_attrs=".p-0") +express_core_preview("app-panel-absolute-express.py", "app-panel-absolute-core.py") +``` +::: + +Follow these steps to create an app that has a panel floating over a main image. + +First, to create the image: + + 1. Pass `ui.img()` to any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.img()` creates an image. + + 2. Pass the path or URL of your desired image to `ui.img()`'s `src` parameter. Set additional parameters to control the appearance of the image (e.g., `width` and `height`). + +Then, to create the floating panel: + + 1. Pass `ui.panel_absolute()` as the second argument of your Shiny UI page method, after `ui.img()`. Pass elements that you want to appear inside the panel to `ui.panel_absolute()`. + + 2. Position the panel using the `top`, `bottom`, `left`, and/or `right` parameters. Set the size of the panel using the `height` and/or `width` parameters. + + 3. If you want the panel to be draggable, set the `draggable` parameter to `True`. + +See also: [`ui.panel_fixed()`](https://shiny.posit.co/py/api/ui.panel_fixed.html). `ui.panel_fixed()` is equivalent to calling `ui.panel_absolute()` with `fixed=True` (i.e., the panel does not scroll with the rest of the page). + +:::{.border-bottom .blue .my-5} +::: + +## Content divided by cards + +:::{.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-two-cards-core.py", viewer_height=320, div_attrs=".p-0") +express_core_preview("app-two-cards-express.py", "app-two-cards-core.py") +``` +::: + +Cards are general purpose containers used to group related UI elements together with a border and optional padding. + +Follow these steps to create an app with content separated into cards: + + 1. Add `ui.card()` inside any Shiny UI page method (e.g., `ui.page_fluid()`) to create a card. Add additional cards by adding additional calls to `ui.card()`. + + 2. Define the contents of each card. Inside `ui.card()`, pass elements that you would like to appear inside the card (e.g., inputs, text). You can also use `ui.card_header()` and `ui.card_footer()` to create a card header and footer. + + 3. Control the appearance and functionality of each card by passing additional arguments to `ui.card()`. For example, you can set `full_screen` to `True` to allow the user to expand the card to fullscreen, or control the height of the card with the `height` parameter. From 89775c508d3a2da563b8610ba599eee8b0752eeb Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 23 Jan 2024 13:55:22 -0500 Subject: [PATCH 12/19] standardize comments --- docs/helpers.py | 14 +++++++----- layouts/sidebars/app-sidebar-card-core.py | 5 +++-- layouts/sidebars/app-sidebar-card-express.py | 14 +++++++----- .../sidebars/app-sidebar-collapsed-express.py | 2 +- layouts/sidebars/app-sidebar-left-core.py | 2 +- layouts/sidebars/app-sidebar-left-express.py | 2 +- layouts/sidebars/index.qmd | 2 +- layouts/tabs/app-accordion-core.py | 22 ++++++++++--------- layouts/tabs/app-accordion-express.py | 12 +++++----- layouts/tabs/app-tabset-pill-card-core.py | 2 +- layouts/tabs/app-tabset-pill-list-core.py | 4 ++-- layouts/tabs/app-tabset-pill-list-express.py | 2 +- layouts/tabs/app-tabset-pills-core.py | 6 ++--- layouts/tabs/app-tabset-pills-express.py | 2 +- layouts/tabs/app-tabset-tab-card-core.py | 4 ++-- layouts/tabs/app-tabset-tab-core.py | 6 ++--- layouts/tabs/app-tabset-tab-express.py | 2 +- 17 files changed, 57 insertions(+), 46 deletions(-) diff --git a/docs/helpers.py b/docs/helpers.py index ebd413c4..1f4dc40e 100644 --- a/docs/helpers.py +++ b/docs/helpers.py @@ -2,7 +2,7 @@ from pathlib import Path from typing import Any, Iterable, List, Literal, Optional, Sequence -import shinylive +from shinylive import ShinyliveApp class QuartoPrint(List[str]): @@ -21,16 +21,20 @@ def append_file(self, file_path: str, file_name: Optional[str] = None): self.append(app_contents) def append_shinylive_chunk( - self, files: list[str] | str, language: str = "py", **kwargs + self, + files: list[str] | str, + language: str = "py", + **kwargs, ): if isinstance(files, str): app_file = files + files = [] else: app_file = files.pop(0) - bundle = shinylive.url_encode(app_file, files, language) + app = ShinyliveApp.from_local(app_file, files, language) - self.append(bundle.chunk(**kwargs)) + self.append(app.chunk(**kwargs)) def shinylive_chunk( @@ -225,7 +229,7 @@ def express_core_preview( if app_file is None: continue - sl_app = shinylive.url_encode(app_file, files, language) + sl_app = ShinyliveApp.from_local(app_file, files, language) block.append("### " + tab_name) block.append( diff --git a/layouts/sidebars/app-sidebar-card-core.py b/layouts/sidebars/app-sidebar-card-core.py index 7ccdeb43..3435fe84 100644 --- a/layouts/sidebars/app-sidebar-card-core.py +++ b/layouts/sidebars/app-sidebar-card-core.py @@ -1,10 +1,11 @@ from shiny import App, ui -app_ui = ui.page_fluid( +app_ui = ui.page_fillable( ui.card( # << + ui.card_header("Card with sidebar"), ui.layout_sidebar( # << ui.sidebar("Sidebar", bg="#f8f8f8"), # << - "Card content", + "Card content", # << ), # << ) # << ) diff --git a/layouts/sidebars/app-sidebar-card-express.py b/layouts/sidebars/app-sidebar-card-express.py index 7d60cc67..d0f6c327 100644 --- a/layouts/sidebars/app-sidebar-card-express.py +++ b/layouts/sidebars/app-sidebar-card-express.py @@ -1,8 +1,12 @@ from shiny.express import ui -with ui.card(): - with ui.layout_sidebar(): - with ui.sidebar(bg="#f8f8f8"): - "Sidebar" +ui.page_opts(fillable=True) - "Card content" +with ui.card(): # << + ui.card_header("Card with sidebar") + + with ui.layout_sidebar(): # << + with ui.sidebar(bg="#f8f8f8"): # << + "Sidebar" # << + + "Card content" # << diff --git a/layouts/sidebars/app-sidebar-collapsed-express.py b/layouts/sidebars/app-sidebar-collapsed-express.py index bf2d0b14..28f85802 100644 --- a/layouts/sidebars/app-sidebar-collapsed-express.py +++ b/layouts/sidebars/app-sidebar-collapsed-express.py @@ -1,6 +1,6 @@ from shiny.express import ui -with ui.sidebar(open="closed", bg="#f8f8f8"): +with ui.sidebar(open="closed", bg="#f8f8f8"): # << "Sidebar" "Main content" diff --git a/layouts/sidebars/app-sidebar-left-core.py b/layouts/sidebars/app-sidebar-left-core.py index aff93f81..22d04be6 100644 --- a/layouts/sidebars/app-sidebar-left-core.py +++ b/layouts/sidebars/app-sidebar-left-core.py @@ -2,7 +2,7 @@ app_ui = ui.page_sidebar( # << ui.sidebar("Sidebar", bg="#f8f8f8"), # << - "Main content", + "Main content", # << ) # << diff --git a/layouts/sidebars/app-sidebar-left-express.py b/layouts/sidebars/app-sidebar-left-express.py index e5d0e145..642bd670 100644 --- a/layouts/sidebars/app-sidebar-left-express.py +++ b/layouts/sidebars/app-sidebar-left-express.py @@ -3,4 +3,4 @@ with ui.sidebar(bg="#f8f8f8"): # << "Sidebar" # << -"Main content" +"Main content" # << diff --git a/layouts/sidebars/index.qmd b/layouts/sidebars/index.qmd index 137e6374..d6425e31 100644 --- a/layouts/sidebars/index.qmd +++ b/layouts/sidebars/index.qmd @@ -88,7 +88,7 @@ Follow these steps to add a right-side sidebar to your app: #| output: asis #| echo: false -shinylive_app_preview("app-sidebar-card-core.py", viewer_height=125, div_attrs=".p-0") +shinylive_app_preview("app-sidebar-card-core.py", viewer_height=175, div_attrs=".p-0") express_core_preview("app-sidebar-card-express.py", "app-sidebar-card-core.py") ``` diff --git a/layouts/tabs/app-accordion-core.py b/layouts/tabs/app-accordion-core.py index db0985ef..51658f92 100644 --- a/layouts/tabs/app-accordion-core.py +++ b/layouts/tabs/app-accordion-core.py @@ -1,18 +1,20 @@ from shiny import App, ui app_ui = ui.page_fluid( - ui.accordion( - ui.accordion_panel("Section A", "Section A content"), - ui.accordion_panel("Section B", "Section B content"), - ui.accordion_panel("Section C", "Section C content"), - ui.accordion_panel("Section D", "Section D content"), - ui.accordion_panel("Section E", "Section E content"), - id="acc", - open = "Section A" - ), + ui.accordion( # << + ui.accordion_panel("Section A", "Section A content"), # << + ui.accordion_panel("Section B", "Section B content"), # << + ui.accordion_panel("Section C", "Section C content"), # << + ui.accordion_panel("Section D", "Section D content"), # << + ui.accordion_panel("Section E", "Section E content"), # << + id="acc", # << + open="Section A", # << + ), # << ) + def server(input, output, session): pass -app = App(app_ui, server) \ No newline at end of file + +app = App(app_ui, server) diff --git a/layouts/tabs/app-accordion-express.py b/layouts/tabs/app-accordion-express.py index e50b8819..5beffb0a 100644 --- a/layouts/tabs/app-accordion-express.py +++ b/layouts/tabs/app-accordion-express.py @@ -1,17 +1,17 @@ from shiny.express import ui -with ui.accordion(id="acc", open="Section A"): - with ui.accordion_panel("Section A"): +with ui.accordion(id="acc", open="Section A"): # << + with ui.accordion_panel("Section A"): # << "Section A content" - with ui.accordion_panel("Section B"): + with ui.accordion_panel("Section B"): # << "Section B content" - with ui.accordion_panel("Section C"): + with ui.accordion_panel("Section C"): # << "Section C content" - with ui.accordion_panel("Section D"): + with ui.accordion_panel("Section D"): # << "Section D content" - with ui.accordion_panel("Section E"): + with ui.accordion_panel("Section E"): # << "Section E content" diff --git a/layouts/tabs/app-tabset-pill-card-core.py b/layouts/tabs/app-tabset-pill-card-core.py index f7b647bb..c96208d5 100644 --- a/layouts/tabs/app-tabset-pill-card-core.py +++ b/layouts/tabs/app-tabset-pill-card-core.py @@ -15,7 +15,7 @@ ), ), id="tab", - ) + ) # << ) diff --git a/layouts/tabs/app-tabset-pill-list-core.py b/layouts/tabs/app-tabset-pill-list-core.py index f6734988..ef67cc04 100644 --- a/layouts/tabs/app-tabset-pill-list-core.py +++ b/layouts/tabs/app-tabset-pill-list-core.py @@ -1,7 +1,7 @@ from shiny import App, ui app_ui = ui.page_fluid( - ui.navset_pill_list( + ui.navset_pill_list( # << ui.nav_panel("A", "Panel A content"), ui.nav_panel("B", "Panel B content"), ui.nav_panel("C", "Panel C content"), @@ -15,7 +15,7 @@ ), ), id="tab", - ) + ) # << ) diff --git a/layouts/tabs/app-tabset-pill-list-express.py b/layouts/tabs/app-tabset-pill-list-express.py index 38b96159..5747d8f8 100644 --- a/layouts/tabs/app-tabset-pill-list-express.py +++ b/layouts/tabs/app-tabset-pill-list-express.py @@ -1,6 +1,6 @@ from shiny.express import ui -with ui.navset_pill_list(id="tab"): +with ui.navset_pill_list(id="tab"): # << with ui.nav_panel("A"): "Panel A content" diff --git a/layouts/tabs/app-tabset-pills-core.py b/layouts/tabs/app-tabset-pills-core.py index 4be31b11..18b8374f 100644 --- a/layouts/tabs/app-tabset-pills-core.py +++ b/layouts/tabs/app-tabset-pills-core.py @@ -1,7 +1,7 @@ from shiny import App, ui app_ui = ui.page_fluid( - ui.navset_pill( + ui.navset_pill( # << ui.nav_panel("A", "Panel A content"), ui.nav_panel("B", "Panel B content"), ui.nav_panel("C", "Panel C content"), @@ -14,8 +14,8 @@ ui.a("Shiny", href="https://shiny.posit.co", target="_blank") ), ), - id="tab", - ) + id="tab", # << + ) # << ) diff --git a/layouts/tabs/app-tabset-pills-express.py b/layouts/tabs/app-tabset-pills-express.py index b367d507..6e96d099 100644 --- a/layouts/tabs/app-tabset-pills-express.py +++ b/layouts/tabs/app-tabset-pills-express.py @@ -1,6 +1,6 @@ from shiny.express import ui -with ui.navset_pill(id="tab"): +with ui.navset_pill(id="tab"): # << with ui.nav_panel("A"): "Panel A content" diff --git a/layouts/tabs/app-tabset-tab-card-core.py b/layouts/tabs/app-tabset-tab-card-core.py index 09ecfedc..61ba8c60 100644 --- a/layouts/tabs/app-tabset-tab-card-core.py +++ b/layouts/tabs/app-tabset-tab-card-core.py @@ -14,8 +14,8 @@ ui.a("Shiny", href="https://shiny.posit.co", target="_blank") ), ), - id="tab", - ) + id="tab", # << + ) # << ) diff --git a/layouts/tabs/app-tabset-tab-core.py b/layouts/tabs/app-tabset-tab-core.py index bbabfb32..4f393972 100644 --- a/layouts/tabs/app-tabset-tab-core.py +++ b/layouts/tabs/app-tabset-tab-core.py @@ -1,7 +1,7 @@ from shiny import App, ui app_ui = ui.page_fluid( - ui.navset_tab( + ui.navset_tab( # << ui.nav_panel("A", "Panel A content"), ui.nav_panel("B", "Panel B content"), ui.nav_panel("C", "Panel C content"), @@ -14,8 +14,8 @@ ui.a("Shiny", href="https://shiny.posit.co", target="_blank") ), ), - id="tab", - ) + id="tab", # << + ) # << ) diff --git a/layouts/tabs/app-tabset-tab-express.py b/layouts/tabs/app-tabset-tab-express.py index b367d507..6e96d099 100644 --- a/layouts/tabs/app-tabset-tab-express.py +++ b/layouts/tabs/app-tabset-tab-express.py @@ -1,6 +1,6 @@ from shiny.express import ui -with ui.navset_pill(id="tab"): +with ui.navset_pill(id="tab"): # << with ui.nav_panel("A"): "Panel A content" From 77056ac769c270eecd60745308f614ac899d9939 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 23 Jan 2024 17:12:37 -0500 Subject: [PATCH 13/19] expressify layouts/arrange --- _quarto.yml | 6 +- layouts/arrange.qmd | 339 --------------------- layouts/arrange/app-column-nest-core.py | 32 ++ layouts/arrange/app-column-nest-express.py | 15 + layouts/arrange/app-fillable-core.py | 26 ++ layouts/arrange/app-fillable-express.py | 15 + layouts/arrange/app-grid-core.py | 18 ++ layouts/arrange/app-grid-express.py | 12 + layouts/arrange/app-grid-offset-core.py | 22 ++ layouts/arrange/app-grid-offset-express.py | 19 ++ layouts/arrange/app-page-fixed-core.py | 26 ++ layouts/arrange/app-page-fixed-express.py | 13 + layouts/arrange/app-page-fluid-core.py | 26 ++ layouts/arrange/app-page-fluid-express.py | 13 + layouts/arrange/index.qmd | 196 ++++++++++++ layouts/index.qmd | 22 +- 16 files changed, 447 insertions(+), 353 deletions(-) delete mode 100644 layouts/arrange.qmd create mode 100644 layouts/arrange/app-column-nest-core.py create mode 100644 layouts/arrange/app-column-nest-express.py create mode 100644 layouts/arrange/app-fillable-core.py create mode 100644 layouts/arrange/app-fillable-express.py create mode 100644 layouts/arrange/app-grid-core.py create mode 100644 layouts/arrange/app-grid-express.py create mode 100644 layouts/arrange/app-grid-offset-core.py create mode 100644 layouts/arrange/app-grid-offset-express.py create mode 100644 layouts/arrange/app-page-fixed-core.py create mode 100644 layouts/arrange/app-page-fixed-express.py create mode 100644 layouts/arrange/app-page-fluid-core.py create mode 100644 layouts/arrange/app-page-fluid-express.py create mode 100644 layouts/arrange/index.qmd diff --git a/_quarto.yml b/_quarto.yml index 3980dbe1..8d4462dd 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -200,11 +200,11 @@ website: - section: "![](/images/arrange-blue.svg){.sidebar-icon .sidebar-subtitle}__Arrange Elements__" contents: - text: "Grid Layouts" - href: "/layouts/arrange.html#grid-layouts" + href: "/layouts/arrange/index.html#grid-layouts" - text: "Column Nesting" - href: "/layouts/arrange.html#column-nesting" + href: "/layouts/arrange/index.html#column-nesting" - text: "Controlling for Page Size" - href: "/layouts/arrange.html#controlling-for-page-width-and-height" + href: "/layouts/arrange/index.html#controlling-for-page-width-and-height" - id: docs style: "floating" diff --git a/layouts/arrange.qmd b/layouts/arrange.qmd deleted file mode 100644 index 942e7791..00000000 --- a/layouts/arrange.qmd +++ /dev/null @@ -1,339 +0,0 @@ ---- -title: "Arrange Elements" -description: > - Shiny's premade layouts, such as ui.page_sidebar() and ui.page_navbar() make use of - Shiny's lower-level grid layout functions. ---- - -Shiny's premade layouts, such as [ui.layout_sidebar](https://shiny.posit.co/py/api/ui.layout_sidebar.html) and [ui.page_navbar](https://shiny.posit.co/py/api/ui.page_navbar.html) make use of Shiny's lower-level grid layout functions: - -* Rows are created by the `ui.row()` function and include columns. -* Columns are defined by the `ui.column()` function. Column widths are based on the [Bootstrap](https://getbootstrap.com/) 12-wide grid system, so column widths should add up to 12 within a row. - -You can use these functions directly to create your own layout. Grid layouts can be used within a page, panel, or card and can even be nested within each other. - -## Relevant functions - -- [ui.row](https://shiny.posit.co/py/api/ui.row.html) - `ui.row(*args, **kwargs)` - -- [ui.column](https://shiny.posit.co/py/api/ui.column.html) - `ui.column(width, *args, offset=0, **kwargs)` - -- [ui.page_fluid](https://shiny.posit.co/py/api/ui.page_fluid.html) - `ui.page_fluid(*args, title=None, lang=None, **kwargs)` - -- [ui.page_fixed](https://shiny.posit.co/py/api/ui.page_fixed.html) - `ui.page_fixed(*args, title=None, lang=None, **kwargs)` - -- [ui.page_fillable](https://shiny.posit.co/py/api/ui.page_fillable.html) - `ui.page_fillable(*args, padding=None, gap=None, fillable_mobile=False, title=None, lang=None, **kwargs)` - -:::{.border-bottom .blue .mt-6 .mb-5} -::: - -## Grid Layouts - -To create a layout using the grid system, first build a grid by interlacing calls to [ui.row](https://shiny.posit.co/py/api/ui.row.html) and [ui.column](https://shiny.posit.co/py/api/ui.column.html). Then place UI elements within the grid by passing them to a `ui.row()` or `ui.column()` function. For example, the app below arranges three cards into a simple pattern. - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - - -```{.python filename="app.py" } -from shiny import App, render, ui - -app_ui = ui.page_fluid( - ui.row( #<< - ui.column(12, ui.card("Card 1")) #<< - ), #<< - ui.row( #<< - ui.column(6, ui.card("Card 2")), #<< - ui.column(6, ui.card("Card 3")) #<< - ) #<< -) - -def server(input, output, session): - return None - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - -Notice three things about the app: - -1. You always create rows before columns, and then embed columns within the rows. - -2. Rows are always as wide as the container they appear in, but columns take a `width` parameter. `width` should be an integer from 1 to 12. Bootstrap uses `width` to determine the width of the column relative to the row that contains it: - - - A column of `width=12` will span the entire row. - - A column of `width=6` will span half the row. - - A column of `width=3` will span a quarter of the row. - - And so on. - -3. You pass UI elements to `ui.row()` or `ui.column()` to place them in the grid. Here we pass `ui.card()`s to various parts of the grid. Each element is fitted to the column or row that it appears in. - -In addition to setting a column's width, you can also set its offset with the `offset` parameter. `offset` controls how many units of padding appear to the left of the column. Once again, `offset` should be an integer from 1 to 12. The app below uses offsets to make a new arrangement: - - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - - - - -```{.python filename="app.py" } -from shiny import App, render, ui - -app_ui = ui.page_fluid( - ui.row( - ui.column(4, ui.card("Chart 1")), - ui.column(4, ui.card("Chart 2"), offset=4) #<< - ), - ui.row( - ui.column(4, ui.card("Chart 3"), offset=4) #<< - ), - ui.row( - ui.column(4, ui.card("Chart 4")), - ui.column(4, ui.card("Chart 5"), offset=4) #<< - ) -) - -def server(input, output, session): - return None - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - -:::{.border-bottom .blue .mt-6 .mb-5} -::: - -## Column nesting - -When you nest rows and columns within a column, each nested level of column widths should add up to 12. This is because Bootstrap divides every row into 12 units, no matter the actual width of the row. - -Consider this page layout: - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - - - - -Each width is relative to the row that immediately contains it. To create this layout in a Shiny application you'd use the following code: - -```{.python filename="app.py" } -from shiny import App, render, ui - -app_ui = ui.page_fluid( - ui.row( - ui.column(12, - ui.card("Width 12", - ui.row( - ui.column(6, - ui.card("Width 6", - ui.row( - ui.column(6, ui.card("Width 6")), - ui.column(6, ui.card("Width 6")) - ) - ) - ), - ui.column(6, ui.card("Width 6")) - ) - ) - ) - ) -) - -def server(input, output, session): - return None - -app = App(app_ui, server) -``` - -

Edit in Shinylive

-::: - -:::{.border-bottom .blue .mt-6 .mb-5} -::: - -## Controlling for page width and height - -There are multiple types of Shiny grids: fluid, fixed, and fillable. The fluid grid system is -recommended for most applications and is the default for Shiny functions like `ui.page_navbar()` and `ui.page_sidebar()`. So far, all the examples on this page have used the fluid grid system. - -Each grid system uses a flexibly sub-dividable 12-column grid for layout. They differ in how they interact with the viewer's browser window: - -* The fluid system always occupies the full width of the web page and re-sizes its components dynamically as the size of the page changes. - -* The fixed system occupies a fixed width of 940 pixels by default and may assume other widths when Bootstrap's responsive layout kicks in (e.g., when on a tablet). - -* The fillable system always occupies the full width _and height_ of the web page and re-sizes its components dynamically as the size of the page changes. - -!["Differences between the three systems"](/images/page-control.png){.pt-4} - -### Fluid Grid System - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - - - - -```{.python filename="app.py" } -import matplotlib.pyplot as plt -import numpy as np -from shiny import App, render, ui - -app_ui = ui.page_fluid( #<< - ui.layout_sidebar( - ui.panel_sidebar( - ui.input_slider("n", "N", 0, 100, 20), - ), - ui.panel_main( - ui.output_plot("histogram"), - ), - ), -) #<< - - -def server(input, output, session): - @output - @render.plot(alt="A histogram") - def histogram(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) - plt.hist(x, input.n(), density=True) - - -app = App(app_ui, server, debug=True) -``` - -

Edit in Shinylive

-::: - -To make a fluid grid that adapts to the width of the user's viewing window, build your app UI with [ui.page_fluid](https://shiny.posit.co/py/api/ui.page_fluid.html). - -### Fixed Grid System - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - - - - -```{.python filename="app.py" } -import matplotlib.pyplot as plt -import numpy as np -from shiny import App, render, ui - -app_ui = ui.page_fixed( #<< - ui.layout_sidebar( - ui.panel_sidebar( - ui.input_slider("n", "N", 0, 100, 20), - ), - ui.panel_main( - ui.output_plot("histogram"), - ), - ), -) #<< - - -def server(input, output, session): - @output - @render.plot(alt="A histogram") - def histogram(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) - plt.hist(x, input.n(), density=True) - - -app = App(app_ui, server, debug=True) -``` - -

Edit in Shinylive

-::: - -To make a fixed grid that maintains a constant maximum width, build your app UI with [ui.page_fluid](https://shiny.posit.co/py/api/ui.page_fluid.html). - -The fixed grid system maintains a fixed page width of 940 pixels by default. If Bootstrap responsive features are enabled (they are by default in Shiny) then the grid will also adapt to be 724px or 1170px wide depending on your viewport (e.g., when on a tablet). The fixed system does this by using Bootstrap `.container`s, whereas the fluid system uses Bootstrap `.container-fluid`s. Learn more about the difference [here](https://getbootstrap.com/docs/5.3/layout/containers/#how-they-work). - -The main benefit of a fixed grid is that it provides stronger guarantees about how users will see the various elements of your UI laid out (because it's not being dynamically laid out according to the width of the browser). The main drawback is that a fixed grid is more complex to work with. In general, we recommend using a fluid grid unless you absolutely require the lower level layout control afforded by a fixed grid. - -### Fillable Grid System - -:::{.column-screen-inset-right style="max-width:800px;"} - - - - - - - -```{.python filename="app.py" } -import matplotlib.pyplot as plt -import numpy as np -from shiny import App, render, ui - -app_ui = ui.page_fillable( #<< - ui.layout_sidebar( - ui.panel_sidebar( - ui.input_slider("n", "N", 0, 100, 20), - ), - ui.panel_main( - ui.output_plot("histogram", height="100%"), #<< - ), - ), -) #<< - - -def server(input, output, session): - @output - @render.plot(alt="A histogram") - def histogram(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) - plt.hist(x, input.n(), density=True) - - -app = App(app_ui, server, debug=True) -``` - -

Edit in Shinylive

-::: - -To make a fixed grid that maintains a constant maximum width, build your app UI with [ui.page_fillable](https://shiny.posit.co/py/api/ui.page_fillable.html). - -`ui.page_fluid()` and `ui.page_fixed()` create web pages that are laid out from the top down, leaving whitespace at the bottom if the page content's height is smaller than the browser window, and scrolling if the content is larger than the window. - -`ui.page_fillable()` is designed to latch the document body's size to the size of the window. This makes it possible to fill it with content that also scales to the size of the window. - -For example, `ui.page_fluid(ui.output_plot("plot", height="100%"))` will not work as expected. The plot element's effective height will be 0 because the plot's containing elements (`
` and ``) have automatic height. They determine their own height based on the height of their contained elements. However, `ui.page_fillable(ui.output_plot("plot", height="100%"))` will work as expected because `ui.page_fillable()` fixes the `` height at 100% of the window height. - -Note that `ui.page_fillable(ui.output_plot("plot"))` may not cause the plot to fill the page. Like most Shiny output widgets, `ui.output_plot()`'s default height is a fixed number of pixels. You must explicitly set `height="100%"` if you want a plot (or htmlwidget, say) to fill its container. - -One must be careful what layouts/panels/elements come between the `ui.page_fillable()` and the plots/widgets. Any container that has an automatic height will cause children with `height="100%"` to misbehave. Stick to functions that are designed for fill layouts, such as the ones in the Shiny package. - - - diff --git a/layouts/arrange/app-column-nest-core.py b/layouts/arrange/app-column-nest-core.py new file mode 100644 index 00000000..19eb279a --- /dev/null +++ b/layouts/arrange/app-column-nest-core.py @@ -0,0 +1,32 @@ +from shiny import App, ui + +app_ui = ui.page_fluid( + ui.row( + ui.column( + 12, + ui.card( + "Width 12", + ui.row( + ui.column( + 6, + ui.card( + "Width 6", + ui.row( + ui.column(6, ui.card("Width 6")), + ui.column(6, ui.card("Width 6")), + ), + ), + ), + ui.column(6, ui.card("Width 6")), + ), + ), + ) + ) +) + + +def server(input, output, session): + return None + + +app = App(app_ui, server) diff --git a/layouts/arrange/app-column-nest-express.py b/layouts/arrange/app-column-nest-express.py new file mode 100644 index 00000000..593cc469 --- /dev/null +++ b/layouts/arrange/app-column-nest-express.py @@ -0,0 +1,15 @@ +from shiny.express import ui + +with ui.row(): + with ui.column(12): + with ui.card("Width 12"): + with ui.row(): + with ui.column(6): + with ui.card("Width 6"): + with ui.row(): + with ui.column(6): + ui.card("Width 6") + with ui.column(6): + ui.card("Width 6") + with ui.column(6): + ui.card("Width 6") diff --git a/layouts/arrange/app-fillable-core.py b/layouts/arrange/app-fillable-core.py new file mode 100644 index 00000000..5f4df2c1 --- /dev/null +++ b/layouts/arrange/app-fillable-core.py @@ -0,0 +1,26 @@ +import matplotlib.pyplot as plt +import numpy as np +from shiny import App, render, ui + +app_ui = ui.page_fillable( # << + ui.layout_sidebar( + ui.panel_sidebar( + ui.input_slider("n", "N", 0, 100, 20), + ), + ui.panel_main( + ui.output_plot("histogram", height="100%"), # << + ), + ), +) # << + + +def server(input, output, session): + @output + @render.plot(alt="A histogram") + def histogram(): + np.random.seed(19680801) + x = 100 + 15 * np.random.randn(437) + plt.hist(x, input.n(), density=True) + + +app = App(app_ui, server, debug=True) diff --git a/layouts/arrange/app-fillable-express.py b/layouts/arrange/app-fillable-express.py new file mode 100644 index 00000000..14ac57f8 --- /dev/null +++ b/layouts/arrange/app-fillable-express.py @@ -0,0 +1,15 @@ +import matplotlib.pyplot as plt +import numpy as np +from shiny.express import input, render, ui + +ui.page_opts(fillable=True) + +with ui.sidebar(): + ui.input_slider("n", "N", 0, 100, 20) + + +@render.plot(alt="A histogram") +def histogram(): + np.random.seed(19680801) + x = 100 + 15 * np.random.randn(437) + plt.hist(x, input.n(), density=True) diff --git a/layouts/arrange/app-grid-core.py b/layouts/arrange/app-grid-core.py new file mode 100644 index 00000000..a48dc1a1 --- /dev/null +++ b/layouts/arrange/app-grid-core.py @@ -0,0 +1,18 @@ +from shiny import App, ui + +app_ui = ui.page_fluid( + ui.row( # << + ui.column(12, ui.card("Card 1")) # << + ), # << + ui.row( # << + ui.column(6, ui.card("Card 2")), # << + ui.column(6, ui.card("Card 3")), # << + ), # << +) + + +def server(input, output, session): + return None + + +app = App(app_ui, server) diff --git a/layouts/arrange/app-grid-express.py b/layouts/arrange/app-grid-express.py new file mode 100644 index 00000000..327d563a --- /dev/null +++ b/layouts/arrange/app-grid-express.py @@ -0,0 +1,12 @@ +from shiny.express import ui + +with ui.row(): # << + with ui.column(12): # << + ui.card("Card 1") + +with ui.row(): # << + with ui.column(6): # << + ui.card("Card 2") + + with ui.column(6): # << + ui.card("Card 2") diff --git a/layouts/arrange/app-grid-offset-core.py b/layouts/arrange/app-grid-offset-core.py new file mode 100644 index 00000000..528b9f2e --- /dev/null +++ b/layouts/arrange/app-grid-offset-core.py @@ -0,0 +1,22 @@ +from shiny import App, ui + +app_ui = ui.page_fluid( + ui.row( + ui.column(4, ui.card("Card 1")), + ui.column(4, ui.card("Card 2"), offset=4), # << + ), + ui.row( + ui.column(4, ui.card("Card 3"), offset=4) # << + ), + ui.row( + ui.column(4, ui.card("Card 4")), # << + ui.column(4, ui.card("Card 5"), offset=4), # << + ), +) + + +def server(input, output, session): + return None + + +app = App(app_ui, server) diff --git a/layouts/arrange/app-grid-offset-express.py b/layouts/arrange/app-grid-offset-express.py new file mode 100644 index 00000000..21bc0e3f --- /dev/null +++ b/layouts/arrange/app-grid-offset-express.py @@ -0,0 +1,19 @@ +from shiny.express import ui + +with ui.row(): + with ui.column(4): + ui.card("Card 1") + + with ui.column(4, offset=4): # << + ui.card("Card 2") + +with ui.row(): + with ui.column(4, offset=4): # << + ui.card("Card 3") + +with ui.row(): + with ui.column(4): # << + ui.card("Card 4") + + with ui.column(4, offset=4): # << + ui.card("Card 5") diff --git a/layouts/arrange/app-page-fixed-core.py b/layouts/arrange/app-page-fixed-core.py new file mode 100644 index 00000000..a17597ee --- /dev/null +++ b/layouts/arrange/app-page-fixed-core.py @@ -0,0 +1,26 @@ +import matplotlib.pyplot as plt +import numpy as np +from shiny import App, render, ui + +app_ui = ui.page_fixed( # << + ui.layout_sidebar( + ui.panel_sidebar( + ui.input_slider("n", "N", 0, 100, 20), + ), + ui.panel_main( + ui.output_plot("histogram"), + ), + ), +) # << + + +def server(input, output, session): + @output + @render.plot(alt="A histogram") + def histogram(): + np.random.seed(19680801) + x = 100 + 15 * np.random.randn(437) + plt.hist(x, input.n(), density=True) + + +app = App(app_ui, server, debug=True) diff --git a/layouts/arrange/app-page-fixed-express.py b/layouts/arrange/app-page-fixed-express.py new file mode 100644 index 00000000..47c7c879 --- /dev/null +++ b/layouts/arrange/app-page-fixed-express.py @@ -0,0 +1,13 @@ +import matplotlib.pyplot as plt +import numpy as np +from shiny.express import input, render, ui + +with ui.sidebar(): + ui.input_slider("n", "N", 0, 100, 20) + + +@render.plot(alt="A histogram") +def histogram(): + np.random.seed(19680801) + x = 100 + 15 * np.random.randn(437) + plt.hist(x, input.n(), density=True) diff --git a/layouts/arrange/app-page-fluid-core.py b/layouts/arrange/app-page-fluid-core.py new file mode 100644 index 00000000..95d04701 --- /dev/null +++ b/layouts/arrange/app-page-fluid-core.py @@ -0,0 +1,26 @@ +import matplotlib.pyplot as plt +import numpy as np +from shiny import App, render, ui + +app_ui = ui.page_fluid( # << + ui.layout_sidebar( + ui.panel_sidebar( + ui.input_slider("n", "N", 0, 100, 20), + ), + ui.panel_main( + ui.output_plot("histogram"), + ), + ), +) # << + + +def server(input, output, session): + @output + @render.plot(alt="A histogram") + def histogram(): + np.random.seed(19680801) + x = 100 + 15 * np.random.randn(437) + plt.hist(x, input.n(), density=True) + + +app = App(app_ui, server, debug=True) diff --git a/layouts/arrange/app-page-fluid-express.py b/layouts/arrange/app-page-fluid-express.py new file mode 100644 index 00000000..47c7c879 --- /dev/null +++ b/layouts/arrange/app-page-fluid-express.py @@ -0,0 +1,13 @@ +import matplotlib.pyplot as plt +import numpy as np +from shiny.express import input, render, ui + +with ui.sidebar(): + ui.input_slider("n", "N", 0, 100, 20) + + +@render.plot(alt="A histogram") +def histogram(): + np.random.seed(19680801) + x = 100 + 15 * np.random.randn(437) + plt.hist(x, input.n(), density=True) diff --git a/layouts/arrange/index.qmd b/layouts/arrange/index.qmd new file mode 100644 index 00000000..8386692e --- /dev/null +++ b/layouts/arrange/index.qmd @@ -0,0 +1,196 @@ +--- +title: "Arrange Elements" +description: > + Shiny's premade layouts, such as ui.page_sidebar() and ui.page_navbar() make use of + Shiny's lower-level grid layout functions. +aliases: + - "../arrange.html" +--- + +```{python} +#| include: false +import sys +sys.path.append("../..") + +from docs.helpers import express_core_preview, shinylive_app_preview +``` + +Shiny's premade layouts, such as [ui.layout_sidebar](https://shiny.posit.co/py/api/ui.layout_sidebar.html) and [ui.page_navbar](https://shiny.posit.co/py/api/ui.page_navbar.html) make use of Shiny's lower-level grid layout functions: + +- Rows are created by the `ui.row()` function and include columns. +- Columns are defined by the `ui.column()` function. Column widths are based on the [Bootstrap](https://getbootstrap.com/) 12-wide grid system, so column widths should add up to 12 within a row. + +You can use these functions directly to create your own layout. +Grid layouts can be used within a page, panel, or card and can even be nested within each other. + +## Relevant functions + +- [ui.row](https://shiny.posit.co/py/api/ui.row.html) `ui.row(*args, **kwargs)` + +- [ui.column](https://shiny.posit.co/py/api/ui.column.html) `ui.column(width, *args, offset=0, **kwargs)` + +- [ui.page_fluid](https://shiny.posit.co/py/api/ui.page_fluid.html) `ui.page_fluid(*args, title=None, lang=None, **kwargs)` + +- [ui.page_fixed](https://shiny.posit.co/py/api/ui.page_fixed.html) `ui.page_fixed(*args, title=None, lang=None, **kwargs)` + +- [ui.page_fillable](https://shiny.posit.co/py/api/ui.page_fillable.html) `ui.page_fillable(*args, padding=None, gap=None, fillable_mobile=False, title=None, lang=None, **kwargs)` + +::: {.border-bottom .blue .mt-6 .mb-5} +::: + +## Grid Layouts + +To create a layout using the grid system, first build a grid by interlacing calls to [ui.row](https://shiny.posit.co/py/api/ui.row.html) and [ui.column](https://shiny.posit.co/py/api/ui.column.html). +Then place UI elements within the grid by passing them to a `ui.row()` or `ui.column()` function. +For example, the app below arranges three cards into a simple pattern. + +::: {.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-grid-core.py", viewer_height=175, div_attrs=".p-0") +express_core_preview(None, "app-grid-core.py") +``` +::: + +Notice three things about the app: + +1. You always create rows before columns, and then embed columns within the rows. + +2. Rows are always as wide as the container they appear in, but columns take a `width` parameter. + `width` should be an integer from 1 to 12. + Bootstrap uses `width` to determine the width of the column relative to the row that contains it: + + - A column of `width=12` will span the entire row. + - A column of `width=6` will span half the row. + - A column of `width=3` will span a quarter of the row. + - And so on. + +3. You pass UI elements to `ui.row()` or `ui.column()` to place them in the grid. + Here we pass `ui.card()`s to various parts of the grid. + Each element is fitted to the column or row that it appears in. + +In addition to setting a column's width, you can also set its offset with the `offset` parameter. +`offset` controls how many units of padding appear to the left of the column. +Once again, `offset` should be an integer from 1 to 12. +The app below uses offsets to make a new arrangement: + +::: {.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-grid-offset-core.py", viewer_height=300, div_attrs=".p-0") +express_core_preview(None, "app-grid-offset-core.py") +``` +::: + +::: {.border-bottom .blue .mt-6 .mb-5} +::: + +## Column nesting + +When you nest rows and columns within a column, each nested level of column widths should add up to 12. +This is because Bootstrap divides every row into 12 units, no matter the actual width of the row. + +Consider this page layout: + +::: {.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-column-nest-core.py", viewer_height=300, div_attrs=".p-0") +express_core_preview(None, "app-column-nest-core.py") +``` +::: + +::: {.border-bottom .blue .mt-6 .mb-5} +::: + +## Controlling for page width and height + +There are multiple types of Shiny grids: fluid, fixed, and fillable. +The fluid grid system is recommended for most applications and is the default for Shiny functions like `ui.page_navbar()` and `ui.page_sidebar()`. +So far, all the examples on this page have used the fluid grid system. + +Each grid system uses a flexibly sub-dividable 12-column grid for layout. +They differ in how they interact with the viewer's browser window: + +- The fluid system always occupies the full width of the web page and re-sizes its components dynamically as the size of the page changes. + +- The fixed system occupies a fixed width of 940 pixels by default and may assume other widths when Bootstrap's responsive layout kicks in (e.g., when on a tablet). + +- The fillable system always occupies the full width *and height* of the web page and re-sizes its components dynamically as the size of the page changes. + +!["Differences between the three systems"](/images/page-control.png){.pt-4} + +### Fluid Grid System + +::: {.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-page-fluid-core.py", viewer_height=500, div_attrs=".p-0") +express_core_preview("app-page-fluid-express.py", "app-page-fluid-core.py") +``` +::: + +To make a fluid grid that adapts to the width of the user's viewing window, build your app UI with [ui.page_fluid](https://shiny.posit.co/py/api/ui.page_fluid.html). + +### Fixed Grid System + +::: {.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-page-fixed-core.py", viewer_height=500, div_attrs=".p-0") +express_core_preview("app-page-fixed-express.py", "app-page-fixed-core.py") +``` +::: + +To make a fixed grid that maintains a constant maximum width, build your app UI with [ui.page_fluid](https://shiny.posit.co/py/api/ui.page_fluid.html). + +The fixed grid system maintains a fixed page width of 940 pixels by default. +If Bootstrap responsive features are enabled (they are by default in Shiny) then the grid will also adapt to be 724px or 1170px wide depending on your viewport (e.g., when on a tablet). +The fixed system does this by using Bootstrap `.container`s, whereas the fluid system uses Bootstrap `.container-fluid`s. +Learn more about the difference [here](https://getbootstrap.com/docs/5.3/layout/containers/#how-they-work). + +The main benefit of a fixed grid is that it provides stronger guarantees about how users will see the various elements of your UI laid out (because it's not being dynamically laid out according to the width of the browser). +The main drawback is that a fixed grid is more complex to work with. +In general, we recommend using a fluid grid unless you absolutely require the lower level layout control afforded by a fixed grid. + +### Fillable Grid System + +::: {.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-fillable-core.py", viewer_height=425, div_attrs=".p-0") +express_core_preview("app-fillable-express.py", "app-fillable-core.py") +``` +::: + +To make a fixed grid that maintains a constant maximum width, build your app UI with [ui.page_fillable](https://shiny.posit.co/py/api/ui.page_fillable.html). + +`ui.page_fluid()` and `ui.page_fixed()` create web pages that are laid out from the top down, leaving whitespace at the bottom if the page content's height is smaller than the browser window, and scrolling if the content is larger than the window. + +`ui.page_fillable()` is designed to latch the document body's size to the size of the window. +This makes it possible to fill it with content that also scales to the size of the window. + +For example, `ui.page_fluid(ui.output_plot("plot", height="100%"))` will not work as expected. +The plot element's effective height will be 0 because the plot's containing elements (`
` and ``) have automatic height. +They determine their own height based on the height of their contained elements. +However, `ui.page_fillable(ui.output_plot("plot", height="100%"))` will work as expected because `ui.page_fillable()` fixes the `` height at 100% of the window height. + +Note that `ui.page_fillable(ui.output_plot("plot"))` may not cause the plot to fill the page. +Like most Shiny output widgets, `ui.output_plot()`'s default height is a fixed number of pixels. +You must explicitly set `height="100%"` if you want a plot (or htmlwidget, say) to fill its container. + +One must be careful what layouts/panels/elements come between the `ui.page_fillable()` and the plots/widgets. +Any container that has an automatic height will cause children with `height="100%"` to misbehave. +Stick to functions that are designed for fill layouts, such as the ones in the Shiny package. diff --git a/layouts/index.qmd b/layouts/index.qmd index 4cacf5db..454a9e9c 100644 --- a/layouts/index.qmd +++ b/layouts/index.qmd @@ -402,14 +402,14 @@ Use panels and cards to define areas of related content. ::: {.sticky-xl-top .pt-4}

- + Arrange Elements

Use rows and columns to create your own layout for every device size. -Arrange Elements +Arrange Elements ::: :::: @@ -420,17 +420,17 @@ Use rows and columns to create your own layout for every device size. @@ -438,17 +438,17 @@ Use rows and columns to create your own layout for every device size. @@ -456,17 +456,17 @@ Use rows and columns to create your own layout for every device size. From d3b5411585511780088c4b76f6f0f1bc69bada4a Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 23 Jan 2024 17:12:44 -0500 Subject: [PATCH 14/19] add aliases --- layouts/navbars/index.qmd | 6 +++++- layouts/panels-cards/index.qmd | 2 ++ layouts/sidebars/index.qmd | 2 ++ layouts/tabs/index.qmd | 2 ++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/layouts/navbars/index.qmd b/layouts/navbars/index.qmd index e1586ef5..748e2aed 100644 --- a/layouts/navbars/index.qmd +++ b/layouts/navbars/index.qmd @@ -1,6 +1,10 @@ --- title: "Navbars" -description: "A navbar adds a navigation bar, allowing users to easily navigate your Shiny app." +description: > + A navbar adds a navigation bar, allowing users to easily navigate your + Shiny app. +aliases: + - "../navbars.html" --- ```{python} diff --git a/layouts/panels-cards/index.qmd b/layouts/panels-cards/index.qmd index e22f23cb..be3eedf6 100644 --- a/layouts/panels-cards/index.qmd +++ b/layouts/panels-cards/index.qmd @@ -2,6 +2,8 @@ title: "Panels and cards" description: > Use panels and cards to define areas of related content in your Shiny app. +aliases: + - "../panels-cards.html" --- ```{python} diff --git a/layouts/sidebars/index.qmd b/layouts/sidebars/index.qmd index d6425e31..64ababeb 100644 --- a/layouts/sidebars/index.qmd +++ b/layouts/sidebars/index.qmd @@ -3,6 +3,8 @@ title: "Sidebars" description: > A sidebar layout creates a sidebar in your Shiny app, typically used for inputs, and a large main area, typically used for outputs. +aliases: + - "../sidebars.html" --- ```{python} diff --git a/layouts/tabs/index.qmd b/layouts/tabs/index.qmd index e6cdcff9..b030fa7b 100644 --- a/layouts/tabs/index.qmd +++ b/layouts/tabs/index.qmd @@ -2,6 +2,8 @@ title: "Tabs" description: > Tabs and navigation allow you to create Shiny apps with multiple pages. +aliases: + - "../tabs.html" --- ```{python} From b7f1292d42b3d09d54f8acaf4f92018aad5df953 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 24 Jan 2024 11:15:41 -0500 Subject: [PATCH 15/19] use latest shinylive syntax --- docs/helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/helpers.py b/docs/helpers.py index 1f4dc40e..fc0a605d 100644 --- a/docs/helpers.py +++ b/docs/helpers.py @@ -34,7 +34,7 @@ def append_shinylive_chunk( app = ShinyliveApp.from_local(app_file, files, language) - self.append(app.chunk(**kwargs)) + self.append(app.to_chunk(**kwargs)) def shinylive_chunk( @@ -233,7 +233,7 @@ def express_core_preview( block.append("### " + tab_name) block.append( - '```{.python .code-overflow-scroll shinylive="' + sl_app.url() + '"}' + '```{.python .code-overflow-scroll shinylive="' + sl_app.to_url() + '"}' ) block.append_file(app_file) block.extend(["```", ""]) From d4e197144cb8060c878835c3804bef079d06c10b Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 24 Jan 2024 11:16:23 -0500 Subject: [PATCH 16/19] use relevant-functions listing in layouts --- layouts/navbars/index.qmd | 20 ++++++++---- layouts/panels-cards/index.qmd | 50 +++++++++++++++++------------ layouts/sidebars/index.qmd | 22 ++++++++----- layouts/tabs/index.qmd | 57 +++++++++++++++++++++------------- 4 files changed, 95 insertions(+), 54 deletions(-) diff --git a/layouts/navbars/index.qmd b/layouts/navbars/index.qmd index 748e2aed..9f9fd917 100644 --- a/layouts/navbars/index.qmd +++ b/layouts/navbars/index.qmd @@ -3,8 +3,20 @@ title: "Navbars" description: > A navbar adds a navigation bar, allowing users to easily navigate your Shiny app. + aliases: - "../navbars.html" + +listing: +- id: relevant-functions + template: ../../components/_partials/components-detail-relevant-functions.ejs + contents: + - title: ui.page_navbar + href: https://shiny.posit.co/py/api/ui.page_navbar.html + signature: ui.page_navbar(*args, title=None, id=None, selected=None, sidebar=None, fillable=True, fillable_mobile=False, gap=None, padding=None, position='static-top', header=None, footer=None, bg=None, inverse=False, underline=True, collapsible=True, fluid=True, window_title=MISSING, lang=None) + - title: ui.nav + href: https://shiny.posit.co/py/api/ui.nav.html + signature: ui.nav_panel(title, *args, value=None, icon=None) --- ```{python} @@ -17,12 +29,8 @@ from docs.helpers import express_core_preview, shinylive_app_preview A navbar adds a navigation bar to your app, allowing users to easily navigate your app. -## Relevant functions - -- [ui.page_navbar](https://shiny.posit.co/py/api/ui.page_navbar.html) - `ui.page_navbar(*args, title=None, id=None, selected=None, sidebar=None, fillable=True, fillable_mobile=False, gap=None, padding=None, position='static-top', header=None, footer=None, bg=None, inverse=False, underline=True, collapsible=True, fluid=True, window_title=MISSING, lang=None)` -- [ui.nav](https://shiny.posit.co/py/api/ui.nav.html) - `ui.nav_panel(title, *args, value=None, icon=None)` +:::{#relevant-functions} +::: :::{.border-bottom .blue .mt-6 .mb-5} ::: diff --git a/layouts/panels-cards/index.qmd b/layouts/panels-cards/index.qmd index be3eedf6..94a96370 100644 --- a/layouts/panels-cards/index.qmd +++ b/layouts/panels-cards/index.qmd @@ -2,8 +2,37 @@ title: "Panels and cards" description: > Use panels and cards to define areas of related content in your Shiny app. + aliases: - "../panels-cards.html" + +listing: +- id: relevant-functions + template: ../../components/_partials/components-detail-relevant-functions.ejs + contents: + - title: ui.card + href: https://shiny.posit.co/py/api/ui.card.html + signature: ui.card(*args, full_screen=False, height=None, max_height=None, min_height=None, fill=True, class_=None, **kwargs) + + - title: ui.card_footer + href: https://shiny.posit.co/py/api/ui.card_footer.html + signature: ui.card_footer(*args, **kwargs) + + - title: ui.card_header + href: https://shiny.posit.co/py/api/ui.card_header.html + signature: ui.card_header(*args, container=tags.div, **kwargs) + + - title: ui.panel_absolute + href: https://shiny.posit.co/py/api/ui.panel_absolute.html + signature: ui.panel_absolute(*args, top=None, left=None, right=None, bottom=None, width=None, height=None, draggable=False, fixed=False, cursor='auto', **kwargs) + + - title: ui.panel_fixed + href: https://shiny.posit.co/py/api/ui.panel_fixed.html + signature: ui.panel_fixed(*args, **kwargs) + + - title: ui.panel_well + href: https://shiny.posit.co/py/api/ui.panel_well.html + signature: ui.panel_well(*args, **kwargs) --- ```{python} @@ -16,25 +45,8 @@ from docs.helpers import express_core_preview, shinylive_app_preview Use panels and cards to define areas of related content. -## Relevant functions - -- [ui.card](https://shiny.posit.co/py/api/ui.card.html) - `ui.card(*args, full_screen=False, height=None, max_height=None, min_height=None, fill=True, class_=None, **kwargs)` - -- [ui.card_footer](https://shiny.posit.co/py/api/ui.card_footer.html) - `ui.card_footer(*args, **kwargs)` - -- [ui.card_header](https://shiny.posit.co/py/api/ui.card_header.html) - `ui.card_header(*args, container=tags.div, **kwargs)` - -- [ui.panel_absolute](https://shiny.posit.co/py/api/ui.panel_absolute.html) - `ui.panel_absolute(*args, top=None, left=None, right=None, bottom=None, width=None, height=None, draggable=False, fixed=False, cursor='auto', **kwargs)` - -- [ui.panel_fixed](https://shiny.posit.co/py/api/ui.panel_fixed.html) - `ui.panel_fixed(*args, **kwargs)` - -- [ui.panel_well](https://shiny.posit.co/py/api/ui.panel_well.html) - `ui.panel_well(*args, **kwargs)` +:::{#relevant-functions} +::: :::{.border-bottom .blue .mt-6 .mb-5} ::: diff --git a/layouts/sidebars/index.qmd b/layouts/sidebars/index.qmd index 64ababeb..303d3f7c 100644 --- a/layouts/sidebars/index.qmd +++ b/layouts/sidebars/index.qmd @@ -3,8 +3,21 @@ title: "Sidebars" description: > A sidebar layout creates a sidebar in your Shiny app, typically used for inputs, and a large main area, typically used for outputs. + aliases: - "../sidebars.html" + +listing: +- id: relevant-functions + template: ../../components/_partials/components-detail-relevant-functions.ejs + contents: + - title: ui.layout_sidebar + href: https://shiny.posit.co/py/api/ui.layout_sidebar.html + signature: ui.layout_sidebar(sidebar, *args, fillable=True, fill=True, bg=None, fg=None, border=None, border_radius=None, border_color=None, gap=None, padding=None, height=None, **kwargs) + + - title: ui.sidebar + href: https://shiny.posit.co/py/api/ui.sidebar.html#shiny.ui.sidebar + signature: ui.sidebar(*args, width=250, position='left', open='desktop', id=None, title=None, bg=None, fg=None, class_=None, max_height_mobile=None, gap=None, padding=None) --- ```{python} @@ -17,13 +30,8 @@ from docs.helpers import express_core_preview, shinylive_app_preview A sidebar layout creates a sidebar, typically used for inputs, and a large main area, typically used for outputs. -## Relevant functions - -- [ui.layout_sidebar](https://shiny.posit.co/py/api/ui.layout_sidebar.html) - `ui.layout_sidebar(sidebar, *args, fillable=True, fill=True, bg=None, fg=None, border=None, border_radius=None, border_color=None, gap=None, padding=None, height=None, **kwargs)` - -- [ui.sidebar](https://shiny.posit.co/py/api/ui.sidebar.html#shiny.ui.sidebar) - `ui.sidebar(*args, width=250, position='left', open='desktop', id=None, title=None, bg=None, fg=None, class_=None, max_height_mobile=None, gap=None, padding=None)` +::: {#relevant-functions} +::: :::{.border-bottom .blue .mt-6 .mb-5} diff --git a/layouts/tabs/index.qmd b/layouts/tabs/index.qmd index b030fa7b..d08ff092 100644 --- a/layouts/tabs/index.qmd +++ b/layouts/tabs/index.qmd @@ -2,8 +2,41 @@ title: "Tabs" description: > Tabs and navigation allow you to create Shiny apps with multiple pages. + aliases: - "../tabs.html" + +listing: +- id: relevant-functions + template: ../../components/_partials/components-detail-relevant-functions.ejs + contents: + - title: ui.accordion + href: https://shiny.posit.co/py/api/ui.accordion.html#shiny.ui.accordion + signature: ui.accordion(*args, id=None, open=None, multiple=True, class_=None, width=None, height=None, **kwargs) + + - title: ui.accordion_panel + href: https://shiny.posit.co/py/api/ui.accordion_panel.html + signature: ui.accordion_panel(title, *args, value=MISSING, icon=None, **kwargs) + + - title: ui.navset_card_tab + href: https://shiny.posit.co/py/api/ui.navset_card_tab.html + signature: ui.navset_card_tab(*args, id=None, selected=None, title=None, sidebar=None, header=None, footer=None) + + - title: ui.navset_card_pill + href: https://shiny.posit.co/py/api/ui.navset_card_pill.html + signature: ui.navset_card_pill(*args, id=None, selected=None, title=None, sidebar=None, header=None, footer=None, placement='above') + + - title: ui.navset_pill + href: https://shiny.posit.co/py/api/ui.navset_pill.html + signature: ui.navset_pill(*args, id=None, selected=None, header=None, footer=None) + + - title: ui.navset_pill_list + href: https://shiny.posit.co/py/api/ui.navset_pill_list.html + signature: ui.navset_pill_list(*args, id=None, selected=None, header=None, footer=None, well=True, widths=(4, 8)) + + - title: ui.navset_tab + href: https://shiny.posit.co/py/api/ui.navset_tab.html + signature: ui.navset_tab(*args, id=None, selected=None, header=None, footer=None) --- ```{python} @@ -16,28 +49,8 @@ from docs.helpers import express_core_preview, shinylive_app_preview Tabs and navigation allow you to create apps that have multiple pages. -## Relevant functions - -- [ui.accordion](https://shiny.posit.co/py/api/ui.accordion.html#shiny.ui.accordion) - `ui.accordion(*args, id=None, open=None, multiple=True, class_=None, width=None, height=None, **kwargs)` - -- [ui.accordion_panel](https://shiny.posit.co/py/api/ui.accordion_panel.html) - `ui.accordion_panel(title, *args, value=MISSING, icon=None, **kwargs)` - -- [ui.navset_card_tab](https://shiny.posit.co/py/api/ui.navset_card_tab.html) - `ui.navset_card_tab(*args, id=None, selected=None, title=None, sidebar=None, header=None, footer=None)` - -- [ui.navset_card_pill](https://shiny.posit.co/py/api/ui.navset_card_pill.html) - `ui.navset_card_pill(*args, id=None, selected=None, title=None, sidebar=None, header=None, footer=None, placement='above')` - -- [ui.navset_pill](https://shiny.posit.co/py/api/ui.navset_pill.html) - `ui.navset_pill(*args, id=None, selected=None, header=None, footer=None)` - -- [ui.navset_pill_list](https://shiny.posit.co/py/api/ui.navset_pill_list.html) - `ui.navset_pill_list(*args, id=None, selected=None, header=None, footer=None, well=True, widths=(4, 8))` - -- [ui.navset_tab](https://shiny.posit.co/py/api/ui.navset_tab.html) - `ui.navset_tab(*args, id=None, selected=None, header=None, footer=None)` +::: {#relevant-functions} +::: :::{.border-bottom .blue .mt-6 .mb-5} ::: From 51ce6d90636526a10b7441fefc4468a74fdaed2c Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 25 Jan 2024 13:52:13 -0500 Subject: [PATCH 17/19] rewrite arrange to use layout_columns() and layout_column_wrap() --- layouts/arrange/app-column-nest-core.py | 33 ++-- layouts/arrange/app-column-nest-express.py | 26 +-- layouts/arrange/app-grid-core.py | 18 --- layouts/arrange/app-grid-express.py | 12 -- layouts/arrange/app-grid-offset-core.py | 22 --- layouts/arrange/app-grid-offset-express.py | 19 --- .../arrange/app-layout-column-wrap-core.py | 17 ++ .../app-layout-column-wrap-dynamic-core.py | 18 +++ .../app-layout-column-wrap-dynamic-express.py | 13 ++ .../arrange/app-layout-column-wrap-express.py | 13 ++ .../app-layout-column-wrap-half-core.py | 18 +++ .../app-layout-column-wrap-half-express.py | 13 ++ .../app-layout-columns-col-widths-core.py | 17 ++ .../app-layout-columns-col-widths-express.py | 11 ++ layouts/arrange/app-layout-columns-core.py | 17 ++ layouts/arrange/app-layout-columns-express.py | 13 ++ layouts/arrange/index.qmd | 153 +++++++++++++----- quarto-style.scss | 17 ++ 18 files changed, 304 insertions(+), 146 deletions(-) delete mode 100644 layouts/arrange/app-grid-core.py delete mode 100644 layouts/arrange/app-grid-express.py delete mode 100644 layouts/arrange/app-grid-offset-core.py delete mode 100644 layouts/arrange/app-grid-offset-express.py create mode 100644 layouts/arrange/app-layout-column-wrap-core.py create mode 100644 layouts/arrange/app-layout-column-wrap-dynamic-core.py create mode 100644 layouts/arrange/app-layout-column-wrap-dynamic-express.py create mode 100644 layouts/arrange/app-layout-column-wrap-express.py create mode 100644 layouts/arrange/app-layout-column-wrap-half-core.py create mode 100644 layouts/arrange/app-layout-column-wrap-half-express.py create mode 100644 layouts/arrange/app-layout-columns-col-widths-core.py create mode 100644 layouts/arrange/app-layout-columns-col-widths-express.py create mode 100644 layouts/arrange/app-layout-columns-core.py create mode 100644 layouts/arrange/app-layout-columns-express.py diff --git a/layouts/arrange/app-column-nest-core.py b/layouts/arrange/app-column-nest-core.py index 19eb279a..e59f45b0 100644 --- a/layouts/arrange/app-column-nest-core.py +++ b/layouts/arrange/app-column-nest-core.py @@ -1,27 +1,18 @@ from shiny import App, ui -app_ui = ui.page_fluid( - ui.row( - ui.column( - 12, - ui.card( - "Width 12", - ui.row( - ui.column( - 6, - ui.card( - "Width 6", - ui.row( - ui.column(6, ui.card("Width 6")), - ui.column(6, ui.card("Width 6")), - ), - ), - ), - ui.column(6, ui.card("Width 6")), - ), +app_ui = ui.page_fillable( + ui.layout_columns( + ui.card("Card 1"), + ui.card( + "Card 2", + ui.layout_columns( + ui.card("Card 2.1"), + ui.card("Card 2.2"), + width=1 / 2, ), - ) - ) + ), + col_widths=(4, 8), + ), ) diff --git a/layouts/arrange/app-column-nest-express.py b/layouts/arrange/app-column-nest-express.py index 593cc469..826da0fc 100644 --- a/layouts/arrange/app-column-nest-express.py +++ b/layouts/arrange/app-column-nest-express.py @@ -1,15 +1,15 @@ from shiny.express import ui -with ui.row(): - with ui.column(12): - with ui.card("Width 12"): - with ui.row(): - with ui.column(6): - with ui.card("Width 6"): - with ui.row(): - with ui.column(6): - ui.card("Width 6") - with ui.column(6): - ui.card("Width 6") - with ui.column(6): - ui.card("Width 6") +ui.page_opts(fillable=True) + +with ui.layout_columns(col_widths=(4, 8)): + with ui.card(): + "Card 1" + with ui.card(): + "Card 2" + + with ui.layout_column_wrap(width=1 / 2): + with ui.card(): + "Card 2.1" + with ui.card(): + "Card 2.2" diff --git a/layouts/arrange/app-grid-core.py b/layouts/arrange/app-grid-core.py deleted file mode 100644 index a48dc1a1..00000000 --- a/layouts/arrange/app-grid-core.py +++ /dev/null @@ -1,18 +0,0 @@ -from shiny import App, ui - -app_ui = ui.page_fluid( - ui.row( # << - ui.column(12, ui.card("Card 1")) # << - ), # << - ui.row( # << - ui.column(6, ui.card("Card 2")), # << - ui.column(6, ui.card("Card 3")), # << - ), # << -) - - -def server(input, output, session): - return None - - -app = App(app_ui, server) diff --git a/layouts/arrange/app-grid-express.py b/layouts/arrange/app-grid-express.py deleted file mode 100644 index 327d563a..00000000 --- a/layouts/arrange/app-grid-express.py +++ /dev/null @@ -1,12 +0,0 @@ -from shiny.express import ui - -with ui.row(): # << - with ui.column(12): # << - ui.card("Card 1") - -with ui.row(): # << - with ui.column(6): # << - ui.card("Card 2") - - with ui.column(6): # << - ui.card("Card 2") diff --git a/layouts/arrange/app-grid-offset-core.py b/layouts/arrange/app-grid-offset-core.py deleted file mode 100644 index 528b9f2e..00000000 --- a/layouts/arrange/app-grid-offset-core.py +++ /dev/null @@ -1,22 +0,0 @@ -from shiny import App, ui - -app_ui = ui.page_fluid( - ui.row( - ui.column(4, ui.card("Card 1")), - ui.column(4, ui.card("Card 2"), offset=4), # << - ), - ui.row( - ui.column(4, ui.card("Card 3"), offset=4) # << - ), - ui.row( - ui.column(4, ui.card("Card 4")), # << - ui.column(4, ui.card("Card 5"), offset=4), # << - ), -) - - -def server(input, output, session): - return None - - -app = App(app_ui, server) diff --git a/layouts/arrange/app-grid-offset-express.py b/layouts/arrange/app-grid-offset-express.py deleted file mode 100644 index 21bc0e3f..00000000 --- a/layouts/arrange/app-grid-offset-express.py +++ /dev/null @@ -1,19 +0,0 @@ -from shiny.express import ui - -with ui.row(): - with ui.column(4): - ui.card("Card 1") - - with ui.column(4, offset=4): # << - ui.card("Card 2") - -with ui.row(): - with ui.column(4, offset=4): # << - ui.card("Card 3") - -with ui.row(): - with ui.column(4): # << - ui.card("Card 4") - - with ui.column(4, offset=4): # << - ui.card("Card 5") diff --git a/layouts/arrange/app-layout-column-wrap-core.py b/layouts/arrange/app-layout-column-wrap-core.py new file mode 100644 index 00000000..57785f31 --- /dev/null +++ b/layouts/arrange/app-layout-column-wrap-core.py @@ -0,0 +1,17 @@ +from shiny import App, ui + +app_ui = ui.page_fillable( + "ui.layout_column_wrap()", + ui.layout_column_wrap( + ui.card("Card 1"), + ui.card("Card 2"), + ui.card("Card 3"), + ), +) + + +def server(input, output, session): + pass + + +app = App(app_ui, server=server) diff --git a/layouts/arrange/app-layout-column-wrap-dynamic-core.py b/layouts/arrange/app-layout-column-wrap-dynamic-core.py new file mode 100644 index 00000000..6d0bc59b --- /dev/null +++ b/layouts/arrange/app-layout-column-wrap-dynamic-core.py @@ -0,0 +1,18 @@ +from shiny import App, ui + +app_ui = ui.page_fillable( + ui.layout_column_wrap( # << + ui.card("Card 1"), + ui.card("Card 2"), + ui.card("Card 3"), + ui.card("Card 4"), + width="300px", # << + ), +) + + +def server(input, output, session): + pass + + +app = App(app_ui, server=server) diff --git a/layouts/arrange/app-layout-column-wrap-dynamic-express.py b/layouts/arrange/app-layout-column-wrap-dynamic-express.py new file mode 100644 index 00000000..357f18ca --- /dev/null +++ b/layouts/arrange/app-layout-column-wrap-dynamic-express.py @@ -0,0 +1,13 @@ +from shiny.express import ui + +ui.page_opts(fillable=True) + +with ui.layout_column_wrap(width="300px"): # << + with ui.card(): + "Card 1" + with ui.card(): + "Card 2" + with ui.card(): + "Card 3" + with ui.card(): + "Card 4" diff --git a/layouts/arrange/app-layout-column-wrap-express.py b/layouts/arrange/app-layout-column-wrap-express.py new file mode 100644 index 00000000..9a8fe760 --- /dev/null +++ b/layouts/arrange/app-layout-column-wrap-express.py @@ -0,0 +1,13 @@ +from shiny.express import ui + +ui.page_opts(fillable=True) + +"ui.layout_column_wrap()" + +with ui.layout_column_wrap(): + with ui.card(): + "Card 1" + with ui.card(): + "Card 2" + with ui.card(): + "Card 3" diff --git a/layouts/arrange/app-layout-column-wrap-half-core.py b/layouts/arrange/app-layout-column-wrap-half-core.py new file mode 100644 index 00000000..8e193f77 --- /dev/null +++ b/layouts/arrange/app-layout-column-wrap-half-core.py @@ -0,0 +1,18 @@ +from shiny import App, ui + +app_ui = ui.page_fillable( + ui.layout_column_wrap( # << + ui.card("Card 1"), + ui.card("Card 2"), + ui.card("Card 3"), + ui.card("Card 4"), + width=1 / 2, # << + ), +) + + +def server(input, output, session): + pass + + +app = App(app_ui, server=server) diff --git a/layouts/arrange/app-layout-column-wrap-half-express.py b/layouts/arrange/app-layout-column-wrap-half-express.py new file mode 100644 index 00000000..29d5dbd5 --- /dev/null +++ b/layouts/arrange/app-layout-column-wrap-half-express.py @@ -0,0 +1,13 @@ +from shiny.express import ui + +ui.page_opts(fillable=True) + +with ui.layout_column_wrap(width=1 / 2): # << + with ui.card(): + "Card 1" + with ui.card(): + "Card 2" + with ui.card(): + "Card 3" + with ui.card(): + "Card 4" diff --git a/layouts/arrange/app-layout-columns-col-widths-core.py b/layouts/arrange/app-layout-columns-col-widths-core.py new file mode 100644 index 00000000..3b00a175 --- /dev/null +++ b/layouts/arrange/app-layout-columns-col-widths-core.py @@ -0,0 +1,17 @@ +from shiny import App, ui + +app_ui = ui.page_fillable( + ui.layout_columns( + ui.card("Card 1"), + ui.card("Card 2"), + ui.card("Card 3"), + col_widths=(2, 4, 6), + ), +) + + +def server(input, output, session): + pass + + +app = App(app_ui, server=server) diff --git a/layouts/arrange/app-layout-columns-col-widths-express.py b/layouts/arrange/app-layout-columns-col-widths-express.py new file mode 100644 index 00000000..75707465 --- /dev/null +++ b/layouts/arrange/app-layout-columns-col-widths-express.py @@ -0,0 +1,11 @@ +from shiny.express import ui + +ui.page_opts(fillable=True) + +with ui.layout_columns(col_widths=(2, 4, 6)): + with ui.card(): + "Card 1" + with ui.card(): + "Card 2" + with ui.card(): + "Card 3" diff --git a/layouts/arrange/app-layout-columns-core.py b/layouts/arrange/app-layout-columns-core.py new file mode 100644 index 00000000..0c723bde --- /dev/null +++ b/layouts/arrange/app-layout-columns-core.py @@ -0,0 +1,17 @@ +from shiny import App, ui + +app_ui = ui.page_fillable( + "ui.layout_columns()", + ui.layout_columns( + ui.card("Card 1"), + ui.card("Card 2"), + ui.card("Card 3"), + ), +) + + +def server(input, output, session): + pass + + +app = App(app_ui, server=server) diff --git a/layouts/arrange/app-layout-columns-express.py b/layouts/arrange/app-layout-columns-express.py new file mode 100644 index 00000000..ec1d3b0e --- /dev/null +++ b/layouts/arrange/app-layout-columns-express.py @@ -0,0 +1,13 @@ +from shiny.express import ui + +ui.page_opts(fillable=True) + +"ui.layout_columns()" + +with ui.layout_columns(): + with ui.card(): + "Card 1" + with ui.card(): + "Card 2" + with ui.card(): + "Card 3" diff --git a/layouts/arrange/index.qmd b/layouts/arrange/index.qmd index 8386692e..7952ba1a 100644 --- a/layouts/arrange/index.qmd +++ b/layouts/arrange/index.qmd @@ -1,10 +1,28 @@ --- title: "Arrange Elements" description: > - Shiny's premade layouts, such as ui.page_sidebar() and ui.page_navbar() make use of - Shiny's lower-level grid layout functions. + Layout elements into rows and columns that responsively adapt to a wide range of + screen sizes. + aliases: - "../arrange.html" + +listing: +- id: relevant-functions + template: ../../components/_partials/components-detail-relevant-functions.ejs + contents: + - title: ui.layout_columns + href: https://shiny.posit.co/py/api/ui.layout_columns.html + signature: ui.layout_columns(*args, col_widths=None, row_heights=None, fill=True, fillable=True, gap=None, class_=None, height=None, **kwargs) + - title: ui.layout_column_wrap + href: https://shiny.posit.co/py/api/ui.layout_column_wrap.html + signature: ui.layout_columns(*args, col_widths=None, row_heights=None, fill=True, fillable=True, gap=None, class_=None, height=None, **kwargs) + - title: ui.page_fixed + href: https://shiny.posit.co/py/api/ui.page_fixed.html + signature: ui.page_fixed(*args, title=None, lang=None, **kwargs) + - title: ui.page_fillable + href: https://shiny.posit.co/py/api/ui.page_fillable.html + signature: ui.page_fillable(*args, padding=None, gap=None, fillable_mobile=False, title=None, lang=None, **kwargs) --- ```{python} @@ -15,84 +33,137 @@ sys.path.append("../..") from docs.helpers import express_core_preview, shinylive_app_preview ``` -Shiny's premade layouts, such as [ui.layout_sidebar](https://shiny.posit.co/py/api/ui.layout_sidebar.html) and [ui.page_navbar](https://shiny.posit.co/py/api/ui.page_navbar.html) make use of Shiny's lower-level grid layout functions: +Shiny provides two core functions for arranging elements into rows and columns: + +- `ui.layout_columns()` uses [Bootstrap's 12-column CSS Grid](https://getbootstrap.com/docs/5.3/layout/grid/) to create responsive and highly customizable layouts; -- Rows are created by the `ui.row()` function and include columns. -- Columns are defined by the `ui.column()` function. Column widths are based on the [Bootstrap](https://getbootstrap.com/) 12-wide grid system, so column widths should add up to 12 within a row. +- `ui.layout_column_wrap()` arranges elements into a grid with equally-sized cells. You can use these functions directly to create your own layout. Grid layouts can be used within a page, panel, or card and can even be nested within each other. -## Relevant functions +::: {#relevant-functions} +::: -- [ui.row](https://shiny.posit.co/py/api/ui.row.html) `ui.row(*args, **kwargs)` +::: {.border-bottom .blue .mt-6 .mb-5} +::: -- [ui.column](https://shiny.posit.co/py/api/ui.column.html) `ui.column(width, *args, offset=0, **kwargs)` +## Grid Layouts -- [ui.page_fluid](https://shiny.posit.co/py/api/ui.page_fluid.html) `ui.page_fluid(*args, title=None, lang=None, **kwargs)` +Both `ui.layout_columns()` and `ui.layout_column_wrap()` follow the same general pattern: pass each function a series of elements to have them arranged into a grid layout. -- [ui.page_fixed](https://shiny.posit.co/py/api/ui.page_fixed.html) `ui.page_fixed(*args, title=None, lang=None, **kwargs)` +::: {.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false -- [ui.page_fillable](https://shiny.posit.co/py/api/ui.page_fillable.html) `ui.page_fillable(*args, padding=None, gap=None, fillable_mobile=False, title=None, lang=None, **kwargs)` +shinylive_app_preview("app-layout-columns-core.py", viewer_height=175, div_attrs='.p-0 .resize-inline') +express_core_preview("app-layout-columns-express.py", "app-layout-columns-core.py") +``` -::: {.border-bottom .blue .mt-6 .mb-5} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-layout-column-wrap-core.py", viewer_height=175, div_attrs='.p-0 .resize-inline') +express_core_preview("app-layout-column-wrap-express.py", "app-layout-column-wrap-core.py") +``` ::: -## Grid Layouts +Notice two things about the above apps: + +1. Both approaches create one or more rows, filling the rows with columns to hold its children. + +2. Use the handle in the bottom right corner of the apps above to view each layout at different screen sizes (this works best on desktop screens). + How do the layouts shift as the screen gets smaller or larger? + +### How to choose between `layout_columns()` and `layout_column_wrap()` + +For simple layouts, it can be hard to tell `ui.layout_columns()` and `ui.layout_column_wrap()` apart. +Here's a short set of questions to ask yourself to help you decide which function to use: -To create a layout using the grid system, first build a grid by interlacing calls to [ui.row](https://shiny.posit.co/py/api/ui.row.html) and [ui.column](https://shiny.posit.co/py/api/ui.column.html). -Then place UI elements within the grid by passing them to a `ui.row()` or `ui.column()` function. -For example, the app below arranges three cards into a simple pattern. +1. Are all of the items in your arrangement the same, such as a set of cards or plots? + And do you want them all to have the same width and height? + + **If yes**: `ui.layout_column_wrap()` is best for a regularly-sized grid for a homogenous collection of elements. + +2. Do you want to customize the layout's column widths, or to have different layouts at different screen sizes (mobile vs desktop vs ultrawide)? + + **If yes:** `ui.layout_columns()` give you the tools to create highly customized, resposive column layouts. + +3. For all other uses, pick the function you like the most. + I happen to like `ui.layout_columns()` but you might prefer `ui.layout_column_wrap()`. + +### Responsive, configurable column layouts + +In the example above, we saw that, without your intervention, `ui.layout_columns()` will arrange the elements given to it into evenly sized columns. +In the default case, `ui.layout_columns()` makes a choice about the column widths that make the most sense based on the number of items provided. + +For example, when given three items, `ui.layout_columns()` places each item in a column with 4 width units. +You can use the `col_widths` argument to choose a different allocation of column width units. +Keep in mind that [Bootstrap's CSS Grid uses 12 units per row](https://getbootstrap.com/docs/5.3/layout/grid/). +In the example below, we use `col_widths=(2, 4, 6)` to create a row with a small, medium, and large card. ::: {.column-screen-inset-right style="max-width:800px;"} ```{python} #| output: asis #| echo: false -shinylive_app_preview("app-grid-core.py", viewer_height=175, div_attrs=".p-0") -express_core_preview(None, "app-grid-core.py") +shinylive_app_preview("app-layout-columns-col-widths-core.py", viewer_height=240, div_attrs=".p-0 .resize-inline") +express_core_preview("app-layout-columns-col-widths-express.py", "app-layout-columns-col-widths-core.py") ``` ::: -Notice three things about the app: +::: callout-tip +#### Advanced layouts + +There's a lot more that `layout_columns()` can do with `col_widths` to make highly customized layouts. + +- Choose different layouts with a dictionary of column widths at specific breakpoints, e.g. `col_widths={"sm": 4, "md": (2, 4, 6)}`. -1. You always create rows before columns, and then embed columns within the rows. +- Insert empty space between items with a negative column width, e.g. `col_widths=c(4, -4, 4)` creates two columns 4 units wide with an empty space of 4 units between them. + +Learn more in the API reference: [Express](https://shiny.posit.co/py/api/express/ui.layout_columns.html) \| [Core](https://shiny.posit.co/py/api/core/ui.layout_columns.html) +::: -2. Rows are always as wide as the container they appear in, but columns take a `width` parameter. - `width` should be an integer from 1 to 12. - Bootstrap uses `width` to determine the width of the column relative to the row that contains it: +::: {.border-bottom .blue .mt-6 .mb-5} +::: - - A column of `width=12` will span the entire row. - - A column of `width=6` will span half the row. - - A column of `width=3` will span a quarter of the row. - - And so on. +### Uniform grid layouts -3. You pass UI elements to `ui.row()` or `ui.column()` to place them in the grid. - Here we pass `ui.card()`s to various parts of the grid. - Each element is fitted to the column or row that it appears in. +`ui.layout_column_wrap()` arranges elements into a uniform grid, so its `width` argument applies a single value to all elements in the grid. -In addition to setting a column's width, you can also set its offset with the `offset` parameter. -`offset` controls how many units of padding appear to the left of the column. -Once again, `offset` should be an integer from 1 to 12. -The app below uses offsets to make a new arrangement: +When `width` is a fractional each item is given an equal portion of the row's width. +For example, `width=1 / 2` gives each item half of the row width. ::: {.column-screen-inset-right style="max-width:800px;"} ```{python} #| output: asis #| echo: false -shinylive_app_preview("app-grid-offset-core.py", viewer_height=300, div_attrs=".p-0") -express_core_preview(None, "app-grid-offset-core.py") +shinylive_app_preview("app-layout-column-wrap-half-core.py", viewer_height=240, div_attrs=".p-0 .resize-inline") +express_core_preview("app-layout-column-wrap-half-express.py", "app-layout-column-wrap-half-core.py") ``` ::: -::: {.border-bottom .blue .mt-6 .mb-5} +When `width` is a CSS unit, like `"300px"`, `ui.layout_column_wrap()` ensures that every item is *at least as wide* as `width`. +The items in the grid expand to fill horizontal space or contract when the layout container is smaller than `width`. + +Try resizing the app preview below to see how the layout shifts at different container widths. + +::: {.column-screen-inset-right style="max-width:800px;"} +```{python} +#| output: asis +#| echo: false + +shinylive_app_preview("app-layout-column-wrap-dynamic-core.py", viewer_height=240, div_attrs='.p-0 .resize-inline style="--max-width:1300px"') +express_core_preview("app-layout-column-wrap-dynamic-express.py", "app-layout-column-wrap-dynamic-core.py") +``` ::: ## Column nesting -When you nest rows and columns within a column, each nested level of column widths should add up to 12. -This is because Bootstrap divides every row into 12 units, no matter the actual width of the row. +Both `ui.layout_columns()` and `ui.layout_column_wrap()` can be nested, even within each other. Consider this page layout: @@ -101,8 +172,8 @@ Consider this page layout: #| output: asis #| echo: false -shinylive_app_preview("app-column-nest-core.py", viewer_height=300, div_attrs=".p-0") -express_core_preview(None, "app-column-nest-core.py") +shinylive_app_preview("app-column-nest-core.py", viewer_height=200, div_attrs=".p-0") +express_core_preview("app-column-nest-express.py", "app-column-nest-core.py") ``` ::: diff --git a/quarto-style.scss b/quarto-style.scss index 305f37af..b707d89c 100644 --- a/quarto-style.scss +++ b/quarto-style.scss @@ -1710,6 +1710,23 @@ btn.action-button:hover { border: 1px solid rgba(233,236,239,.9); border-radius: 0.5em; margin-block: 1em; + + &.resize-inline, + &.resize-block { + .shinylive-viewer { + background-color: var(--bs-gray-800); + } + } + + &.resize-inline .app-frame { + resize: inline; + min-width: var(--min-width, 200px); + max-width: var(--max-width, 100%); + } + &.resize-block .app-frame { + resize: block; + min-height: var(--min-height, 100px); + } } .app-preview .sourceCode { From 0c78ebcc513d9c60c545faaa739c13370187143d Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 25 Jan 2024 13:52:34 -0500 Subject: [PATCH 18/19] update py-shiny --- py-shiny | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py-shiny b/py-shiny index a4ab9508..e6a89846 160000 --- a/py-shiny +++ b/py-shiny @@ -1 +1 @@ -Subproject commit a4ab950834e6a96c94258b067b33889dbf853737 +Subproject commit e6a89846a33d4d6265954188bb3fcc677a3a20a9 From f2856a073853ac7489eb7a9881f4301a01793881 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 25 Jan 2024 13:55:02 -0500 Subject: [PATCH 19/19] use released shinylive 0.2.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7fcc1746..cd5c9792 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ jupyter jupyter_client < 8.0.0 tabulate -shinylive @ git+https://github.com/posit-dev/py-shinylive.git +shinylive==0.2.0 matplotlib==3.8.1 shiny seaborn==0.13.0