Skip to content

Commit

Permalink
feat(bs_theme): Separate preset and brand arguments
Browse files Browse the repository at this point in the history
Tests are not updated yet
  • Loading branch information
gadenbuie committed Dec 13, 2024
1 parent b6314ca commit dfd71ab
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 86 deletions.
9 changes: 5 additions & 4 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
107 changes: 68 additions & 39 deletions R/bs-theme-preset-brand.R
Original file line number Diff line number Diff line change
@@ -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")
),
Expand All @@ -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 -------------------------------------------------------------------
Expand Down
3 changes: 1 addition & 2 deletions R/bs-theme-preset-builtin.R
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
19 changes: 1 addition & 18 deletions R/bs-theme-preset.R
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
Expand Down
56 changes: 37 additions & 19 deletions R/bs-theme.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -140,6 +154,7 @@ bs_theme <- function(
version = version_default(),
preset = NULL,
...,
brand = TRUE,
bg = NULL,
fg = NULL,
primary = NULL,
Expand All @@ -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)) {
Expand All @@ -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
Expand Down
13 changes: 9 additions & 4 deletions inst/examples-shiny/brand/app.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
library(shiny)
# pkgload::load_all()
library(bslib)
pkgload::load_all()
# library(bslib)
library(ggplot2)

options(bslib.color_contrast_warnings = FALSE)
Expand All @@ -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(
Expand Down

0 comments on commit dfd71ab

Please sign in to comment.