Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supply smarter theming defaults if a {bslib} theme is active #96

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
6 changes: 5 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ BugReports: https://github.com/glin/reactable/issues
Depends:
R (>= 3.1)
Imports:
utils,
digest,
htmltools,
htmlwidgets,
@@ -31,8 +32,11 @@ Suggests:
rmarkdown,
shiny,
sparkline,
testthat
testthat,
bslib,
sass
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.1.1
Remotes: rstudio/bslib
134 changes: 134 additions & 0 deletions R/bslib.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
supplyBsThemeDefaults <- function(instance) {
if (system.file(package = "bslib") == "") {
return(instance)
}
theme <- bslib::bs_current_theme()
if (!bslib::is_bs_theme(theme)) {
return(instance)
}
# If a bslib theme is relevant, supply new reactableTheme() defaults
# based on the relevant Bootstrap Sass variables
themeVars <- getThemeVars(theme)
for (x in names(themeVars)) {
instance$x$tag$attribs$theme[[x]] <-
instance$x$tag$attribs$theme[[x]] %||% themeVars[[x]]
}
styleVals <- getStyleVals(theme)
for (x in names(styleVals)) {
vals <- styleVals[[x]]
if (isTRUE(is.na(vals))) next # failed to parse Sass rules
instance$x$tag$attribs$theme[[x]] <- utils::modifyList(
vals, instance$x$tag$attribs$theme[[x]] %||% list()
)
}

instance
}


getThemeVars <- function(theme) {
map <- if (is_bs3(theme)) bsVariableMap3 else bsVariableMap
vars <- bslib::bs_get_variables(theme, as.character(map))
vars <- setNames(vars, names(map))
vars[!is.na(vars)]
}

# Map the non-style reactableTheme() settings to main Bootstrap Sass variables
bsVariableMap <- c(
color = "table-color",
borderColor = "table-border-color",
borderWidth = "table-border-width",
stripedColor = "table-accent-bg",
highlightColor = "primary",
cellPadding = "table-cell-padding"
)

bsVariableMap3 <- c(
color = "text-color",
borderColor = "table-border-color",
stripedColor = "table-bg-accent",
highlightColor = "brand-primary",
cellPadding = "table-cell-padding"
)


getStyleVals <- function(theme) {
lapply(bsStyleMap, computeStyles, theme = theme)
}

computeStyles <- function(x, theme) {
# Handle BS3isms (without requiring a different bsStyleMap)
if (is_bs3(theme)) {
theme <- bslib::bs_add_variables(
theme, "input-border-width" = "1px",
"pagination-border-width" = "1px",
"pagination-border-color" = "$pagination-border",
"pagination-hover-border-color" = "$pagination-hover-border",
"pagination-active-border-color" = "$pagination-active-border",
.where = "declarations"
)
}
# Try to compile the Sass rules. Note that an error could happen
# if Bootstrap Sass variables change in future versions.
# (In that case, we'll need to update accordingly to support BS5+)
prop_string <- paste0(names(x), ":", x, collapse = ";")
res <- try(
sass::sass_partial(
paste0(".fake-selector{", prop_string, "}"),
theme, options = sass::sass_options(output_style = "compressed")
),
silent = TRUE
)
if (inherits(res, "try-error")) {
warning(
"Failed to compute the following Sass rule(s) '", prop_string, "'. ",
"{reactable}'s theming defaults may not reflect the {bslib} theme.",
call. = FALSE
)
return(NA)
}
matches <- regmatches(res, regexec(".fake-selector\\s*\\{(.+)\\}", res))
asReactStyle(matches[[1]][2])
}

bsStyleMap <- list(
style = list(
fontFamily = "$font-family-base",
backgroundColor = "if($table-bg==null or alpha($table-bg)==0, $body-bg, $table-bg)"
),
headerStyle = list(
fontFamily = "$headings-font-family"
),
rowHighlightStyle = list(
color = "color-contrast($primary)"
),
inputStyle = list(
color = "$input-color",
backgroundColor = "$input-bg",
border = "$input-border-width solid $input-border-color"
),
pageButtonStyle = list(
color = "$pagination-color",
backgroundColor = "$pagination-bg",
border = "$pagination-border-width solid $pagination-border-color"
),
pageButtonHoverStyle = list(
color = "$pagination-hover-color",
backgroundColor = "$pagination-hover-bg",
border = "$pagination-border-width solid $pagination-hover-border-color"
),
pageButtonActiveStyle = list(
color = "$pagination-active-color",
backgroundColor = "$pagination-active-bg",
border = "$pagination-border-width solid $pagination-active-border-color"
),
pageButtonCurrentStyle = list(
color = "$pagination-active-color",
backgroundColor = "$pagination-active-bg",
border = "$pagination-border-width solid $pagination-active-border-color"
)
)

is_bs3 <- function(theme) {
"3" %in% bslib::theme_version(theme)
}
7 changes: 5 additions & 2 deletions R/reactable.R
Original file line number Diff line number Diff line change
@@ -597,12 +597,15 @@ reactable <- function(data, columns = NULL, columnGroups = NULL,

htmlwidgets::createWidget(
name = "reactable",
reactR::reactMarkup(component),
x = reactR::reactMarkup(component),
width = width,
height = height,
package = "reactable",
dependencies = dependencies,
elementId = elementId
elementId = elementId,
preRenderHook = function(instance) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific reason to set the theme in preRenderHook instead of reactable()? I guess I'm just generally wondering what preRenderHook is used for, since there's not much documentation about it out there.

Copy link
Contributor Author

@cpsievert cpsievert Jan 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific reason to set the theme in preRenderHook instead of reactable()?

Yes, preRenderHook gets called at print-time, just before the widget is serialized into JSON. When calling bs_current_theme() at print-time, we can have some guarantee of knowing when a {bslib} theme has been provided to a page layout function like fluidPage():

library(shiny)

ui <- fluidPage(
  theme = bslib::bs_theme(),
  verbatimTextOutput("txt")
)

server <- function(input, output, session) {
  output$txt <- renderPrint(bslib::bs_current_theme())
}

shinyApp(ui, server)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh gotcha, that makes sense.

supplyBsThemeDefaults(instance)
}
)
}

4 changes: 4 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
@@ -246,3 +246,7 @@ callFunc <- function(func, ...) {
numArgs <- length(formals(func))
do.call(func, args[seq_len(numArgs)])
}

"%||%" <- function(x, y) {
if (is.null(x)) y else x
}