Skip to content

feat: add offcanvas() panels#1328

Merged
gadenbuie merged 15 commits into
mainfrom
feat/offcanvas
Jul 1, 2026
Merged

feat: add offcanvas() panels#1328
gadenbuie merged 15 commits into
mainfrom
feat/offcanvas

Conversation

@gadenbuie

Copy link
Copy Markdown
Member

Closes #1194

Summary

Adds offcanvas() plus server verbs show_offcanvas()/hide_offcanvas()/toggle_offcanvas() for Bootstrap offcanvas panels (sliding side/top/bottom drawers). offcanvas() returns a deferred bslib_offcanvas object (same pattern as toast()/sidebar()) supporting three usage modes, selected by whether trigger/id are supplied. State and lifecycle are governed by id: an explicit id gets a live input$<id> logical binding and persists in the DOM across hide/show; an anonymous panel self-destructs (unbinds + removes) on hide. Client-side binding is a <bslib-offcanvas> web component wrapping bootstrap.Offcanvas, registered alongside the existing popover/switch web components; show_offcanvas() rides a custom message (insert-if-absent + show, required since no client binding exists yet for server-only panels) while hide/toggle ride sendInputMessage. Styling lives in inst/components/scss/offcanvas.scss, auto-picked-up by the existing SCSS glob (no new dependency registration needed).

Verification

(a) Trigger + panel in the UI -- clicking the trigger reveals the panel client-side, no server round-trip:

offcanvas(
  "Panel content here.",
  title = "Settings",
  trigger = actionButton("open", "Open panel")
)

(b) Id'd panel in the UI, controlled from the server:

ui <- page_fillable(
  offcanvas("Panel content here.", title = "Settings", id = "oc"),
  actionButton("open", "Open panel"),
  verbatimTextOutput("state")
)
server <- function(input, output, session) {
  observeEvent(input$open, toggle_offcanvas("oc"))
  output$state <- renderText(paste("input$oc =", input$oc))
}

(c) Server-defined panel, revealed on demand (no UI declaration needed):

server <- function(input, output, session) {
  observeEvent(input$open, {
    show_offcanvas(offcanvas("Panel content here.", title = "Settings"))
  })
}

In each case the panel slides in from the right edge of the viewport (default placement); input$oc (mode b) flips to TRUE/FALSE as it's shown/hidden.

gadenbuie and others added 12 commits June 30, 2026 16:09
Add offcanvas() deferred object with as.tags/print rendering of the
<bslib-offcanvas> custom element, plus show/hide/toggle_offcanvas()
server verbs. Add offcanvas.scss to pin an optional footer via flexbox.

Client-side JS binding + custom-message handler follow in subsequent
commits.
Add <bslib-offcanvas> Lit web component (Shiny input binding wrapping
bootstrap.Offcanvas; reports open/closed state; handles hide/toggle
messages) and the bslib.show-offcanvas custom-message handler (insert-
if-absent, bind, show; anonymous panels self-destruct on hide). Rebuild
dist bundles.
Add test-offcanvas.R, roxygen Module IDs section, _pkgdown.yml and
NEWS.md entries, and generated man pages.

Tests surfaced three render-time bugs, now fixed:
- normalize_offcanvas_placement() errored on the default placement vector
  (arg_match0 on the first element instead of arg_match on the vector)
- splice named ... attributes via rlang::list2() (list() is not dynamic-dots)
- wire the trigger via tagAppendAttributes on its last element
  (tagQuery()$last() does not exist)

Also suppress the title-NULL accessibility warning when an aria-label or
aria-labelledby is supplied via ....
- offcanvas_wire_trigger() wraps a bare-string trigger (or bare-string
  last element of a tagList) in a <span> before attaching Bootstrap
  wiring, mirroring getOrCreateTriggerEl() in _utilsTooltip.ts.
- Document that show_offcanvas() reveals an existing id'd panel without
  re-rendering its content.
- Align server-verb `...` roxygen with show_toast() wording.
- Dispose the Bootstrap Offcanvas instance in disconnectedCallback so
  anonymous (temporary) panels removed on hide don't leak the instance,
  backdrop, and focustrap (matches popover/tooltip teardown).
- Narrow receiveMessage on data.method directly, dropping the redundant
  ToggleMessage cast.
- Note that .offcanvas styling overrides the :host display fallback.
Adds tests for footer rendering, the close_button / header-omission
branches, width/height CSS variables, the print preview, and
tagList/bare-string trigger wiring.

Extracts offcanvas_preview_tags() from print.bslib_offcanvas() so the
preview rendering can be tested without print() opening a browser.

Fixes a bug surfaced by the new tests: the trigger branch used find()
(descendants only) to add the .show class, but the panel is a top-level
sibling of the trigger, so it was never shown in the preview. Use
filter() instead.
@gadenbuie gadenbuie marked this pull request as ready for review June 30, 2026 21:58
@gadenbuie gadenbuie requested a review from cpsievert June 30, 2026 21:58
@cpsievert cpsievert requested a review from Copilot June 30, 2026 22:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Bootstrap 5 offcanvas component to bslib, including an R API (offcanvas()) and Shiny server controls (show_offcanvas(), hide_offcanvas(), toggle_offcanvas()), backed by new client-side web components and message handlers plus styling and documentation updates.

Changes:

  • Introduces offcanvas() + server verbs with lifecycle semantics (persistent vs temporary panels).
  • Adds a <bslib-offcanvas> web component (Shiny input binding) and a bslib.show-offcanvas custom message handler for server-defined insertion.
  • Adds SCSS styling, docs/reference entries, NEWS entry, and comprehensive test coverage.

Reviewed changes

Copilot reviewed 12 out of 25 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
R/offcanvas.R New offcanvas API, tag rendering, trigger wiring, preview, and server verbs.
tests/testthat/test-offcanvas.R Unit tests for constructor defaults, tag output, a11y wiring, sizing, and server verb messaging.
srcts/src/components/webcomponents/offcanvas.ts New <bslib-offcanvas> Shiny input web component wrapping Bootstrap Offcanvas.
srcts/src/components/webcomponents/index.ts Registers the new web component.
srcts/src/components/offcanvas.ts Adds bslib.show-offcanvas custom message handler to insert/show server-defined panels.
srcts/src/components/index.ts Ensures the new offcanvas message handler module is loaded.
inst/components/scss/offcanvas.scss Styling for offcanvas title and footer.
inst/components/dist/web-components.js Built web-components bundle update to include <bslib-offcanvas>.
inst/components/dist/web-components.min.js Minified web-components bundle update.
inst/components/dist/components.js Built components bundle update to include bslib.show-offcanvas handler.
inst/components/dist/components.min.js Minified components bundle update.
inst/components/dist/components.css Built CSS update to include offcanvas styles.
pkgdown/_pkgdown.yml Adds Offcanvas reference section to pkgdown site.
man/offcanvas.Rd Generated documentation for offcanvas().
man/show_offcanvas.Rd Generated documentation for server verbs.
NEWS.md Adds a NEWS entry for the new feature.
NAMESPACE Exports new functions and S3 methods.
DESCRIPTION Adds offcanvas.R to collate list.
man/navbar_options.Rd Minor formatting tweak in docs.
inst/examples-shiny/code-editor/examples/r.r Fixes assignment operator in an example snippet.
Files not reviewed (2)
  • man/offcanvas.Rd: Generated file
  • man/show_offcanvas.Rd: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread R/offcanvas.R
Comment thread R/offcanvas.R
Comment thread srcts/src/components/webcomponents/offcanvas.ts
Comment thread pkgdown/_pkgdown.yml
Comment thread NEWS.md

@cpsievert cpsievert left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks! Copilot left some nitpicky feedback, but the overall API here looks great.

gadenbuie and others added 3 commits July 1, 2026 11:31
Demonstrates the local-id contract (ns() only in the UI; server verbs
and input[[id]] use the module-local id) with a slider, verbatim
output, and self-closing button inside the panel.
@gadenbuie gadenbuie merged commit f9536ec into main Jul 1, 2026
12 checks passed
@gadenbuie gadenbuie deleted the feat/offcanvas branch July 1, 2026 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Offcanvas Element

3 participants