Compare commits

..

2 Commits

Author SHA1 Message Date
cpsievert
0dade0c553 Document (GitHub Actions) 2021-03-22 17:45:21 +00:00
Carson
ad6c1a4660 Revert "remove card argument (at least for now)"
This reverts commit b5b0ba1774.
2021-03-22 12:39:38 -05:00
21 changed files with 92 additions and 434 deletions

View File

@@ -170,7 +170,6 @@ Collate:
'mock-session.R'
'modal.R'
'modules.R'
'navtreePanel.R'
'notifications.R'
'priorityqueue.R'
'progress.R'

View File

@@ -175,7 +175,6 @@ export(moduleServer)
export(navbarMenu)
export(navbarPage)
export(navlistPanel)
export(navtreePanel)
export(nearPoints)
export(need)
export(ns.sep)

13
NEWS.md
View File

@@ -7,18 +7,7 @@ shiny 1.6.0.9000
* The `format` and `locale` arguments to `sliderInput()` have been removed. They have been deprecated since 0.10.2.2 (released on 2014-12-08).
### New features and improvements
* All uses of `list(...)` have been replaced with `rlang::list2(...)`. This means that you can use trailing `,` without error and use rlang's `!!!` operator to "splice" a list of argument values into `...`. We think this'll be particularly useful for passing a list of `tabPanel()` to their consumers (i.e., `tabsetPanel()`, `navbarPage()`, etc). For example, `tabs <- list(tabPanel("A", "a"), tabPanel("B", "b")); navbarPage(!!!tabs)`. (#3315 and #3328)
* Numerous improvements tabset panels (i.e., `tabPanel()`, `navbarMenu()`, `tabsetPanel()`, `navbarPage()`, etc) (#3315):
* Closed #3322: `tabsetPanel()` and `navlistPanel()` gain `header`/`footer` arguments (inspired by `navbarPage()`'s already existing `header`/`footer`), making it easier to include content that should appear on every tab.
* Closed #3313 and #1823: More informative error when non-`tabPanel()`/`shiny.tag` objects are supplied to `...`.
* Closed #3321: New informative warning when `shiny.tag` object(s) are supplied to `...`. In this case we will continue to create an "empty" nav item and include the content on every tab, but the warning will mention the (new) `header`/`footer` args, which is likely what the user wants.
* Closed #3320: The HTML markup that `tabPanel()` et. al generate (for Bootstrap nav) is now Bootstrap 4+ compliant when used with `theme = bslib::bs_theme()`.
* Closed #1928: `NULL` values are now dropped instead of producing an empty nav item.
### Other improvements
### Minor new features and improvements
* Shiny's core JavaScript code was converted to TypeScript. For the latest development information, please see the [README.md in `./srcts`](https://github.com/rstudio/shiny/tree/master/srcts). (#3296)

View File

@@ -396,7 +396,7 @@ mainPanel <- function(..., width = 8) {
#' }
#' @export
verticalLayout <- function(..., fluid = TRUE) {
lapply(list2(...), function(row) {
lapply(list(...), function(row) {
col <- column(12, row)
if (fluid)
fluidRow(col)
@@ -433,7 +433,7 @@ verticalLayout <- function(..., fluid = TRUE) {
#' @export
flowLayout <- function(..., cellArgs = list()) {
children <- list2(...)
children <- list(...)
childIdx <- !nzchar(names(children) %||% character(length(children)))
attribs <- children[!childIdx]
children <- children[childIdx]
@@ -516,7 +516,7 @@ inputPanel <- function(...) {
#' @export
splitLayout <- function(..., cellWidths = NULL, cellArgs = list()) {
children <- list2(...)
children <- list(...)
childIdx <- !nzchar(names(children) %||% character(length(children)))
attribs <- children[!childIdx]
children <- children[childIdx]
@@ -614,7 +614,7 @@ fillCol <- function(..., flex = 1, width = "100%", height = "100%") {
}
flexfill <- function(..., direction, flex, width = width, height = height) {
children <- list2(...)
children <- list(...)
attrs <- list()
if (!is.null(names(children))) {

View File

@@ -49,7 +49,7 @@ bootstrapPage <- function(..., title = NULL, responsive = deprecated(), theme =
tags$head(tags$link(rel="stylesheet", type="text/css", href=theme))
},
# remainder of tags passed to the function
list2(...)
list(...)
)
# If theme is a bslib::bs_theme() object, bootstrapLib() needs to come first
@@ -91,10 +91,6 @@ getLang <- function(ui) {
#' @export
bootstrapLib <- function(theme = NULL) {
tagFunction(function() {
if (isRunning()) {
setCurrentTheme(theme)
}
# If we're not compiling Bootstrap Sass (from bslib), return the
# static Bootstrap build.
if (!is_bs_theme(theme)) {
@@ -116,6 +112,7 @@ bootstrapLib <- function(theme = NULL) {
# Note also that since this is shinyOptions() (and not options()), the
# option is automatically reset when the app (or session) exits
if (isRunning()) {
setCurrentTheme(theme)
registerThemeDependency(bs_theme_deps)
} else {
@@ -710,6 +707,7 @@ tabPanelBody <- function(value, ..., icon = NULL) {
#' conjunction with [tabPanelBody()] and [updateTabsetPanel()] to control the
#' active tab via other input controls. (See example below)}
#' }
#' @param card whether to wrap the navigation controls and content into an 'output card'. This functionality currently requires a [bslib::bs_theme()] in the page layout with `version = 4` or higher.
#' @param position This argument is deprecated; it has been discontinued in
#' Bootstrap 3.
#' @inheritParams navbarPage
@@ -764,6 +762,7 @@ tabsetPanel <- function(...,
type = c("tabs", "pills", "hidden"),
header = NULL,
footer = NULL,
card = FALSE,
position = deprecated()) {
if (lifecycle::is_present(position)) {
shinyDeprecated(
@@ -778,15 +777,34 @@ tabsetPanel <- function(...,
type <- match.arg(type)
tabset <- buildTabset(..., ulClass = paste0("nav nav-", type), id = id, selected = selected)
tags$div(
class = "tabbable",
!!!dropNulls(list(
tabset$navList,
header,
tabset$content,
footer
))
)
nav <- tabset$navList
if (card) {
nav <- tags$div(
class = "card-header",
tagFunction(function() {
if (getCurrentVersion() >= 4) {
return(NULL)
}
stop(
"`tabsetPanel(card = TRUE)` requires Bootstrap 4 or higher. ",
"Please supply `bslib::bs_theme()` to the UI's page layout function ",
"(e.g., `fluidPage(theme = bslib::bs_theme())`).",
call. = FALSE
)
}),
tagAppendAttributes(
nav, class = paste0("card-header-", type)
)
)
}
tabs <- tags$div(class = "tabbable", class = if (card) "card", nav)
content <- dropNulls(list(header, tabset$content, footer))
if (card) {
tagAppendChild(tabs, tags$div(class = "card-body", !!!content))
} else {
tagAppendChildren(tabs, content)
}
}
#' Create a navigation list panel

View File

@@ -1,159 +0,0 @@
#' @export
navtreePanel <- function(..., id = NULL,
selected = NULL,
fluid = TRUE,
# Also allow for string to determine padding in a flex layout?
widths = c(3, 9)) {
# TODO: how to incorporate this into a sidebar layout?
if (!is.null(id))
selected <- restoreInput(id = id, default = selected)
tabset <- buildTreePanel(..., ulClass = "nav nav-navtree", id = id, selected = selected)
row <- if (fluid) fluidRow else fixedRow
navList <- attachDependencies(
tabset$navList,
bslib::bs_dependency_defer(navtreeCssDependency)
)
row(
column(widths[[1]], navList),
column(widths[[2]], tabset$content)
)
}
# Algorithm inspired by buildTabset() but we need different HTML/CSS,
# and menus are rendered as collapse toggles instead of Bootstrap dropdowns
buildTreePanel <- function(..., ulClass, id = NULL, selected = NULL, foundSelected = FALSE, depth = 0) {
tabs <- list2(...)
res <- findAndMarkSelectedTab(tabs, selected, foundSelected)
tabs <- res$tabs
foundSelected <- res$foundSelected
# add input class if we have an id
if (!is.null(id)) ulClass <- paste(ulClass, "shiny-tab-input")
if (anyNamed(tabs)) {
nms <- names(tabs)
nms <- nms[nzchar(nms)]
stop("Tabs should all be unnamed arguments, but some are named: ",
paste(nms, collapse = ", "))
}
tabsetId <- p_randomInt(1000, 10000)
tabs <- lapply(
seq_len(length(tabs)), buildTreeItem,
tabsetId = tabsetId,
foundSelected = foundSelected,
tabs = tabs, depth = depth
)
list(
navList = tags$ul(
class = ulClass, id = id,
`data-tabsetid` = tabsetId,
!!!lapply(tabs, "[[", "liTag")
),
content = div(
class = "tab-content",
`data-tabsetid` = tabsetId,
!!!lapply(tabs, "[[", "divTag")
)
)
}
buildTreeItem <- function(index, tabsetId, foundSelected, tabs = NULL, divTag = NULL, depth = 0) {
divTag <- divTag %||% tabs[[index]]
subMenuPadding <- if (depth > 0) css(padding_left = paste0(depth * 1.25, "rem"))
if (isNavbarMenu(divTag)) {
icon <- getIcon(iconClass = divTag$iconClass)
if (!is.null(icon)) {
warning("Configurable icons are not yet supported in navtreePanel().")
}
tabset <- buildTreePanel(
!!!divTag$tabs, ulClass = "nav nav-navtree",
foundSelected = foundSelected, depth = depth + 1
)
# Sort of like .dropdown in the tabsetPanel() case,
# but utilizes collapsing (which is recursive) instead of dropdown
active <- containsSelectedTab(divTag$tabs)
menuId <- paste0("collapse-", p_randomInt(1000, 10000))
liTag <- tags$li(
tags$a(
class = if (!active) "collapsed",
"data-toggle" = "collapse",
"data-value" = divTag$menuName,
"data-target" = paste0("#", menuId),
role = "button",
style = subMenuPadding,
getIcon(iconClass = divTag$iconClass),
divTag$title
),
div(
class = "collapse",
class = if (active) "show in",
id = menuId,
tabset$navList
)
)
return(list(liTag = liTag, divTag = tabset$content$children))
}
if (isTabPanel(divTag)) {
# Borrow from the usual nav logic so we get the right
# li.active vs li > a.active (BS4) markup
navItem <- buildNavItem(divTag, tabsetId, index)
liTag <- navItem$liTag
return(
list(
divTag = navItem$divTag,
liTag = tagFunction(function() {
# Incoming liTag should be a tagFunction()
liTag <- if (inherits(liTag, "shiny.tag.function")) liTag() else liTag
# Add padding for sub menu anchors
liTag$children[[1]] <- tagAppendAttributes(
liTag$children[[1]], style = subMenuPadding
)
liTag
})
)
)
}
abort("navtreePanel() items must be tabPanel()s and/or tabPanelMenu()s")
}
navtreeCssDependency <- function(theme) {
name <- "navtreePanel"
version <- packageVersion("shiny")
if (!is_bs_theme(theme)) {
# TODO: Should we allow navtreePanel() to be statically rendered?
# Can/should we move away from href="shared/*"?
htmlDependency(
name = name, version = version,
src = c(href = "shared/navtree", file = "www/shared/navtree"),
package = "shiny",
stylesheet = "navtree.css",
script = "navtree.js"
)
} else {
navtree <- system.file(package = "shiny", "www/shared/navtree")
bslib::bs_dependency(
sass::sass_file(file.path(navtree, "navtree.scss")),
theme = theme,
name = name,
version = version,
cache_key_extra = version,
.dep_args = list(script = file.path(navtree, "navtree.js"))
)
}
}

View File

@@ -568,7 +568,7 @@ ReactiveValues <- R6Class(
#' @seealso [isolate()] and [is.reactivevalues()].
#' @export
reactiveValues <- function(...) {
args <- list2(...)
args <- list(...)
if ((length(args) > 0) && (is.null(names(args)) || any(names(args) == "")))
rlang::abort("All arguments passed to reactiveValues() must be named.")
@@ -1915,7 +1915,7 @@ reactivePoll <- function(intervalMillis, session, checkFunc, valueFunc) {
#' @export
reactiveFileReader <- function(intervalMillis, session, filePath, readFunc, ...) {
filePath <- coerceToFunc(filePath)
extraArgs <- list2(...)
extraArgs <- list(...)
reactivePoll(
intervalMillis, session,

View File

@@ -178,7 +178,7 @@ getShinyOption <- function(name, default = NULL) {
#' @aliases shiny-options
#' @export
shinyOptions <- function(...) {
newOpts <- list2(...)
newOpts <- list(...)
if (length(newOpts) > 0) {
# If we're within a session, modify at the session level.

View File

@@ -1176,7 +1176,7 @@ reactiveStop <- function(message = "", class = NULL) {
#'
#' }
validate <- function(..., errorClass = character(0)) {
results <- sapply(list2(...), function(x) {
results <- sapply(list(...), function(x) {
# Detect NULL or NA
if (is.null(x))
return(NA_character_)

View File

@@ -1,50 +0,0 @@
.nav-navtree {
display: block !important;
}
.nav-navtree li.active, .nav-navtree li > a.active, .nav-navtree li a:focus {
font-weight: 600;
background-color: rgba(0, 123, 255, 0.3) !important;
color: rgba(33, 37, 41, 0.85);
}
.nav-navtree li:not(.active) > a:not(.active):hover {
background-color: rgba(33, 37, 41, 0.1);
}
.nav-navtree li a {
display: inline-flex !important;
align-items: center !important;
width: 100% !important;
padding: .1875rem .5rem;
border: 1px solid #dee2e6 !important;
color: rgba(33, 37, 41, 0.65) !important;
text-decoration: none !important;
}
.nav-navtree li a i {
padding-right: 1.25rem !important;
}
.nav-navtree li a[data-toggle="collapse"]::before {
width: 1.25em;
line-height: 0;
content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%2833, 37, 41, 0.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e");
transition: transform 0.35s ease;
transform-origin: .5em 50%;
padding-right: 1rem;
}
@media (prefers-reduced-motion: reduce) {
.nav-navtree li a[data-toggle="collapse"]::before {
transition: none;
}
}
.nav-navtree li a[data-toggle="collapse"]:not(.collapsed) {
color: rgba(33, 37, 41, 0.85);
}
.nav-navtree li a[data-toggle="collapse"]:not(.collapsed)::before {
transform: rotate(90deg);
}

View File

@@ -1,11 +0,0 @@
// nav-navtree is built on a combination of Bootstrap's tab &
// collapse components, but the tab component isn't smart enough to
// know about the deactive when are activated. Note that this logic also
// exists in input_binding_tabinput.js and is repeated here in case this
// component wants to be statically rendered
$(document).on("shown.bs.tab", ".nav-navtree", function(e) {
var tgt = $(e.target);
var nav = tgt.parents(".nav-navtree");
nav.find("li").not(tgt).removeClass("active"); // BS3
nav.find("li > a").not(tgt).removeClass("active"); // BS4
});

View File

@@ -1,66 +0,0 @@
// Inspired by BS5's sidebar nav
// https://github.com/twbs/bootstrap/blob/548be2e/site/assets/scss/_sidebar.scss#L7
// As well as
// https://chniter.github.io/bstreeview/
$body-color: $text-color !default; // BS3compat
$link-decoration: none !default;
$link-hover-decoration: underline !default;
$sidebar-collapse-icon-color: rgba($body-color, 0.5) !default;
$sidebar-collapse-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path fill='none' stroke='#{$sidebar-collapse-icon-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/></svg>") !default;
// TODO: make this more configurable from the R side?
$navtree-anchor-color: rgba($body-color, .65) !default;
$navtree-active-bg: rgba($component-active-bg, .3) !default;
$navtree-active-color: rgba($body-color, .85) !default;
.nav-navtree {
// Override collapse behaviors
display: block !important;
li {
// Handle both li.active (BS3) and li > a.active (BS4)
&.active, > a.active, a:focus {
font-weight: 600;
background-color: $navtree-active-bg !important;
color: $navtree-active-color;
}
&:not(.active) > a:not(.active):hover {
background-color: rgba($body-color, .1);
}
a {
display: inline-flex !important; // Needed for centering/transforming the collapse icon
align-items: center !important;
width: 100% !important;
padding: .1875rem .5rem;
border: $border-width solid $border-color !important;
color: $navtree-anchor-color !important;
text-decoration: none !important;
i {
padding-right: 1.25rem !important;
}
// Add chevron if there's a submenu
&[data-toggle="collapse"] {
&::before {
width: 1.25em;
line-height: 0; // Align in the middle
content: escape-svg($sidebar-collapse-icon);
@include transition(transform .35s ease);
transform-origin: .5em 50%;
padding-right: 1rem;
}
&:not(.collapsed) {
color: $navtree-active-color;
&::before {
transform: rotate(90deg);
}
}
}
} // a
} // li
} // nav

View File

@@ -3086,22 +3086,6 @@
$tabContent[0].appendChild(el);
Shiny.renderContent(el, el.innerHTML || el.textContent);
});
if ($tabset.hasClass("nav-navtree") && $liTag.hasClass("dropdown")) {
var collapseId = "collapse-" + tabsetId + "-" + getTabIndex($tabset, tabsetId);
$tabset.find(".dropdown").each(function(i, el) {
var $el = import_jquery6.default(el).removeClass("dropdown nav-item");
$el.find(".dropdown-toggle").removeClass("dropdown-toggle nav-link").addClass(message.select ? "" : "collapsed").attr("data-toggle", "collapse").attr("data-target", "#" + collapseId);
var collapse = import_jquery6.default("<div>").addClass("collapse" + (message.select ? " show" : "")).attr("id", collapseId);
var menu = $el.find(".dropdown-menu").removeClass("dropdown-menu").addClass("nav nav-navtree").wrap(collapse);
var depth = $el.parents(".nav-navtree").length - 1;
if (depth > 0) {
$el.find("a").css("padding-left", depth + "rem");
}
if (menu.find("li").length === 0) {
menu.find("a").removeClass("dropdown-item").addClass("nav-link").wrap("<li class='nav-item'></li>");
}
});
}
if (message.select) {
$liTag.find("a").tab("show");
}
@@ -3143,9 +3127,9 @@
}
});
function ensureTabsetHasVisibleTab($tabset) {
var inputBinding = $tabset.data("shiny-input-binding");
if (!inputBinding.getValue($tabset)) {
if ($tabset.find("li.active").not(".dropdown").length === 0) {
var destTabValue = getFirstTab($tabset);
var inputBinding = $tabset.data("shiny-input-binding");
var evt = jQuery.Event("shiny:updateinput");
evt.binding = inputBinding;
$tabset.trigger(evt);
@@ -6124,7 +6108,6 @@
anchors.each(function() {
if (self2._getTabName(import_jquery6.default(this)) === value) {
import_jquery6.default(this).tab("show");
import_jquery6.default(this).parents(".collapse").collapse("show");
success = true;
return false;
}
@@ -6146,9 +6129,7 @@
import_jquery6.default(el).trigger("change");
},
subscribe: function subscribe(el, callback) {
var deactivateOtherTabs = this._deactivateOtherTabs;
import_jquery6.default(el).on("change shown.bootstrapTabInputBinding shown.bs.tab.bootstrapTabInputBinding", function(event) {
deactivateOtherTabs(event);
callback();
});
},
@@ -6157,12 +6138,6 @@
},
_getTabName: function _getTabName(anchor) {
return anchor.attr("data-value") || anchor.text();
},
_deactivateOtherTabs: function _deactivateOtherTabs(event) {
var tgt = import_jquery6.default(event.target);
var nav = tgt.parents(".nav-navtree");
nav.find("li").not(tgt).removeClass("active");
nav.find("li > a").not(tgt).removeClass("active");
}
});
inputBindings.register(bootstrapTabInputBinding, "shiny.bootstrapTabInput");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -11,6 +11,7 @@ tabsetPanel(
type = c("tabs", "pills", "hidden"),
header = NULL,
footer = NULL,
card = FALSE,
position = deprecated()
)
}
@@ -40,6 +41,8 @@ tabPanels.}
\item{footer}{Tag or list of tags to display as a common footer below all
tabPanels}
\item{card}{whether to wrap the navigation controls and content into an 'output card'. This functionality currently requires a \code{\link[bslib:bs_theme]{bslib::bs_theme()}} in the page layout with \code{version = 4} or higher.}
\item{position}{This argument is deprecated; it has been discontinued in
Bootstrap 3.}
}

View File

@@ -1407,50 +1407,6 @@ function main(): void {
Shiny.renderContent(el, el.innerHTML || el.textContent);
});
// If we're inserting a navbarMenu() into a navtreePanel() target, we need
// to transform buildTabset() output (i.e., a .dropdown component) to
// buildTreePanel() output (i.e., a .collapse component), because
// insertTab() et al. doesn't know about the relevant tabset container
if ($tabset.hasClass("nav-navtree") && $liTag.hasClass("dropdown")) {
let collapseId =
"collapse-" + tabsetId + "-" + getTabIndex($tabset, tabsetId);
$tabset.find(".dropdown").each(function (i, el) {
let $el = $(el).removeClass("dropdown nav-item");
$el
.find(".dropdown-toggle")
.removeClass("dropdown-toggle nav-link")
.addClass(message.select ? "" : "collapsed")
.attr("data-toggle", "collapse")
.attr("data-target", "#" + collapseId);
let collapse = $("<div>")
.addClass("collapse" + (message.select ? " show" : ""))
.attr("id", collapseId);
let menu = $el
.find(".dropdown-menu")
.removeClass("dropdown-menu")
.addClass("nav nav-navtree")
.wrap(collapse);
let depth = $el.parents(".nav-navtree").length - 1;
if (depth > 0) {
$el.find("a").css("padding-left", depth + "rem");
}
if (menu.find("li").length === 0) {
menu
.find("a")
.removeClass("dropdown-item")
.addClass("nav-link")
.wrap("<li class='nav-item'></li>");
}
});
}
if (message.select) {
$liTag.find("a").tab("show");
}
@@ -1524,16 +1480,13 @@ function main(): void {
// If the given tabset has no active tabs, select the first one
function ensureTabsetHasVisibleTab($tabset) {
const inputBinding = $tabset.data("shiny-input-binding");
// Use the getValue() method to avoid duplicating the CSS selector
// for querying the DOM for the currently active tab
if (!inputBinding.getValue($tabset)) {
if ($tabset.find("li.active").not(".dropdown").length === 0) {
// Note: destTabValue may be null. We still want to proceed
// through the below logic and setValue so that the input
// value for the tabset gets updated (i.e. input$tabsetId
// should be null if there are no tabs).
const destTabValue = getFirstTab($tabset);
const inputBinding = $tabset.data("shiny-input-binding");
const evt = jQuery.Event("shiny:updateinput");
evt.binding = inputBinding;
@@ -5969,8 +5922,6 @@ function main(): void {
anchors.each(function () {
if (self._getTabName($(this)) === value) {
$(this).tab("show");
// navtreePanel()'s navbarMenu() uses collapsing -- expand the menu when activated!
$(this).parents(".collapse").collapse("show");
success = true;
return false; // Break out of each()
}
@@ -5991,12 +5942,9 @@ function main(): void {
$(el).trigger("change");
},
subscribe: function (el, callback) {
let deactivateOtherTabs = this._deactivateOtherTabs;
$(el).on(
"change shown.bootstrapTabInputBinding shown.bs.tab.bootstrapTabInputBinding",
function (event) {
deactivateOtherTabs(event);
callback();
}
);
@@ -6007,17 +5955,6 @@ function main(): void {
_getTabName: function (anchor) {
return anchor.attr("data-value") || anchor.text();
},
// nav-navtree is built on a combination of Bootstrap's tab &
// collapse components, but the tab component isn't smart enough to
// know about the deactive when are activated. Note that this logic is
// very similar to shinydashboard's deactivateOtherTabs() (in tab.js)
_deactivateOtherTabs: function (event) {
let tgt = $(event.target);
let nav = tgt.parents(".nav-navtree");
nav.find("li").not(tgt).removeClass("active"); // BS3
nav.find("li > a").not(tgt).removeClass("active"); // BS4
},
});
inputBindings.register(bootstrapTabInputBinding, "shiny.bootstrapTabInput");

View File

@@ -138,6 +138,43 @@
<div class="content-footer"></div>
</div>
---
Code
bslib_tags(x)
Output
<div class="tabbable card">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs" data-tabsetid="4785">
<li class="nav-item">
<a class="nav-link active" data-toggle="tab" data-value="A" href="#tab-4785-1">A</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#tab-4785-2" data-toggle="tab" data-value="B">
<i class=" fab fa-github fa-fw" role="presentation" aria-label=" icon"></i>
B
</a>
</li>
<li class="dropdown nav-item">
<a class="dropdown-toggle nav-link" data-toggle="dropdown" data-value="Menu" href="#">
Menu
<b class="caret"></b>
</a>
<ul class="dropdown-menu" data-tabsetid="1502">
<a class="dropdown-item" href="#tab-1502-1" data-toggle="tab" data-value="C">C</a>
</ul>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content" data-tabsetid="4785">
<div class="tab-pane active" data-value="A" id="tab-4785-1">a</div>
<div class="tab-pane" data-value="B" data-icon-class="fab fa-github" id="tab-4785-2">b</div>
<div class="tab-pane" data-value="C" id="tab-1502-1">c</div>
</div>
</div>
</div>
# navbarPage() markup is correct
Code

View File

@@ -54,6 +54,8 @@ test_that("tabsetPanel() markup is correct", {
# BS4
expect_snapshot_bslib(default)
expect_snapshot_bslib(pills)
card <- tabset_panel(!!!panels, card = TRUE)
expect_snapshot_bslib(card)
})
test_that("navbarPage() markup is correct", {

View File

@@ -1,15 +0,0 @@
library(sass)
home <- rprojroot::find_package_root_file("inst/www/shared/navtree")
# TODO: write a unit test
withr::with_dir(
home, {
sass_partial(
sass_file("navtree.scss"),
bslib::bs_theme(),
output = "navtree.css",
cache = FALSE,
write_attachments = FALSE
)
}
)