Skip to content
Open
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
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Imports:
DT (>= 0.34.0),
gt (>= 1.3.0),
magrittr (>= 2.0.3),
reactable (>= 0.4.5),
rlang (>= 1.1.0),
shiny (>= 1.14.0),
ROvis.utils
Expand Down
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
export(ro_dt_table)
export(ro_dt_theme)
export(ro_gt_theme)
export(ro_reactable_css)
export(ro_reactable_theme)
import(rlang)
importFrom(DT,
JS,
Expand Down Expand Up @@ -31,6 +33,7 @@ importFrom(gt,
where
)
importFrom(magrittr,"%>%")
importFrom(reactable,reactable)
importFrom(shiny,
HTML,
tags
Expand Down
1 change: 1 addition & 0 deletions R/ROvis.table-package.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#' @importFrom magrittr %>%
#' @importFrom DT datatable
#' @importFrom gt gt
#' @importFrom reactable reactable
#' @importFrom ROvis.utils ro_check_if_font_available
## usethis namespace: end
NULL
234 changes: 234 additions & 0 deletions R/ro_reactable_theme.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
#' Reactable theme following Rijksoverheid huisstijl.
#' @description
#' Creates a `reactableTheme` object with table styling according to the
#' Rijksoverheid huisstijl.
#'
#' @importFrom ROvis.utils ro_check_if_font_available
#'
#' @param base_family Character. Font family to use for table text. Default is
#' "RijksoverheidSansWebText". If not installed, Verdana is used, then Arial,
#' then the first font found on your device, whichever is installed first.
#' @param sorting Logical. If `TRUE`, extra space is added to the right
#' in the column header for sort arrows. Default `FALSE`.
#'
#' @return A `reactable::reactableTheme` object.
#' @export
#' @family reactable
ro_reactable_theme <- function(
base_family = "RijksoverheidSansWebText",
sorting = FALSE
) {
check_string(base_family)

base_family <- ROvis.utils::ro_check_if_font_available(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe something minor (I'm not sure if it has impact on anything): The gt and DT theme call it without the ROvis.utils::. It's already loaded with the NAMESPACE importFrom

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's already imported here:

#' @importFrom ROvis.utils ro_check_if_font_available
so no need to put importFrom here or call ROvis.utils::

target_font_family = base_family
)

header_padding <- if (sorting) "10px" else "3px"

reactable::reactableTheme(
tableStyle = list(
border = "1px solid #bababa",
borderCollapse = "collapse",
fontFamily = base_family
),
headerStyle = list(
fontFamily = base_family,
fontWeight = "normal",
fontStyle = "normal",
fontSize = "13px",
textAlign = "right",
paddingTop = "1px",
paddingBottom = "1px",
paddingLeft = "3px",
paddingRight = header_padding,
borderBottom = "1px solid #222222",
borderLeft = "none",
borderRight = "none",
borderTop = "none"
),
cellStyle = list(
fontFamily = base_family,
fontWeight = "normal",
fontStyle = "normal",
fontSize = "13px",
textAlign = "right",
paddingTop = "1px",
paddingBottom = "1px",
paddingLeft = "3px",
paddingRight = "3px",
border = "none"
)
)
}

#' Custom CSS for reactable tables
#'
#' @description
#' Returns a Shiny tags object with custom CSS for reactable tables,
#' with table styling according to the Rijksoverheid huisstijl.
#'
#' @importFrom shiny tags HTML
#'
#' @param sorting Logical. If `TRUE`, custom sorting arrows are shown in the
#' column headers. If `FALSE`, only the default arrows are hidden.
#' Default `TRUE`.
#'
#' @return A `shiny.tag` with a `<head>` tag that contains custom CSS
#' for reactable tables.
#' @export
#' @family reactable
#'
#' @examples
#' if (interactive()) {
#' library(shiny)
#' library(reactable)
#'
#' ui <- fluidPage(
#' ro_reactable_css(sorting = TRUE),
#' reactableOutput("mytable")
#' )
#'
#' server <- function(input, output, session) {
#' output$mytable <- renderReactable(
#' reactable(
#' iris,
#' defaultColDef = colDef(align = "right", sortable = TRUE),
#' theme = ro_reactable_theme(sorting = TRUE)
#' )
#' )
#' }
#'
#' shinyApp(ui, server)
#' }
ro_reactable_css <- function(sorting = TRUE) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would put this default as sorting = FALSE, so it is aligned with the ro_reactable_theme default (and also with Laurens-table opinion).

arrow_css <- if (sorting) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not something for now, but maybe for the future(?):
Do you think its possible to have one css function for the arrow-theming, which then can be applied to both reactable and DT? In that way, it would be possible to keep the arrow-theming in one place. (Or maybe even more broadly, for the table theming in general).
@mplasmans @hypebright What do you think?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah that sounds like a good idea @tkranenburgrivm! Keeps it DRY. We would also need to make sure that we avoid hard-coding any colors that are organisation-dependent. E.g. #44688f, #f2f2f2 and #007bff instead of coming from ro_color()? Or is that overkill and not relevant for an arrow @mplasmans 🤓?

"
/* Hide the extra sort arrow span */
.reactable .rt-th .rt-sort-left {
display: none !important;
}

/* Custom sorting arrows based on aria-sort */
.reactable .rt-th {
position: relative;
cursor: pointer;
}

/* Unsorted: double arrow */
.reactable .rt-th[aria-sort='none']::after,
.reactable .rt-th:not([aria-sort])::after {
content: '\\2195';
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
font-size: 16px;
line-height: 1;
color: #888888;
}

/* Ascending: up arrow */
.reactable .rt-th[aria-sort='ascending']::after {
content: '\\2191';
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
font-size: 16px;
line-height: 1;
color: #007bff;
font-weight: 700;
}

/* Descending: down arrow */
.reactable .rt-th[aria-sort='descending']::after {
content: '\\2193';
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
font-size: 16px;
color: #007bff;
font-weight: 700;
}
"
} else {
"
/* Hide the extra sort arrow span */
.reactable .rt-th .rt-sort-left {
display: none !important;
}
"
}

shiny::tags$head(
shiny::tags$style(
shiny::HTML(paste0(
"
/* Caption styling (if a caption is used) */
.reactable .rt-table caption {
caption-side: top;
text-align: right;
font-weight: normal;
font-style: normal;
font-size: 14px;
padding: 1px 3px;
color: #505050;
}

/* Vertical line after first column header */
.reactable .rt-thead .rt-tr > .rt-th:first-child {
border-right: 1px solid #222222 !important;
}

/* Vertical line after first column in body */
.reactable .rt-tbody .rt-tr-group .rt-tr > .rt-td:first-child {
border-right: 1px solid #222222 !important;
}

/* Zebra striping */
.reactable .rt-tbody .rt-tr-group:nth-child(odd) {
background-color: #f2f2f2;
}
.reactable .rt-tbody .rt-tr-group:nth-child(even) {
background-color: #ffffff;
}

/* Hover effect */
.reactable .rt-tbody .rt-tr-group:hover {
background-color: #44688f;
color: #ffffff;
}

/* Selected row */
.reactable .rt-tr-group.rt-selected,
.reactable .rt-tr-group.rt-selected .rt-td {
background-color: #44688f !important;
color: #ffffff !important;
}

/* Focus indicators: table cells & header */
.reactable .rt-td:focus,
.reactable .rt-th:focus {
outline: 2px dotted #000 !important;
box-shadow: none !important;
border: none !important;
}

/* Pagination buttons */
.reactable .rt-pagination button:focus,
.reactable .rt-pagination button:focus-visible {
outline: 2px dotted #000 !important;
outline-offset: 2px !important;
box-shadow: none !important;
}
.reactable .rt-pagination button[aria-current='page'] {
border: 2px solid #949494 !important;
}
",
arrow_css
))
)
)
}
49 changes: 49 additions & 0 deletions man/ro_reactable_css.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions man/ro_reactable_theme.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions tests/testthat/test-ro_reactable_theme.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
test_that("ro_reactable_theme applies the font resolved by ro_check_if_font_available", {
local_mocked_bindings(
ro_check_if_font_available = function(target_font_family) "Verdana"
)
css <- ro_dt_theme()$children[[1]]$children[[1]]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The test is using the ro_dt_theme instead of the ro_reactable_theme

expect_true(grepl("Verdana", css, fixed = TRUE))
})

test_that("ro_reactable_theme errors for an unavailable custom font", {
expect_error(
ro_dt_theme(base_family = "Not an installed font"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The test is using the ro_dt_theme instead of the ro_reactable_theme

"Can't find"
)
})
Loading