From 01f286c126dc379ff21e65b2e2e8c0624528c790 Mon Sep 17 00:00:00 2001 From: Carson Sievert Date: Wed, 26 May 2021 12:40:01 -0500 Subject: [PATCH] More page constructors (#320) * Add page_navbar(); rename bs_page() to page() * Put all page constructor on the same Rd page * Better title/window_title handling * Don't add special title handling in page() (do that in shiny instead) * Add some snapshots tests for page_navbar() * Fix the ritual build * Document (GitHub Actions) * No warning should be thrown * Skip snapshots on R < 3.6 Co-authored-by: cpsievert --- NAMESPACE | 4 +- R/navs-legacy.R | 8 +-- R/page.R | 95 ++++++++++++++++++++++------ _pkgdown.yml | 4 +- man/bs_page.Rd | 30 --------- man/page.Rd | 112 ++++++++++++++++++++++++++++++++++ man/page_fixed.Rd | 25 -------- man/page_fluid.Rd | 26 -------- tests/testthat/_snaps/page.md | 45 ++++++++++++++ tests/testthat/test-page.R | 50 +++++++++++++++ 10 files changed, 293 insertions(+), 106 deletions(-) delete mode 100644 man/bs_page.Rd create mode 100644 man/page.Rd delete mode 100644 man/page_fixed.Rd delete mode 100644 man/page_fluid.Rd create mode 100644 tests/testthat/_snaps/page.md create mode 100644 tests/testthat/test-page.R diff --git a/NAMESPACE b/NAMESPACE index d46c5be68..3458e1a14 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -25,7 +25,6 @@ export(bs_global_get) export(bs_global_set) export(bs_global_theme) export(bs_global_theme_update) -export(bs_page) export(bs_remove) export(bs_retrieve) export(bs_theme) @@ -61,8 +60,11 @@ export(navs_hidden) export(navs_pill) export(navs_pill_list) export(navs_tab) +export(page) +export(page_fill) export(page_fixed) export(page_fluid) +export(page_navbar) export(precompiled_css_path) export(run_with_themer) export(theme_bootswatch) diff --git a/R/navs-legacy.R b/R/navs-legacy.R index e2d25a603..cab71847f 100644 --- a/R/navs-legacy.R +++ b/R/navs-legacy.R @@ -168,13 +168,13 @@ navs_bar <- function(..., title = NULL, id = NULL, selected = NULL, ) if (!is.null(bg)) { - navbar <- tagAppendAttributes( - navbar, .cssSelector = ".navbar", - style = css(background_color = paste(bg, "!important")) + # navbarPage_() returns a tagList() of the nav and content + navbar[[1]] <- tagAppendAttributes( + navbar[[1]], style = css(background_color = paste(bg, "!important")) ) } - as_fragment(navbar, page = bs_page) + as_fragment(navbar, page = page) } diff --git a/R/page.R b/R/page.R index 61aa840af..78ff9b64d 100644 --- a/R/page.R +++ b/R/page.R @@ -4,40 +4,101 @@ #' Create a Bootstrap page #' -#' Alias for [shiny::bootstrapPage()] with `theme` defaulting to bslib's -#' recommended version Bootstrap. +#' These functions are small wrappers around shiny's page constructors (i.e., [shiny::fluidPage()], [shiny::navbarPage()], etc) that differ in two ways: +#' * The `theme` parameter defaults bslib's recommended version of Bootstrap (for new projects). +#' * The return value is rendered as an static HTML page when printed interactively at the console. #' -#' @export #' @inheritParams shiny::bootstrapPage -bs_page <- function(..., title = NULL, theme = bs_theme(), lang = NULL) { +#' @seealso [shiny::bootstrapPage()] +#' @export +page <- function(..., title = NULL, theme = bs_theme(), lang = NULL) { as_page( shiny::bootstrapPage(..., title = title, theme = theme, lang = lang) ) } - -#' Create a page with fluid layout -#' -#' Alias for [shiny::fluidPage()] with `theme` defaulting to bslib's recommended -#' version Bootstrap. -#' -#' @export +#' @rdname page #' @inheritParams shiny::fluidPage +#' @seealso [shiny::fluidPage()] +#' @export page_fluid <- function(..., title = NULL, theme = bs_theme(), lang = NULL) { as_page( shiny::fluidPage(..., title = title, theme = theme, lang = lang) ) } -#' Create a page with fluid layout -#' -#' Alias for [shiny::fixedPage()] with `theme` defaulting to bslib's recommended -#' version Bootstrap. -#' -#' @export +#' @rdname page #' @inheritParams shiny::fixedPage +#' @seealso [shiny::fixedPage()] +#' @export page_fixed <- function(..., title = NULL, theme = bs_theme(), lang = NULL) { as_page( shiny::fixedPage(..., title = title, theme = theme, lang = lang) ) } + +#' @rdname page +#' @inheritParams shiny::fillPage +#' @seealso [shiny::fillPage()] +#' @export +page_fill <- function(..., padding = 0, title = NULL, + theme = bs_theme(), lang = NULL) { + as_page( + shiny::fillPage(..., padding = padding, title = title, theme = theme, lang = lang) + ) +} + +#' @rdname page +#' @inheritParams navs_bar +#' @inheritParams bs_page +#' @seealso [shiny::navbarPage()] +#' @param window_title the browser window title. The default value, `NA`, means +#' to use any character strings that appear in `title` (if none are found, the +#' host URL of the page is displayed by default). +#' @export +page_navbar <- function(..., title = NULL, id = NULL, selected = NULL, + position = c("static-top", "fixed-top", "fixed-bottom"), + header = NULL, footer = NULL, + bg = NULL, inverse = "auto", + collapsible = TRUE, fluid = TRUE, + theme = bs_theme(), + window_title = NA, + lang = NULL) { + + # https://github.com/rstudio/shiny/issues/2310 + if (!is.null(title) && isTRUE(is.na(window_title))) { + window_title <- unlist(find_characters(title)) + if (is.null(window_title)) { + warning("Unable to infer a `window_title` default from `title`. Consider providing a character string to `window_title`.") + } else { + window_title <- paste(window_title, collapse = " ") + } + } + + page( + title = window_title, + theme = theme, + lang = lang, + navs_bar( + ..., title = title, id = id, selected = selected, + position = match.arg(position), header = header, + footer = footer, bg = bg, inverse = inverse, + collapsible = collapsible, fluid = fluid + ) + ) +} + +#> unlist(find_characters(div(h1("foo"), h2("bar")))) +#> [1] "foo" "bar" +find_characters <- function(x) { + if (is.character(x)) { + return(x) + } + if (inherits(x, "shiny.tag")) { + return(lapply(x$children, find_characters)) + } + if (is.list(x)) { + return(lapply(x, find_characters)) + } + NULL +} diff --git a/_pkgdown.yml b/_pkgdown.yml index 2ba3afdd9..ecfe2c8a8 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -68,9 +68,7 @@ reference: - nav_select - title: Create a Bootstrap page contents: - - page_fluid - - page_fixed - - bs_page + - page - title: Dynamic theming description: | Create dynamically themable HTML widgets. diff --git a/man/bs_page.Rd b/man/bs_page.Rd deleted file mode 100644 index f3e7a9d4c..000000000 --- a/man/bs_page.Rd +++ /dev/null @@ -1,30 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/page.R -\name{bs_page} -\alias{bs_page} -\title{Create a Bootstrap page} -\usage{ -bs_page(..., title = NULL, theme = bs_theme(), lang = NULL) -} -\arguments{ -\item{...}{The contents of the document body.} - -\item{title}{The browser window title (defaults to the host URL of the page)} - -\item{theme}{One of the following: -\itemize{ -\item \code{NULL} (the default), which implies a "stock" build of Bootstrap 3. -\item A \code{\link[bslib:bs_theme]{bslib::bs_theme()}} object. This can be used to replace a stock -build of Bootstrap 3 with a customized version of Bootstrap 3 or higher. -\item A character string pointing to an alternative Bootstrap stylesheet -(normally a css file within the www directory, e.g. \code{www/bootstrap.css}). -}} - -\item{lang}{ISO 639-1 language code for the HTML page, such as "en" or "ko". -This will be used as the lang in the \code{} tag, as in \code{}. -The default (NULL) results in an empty string.} -} -\description{ -Alias for \code{\link[shiny:bootstrapPage]{shiny::bootstrapPage()}} with \code{theme} defaulting to bslib's -recommended version Bootstrap. -} diff --git a/man/page.Rd b/man/page.Rd new file mode 100644 index 000000000..955d40b95 --- /dev/null +++ b/man/page.Rd @@ -0,0 +1,112 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/page.R +\name{page} +\alias{page} +\alias{page_fluid} +\alias{page_fixed} +\alias{page_fill} +\alias{page_navbar} +\title{Create a Bootstrap page} +\usage{ +page(..., title = NULL, theme = bs_theme(), lang = NULL) + +page_fluid(..., title = NULL, theme = bs_theme(), lang = NULL) + +page_fixed(..., title = NULL, theme = bs_theme(), lang = NULL) + +page_fill(..., padding = 0, title = NULL, theme = bs_theme(), lang = NULL) + +page_navbar( + ..., + title = NULL, + id = NULL, + selected = NULL, + position = c("static-top", "fixed-top", "fixed-bottom"), + header = NULL, + footer = NULL, + bg = NULL, + inverse = "auto", + collapsible = TRUE, + fluid = TRUE, + theme = bs_theme(), + window_title = NA, + lang = NULL +) +} +\arguments{ +\item{...}{The contents of the document body.} + +\item{title}{The browser window title (defaults to the host URL of the page)} + +\item{theme}{One of the following: +\itemize{ +\item \code{NULL} (the default), which implies a "stock" build of Bootstrap 3. +\item A \code{\link[bslib:bs_theme]{bslib::bs_theme()}} object. This can be used to replace a stock +build of Bootstrap 3 with a customized version of Bootstrap 3 or higher. +\item A character string pointing to an alternative Bootstrap stylesheet +(normally a css file within the www directory, e.g. \code{www/bootstrap.css}). +}} + +\item{lang}{ISO 639-1 language code for the HTML page, such as "en" or "ko". +This will be used as the lang in the \code{} tag, as in \code{}. +The default (NULL) results in an empty string.} + +\item{padding}{Padding to use for the body. This can be a numeric vector +(which will be interpreted as pixels) or a character vector with valid CSS +lengths. The length can be between one and four. If one, then that value +will be used for all four sides. If two, then the first value will be used +for the top and bottom, while the second value will be used for left and +right. If three, then the first will be used for top, the second will be +left and right, and the third will be bottom. If four, then the values will +be interpreted as top, right, bottom, and left respectively.} + +\item{id}{a character string used for dynamically updating the container (see \code{\link[=nav_select]{nav_select()}}).} + +\item{selected}{a character string matching the \code{value} of a particular \code{\link[=nav]{nav()}} item to selected by default.} + +\item{position}{Determines whether the navbar should be displayed at the top +of the page with normal scrolling behavior (\code{"static-top"}), pinned at +the top (\code{"fixed-top"}), or pinned at the bottom +(\code{"fixed-bottom"}). Note that using \code{"fixed-top"} or +\code{"fixed-bottom"} will cause the navbar to overlay your body content, +unless you add padding, e.g.: \code{tags$style(type="text/css", "body + {padding-top: 70px;}")}} + +\item{header}{UI element(s) (\link{tags}) to display \emph{above} the nav content.} + +\item{footer}{UI element(s) (\link{tags}) to display \emph{below} the nav content.} + +\item{bg}{a CSS color to use for the navbar's background color.} + +\item{inverse}{Either \code{TRUE} for a light text color or \code{FALSE} for a dark +text color. If \code{"auto"} (the default), the best contrast to \code{bg} is chosen.} + +\item{collapsible}{\code{TRUE} to automatically collapse the navigation +elements into a menu when the width of the browser is less than 940 pixels +(useful for viewing on smaller touchscreen device)} + +\item{fluid}{\code{TRUE} to use fluid layout; \code{FALSE} to use fixed +layout.} + +\item{window_title}{the browser window title. The default value, \code{NA}, means +to use any character strings that appear in \code{title} (if none are found, the +host URL of the page is displayed by default).} +} +\description{ +These functions are small wrappers around shiny's page constructors (i.e., \code{\link[shiny:fluidPage]{shiny::fluidPage()}}, \code{\link[shiny:navbarPage]{shiny::navbarPage()}}, etc) that differ in two ways: +\itemize{ +\item The \code{theme} parameter defaults bslib's recommended version of Bootstrap (for new projects). +\item The return value is rendered as an static HTML page when printed interactively at the console. +} +} +\seealso{ +\code{\link[shiny:bootstrapPage]{shiny::bootstrapPage()}} + +\code{\link[shiny:fluidPage]{shiny::fluidPage()}} + +\code{\link[shiny:fixedPage]{shiny::fixedPage()}} + +\code{\link[shiny:fillPage]{shiny::fillPage()}} + +\code{\link[shiny:navbarPage]{shiny::navbarPage()}} +} diff --git a/man/page_fixed.Rd b/man/page_fixed.Rd deleted file mode 100644 index 7198bfcd9..000000000 --- a/man/page_fixed.Rd +++ /dev/null @@ -1,25 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/page.R -\name{page_fixed} -\alias{page_fixed} -\title{Create a page with fluid layout} -\usage{ -page_fixed(..., title = NULL, theme = bs_theme(), lang = NULL) -} -\arguments{ -\item{...}{Elements to include within the container} - -\item{title}{The browser window title (defaults to the host URL of the page)} - -\item{theme}{Alternative Bootstrap stylesheet (normally a css file within the -www directory). For example, to use the theme located at -\code{www/bootstrap.css} you would use \code{theme = "bootstrap.css"}.} - -\item{lang}{ISO 639-1 language code for the HTML page, such as "en" or "ko". -This will be used as the lang in the \code{} tag, as in \code{}. -The default (NULL) results in an empty string.} -} -\description{ -Alias for \code{\link[shiny:fixedPage]{shiny::fixedPage()}} with \code{theme} defaulting to bslib's recommended -version Bootstrap. -} diff --git a/man/page_fluid.Rd b/man/page_fluid.Rd deleted file mode 100644 index 4e1a65734..000000000 --- a/man/page_fluid.Rd +++ /dev/null @@ -1,26 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/page.R -\name{page_fluid} -\alias{page_fluid} -\title{Create a page with fluid layout} -\usage{ -page_fluid(..., title = NULL, theme = bs_theme(), lang = NULL) -} -\arguments{ -\item{...}{Elements to include within the page} - -\item{title}{The browser window title (defaults to the host URL of the page). -Can also be set as a side effect of the \code{\link[shiny:titlePanel]{titlePanel()}} function.} - -\item{theme}{Alternative Bootstrap stylesheet (normally a css file within the -www directory). For example, to use the theme located at -\code{www/bootstrap.css} you would use \code{theme = "bootstrap.css"}.} - -\item{lang}{ISO 639-1 language code for the HTML page, such as "en" or "ko". -This will be used as the lang in the \code{} tag, as in \code{}. -The default (NULL) results in an empty string.} -} -\description{ -Alias for \code{\link[shiny:fluidPage]{shiny::fluidPage()}} with \code{theme} defaulting to bslib's recommended -version Bootstrap. -} diff --git a/tests/testthat/_snaps/page.md b/tests/testthat/_snaps/page.md new file mode 100644 index 000000000..f9cbe8877 --- /dev/null +++ b/tests/testthat/_snaps/page.md @@ -0,0 +1,45 @@ +# page_navbar() + + Code + renderTags(page_navbar(title = div(h1("foo"), h2("bar"))))$head + Output + foo bar + +--- + + Code + renderTags(page_navbar(title = "foo", window_title = "bar"))$head + Output + bar + +--- + + Code + renderTags(page_navbar2(bg = "red", nav("a", "A")))$html + Output + +
+
+
A
+
+
+ diff --git a/tests/testthat/test-page.R b/tests/testthat/test-page.R new file mode 100644 index 000000000..2574361e7 --- /dev/null +++ b/tests/testthat/test-page.R @@ -0,0 +1,50 @@ +library(htmltools) + +# All page_*() functions are very thin wrappers around +# shiny::*Page() at the moment (except for page_navbar(), which +# which why we only have tests for page_navbar()) + +test_that("page_navbar()", { + + expect_snapshot( + renderTags( + page_navbar( + title = div(h1("foo"), h2("bar")) + ) + )$head, + cran = TRUE + ) + + expect_snapshot( + renderTags( + page_navbar( + title = "foo", + window_title = "bar" + ) + )$head, + cran = TRUE + ) + + expect_snapshot2 <- function(...) { + if (getRversion() < "3.6.0") { + skip("Skipping snapshots on R < 3.6 because of different RNG method") + } + expect_snapshot(...) + } + + page_navbar2 <- function(...) { + shiny:::withPrivateSeed(set.seed(100)) + page_navbar(...) + } + + expect_snapshot2( + renderTags( + page_navbar2( + bg = "red", + nav("a", "A") + ) + )$html, + cran = TRUE + ) + +})