diff --git a/NEWS.md b/NEWS.md index 0dec2e8ba..fa621ff6c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,8 +3,11 @@ ## Breaking changes * `value_box()` no longer defaults to `theme_color = "primary"`. To restore the previous behavior, please use `theme = "primary"`. In addition to the default style change, the `theme_color` is now deprecated in favor of `theme`. (#758) + * `page_navbar()` now defaults to `underline = TRUE`, meaning that navigation links in the navbar now have underline styling by default (set `underline = FALSE` to revert to previous behavior). (#784) +* `layout_column_wrap()` no longer requires `width` and `width` is no longer the first argument, meaning that `width` must be named if used. The new default is `width = "200px"`, which combines with `fixed_width = FALSE`, and produces an automatically responsive layout where each column is at least 200px wide. This means that, in most cases, `layout_column_wrap()` can layout an unknown number of items without you having to set `width`. (#799) + ## New features * Upgraded the default version of Bootstrap from v5.2.2 to v5.3.1. The most notable thing that comes with the update is the ability to toggle between light/dark [color modes](https://getbootstrap.com/docs/5.3/customize/color-modes/) purely client-side (i.e., no calls to Sass required). (#749, #764) diff --git a/R/layout.R b/R/layout.R index db3de1f39..7ad944a29 100644 --- a/R/layout.R +++ b/R/layout.R @@ -2,10 +2,11 @@ #' #' Wraps a 1d sequence of UI elements into a 2d grid. The number of columns (and #' rows) in the grid dependent on the column `width` as well as the size of the -#' display. For more explanation and illustrative examples, see [here](https://rstudio.github.io/bslib/articles/cards.html#multiple-cards) +#' display. For more explanation and illustrative examples, see +#' [here](https://rstudio.github.io/bslib/articles/cards.html#multiple-cards). #' -#' @param ... Unnamed arguments should be UI elements (e.g., [card()]) -#' Named arguments become attributes on the containing [htmltools::tag] element. +#' @param ... Unnamed arguments should be UI elements (e.g., [card()]). Named +#' arguments become attributes on the containing [htmltools::tag] element. #' @param width The desired width of each card, which can be any of the #' following: #' * A (unit-less) number between 0 and 1. @@ -17,9 +18,14 @@ #' * `NULL` #' * Allows power users to set the `grid-template-columns` CSS property #' manually, either via a `style` attribute or a CSS stylesheet. -#' @param fixed_width Whether or not to interpret the `width` as a minimum -#' (`fixed_width=FALSE`) or fixed (`fixed_width=TRUE`) width when it is a CSS -#' length unit. +#' @param fixed_width When `width` is greater than 1 or is a CSS length unit, +#' e.g. `"200px"`, `fixed_width` indicates whether that `width` value +#' represents the absolute size of each column (`fixed_width=TRUE`) or the +#' minimum size of a column (`fixed_width=FALSE`). When `fixed_width=FALSE`, +#' new columns are added to a row when `width` space is available and columns +#' will never exceed the container or viewport size. When `fixed_width=TRUE`, +#' all columns will be exactly `width` wide, which may result in columns +#' overflowing the parent container. #' @param heights_equal If `"all"` (the default), every card in every row of the #' grid will have the same height. If `"row"`, then every card in _each_ row #' of the grid will have the same height, but heights may vary between rows. @@ -31,18 +37,23 @@ #' @inheritParams card #' @inheritParams card_body #' -#' @export #' @examples -#' #' x <- card("A simple card") #' # Always has 2 columns (on non-mobile) -#' layout_column_wrap(1/2, x, x, x) -#' # Has three columns when viewport is wider than 750px -#' layout_column_wrap("250px", x, x, x) +#' layout_column_wrap(width = 1/2, x, x, x) +#' +#' # Automatically lays out three cards into columns +#' # such that each column is at least 200px wide: +#' layout_column_wrap(x, x, x) #' +#' # To use larger column widths by default, set `width`. +#' # This example has 3 columns when the screen is at least 900px wide: +#' layout_column_wrap(width = "300px", x, x, x) +#' +#' @export layout_column_wrap <- function( - width, ..., + width = "200px", fixed_width = FALSE, heights_equal = c("all", "row"), fill = TRUE, @@ -59,6 +70,23 @@ layout_column_wrap <- function( attribs <- args$attribs children <- args$children + if (missing(width)) { + first_is_width <- + is.null(children[[1]]) || + is_probably_a_css_unit(children[[1]]) + + if (first_is_width) { + # Assume an unnamed first argument that matches our expectations for + # `width` is actually the width argument, with a warning + lifecycle::deprecate_warn( + "0.6.0", + "layout_column_wrap(width = 'must be named')" + ) + width <- children[[1]] + children <- children[-1] + } + } + if (length(width) > 1) { stop("`width` of length greater than 1 is not currently supported.") } @@ -74,7 +102,10 @@ layout_column_wrap <- function( if (fixed_width) { paste0("repeat(auto-fit, ", validateCssUnit(width), ")") } else { - paste0("repeat(auto-fit, minmax(", validateCssUnit(width), ", 1fr))") + sprintf( + "repeat(auto-fit, minmax(min(%s, 100%%), 1fr))", + validateCssUnit(width) + ) } } } @@ -107,6 +138,16 @@ layout_column_wrap <- function( ) } +is_probably_a_css_unit <- function(x) { + if (length(x) != 1) return(FALSE) + if (is.numeric(x)) return(TRUE) + if (!is.character(x)) return(FALSE) + tryCatch( + { validateCssUnit(x); TRUE }, + error = function(e) FALSE + ) +} + #' Responsive column-based grid layouts #' #' Create responsive, column-based grid layouts, based on a 12-column grid. diff --git a/R/sysdata.rda b/R/sysdata.rda index ed0482730..b5cc3afb2 100644 Binary files a/R/sysdata.rda and b/R/sysdata.rda differ diff --git a/inst/fonts/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVs9pbCIPrc.woff b/inst/fonts/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVs9pbCIPrc.woff index 958d003be..fa224cfe4 100644 Binary files a/inst/fonts/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVs9pbCIPrc.woff and b/inst/fonts/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVs9pbCIPrc.woff differ diff --git a/inst/fonts/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaorCIPrc.woff b/inst/fonts/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaorCIPrc.woff index c5bf2f05c..6fd8981ac 100644 Binary files a/inst/fonts/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaorCIPrc.woff and b/inst/fonts/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaorCIPrc.woff differ diff --git a/inst/fonts/HI_diYsKILxRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DBKXhM0.woff b/inst/fonts/HI_diYsKILxRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DBKXhM0.woff index d992182e2..1f91f8ace 100644 Binary files a/inst/fonts/HI_diYsKILxRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DBKXhM0.woff and b/inst/fonts/HI_diYsKILxRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DBKXhM0.woff differ diff --git a/inst/fonts/HI_diYsKILxRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DMyQhM0.woff b/inst/fonts/HI_diYsKILxRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DMyQhM0.woff index 913496c95..281a01344 100644 Binary files a/inst/fonts/HI_diYsKILxRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DMyQhM0.woff and b/inst/fonts/HI_diYsKILxRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DMyQhM0.woff differ diff --git a/inst/fonts/HI_jiYsKILxRpg3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTbI1rSg.woff b/inst/fonts/HI_jiYsKILxRpg3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTbI1rSg.woff index 5c95d895b..2a9ddb683 100644 Binary files a/inst/fonts/HI_jiYsKILxRpg3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTbI1rSg.woff and b/inst/fonts/HI_jiYsKILxRpg3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTbI1rSg.woff differ diff --git a/inst/fonts/HI_jiYsKILxRpg3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTsoprSg.woff b/inst/fonts/HI_jiYsKILxRpg3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTsoprSg.woff index 3fce06f47..1beb305a4 100644 Binary files a/inst/fonts/HI_jiYsKILxRpg3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTsoprSg.woff and b/inst/fonts/HI_jiYsKILxRpg3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTsoprSg.woff differ diff --git a/inst/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtZ6Ew9.woff b/inst/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtZ6Ew9.woff index d6024de72..fcb8865ac 100644 Binary files a/inst/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtZ6Ew9.woff and b/inst/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtZ6Ew9.woff differ diff --git a/inst/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Ew9.woff b/inst/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Ew9.woff index 1851360a2..d8b287e3d 100644 Binary files a/inst/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Ew9.woff and b/inst/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Ew9.woff differ diff --git a/inst/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCuM70w9.woff b/inst/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCuM70w9.woff index 987c3a57c..6f9ee1acc 100644 Binary files a/inst/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCuM70w9.woff and b/inst/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCuM70w9.woff differ diff --git a/inst/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuFuYMZs.woff b/inst/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuFuYMZs.woff index 7248f274d..1a02007ec 100644 Binary files a/inst/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuFuYMZs.woff and b/inst/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuFuYMZs.woff differ diff --git a/inst/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuI6fMZs.woff b/inst/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuI6fMZs.woff index ec2c08d3a..2e4de9476 100644 Binary files a/inst/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuI6fMZs.woff and b/inst/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuI6fMZs.woff differ diff --git a/inst/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfMZs.woff b/inst/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfMZs.woff index db0f8d1c3..e600f0c41 100644 Binary files a/inst/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfMZs.woff and b/inst/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfMZs.woff differ diff --git a/inst/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDFwmRTA.woff b/inst/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDFwmRTA.woff index 48b8f836e..9b6908035 100644 Binary files a/inst/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDFwmRTA.woff and b/inst/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDFwmRTA.woff differ diff --git a/inst/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDGUmRTA.woff b/inst/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDGUmRTA.woff index e0cae253c..4f589d162 100644 Binary files a/inst/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDGUmRTA.woff and b/inst/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDGUmRTA.woff differ diff --git a/inst/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDLshRTA.woff b/inst/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDLshRTA.woff index 1545c54da..3c5f87b56 100644 Binary files a/inst/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDLshRTA.woff and b/inst/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDLshRTA.woff differ diff --git a/man/layout_column_wrap.Rd b/man/layout_column_wrap.Rd index dca6ec147..32fb97162 100644 --- a/man/layout_column_wrap.Rd +++ b/man/layout_column_wrap.Rd @@ -5,8 +5,8 @@ \title{A grid-like, column-first, layout} \usage{ layout_column_wrap( - width, ..., + width = "200px", fixed_width = FALSE, heights_equal = c("all", "row"), fill = TRUE, @@ -18,6 +18,9 @@ layout_column_wrap( ) } \arguments{ +\item{...}{Unnamed arguments should be UI elements (e.g., \code{\link[=card]{card()}}). Named +arguments become attributes on the containing \link[htmltools:builder]{htmltools::tag} element.} + \item{width}{The desired width of each card, which can be any of the following: \itemize{ @@ -38,12 +41,14 @@ manually, either via a \code{style} attribute or a CSS stylesheet. } }} -\item{...}{Unnamed arguments should be UI elements (e.g., \code{\link[=card]{card()}}) -Named arguments become attributes on the containing \link[htmltools:builder]{htmltools::tag} element.} - -\item{fixed_width}{Whether or not to interpret the \code{width} as a minimum -(\code{fixed_width=FALSE}) or fixed (\code{fixed_width=TRUE}) width when it is a CSS -length unit.} +\item{fixed_width}{When \code{width} is greater than 1 or is a CSS length unit, +e.g. \code{"200px"}, \code{fixed_width} indicates whether that \code{width} value +represents the absolute size of each column (\code{fixed_width=TRUE}) or the +minimum size of a column (\code{fixed_width=FALSE}). When \code{fixed_width=FALSE}, +new columns are added to a row when \code{width} space is available and columns +will never exceed the container or viewport size. When \code{fixed_width=TRUE}, +all columns will be exactly \code{width} wide, which may result in columns +overflowing the parent container.} \item{heights_equal}{If \code{"all"} (the default), every card in every row of the grid will have the same height. If \code{"row"}, then every card in \emph{each} row @@ -69,14 +74,20 @@ devices (or narrow windows).} \description{ Wraps a 1d sequence of UI elements into a 2d grid. The number of columns (and rows) in the grid dependent on the column \code{width} as well as the size of the -display. For more explanation and illustrative examples, see \href{https://rstudio.github.io/bslib/articles/cards.html#multiple-cards}{here} +display. For more explanation and illustrative examples, see +\href{https://rstudio.github.io/bslib/articles/cards.html#multiple-cards}{here}. } \examples{ - x <- card("A simple card") # Always has 2 columns (on non-mobile) -layout_column_wrap(1/2, x, x, x) -# Has three columns when viewport is wider than 750px -layout_column_wrap("250px", x, x, x) +layout_column_wrap(width = 1/2, x, x, x) + +# Automatically lays out three cards into columns +# such that each column is at least 200px wide: +layout_column_wrap(x, x, x) + +# To use larger column widths by default, set `width`. +# This example has 3 columns when the screen is at least 900px wide: +layout_column_wrap(width = "300px", x, x, x) } diff --git a/man/layout_columns.Rd b/man/layout_columns.Rd index 1e208c410..05425c4c5 100644 --- a/man/layout_columns.Rd +++ b/man/layout_columns.Rd @@ -16,8 +16,8 @@ layout_columns( ) } \arguments{ -\item{...}{Unnamed arguments should be UI elements (e.g., \code{\link[=card]{card()}}) -Named arguments become attributes on the containing \link[htmltools:builder]{htmltools::tag} element.} +\item{...}{Unnamed arguments should be UI elements (e.g., \code{\link[=card]{card()}}). Named +arguments become attributes on the containing \link[htmltools:builder]{htmltools::tag} element.} \item{col_widths}{One of the following: \itemize{ diff --git a/tests/testthat/test-layout.R b/tests/testthat/test-layout.R index 0f78daeb9..ee534e679 100644 --- a/tests/testthat/test-layout.R +++ b/tests/testthat/test-layout.R @@ -344,3 +344,51 @@ test_that("row_heights_css_vars() decides fr/px for numeric, passes character", "--bslib-grid--row-heights--md:10px 1fr;" ) }) + +test_that("layout_column_wrap() handles deprecated width as first arg", { + # first arg is fractional + lifecycle::expect_deprecated( + lc_implicit_width_frac <- layout_column_wrap(1/2, "one", "two") + ) + + expect_equal( + as.character(lc_implicit_width_frac), + as.character(layout_column_wrap(width = 1/2, "one", "two")) + ) + + # first arg is explicitly px character + lifecycle::expect_deprecated( + lc_implicit_width_px <- layout_column_wrap("400px", "one", "two") + ) + + expect_equal( + as.character(lc_implicit_width_px), + as.character(layout_column_wrap(width = "400px", "one", "two")) + ) + + # first arg is px, but numeric + lifecycle::expect_deprecated( + lc_implicit_width_px_implied <- layout_column_wrap(365, "one", "two") + ) + + expect_equal( + as.character(lc_implicit_width_px_implied), + as.character(layout_column_wrap(width = 365, "one", "two")) + ) + + # first arg is NULL + lifecycle::expect_deprecated( + lc_implicit_width_null <- layout_column_wrap(NULL, "one", "two") + ) + + expect_equal( + as.character(lc_implicit_width_null), + as.character(layout_column_wrap(width = NULL, "one", "two")) + ) + + # first arg is not a CSS unit + rlang::local_options(lifecycle_verbosity = "warning") + testthat::expect_silent( + as.character(layout_column_wrap("1ft", "one", "two")) + ) +})