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

feat!: Introduce navbar_options() #1141

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
01ff848
chore: code style
gadenbuie Nov 26, 2024
9ed36bb
feat!(navbar_options): Introduce `navbar_options()`, deprecate navbar…
gadenbuie Nov 26, 2024
0612d30
feat: Add `underline` to `navbar_options()`
gadenbuie Nov 26, 2024
c726c6b
feat!(page_navbar): Use `navbar_options()`
gadenbuie Nov 26, 2024
284d10f
docs: Move `navbar_options()` to its own docs page
gadenbuie Nov 26, 2024
8abc2b7
chore(page_navbar): Move deprecated arguments to the end
gadenbuie Nov 26, 2024
388720b
docs: Update navset examples
gadenbuie Nov 26, 2024
33732ae
Resave data (GitHub Action)
gadenbuie Nov 26, 2024
f24c0c6
feat: Prevent deprecation warnings from `shiny::navbarPage()`
gadenbuie Nov 26, 2024
43e01c6
chore: small edits/fixes
gadenbuie Nov 26, 2024
15c42d4
chore: only warn if values are different
gadenbuie Nov 26, 2024
85b282a
chore: restore legacy code for minimal diff
gadenbuie Nov 26, 2024
a5b2868
chore(navbar_options): Expose defaults in function signature
gadenbuie Nov 27, 2024
5e1f0e2
Resave data (GitHub Action)
gadenbuie Nov 27, 2024
b017dc5
Update website deps (GitHub Action)
gadenbuie Nov 27, 2024
7d473ee
refactor: simplify navbar_options()
gadenbuie Nov 27, 2024
fcd6d5c
Revert "Update website deps (GitHub Action)"
gadenbuie Nov 27, 2024
3be0434
chore: use version 2 for internal data
gadenbuie Nov 27, 2024
bd43c4d
Resave data (GitHub Action)
gadenbuie Nov 27, 2024
a85ff8a
feat: Make `underline = TRUE` the default
gadenbuie Nov 27, 2024
0fc9985
chore: style
gadenbuie Nov 27, 2024
bf0f402
refactor: consolidate deprecation warning into single message
gadenbuie Nov 27, 2024
6f66bb7
tests: update snapshots
gadenbuie Nov 27, 2024
76a9692
`devtools::document()` (GitHub Actions)
gadenbuie Nov 27, 2024
60b84ce
docs: Add news item
gadenbuie Nov 27, 2024
3f0d9bd
docs: Add a note about `navset_bar()` in the examples page
gadenbuie Nov 27, 2024
7028fba
`devtools::document()` (GitHub Actions)
gadenbuie Nov 27, 2024
bc87599
chore: add todo note to remember to remove code when fully deprecated
gadenbuie Nov 27, 2024
cf8d423
chore: Include `...` for future expansion
gadenbuie Nov 27, 2024
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
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ S3method(is_fillable_container,default)
S3method(is_fillable_container,htmlwidget)
S3method(print,bslib_breakpoints)
S3method(print,bslib_fragment)
S3method(print,bslib_navbar_options)
S3method(print,bslib_page)
S3method(print,bslib_showcase_layout)
S3method(print,bslib_value_box_theme)
Expand Down Expand Up @@ -105,6 +106,7 @@ export(nav_remove)
export(nav_select)
export(nav_show)
export(nav_spacer)
export(navbar_options)
export(navs_bar)
export(navs_hidden)
export(navs_pill)
Expand Down
8 changes: 8 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# bslib (development version)

## Breaking changes

* The navbar-related style options of `page_navbar()` and `navset_bar()` have been consolidated into a single `navbar_options` argument that pairs with a new `navbar_options()` helper. Using the direct `position`, `bg`, `inverse`, `collapsible`, and `underline` arguments will continue to work with a deprecation message. (#1141)

Related to the above change, `navset_bar()` now defaults to using `underline = TRUE` so that both `page_navbar()` and `navset_bar()` use the same set of default `navbar_options()`.

## Improvements and bug fixes

* `navset_card_pills()`, `navset_card_underline()`, `navset_card_tabs()` fixed to now respect header/footer arguments (@tanho63, #1024)

* Fixed a bug in `bs_themer()` (and `bs_theme_preview()`) that caused it to stop applying changes if a Sass variable was `NULL`. (@meztez, #1112)
Expand Down
220 changes: 202 additions & 18 deletions R/navs-legacy.R
Original file line number Diff line number Diff line change
Expand Up @@ -105,36 +105,220 @@ navset_hidden <- function(..., id = NULL, selected = NULL,
#' character vector, matching the `value` of [nav_panel()]s to be filled, may
#' also be provided. Note that, if a `sidebar` is provided, `fillable` makes
#' the main content portion fillable.
#' @param bg a CSS color to use for the navbar's background color.
#' @param inverse Either `TRUE` for a light text color or `FALSE` for a dark
#' text color. If `"auto"` (the default), the best contrast to `bg` is chosen.
#' @param navbar_options Options to control the appearance and behavior of the
#' navbar. Use [navbar_options()] to create the list of options.
#' @param position `r lifecycle::badge("deprecated")` Please use
#' [`navbar_options = navbar_options(position=)`][navbar_options] instead.
#' @param collapsible `r lifecycle::badge("deprecated")` Please use
#' [`navbar_options = navbar_options(collapsible=)`][navbar_options] instead.
#' @param bg `r lifecycle::badge("deprecated")` Please use
#' [`navbar_options = navbar_options(bg=)`][navbar_options] instead.
#' @param inverse `r lifecycle::badge("deprecated")` Please use
#' [`navbar_options = navbar_options(inverse=)`][navbar_options] instead.
#'
#' @export
#' @rdname navset
navset_bar <- function(..., title = NULL, id = NULL, selected = NULL,
sidebar = NULL, fillable = TRUE,
gap = NULL, padding = NULL,
# TODO: add sticky-top as well?
position = c("static-top", "fixed-top", "fixed-bottom"),
header = NULL, footer = NULL,
bg = NULL, inverse = "auto",
collapsible = TRUE, fluid = TRUE) {
navset_bar <- function(
...,
title = NULL,
id = NULL,
selected = NULL,
sidebar = NULL,
fillable = TRUE,
gap = NULL,
padding = NULL,
header = NULL,
footer = NULL,
fluid = TRUE,
navbar_options = NULL,
position = deprecated(),
bg = deprecated(),
inverse = deprecated(),
collapsible = deprecated()
) {
padding <- validateCssPadding(padding)
gap <- validateCssUnit(gap)

navs_bar_(
..., title = title, id = id, selected = selected,
sidebar = sidebar, fillable = fillable,
gap = gap, padding = padding,
.navbar_options <- navbar_options_resolve_deprecated(
options_user = navbar_options,
position = position,
header = header, footer = footer,
bg = bg, inverse = inverse,
collapsible = collapsible, fluid = fluid,
bg = bg,
inverse = inverse,
collapsible = collapsible
)

navs_bar_(
...,
title = title,
id = id,
selected = selected,
sidebar = sidebar,
fillable = fillable,
gap = gap,
padding = padding,
header = header,
footer = footer,
fluid = fluid,
position = .navbar_options$position,
bg = .navbar_options$bg,
inverse = .navbar_options$inverse,
collapsible = .navbar_options$collapsible,
underline = .navbar_options$underline,
# theme is only used to determine whether legacy style markup should be used
# (and, at least at the moment, we don't need legacy markup for this exported function)
theme = bs_theme()
)
}

#' Create a set of navbar options
#'
#' A `navbar_options()` object captures options specific to the appearance and
#' behavior of the navbar, independent from the content displayed on the page.
#' This helper should be used to create the list of options expected by
#' `navbar_options` in [page_navbar()] and [navset_bar()].
#'
#' ## Changelog
#'
#' This function was introduced in \pkg{bslib} v0.9.0, replacing the `position`,
#' `bg`, `inverse`, `collapsible` and `underline` arguments of [page_navbar()]
#' and [navset_bar()]. Those arguments are deprecated with a warning and will be
#' removed in a future version of \pkg{bslib}.
#'
#' @examples
#' navbar_options(position = "static-top", bg = "#2e9f7d", underline = FALSE)
#'
#' @inheritParams shiny::navbarPage
#' @param bg a CSS color to use for the navbar's background color.
#' @param inverse Either `TRUE` for a light text color or `FALSE` for a dark
#' text color. If `"auto"` (the default), the best contrast to `bg` is chosen.
#' @param underline Whether or not to add underline styling to page or navbar
#' links when active or focused.
#'
#' @returns Returns a list of navbar options.
#'
#' @export
navbar_options <- function(
position = c("static-top", "fixed-top", "fixed-bottom"),
bg = NULL,
inverse = "auto",
collapsible = TRUE,
underline = TRUE
) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Should we include ... here to make future expansion easier? Maybe even making all arguments required?

Copy link
Member Author

Choose a reason for hiding this comment

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

I made this change. We'll need the cover from ... and because there's no clear ordering I'd rather not encourage the positional argument usage that would make future expansion difficult.

position2 <- rlang::arg_match(position)

opts <- list(
position = if (missing(position)) as_bslib_default(position[[1]]) else position2,
bg = if (missing(bg)) as_bslib_default(bg) else bg,
inverse = if (missing(inverse)) as_bslib_default(inverse) else inverse,
collapsible = if (missing(collapsible)) as_bslib_default(collapsible) else collapsible,
underline = if (missing(underline)) as_bslib_default(underline) else underline
)

structure(dropNulls(opts), class = c("bslib_navbar_options", "list"))
}

as_bslib_default <- function(x) {
if (is.null(x)) return(x)
attr(x, "bslib_default") <- TRUE
attr(x, "waldo_opts") <- list(ignore_attr = TRUE)
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved
x
}

is_bslib_default <- function(x) {
isTRUE(attr(x, "bslib_default"))
}

navbar_options_resolve_deprecated <- function(
options_user = NULL,
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we make it more clear this is a list(), you could get rid of 271

Suggested change
options_user = NULL,
options_user = navbar_options(),

position = deprecated(),
bg = deprecated(),
inverse = deprecated(),
collapsible = deprecated(),
underline = deprecated(),
.fn_caller = "navset_bar",
.warn_deprecated = TRUE
) {
options_old <- list(
position = if (lifecycle::is_present(position)) position,
bg = if (lifecycle::is_present(bg)) bg,
inverse = if (lifecycle::is_present(inverse)) inverse,
collapsible = if (lifecycle::is_present(collapsible)) collapsible,
underline = if (lifecycle::is_present(underline)) underline
)
options_old <- dropNulls(options_old)

args_deprecated <- names(options_old)

if (.warn_deprecated && length(args_deprecated)) {
lifecycle::deprecate_warn(
"0.9.0", # 2024-11
I(sprintf(
"The %s argument%s of `%s()` have been consolidated into a single `navbar_options` argument and ",
paste(sprintf("`%s`", args_deprecated), collapse = ", "),
if (length(args_deprecated) > 1) "s" else "",
.fn_caller
))
)
}

# Consolidate `navbar_options` (options_user) with direction options taking
# the direct option if the user option is a default value, warning if
# otherwise ignored.
# TODO-deprecated Remove this and warning when direct options are hard-deprecated
options_user <- options_user %||% list()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
options_user <- options_user %||% list()

ignored <- c()
for (opt in names(options_old)) {
can_use_direct <-
!opt %in% names(options_user) ||
is_bslib_default(options_user[[opt]])

if (can_use_direct) {
options_user[[opt]] <- options_old[[opt]]
} else if (!identical(c(options_old[[opt]]), c(options_user[[opt]]))) {
ignored <- c(ignored, opt)
}
}

if (length(ignored) > 0) {
rlang::warn(
c(
sprintf(
"`%s` %s provided twice: once directly and once in `navbar_options`.",
paste(ignored, collapse = "`, `"),
if (length(ignored) == 1) "was" else "were"
),
"The deprecated direct option(s) will be ignored and the values from `navbar_options` will be used."
),
call = rlang::caller_call()
)
}

ret <- rlang::exec(navbar_options, !!!options_user)
# strip bslib_default attributes now that we've resolved defaults
lapply(ret, c)
}

#' @export
print.bslib_navbar_options <- function(x, ...) {
cat("<bslib_navbar_options>\n")

if (length(x) == 0) {
return(invisible(x))
}

fields <- names(dropNulls(x))
opt_w <- max(nchar(fields))
for (opt in fields) {
value <- x[[opt]]
if (is_bslib_default(value)) {
value <- sprintf("(%s)", value)
}
cat(sprintf("%*s", opt_w, opt), ": ", value, "\n", sep = "")
}

invisible(x)
}

# This internal version of navs_bar() exists so both it and page_navbar()
# (and thus shiny::navbarPage()) can use it. And in the page_navbar() case,
# we can use addition theme information as an indication of whether we need
Expand Down
65 changes: 45 additions & 20 deletions R/page.R
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,8 @@ maybe_page_sidebar <- function(x) {
#'
#' @param fillable_mobile Whether or not `fillable` pages should fill the viewport's
#' height on mobile devices (i.e., narrow windows).
#' @param underline Whether or not to add underline styling to page links when
#' active or focused.
#' @param underline `r lifecycle::badge("deprecated")` Please use
#' [`navbar_options = navbar_options(underline=)`][navbar_options] instead.
#' @param window_title the browser window title. The default value, `NA`, means
#' to use any character strings that appear in `title` (if none are found, the
#' host URL of the page is displayed by default).
Expand Down Expand Up @@ -397,31 +397,46 @@ page_navbar <- function(
fillable_mobile = FALSE,
gap = NULL,
padding = NULL,
position = c("static-top", "fixed-top", "fixed-bottom"),
header = NULL,
footer = NULL,
bg = NULL,
inverse = "auto",
underline = TRUE,
collapsible = TRUE,
navbar_options = NULL,
fluid = TRUE,
theme = bs_theme(),
window_title = NA,
lang = NULL
lang = NULL,
position = deprecated(),
bg = deprecated(),
inverse = deprecated(),
underline = deprecated(),
collapsible = deprecated()
) {

sidebar <- maybe_page_sidebar(sidebar)

padding <- validateCssPadding(padding)
gap <- validateCssUnit(gap)

# Change behavior when called by Shiny
# TODO: Coordinate with next bslib version bump in Shiny to use the new interface
was_called_by_shiny <-
isNamespaceLoaded("shiny") &&
identical(rlang::caller_fn(), shiny::navbarPage)

.navbar_options <- navbar_options_resolve_deprecated(
options_user = navbar_options,
position = position,
bg = bg,
inverse = inverse,
collapsible = collapsible,
underline = underline,
.fn_caller = "page_navbar",
.warn_deprecated = !was_called_by_shiny
Copy link
Member Author

Choose a reason for hiding this comment

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

I would really really like to decouple shiny::navbarPage() from bslib::page_navbar(). I'm thinking it'd be an update to add page_navbar_legacy() that we'd export but mark as internal so it doesn't show up in docs and indices.

What I want out of that change is to have a clear line around what parts of the navbar API we shouldn't touch without thinking carefully about backwards compatibility or API changes and which parts are owned by bslib. Currently it feels too entangled and over time entropy wins leading to more entanglement.

)

# Default to fillable = F when this is called from shiny::navbarPage()
# TODO: update shiny::navbarPage() to set fillable = FALSE and get rid of this hack
if (missing(fillable)) {
isNavbarPage <- isNamespaceLoaded("shiny") && identical(rlang::caller_fn(), shiny::navbarPage)
if (isNavbarPage) {
fillable <- FALSE
}
if (missing(fillable) && was_called_by_shiny) {
fillable <- FALSE
}

# If a sidebar is provided, we want the layout_sidebar(fill = TRUE) component
Expand All @@ -439,13 +454,23 @@ page_navbar <- function(
class = "bslib-page-navbar",
class = if (!is.null(sidebar)) "has-page-sidebar",
navs_bar_(
..., title = title, id = id, selected = selected,
sidebar = sidebar, fillable = fillable,
gap = gap, padding = padding,
position = match.arg(position), header = header,
footer = footer, bg = bg, inverse = inverse,
underline = underline, collapsible = collapsible,
fluid = fluid, theme = theme
...,
title = title,
id = id,
selected = selected,
sidebar = sidebar,
fillable = fillable,
gap = gap,
padding = padding,
header = header,
footer = footer,
position = .navbar_options$position,
bg = .navbar_options$bg,
inverse = .navbar_options$inverse,
underline = .navbar_options$underline,
collapsible = .navbar_options$collapsible,
fluid = fluid,
theme = theme
)
)
}
Expand Down
Binary file modified R/sysdata.rda
Binary file not shown.
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ reference:
- navset
- nav-items
- nav_select
- navbar_options
- subtitle: Sidebar layout
desc: >
Place input controls or additional context in a sidebar next to the main
Expand Down
10 changes: 7 additions & 3 deletions man/fragments/ex-navset_tab.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,17 @@ navset_pill_list(
Finally, `page_navbar()` provides full-page navigation container
similar to `navset_underline()` but where each `nav_panel()` is treated as a full page of content
and the navigation controls appear in a top-level navigation bar.
Note also that the underline styling can be removed via the `underline` argument.
Note that the navbar background and underline styling can be controlled via `navbar_options`.

`page_navbar()` is complimented by `navset_bar()` which produces a similar layout intended to be used within an app.

```{r page_navbar, fig.width=10}
page_navbar(
title = "My App",
bg = "#0062cc",
underline = TRUE,
navbar_options = navbar_options(
bg = "#0062cc",
underline = TRUE
),
nav_panel(title = "One", p("First tab content.")),
nav_panel(title = "Two", p("Second tab content.")),
nav_panel(title = "Three", p("Third tab content")),
Expand Down
Loading
Loading