diff --git a/DESCRIPTION b/DESCRIPTION index 4182de340..cd987d022 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -116,6 +116,7 @@ Collate: 'sidebar.R' 'staticimports.R' 'toast.R' + 'toolbar.R' 'tooltip.R' 'utils-deps.R' 'utils-shiny.R' diff --git a/NAMESPACE b/NAMESPACE index 836b2303a..07c1eb683 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -162,6 +162,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/NEWS.md b/NEWS.md index 85ae879d3..45a9d1a03 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,8 @@ * Added toast notifications based on [Bootstrap's Toast component](https://getbootstrap.com/docs/5.3/components/toasts/): Use `toast()` to create customizable toast objects, `show_toast()` to display a toast message, `hide_toast()` for manual dismissal, and `toast_header()` for structured headers with icons and status indicators. (#1246) +* Added a new `toolbar()` component for creating Bootstrap toolbars that can contain buttons, text, and other elements. (#1247) + ## Improvements and bug fixes * `card_header()` is now flex by default and gains a `gap` argument to better support complex header layouts. (#1253) diff --git a/R/toolbar.R b/R/toolbar.R new file mode 100644 index 000000000..f100b5951 --- /dev/null +++ b/R/toolbar.R @@ -0,0 +1,28 @@ +#' 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. +#' +#' @param ... UI elements for the toolbar. +#' @param align Determines if toolbar should be aligned to the `"right"` or +#' `"left"`. +#' @return Returns a toolbar element. +#' +#' @export +toolbar <- function( + ..., + align = c("right", "left") +) { + align <- rlang::arg_match(align) + + tag <- div( + class = "bslib-toolbar bslib-gap-spacing", + "data-align" = align, + ..., + component_dependencies() + ) + + tag_require(tag, version = 5, caller = "toolbar()") + as_fragment(tag) +} diff --git a/inst/components/scss/toolbar.scss b/inst/components/scss/toolbar.scss new file mode 100644 index 000000000..75f11209d --- /dev/null +++ b/inst/components/scss/toolbar.scss @@ -0,0 +1,36 @@ +/* Toolbar */ +.bslib-toolbar { + display: flex; + align-items: center; + + /* ---- Toolbar options ---- */ + + &[data-align="left"] { + margin-right: auto; + } + + &[data-align="right"] { + margin-left: auto; + } + + /* ---- Adjustments to other elements ---- */ + + // Ensures uniformity of font sizing in elements and sub-elements (e.g., input select) + &, + & * { + font-size: 0.8rem; + } + + &>* { + margin-bottom: 0 !important; + width: auto; + align-self: center; + } + + // 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; + } +} diff --git a/man/toolbar.Rd b/man/toolbar.Rd new file mode 100644 index 000000000..6a398d85f --- /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{Toolbar component} +\usage{ +toolbar(..., align = c("right", "left")) +} +\arguments{ +\item{...}{UI elements for the toolbar.} + +\item{align}{Determines if toolbar should be aligned to the \code{"right"} or +\code{"left"}.} +} +\value{ +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. +} diff --git a/tests/testthat/_snaps/toolbar.md b/tests/testthat/_snaps/toolbar.md new file mode 100644 index 000000000..2b0f09a1d --- /dev/null +++ b/tests/testthat/_snaps/toolbar.md @@ -0,0 +1,30 @@ +# toolbar() basic attributes and defaults + + Code + show_raw_html(toolbar("Item 1", "Item 2")) + Output +
+ +# toolbar() aligns correctly + + Code + show_raw_html(toolbar("Item 1", "Item 2", align = "left")) + Output + + +--- + + Code + show_raw_html(toolbar("Item 1", "Item 2", align = "right")) + Output + + diff --git a/tests/testthat/helper-html.R b/tests/testthat/helper-html.R new file mode 100644 index 000000000..9965671bf --- /dev/null +++ b/tests/testthat/helper-html.R @@ -0,0 +1,9 @@ +show_raw_html <- function(x) { + cat(format(x)) +} + +expect_snapshot_html <- function(x, .envir = parent.frame()) { + x_str <- deparse(substitute(x)) + 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 new file mode 100644 index 000000000..b58d13bd5 --- /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_snapshot_html( + toolbar("Item 1", "Item 2") + ) +}) + +test_that("toolbar() aligns correctly", { + tb <- as.tags(toolbar(align = "left")) + expect_equal(htmltools::tagGetAttribute(tb, "data-align"), "left") + expect_snapshot_html( + toolbar("Item 1", "Item 2", align = "left") + ) + expect_snapshot_html( + toolbar("Item 1", "Item 2", align = "right") + ) + expect_error(toolbar("x", align = "center")) +})