如何使用
bslib
实现与 shinydashboard
类似的行为?我特别对带有手风琴的侧边栏感兴趣。当选择一项时,我想选择一个预定义的 page_fluid
对象。
我尝试定义一个看起来像侧边栏菜单的手风琴。我想出了
ui <- bslib::page_navbar(
sidebar=bslib::sidebar(
bslib::accordion(
shiny::actionButton(inputId = "btn_start", label = "Start"),
shiny::actionButton(inputId = "btn_overview", label = "Overview"),
bslib::accordion_panel(
"Menu Level 1",
shiny::actionButton(inputId="btn_lvl1_a", label="Menu Level 1a"),
shiny::actionButton(inputId="btn_lvl1_b", label="Menu Level 1b")
),
bslib::accordion_panel(
"Menu Level 2",
shiny::actionButton(inputId="btn_lvl2_a", label="Menu Level 2a"),
shiny::actionButton(inputId="btn_lvl2_b", label="Menu Level 2b"),
shiny::actionButton(inputId="btn_lvl2_c", label="Menu Level 2c")
)
)
)
)
server <- function(input, output, session) {
}
shiny::shinyApp(ui, server)
并计划
observeEvent
点击actionButton
,但这似乎不是定义菜单的正确方法。看起来很奇怪。
一个最小的例子将不胜感激。
一个好的方法是在侧边栏上使用 html 列表,并在主区域使用隐藏的 tabsetPanel。
虽然有点长,但我在下面提供的reprex很容易理解,并且它清楚地描述了shiny在定制方面的功能。
global.R
library(shiny)
ui.R
ui <- tags$html(
lang = "en",
`data-bs-theme` = "auto",
tags$head(
tags$meta(charset = "utf-8"),
tags$meta(name = "viewport", content = "width=device-width, initial-scale=1"),
tags$title("Sidebar Demo"),
# bootstrap css:
tags$link(
href = "https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css",
rel = "stylesheet",
integrity = "sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM",
crossorigin = "anonymous"
),
# styles.css:
tags$link(
href = "styles.css",
rel = "stylesheet"
),
# google fonts:
tags$link(rel = "preconnect", href = "https://fonts.googleapis.com"),
tags$link(rel = "preconnect", href = "https://fonts.gstatic.com", crossorigin = NA),
tags$link(
href = "https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600;700&display=swap",
rel = "stylesheet"
)
),
tags$body(
class = "bg-light",
bootstrapLib(theme = bslib::bs_theme(version = 5)),
suppressDependencies("bootstrap"),
tags$div(
class = "d-flex vh-100",
# sidebar
tags$div(
class = "flex-shrink-0 p-3 bg-white border-end shadow-sm",
style = "width: 280px;",
tags$a(
href = "https://shiny.posit.co/",
class = paste(
"d-flex align-items-center pb-3 mb-3 link-body-emphasis",
"text-decoration-none border-bottom"
),
tags$img(
src = "shiny-solo.png",
alt = "Shiny Logo",
width = 50,
height = 25
),
tags$span(
class = "fs-5 fw-semibold ps-2",
"Sidebar Showcase"
)
),
tags$ul(
class = "list-unstyled ps-0",
tags$li(
class = "mb-1",
create_sidebar_menu_header(
title = "Home",
data_bs_target = "#home-collapse",
aria_expanded = "true"
),
tags$div(
class = "collapse show",
id = "home-collapse",
tags$ul(
class = "btn-toggle-nav list-unstyled fw-normal pb-1 small",
create_sidebar_link(id = "overview", label = "Overview", active = TRUE),
create_sidebar_link(id = "updates", label = "Updates"),
create_sidebar_link(id = "reports", label = "Reports")
)
)
),
tags$li(
class = "mb-1",
create_sidebar_menu_header(
title = "Dashboard",
data_bs_target = "#dashboard_collapse"
),
tags$div(
class = "collapse",
id = "dashboard_collapse",
tags$ul(
class = "btn-toggle-nav list-unstyled fw-normal pb-1 small",
create_sidebar_link(id = "weekly", label = "Weekly"),
create_sidebar_link(id = "monthly", label = "Monthly"),
create_sidebar_link(id = "annually", label = "Annually")
)
)
),
tags$li(
class = "mb-1",
create_sidebar_menu_header(
title = "Orders",
data_bs_target = "#orders_collapse"
),
tags$div(
class = "collapse",
id = "orders_collapse",
tags$ul(
class = "btn-toggle-nav list-unstyled fw-normal pb-1 small",
create_sidebar_link(id = "new_orders", label = "New"),
create_sidebar_link(id = "processed_orders", label = "Processed"),
create_sidebar_link(id = "shipped_orders", label = "Shipped"),
create_sidebar_link(id = "returned_orders", label = "Returned")
)
)
),
tags$li(class = "border-top my-3"),
tags$li(
class = "mb-1",
create_sidebar_menu_header(
title = "Account",
data_bs_target = "#account_collapse"
),
tags$div(
class = "collapse",
id = "account_collapse",
tags$ul(
class = "btn-toggle-nav list-unstyled fw-normal pb-1 small",
create_sidebar_link(id = "new_account", label = "New..."),
create_sidebar_link(id = "profile", label = "Profile"),
create_sidebar_link(id = "account_settings", label = "Settings"),
create_sidebar_link(id = "sign_out", label = "Sign Out")
)
)
),
)
),
# main
tags$div(
class = "p-3",
tabsetPanel(
id = "tabs",
type = "hidden",
tabPanelBody(
value = "overview",
tags$h3("Overview")
),
tabPanelBody(
value = "updates",
tags$h3("Updates")
),
tabPanelBody(
value = "reports",
tags$h3("Reports")
),
tabPanelBody(
value = "weekly",
tags$h3("Weekly Dashboard")
),
tabPanelBody(
value = "monthly",
tags$h3("Monthly Dashboard Summary")
),
tabPanelBody(
value = "annually",
tags$h3("Annual Dashboard Analytics")
),
tabPanelBody(
value = "new_orders",
tags$h3("New Orders")
),
tabPanelBody(
value = "processed_orders",
tags$h3("Processed Orders")
),
tabPanelBody(
value = "shipped_orders",
tags$h3("Here are the shipped orders")
),
tabPanelBody(
value = "returned_orders",
tags$h3("Returned orders here")
),
tabPanelBody(
value = "new_account",
tags$h3("Create New Account")
),
tabPanelBody(
value = "profile",
tags$h3("View your profile")
),
tabPanelBody(
value = "account_settings",
tags$h3("Your account settings")
),
tabPanelBody(
value = "sign_out",
tags$h3("You're now signed out")
)
)
)
),
# bootstrap js:
tags$script(
src = "https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js",
integrity = "sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz",
crossorigin = "anonymous"
),
# script.js:
tags$script(src = "script.js")
)
)
server.R
server <- function(input, output, session) {
sidebar_link_ids <- c(
"overview", "updates", "reports",
"weekly", "monthly", "annually",
"new_orders", "processed_orders", "shipped_orders", "returned_orders",
"new_account", "profile", "account_settings", "sign_out"
)
# add observers to switch to the clicked link's tab:
lapply(sidebar_link_ids, \(id) {
observeEvent(input[[id]], {
freezeReactiveValue(input, "tabs")
updateTabsetPanel(session = session, inputId = "tabs", selected = id)
})
})
}
R/create_sidebar_link.R
#' Create sidebar link
#'
#' @param id input id for the link
#' @param label Label
#' @param class Bootstrap classes to apply to the link
#' @param active Whether this should be the active link
#' @return tagList with a tags$li
create_sidebar_link <- \(
id,
label,
class = "link-body-emphasis d-inline-flex text-decoration-none rounded w-100",
active = FALSE
) {
if (active) {
class <- paste(class, "active")
}
tagList(
tags$li(
actionLink(
inputId = id,
label = label,
class = class
)
)
)
}
R/create_sidebar_menu_header.R
#' Create a sidebar menu header
#'
#' When clicked, it collapses it's contents (sidebar menus)
#'
#' @param title Header title
#' @param title_class Bootstrap classes to apply to the title
#' @param data_bs_target 'data-bs-target' attribute of the menu header
#' @param data_bs_toggle 'data-bs-toggle' attribute of the menu header
#' @param class Bootstrap classes to apply to the menu header
#' @param aria_expanded 'aria-expanded' attribute of the menu header. Whether
#' this sidebar menu is open or closed. Either "true" or "false".
#' @return tagList with a tags$button
create_sidebar_menu_header <- \(
title,
title_class = "ps-2",
data_bs_target,
data_bs_toggle = "collapse",
class = "btn btn-toggle d-inline-flex align-items-center rounded border-0 collapsed w-100",
aria_expanded = "false"
) {
tagList(
tags$button(
class = class,
`data-bs-toggle` = data_bs_toggle,
`data-bs-target` = data_bs_target,
`aria-expanded` = aria_expanded,
tags$span(
class = title_class,
title
)
)
)
}
www/styles.css
body {
min-height: 100vh;
min-height: -webkit-fill-available;
font-family: 'Quicksand', sans-serif;
}
html {
height: -webkit-fill-available;
}
.btn-toggle {
padding: .25rem .5rem;
font-weight: 600;
color: var(--bs-emphasis-color);
background-color: transparent;
}
.btn-toggle:hover,
.btn-toggle:focus {
color: rgba(var(--bs-emphasis-color-rgb), .85);
background-color: var(--bs-tertiary-bg);
}
.btn-toggle::before {
width: 1.25rem;
line-height: 0;
content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%280,0,0,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e");
transition: transform .35s ease;
transform-origin: .5em 50%;
}
[data-bs-theme="dark"] .btn-toggle::before {
content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%28255,255,255,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e");
}
.btn-toggle[aria-expanded="true"] {
color: rgba(var(--bs-emphasis-color-rgb), .85);
}
.btn-toggle[aria-expanded="true"]::before {
transform: rotate(90deg);
}
.btn-toggle-nav a {
padding: .1875rem .5rem;
margin-top: .125rem;
margin-left: 1.25rem;
max-width: 190px;
}
.btn-toggle-nav a:hover,
.btn-toggle-nav a:focus {
background-color: var(--bs-tertiary-bg);
}
.btn-toggle-nav a.active {
background-color: var(--bs-dark);
color: var(--bs-white) !important;
}
www/script.js
$(document).ready(function() {
$(".btn-toggle-nav").on("click", "a", function() {
// remove "active" class from all elements:
$(".btn-toggle-nav a").removeClass("active");
// add "active" class to the clicked element:
$(this).addClass("active");
});
});
www/shiny-solo.png
我想建议将其作为纯 bslib 解决方案来生成闪亮的仪表板外观和感觉:
library(shiny)
library(bslib)
# Define UI
ui <- page_fluid(
navset_pill_list(
id = "sidebar_menu",
widths = c(2, 10),
nav_item(h1("Bslib shinyDashboard Mimic")),
nav_panel(
title = "One",
p("First tab content."), plotOutput("distPlot")
),
nav_panel(
title = "Two",
p("Second tab content."), textOutput("textOutput"), tableOutput("tabcars")
)
)
)
# Define server logic
server <- function(input, output, session) {
output$distPlot <- renderPlot({
hist(rnorm(500))
})
output$textOutput <- renderText({
"Settings content goes here."
})
output$tabcars <- renderTable({
head(mtcars)
})
}
# Run the application
shinyApp(ui = ui, server = server, options = list(launch.browser = FALSE))
截图: