From 374f75795e062b61d97b10ac67608fa820b7ed5f Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Mon, 3 Nov 2025 11:02:45 -0500 Subject: [PATCH 01/41] Initial toolbar container --- DESCRIPTION | 1 + NAMESPACE | 1 + R/toolbar.R | 32 ++++++++++++++++++++ inst/components/scss/toolbar.scss | 50 +++++++++++++++++++++++++++++++ man/toolbar.Rd | 21 +++++++++++++ 5 files changed, 105 insertions(+) create mode 100644 R/toolbar.R create mode 100644 inst/components/scss/toolbar.scss create mode 100644 man/toolbar.Rd diff --git a/DESCRIPTION b/DESCRIPTION index c20469d01..e755ac721 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -115,6 +115,7 @@ Collate: 'shiny-devmode.R' 'sidebar.R' 'staticimports.R' + 'toolbar.R' 'tooltip.R' 'utils-deps.R' 'utils-shiny.R' diff --git a/NAMESPACE b/NAMESPACE index 27d4445aa..4d613fd84 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -152,6 +152,7 @@ export(toggle_popover) export(toggle_sidebar) export(toggle_switch) export(toggle_tooltip) +export(toolbar) export(tooltip) export(update_popover) export(update_submit_textarea) diff --git a/R/toolbar.R b/R/toolbar.R new file mode 100644 index 000000000..3fabf5cfb --- /dev/null +++ b/R/toolbar.R @@ -0,0 +1,32 @@ +#' Add a toolbar to a UI element +#' +#' @description +#' Display additional information when focusing (or hovering over) a UI element. +#' +#' @param ... UI elements for the toolbar. +#' @param align A character string. +#' @param size STUFF HERE +#' +#' @return Returns a toolbar container. +#' +#' @export +toolbar <- function( + ..., + align = c("right", "left"), + size = c("sm", "md", "lg") +) { + align <- rlang::arg_match(align) + size <- rlang::arg_match(size) + + tag <- htmltools::div( + class = "bslib-toolbar", + "data-align" = align, + "data-size" = size, + rlang::list2(...), + component_dependencies() + ) + + as_fragment( + tag_require(tag, version = 5, caller = "toolbar()") + ) +} diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss new file mode 100644 index 000000000..8b162659e --- /dev/null +++ b/inst/components/scss/toolbar.scss @@ -0,0 +1,50 @@ +/* Toolbar */ +.bslib-toolbar { + display: flex; + align-items: center; + gap: 0.5rem; + + /* Toolbar options */ + + &[data-align="left"] { + margin-right: auto; + align-items: flex-start; + } + + &[data-align="right"] { + margin-left: auto; + align-items: flex-end; + } + + &[data-size="sm"] { + font-size: 0.8rem; + } + + &[data-size="md"] { + font-size: 1rem; + } + + &[data-size="lg"] { + font-size: 1.25rem; + } + + /* Adjustments to other elements */ + + &, + &>* { + margin-bottom: 0 !important; + width: auto; + } + + >.form-group.shiny-input-container, .form-group.shiny-input-container>* { + width: auto; + margin-bottom: 0; + min-width: 0; + } + + /* Ensure card headers with toolbars use flexbox for alignment */ + .card-header:has(> &) { + display: flex; + align-items: center; + } +} \ No newline at end of file diff --git a/man/toolbar.Rd b/man/toolbar.Rd new file mode 100644 index 000000000..9c6fb45b8 --- /dev/null +++ b/man/toolbar.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/toolbar.R +\name{toolbar} +\alias{toolbar} +\title{Add a toolbar to a UI element} +\usage{ +toolbar(..., align = c("right", "left"), size = c("sm", "md", "lg")) +} +\arguments{ +\item{...}{UI elements for the toolbar.} + +\item{align}{A character string.} + +\item{size}{STUFF HERE} +} +\value{ +Returns a toolbar container. +} +\description{ +Display additional information when focusing (or hovering over) a UI element. +} From ed1a07e8ce820adc858149862cecc90d996eb326 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Mon, 3 Nov 2025 11:15:13 -0500 Subject: [PATCH 02/41] Updates --- R/toolbar.R | 11 +++++++---- man/toolbar.Rd | 11 +++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/R/toolbar.R b/R/toolbar.R index 3fabf5cfb..5f8891b54 100644 --- a/R/toolbar.R +++ b/R/toolbar.R @@ -1,13 +1,16 @@ #' Add a toolbar to a UI element #' #' @description -#' Display additional information when focusing (or hovering over) a UI element. +#' A toolbar which can contain buttons, inputs, and other UI elements +#' in a small form suitable for inclusion in card headers, +#' footers, and other small places. #' #' @param ... UI elements for the toolbar. -#' @param align A character string. -#' @param size STUFF HERE +#' @param align Determines if toolbar should be aligned to the right or left. +#' Must be one of "right" or "left". +#' @param size The size of the toolbar. Must be one of "sm", "md", or "lg". #' -#' @return Returns a toolbar container. +#' @return Returns a toolbar. #' #' @export toolbar <- function( diff --git a/man/toolbar.Rd b/man/toolbar.Rd index 9c6fb45b8..ffdc940c3 100644 --- a/man/toolbar.Rd +++ b/man/toolbar.Rd @@ -9,13 +9,16 @@ toolbar(..., align = c("right", "left"), size = c("sm", "md", "lg")) \arguments{ \item{...}{UI elements for the toolbar.} -\item{align}{A character string.} +\item{align}{Determines if toolbar should be aligned to the right or left. +Must be one of "right" or "left".} -\item{size}{STUFF HERE} +\item{size}{The size of the toolbar. Must be one of "sm", "md", or "lg".} } \value{ -Returns a toolbar container. +Returns a toolbar. } \description{ -Display additional information when focusing (or hovering over) a UI element. +A toolbar which can contain buttons, inputs, and other UI elements +in a small form suitable for inclusion in card headers, +footers, and other small places. } From 04bd68b6cd2e748a26014976d8a69f6f1319df99 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Mon, 3 Nov 2025 11:31:31 -0500 Subject: [PATCH 03/41] Minor updates --- R/toolbar.R | 6 +++--- inst/components/scss/toolbar.scss | 12 +++++++++++ inst/examples-shiny/testapp.R | 35 +++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 inst/examples-shiny/testapp.R diff --git a/R/toolbar.R b/R/toolbar.R index 5f8891b54..340721788 100644 --- a/R/toolbar.R +++ b/R/toolbar.R @@ -18,6 +18,7 @@ toolbar <- function( align = c("right", "left"), size = c("sm", "md", "lg") ) { + dots <- separate_arguments(...) align <- rlang::arg_match(align) size <- rlang::arg_match(size) @@ -29,7 +30,6 @@ toolbar <- function( component_dependencies() ) - as_fragment( - tag_require(tag, version = 5, caller = "toolbar()") - ) + tag_require(tag, version = 5, caller = "toolbar()") + as_fragment(tag) } diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index 8b162659e..4336218f6 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -18,14 +18,26 @@ &[data-size="sm"] { font-size: 0.8rem; + + & > * { + font-size: 0.8rem; + } } &[data-size="md"] { font-size: 1rem; + + & > * { + font-size: 1rem; + } } &[data-size="lg"] { font-size: 1.25rem; + + & > * { + font-size: 1.25rem; + } } /* Adjustments to other elements */ diff --git a/inst/examples-shiny/testapp.R b/inst/examples-shiny/testapp.R new file mode 100644 index 000000000..b9dc0b553 --- /dev/null +++ b/inst/examples-shiny/testapp.R @@ -0,0 +1,35 @@ +library(shiny) +library(bslib) + +ui <- page_fillable( + tags$head( + tags$link(rel = "stylesheet", type = "text/css", href = "styles.css") + ), + card( + card_header( + "Card 1 header", + toolbar( + align = "right", + size = "sm", + input_switch("check", "Pick me!"), + ) + ), + p("Card 1 body"), + sliderInput("slider", "Slider", 0, 10, 5), + max_height = "500px", + card_footer() + ), + toolbar( + align = "left", + actionButton("test", NULL, icon = icon("calendar")) + ) +) + +server <- function(input, output) { + observe({ + print(input$bgc) + }) +} + + +shinyApp(ui = ui, server = server) From 8f6d6f11d7788fd08d15d68703cebea20e3f57be Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Mon, 3 Nov 2025 16:09:43 -0500 Subject: [PATCH 04/41] Added tests --- tests/testthat/test-toolbar.R | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/testthat/test-toolbar.R diff --git a/tests/testthat/test-toolbar.R b/tests/testthat/test-toolbar.R new file mode 100644 index 000000000..d1b433722 --- /dev/null +++ b/tests/testthat/test-toolbar.R @@ -0,0 +1,20 @@ +test_that("toolbar() basic attributes and defaults", { + tb <- as.tags(toolbar(htmltools::span("Test"))) + + expect_match(htmltools::tagGetAttribute(tb, "class"), "bslib-toolbar") + + expect_match(htmltools::tagGetAttribute(tb, "data-align"), "right") + expect_match(htmltools::tagGetAttribute(tb, "data-size"), "sm") +}) + +test_that("toolbar() assigns correct attributes", { + tb <- as.tags(toolbar(align = "left", size = "md")) + + expect_equal(htmltools::tagGetAttribute(tb, "data-align"), "left") + expect_equal(htmltools::tagGetAttribute(tb, "data-size"), "md") +}) + +test_that("toolbar() validation of inputs", { + expect_error(toolbar("x", align = "center")) + expect_error(toolbar("x", size = "xl")) +}) From 63893071ad731410f75d38623ed1abf2014d6b9c Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Mon, 3 Nov 2025 16:29:12 -0500 Subject: [PATCH 05/41] Updated news --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index b9d05c483..ed19fc40a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,8 @@ * Added a new `input_submit_textarea()` input element, which is similar to `shiny::textAreaInput()`, but includes a submit button to only submit the text changes to the server on click. This is especially useful when the input text change triggers a long-running operation and/or the user wants to type longer-form input and review it before submitting it. +* Added a new `toolbar()` component for creating Bootstrap toolbars that can contain buttons, text, and other elements. (#1247) + ## Improvements and bug fixes * `bs_theme_dependencies()` now avoids unnecessarily copying internal package files to R's temporary directory more than once when preparing precompiled theme dependencies (e.g. for a standard `bs_theme()` theme). (#1184) From c7d70e694d20c2385e07033f2377897357f535ab Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Mon, 3 Nov 2025 16:49:01 -0500 Subject: [PATCH 06/41] Removing example: --- inst/components/scss/toolbar.scss | 4 ++++ inst/examples-shiny/testapp.R | 35 ------------------------------- 2 files changed, 4 insertions(+), 35 deletions(-) delete mode 100644 inst/examples-shiny/testapp.R diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index 4336218f6..a900f6822 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -59,4 +59,8 @@ display: flex; align-items: center; } + .card-footer:has(> &) { + display: flex; + align-items: center; + } } \ No newline at end of file diff --git a/inst/examples-shiny/testapp.R b/inst/examples-shiny/testapp.R deleted file mode 100644 index b9dc0b553..000000000 --- a/inst/examples-shiny/testapp.R +++ /dev/null @@ -1,35 +0,0 @@ -library(shiny) -library(bslib) - -ui <- page_fillable( - tags$head( - tags$link(rel = "stylesheet", type = "text/css", href = "styles.css") - ), - card( - card_header( - "Card 1 header", - toolbar( - align = "right", - size = "sm", - input_switch("check", "Pick me!"), - ) - ), - p("Card 1 body"), - sliderInput("slider", "Slider", 0, 10, 5), - max_height = "500px", - card_footer() - ), - toolbar( - align = "left", - actionButton("test", NULL, icon = icon("calendar")) - ) -) - -server <- function(input, output) { - observe({ - print(input$bgc) - }) -} - - -shinyApp(ui = ui, server = server) From 3f8b63016185e9a985fc7af670badee693656744 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Tue, 4 Nov 2025 22:40:27 -0500 Subject: [PATCH 07/41] Adding snaps --- tests/testthat/_snaps/toolbar.md | 69 ++++++++++++++++++++++++++++++++ tests/testthat/test-toolbar.R | 66 ++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 tests/testthat/_snaps/toolbar.md diff --git a/tests/testthat/_snaps/toolbar.md b/tests/testthat/_snaps/toolbar.md new file mode 100644 index 000000000..a15c93bf0 --- /dev/null +++ b/tests/testthat/_snaps/toolbar.md @@ -0,0 +1,69 @@ +# toolbar() markup snapshots + + Code + show_raw_html(toolbar("Item 1", "Item 2")) + Output +
+ Item 1 + Item 2 +
+ +--- + + Code + show_raw_html(toolbar(shiny::actionButton("btn1", "Button 1"), align = "left")) + Output +
+ +
+ +--- + + Code + show_raw_html(toolbar(size = "md")) + Output +
+ +--- + + Code + show_raw_html(card(card_header("Card Title", toolbar(tags$button("Settings"), + align = "right", size = "sm")), card_body("Card content"))) + Output +
+
+ Card Title +
+ +
+
+
Card content
+ +
+ +--- + + Code + show_raw_html(toolbar(shiny::selectInput("select", NULL, choices = c("A", "B", + "C"), multiple = FALSE, selectize = FALSE), shiny::checkboxInput("check", + "Check"), align = "right")) + Output +
+
+ +
+ +
+
+
+
+ +
+
+
+ diff --git a/tests/testthat/test-toolbar.R b/tests/testthat/test-toolbar.R index d1b433722..75000e1f9 100644 --- a/tests/testthat/test-toolbar.R +++ b/tests/testthat/test-toolbar.R @@ -18,3 +18,69 @@ test_that("toolbar() validation of inputs", { expect_error(toolbar("x", align = "center")) expect_error(toolbar("x", size = "xl")) }) + +test_that("toolbar() markup snapshots", { + show_raw_html <- function(x) { + cat(format(x)) + } + + # Basic toolbar + expect_snapshot( + show_raw_html( + toolbar("Item 1", "Item 2") + ) + ) + + # Toolbar with alignment options + expect_snapshot( + show_raw_html( + toolbar( + shiny::actionButton("btn1", "Button 1"), + align = "left" + ) + ) + ) + + # Toolbar with size options + expect_snapshot( + show_raw_html( + toolbar( + size = "md" + ) + ) + ) + + # Toolbar in card header + expect_snapshot( + show_raw_html( + card( + card_header( + "Card Title", + toolbar( + tags$button("Settings"), + align = "right", + size = "sm" + ) + ), + card_body("Card content") + ) + ) + ) + + # Toolbar with Shiny inputs + expect_snapshot( + show_raw_html( + toolbar( + shiny::selectInput( + "select", + NULL, + choices = c("A", "B", "C"), + multiple = FALSE, + selectize = FALSE + ), + shiny::checkboxInput("check", "Check"), + align = "right" + ) + ) + ) +}) From 1abed04d5644f63edfeeadf8670feb39d05dd532 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Tue, 4 Nov 2025 22:57:08 -0500 Subject: [PATCH 08/41] Adding a few comments --- inst/components/scss/toolbar.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index a900f6822..1066fe08a 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -48,6 +48,7 @@ width: auto; } + /* Ensures that inputs inherit the formatting of the toolbar and align correctly */ >.form-group.shiny-input-container, .form-group.shiny-input-container>* { width: auto; margin-bottom: 0; From 5fd92549bcf8cce905dcd430cdba125b65850fbb Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Wed, 5 Nov 2025 13:31:58 -0500 Subject: [PATCH 09/41] Update to toolbar testing --- tests/testthat/_snaps/toolbar.md | 53 +++++--------------------------- tests/testthat/test-toolbar.R | 39 +++-------------------- 2 files changed, 11 insertions(+), 81 deletions(-) diff --git a/tests/testthat/_snaps/toolbar.md b/tests/testthat/_snaps/toolbar.md index a15c93bf0..e1362b59d 100644 --- a/tests/testthat/_snaps/toolbar.md +++ b/tests/testthat/_snaps/toolbar.md @@ -11,59 +11,20 @@ --- Code - show_raw_html(toolbar(shiny::actionButton("btn1", "Button 1"), align = "left")) + show_raw_html(toolbar("Item 1", "Item 2", align = "left")) Output
- -
- ---- - - Code - show_raw_html(toolbar(size = "md")) - Output -
- ---- - - Code - show_raw_html(card(card_header("Card Title", toolbar(tags$button("Settings"), - align = "right", size = "sm")), card_body("Card content"))) - Output -
-
- Card Title -
- -
-
-
Card content
- + Item 1 + Item 2
--- Code - show_raw_html(toolbar(shiny::selectInput("select", NULL, choices = c("A", "B", - "C"), multiple = FALSE, selectize = FALSE), shiny::checkboxInput("check", - "Check"), align = "right")) + show_raw_html(toolbar("Item 1", "Item 2", size = "md")) Output -
-
- -
- -
-
-
-
- -
-
+
+ Item 1 + Item 2
diff --git a/tests/testthat/test-toolbar.R b/tests/testthat/test-toolbar.R index 75000e1f9..1085b1bfe 100644 --- a/tests/testthat/test-toolbar.R +++ b/tests/testthat/test-toolbar.R @@ -35,7 +35,8 @@ test_that("toolbar() markup snapshots", { expect_snapshot( show_raw_html( toolbar( - shiny::actionButton("btn1", "Button 1"), + "Item 1", + "Item 2", align = "left" ) ) @@ -45,42 +46,10 @@ test_that("toolbar() markup snapshots", { expect_snapshot( show_raw_html( toolbar( + "Item 1", + "Item 2", size = "md" ) ) ) - - # Toolbar in card header - expect_snapshot( - show_raw_html( - card( - card_header( - "Card Title", - toolbar( - tags$button("Settings"), - align = "right", - size = "sm" - ) - ), - card_body("Card content") - ) - ) - ) - - # Toolbar with Shiny inputs - expect_snapshot( - show_raw_html( - toolbar( - shiny::selectInput( - "select", - NULL, - choices = c("A", "B", "C"), - multiple = FALSE, - selectize = FALSE - ), - shiny::checkboxInput("check", "Check"), - align = "right" - ) - ) - ) }) From c2289a38fe1b97f076f39cd63a65b8114ec7f0b0 Mon Sep 17 00:00:00 2001 From: E Nelson Date: Wed, 5 Nov 2025 13:52:14 -0500 Subject: [PATCH 10/41] Fix dots in toolbar function Co-authored-by: Garrick Aden-Buie --- R/toolbar.R | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/R/toolbar.R b/R/toolbar.R index 340721788..531da5ebe 100644 --- a/R/toolbar.R +++ b/R/toolbar.R @@ -18,15 +18,14 @@ toolbar <- function( align = c("right", "left"), size = c("sm", "md", "lg") ) { - dots <- separate_arguments(...) align <- rlang::arg_match(align) size <- rlang::arg_match(size) - tag <- htmltools::div( + tag <- div( class = "bslib-toolbar", "data-align" = align, "data-size" = size, - rlang::list2(...), + ..., component_dependencies() ) From b23fc17683eb9ac3602ddea5bdaef1e54b9d79e0 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Wed, 5 Nov 2025 13:52:58 -0500 Subject: [PATCH 11/41] updates to dots --- R/toolbar.R | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/R/toolbar.R b/R/toolbar.R index 340721788..531da5ebe 100644 --- a/R/toolbar.R +++ b/R/toolbar.R @@ -18,15 +18,14 @@ toolbar <- function( align = c("right", "left"), size = c("sm", "md", "lg") ) { - dots <- separate_arguments(...) align <- rlang::arg_match(align) size <- rlang::arg_match(size) - tag <- htmltools::div( + tag <- div( class = "bslib-toolbar", "data-align" = align, "data-size" = size, - rlang::list2(...), + ..., component_dependencies() ) From 08363745653325c272bc4199af3f1030fc65696f Mon Sep 17 00:00:00 2001 From: E Nelson Date: Wed, 5 Nov 2025 17:10:27 -0500 Subject: [PATCH 12/41] Update R/toolbar.R Co-authored-by: Garrick Aden-Buie --- R/toolbar.R | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/R/toolbar.R b/R/toolbar.R index 531da5ebe..8d0e4616f 100644 --- a/R/toolbar.R +++ b/R/toolbar.R @@ -1,16 +1,16 @@ -#' Add a toolbar to a UI element +#' Toolbar component #' #' @description -#' A toolbar which can contain buttons, inputs, and other UI elements -#' in a small form suitable for inclusion in card headers, -#' footers, and other small places. +#' A toolbar which can contain buttons, inputs, and other UI elements in a small +#' form suitable for inclusion in card headers, footers, and other small places. #' #' @param ... UI elements for the toolbar. -#' @param align Determines if toolbar should be aligned to the right or left. -#' Must be one of "right" or "left". -#' @param size The size of the toolbar. Must be one of "sm", "md", or "lg". +#' @param align Determines if toolbar should be aligned to the `"right"` or +#' `"left"`. +#' @param size The size of the toolbar. Must be one of `"sm"`, `"md"`, or +#' `"lg"`. #' -#' @return Returns a toolbar. +#' @return Returns a toolbar element. #' #' @export toolbar <- function( From a55d03fdfa3a246a013dea3f9eab62ec10ec088c Mon Sep 17 00:00:00 2001 From: E Nelson Date: Wed, 5 Nov 2025 17:10:34 -0500 Subject: [PATCH 13/41] Update inst/components/scss/toolbar.scss Co-authored-by: Garrick Aden-Buie --- inst/components/scss/toolbar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index 1066fe08a..bc07e6ea2 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -55,7 +55,7 @@ min-width: 0; } - /* Ensure card headers with toolbars use flexbox for alignment */ + // Ensure card headers with toolbars use flexbox for alignment .card-header:has(> &) { display: flex; align-items: center; From 0afabf4b0801515501af7a03998bf177bec43095 Mon Sep 17 00:00:00 2001 From: E Nelson Date: Wed, 5 Nov 2025 17:15:21 -0500 Subject: [PATCH 14/41] Update inst/components/scss/toolbar.scss Co-authored-by: Garrick Aden-Buie --- inst/components/scss/toolbar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index bc07e6ea2..b6d00b701 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -40,7 +40,7 @@ } } - /* Adjustments to other elements */ + /* ---- Adjustments to other elements ---- */ &, &>* { From e6e9d88498b52a295a180538a35b799c2152209d Mon Sep 17 00:00:00 2001 From: E Nelson Date: Wed, 5 Nov 2025 17:15:28 -0500 Subject: [PATCH 15/41] Update inst/components/scss/toolbar.scss Co-authored-by: Garrick Aden-Buie --- inst/components/scss/toolbar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index b6d00b701..fbe92cedc 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -4,7 +4,7 @@ align-items: center; gap: 0.5rem; - /* Toolbar options */ + /* ---- Toolbar options ---- */ &[data-align="left"] { margin-right: auto; From 626af55c60634519d2f0064579ef3d9453845e3f Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Wed, 12 Nov 2025 09:09:44 -0500 Subject: [PATCH 16/41] Adding input buttons --- NAMESPACE | 1 + NEWS.md | 1 + R/toolbar.R | 39 +++++++++++++++++++ tests/testthat/_snaps/toolbar.md | 34 +++++++++++++++++ tests/testthat/test-toolbar.R | 64 ++++++++++++++++++++++++++++++++ 5 files changed, 139 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 4d613fd84..a868d82b9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -153,6 +153,7 @@ export(toggle_sidebar) export(toggle_switch) export(toggle_tooltip) export(toolbar) +export(toolbar_input_button) export(tooltip) export(update_popover) export(update_submit_textarea) diff --git a/NEWS.md b/NEWS.md index ed19fc40a..5b60447bc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,7 @@ * Added a new `input_submit_textarea()` input element, which is similar to `shiny::textAreaInput()`, but includes a submit button to only submit the text changes to the server on click. This is especially useful when the input text change triggers a long-running operation and/or the user wants to type longer-form input and review it before submitting it. * Added a new `toolbar()` component for creating Bootstrap toolbars that can contain buttons, text, and other elements. (#1247) + * Added `toolbar_input_button()` for easily creating buttons to include in a `toolbar()`. (#1248) ## Improvements and bug fixes diff --git a/R/toolbar.R b/R/toolbar.R index 531da5ebe..0159ac969 100644 --- a/R/toolbar.R +++ b/R/toolbar.R @@ -32,3 +32,42 @@ toolbar <- function( tag_require(tag, version = 5, caller = "toolbar()") as_fragment(tag) } + +#' Add toolbar button input +#' +#' @description +#' A button designed to fit well in small places such as toolbars. +#' +#' @param ... UI elements for the button. +#' @param icon An icon to display in the button. +#' (One of icon or label must be supplied.) +#' @param label The label to display in the button. +#' (One of icon or label must be supplied.) +#' @param border Whether to show a border around the button. +#' +#' @return Returns a button suitable for use in a toolbar. +#' +#' @export +toolbar_input_button <- function( + id, + ..., + icon = NULL, + label = NULL, + border = FALSE +) { + if (is.null(icon) && is.null(label)) { + stop( + "At least one of 'icon' or 'label' must be provided.", + call. = TRUE + ) + } + + shiny::actionButton( + id, + label = label, + icon = icon, + class = "bslib-toolbar-input-button btn-sm", + class = if (!border) "border-0" else NULL, + ... + ) +} diff --git a/tests/testthat/_snaps/toolbar.md b/tests/testthat/_snaps/toolbar.md index e1362b59d..685b6582e 100644 --- a/tests/testthat/_snaps/toolbar.md +++ b/tests/testthat/_snaps/toolbar.md @@ -28,3 +28,37 @@ Item 2
+# toolbar_input_button() markup snapshots + + Code + show_raw_html(toolbar_input_button(id = "btn1", label = "Click me")) + Output + + +--- + + Code + show_raw_html(toolbar_input_button(id = "btn2", icon = shiny::icon("star"))) + Output + + +--- + + Code + show_raw_html(toolbar_input_button(id = "btn3", label = "Save", icon = shiny::icon( + "save"))) + Output + + +--- + + Code + show_raw_html(toolbar_input_button(id = "btn4", label = "Delete", class = "btn-danger")) + Output + + diff --git a/tests/testthat/test-toolbar.R b/tests/testthat/test-toolbar.R index 1085b1bfe..621454d07 100644 --- a/tests/testthat/test-toolbar.R +++ b/tests/testthat/test-toolbar.R @@ -53,3 +53,67 @@ test_that("toolbar() markup snapshots", { ) ) }) + +test_that("toolbar_input_button() creates an actionButton", { + btn <- toolbar_input_button(id = "test_btn", label = "Click me") + + expect_equal(btn$name, "button") + expect_match(htmltools::tagGetAttribute(btn, "class"), "btn") + expect_match(htmltools::tagGetAttribute(btn, "class"), "action-button") + expect_match(htmltools::tagGetAttribute(btn, "class"), "btn-sm") + expect_equal(htmltools::tagGetAttribute(btn, "id"), "test_btn") +}) + +test_that("toolbar_input_button() passes additional arguments", { + btn <- toolbar_input_button( + id = "custom_btn", + label = "Custom", + class = "custom-class", + custom = "custom-value" + ) + + expect_match(htmltools::tagGetAttribute(btn, "class"), "custom-class") + expect_equal(htmltools::tagGetAttribute(btn, "custom"), "custom-value") +}) + +test_that("toolbar_input_button() markup snapshots", { + show_raw_html <- function(x) { + cat(format(x)) + } + + # Button with label only + expect_snapshot( + show_raw_html( + toolbar_input_button(id = "btn1", label = "Click me") + ) + ) + + # Button with icon only + expect_snapshot( + show_raw_html( + toolbar_input_button(id = "btn2", icon = shiny::icon("star")) + ) + ) + + # Button with label and icon + expect_snapshot( + show_raw_html( + toolbar_input_button( + id = "btn3", + label = "Save", + icon = shiny::icon("save") + ) + ) + ) + + # Button with custom class + expect_snapshot( + show_raw_html( + toolbar_input_button( + id = "btn4", + label = "Delete", + class = "btn-danger" + ) + ) + ) +}) From 180319aada75c762addea9fff252ba1655f37b5f Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Wed, 12 Nov 2025 09:48:10 -0500 Subject: [PATCH 17/41] Added disabled option --- R/toolbar.R | 6 +++++- tests/testthat/test-toolbar.R | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/R/toolbar.R b/R/toolbar.R index 0159ac969..d122cfd9a 100644 --- a/R/toolbar.R +++ b/R/toolbar.R @@ -38,12 +38,14 @@ toolbar <- function( #' @description #' A button designed to fit well in small places such as toolbars. #' +#' @param id The `input` slot that will be used to access the value. #' @param ... UI elements for the button. #' @param icon An icon to display in the button. #' (One of icon or label must be supplied.) #' @param label The label to display in the button. #' (One of icon or label must be supplied.) #' @param border Whether to show a border around the button. +#' @param disabled If `TRUE`, the button will not be clickable. Use `updateActionButton()` to dynamically enable/disable the button. #' #' @return Returns a button suitable for use in a toolbar. #' @@ -53,7 +55,8 @@ toolbar_input_button <- function( ..., icon = NULL, label = NULL, - border = FALSE + border = FALSE, + disabled = FALSE ) { if (is.null(icon) && is.null(label)) { stop( @@ -66,6 +69,7 @@ toolbar_input_button <- function( id, label = label, icon = icon, + disabled = disabled, class = "bslib-toolbar-input-button btn-sm", class = if (!border) "border-0" else NULL, ... diff --git a/tests/testthat/test-toolbar.R b/tests/testthat/test-toolbar.R index 621454d07..43e8ccea1 100644 --- a/tests/testthat/test-toolbar.R +++ b/tests/testthat/test-toolbar.R @@ -76,6 +76,24 @@ test_that("toolbar_input_button() passes additional arguments", { expect_equal(htmltools::tagGetAttribute(btn, "custom"), "custom-value") }) +test_that("toolbar_input_button() disabled argument", { + btn_disabled <- toolbar_input_button( + id = "disabled_btn", + label = "Disabled", + disabled = TRUE + ) + + expect_true(htmltools::tagHasAttribute(btn_disabled, "disabled")) + + btn_enabled <- toolbar_input_button( + id = "enabled_btn", + label = "Enabled", + disabled = FALSE + ) + + expect_false(htmltools::tagHasAttribute(btn_enabled, "disabled")) +}) + test_that("toolbar_input_button() markup snapshots", { show_raw_html <- function(x) { cat(format(x)) From f8986706c673175d8e9a8b9d8859cdc6a528e5c3 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Thu, 13 Nov 2025 21:59:50 -0500 Subject: [PATCH 18/41] Updated code --- R/toolbar.R | 10 ++---- inst/components/scss/toolbar.scss | 51 ++++++------------------------- tests/testthat/_snaps/toolbar.md | 14 ++------- tests/testthat/test-toolbar.R | 16 +--------- 4 files changed, 14 insertions(+), 77 deletions(-) diff --git a/R/toolbar.R b/R/toolbar.R index 8d0e4616f..f100b5951 100644 --- a/R/toolbar.R +++ b/R/toolbar.R @@ -7,24 +7,18 @@ #' @param ... UI elements for the toolbar. #' @param align Determines if toolbar should be aligned to the `"right"` or #' `"left"`. -#' @param size The size of the toolbar. Must be one of `"sm"`, `"md"`, or -#' `"lg"`. -#' #' @return Returns a toolbar element. #' #' @export toolbar <- function( ..., - align = c("right", "left"), - size = c("sm", "md", "lg") + align = c("right", "left") ) { align <- rlang::arg_match(align) - size <- rlang::arg_match(size) tag <- div( - class = "bslib-toolbar", + class = "bslib-toolbar bslib-gap-spacing", "data-align" = align, - "data-size" = size, ..., component_dependencies() ) diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index fbe92cedc..c680ba5a8 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -2,66 +2,33 @@ .bslib-toolbar { display: flex; align-items: center; - gap: 0.5rem; /* ---- Toolbar options ---- */ &[data-align="left"] { margin-right: auto; - align-items: flex-start; } &[data-align="right"] { margin-left: auto; - align-items: flex-end; - } - - &[data-size="sm"] { - font-size: 0.8rem; - - & > * { - font-size: 0.8rem; - } - } - - &[data-size="md"] { - font-size: 1rem; - - & > * { - font-size: 1rem; - } - } - - &[data-size="lg"] { - font-size: 1.25rem; - - & > * { - font-size: 1.25rem; - } } /* ---- Adjustments to other elements ---- */ + // Ensures uniformity of font sizing in elements and sub-elements (e.g., input select) &, - &>* { - margin-bottom: 0 !important; - width: auto; + & * { + font-size: 0.8rem; } - /* Ensures that inputs inherit the formatting of the toolbar and align correctly */ - >.form-group.shiny-input-container, .form-group.shiny-input-container>* { + & > * { + margin-bottom: 0 !important; width: auto; - margin-bottom: 0; - min-width: 0; + align-self: center; } - // Ensure card headers with toolbars use flexbox for alignment - .card-header:has(> &) { - display: flex; - align-items: center; - } - .card-footer:has(> &) { - display: flex; - align-items: center; + // Ensures that inputs inherit the formatting of the toolbar and align correctly (e.g. input select) + > .form-group { + width: auto; } } \ No newline at end of file diff --git a/tests/testthat/_snaps/toolbar.md b/tests/testthat/_snaps/toolbar.md index e1362b59d..2d6b9e445 100644 --- a/tests/testthat/_snaps/toolbar.md +++ b/tests/testthat/_snaps/toolbar.md @@ -3,7 +3,7 @@ Code show_raw_html(toolbar("Item 1", "Item 2")) Output -
+
Item 1 Item 2
@@ -13,17 +13,7 @@ Code show_raw_html(toolbar("Item 1", "Item 2", align = "left")) Output -
- Item 1 - Item 2 -
- ---- - - Code - show_raw_html(toolbar("Item 1", "Item 2", size = "md")) - Output -
+
Item 1 Item 2
diff --git a/tests/testthat/test-toolbar.R b/tests/testthat/test-toolbar.R index 1085b1bfe..2e370ecb0 100644 --- a/tests/testthat/test-toolbar.R +++ b/tests/testthat/test-toolbar.R @@ -4,19 +4,16 @@ test_that("toolbar() basic attributes and defaults", { expect_match(htmltools::tagGetAttribute(tb, "class"), "bslib-toolbar") expect_match(htmltools::tagGetAttribute(tb, "data-align"), "right") - expect_match(htmltools::tagGetAttribute(tb, "data-size"), "sm") }) test_that("toolbar() assigns correct attributes", { - tb <- as.tags(toolbar(align = "left", size = "md")) + tb <- as.tags(toolbar(align = "left")) expect_equal(htmltools::tagGetAttribute(tb, "data-align"), "left") - expect_equal(htmltools::tagGetAttribute(tb, "data-size"), "md") }) test_that("toolbar() validation of inputs", { expect_error(toolbar("x", align = "center")) - expect_error(toolbar("x", size = "xl")) }) test_that("toolbar() markup snapshots", { @@ -41,15 +38,4 @@ test_that("toolbar() markup snapshots", { ) ) ) - - # Toolbar with size options - expect_snapshot( - show_raw_html( - toolbar( - "Item 1", - "Item 2", - size = "md" - ) - ) - ) }) From 899b644597db8e40bc66861e75af81f2c8c829f3 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Fri, 14 Nov 2025 09:08:05 -0500 Subject: [PATCH 19/41] minor formatting changes --- tests/testthat/test-toolbar.R | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/testthat/test-toolbar.R b/tests/testthat/test-toolbar.R index 2e370ecb0..d8d4eac14 100644 --- a/tests/testthat/test-toolbar.R +++ b/tests/testthat/test-toolbar.R @@ -31,11 +31,7 @@ test_that("toolbar() markup snapshots", { # Toolbar with alignment options expect_snapshot( show_raw_html( - toolbar( - "Item 1", - "Item 2", - align = "left" - ) + toolbar("Item 1", "Item 2", align = "left") ) ) }) From 6a3ccfe196b793a1c99581220625a4aaafa1f86d Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Fri, 14 Nov 2025 09:10:31 -0500 Subject: [PATCH 20/41] Adding footer back --- inst/components/scss/toolbar.scss | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index c680ba5a8..de2636f38 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -31,4 +31,11 @@ > .form-group { width: auto; } -} \ No newline at end of file +} + +// Card header is flex by default, but card footer is not and must be in order for +// toolbar alignment to work +.card-footer:has(> &) { + display: flex; + align-items: center; + } From 88f00d8179b2e9cd87ecc45efb01d7f75ce6aac7 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Fri, 14 Nov 2025 09:11:18 -0500 Subject: [PATCH 21/41] Docs changes --- man/toolbar.Rd | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/man/toolbar.Rd b/man/toolbar.Rd index ffdc940c3..6a398d85f 100644 --- a/man/toolbar.Rd +++ b/man/toolbar.Rd @@ -2,23 +2,20 @@ % Please edit documentation in R/toolbar.R \name{toolbar} \alias{toolbar} -\title{Add a toolbar to a UI element} +\title{Toolbar component} \usage{ -toolbar(..., align = c("right", "left"), size = c("sm", "md", "lg")) +toolbar(..., align = c("right", "left")) } \arguments{ \item{...}{UI elements for the toolbar.} -\item{align}{Determines if toolbar should be aligned to the right or left. -Must be one of "right" or "left".} - -\item{size}{The size of the toolbar. Must be one of "sm", "md", or "lg".} +\item{align}{Determines if toolbar should be aligned to the \code{"right"} or +\code{"left"}.} } \value{ -Returns a toolbar. +Returns a toolbar element. } \description{ -A toolbar which can contain buttons, inputs, and other UI elements -in a small form suitable for inclusion in card headers, -footers, and other small places. +A toolbar which can contain buttons, inputs, and other UI elements in a small +form suitable for inclusion in card headers, footers, and other small places. } From df309bfdf64104a6ec95e24ac2b7fdedeca8e055 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Fri, 14 Nov 2025 09:44:38 -0500 Subject: [PATCH 22/41] Updating footer, updating tests --- inst/components/scss/toolbar.scss | 8 ++++---- tests/testthat/_snaps/toolbar.md | 10 ++++++++++ tests/testthat/test-toolbar.R | 7 +++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index de2636f38..259bca3d8 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -31,11 +31,11 @@ > .form-group { width: auto; } -} -// Card header is flex by default, but card footer is not and must be in order for -// toolbar alignment to work -.card-footer:has(> &) { + // Card header is flex by default, but card footer is not and must be in order for + // toolbar alignment to work + .card-footer:has(> &) { display: flex; align-items: center; } +} \ No newline at end of file diff --git a/tests/testthat/_snaps/toolbar.md b/tests/testthat/_snaps/toolbar.md index 2d6b9e445..dbbbc3744 100644 --- a/tests/testthat/_snaps/toolbar.md +++ b/tests/testthat/_snaps/toolbar.md @@ -18,3 +18,13 @@ Item 2
+--- + + Code + show_raw_html(toolbar("Item 1", "Item 2", align = "right")) + Output +
+ Item 1 + Item 2 +
+ diff --git a/tests/testthat/test-toolbar.R b/tests/testthat/test-toolbar.R index d8d4eac14..ee7ef189a 100644 --- a/tests/testthat/test-toolbar.R +++ b/tests/testthat/test-toolbar.R @@ -34,4 +34,11 @@ test_that("toolbar() markup snapshots", { toolbar("Item 1", "Item 2", align = "left") ) ) + + # Toolbar with alignment options + expect_snapshot( + show_raw_html( + toolbar("Item 1", "Item 2", align = "right") + ) + ) }) From ef468d1da8d03d3895b9a012699e9a4b347aff8a Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Fri, 14 Nov 2025 11:02:11 -0500 Subject: [PATCH 23/41] Updating button spacing --- R/toolbar.R | 19 ++++++++-------- man/toolbar_input_button.Rd | 37 ++++++++++++++++++++++++++++++++ tests/testthat/_snaps/toolbar.md | 8 +++---- 3 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 man/toolbar_input_button.Rd diff --git a/R/toolbar.R b/R/toolbar.R index 8a223e4d6..46d9dce93 100644 --- a/R/toolbar.R +++ b/R/toolbar.R @@ -33,24 +33,25 @@ toolbar <- function( #' A button designed to fit well in small places such as toolbars. #' #' @param id The `input` slot that will be used to access the value. -#' @param ... UI elements for the button. -#' @param icon An icon to display in the button. -#' (One of icon or label must be supplied.) #' @param label The label to display in the button. #' (One of icon or label must be supplied.) +#' @param icon An icon to display in the button. +#' (One of icon or label must be supplied.) +#' @param disabled If `TRUE`, the button will not be clickable. +#' Use `updateActionButton()` to dynamically enable/disable the button. #' @param border Whether to show a border around the button. -#' @param disabled If `TRUE`, the button will not be clickable. Use `updateActionButton()` to dynamically enable/disable the button. +#' @param ... UI elements for the button. #' #' @return Returns a button suitable for use in a toolbar. #' #' @export toolbar_input_button <- function( id, - ..., - icon = NULL, label = NULL, + icon = NULL, + disabled = FALSE, border = FALSE, - disabled = FALSE + ... ) { if (is.null(icon) && is.null(label)) { stop( @@ -64,8 +65,8 @@ toolbar_input_button <- function( label = label, icon = icon, disabled = disabled, - class = "bslib-toolbar-input-button btn-sm", - class = if (!border) "border-0" else NULL, + class = "bslib-toolbar-input-button btn-sm p-1", + class = if (!border) "border-0" else "border-1", ... ) } diff --git a/man/toolbar_input_button.Rd b/man/toolbar_input_button.Rd new file mode 100644 index 000000000..b27bf1e10 --- /dev/null +++ b/man/toolbar_input_button.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/toolbar.R +\name{toolbar_input_button} +\alias{toolbar_input_button} +\title{Add toolbar button input} +\usage{ +toolbar_input_button( + id, + label = NULL, + icon = NULL, + disabled = FALSE, + border = FALSE, + ... +) +} +\arguments{ +\item{id}{The \code{input} slot that will be used to access the value.} + +\item{label}{The label to display in the button. +(One of icon or label must be supplied.)} + +\item{icon}{An icon to display in the button. +(One of icon or label must be supplied.)} + +\item{disabled}{If \code{TRUE}, the button will not be clickable. +Use \code{updateActionButton()} to dynamically enable/disable the button.} + +\item{border}{Whether to show a border around the button.} + +\item{...}{UI elements for the button.} +} +\value{ +Returns a button suitable for use in a toolbar. +} +\description{ +A button designed to fit well in small places such as toolbars. +} diff --git a/tests/testthat/_snaps/toolbar.md b/tests/testthat/_snaps/toolbar.md index c26fe9e04..74babfefe 100644 --- a/tests/testthat/_snaps/toolbar.md +++ b/tests/testthat/_snaps/toolbar.md @@ -33,14 +33,14 @@ Code show_raw_html(toolbar_input_button(id = "btn1", label = "Click me")) Output - + --- Code show_raw_html(toolbar_input_button(id = "btn2", icon = shiny::icon("star"))) Output - @@ -50,7 +50,7 @@ show_raw_html(toolbar_input_button(id = "btn3", label = "Save", icon = shiny::icon( "save"))) Output - @@ -60,5 +60,5 @@ Code show_raw_html(toolbar_input_button(id = "btn4", label = "Delete", class = "btn-danger")) Output - + From 78372c10dac9ac673a85901d2daded5420b7b13f Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Fri, 14 Nov 2025 11:04:49 -0500 Subject: [PATCH 24/41] Reformat spacing --- tests/testthat/test-toolbar.R | 172 +++++++++++++++++----------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/tests/testthat/test-toolbar.R b/tests/testthat/test-toolbar.R index c7cbff544..813562f70 100644 --- a/tests/testthat/test-toolbar.R +++ b/tests/testthat/test-toolbar.R @@ -1,126 +1,126 @@ test_that("toolbar() basic attributes and defaults", { - tb <- as.tags(toolbar(htmltools::span("Test"))) + tb <- as.tags(toolbar(htmltools::span("Test"))) - expect_match(htmltools::tagGetAttribute(tb, "class"), "bslib-toolbar") + expect_match(htmltools::tagGetAttribute(tb, "class"), "bslib-toolbar") - expect_match(htmltools::tagGetAttribute(tb, "data-align"), "right") + expect_match(htmltools::tagGetAttribute(tb, "data-align"), "right") }) test_that("toolbar() assigns correct attributes", { - tb <- as.tags(toolbar(align = "left")) + tb <- as.tags(toolbar(align = "left")) - expect_equal(htmltools::tagGetAttribute(tb, "data-align"), "left") + expect_equal(htmltools::tagGetAttribute(tb, "data-align"), "left") }) test_that("toolbar() validation of inputs", { - expect_error(toolbar("x", align = "center")) + expect_error(toolbar("x", align = "center")) }) test_that("toolbar() markup snapshots", { - show_raw_html <- function(x) { - cat(format(x)) - } - - # Basic toolbar - expect_snapshot( - show_raw_html( - toolbar("Item 1", "Item 2") - ) + show_raw_html <- function(x) { + cat(format(x)) + } + + # Basic toolbar + expect_snapshot( + show_raw_html( + toolbar("Item 1", "Item 2") ) + ) - # Toolbar with alignment options - expect_snapshot( - show_raw_html( - toolbar("Item 1", "Item 2", align = "left") - ) + # Toolbar with alignment options + expect_snapshot( + show_raw_html( + toolbar("Item 1", "Item 2", align = "left") ) + ) - # Toolbar with alignment options - expect_snapshot( - show_raw_html( - toolbar("Item 1", "Item 2", align = "right") - ) + # Toolbar with alignment options + expect_snapshot( + show_raw_html( + toolbar("Item 1", "Item 2", align = "right") ) + ) }) test_that("toolbar_input_button() creates an actionButton", { - btn <- toolbar_input_button(id = "test_btn", label = "Click me") + btn <- toolbar_input_button(id = "test_btn", label = "Click me") - expect_equal(btn$name, "button") - expect_match(htmltools::tagGetAttribute(btn, "class"), "btn") - expect_match(htmltools::tagGetAttribute(btn, "class"), "action-button") - expect_match(htmltools::tagGetAttribute(btn, "class"), "btn-sm") - expect_equal(htmltools::tagGetAttribute(btn, "id"), "test_btn") + expect_equal(btn$name, "button") + expect_match(htmltools::tagGetAttribute(btn, "class"), "btn") + expect_match(htmltools::tagGetAttribute(btn, "class"), "action-button") + expect_match(htmltools::tagGetAttribute(btn, "class"), "btn-sm") + expect_equal(htmltools::tagGetAttribute(btn, "id"), "test_btn") }) test_that("toolbar_input_button() passes additional arguments", { - btn <- toolbar_input_button( - id = "custom_btn", - label = "Custom", - class = "custom-class", - custom = "custom-value" - ) - - expect_match(htmltools::tagGetAttribute(btn, "class"), "custom-class") - expect_equal(htmltools::tagGetAttribute(btn, "custom"), "custom-value") + btn <- toolbar_input_button( + id = "custom_btn", + label = "Custom", + class = "custom-class", + custom = "custom-value" + ) + + expect_match(htmltools::tagGetAttribute(btn, "class"), "custom-class") + expect_equal(htmltools::tagGetAttribute(btn, "custom"), "custom-value") }) test_that("toolbar_input_button() disabled argument", { - btn_disabled <- toolbar_input_button( - id = "disabled_btn", - label = "Disabled", - disabled = TRUE - ) + btn_disabled <- toolbar_input_button( + id = "disabled_btn", + label = "Disabled", + disabled = TRUE + ) - expect_true(htmltools::tagHasAttribute(btn_disabled, "disabled")) + expect_true(htmltools::tagHasAttribute(btn_disabled, "disabled")) - btn_enabled <- toolbar_input_button( - id = "enabled_btn", - label = "Enabled", - disabled = FALSE - ) + btn_enabled <- toolbar_input_button( + id = "enabled_btn", + label = "Enabled", + disabled = FALSE + ) - expect_false(htmltools::tagHasAttribute(btn_enabled, "disabled")) + expect_false(htmltools::tagHasAttribute(btn_enabled, "disabled")) }) test_that("toolbar_input_button() markup snapshots", { - show_raw_html <- function(x) { - cat(format(x)) - } - - # Button with label only - expect_snapshot( - show_raw_html( - toolbar_input_button(id = "btn1", label = "Click me") - ) + show_raw_html <- function(x) { + cat(format(x)) + } + + # Button with label only + expect_snapshot( + show_raw_html( + toolbar_input_button(id = "btn1", label = "Click me") ) + ) - # Button with icon only - expect_snapshot( - show_raw_html( - toolbar_input_button(id = "btn2", icon = shiny::icon("star")) - ) + # Button with icon only + expect_snapshot( + show_raw_html( + toolbar_input_button(id = "btn2", icon = shiny::icon("star")) ) - - # Button with label and icon - expect_snapshot( - show_raw_html( - toolbar_input_button( - id = "btn3", - label = "Save", - icon = shiny::icon("save") - ) - ) + ) + + # Button with label and icon + expect_snapshot( + show_raw_html( + toolbar_input_button( + id = "btn3", + label = "Save", + icon = shiny::icon("save") + ) ) - - # Button with custom class - expect_snapshot( - show_raw_html( - toolbar_input_button( - id = "btn4", - label = "Delete", - class = "btn-danger" - ) - ) + ) + + # Button with custom class + expect_snapshot( + show_raw_html( + toolbar_input_button( + id = "btn4", + label = "Delete", + class = "btn-danger" + ) ) + ) }) From 0c0f8958352d811fedacf0145b2d1e87a7563651 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Wed, 19 Nov 2025 12:15:40 -0500 Subject: [PATCH 25/41] Fixed button sizing, changing to also incorperate label+icon --- inst/components/scss/toolbar.scss | 99 ++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 29 deletions(-) diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index 259bca3d8..2c97901c2 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -1,41 +1,82 @@ /* Toolbar */ .bslib-toolbar { - display: flex; - align-items: center; + display: flex; + align-items: center; + gap: 0; + max-height: var(--_size); // Match approximate card header text height so it doesn't expand beyond normal - /* ---- Toolbar options ---- */ + /* ---- Toolbar options ---- */ - &[data-align="left"] { - margin-right: auto; - } + &[data-align="left"] { + margin-right: auto; + } - &[data-align="right"] { - margin-left: auto; - } + &[data-align="right"] { + margin-left: auto; + } - /* ---- Adjustments to other elements ---- */ + /* ---- Toolbar Input Buttons ---- */ - // Ensures uniformity of font sizing in elements and sub-elements (e.g., input select) - &, - & * { - font-size: 0.8rem; - } + // Keep labels and icons centered in the button + .bslib-toolbar-input-button { + align-items: center; + justify-content: center; + line-height: 1; // Override Bootstrap's line-height - & > * { - margin-bottom: 0 !important; - width: auto; - align-self: center; + // Ensure icon is centered + >.fa, + >.bi, + >.glyphicon { + display: flex; + align-items: center; + font-size: .9rem; + justify-content: center; + line-height: 1; + margin: 0; // Remove any default margins } + } - // Ensures that inputs inherit the formatting of the toolbar and align correctly (e.g. input select) - > .form-group { - width: auto; - } - // Card header is flex by default, but card footer is not and must be in order for - // toolbar alignment to work - .card-footer:has(> &) { - display: flex; - align-items: center; + /* ---- Adjustments to other elements ---- */ + + // Ensures uniformity of font sizing in elements and sub-elements (e.g., input select) + // Exclude icon-only button icons from this rule + &, + & *:not(.bslib-toolbar-input-button[data-type="icon"], .bslib-toolbar-input-button[data-type="icon"] *) { + font-size: 0.8rem; + } + + &>* { + margin-bottom: 0 !important; + width: auto; + align-self: center; + } + + // Ensures that inputs inherit the formatting of the toolbar and align correctly (e.g. input select) + >.form-group { + width: auto; + } + + // Square icon-only buttons (must come after general font-size rule) + .bslib-toolbar-input-button[data-type="icon"] { + --_size: 1.5rem; // Match max-height for consistency + height: var(--_size); + aspect-ratio: 1; // Width automatically matches height + padding: 0 !important; // Remove padding for perfect centering, override Bootstrap + line-height: 1 !important; // Ensure no line-height interference + + // Set icon size for all icon types + >.fa, + >.bi, + >.glyphicon { + font-size: 0.9rem; } -} \ No newline at end of file + } + + // Card header is flex by default, but card footer is not and must be in order for + // toolbar alignment to work + .card-footer:has(> &) { + display: flex; + align-items: center; + } +} From 3ce0b207314cdbc03b4c515604ac50cebe86a3a3 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Wed, 19 Nov 2025 12:28:09 -0500 Subject: [PATCH 26/41] Updated card header and font sizing --- R/toolbar.R | 6 ++++- inst/components/scss/card.scss | 39 +++++++++++++++++++++---------- inst/components/scss/toolbar.scss | 33 ++++++++++---------------- 3 files changed, 44 insertions(+), 34 deletions(-) diff --git a/R/toolbar.R b/R/toolbar.R index 46d9dce93..b2baaac2f 100644 --- a/R/toolbar.R +++ b/R/toolbar.R @@ -60,13 +60,17 @@ toolbar_input_button <- function( ) } + # Determine if this is an icon-only button + is_icon_only <- !is.null(icon) && is.null(label) + shiny::actionButton( id, label = label, icon = icon, disabled = disabled, - class = "bslib-toolbar-input-button btn-sm p-1", + class = "bslib-toolbar-input-button btn-sm", class = if (!border) "border-0" else "border-1", + "data-type" = if (is_icon_only) "icon", ... ) } diff --git a/inst/components/scss/card.scss b/inst/components/scss/card.scss index 30833e7e3..8fc3ab1bd 100644 --- a/inst/components/scss/card.scss +++ b/inst/components/scss/card.scss @@ -3,12 +3,13 @@ overflow: auto; // Avoid "double padding" when two card_body()s are immediate siblings - .card-body + .card-body { + .card-body+.card-body { padding-top: 0; } .card-body { overflow: auto; + p { margin-top: 0; @@ -22,7 +23,7 @@ max-height: var(--bslib-card-body-max-height, none); } - &[data-full-screen="true"] > .card-body { + &[data-full-screen="true"]>.card-body { max-height: var(--bslib-card-body-max-height-full-screen, none); } @@ -31,25 +32,29 @@ flex-direction: row; align-items: center; align-self: stretch; + min-height: 2.5rem; + padding-block: 4px; gap: 0.25rem; - + // Give the nav flex: 1 so that if the card header contains a nav, it will take all the available space // and correctly push items to the right if `nav_spacer` is used - > .nav { + >.nav { flex: 1; min-width: 0; // Prevent flex item from overflowing } - + // Ensures that nav_spacer() correctly pushes subsequent items to the right in card headers - > .bslib-nav-spacer { - margin-left: auto; + >.bslib-nav-spacer { + margin-left: auto; } .form-group { margin-bottom: 0; } + .selectize-control { margin-bottom: 0; + // TODO: we should probably add this to selectize's SCSS since this actually makes selectInput() // usable with width="fit-content" .item { @@ -68,6 +73,7 @@ flex-wrap: wrap; justify-content: space-between; align-items: center; + .nav { margin-left: auto; } @@ -76,6 +82,7 @@ .bslib-sidebar-layout:not([data-bslib-sidebar-border="true"]) { border: none; } + .bslib-sidebar-layout:not([data-bslib-sidebar-border-radius="true"]) { border-top-left-radius: 0; border-top-right-radius: 0; @@ -115,14 +122,19 @@ z-index: $zindex-popover; } -.card:hover, .card:focus-within { - & > * > .bslib-full-screen-enter { +.card:hover, +.card:focus-within { + &>*>.bslib-full-screen-enter { opacity: 0.6; - &:hover, &:focus { opacity: 1; } + + &:hover, + &:focus { + opacity: 1; + } } } -.card[data-full-screen="false"]:hover > * > .bslib-full-screen-enter { +.card[data-full-screen="false"]:hover>*>.bslib-full-screen-enter { display: block; } @@ -142,9 +154,11 @@ margin-right: 2.15rem; align-items: center; color: rgba(var(--bs-body-bg-rgb), 0.8); + &:hover { color: rgba(var(--bs-body-bg-rgb), 1); } + svg { margin-left: 0.5rem; font-size: 1.5rem; @@ -158,13 +172,14 @@ backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); z-index: $zindex-popover - 1; - animation: bslib-full-screen-overlay-enter 400ms cubic-bezier(.6,.02,.65,1) forwards; + animation: bslib-full-screen-overlay-enter 400ms cubic-bezier(.6, .02, .65, 1) forwards; } @keyframes bslib-full-screen-overlay-enter { 0% { opacity: 0; } + 100% { opacity: 1; } diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index 2c97901c2..12843b537 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -29,21 +29,28 @@ >.glyphicon { display: flex; align-items: center; - font-size: .9rem; justify-content: center; line-height: 1; margin: 0; // Remove any default margins } } + // Square icon-only buttons + .bslib-toolbar-input-button[data-type="icon"] { + --_size: 1.5rem; // Match max-height for consistency + height: var(--_size); + aspect-ratio: 1; // Width automatically matches height + padding: 0 !important; // Remove padding for perfect centering, override Bootstrap + line-height: 1 !important; // Ensure no line-height interference + } + /* ---- Adjustments to other elements ---- */ - // Ensures uniformity of font sizing in elements and sub-elements (e.g., input select) - // Exclude icon-only button icons from this rule + // Ensures uniformity of font sizing in elements and sub-elements (e.g., input select) &, - & *:not(.bslib-toolbar-input-button[data-type="icon"], .bslib-toolbar-input-button[data-type="icon"] *) { - font-size: 0.8rem; + & * { + font-size: 0.9rem; } &>* { @@ -57,22 +64,6 @@ width: auto; } - // Square icon-only buttons (must come after general font-size rule) - .bslib-toolbar-input-button[data-type="icon"] { - --_size: 1.5rem; // Match max-height for consistency - height: var(--_size); - aspect-ratio: 1; // Width automatically matches height - padding: 0 !important; // Remove padding for perfect centering, override Bootstrap - line-height: 1 !important; // Ensure no line-height interference - - // Set icon size for all icon types - >.fa, - >.bi, - >.glyphicon { - font-size: 0.9rem; - } - } - // Card header is flex by default, but card footer is not and must be in order for // toolbar alignment to work .card-footer:has(> &) { From cf585de6b9e0051c42c7d9e0a17c05c1a0e4c73e Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Thu, 20 Nov 2025 09:56:50 -0500 Subject: [PATCH 27/41] Updated footer, updated gap --- R/toolbar.R | 26 +++++++++++++++++++------- inst/components/scss/toolbar.scss | 9 ++------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/R/toolbar.R b/R/toolbar.R index b2baaac2f..5c102a3bc 100644 --- a/R/toolbar.R +++ b/R/toolbar.R @@ -7,18 +7,23 @@ #' @param ... UI elements for the toolbar. #' @param align Determines if toolbar should be aligned to the `"right"` or #' `"left"`. +#' @param gap A CSS length unit defining the gap (i.e., spacing) between +#' elements in the toolbar. Defaults to `0` (no gap). #' @return Returns a toolbar element. #' #' @export toolbar <- function( ..., - align = c("right", "left") + align = c("right", "left"), + gap = NULL ) { align <- rlang::arg_match(align) + gap <- validateCssUnit(gap) tag <- div( class = "bslib-toolbar bslib-gap-spacing", "data-align" = align, + style = css(gap = gap), ..., component_dependencies() ) @@ -33,10 +38,11 @@ toolbar <- function( #' A button designed to fit well in small places such as toolbars. #' #' @param id The `input` slot that will be used to access the value. -#' @param label The label to display in the button. +#' #' @param icon An icon to display in the button. #' (One of icon or label must be supplied.) -#' @param icon An icon to display in the button. +#' @param label The label to display in the button. #' (One of icon or label must be supplied.) +#' @param tooltip An optional tooltip to display when hovering over the button. #' @param disabled If `TRUE`, the button will not be clickable. #' Use `updateActionButton()` to dynamically enable/disable the button. #' @param border Whether to show a border around the button. @@ -47,11 +53,12 @@ toolbar <- function( #' @export toolbar_input_button <- function( id, - label = NULL, icon = NULL, + label = NULL, + tooltip = NULL, + ..., disabled = FALSE, - border = FALSE, - ... + border = FALSE ) { if (is.null(icon) && is.null(label)) { stop( @@ -63,7 +70,7 @@ toolbar_input_button <- function( # Determine if this is an icon-only button is_icon_only <- !is.null(icon) && is.null(label) - shiny::actionButton( + button <- shiny::actionButton( id, label = label, icon = icon, @@ -73,4 +80,9 @@ toolbar_input_button <- function( "data-type" = if (is_icon_only) "icon", ... ) + + if (!is.null(tooltip)) { + button <- tooltip(button, tooltip, placement = "bottom") + } + button } diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index 12843b537..ed86fd838 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -9,10 +9,12 @@ &[data-align="left"] { margin-right: auto; + justify-content: start; } &[data-align="right"] { margin-left: auto; + justify-content: end; } /* ---- Toolbar Input Buttons ---- */ @@ -63,11 +65,4 @@ >.form-group { width: auto; } - - // Card header is flex by default, but card footer is not and must be in order for - // toolbar alignment to work - .card-footer:has(> &) { - display: flex; - align-items: center; - } } From 8f3ab2dfd1f3d2adb7208f7895d3cb22b430230d Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Thu, 20 Nov 2025 12:24:56 -0500 Subject: [PATCH 28/41] Updating tests --- .../_snaps/bs-theme-preset-builtin.md | 2 +- tests/testthat/_snaps/bs-theme-preset.md | 6 +- tests/testthat/_snaps/page/modern-page.html | 16 ---- tests/testthat/_snaps/toolbar.md | 75 ++++++++++++++----- tests/testthat/helper-html.R | 4 +- tests/testthat/test-toolbar.R | 6 +- 6 files changed, 68 insertions(+), 41 deletions(-) delete mode 100644 tests/testthat/_snaps/page/modern-page.html diff --git a/tests/testthat/_snaps/bs-theme-preset-builtin.md b/tests/testthat/_snaps/bs-theme-preset-builtin.md index 8a00aa203..ca5c8e28a 100644 --- a/tests/testthat/_snaps/bs-theme-preset-builtin.md +++ b/tests/testthat/_snaps/bs-theme-preset-builtin.md @@ -1,4 +1,4 @@ -# builtin_bundle() / errors for unknown preset names +# builtin_bundle(): errors for unknown preset names Code builtin_bundle("not-a-preset", version = "5") diff --git a/tests/testthat/_snaps/bs-theme-preset.md b/tests/testthat/_snaps/bs-theme-preset.md index 16c1c4412..53ddb0093 100644 --- a/tests/testthat/_snaps/bs-theme-preset.md +++ b/tests/testthat/_snaps/bs-theme-preset.md @@ -1,4 +1,4 @@ -# resolve_bs_preset() / throws an error if both `name` and `bootswatch` are provided +# resolve_bs_preset(): throws an error if both `name` and `bootswatch` are provided Code resolve_bs_preset(preset = "name", bootswatch = "bootswatch") @@ -10,7 +10,7 @@ * `preset = "bootswatch"` * `bootswatch = "bootswatch"` -# resolve_bs_preset() / throws an error if `name` or `bootswatch` are not scalar strings +# resolve_bs_preset(): throws an error if `name` or `bootswatch` are not scalar strings Code resolve_bs_preset(preset = c("a", "b")) @@ -30,7 +30,7 @@ x Bad: `bootswatch = c("flatly", "darkly")` v Good: `bootswatch = "flatly"` -# resolve_bs_preset() / throws an error if `name` or `bootswatch` don't match existing presets +# resolve_bs_preset(): throws an error if `name` or `bootswatch` don't match existing presets Code resolve_bs_preset(preset = "not_a_preset", version = 4) diff --git a/tests/testthat/_snaps/page/modern-page.html b/tests/testthat/_snaps/page/modern-page.html deleted file mode 100644 index fec5d7fd4..000000000 --- a/tests/testthat/_snaps/page/modern-page.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - -A simple page without bs3compat dependencies - diff --git a/tests/testthat/_snaps/toolbar.md b/tests/testthat/_snaps/toolbar.md index cef96d30b..69e38635c 100644 --- a/tests/testthat/_snaps/toolbar.md +++ b/tests/testthat/_snaps/toolbar.md @@ -1,8 +1,4 @@ -<<<<<<< HEAD -# toolbar() markup snapshots -======= # toolbar() basic attributes and defaults ->>>>>>> origin/feat/toolbar-epic Code show_raw_html(toolbar("Item 1", "Item 2")) @@ -12,11 +8,17 @@ Item 2
-<<<<<<< HEAD --- -======= + + Code + show_raw_html(toolbar("Item 1", "Item 2", gap = "10px")) + Output +
+ Item 1 + Item 2 +
+ # toolbar() aligns correctly ->>>>>>> origin/feat/toolbar-epic Code show_raw_html(toolbar("Item 1", "Item 2", align = "left")) @@ -36,40 +38,75 @@ Item 2 -<<<<<<< HEAD -# toolbar_input_button() markup snapshots +# toolbar_input_button() has correct attributes Code - show_raw_html(toolbar_input_button(id = "btn1", label = "Click me")) + show_raw_html(toolbar_input_button(id = "label_only", label = "Click me")) Output - + --- Code - show_raw_html(toolbar_input_button(id = "btn2", icon = shiny::icon("star"))) + show_raw_html(toolbar_input_button(id = "icon_only", icon = shiny::icon("star"))) Output - --- Code - show_raw_html(toolbar_input_button(id = "btn3", label = "Save", icon = shiny::icon( + show_raw_html(toolbar_input_button(id = "both", label = "Save", icon = shiny::icon( "save"))) Output - +# toolbar_input_button() disabled parameter + + Code + show_raw_html(toolbar_input_button(id = "disabled_btn", label = "Disabled", + disabled = TRUE)) + Output + + +--- + + Code + show_raw_html(toolbar_input_button(id = "enabled_btn", label = "Enabled", + disabled = FALSE)) + Output + + +# toolbar_input_button() border parameter + + Code + show_raw_html(toolbar_input_button(id = "no_border", label = "No Border", + border = FALSE)) + Output + + --- Code - show_raw_html(toolbar_input_button(id = "btn4", label = "Delete", class = "btn-danger")) + show_raw_html(toolbar_input_button(id = "with_border", label = "With Border", + border = TRUE)) + Output + + +# toolbar_input_button() tooltip parameter + + Code + show_raw_html(toolbar_input_button(id = "tooltip_icon", icon = shiny::icon( + "question"), tooltip = "Help")) Output - + + + + -======= ->>>>>>> origin/feat/toolbar-epic diff --git a/tests/testthat/helper-html.R b/tests/testthat/helper-html.R index 9965671bf..a612aa75e 100644 --- a/tests/testthat/helper-html.R +++ b/tests/testthat/helper-html.R @@ -3,7 +3,9 @@ show_raw_html <- function(x) { } expect_snapshot_html <- function(x, .envir = parent.frame()) { - x_str <- deparse(substitute(x)) + # Use deparse1() with collapse to ensure single-line output + # This avoids issues with trailing commas from multi-line deparse + x_str <- deparse1(substitute(x), collapse = " ") code <- parse(text = sprintf("expect_snapshot(show_raw_html(%s))", x_str)) eval(code, envir = .envir) } diff --git a/tests/testthat/test-toolbar.R b/tests/testthat/test-toolbar.R index 3604ca228..1f06a977e 100644 --- a/tests/testthat/test-toolbar.R +++ b/tests/testthat/test-toolbar.R @@ -78,7 +78,11 @@ test_that("toolbar_input_button() disabled parameter", { test_that("toolbar_input_button() border parameter", { expect_snapshot_html( - toolbar_input_button(id = "no_border", label = "No Border", border = FALSE) + toolbar_input_button( + id = "no_border", + label = "No Border", + border = FALSE + ) ) expect_snapshot_html( toolbar_input_button( From fa866ba863e8f4d1f19990a36d3792b03ede6ff1 Mon Sep 17 00:00:00 2001 From: E Nelson Date: Thu, 20 Nov 2025 12:35:16 -0500 Subject: [PATCH 29/41] Update headings in bs-theme-preset.md --- tests/testthat/_snaps/bs-theme-preset.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testthat/_snaps/bs-theme-preset.md b/tests/testthat/_snaps/bs-theme-preset.md index 53ddb0093..16c1c4412 100644 --- a/tests/testthat/_snaps/bs-theme-preset.md +++ b/tests/testthat/_snaps/bs-theme-preset.md @@ -1,4 +1,4 @@ -# resolve_bs_preset(): throws an error if both `name` and `bootswatch` are provided +# resolve_bs_preset() / throws an error if both `name` and `bootswatch` are provided Code resolve_bs_preset(preset = "name", bootswatch = "bootswatch") @@ -10,7 +10,7 @@ * `preset = "bootswatch"` * `bootswatch = "bootswatch"` -# resolve_bs_preset(): throws an error if `name` or `bootswatch` are not scalar strings +# resolve_bs_preset() / throws an error if `name` or `bootswatch` are not scalar strings Code resolve_bs_preset(preset = c("a", "b")) @@ -30,7 +30,7 @@ x Bad: `bootswatch = c("flatly", "darkly")` v Good: `bootswatch = "flatly"` -# resolve_bs_preset(): throws an error if `name` or `bootswatch` don't match existing presets +# resolve_bs_preset() / throws an error if `name` or `bootswatch` don't match existing presets Code resolve_bs_preset(preset = "not_a_preset", version = 4) From 06177d61c010fca9118b89430eb02bb6c0374386 Mon Sep 17 00:00:00 2001 From: E Nelson Date: Thu, 20 Nov 2025 12:35:34 -0500 Subject: [PATCH 30/41] Update header format in bs-theme-preset-builtin.md --- tests/testthat/_snaps/bs-theme-preset-builtin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/_snaps/bs-theme-preset-builtin.md b/tests/testthat/_snaps/bs-theme-preset-builtin.md index ca5c8e28a..8a00aa203 100644 --- a/tests/testthat/_snaps/bs-theme-preset-builtin.md +++ b/tests/testthat/_snaps/bs-theme-preset-builtin.md @@ -1,4 +1,4 @@ -# builtin_bundle(): errors for unknown preset names +# builtin_bundle() / errors for unknown preset names Code builtin_bundle("not-a-preset", version = "5") From b49322bfd0aee068e807988b36ab77b2718e7f85 Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Thu, 20 Nov 2025 12:37:03 -0500 Subject: [PATCH 31/41] Add back deleted file --- tests/testthat/_snaps/page/modern-page.html | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/testthat/_snaps/page/modern-page.html diff --git a/tests/testthat/_snaps/page/modern-page.html b/tests/testthat/_snaps/page/modern-page.html new file mode 100644 index 000000000..fec5d7fd4 --- /dev/null +++ b/tests/testthat/_snaps/page/modern-page.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + +A simple page without bs3compat dependencies + From cd2a74620aa9cabcbae1a5c23cedc6494ba64e5e Mon Sep 17 00:00:00 2001 From: Liz Nelson Date: Thu, 20 Nov 2025 12:38:42 -0500 Subject: [PATCH 32/41] removing comment --- tests/testthat/helper-html.R | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/testthat/helper-html.R b/tests/testthat/helper-html.R index a612aa75e..061cd9589 100644 --- a/tests/testthat/helper-html.R +++ b/tests/testthat/helper-html.R @@ -3,8 +3,6 @@ show_raw_html <- function(x) { } expect_snapshot_html <- function(x, .envir = parent.frame()) { - # Use deparse1() with collapse to ensure single-line output - # This avoids issues with trailing commas from multi-line deparse x_str <- deparse1(substitute(x), collapse = " ") code <- parse(text = sprintf("expect_snapshot(show_raw_html(%s))", x_str)) eval(code, envir = .envir) From cc601fb2ee26adb3fa3b6199574629c4318a1d9c Mon Sep 17 00:00:00 2001 From: E Nelson Date: Thu, 20 Nov 2025 12:47:35 -0500 Subject: [PATCH 33/41] Fix tests --- DESCRIPTION | 1 - R/toolbar.R | 2 +- inst/components/scss/toolbar.scss | 23 ----------------------- man/toolbar.Rd | 5 ++++- man/toolbar_input_button.Rd | 17 ++++++++++------- 5 files changed, 15 insertions(+), 33 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 6f0f32cbc..cd987d022 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -115,7 +115,6 @@ Collate: 'shiny-devmode.R' 'sidebar.R' 'staticimports.R' - 'toolbar.R' 'toast.R' 'toolbar.R' 'tooltip.R' diff --git a/R/toolbar.R b/R/toolbar.R index 5c102a3bc..8cc0cb82b 100644 --- a/R/toolbar.R +++ b/R/toolbar.R @@ -38,7 +38,7 @@ toolbar <- function( #' A button designed to fit well in small places such as toolbars. #' #' @param id The `input` slot that will be used to access the value. -#' #' @param icon An icon to display in the button. +#' @param icon An icon to display in the button. #' (One of icon or label must be supplied.) #' @param label The label to display in the button. #' (One of icon or label must be supplied.) diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index 2c05c6faa..ed86fd838 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -2,25 +2,18 @@ .bslib-toolbar { display: flex; align-items: center; -<<<<<<< HEAD gap: 0; max-height: var(--_size); // Match approximate card header text height so it doesn't expand beyond normal -======= ->>>>>>> origin/feat/toolbar-epic /* ---- Toolbar options ---- */ &[data-align="left"] { margin-right: auto; -<<<<<<< HEAD justify-content: start; -======= ->>>>>>> origin/feat/toolbar-epic } &[data-align="right"] { margin-left: auto; -<<<<<<< HEAD justify-content: end; } @@ -54,20 +47,12 @@ } -======= - } - ->>>>>>> origin/feat/toolbar-epic /* ---- Adjustments to other elements ---- */ // Ensures uniformity of font sizing in elements and sub-elements (e.g., input select) &, & * { -<<<<<<< HEAD font-size: 0.9rem; -======= - font-size: 0.8rem; ->>>>>>> origin/feat/toolbar-epic } &>* { @@ -76,16 +61,8 @@ align-self: center; } -<<<<<<< HEAD // Ensures that inputs inherit the formatting of the toolbar and align correctly (e.g. input select) >.form-group { width: auto; -======= - // Card header is flex by default, but card footer is not and must be in order for - // toolbar alignment to work - .card-footer:has(> &) { - display: flex; - align-items: center; ->>>>>>> origin/feat/toolbar-epic } } diff --git a/man/toolbar.Rd b/man/toolbar.Rd index 6a398d85f..6588e9060 100644 --- a/man/toolbar.Rd +++ b/man/toolbar.Rd @@ -4,13 +4,16 @@ \alias{toolbar} \title{Toolbar component} \usage{ -toolbar(..., align = c("right", "left")) +toolbar(..., align = c("right", "left"), gap = NULL) } \arguments{ \item{...}{UI elements for the toolbar.} \item{align}{Determines if toolbar should be aligned to the \code{"right"} or \code{"left"}.} + +\item{gap}{A CSS length unit defining the gap (i.e., spacing) between +elements in the toolbar. Defaults to \code{0} (no gap).} } \value{ Returns a toolbar element. diff --git a/man/toolbar_input_button.Rd b/man/toolbar_input_button.Rd index b27bf1e10..131bd7352 100644 --- a/man/toolbar_input_button.Rd +++ b/man/toolbar_input_button.Rd @@ -6,28 +6,31 @@ \usage{ toolbar_input_button( id, - label = NULL, icon = NULL, + label = NULL, + tooltip = NULL, + ..., disabled = FALSE, - border = FALSE, - ... + border = FALSE ) } \arguments{ \item{id}{The \code{input} slot that will be used to access the value.} -\item{label}{The label to display in the button. +\item{icon}{An icon to display in the button. (One of icon or label must be supplied.)} -\item{icon}{An icon to display in the button. +\item{label}{The label to display in the button. (One of icon or label must be supplied.)} +\item{tooltip}{An optional tooltip to display when hovering over the button.} + +\item{...}{UI elements for the button.} + \item{disabled}{If \code{TRUE}, the button will not be clickable. Use \code{updateActionButton()} to dynamically enable/disable the button.} \item{border}{Whether to show a border around the button.} - -\item{...}{UI elements for the button.} } \value{ Returns a button suitable for use in a toolbar. From 25c6027631c84a5d8bae0d502427ae0927141db1 Mon Sep 17 00:00:00 2001 From: E Nelson Date: Thu, 20 Nov 2025 13:10:39 -0500 Subject: [PATCH 34/41] Update card.scss --- inst/components/scss/card.scss | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/inst/components/scss/card.scss b/inst/components/scss/card.scss index 8fc3ab1bd..1be40901c 100644 --- a/inst/components/scss/card.scss +++ b/inst/components/scss/card.scss @@ -3,7 +3,7 @@ overflow: auto; // Avoid "double padding" when two card_body()s are immediate siblings - .card-body+.card-body { + .card-body + .card-body { padding-top: 0; } @@ -23,7 +23,7 @@ max-height: var(--bslib-card-body-max-height, none); } - &[data-full-screen="true"]>.card-body { + &[data-full-screen="true"] > .card-body { max-height: var(--bslib-card-body-max-height-full-screen, none); } @@ -38,13 +38,12 @@ // Give the nav flex: 1 so that if the card header contains a nav, it will take all the available space // and correctly push items to the right if `nav_spacer` is used - >.nav { + > .nav { flex: 1; min-width: 0; // Prevent flex item from overflowing } - // Ensures that nav_spacer() correctly pushes subsequent items to the right in card headers - >.bslib-nav-spacer { + > .bslib-nav-spacer { margin-left: auto; } @@ -54,7 +53,6 @@ .selectize-control { margin-bottom: 0; - // TODO: we should probably add this to selectize's SCSS since this actually makes selectInput() // usable with width="fit-content" .item { @@ -73,7 +71,6 @@ flex-wrap: wrap; justify-content: space-between; align-items: center; - .nav { margin-left: auto; } @@ -82,7 +79,6 @@ .bslib-sidebar-layout:not([data-bslib-sidebar-border="true"]) { border: none; } - .bslib-sidebar-layout:not([data-bslib-sidebar-border-radius="true"]) { border-top-left-radius: 0; border-top-right-radius: 0; @@ -122,19 +118,15 @@ z-index: $zindex-popover; } -.card:hover, -.card:focus-within { - &>*>.bslib-full-screen-enter { +.card:hover, .card:focus-within { + & > * > .bslib-full-screen-enter { opacity: 0.6; - &:hover, - &:focus { - opacity: 1; - } + &:hover, &:focus { opacity: 1; } } } -.card[data-full-screen="false"]:hover>*>.bslib-full-screen-enter { +.card[data-full-screen="false"]:hover > * > .bslib-full-screen-enter { display: block; } @@ -154,11 +146,9 @@ margin-right: 2.15rem; align-items: center; color: rgba(var(--bs-body-bg-rgb), 0.8); - &:hover { color: rgba(var(--bs-body-bg-rgb), 1); } - svg { margin-left: 0.5rem; font-size: 1.5rem; @@ -172,14 +162,13 @@ backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); z-index: $zindex-popover - 1; - animation: bslib-full-screen-overlay-enter 400ms cubic-bezier(.6, .02, .65, 1) forwards; + animation: bslib-full-screen-overlay-enter 400ms cubic-bezier(.6,.02,.65,1) forwards; } @keyframes bslib-full-screen-overlay-enter { 0% { opacity: 0; } - 100% { opacity: 1; } From e6fac9a03b877a308f0c6682629cd5c80b5d6249 Mon Sep 17 00:00:00 2001 From: E Nelson Date: Thu, 20 Nov 2025 13:17:18 -0500 Subject: [PATCH 35/41] Fixing diff --- inst/components/scss/card.scss | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/inst/components/scss/card.scss b/inst/components/scss/card.scss index 1be40901c..2562909e2 100644 --- a/inst/components/scss/card.scss +++ b/inst/components/scss/card.scss @@ -9,7 +9,6 @@ .card-body { overflow: auto; - p { margin-top: 0; @@ -35,22 +34,21 @@ min-height: 2.5rem; padding-block: 4px; gap: 0.25rem; - // Give the nav flex: 1 so that if the card header contains a nav, it will take all the available space // and correctly push items to the right if `nav_spacer` is used > .nav { flex: 1; min-width: 0; // Prevent flex item from overflowing } + // Ensures that nav_spacer() correctly pushes subsequent items to the right in card headers > .bslib-nav-spacer { - margin-left: auto; + margin-left: auto; } .form-group { margin-bottom: 0; } - .selectize-control { margin-bottom: 0; // TODO: we should probably add this to selectize's SCSS since this actually makes selectInput() @@ -121,7 +119,6 @@ .card:hover, .card:focus-within { & > * > .bslib-full-screen-enter { opacity: 0.6; - &:hover, &:focus { opacity: 1; } } } From c5d9e383e9ac29760723c8dfa30b5a9bb5513cbd Mon Sep 17 00:00:00 2001 From: E Nelson Date: Thu, 20 Nov 2025 13:27:44 -0500 Subject: [PATCH 36/41] Slim down icon button formatting --- inst/components/scss/toolbar.scss | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index ed86fd838..3f86df5fa 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -39,14 +39,11 @@ // Square icon-only buttons .bslib-toolbar-input-button[data-type="icon"] { - --_size: 1.5rem; // Match max-height for consistency height: var(--_size); - aspect-ratio: 1; // Width automatically matches height - padding: 0 !important; // Remove padding for perfect centering, override Bootstrap + aspect-ratio: 1; line-height: 1 !important; // Ensure no line-height interference } - /* ---- Adjustments to other elements ---- */ // Ensures uniformity of font sizing in elements and sub-elements (e.g., input select) From 37b9855f9462e5893b971a092d508281e960c5b0 Mon Sep 17 00:00:00 2001 From: E Nelson Date: Thu, 20 Nov 2025 13:44:09 -0500 Subject: [PATCH 37/41] Clean up scss --- inst/components/scss/toolbar.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index 3f86df5fa..81f3bcb4d 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -23,7 +23,7 @@ .bslib-toolbar-input-button { align-items: center; justify-content: center; - line-height: 1; // Override Bootstrap's line-height + line-height: 1; // Override Bootstrap's line-height to avoid too much vertical space // Ensure icon is centered >.fa, @@ -32,7 +32,7 @@ display: flex; align-items: center; justify-content: center; - line-height: 1; + line-height: 1; // Remove excess line-height margin: 0; // Remove any default margins } } From 16acb76e359a2adf1aca938a4a111602dc7d0894 Mon Sep 17 00:00:00 2001 From: E Nelson Date: Thu, 20 Nov 2025 13:46:34 -0500 Subject: [PATCH 38/41] Fixing diff --- inst/components/scss/card.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inst/components/scss/card.scss b/inst/components/scss/card.scss index 2562909e2..47d2f0572 100644 --- a/inst/components/scss/card.scss +++ b/inst/components/scss/card.scss @@ -34,13 +34,14 @@ min-height: 2.5rem; padding-block: 4px; gap: 0.25rem; + // Give the nav flex: 1 so that if the card header contains a nav, it will take all the available space // and correctly push items to the right if `nav_spacer` is used > .nav { flex: 1; min-width: 0; // Prevent flex item from overflowing } - + // Ensures that nav_spacer() correctly pushes subsequent items to the right in card headers > .bslib-nav-spacer { margin-left: auto; From 5ed4321485ab8b531af85f3dd44ec509c8ae1212 Mon Sep 17 00:00:00 2001 From: E Nelson Date: Thu, 20 Nov 2025 13:49:50 -0500 Subject: [PATCH 39/41] Removing accidental legacy addition --- inst/components/scss/toolbar.scss | 5 ----- 1 file changed, 5 deletions(-) diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index 81f3bcb4d..c1d723c5d 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -57,9 +57,4 @@ width: auto; align-self: center; } - - // Ensures that inputs inherit the formatting of the toolbar and align correctly (e.g. input select) - >.form-group { - width: auto; - } } From 55315724034eb07b61717817bd487def6483f713 Mon Sep 17 00:00:00 2001 From: E Nelson Date: Fri, 21 Nov 2025 09:08:39 -0500 Subject: [PATCH 40/41] Apply suggestions from code review Co-authored-by: Garrick Aden-Buie --- R/toolbar.R | 15 +++++++++++++-- inst/components/scss/toolbar.scss | 2 +- tests/testthat/helper-html.R | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/R/toolbar.R b/R/toolbar.R index 8cc0cb82b..8745aa4bd 100644 --- a/R/toolbar.R +++ b/R/toolbar.R @@ -37,14 +37,14 @@ toolbar <- function( #' @description #' A button designed to fit well in small places such as toolbars. #' -#' @param id The `input` slot that will be used to access the value. +#' @param id The input ID. #' @param icon An icon to display in the button. #' (One of icon or label must be supplied.) #' @param label The label to display in the button. #' (One of icon or label must be supplied.) #' @param tooltip An optional tooltip to display when hovering over the button. #' @param disabled If `TRUE`, the button will not be clickable. -#' Use `updateActionButton()` to dynamically enable/disable the button. +#' Use [shiny::updateActionButton()] to dynamically enable/disable the button. #' @param border Whether to show a border around the button. #' @param ... UI elements for the button. #' @@ -66,7 +66,18 @@ toolbar_input_button <- function( call. = TRUE ) } +has_icon <- !is.null(icon) +has_label <- !is.null(label) +btn_type <- + if (has_icon && !has_label) { + "icon" + } else if (has_label && !has_icon) { + "label" + } else { + # Can't both be missing (checked above) + "both" + } # Determine if this is an icon-only button is_icon_only <- !is.null(icon) && is.null(label) diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index c1d723c5d..f9754ffb2 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -39,7 +39,7 @@ // Square icon-only buttons .bslib-toolbar-input-button[data-type="icon"] { - height: var(--_size); + height: var(--_icon-size, 1em); aspect-ratio: 1; line-height: 1 !important; // Ensure no line-height interference } diff --git a/tests/testthat/helper-html.R b/tests/testthat/helper-html.R index 061cd9589..7048305d4 100644 --- a/tests/testthat/helper-html.R +++ b/tests/testthat/helper-html.R @@ -3,7 +3,7 @@ show_raw_html <- function(x) { } expect_snapshot_html <- function(x, .envir = parent.frame()) { - x_str <- deparse1(substitute(x), collapse = " ") + x_str <- deparse1(substitute(x)) code <- parse(text = sprintf("expect_snapshot(show_raw_html(%s))", x_str)) eval(code, envir = .envir) } From b8d66d5c5ec767d955694ff2076316881725b1f1 Mon Sep 17 00:00:00 2001 From: E Nelson Date: Mon, 24 Nov 2025 10:02:59 -0500 Subject: [PATCH 41/41] Updating with edits based on comments --- DESCRIPTION | 4 +- R/toolbar.R | 62 ++++++++++++++++++++----------- inst/components/scss/toolbar.scss | 19 ++++------ tests/testthat/_snaps/toolbar.md | 36 +++++++++++++----- 4 files changed, 76 insertions(+), 45 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index cd987d022..0b3019de1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -40,7 +40,8 @@ Imports: memoise (>= 2.0.1), mime, rlang, - sass (>= 0.4.9) + sass (>= 0.4.9), + shiny (>= 1.11.1.9000) Suggests: bsicons, curl, @@ -52,7 +53,6 @@ Suggests: magrittr, rappdirs, rmarkdown (>= 2.7), - shiny (>= 1.11.1), testthat, thematic, tools, diff --git a/R/toolbar.R b/R/toolbar.R index 8745aa4bd..687b2a299 100644 --- a/R/toolbar.R +++ b/R/toolbar.R @@ -4,6 +4,14 @@ #' A toolbar which can contain buttons, inputs, and other UI elements in a small #' form suitable for inclusion in card headers, footers, and other small places. #' +#' @examplesIf rlang::is_interactive() +#' toolbar( +#' align = "right", +#' toolbar_input_button(id = "see", icon = icon("eye")), +#' toolbar_input_button(id = "save", icon = icon("save")), +#' toolbar_input_button(id = "edit", icon = icon("pencil")) +#' ) +#' #' @param ... UI elements for the toolbar. #' @param align Determines if toolbar should be aligned to the `"right"` or #' `"left"`. @@ -11,6 +19,7 @@ #' elements in the toolbar. Defaults to `0` (no gap). #' @return Returns a toolbar element. #' +#' @family Toolbar components #' @export toolbar <- function( ..., @@ -35,22 +44,33 @@ toolbar <- function( #' Add toolbar button input #' #' @description -#' A button designed to fit well in small places such as toolbars. +#' A button designed to fit well in small places such as in a [toolbar()]. +#' +#' @examplesIf rlang::is_interactive() +#' toolbar( +#' align = "right", +#' toolbar_input_button(id = "see", icon = icon("eye")), +#' toolbar_input_button(id = "save", label = "Save")), +#' toolbar_input_button(id = "edit", icon = icon("pencil"), label="Edit") +#' ) #' #' @param id The input ID. -#' @param icon An icon to display in the button. -#' (One of icon or label must be supplied.) -#' @param label The label to display in the button. -#' (One of icon or label must be supplied.) -#' @param tooltip An optional tooltip to display when hovering over the button. -#' @param disabled If `TRUE`, the button will not be clickable. -#' Use [shiny::updateActionButton()] to dynamically enable/disable the button. +#' @param icon An icon to display in the button. (One of icon or label must be +#' supplied.) +#' @param label The label to display in the button. (One of icon or label must +#' be supplied.) +#' @param tooltip An optional [tooltip()] to display when hovering over the +#' button. +#' @param disabled If `TRUE`, the button will not be clickable. Use +#' [shiny::updateActionButton()] to dynamically enable/disable the button. #' @param border Whether to show a border around the button. #' @param ... UI elements for the button. #' #' @return Returns a button suitable for use in a toolbar. #' +#' @family Toolbar components #' @export + toolbar_input_button <- function( id, icon = NULL, @@ -66,20 +86,18 @@ toolbar_input_button <- function( call. = TRUE ) } -has_icon <- !is.null(icon) -has_label <- !is.null(label) + has_icon <- !is.null(icon) + has_label <- !is.null(label) -btn_type <- - if (has_icon && !has_label) { - "icon" - } else if (has_label && !has_icon) { - "label" - } else { - # Can't both be missing (checked above) - "both" - } - # Determine if this is an icon-only button - is_icon_only <- !is.null(icon) && is.null(label) + btn_type <- + if (has_icon && !has_label) { + "icon" + } else if (has_label && !has_icon) { + "label" + } else { + # Can't both be missing (checked above) + "both" + } button <- shiny::actionButton( id, @@ -88,7 +106,7 @@ btn_type <- disabled = disabled, class = "bslib-toolbar-input-button btn-sm", class = if (!border) "border-0" else "border-1", - "data-type" = if (is_icon_only) "icon", + "data-type" = btn_type, ... ) diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss index f9754ffb2..c5a369280 100644 --- a/inst/components/scss/toolbar.scss +++ b/inst/components/scss/toolbar.scss @@ -3,7 +3,6 @@ display: flex; align-items: center; gap: 0; - max-height: var(--_size); // Match approximate card header text height so it doesn't expand beyond normal /* ---- Toolbar options ---- */ @@ -24,11 +23,16 @@ align-items: center; justify-content: center; line-height: 1; // Override Bootstrap's line-height to avoid too much vertical space + } + + // Square icon-only buttons + .bslib-toolbar-input-button[data-type="icon"] { + height: var(--_icon-size, 2rem); + aspect-ratio: 1; + line-height: 1 !important; // Ensure no line-height interference // Ensure icon is centered - >.fa, - >.bi, - >.glyphicon { + >.action-icon { display: flex; align-items: center; justify-content: center; @@ -37,13 +41,6 @@ } } - // Square icon-only buttons - .bslib-toolbar-input-button[data-type="icon"] { - height: var(--_icon-size, 1em); - aspect-ratio: 1; - line-height: 1 !important; // Ensure no line-height interference - } - /* ---- Adjustments to other elements ---- */ // Ensures uniformity of font sizing in elements and sub-elements (e.g., input select) diff --git a/tests/testthat/_snaps/toolbar.md b/tests/testthat/_snaps/toolbar.md index 69e38635c..5a26d54a5 100644 --- a/tests/testthat/_snaps/toolbar.md +++ b/tests/testthat/_snaps/toolbar.md @@ -43,7 +43,9 @@ Code show_raw_html(toolbar_input_button(id = "label_only", label = "Click me")) Output - + --- @@ -51,7 +53,9 @@ show_raw_html(toolbar_input_button(id = "icon_only", icon = shiny::icon("star"))) Output --- @@ -60,9 +64,11 @@ show_raw_html(toolbar_input_button(id = "both", label = "Save", icon = shiny::icon( "save"))) Output - # toolbar_input_button() disabled parameter @@ -71,7 +77,9 @@ show_raw_html(toolbar_input_button(id = "disabled_btn", label = "Disabled", disabled = TRUE)) Output - + --- @@ -79,7 +87,9 @@ show_raw_html(toolbar_input_button(id = "enabled_btn", label = "Enabled", disabled = FALSE)) Output - + # toolbar_input_button() border parameter @@ -87,7 +97,9 @@ show_raw_html(toolbar_input_button(id = "no_border", label = "No Border", border = FALSE)) Output - + --- @@ -95,7 +107,9 @@ show_raw_html(toolbar_input_button(id = "with_border", label = "With Border", border = TRUE)) Output - + # toolbar_input_button() tooltip parameter @@ -106,7 +120,9 @@