bslib icon indicating copy to clipboard operation
bslib copied to clipboard

Sidebar navigation

Open cpsievert opened this issue 1 year ago • 11 comments

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)

cpsievert avatar May 18 '23 00:05 cpsievert

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.

asbates avatar Jun 14 '23 19:06 asbates

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!

tuge98 avatar Aug 23 '23 12:08 tuge98

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?

DavZim avatar Sep 07 '23 14:09 DavZim

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)

cpsievert avatar Sep 07 '23 15:09 cpsievert

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

DavZim avatar Sep 08 '23 09:09 DavZim

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?

rwaaijman avatar Jan 15 '24 15:01 rwaaijman

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

toxintoxin avatar Jan 17 '24 02:01 toxintoxin

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).

gadenbuie avatar Feb 02 '24 14:02 gadenbuie

Will this support multiple levels of nesting?

toxintoxin avatar Apr 10 '24 10:04 toxintoxin

Hi! Has anything changed since then?

pawelqs avatar Aug 05 '24 07:08 pawelqs

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

baderd avatar Aug 12 '24 07:08 baderd