blockr.ui ships a
Bootstrap-free slide-in panel that mirrors
shiny::showModal() / shiny::removeModal() in
spirit. Mount the panel once per page with sidebar_ui(),
then drive its content from any moduleServer() body using
show_sidebar() and hide_sidebar().
library(shiny)
library(blockr.ui)
ui <- fluidPage(
actionButton("open", "Open sidebar"),
sidebar_ui("main_sidebar")
)
server <- function(input, output, session) {
observeEvent(input$open, {
show_sidebar(
"main_sidebar",
title = "Add a new block",
ui = tagList(
textInput("block_name", "Name"),
selectInput("block_kind", "Type", c("data", "transform", "plot")),
actionButton("confirm", "Add", class = "btn-primary")
)
)
})
observeEvent(input$confirm, {
# validate, mutate state, ...
hide_sidebar("main_sidebar")
})
}
shinyApp(ui, server)The inputs inside the body (input$block_name,
input$block_kind, input$confirm) live in the
calling session - exactly as they would inside a
modalDialog(). No nested moduleServer() is
required.
Each sidebar_ui() call also registers a Shiny input
under the same id, exposing two logical fields:
input[[id]]$open - TRUE while the panel is
open, FALSE once the user closes it (Esc, X button) or the
server calls hide_sidebar().input[[id]]$pinned - TRUE while the user
has the panel pinned.For a panel whose body is known at UI-build time (a settings form, a
help panel, an “About” sheet), you don’t need a server round-trip to
open it. Pre-render the body at sidebar_ui() time and put
data-blockr-sidebar-target = "<id>" on any element
that should toggle it.
ui <- fluidPage(
actionButton(
"settings_btn", icon("gear"),
`data-blockr-sidebar-target` = "settings_sidebar"
),
sidebar_ui(
"settings_sidebar",
side = "left",
title = "Board options",
ui = tagList(
checkboxInput("opt_a", "Option A", value = TRUE),
sliderInput("opt_b", "Option B", min = 0, max = 100, value = 50)
)
)
)Clicking the gear toggles the panel open / closed entirely on the
client. The inputs inside (input$opt_a,
input$opt_b) are observable from the server exactly as if
they sat in the page directly. A later
show_sidebar("settings_sidebar") (no ui) still
opens the panel from the server when needed;
show_sidebar("settings_sidebar", ui = <tags>)
replaces the pre-rendered body if you want to swap it dynamically.
show_sidebar() replaces the panel’s body and title in
place, and pin state is bound to the panel element. Re-showing the same
panel with fresh content from the same caller (the “chain after confirm”
flow, which keep_or_hide_sidebar() builds on) is the
supported pattern.
Two unrelated concerns - say a “Settings” panel and an “Add block”
form - should mount two sidebar_ui() panels with distinct
ids. Sharing one id between unrelated callers means the second
show_sidebar() will silently overwrite the first, including
any pinned content the user expected to stay put.
A common renderer pattern is to open the sidebar with a “first block”
hint when the session starts on an empty board, while still respecting a
later user dismissal. The recipe is built on top of
show_sidebar() and input[[id]]$open - no
special opt-in is needed on sidebar_ui().
empty_board_renderer <- function(id, board) {
moduleServer(id, function(input, output, session) {
auto_opened <- reactiveVal(FALSE)
observe({
if (auto_opened()) return()
if (length(blockr.core::board_blocks(board)) > 0L) return()
show_sidebar(
session$ns("main_sidebar"),
title = "Add your first block",
ui = tagList(
p("Pick a block to get started."),
actionButton(session$ns("add_dataset"), "Dataset block"),
actionButton(session$ns("add_import"), "Import data")
)
)
auto_opened(TRUE)
})
# Once the user dismisses the hint, input[[id]]$open flips to FALSE
# and the auto_opened flag prevents this observer from re-opening.
})
}Push-mode panels reflow the page content via padding on
<html>, driven by
--blockr-sidebar-width-left and
--blockr-sidebar-width-right custom properties (both
nonzero when both sides are open). The legacy
--blockr-sidebar-width is also set to the open side’s width
when a single panel is open, so existing consumers of the form
margin-right: var(--blockr-sidebar-width, 0px) keep
working.