Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Depends:
R (>= 2.10)
Imports:
base64enc,
brand.yml (>= 0.1.0.9000),
cachem,
fastmap (>= 1.1.1),
grDevices,
Expand All @@ -42,7 +43,6 @@ Imports:
rlang,
sass (>= 0.4.9)
Suggests:
brand.yml,
bsicons,
curl,
fontawesome,
Expand All @@ -60,6 +60,8 @@ Suggests:
utils,
withr,
yaml
Remotes:
posit-dev/brand-yml@feat/brand-color-light-dark
Config/Needs/deploy: BH, chiflights22, colourpicker, commonmark, cpp11,
cpsievert/chiflights22, cpsievert/histoslider, dplyr, DT, ggplot2,
ggridges, gt, hexbin, histoslider, htmlwidgets, lattice, leaflet,
Expand Down
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Improvements

* `bs_theme(brand = ...)` now supports light and dark color and typography
variants from `{brand.yml}`. Bootstrap switches the generated variables
through its existing `data-bs-theme` attribute. (posit-dev/brand-yml#120)

* Navsets created with an `id` (e.g. `navset_tab(id = "tabs")`) now use that `id` as their `data-tabsetid`, so their tab panes get stable `tab-tabs-1` style DOM ids instead of ones derived from a random integer. This makes the rendered markup reproducible across renders and easier to target from custom CSS and JavaScript. Navsets without an `id`, and `nav_menu()` dropdowns, keep the random ID. (#1342)

# bslib 0.12.0
Expand Down
147 changes: 147 additions & 0 deletions R/bs-dependencies.R
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ maybe_precompiled_css <- function(theme, sass_options, precompiled) {

sass_compile_theme <- function(theme, sass_options, sass_cache) {
version <- theme_version(theme)
brand <- attr(theme, "brand")

contrast_warn <- get_shiny_devmode_option(
"bslib.color_contrast_warnings",
Expand All @@ -155,6 +156,7 @@ sass_compile_theme <- function(theme, sass_options, sass_cache) {
)
)
theme <- bs_add_variables(theme, "color-contrast-warnings" = contrast_warn)
attr(theme, "brand") <- brand

out_file <- sass(
input = theme,
Expand All @@ -168,11 +170,156 @@ sass_compile_theme <- function(theme, sass_options, sass_cache) {
)
)

dark_css <- bs_brand_dark_css(
theme = theme,
sass_options = sass_options,
sass_cache = sass_cache
)
if (nzchar(dark_css)) {
out_file <- append_brand_dark_css(out_file, dark_css)
}

bootstrap_javascript_copy_assets(version, dirname(out_file))

out_file
}

bs_brand_dark_css <- function(theme, sass_options, sass_cache) {
brand <- attr(theme, "brand")
if (is.null(brand) || theme_version(theme) < 5) {
return("")
}

dark_theme <- bs_brand_dark_theme(theme, brand)
css <- sass(
input = dark_theme,
options = sass_options,
cache = sass_cache,
cache_key_extra = list(
"bslib-brand-dark",
get_exact_version(theme_version(theme)),
get_package_version("bslib")
)
)

sub(
":root\\s*,\\s*\\[data-bs-theme=(?:\"light\"|light)\\]",
'[data-bs-theme="dark"]',
css,
perl = TRUE
)
}

bs_brand_dark_theme <- function(theme, brand) {
brand_layers <- c("brand_base", "brand_defaults", "brand")
brand_idx <- match("brand_base", names(theme$layers))
if (is.na(brand_idx)) {
return(theme)
}

layers <- theme$layers[!names(theme$layers) %in% brand_layers]
dark_bundle <- bs_brand_dark_bundle(brand)
dark_layers <- dark_bundle$layers
layers <- append(layers, dark_layers, after = brand_idx - 1L)

# Bootstrap components contain Sass-derived colors that root variables alone
# cannot switch. Recompile the theme's rules inside the dark mode selector.
scoped_rules <- if (isTRUE(attr(dark_bundle, "has_dark_variants"))) {
unlist(
lapply(seq_along(layers), function(i) {
if (identical(names(layers)[[i]], "_root")) {
return(NULL)
}
layers[[i]]$rules
}),
recursive = FALSE
)
}

layers <- lapply(layers, function(layer) {
layer$rules <- NULL
layer
})
dark_rule_layers <- list(
"_brand_dark_root" = as_sass_layer(sass_layer(
rules = bs5_sass_files("root")
))
)
if (length(scoped_rules)) {
dark_rule_layers[["_brand_dark_components"]] <- as_sass_layer(sass_layer(
rules = c(
list('[data-bs-theme="dark"] {'),
scoped_rules,
list("}")
)
))
}
dark_rule_layers[["_brand_dark_runtime"]] <- as_sass_layer(sass_layer_file(
system_file(
"brand",
"bs5",
"_brand-yml-dark-rules.scss",
package = "bslib"
)
))
layers <- c(layers, dark_rule_layers)

structure(
list(layers = layers),
class = class(theme)
)
}

bs_brand_dark_bundle <- function(brand) {
brand_fonts <- brand.yml::brand_sass_fonts(brand)
brand_color_palette <- brand.yml::brand_sass_color_palette(brand)
brand_color <- brand.yml::brand_sass_color(brand)
brand_defaults <- brand.yml::brand_sass_defaults_bootstrap(brand)
brand_typography <- brand.yml::brand_sass_typography(brand)

bundle <- sass_bundle(
"brand_dark_base" = sass_layer_file(
system_file("brand", "bs5", "_brand-yml-dark.scss", package = "bslib")
),
"brand_dark_bootstrap_defaults" = brand_defaults$layer,
"brand_dark_defaults" = sass_layer(
defaults = list2(
!!!brand_color_palette$defaults,
!!!brand_defaults$defaults,
!!!(brand_color$defaults_dark %||% brand_color$defaults),
!!!brand_fonts$defaults,
!!!(brand_typography$defaults_dark %||% brand_typography$defaults)
)
)
)
attr(bundle, "has_dark_variants") <-
!is.null(brand_color$defaults_dark) ||
!is.null(brand_typography$defaults_dark)
bundle
}

append_brand_dark_css <- function(out_file, dark_css) {
suffix <- substr(rlang::hash(dark_css), 1L, 12L)
ext <- tools::file_ext(out_file)
out_file_dark <- file.path(
dirname(out_file),
sprintf(
"%s-brand-dark-%s.%s",
tools::file_path_sans_ext(basename(out_file)),
suffix,
ext
)
)

if (!file.exists(out_file_dark)) {
file.copy(out_file, out_file_dark)
cat("\n", dark_css, "\n", file = out_file_dark, append = TRUE, sep = "")
}

attributes(out_file_dark) <- attributes(out_file)
out_file_dark
}

bootstrap_javascript_copy_assets <- function(version, to) {
js_files <- bootstrap_javascript(version)
js_map_files <- bootstrap_javascript_map(version)
Expand Down
6 changes: 5 additions & 1 deletion R/bs-remove.R
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@
#' bs4_no_compat <- bs_remove(bs4, "bs3compat")
bs_remove <- function(theme, ids = character(0)) {
ids <- retain_valid_ids(theme, ids)
sass_bundle_remove(theme, ids)
result <- preserve_theme_attributes(sass_bundle_remove(theme, ids), theme)
if (any(ids %in% c("brand_base", "brand_defaults", "brand"))) {
attr(result, "brand") <- NULL
}
result
}

#' @rdname bs_remove
Expand Down
14 changes: 13 additions & 1 deletion R/bs-theme-layers.R
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,20 @@ bs_add_mixins <- function(theme, mixins) {
#' @export
bs_bundle <- function(theme, ...) {
assert_bs_theme(theme)
structure(
result <- structure(
sass_bundle(theme, ...),
class = class(theme)
)
preserve_theme_attributes(result, theme)
}

preserve_theme_attributes <- function(result, theme) {
extra <- attributes(theme)
extra[c("names", "class")] <- NULL

result_attrs <- attributes(result)
result_attrs[names(extra)] <- extra
attributes(result) <- result_attrs

result
}
18 changes: 18 additions & 0 deletions inst/brand/bs5/_brand-yml-dark-rules.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*-- scss:rules --*/

[data-bs-theme="dark"] {
@if $link-bg == null {
--#{$prefix}link-bg: initial;
} @else {
--#{$prefix}link-bg: #{$link-bg};
}
}

[data-bs-theme="dark"] code {
background-color: $code-bg;
}

[data-bs-theme="dark"] pre {
color: $pre-color;
background-color: $pre-bg;
}
70 changes: 70 additions & 0 deletions inst/brand/bs5/_brand-yml-dark.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*-- scss:defaults --*/

// Sass variables from the dark brand layer are inserted above this file. This
// isolated compile maps them to both the default and dark Bootstrap variables,
// so Bootstrap's root stylesheet can generate a complete dark-mode override.
$brand_color_foreground: null !default;
$brand_color_background: null !default;
$brand_color_primary: null !default;
$brand_color_secondary: null !default;
$brand_color_tertiary: null !default;
$brand_color_success: null !default;
$brand_color_info: null !default;
$brand_color_warning: null !default;
$brand_color_danger: null !default;
$brand_color_light: null !default;
$brand_color_dark: null !default;
$brand_color_link: null !default;
$brand_typography_base_family: null !default;
$brand_typography_base_size: null !default;
$brand_typography_base_line-height: null !default;
$brand_typography_base_weight: null !default;
$brand_typography_headings_color: null !default;
$brand_typography_monospace_inline_color: null !default;
$brand_typography_monospace_inline_background_color: null !default;
$brand_typography_monospace_block_color: null !default;
$brand_typography_monospace_block_background_color: null !default;
$brand_typography_link_color: null !default;
$brand_typography_link_background_color: null !default;

$primary: $brand_color_primary !default;
$secondary: $brand_color_secondary !default;
$tertiary: $brand_color_tertiary !default;
$success: $brand_color_success !default;
$info: $brand_color_info !default;
$warning: $brand_color_warning !default;
$danger: $brand_color_danger !default;
$light: $brand_color_light !default;
$dark: $brand_color_dark !default;

$body-color: $brand_color_foreground !default;
$body-bg: $brand_color_background !default;
$body-color-dark: $brand_color_foreground !default;
$body-bg-dark: $brand_color_background !default;
$body-secondary-color: $brand_color_secondary !default;
$body-secondary-color-dark: $brand_color_secondary !default;
$body-tertiary-color: $brand_color_tertiary !default;
$body-tertiary-color-dark: $brand_color_tertiary !default;

$font-family-base: $brand_typography_base_family !default;
$font-size-base: $brand_typography_base_size !default;
$line-height-base: $brand_typography_base_line-height !default;
$font-weight-base: $brand_typography_base_weight !default;
$headings-color: $brand_typography_headings_color !default;
$headings-color-dark: $brand_typography_headings_color !default;
$code-color: $brand_typography_monospace_inline_color !default;
$code-color-dark: $brand_typography_monospace_inline_color !default;
$code-bg: $brand_typography_monospace_inline_background_color !default;
$pre-color: $brand_typography_monospace_block_color !default;
$pre-bg: $brand_typography_monospace_block_background_color !default;
$link-color: if(
$brand_typography_link_color != null,
$brand_typography_link_color,
$brand_color_link
) !default;
$link-color-dark: if(
$brand_typography_link_color != null,
$brand_typography_link_color,
$brand_color_link
) !default;
$link-bg: $brand_typography_link_background_color !default;
13 changes: 11 additions & 2 deletions inst/brand/bs5/_brand-yml.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ $brand_color_warning: null !default;
$brand_color_danger: null !default;
$brand_color_light: null !default;
$brand_color_dark: null !default;
$brand_color_link: null !default;
$brand_typography_base_family: null !default;
$brand_typography_base_size: null !default;
$brand_typography_base_line-height: null !default;
Expand Down Expand Up @@ -120,8 +121,16 @@ $code-block-font-weight: $brand_typography_monospace-block_weight !default;
$code-block-font-size: $brand_typography_monospace-block_size !default;
// brand.typography.link
$link-bg: $brand_typography_link_background-color !default;
$link-color: $brand_typography_link_color !default;
$link-color-dark: $brand_typography_link_color !default;
$link-color: if(
$brand_typography_link_color != null,
$brand_typography_link_color,
$brand_color_link
) !default;
$link-color-dark: if(
$brand_typography_link_color != null,
$brand_typography_link_color,
$brand_color_link
) !default;
$link-weight: $brand_typography_link_weight !default;
$link-decoration: $brand_typography_link_decoration !default;

Expand Down
Loading
Loading