diff --git a/NAMESPACE b/NAMESPACE index aca67ec2c..ef4ea6524 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,10 +3,11 @@ S3method(as.tags,bslib_sidebar) S3method(bind_task_button,ExtendedTask) S3method(bind_task_button,default) -S3method(brand_resolve_preset,"NULL") -S3method(brand_resolve_preset,brand_yml) -S3method(brand_resolve_preset,character) -S3method(brand_resolve_preset,list) +S3method(brand_resolve,"NULL") +S3method(brand_resolve,brand_yml) +S3method(brand_resolve,character) +S3method(brand_resolve,list) +S3method(brand_resolve,logical) S3method(is_fill_item,default) S3method(is_fill_item,htmlwidget) S3method(is_fillable_container,default) diff --git a/R/bs-theme-preset-brand.R b/R/bs-theme-preset-brand.R index 4bd8a64e1..7dd8e7d32 100644 --- a/R/bs-theme-preset-brand.R +++ b/R/bs-theme-preset-brand.R @@ -1,18 +1,47 @@ -bs_preset_brand_bundle <- function(brand_preset = NULL) { - if (is.null(brand_preset)) { - brand_preset <- brand_resolve_preset() - } - - brand_fonts <- brand_sass_fonts(brand_preset$brand) - brand_color_palette <- brand_sass_color_palette(brand_preset$brand) - brand_color <- brand_sass_color(brand_preset$brand) - brand_defaults <- brand_sass_defaults_bootstrap(brand_preset$brand) - brand_typography <- brand_sass_typography(brand_preset$brand) +# "brand" and "preset" interact in a few ways that need to be reconciled. +# Ultimately, we want three layers: +# +# 1. The base Bootstrap styles, by version. E.g. Bootstrap 5 +# 2. The `preset` styles, which are one layer above and adjust the base +# Bootstrap with the preset defaults E.g. "shiny" (bslib) or "flatly" (Bootswatch) +# 3. The `brand` styles, which can be seen as the final layer of default styles. +# These styles set Bootstrap variables to adjust the `preset` theme in the +# direction of the brand's color palette and typography. +# +# The `preset` and `version` can be specified in two ways with `brand`: +# +# 1. Directly via `bs_theme()`: +# `bs_theme(preset = "shiny", version = 5, brand = TRUE)`. +# +# 2. As part of the `brand` definition: +# `bs_theme(brand = list(defaults = list(shiny = list(theme = list(preset = "flatly")))))` +# +# So the order of operations is: +# +# 1. Read `brand`, if necessary. +# 2. If `bs_theme()` provides guidance for `version` and `theme`, use those preferences. +# 3. If not, inspect `brand` for guidance. +# 4. Finally, use our own defaults. +# +# Importantly we need to separately read `brand` and then resolve the preset and +# then resolve the brand bundle. + +bs_brand_bundle <- function(brand, version = version_default()) { + brand <- brand_resolve(brand) + + if (is.null(brand)) { + return() + } + + brand_fonts <- brand_sass_fonts(brand) + brand_color_palette <- brand_sass_color_palette(brand) + brand_color <- brand_sass_color(brand) + brand_defaults <- brand_sass_defaults_bootstrap(brand) + brand_typography <- brand_sass_typography(brand) sass_bundle( - "base" = bs_preset_bundle(brand_preset$preset), "brand_base" = switch_version( - brand_preset$version, + version, five = sass_layer_file( system_file("brand", "bs5", "_brand-yml.scss", package = "bslib") ), @@ -39,54 +68,54 @@ bs_preset_brand_bundle <- function(brand_preset = NULL) { ) } -brand_resolve_preset <- function(brand, ..., version = NULL) { - UseMethod("brand_resolve_preset") +brand_resolve <- function(brand, ...) { + UseMethod("brand_resolve") } #' @export -brand_resolve_preset.list <- function(brand, ..., version = NULL) { +brand_resolve.list <- function(brand, ...) { brand <- as_brand_yml(brand) - brand_resolve_preset(brand, ..., version = version) + brand_resolve(brand, ...) } #' @export -`brand_resolve_preset.NULL` <- function(brand, ..., version = NULL) { - brand <- read_brand_yml(brand) - brand_resolve_preset(brand, ..., version = version) +`brand_resolve.NULL` <- function(brand, ...) { + NULL +} + +#' @export +brand_resolve.logical <- function(brand, ...) { + if (identical(brand, FALSE)) { + return() + } + brand <- read_brand_yml(NULL) + brand_resolve(brand, ...) # future compat if we add anything to the ... } #' @export -brand_resolve_preset.character <- function(brand, ..., version = NULL) { +brand_resolve.character <- function(brand, ...) { brand <- read_brand_yml(brand) - brand_resolve_preset(brand, ..., version = version) + brand_resolve(brand, ...) } #' @export -brand_resolve_preset.brand_yml <- function(brand = NULL, ..., version = NULL) { - base_version <- +brand_resolve.brand_yml <- function(brand, ...) { + brand +} + +brand_resolve_preset <- function(brand, preset = NULL, version = NULL) { + version_resolved <- version %||% b_get(brand, "defaults", "shiny", "theme", "version") %||% b_get(brand, "defaults", "bootstrap", "version") %||% version_default() - base_theme_preset <- b_get(brand, "defaults", "shiny", "theme", "preset") %||% + preset_resolved <- + preset %||% + b_get(brand, "defaults", "shiny", "theme", "preset") %||% switch_version(base_version, five = "shiny", default = "bootstrap") - if (!rlang::is_string(base_theme_preset) || base_theme_preset == "brand") { - abort( - "brand.defaults.shiny.theme.preset must be a string and cannot be 'brand'." - ) - } - - base_preset <- resolve_bs_preset(base_theme_preset, version = base_version) - - new_bs_preset( - type = "brand", - name = b_get(brand, "meta", "name", "short"), - version = base_preset$version, - brand = brand, - preset = base_preset - ) + resolve_bs_preset(preset_resolved, version = version_resolved) } # Brand Sass ------------------------------------------------------------------- diff --git a/R/bs-theme-preset-builtin.R b/R/bs-theme-preset-builtin.R index c1265437f..65b018dd1 100644 --- a/R/bs-theme-preset-builtin.R +++ b/R/bs-theme-preset-builtin.R @@ -12,8 +12,7 @@ builtin_themes <- function(version = version_default(), full_path = FALSE) { path_builtins <- path_builtin_theme(version = version) if (is.null(path_builtins)) return(NULL) - packaged <- list.dirs(path_builtins, full.names = full_path, recursive = FALSE) - c(packaged, "brand") + list.dirs(path_builtins, full.names = full_path, recursive = FALSE) } builtin_bundle <- function(name = "shiny", version = version_default()) { diff --git a/R/bs-theme-preset.R b/R/bs-theme-preset.R index 5960e3990..5d92d9046 100644 --- a/R/bs-theme-preset.R +++ b/R/bs-theme-preset.R @@ -11,19 +11,7 @@ resolve_bs_preset <- function( if (!is.null(version)) { version <- switch_version(version, five = "5", four = "4", three = "3") } - - if (is.list(preset)) { - if (!"brand" %in% names(preset)) { - abort("If `preset` is a list, it may only contain a single `brand` key.") - } - if (!is.character(preset$brand) && !is.list(preset$brand)) { - abort("`preset$brand` must be a path to a brand.yml file or a brand definition as a list.") - } - return( - brand_resolve_preset(preset$brand, version = version) - ) - } - + assert_preset_scalar_string(preset) assert_preset_scalar_string(bootswatch) assert_preset_only_one_name_arg(preset, bootswatch) @@ -33,11 +21,6 @@ resolve_bs_preset <- function( if (preset_name %in% c("default", "bootstrap")) { # "bootstrap" means no preset bundle, just bare default Bootstrap return(new_bs_preset("bootstrap", version %||% version_default())) - } else if (preset_name == "brand") { - # "brand" means we go find `_brand.yml` to create the preset bundle - return( - brand_resolve_preset(brand = NULL, version = version) - ) } version <- version %||% version_default() diff --git a/R/bs-theme.R b/R/bs-theme.R index dc57df907..16b563e88 100644 --- a/R/bs-theme.R +++ b/R/bs-theme.R @@ -86,6 +86,20 @@ #' higher, `preset` defaults to `"shiny"`. To remove the `"shiny"` preset, #' provide a value of `"bootstrap"` (this value will also work in #' `bs_theme_update()` to remove a `preset` or `bootswatch` theme). +#' @param brand Specifies how to apply branding to your theme using +#' [brand.yml](https://posit-dev.github.io/brand-yml), a simple YAML file that +#' defines key brand elements like colors, fonts, and logos. Valid options: +#' +#' - `TRUE` (default): Automatically looks for a `_brand.yml` file in the +#' current or app directory. +#' - `FALSE`: Disables any brand.yml usage. +#' - A file path that directly points to a specific brand.yml file you want to +#' use. +#' - Use a list to directly provide brand settings directly in R, following +#' the brand.yml structure. +#' +#' Learn more about creating and using brand.yml files at the +#' [brand.yml homepage](https://posit-dev.github.io/brand-yml). #' @param bootswatch The name of a bootswatch theme (see [bootswatch_themes()] #' for possible values). When provided to `bs_theme_update()`, any previous #' Bootswatch theme is first removed before the new one is applied (use @@ -140,6 +154,7 @@ bs_theme <- function( version = version_default(), preset = NULL, ..., + brand = TRUE, bg = NULL, fg = NULL, primary = NULL, @@ -154,27 +169,30 @@ bs_theme <- function( font_scale = NULL, bootswatch = NULL ) { - is_default_version <- missing(version) + is_version_from_user <- !missing(version) - if (is.null(preset) && is.null(bootswatch) && version >= 5) { - preset <- "shiny" - } + brand <- brand_resolve(brand) + + preset <- + if (!is.null(brand)) { + brand_resolve_preset( + brand = brand, + preset = preset, + version = if (is_version_from_user) version + ) + } else { + if (is.null(preset) && is.null(bootswatch) && version >= 5) { + preset <- "shiny" + } + resolve_bs_preset(preset, bootswatch, version = version) + } - preset <- resolve_bs_preset( - preset, - bootswatch, - version = if (!is_default_version) version - ) - - use_brand_version <- is_default_version && identical(preset$type, "brand") - if (is.null(version) || use_brand_version) { - version <- preset$version - } bundle <- bs_bundle( - bs_theme_init(version), - bootstrap_bundle(version), - bs_preset_bundle(preset) + bs_theme_init(preset$version), + bootstrap_bundle(preset$version), + bs_preset_bundle(preset), + bs_brand_bundle(brand, version = preset$version) ) if (!is.null(preset$type)) { @@ -196,9 +214,9 @@ bs_theme <- function( font_scale = font_scale ) - if (identical(preset$type, "brand")) { + if (!is.null(brand)) { bundle <- add_class(bundle, "bs_theme_brand") - attr(bundle, "brand") <- preset$brand + attr(bundle, "brand") <- brand } bundle diff --git a/inst/examples-shiny/brand/app.R b/inst/examples-shiny/brand/app.R index 8d07811fa..fb5dc4c23 100644 --- a/inst/examples-shiny/brand/app.R +++ b/inst/examples-shiny/brand/app.R @@ -1,6 +1,6 @@ library(shiny) -# pkgload::load_all() -library(bslib) +pkgload::load_all() +# library(bslib) library(ggplot2) options(bslib.color_contrast_warnings = FALSE) @@ -16,13 +16,18 @@ if (!file.exists("Monda.ttf")) { ) } -theme_brand <- bs_theme(preset = "brand") +theme_brand <- bs_theme(brand = TRUE) + brand <- attr(theme_brand, "brand") theme_set(theme_minimal()) if (requireNamespace("thematic", quietly = TRUE)) { - thematic::thematic_shiny(font = brand$typography$base$family) + if (!is.null(brand)) { + thematic::thematic_shiny(font = bslib:::b_get(brand, "typography", "base", "family")) + } else { + thematic::thematic_shiny() + } } ui <- page_navbar(