From 3b0b564a4b06388b0f955f0d9ea27fbba99ae263 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 19 Oct 2023 16:25:00 -0400 Subject: [PATCH] breaking(layout_column_wrap): Make `width` optional (#853) --- NEWS.md | 1 + R/layout.R | 51 +++++++++++++++++++++++++++++------- man/layout_column_wrap.Rd | 24 ++++++++++------- man/layout_columns.Rd | 4 +-- tests/testthat/test-layout.R | 48 +++++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 20 deletions(-) diff --git a/NEWS.md b/NEWS.md index fbb161930..534c0a3f4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,7 @@ * `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) * `page()` now returns a `` tag instead of `tagList()`. This change allows `page()` to treat named arguments as HTML attributes. (#809) * The JS/CSS assets behind `{bslib}` components (e.g., `card()`, `value_box()`, etc) are all now bundled into one `htmlDependency()` and included with the return value of `bs_theme_dependencies()` (previously they were attached at the component-level). (#810) +* `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`. (#853) ## New features diff --git a/R/layout.R b/R/layout.R index 5dc2df1a8..57dad2c7a 100644 --- a/R/layout.R +++ b/R/layout.R @@ -5,10 +5,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. @@ -39,18 +40,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, @@ -67,6 +73,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.") } @@ -118,6 +141,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 12-column grid layouts #' #' Create responsive, column-based grid layouts, based on a 12-column grid. diff --git a/man/layout_column_wrap.Rd b/man/layout_column_wrap.Rd index f4982ec74..0753cd584 100644 --- a/man/layout_column_wrap.Rd +++ b/man/layout_column_wrap.Rd @@ -5,8 +5,8 @@ \title{Column-first uniform grid layouts} \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,9 +41,6 @@ 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}{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 @@ -76,14 +76,20 @@ devices (or narrow windows).} 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 e988226ff..cce213f66 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")) + ) +})