rstudio / bslib

Tools for theming Shiny and R Markdown via Bootstrap 3, 4, or 5.
https://rstudio.github.io/bslib/
Other
487 stars 58 forks source link

Sidebar navigation #585

Open cpsievert opened 1 year ago

cpsievert commented 1 year ago

The underlying component-level API could be something like:

page_fillable(
  navset_sidebar(
    nav_panel("A", "a"),
    nav_panel("B", "b")
  )
)

And implementation wise could look quite similar to navset_pill_list() except uses layout_sidebar() for the layout. We'd also have to face a tough decision on whether to support nav_menu() (i.e., dropdowns) in this context, or maybe introduce something more general, like nav_group(), which would maybe avoid collapsing altogether)

asbates commented 1 year ago

Does this refer to clicking on a sidebar icon taking you to a different page/tab of the app? Something like tabItem in {shinydasboard} or menuItem in {bs4Dash}? If so, yes please!

And having nav_menu() like functionality where you can expand/collapse a list would be really nice to have.

tuge98 commented 1 year ago

This would be a really cool feature! Currently without possibility to create sidebar navigation it makes this framework a bit unusable especially for mid/larger side dashboard projects. Looking forward to see if you guys can implement it!

DavZim commented 1 year ago

Just wanted to voice my support for this feature. I love what I see about bslib, but being able to create a "multipage sidebar dashboard" would make this even better :D

Something along the lines of this (using shinydashboard or bs4Dash):

library(shinydashboard)

ui <- dashboardPage(
  dashboardHeader(title = "Basic dashboard"),
  dashboardSidebar(
    sidebarMenu(
      menuItem("Dashboard", tabName = "dashboard", icon = icon("dashboard")),
      menuItem("Widgets", tabName = "widgets", icon = icon("th"))
    )
  ),
  dashboardBody(
    tabItems(
      # First tab content
      tabItem(tabName = "dashboard", h2("Dashboard")),

      # Second tab content
      tabItem(tabName = "widgets", h2("Widgets tab content"))
    )
  )
)

shinyApp(ui, function(input, output, session) {})

Is there anything that I could do to help this feature come alive?

cpsievert commented 1 year ago

being able to create a "multipage sidebar dashboard" would make this even better :D

You can make multi-page sidebar layouts. It's just that, currently, navigation must be in the header, not the sidebar (which is arguably better UX if you also want non-navigation controls in the sidebar)

DavZim commented 1 year ago

Thanks for the comment. At the moment we use the left sidebar solely for navigation and want to use element-specific sidebars for control/filter etc.

If it helps, this is a quick hack that has the basic sidebar functionality (inspired by the shiny.tailwind package, esp the twTabNav() and twTabContent() functions as well as the JS code).

It does not allow for submenus, but might be a good start if you want to go this way.

library(shiny)
library(bslib)
library(bsicons)

# Javascript to actually change the tabs =====
js <- '
function opentab(tabsetid, tabid) {
    const tabs = document.querySelectorAll("." + tabsetid);
    const contents = document.querySelectorAll("." + tabsetid + "-content");
    // remove bsTab-active and bsTabContent-active classes and hide content
    tabs.forEach(tab => {
      tab.classList.remove("active");
      // set active tab
      if (tab.id == tabid) tab.classList.add("active");
    });
    contents.forEach(c => {
      c.style.display = "none";
      // show content tab
      if (c.id == tabid + "-content") {
        c.style.display = "block";
        $(c).trigger("shown");
      };
    });
}'

# Helper functions to create the content and tab ====
nav_content <- function(..., ids = NULL, container_class = NULL,
                        content_class = NULL, tabsetid = "tabSet1") {
  dots <- list(...)

  if (is.null(ids)) ids <- paste0("bsTab-", seq_along(dots))

  if (length(dots) != length(ids))
    stop("ids has to have the same length as the provided tab navigation elements")

  shiny::div(
    class = container_class,
    lapply(seq_along(dots), function(i) {
      id <- dots[[i]]$attribs$id
      if(is.null(id)) id <- ids[[i]]

      idc <- strsplit(id, "-")[[1]]
      if(idc[length(idc)] != "content") id <- paste0(id, "-content")

      shiny::div(
        class = paste(paste0(tabsetid, "-content"), content_class),
        style = if(i == 1) "display: block;" else "display: none;",
        id = id,
        dots[[i]]
      )
    })
  )
}

nav_tab <- function(..., ids = NULL, tabsetid = "tabSet1") {
  dots <- list(...)

  if (is.null(ids)) ids <- paste0("bsTab-", seq_along(dots))

  if (length(dots) != length(ids))
    stop("ids has to have the same length as the provided tab navigation elements")

  shiny::div(
    class = "row",
    shiny::div(
      class = "col-sm-12",
      shiny::tags$ul(
        style = "cursor: pointer;",
        class = "nav nav-pills nav-stacked",
        shiny::singleton(tags$script(shiny::HTML(js))),

        lapply(seq_along(dots), function(i) {
          id <- dots[[i]]$attribs$id
          if(is.null(id)) id <- ids[[i]]

          cl <- paste("nav-item", tabsetid, if (i == 1) "active")

          tags$li(
            class = cl, id = id,
            onclick = sprintf("opentab('%s', '%s');", tabsetid, id),
            tags$a(dots[[i]])
          )
        })
      )
    )
  )
}

# Construct the UI =====
ui <- page_sidebar(
  sidebar = sidebar(
    nav_tab(
      span(bs_icon("database"), span("Dashboard")),
      span(bs_icon("bar-chart-fill"), span("Widgets"))
    )
  ),

  nav_content(
    div(h1("Dashboard"), plotOutput("dashboard_plot")),
    div(h1("Widgets"), plotOutput("widget_plot"))
  )
)

# Construct the Server ====
server <- function(input, output, session) {
  output$dashboard_plot <- renderPlot({
    plot(1:100, cumsum(rnorm(100)), main = "Dashboard", type = "l")
  })
  output$widget_plot <- renderPlot({
    boxplot(rnorm(100))
    title("Widget")
  })
}

shinyApp(ui, server)

image

rwaaijman commented 10 months ago

Our company would also like to create dashboards with a sidebar naviagation (just like shinydashboards), but then with bs4 or higher. I read that you are planning to implement this in a new release. Is there a global timespan for the new release?

toxintoxin commented 10 months ago

Here's a very nice example done in html and js, if you guys can package it as a function that would be best! https://stackoverflow.com/a/76648897/22331901

gadenbuie commented 9 months ago

From @WickM in #976:

I'd like to request the addition of a highly valuable feature: the ability to minify the sidebar, similar to the functionality showcased in the Bootstrap 5 documentation (getbootstrap.com/docs/5.0/examples/sidebars) and the bs4Dash package (rinterface.github.io/bs4Dash/reference/dashboardSidebar.html).

toxintoxin commented 7 months ago

Will this support multiple levels of nesting?

pawelqs commented 3 months ago

Hi! Has anything changed since then?

baderd commented 3 months ago

Here's a very nice example done in html and js, if you guys can package it as a function that would be best! https://stackoverflow.com/a/76648897/22331901

image

I added a sidebar navigation with bslib alone to this post using navset_pill_list() facing the issues that are mentioned here: https://github.com/rstudio/bslib/issues/980