Permalink
Browse files

feat(tabs): vertical tabs + new props for adding classes to inner ele…

…ments (#1362)

* feat(tabs): add props for adding classes to inner elements

Adds the following new props:
- `containerClass` added to the `.tab-container` div
- `navClass` added to the `.nav` tab controls
- `navWrapperClass` (added to div wrapper around nav tab controls

* ESLint

* [tabs] support for vertical tabs

* [tab] support for vertical tabs

* Update README.md

* Update README.md

* readme typos

* [tabs] use card-footer class for bottom, & column ordering for accessible markup

* Update tabs.js

* Update README.md

* Update README.md

* Update tabs.js

* tabs: always use row/col layout for ordering

* tabs: revert to physical ordering from flex ordering

Keyboard navigation using flex ordering sucks

* ESLint

* Update README.md

* fix(docs) change notes formatting
  • Loading branch information...
tmorehouse committed Nov 20, 2017
1 parent 360b337 commit 51d0e03a14ad792966f7d9bd5da95b82158dac25
Showing with 173 additions and 11 deletions.
  1. +104 −2 src/components/tabs/README.md
  2. +1 −0 src/components/tabs/tab.js
  3. +68 −9 src/components/tabs/tabs.js
@@ -64,10 +64,113 @@ the pill style variant.
<!-- tabs-pills.vue -->
```
## Bottom placement of tab controls
Visually move the tab controls to the bottom by setting the prop `end`
```html
<b-card no-body>
<b-tabs pills card end>
<b-tab title="Tab 1" active>
Tab Contents
</b-tab>
<b-tab title="Tab 2">
Tab Contents 2
</b-tab>
</b-tabs>
</b-card>
<!-- tabs-bottom.vue -->
```
**Caveats:**
- Bottom placement visually works best with the `pills` variant. When using the default
`tabs` vairiant, you may want to provided your own custom styling classes, as Bootstrap
V4 CSS assumes the tabs will always be placed on the top of the tabs content.
- To provide a better user experience with bottom palced controls, ensure that the
content of each tab pane is the same height and fits completely within the visible
viewport, otherwise the user will need to scroll up to read the start of the tabed content.
**Note:** _the `bottom` prop has been deprecated in favor of the `end` prop._
## Vertical tabs
Have the tab controls placed on the lefthand side by setting the `vertical` prop to `true`.
```html
<b-card no-body>
<b-tabs pills card vertical>
<b-tab title="Tab 1" active>
Tab Contents
</b-tab>
<b-tab title="Tab 2">
Tab Contents 2
</b-tab>
<b-tab title="Tab 3">
Tab Contents 3
</b-tab>
</b-tabs>
</b-card>
<!-- tabs-vertical.vue -->
```
Visually move the tab controls to the right hand side by setting the `end` prop:
```html
<b-card no-body>
<b-tabs pills card vertical end>
<b-tab title="Tab 1" active>
Tab Contents
</b-tab>
<b-tab title="Tab 2">
Tab Contents 2
</b-tab>
<b-tab title="Tab 3">
Tab Contents 3
</b-tab>
</b-tabs>
</b-card>
<!-- tabs-vertical-end.vue -->
```
The width of the vertical tab controls will expand to fit the width of the tab title.
To control the width, set a [width utility class](/docs/reference/size-props#sizing-utility-classes)
via the prop `nav-wrapper-class`. You can use values such as `w-25` (25% width), `w-50` (50% width), etc.,
or column classes such as `col-2`, `col-3`, etc.
```html
<b-card no-body>
<b-tabs pills card vertical nav-wrapper-class="w-50">
<b-tab title="Tab 1" active>
Tab Contents
</b-tab>
<b-tab title="Tab 2">
Tab Contents 2
</b-tab>
<b-tab title="Tab 3">
Tab Contents 3
</b-tab>
</b-tabs>
</b-card>
<!-- tabs-vertical-width.vue -->
```
Vertical placement visually works best with the `pills` variant. When using the default
`tabs` vairiant, you may want to provided your own custom styling classes, as Bootstrap
V4 CSS assumes the tabs will always be placed on the top of the tabs content.
**Note:** _overflowing text may occur if your width is narrower than the tab title. You may need additional custom styling._
## Fade animation
Fade is enabled by default when changing tabs. It can disabled with `no-fade` property.
Note you should use the `<b-nav-item>` component when adding contentless-tabs to maintain
correct sizing and alignment. See the advanced usage examples below for an example.
## Add Tabs without content
@@ -92,8 +195,7 @@ In some situations, you may want to add classes to the `<li>` (nav-item) and/or
the `title-item-class` prop (for the `<li>` element) or `title-link-class` prop (for the
`<a>` element). Value's can be passed as a string or array of strings.
Note: THe `ative` class is automatically applied to the `<a>` element. You may need to accomodate
your custom classes for this.
**Note:** _The `active` class is automatically applied to the active tabs `<a>` element. You may need to accomodate your custom classes for this._
```html
<template>
@@ -60,6 +60,7 @@ export default {
tabClasses () {
return [
'tab-pane',
(this.$parent && this.$parent.card) ? 'card-body' : '',
this.show ? 'show' : '',
this.computedFade ? 'fade' : '',
this.disabled ? 'disabled' : '',
@@ -89,38 +89,76 @@ export default {
})
// Nav 'button' wrapper
const navs = h(
let navs = h(
'ul',
{
class: [ 'nav', `nav-${t.navStyle}`, { [`card-header-${t.navStyle}`]: this.card, small: t.small } ],
attrs: { role: 'tablist', tabindex: '0' },
class: [
'nav',
`nav-${t.navStyle}`,
{
[`card-header-${t.navStyle}`]: (t.card && !t.vertical),
'card-header': (t.card && t.vertical),
'h-100': (t.card && t.vertical),
'flex-column': t.vertical,
'border-bottom-0': t.vertical,
'rounded-0': t.vertical,
small: t.small
},
t.navClass
],
attrs: { role: 'tablist', tabindex: '0', id: t.safeId('_BV_tab_controls_') },
on: { keydown: t.onKeynav }
},
[ buttons, t.$slots.tabs ]
)
navs = h(
'div',
{
class: [
{
'card-header': (t.card && !t.vertical && !(t.end || t.bottom)),
'card-footer': (t.card && !t.vertical && (t.end || t.bottom)),
'col-auto': t.vertical
},
t.navWrapperClass
]
},
[ navs ]
)
let empty
if (tabs && tabs.length) {
empty = h(false)
} else {
empty = h(
'div',
{ class: [ 'tab-pane', 'active', { 'card-body': t.card } ] },
t.$slots.empty
)
}
// Main content section
const content = h(
'div',
{
ref: 'tabsContainer',
class: [ 'tab-content', { 'card-body': t.card } ],
class: [ 'tab-content', { 'col': t.vertical }, t.contentClass ],
attrs: { id: t.safeId('_BV_tab_container_') }
},
[ t.$slots.default, (tabs && tabs.length) ? h(false) : t.$slots.empty ]
[ t.$slots.default, empty ]
)
// Render final output
return h(
t.tag,
{
class: [ 'tabs' ],
class: [ 'tabs', { 'row': t.vertical, 'no-gutters': (t.vertical && t.card) } ],
attrs: { id: t.safeId() }
},
[
t.bottom ? content : h(false),
h('div', { class: { 'card-header': t.card } }, [ navs ]),
t.bottom ? h(false) : content
(t.end || t.bottom) ? content : h(false),
[ navs ],
(t.end || t.bottom) ? h(false) : content
]
)
},
@@ -151,10 +189,19 @@ export default {
type: Boolean,
default: false
},
vertical: {
type: Boolean,
default: false
},
bottom: {
type: Boolean,
default: false
},
end: {
// Synonym for 'bottom'
type: Boolean,
default: false
},
noFade: {
type: Boolean,
default: false
@@ -163,6 +210,18 @@ export default {
// This prop is sniffed by the tab child
type: Boolean,
default: false
},
contentClass: {
type: [String, Array, Object],
default: null
},
navClass: {
type: [String, Array, Object],
default: null
},
navWrapperClass: {
type: [String, Array, Object],
default: null
}
},
watch: {

0 comments on commit 51d0e03

Please sign in to comment.