Skip to content

Commit 74a138c

Browse files
authored
feat!: Introduce navbar_options() (#1141)
* chore: code style * feat!(navbar_options): Introduce `navbar_options()`, deprecate navbar-related options in `navset_bar()` * feat: Add `underline` to `navbar_options()` * feat!(page_navbar): Use `navbar_options()` * docs: Move `navbar_options()` to its own docs page * chore(page_navbar): Move deprecated arguments to the end * docs: Update navset examples * feat: Prevent deprecation warnings from `shiny::navbarPage()` * refactor: simplify navbar_options() * chore: use version 2 for internal data * feat: Make `underline = TRUE` the default * refactor: consolidate deprecation warning into single message * tests: update snapshots * docs: Add news item * docs: Add a note about `navset_bar()` in the examples page Fixes #552 * chore: add todo note to remember to remove code when fully deprecated * chore: Include `...` for future expansion * refactor: Use attributes on `navbar_options()` to track source * chore: Apply changes from review * chore: edit wording in comment * feat: resolved navbar options equivalent to `navbar_options()` * chore: Error if `...` includes unused options
1 parent a6fda93 commit 74a138c

13 files changed

+604
-86
lines changed

NAMESPACE

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ S3method(is_fillable_container,default)
99
S3method(is_fillable_container,htmlwidget)
1010
S3method(print,bslib_breakpoints)
1111
S3method(print,bslib_fragment)
12+
S3method(print,bslib_navbar_options)
1213
S3method(print,bslib_page)
1314
S3method(print,bslib_showcase_layout)
1415
S3method(print,bslib_value_box_theme)
@@ -105,6 +106,7 @@ export(nav_remove)
105106
export(nav_select)
106107
export(nav_show)
107108
export(nav_spacer)
109+
export(navbar_options)
108110
export(navs_bar)
109111
export(navs_hidden)
110112
export(navs_pill)

NEWS.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# bslib (development version)
22

3+
## Breaking changes
4+
5+
* 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)
6+
7+
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()`.
8+
9+
## Improvements and bug fixes
10+
311
* `navset_card_pills()`, `navset_card_underline()`, `navset_card_tabs()` fixed to now respect header/footer arguments (@tanho63, #1024)
412

513
* 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)

R/navs-legacy.R

+215-18
Original file line numberDiff line numberDiff line change
@@ -105,36 +105,233 @@ navset_hidden <- function(..., id = NULL, selected = NULL,
105105
#' character vector, matching the `value` of [nav_panel()]s to be filled, may
106106
#' also be provided. Note that, if a `sidebar` is provided, `fillable` makes
107107
#' the main content portion fillable.
108-
#' @param bg a CSS color to use for the navbar's background color.
109-
#' @param inverse Either `TRUE` for a light text color or `FALSE` for a dark
110-
#' text color. If `"auto"` (the default), the best contrast to `bg` is chosen.
108+
#' @param navbar_options Options to control the appearance and behavior of the
109+
#' navbar. Use [navbar_options()] to create the list of options.
110+
#' @param position `r lifecycle::badge("deprecated")` Please use
111+
#' [`navbar_options = navbar_options(position=)`][navbar_options] instead.
112+
#' @param collapsible `r lifecycle::badge("deprecated")` Please use
113+
#' [`navbar_options = navbar_options(collapsible=)`][navbar_options] instead.
114+
#' @param bg `r lifecycle::badge("deprecated")` Please use
115+
#' [`navbar_options = navbar_options(bg=)`][navbar_options] instead.
116+
#' @param inverse `r lifecycle::badge("deprecated")` Please use
117+
#' [`navbar_options = navbar_options(inverse=)`][navbar_options] instead.
118+
#'
111119
#' @export
112120
#' @rdname navset
113-
navset_bar <- function(..., title = NULL, id = NULL, selected = NULL,
114-
sidebar = NULL, fillable = TRUE,
115-
gap = NULL, padding = NULL,
116-
# TODO: add sticky-top as well?
117-
position = c("static-top", "fixed-top", "fixed-bottom"),
118-
header = NULL, footer = NULL,
119-
bg = NULL, inverse = "auto",
120-
collapsible = TRUE, fluid = TRUE) {
121+
navset_bar <- function(
122+
...,
123+
title = NULL,
124+
id = NULL,
125+
selected = NULL,
126+
sidebar = NULL,
127+
fillable = TRUE,
128+
gap = NULL,
129+
padding = NULL,
130+
header = NULL,
131+
footer = NULL,
132+
fluid = TRUE,
133+
navbar_options = NULL,
134+
position = deprecated(),
135+
bg = deprecated(),
136+
inverse = deprecated(),
137+
collapsible = deprecated()
138+
) {
121139
padding <- validateCssPadding(padding)
122140
gap <- validateCssUnit(gap)
123141

124-
navs_bar_(
125-
..., title = title, id = id, selected = selected,
126-
sidebar = sidebar, fillable = fillable,
127-
gap = gap, padding = padding,
142+
.navbar_options <- navbar_options_resolve_deprecated(
143+
options_user = navbar_options,
128144
position = position,
129-
header = header, footer = footer,
130-
bg = bg, inverse = inverse,
131-
collapsible = collapsible, fluid = fluid,
145+
bg = bg,
146+
inverse = inverse,
147+
collapsible = collapsible
148+
)
149+
150+
navs_bar_(
151+
...,
152+
title = title,
153+
id = id,
154+
selected = selected,
155+
sidebar = sidebar,
156+
fillable = fillable,
157+
gap = gap,
158+
padding = padding,
159+
header = header,
160+
footer = footer,
161+
fluid = fluid,
162+
position = .navbar_options$position,
163+
bg = .navbar_options$bg,
164+
inverse = .navbar_options$inverse,
165+
collapsible = .navbar_options$collapsible,
166+
underline = .navbar_options$underline,
132167
# theme is only used to determine whether legacy style markup should be used
133168
# (and, at least at the moment, we don't need legacy markup for this exported function)
134169
theme = bs_theme()
135170
)
136171
}
137172

173+
#' Create a set of navbar options
174+
#'
175+
#' A `navbar_options()` object captures options specific to the appearance and
176+
#' behavior of the navbar, independent from the content displayed on the page.
177+
#' This helper should be used to create the list of options expected by
178+
#' `navbar_options` in [page_navbar()] and [navset_bar()].
179+
#'
180+
#' ## Changelog
181+
#'
182+
#' This function was introduced in \pkg{bslib} v0.9.0, replacing the `position`,
183+
#' `bg`, `inverse`, `collapsible` and `underline` arguments of [page_navbar()]
184+
#' and [navset_bar()]. Those arguments are deprecated with a warning and will be
185+
#' removed in a future version of \pkg{bslib}.
186+
#'
187+
#' @examples
188+
#' navbar_options(position = "static-top", bg = "#2e9f7d", underline = FALSE)
189+
#'
190+
#' @inheritParams shiny::navbarPage
191+
#' @param bg a CSS color to use for the navbar's background color.
192+
#' @param inverse Either `TRUE` for a light text color or `FALSE` for a dark
193+
#' text color. If `"auto"` (the default), the best contrast to `bg` is chosen.
194+
#' @param underline Whether or not to add underline styling to page or navbar
195+
#' links when active or focused.
196+
#' @param ... Additional arguments are ignored. `...` is included for future
197+
#' expansion on `navbar_options()`.
198+
#'
199+
#' @returns Returns a list of navbar options.
200+
#'
201+
#' @export
202+
navbar_options <- function(
203+
...,
204+
position = c("static-top", "fixed-top", "fixed-bottom"),
205+
bg = NULL,
206+
inverse = "auto",
207+
collapsible = TRUE,
208+
underline = TRUE
209+
) {
210+
# Track user-provided arguments for print method and deprecation warnings
211+
is_default <- list(
212+
position = missing(position),
213+
bg = missing(bg),
214+
inverse = missing(inverse),
215+
collapsible = missing(collapsible),
216+
underline = missing(underline)
217+
)
218+
219+
rlang::check_dots_empty()
220+
221+
opts <- list(
222+
position = rlang::arg_match(position),
223+
bg = bg,
224+
inverse = inverse,
225+
collapsible = collapsible,
226+
underline = underline
227+
)
228+
229+
structure(
230+
opts,
231+
class = c("bslib_navbar_options", "list"),
232+
is_default = is_default,
233+
waldo_opts = list(ignore_attr = TRUE)
234+
)
235+
}
236+
237+
navbar_options_resolve_deprecated <- function(
238+
options_user = list(),
239+
position = deprecated(),
240+
bg = deprecated(),
241+
inverse = deprecated(),
242+
collapsible = deprecated(),
243+
underline = deprecated(),
244+
.fn_caller = "navset_bar",
245+
.warn_deprecated = TRUE
246+
) {
247+
options_old <- list(
248+
position = if (lifecycle::is_present(position)) position,
249+
bg = if (lifecycle::is_present(bg)) bg,
250+
inverse = if (lifecycle::is_present(inverse)) inverse,
251+
collapsible = if (lifecycle::is_present(collapsible)) collapsible,
252+
underline = if (lifecycle::is_present(underline)) underline
253+
)
254+
options_old <- dropNulls(options_old)
255+
256+
args_deprecated <- names(options_old)
257+
258+
if (.warn_deprecated && length(args_deprecated)) {
259+
# TODO-deprecated: (2024-12) Elevate deprecation to an error
260+
lifecycle::deprecate_warn(
261+
"0.9.0",
262+
I(sprintf(
263+
"The %s argument%s of `%s()` have been consolidated into a single `navbar_options` argument and ",
264+
paste(sprintf("`%s`", args_deprecated), collapse = ", "),
265+
if (length(args_deprecated) > 1) "s" else "",
266+
.fn_caller
267+
))
268+
)
269+
}
270+
271+
# Consolidate `navbar_options` (options_user) with the deprecated direct
272+
# options. We take the direct option if the user option is a default value,
273+
# warning if otherwise ignored.
274+
# TODO-deprecated: Remove this and warning when direct options are hard-deprecated
275+
is_default <- attr(options_user, "is_default") %||% list()
276+
keep_user_values <- vapply(
277+
names(options_user),
278+
function(x) !isTRUE(is_default[[x]]),
279+
logical(1)
280+
)
281+
options_user <- options_user[keep_user_values]
282+
283+
ignored <- c()
284+
for (opt in names(options_old)) {
285+
if (!opt %in% names(options_user)) {
286+
options_user[[opt]] <- options_old[[opt]]
287+
} else if (!identical(options_old[[opt]], options_user[[opt]])) {
288+
ignored <- c(ignored, opt)
289+
}
290+
}
291+
292+
if (length(ignored) > 0) {
293+
rlang::warn(
294+
c(
295+
sprintf(
296+
"`%s` %s provided twice: once directly and once in `navbar_options`.",
297+
paste(ignored, collapse = "`, `"),
298+
if (length(ignored) == 1) "was" else "were"
299+
),
300+
"The deprecated direct option(s) will be ignored and the values from `navbar_options` will be used."
301+
),
302+
call = rlang::caller_call()
303+
)
304+
}
305+
306+
rlang::exec(navbar_options, !!!options_user)
307+
}
308+
309+
#' @export
310+
print.bslib_navbar_options <- function(x, ...) {
311+
cat("<bslib_navbar_options>\n")
312+
313+
if (length(x) == 0) {
314+
return(invisible(x))
315+
}
316+
317+
fields <- names(x)
318+
opt_w <- max(nchar(fields))
319+
is_default <- attr(x, "is_default") %||% list()
320+
for (opt in fields) {
321+
value <- x[[opt]] %||% "NULL"
322+
if (isTRUE(is_default[[opt]])) {
323+
if (identical(value, "NULL")) {
324+
# Skip printing default NULL values
325+
next
326+
}
327+
value <- sprintf("(%s)", value)
328+
}
329+
cat(sprintf("%*s", opt_w, opt), ": ", value, "\n", sep = "")
330+
}
331+
332+
invisible(x)
333+
}
334+
138335
# This internal version of navs_bar() exists so both it and page_navbar()
139336
# (and thus shiny::navbarPage()) can use it. And in the page_navbar() case,
140337
# we can use addition theme information as an indication of whether we need

R/page.R

+45-20
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,8 @@ maybe_page_sidebar <- function(x) {
333333
#'
334334
#' @param fillable_mobile Whether or not `fillable` pages should fill the viewport's
335335
#' height on mobile devices (i.e., narrow windows).
336-
#' @param underline Whether or not to add underline styling to page links when
337-
#' active or focused.
336+
#' @param underline `r lifecycle::badge("deprecated")` Please use
337+
#' [`navbar_options = navbar_options(underline=)`][navbar_options] instead.
338338
#' @param window_title the browser window title. The default value, `NA`, means
339339
#' to use any character strings that appear in `title` (if none are found, the
340340
#' host URL of the page is displayed by default).
@@ -397,31 +397,46 @@ page_navbar <- function(
397397
fillable_mobile = FALSE,
398398
gap = NULL,
399399
padding = NULL,
400-
position = c("static-top", "fixed-top", "fixed-bottom"),
401400
header = NULL,
402401
footer = NULL,
403-
bg = NULL,
404-
inverse = "auto",
405-
underline = TRUE,
406-
collapsible = TRUE,
402+
navbar_options = NULL,
407403
fluid = TRUE,
408404
theme = bs_theme(),
409405
window_title = NA,
410-
lang = NULL
406+
lang = NULL,
407+
position = deprecated(),
408+
bg = deprecated(),
409+
inverse = deprecated(),
410+
underline = deprecated(),
411+
collapsible = deprecated()
411412
) {
412413

413414
sidebar <- maybe_page_sidebar(sidebar)
414415

415416
padding <- validateCssPadding(padding)
416417
gap <- validateCssUnit(gap)
417418

419+
# Change behavior when called by Shiny
420+
# TODO: Coordinate with next bslib version bump in Shiny to use the new interface
421+
was_called_by_shiny <-
422+
isNamespaceLoaded("shiny") &&
423+
identical(rlang::caller_fn(), shiny::navbarPage)
424+
425+
.navbar_options <- navbar_options_resolve_deprecated(
426+
options_user = navbar_options,
427+
position = position,
428+
bg = bg,
429+
inverse = inverse,
430+
collapsible = collapsible,
431+
underline = underline,
432+
.fn_caller = "page_navbar",
433+
.warn_deprecated = !was_called_by_shiny
434+
)
435+
418436
# Default to fillable = F when this is called from shiny::navbarPage()
419437
# TODO: update shiny::navbarPage() to set fillable = FALSE and get rid of this hack
420-
if (missing(fillable)) {
421-
isNavbarPage <- isNamespaceLoaded("shiny") && identical(rlang::caller_fn(), shiny::navbarPage)
422-
if (isNavbarPage) {
423-
fillable <- FALSE
424-
}
438+
if (missing(fillable) && was_called_by_shiny) {
439+
fillable <- FALSE
425440
}
426441

427442
# If a sidebar is provided, we want the layout_sidebar(fill = TRUE) component
@@ -439,13 +454,23 @@ page_navbar <- function(
439454
class = "bslib-page-navbar",
440455
class = if (!is.null(sidebar)) "has-page-sidebar",
441456
navs_bar_(
442-
..., title = title, id = id, selected = selected,
443-
sidebar = sidebar, fillable = fillable,
444-
gap = gap, padding = padding,
445-
position = match.arg(position), header = header,
446-
footer = footer, bg = bg, inverse = inverse,
447-
underline = underline, collapsible = collapsible,
448-
fluid = fluid, theme = theme
457+
...,
458+
title = title,
459+
id = id,
460+
selected = selected,
461+
sidebar = sidebar,
462+
fillable = fillable,
463+
gap = gap,
464+
padding = padding,
465+
header = header,
466+
footer = footer,
467+
position = .navbar_options$position,
468+
bg = .navbar_options$bg,
469+
inverse = .navbar_options$inverse,
470+
underline = .navbar_options$underline,
471+
collapsible = .navbar_options$collapsible,
472+
fluid = fluid,
473+
theme = theme
449474
)
450475
)
451476
}

R/sysdata.rda

6 Bytes
Binary file not shown.

_pkgdown.yml

+1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ reference:
134134
- navset
135135
- nav-items
136136
- nav_select
137+
- navbar_options
137138
- subtitle: Sidebar layout
138139
desc: >
139140
Place input controls or additional context in a sidebar next to the main

0 commit comments

Comments
 (0)