diff --git a/.Rbuildignore b/.Rbuildignore
index cdadbc29d..70da815c8 100644
--- a/.Rbuildignore
+++ b/.Rbuildignore
@@ -2,3 +2,4 @@
^\.Rproj\.user$
inst/node_modules/.yarn-integrity
inst/node_modules/bootswatch/.github
+^test-apps$
diff --git a/R/themes.R b/R/themes.R
index a5e421bac..1904b2bed 100644
--- a/R/themes.R
+++ b/R/themes.R
@@ -41,7 +41,7 @@ bs4_themes_join <- function(theme1 = bs4_theme(), theme2 = bs4_theme()) {
bs4_theme(
pre = as_sass(paste0(theme1$pre, theme2$pre)),
post = as_sass(paste0(theme2$post, theme1$post)),
- deps = c(theme1$deps, theme1$deps)
+ deps = c(theme1$deps, theme2$deps)
)
}
@@ -69,7 +69,15 @@ bs4_theme_bootswatch <- function(theme = "") {
bs4_theme_bs3compat <- function() {
bs4_theme(
pre = sass_file(system.file("bs3compat", "_pre_variables.scss", package = "bootscss")),
- post = sass_file(system.file("bs3compat", "_post_variables.scss", package = "bootscss"))
+ post = sass_file(system.file("bs3compat", "_post_variables.scss", package = "bootscss")),
+ deps = list(
+ htmltools::htmlDependency(
+ "bs3compat", packageVersion("bootscss"),
+ package = "bootscss",
+ src = "bs3compat/js",
+ script = c("tabs.js", "bs3compat.js")
+ )
+ )
)
}
diff --git a/inst/bs3compat/_components_compat.scss b/inst/bs3compat/_components_compat.scss
new file mode 100644
index 000000000..97ee261cd
--- /dev/null
+++ b/inst/bs3compat/_components_compat.scss
@@ -0,0 +1,7 @@
+.well {
+ @extend .bg-light; @extend .card; @extend .p-3;
+}
+
+.help-text, .help-block {
+ @extend .form-text; @extend .text-muted;
+}
diff --git a/inst/bs3compat/_dropdown_compat.scss b/inst/bs3compat/_dropdown_compat.scss
new file mode 100644
index 000000000..0f4d19585
--- /dev/null
+++ b/inst/bs3compat/_dropdown_compat.scss
@@ -0,0 +1,23 @@
+// # DROPDOWNS
+//
+// In bs3, dropdown menus are .dropdown-menu>li.active>a
+// In bs4, dropdown menus are .dropdown-menu>.dropdown-item.active
+//
+// Also, bs3 dropdowns within tabs/pills are interfered with in bs4 by
+// selectors like `.bs-tabs li>a`, making menu items look like tabs.
+
+.dropdown-menu>li>a {
+ @extend .dropdown-item;
+}
+.dropdown-menu>li.active>a {
+ // This @extend works, but it litters `.dropdown-menu>li.active>a` all over
+ // the bootstrap.css output because it's such a common class. Instead, we
+ // copy these few properties from from _dropdown.scss.
+ // @extend .active;
+ color: $dropdown-link-active-color;
+ text-decoration: none;
+ @include gradient-bg($dropdown-link-active-bg);
+}
+.dropdown-menu>li.divider {
+ @extend .dropdown-divider;
+}
diff --git a/inst/bs3compat/_nav_compat.scss b/inst/bs3compat/_nav_compat.scss
new file mode 100644
index 000000000..25ee4e530
--- /dev/null
+++ b/inst/bs3compat/_nav_compat.scss
@@ -0,0 +1,32 @@
+// Fix tab selector borders in bs3.
+.nav-tabs>li,
+.nav-pills>li {
+ @extend .nav-item;
+}
+.nav-tabs>li>a,
+.nav-pills>li>a {
+ @extend .nav-link;
+}
+
+// Active tab/pill.
+//
+// bs3 uses .nav>li.active>a, bs4 uses .nav>li>a.active or .nav>li.show>a.
+//
+// My original approach to this was making .nav>li.active @extend .show, but
+// after a lot of trial and error I could not get it to fully work.
+.nav-tabs>li.active>a {
+ color: $nav-tabs-link-active-color;
+ background-color: $nav-tabs-link-active-bg;
+ border-color: $nav-tabs-link-active-border-color;
+}
+.nav-pills>li.active>a {
+ color: $nav-pills-link-active-color;
+ background-color: $nav-pills-link-active-bg;
+}
+
+// Support vertical pills
+.nav-stacked {
+ // Don't extend the .flex-column utility, it uses !important
+ // @extend .flex-column;
+ flex-direction: column;
+}
diff --git a/inst/bs3compat/_navbar_compat.scss b/inst/bs3compat/_navbar_compat.scss
new file mode 100644
index 000000000..9b9f55a2a
--- /dev/null
+++ b/inst/bs3compat/_navbar_compat.scss
@@ -0,0 +1,53 @@
+// bs4 navbars require .navbar-expand[-sm|-md|-lg|-xl], but bs3 navbars
+// don't have them. This selector matches .navbar without .navbar-expand
+// and defaults it to .navbar-expand-sm.
+.navbar:not(.navbar-expand):not(.navbar-expand-sm):not(.navbar-expand-md):not(.navbar-expand-lg):not(.navbar-expand-xl) {
+ @extend .navbar-expand-sm;
+}
+
+// Map BS3 navbar positioning to general utilities
+.navbar-fixed-top {
+ @extend .fixed-top;
+}
+.navbar-fixed-bottom {
+ @extend .fixed-bottom;
+}
+.navbar-sticky-top {
+ @extend .sticky-top;
+}
+
+ul.nav.navbar-nav {
+ flex: 1;
+ min-width: map-get($container-max-widths, sm) - 30px;
+ &.navbar-right {
+ justify-content: end;
+ }
+}
+
+ul.nav.navbar-nav>li:not(.dropdown) {
+ @extend .nav-item;
+}
+ul.nav.navbar-nav>li>a {
+ @extend .nav-link;
+}
+.navbar.navbar-default {
+ @extend .navbar-light;
+ @extend .bg-light;
+}
+.navbar.navbar-inverse {
+ color: $navbar-dark-color;
+ @extend .navbar-dark;
+ @extend .bg-dark;
+}
+
+// Implement bs3 navbar toggler; used in Rmd websites, i.e.
+// https://github.com/rstudio/rmarkdown-website/blob/453e1802b32b5baf1c8a67f80947adcc53e49b7f/_navbar.html
+.navbar-toggle {
+ @extend .navbar-toggler;
+}
+.navbar-toggle>.icon-bar+.icon-bar {
+ display: none;
+}
+.navbar-toggle>.icon-bar:first-child {
+ @extend .navbar-toggler-icon;
+}
diff --git a/inst/bs3compat/_post_variables.scss b/inst/bs3compat/_post_variables.scss
index 048db5aee..a4ba449a0 100644
--- a/inst/bs3compat/_post_variables.scss
+++ b/inst/bs3compat/_post_variables.scss
@@ -1,79 +1,7 @@
-.well {
- @extend .bg-light; @extend .card; @extend .p-3;
-}
+@import "components_compat";
+@import "dropdown_compat";
+@import "navbar_compat";
+@import "nav_compat";
-// For verbatimTextOutput()
-pre.shiny-text-output {
- @extend .bg-light; @extend .card; @extend .p-2;
-}
-
-// For code inside of showcase mode
-pre.shiny-code {
- padding: 0.5rem;
-}
-
-.help-text, .help-block {
- @extend .form-text; @extend .text-muted;
-}
-
-/*
-/ Shim for navbarMenu() inside tabsetPanel() (or navlistPanel())
-/ Note that the additional CSS intentionally makes it virtually impossible
-/ to click the
tag, so we have "one source of truth" when we clean up
-/ active tags in shiny's tab showing logic
-/ https://github.com/rstudio/shiny/blob/2d979d0/srcjs/input_binding_tabinput.js#L41-L63
-*/
-.nav .dropdown-menu li:not(.divider) {
- @extend .nav-pills, .dropdown-item;
- padding: 0;
- a.nav-link {
- border: none;
- border-radius: 0;
- }
- a.nav-link:not(.active):hover {
- background-color: $light !important;
- }
-}
-
-.shiny-input-checkboxgroup, .shiny-input-radiogroup {
- .checkbox, .radio {
- @extend .form-check;
- label {
- @extend .form-check-label;
- }
- label > input {
- @extend .form-check-input;
- }
- }
-
- // Since these inline classes don't have a proper div container
- // (they're labels), we borrow just the styling we need from
- // .form-check-inline
- // https://github.com/rstudio/bs4/blob/7aadd19/inst/node_modules/bootstrap/scss/_forms.scss#L227-L240
- .checkbox-inline, .radio-inline {
- padding-left: 0;
- margin-right: $form-check-inline-margin-x;
-
- label > input {
- margin-top: 0;
- margin-right: $form-check-inline-input-margin-x;
- margin-bottom: 0;
- }
- }
-}
-
-.input-daterange .input-group-addon.input-group-prepend.input-group-append {
- padding: inherit;
- line-height: inherit;
- text-shadow: inherit;
- border-width: 0;
-}
-
-.selectize-input.focus {
- @extend .form-control:focus;
-}
-
-.selectize-control.multi .selectize-input > div.active {
- background: $component-active-bg;
- color: $component-active-color;
-}
+@import "shiny_input";
+@import "shiny_misc";
diff --git a/inst/bs3compat/_shiny_input.scss b/inst/bs3compat/_shiny_input.scss
new file mode 100644
index 000000000..f27e06ad1
--- /dev/null
+++ b/inst/bs3compat/_shiny_input.scss
@@ -0,0 +1,42 @@
+.shiny-input-checkboxgroup, .shiny-input-radiogroup {
+ .checkbox, .radio {
+ @extend .form-check;
+ label {
+ @extend .form-check-label;
+ }
+ label > input {
+ @extend .form-check-input;
+ }
+ }
+
+ // Since these inline classes don't have a proper div container
+ // (they're labels), we borrow just the styling we need from
+ // .form-check-inline
+ // https://github.com/rstudio/bs4/blob/7aadd19/inst/node_modules/bootstrap/scss/_forms.scss#L227-L240
+ .checkbox-inline, .radio-inline {
+ padding-left: 0;
+ margin-right: $form-check-inline-margin-x;
+
+ label > input {
+ margin-top: 0;
+ margin-right: $form-check-inline-input-margin-x;
+ margin-bottom: 0;
+ }
+ }
+}
+
+.input-daterange .input-group-addon.input-group-prepend.input-group-append {
+ padding: inherit;
+ line-height: inherit;
+ text-shadow: inherit;
+ border-width: 0;
+}
+
+.selectize-input.focus {
+ @extend .form-control:focus;
+}
+
+.selectize-control.multi .selectize-input > div.active {
+ background: $component-active-bg;
+ color: $component-active-color;
+}
diff --git a/inst/bs3compat/_shiny_misc.scss b/inst/bs3compat/_shiny_misc.scss
new file mode 100644
index 000000000..3762c2253
--- /dev/null
+++ b/inst/bs3compat/_shiny_misc.scss
@@ -0,0 +1,9 @@
+// For verbatimTextOutput()
+pre.shiny-text-output {
+ @extend .bg-light; @extend .card; @extend .p-2;
+}
+
+// For code inside of showcase mode
+pre.shiny-code {
+ padding: 0.5rem;
+}
diff --git a/inst/bs3compat/js/bs3compat.js b/inst/bs3compat/js/bs3compat.js
new file mode 100644
index 000000000..230aa7122
--- /dev/null
+++ b/inst/bs3compat/js/bs3compat.js
@@ -0,0 +1,72 @@
+(function($) {
+ if (!$.fn.tab.Constructor.VERSION.match(/^3\./)) {
+ (console.warn || console.error || console.log)("bs3compat.js couldn't find bs3 tab impl; bs3 tabs will not be properly supported");
+ return;
+ }
+ var bs3TabPlugin = $.fn.tab.noConflict();
+
+ if (!$.fn.tab.Constructor.VERSION.match(/^4\./)) {
+ (console.warn || console.error || console.log)("bs3compat.js couldn't find bs4 tab impl; bs3 tabs will not be properly supported");
+ return;
+ }
+ var bs4TabPlugin = $.fn.tab.noConflict();
+
+ var EVENT_KEY = "click.bs.tab.data-api";
+ var SELECTOR = '[data-toggle="tab"], [data-toggle="pill"]';
+
+ $(document).off(EVENT_KEY);
+ $(document).on(EVENT_KEY, SELECTOR, function(event) {
+ event.preventDefault();
+ $(this).tab("show");
+ });
+
+ function TabPlugin(config) {
+ if ($(this).closest(".nav").find(".nav-item, .nav-link").length === 0) {
+ // Bootstrap 3 tabs detected
+ bs3TabPlugin.call($(this), config);
+ } else {
+ // Bootstrap 4 tabs detected
+ bs4TabPlugin.call($(this), config);
+ }
+ }
+
+ var noconflict = $.fn.tab;
+ $.fn.tab = TabPlugin;
+ $.fn.tab.Constructor = bs4TabPlugin.Constructor;
+ $.fn.tab.noConflict = function() {
+ $.fn.tab = noconflict;
+ return TabPlugin;
+ };
+
+})(jQuery);
+
+// bs3 navbar: li.active > a
+// bs4 navbar: li > a.active
+// bs3 tabset: li.active > a
+// bs4 tabset: li > a.active
+
+
+(function($) {
+ /*
+ * Bootstrap 4 uses poppler.js to choose what direction to show dropdown
+ * menus, except in the case of navbars; they assume that navbars are always
+ * at the top of the page, so this isn't necessary. However, Bootstrap 3
+ * explicitly supported bottom-positioned navbars via .navbar-fixed-bottom,
+ * and .fixed-bottom works on Bootstrap 4 as well.
+ *
+ * We monkeypatch the dropdown plugin's _detectNavbar method to return false
+ * if we're in a bottom-positioned navbar.
+ */
+ if (!$.fn.dropdown.Constructor.prototype._detectNavbar) {
+ // If we get here, the dropdown plugin's implementation must've changed.
+ // Someone will need to go into Bootstrap's dropdown.js.
+ (console.warn || console.error || console.log)("bs3compat.js couldn't detect the dropdown plugin's _detectNavbar method");
+ return;
+ }
+
+ var oldDetectNavbar = $.fn.dropdown.Constructor.prototype._detectNavbar;
+ $.fn.dropdown.Constructor.prototype._detectNavbar = function() {
+ return oldDetectNavbar.apply(this, this.arguments) &&
+ !($(this._element).closest('.navbar').filter('.navbar-fixed-bottom, .fixed-bottom').length > 0);
+ };
+})(jQuery);
diff --git a/inst/bs3compat/js/tabs.js b/inst/bs3compat/js/tabs.js
new file mode 100644
index 000000000..74495dffc
--- /dev/null
+++ b/inst/bs3compat/js/tabs.js
@@ -0,0 +1,155 @@
+/* ========================================================================
+ * Bootstrap: tab.js v3.4.1
+ * https://getbootstrap.com/docs/3.4/javascript/#tabs
+ * ========================================================================
+ * Copyright 2011-2019 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // TAB CLASS DEFINITION
+ // ====================
+
+ var Tab = function (element) {
+ // jscs:disable requireDollarBeforejQueryAssignment
+ this.element = $(element)
+ // jscs:enable requireDollarBeforejQueryAssignment
+ }
+
+ Tab.VERSION = '3.4.1'
+
+ Tab.TRANSITION_DURATION = 150
+
+ Tab.prototype.show = function () {
+ var $this = this.element
+ var $ul = $this.closest('ul:not(.dropdown-menu)')
+ var selector = $this.data('target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ if ($this.parent('li').hasClass('active')) return
+
+ var $previous = $ul.find('.active:last a')
+ var hideEvent = $.Event('hide.bs.tab', {
+ relatedTarget: $this[0]
+ })
+ var showEvent = $.Event('show.bs.tab', {
+ relatedTarget: $previous[0]
+ })
+
+ $previous.trigger(hideEvent)
+ $this.trigger(showEvent)
+
+ if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
+
+ var $target = $(document).find(selector)
+
+ this.activate($this.closest('li'), $ul)
+ this.activate($target, $target.parent(), function () {
+ $previous.trigger({
+ type: 'hidden.bs.tab',
+ relatedTarget: $this[0]
+ })
+ $this.trigger({
+ type: 'shown.bs.tab',
+ relatedTarget: $previous[0]
+ })
+ })
+ }
+
+ Tab.prototype.activate = function (element, container, callback) {
+ var $active = container.find('> .active')
+ var transition = callback
+ && $.support.transition
+ && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
+
+ function next() {
+ $active
+ .removeClass('active')
+ .find('> .dropdown-menu > .active')
+ .removeClass('active')
+ .end()
+ .find('[data-toggle="tab"]')
+ .attr('aria-expanded', false)
+
+ element
+ .addClass('active')
+ .find('[data-toggle="tab"]')
+ .attr('aria-expanded', true)
+
+ if (transition) {
+ element[0].offsetWidth // reflow for transition
+ element.addClass('in')
+ } else {
+ element.removeClass('fade')
+ }
+
+ if (element.parent('.dropdown-menu').length) {
+ element
+ .closest('li.dropdown')
+ .addClass('active')
+ .end()
+ .find('[data-toggle="tab"]')
+ .attr('aria-expanded', true)
+ }
+
+ callback && callback()
+ }
+
+ $active.length && transition ?
+ $active
+ .one('bsTransitionEnd', next)
+ .emulateTransitionEnd(Tab.TRANSITION_DURATION) :
+ next()
+
+ $active.removeClass('in')
+ }
+
+
+ // TAB PLUGIN DEFINITION
+ // =====================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.tab')
+
+ if (!data) $this.data('bs.tab', (data = new Tab(this)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.tab
+
+ $.fn.tab = Plugin
+ $.fn.tab.Constructor = Tab
+
+
+ // TAB NO CONFLICT
+ // ===============
+
+ $.fn.tab.noConflict = function () {
+ $.fn.tab = old
+ return this
+ }
+
+
+ // TAB DATA-API
+ // ============
+
+ var clickHandler = function (e) {
+ e.preventDefault()
+ Plugin.call($(this), 'show')
+ }
+
+ $(document)
+ .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
+ .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
+
+}(jQuery);
diff --git a/test-apps/bs3-navs/app.R b/test-apps/bs3-navs/app.R
new file mode 100644
index 000000000..0426dc8a2
--- /dev/null
+++ b/test-apps/bs3-navs/app.R
@@ -0,0 +1,204 @@
+library(bootscss)
+library(shiny)
+
+make_bs3_tabs <- function() {
+ list(
+ tabPanel("One",
+ "One"
+ ),
+ tabPanel("Two",
+ icon = icon("download"),
+ "Two"
+ ),
+ navbarMenu("A submenu",
+ tabPanel("Three", "Three"),
+ "---",
+ tabPanel("Four", "Four"),
+ tabPanel("Five", "Five")
+ )
+ )
+}
+
+
+make_bs4_tabs <- function(id, type = "nav-tabs") {
+ ns <- NS(paste0("#", id))
+ withTags(
+ ul(class = "nav",
+ class = type,
+ li(class = "nav-item",
+ a("data-toggle" = "tab",
+ class = "nav-link active",
+ href = ns("1"),
+ "Link 1"
+ )
+ ),
+ li(class = "nav-item",
+ a("data-toggle" = "tab",
+ class = "nav-link",
+ href = ns("2"),
+
+ icon("download"),
+ "Link 2"
+ )
+ ),
+ li(class = "nav-item dropdown",
+ a(class = "nav-link dropdown-toggle",
+ "data-toggle" = "dropdown",
+ href = "#",
+ role = "button",
+ "aria-haspopup" = "true",
+ "aria-expanded" = "false",
+ "Dropdown"
+ ),
+ div(class = "dropdown-menu",
+ a("data-toggle" = "tab",
+ class = "dropdown-item",
+ href = ns("3"),
+ "Link 3"
+ ),
+ a("data-toggle" = "tab",
+ class = "dropdown-item",
+ href = ns("4"),
+ "Link 4"
+ ),
+ div(class = "dropdown-divider"),
+ a("data-toggle" = "tab",
+ class = "dropdown-item disabled",
+ href = ns("dis-1"),
+ tabindex = "-1",
+ "aria-disabled" = "true",
+ "Disabled Link"
+ ),
+ a("data-toggle" = "tab",
+ class = "dropdown-item disabled",
+ href = ns("dis-2"),
+ tabindex = "-1",
+ "aria-disabled" = "true",
+ "Another Disabled Link"
+ )
+ )
+ ),
+ li(class = "nav-item",
+ a("data-toggle" = "tab",
+ class = "nav-link disabled",
+ href = ns("dis-3"),
+ tabindex = "-1",
+ "aria-disabled" = "true",
+ "Disabled Link"
+ )
+ )
+ )
+ )
+}
+
+make_bs4_tab_contents <- function(id) {
+ ns <- NS(id)
+ div(class = "tab-content",
+ div(class = "tab-pane fade show active",
+ id = ns("1"),
+ "Panel 1"
+ ),
+ div(class = "tab-pane fade",
+ id = ns("2"),
+ "Panel 2"
+ ),
+ div(class = "tab-pane fade",
+ id = ns("3"),
+ "Panel 3"
+ ),
+ div(class = "tab-pane fade",
+ id = ns("4"),
+ "Panel 4"
+ )
+ )
+}
+
+
+ui <- fluidPage(
+ bs4_sass(),
+ tags$style(
+ "h4 { margin-top: 120px; }"
+ ),
+
+ tags$br(),
+ tags$br(),
+ tags$br(),
+ tags$br(),
+ tags$br(),
+
+ h4("bs3 navbarPage"),
+ do.call(navbarPage, rlang::list2(
+ "bs3 navbarPage", inverse = FALSE,
+ !!!make_bs3_tabs()
+ )),
+
+ h4("bs3 navbarPage (inverse)"),
+ helpText("(Fixed to bottom of page)"),
+ do.call(navbarPage, rlang::list2(
+ "bs3 navbarPage (inverse)", inverse = TRUE, position = "fixed-bottom",
+ !!!make_bs3_tabs()
+ )),
+
+ h4("bs4 navbar - default"),
+ tags$nav(class="navbar navbar-expand-sm navbar-light bg-light",
+ tags$a(class="navbar-brand", href="#", "Navbar"),
+ make_bs4_tabs("bs4nav", "navbar-nav mr-auto"),
+ ),
+ make_bs4_tab_contents("bs4nav"),
+
+ h4("bs4 navbar (dark)"),
+ tags$nav(class="navbar navbar-expand-sm navbar-dark bg-dark",
+ tags$a(class="navbar-brand", href="#", "Navbar"),
+ make_bs4_tabs("bs4navdark", "navbar-nav mr-auto"),
+ ),
+ make_bs4_tab_contents("bs4navdark"),
+
+ h4("bs4 navbar (.bg-success)"),
+ tags$nav(class="navbar navbar-expand-sm navbar-dark bg-success",
+ tags$a(class="navbar-brand", href="#", "Navbar"),
+ make_bs4_tabs("bs4navsuccess", "navbar-nav mr-auto"),
+ ),
+ make_bs4_tab_contents("bs4navsuccess"),
+
+ h4("bs3 rmarkdown site navbar"),
+ p(class = "text-center", "(pinned to top of page)"),
+ includeHTML("navbar-rmdsite.html"),
+
+ h4("bs3 tabsetPanel"),
+ do.call(tabsetPanel, rlang::list2(
+ !!!make_bs3_tabs()
+ )),
+
+ h4("bs4 tabs"),
+ make_bs4_tabs("bs4tabs", "nav-tabs"),
+ make_bs4_tab_contents("bs4tabs"),
+
+ h4("bs3 tabsetPanel(type=\"pills\")"),
+ do.call(tabsetPanel, rlang::list2(
+ type = "pills",
+ !!!make_bs3_tabs()
+ )),
+
+ h4("bs4 pills"),
+ make_bs4_tabs("bs4pills", "nav-pills"),
+ make_bs4_tab_contents("bs4pills"),
+
+ h4("bs3 navlist panel"),
+ do.call(navlistPanel, rlang::list2(
+ !!!make_bs3_tabs()
+ )),
+
+ tags$br(),
+ tags$br(),
+ tags$br(),
+ tags$br()
+)
+
+# .navbar-expand .navbar-nav .nav-link
+# .navbar-expand ul.nav.navbar-nav > li > a
+
+server <- function(input, output, session) {
+
+}
+
+shinyApp(ui, server)
diff --git a/test-apps/bs3-navs/navbar-rmdsite.html b/test-apps/bs3-navs/navbar-rmdsite.html
new file mode 100644
index 000000000..dbd24e882
--- /dev/null
+++ b/test-apps/bs3-navs/navbar-rmdsite.html
@@ -0,0 +1,24 @@
+
+