Skip to content

Commit 62c6105

Browse files
feat(b-nav-item-dropdown): improve default handling of dropdown toggle link (closes #3942) (#5344)
* fix(b-nav-item-dropdown): let `<b-link>` handle `href` default * Update nav-item-dropdown.js * Update nav-item-dropdown.spec.js * Update nav-item-dropdown.js * Update nav-item-dropdown.js * Update id.js * Update nav-item-dropdown.js * Update nav-item-dropdown.spec.js * Update nav-item-dropdown.spec.js * Update README.md * Update nav-item-dropdown.spec.js * Update nav-item-dropdown.js * Update nav-item-dropdown.spec.js * Update nav-item-dropdown.spec.js * Update nav-item-dropdown.spec.js * Update README.md * Update nav-item-dropdown.js * Update nav-item-dropdown.spec.js Co-authored-by: Troy Morehouse <troymore@nbnet.nb.ca>
1 parent 70c00e8 commit 62c6105

File tree

4 files changed

+292
-53
lines changed

4 files changed

+292
-53
lines changed

src/components/nav/README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,13 @@ Use `<b-nav-item-dropdown>` to place dropdown items within your nav.
190190

191191
Sometimes you want to add your own class names to the generated dropdown toggle button, that by
192192
default have the classes `nav-link` and `dropdown-toggle`. Use the `toggle-class` prop to add them
193-
(like above) which will produce something like:
193+
(like above) which will render HTML similar to:
194194

195195
```html
196196
<li id="my-nav-dropdown" class="nav-item b-nav-dropdown dropdown">
197197
<a
198-
href="#"
198+
role="button"
199+
href="#my-nav-dropdown"
199200
id="my-nav-dropdown__BV_button_"
200201
aria-haspopup="true"
201202
aria-expanded="false"
@@ -222,6 +223,17 @@ shown. When there are a large number of dropdowns rendered on the same page, per
222223
impacted due to larger overall memory utilization. You can instruct `<b-nav-item-dropdown>` to
223224
render the menu contents only when it is shown by setting the `lazy` prop to true.
224225

226+
### Dropdown implementation note
227+
228+
Note that the toggle button is actually rendered as a link `<a>` tag with `role="button"` for
229+
styling purposes, and typically has the `href` set to `#` unless an ID is provided via the `id`
230+
prop.
231+
232+
The toggle will prevent scroll-top-top behaviour (via JavaScript) when clicking the toggle link. In
233+
some cases when using SSR, and the user clicks the toggle button _before_ Vue has had a chance to
234+
hydrate the component, the page will scroll to top. In these cases, simply providing a unique ID via
235+
the `id` prop will prevent the unwanted scroll-to-top behaviour.
236+
225237
## Nav text content
226238

227239
Use the `<b-nav-text>` child component to place plain text content into the nav:
Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
import Vue from '../../utils/vue'
2-
import { props as BDropdownProps } from '../dropdown/dropdown'
3-
import idMixin from '../../mixins/id'
4-
import dropdownMixin from '../../mixins/dropdown'
5-
import normalizeSlotMixin from '../../mixins/normalize-slot'
62
import pluckProps from '../../utils/pluck-props'
73
import { htmlOrText } from '../../utils/html'
4+
import dropdownMixin from '../../mixins/dropdown'
5+
import idMixin from '../../mixins/id'
6+
import normalizeSlotMixin from '../../mixins/normalize-slot'
7+
import { props as BDropdownProps } from '../dropdown/dropdown'
88
import { BLink } from '../link/link'
99

10-
// -- Constants --
11-
10+
// --- Props ---
1211
export const props = pluckProps(
1312
['text', 'html', 'menuClass', 'toggleClass', 'noCaret', 'role', 'lazy'],
1413
BDropdownProps
1514
)
1615

16+
// --- Main component ---
1717
// @vue/component
1818
export const BNavItemDropdown = /*#__PURE__*/ Vue.extend({
1919
name: 'BNavItemDropdown',
2020
mixins: [idMixin, dropdownMixin, normalizeSlotMixin],
2121
props,
2222
computed: {
23+
toggleId() {
24+
return this.safeId('_BV_toggle_')
25+
},
2326
isNav() {
2427
// Signal to dropdown mixin that we are in a navbar
2528
return true
@@ -41,57 +44,62 @@ export const BNavItemDropdown = /*#__PURE__*/ Vue.extend({
4144
}
4245
},
4346
render(h) {
44-
const button = h(
47+
const { toggleId, visible } = this
48+
49+
const $toggle = h(
4550
BLink,
4651
{
47-
ref: 'toggle',
4852
staticClass: 'nav-link dropdown-toggle',
4953
class: this.toggleClasses,
5054
props: {
51-
href: '#',
55+
href: `#${this.id || ''}`,
5256
disabled: this.disabled
5357
},
5458
attrs: {
55-
id: this.safeId('_BV_button_'),
59+
id: toggleId,
60+
role: 'button',
5661
'aria-haspopup': 'true',
57-
'aria-expanded': this.visible ? 'true' : 'false'
62+
'aria-expanded': visible ? 'true' : 'false'
5863
},
5964
on: {
6065
mousedown: this.onMousedown,
6166
click: this.toggle,
6267
keydown: this.toggle // Handle ENTER, SPACE and DOWN
63-
}
68+
},
69+
ref: 'toggle'
6470
},
6571
[
66-
this.$slots['button-content'] ||
67-
this.$slots.text ||
72+
// TODO: The `text` slot is deprecated in favor of the `button-content` slot
73+
this.normalizeSlot(['button-content', 'text']) ||
6874
h('span', { domProps: htmlOrText(this.html, this.text) })
6975
]
7076
)
71-
const menu = h(
77+
78+
const $menu = h(
7279
'ul',
7380
{
7481
staticClass: 'dropdown-menu',
7582
class: this.menuClasses,
76-
ref: 'menu',
7783
attrs: {
7884
tabindex: '-1',
79-
'aria-labelledby': this.safeId('_BV_button_')
85+
'aria-labelledby': toggleId
8086
},
8187
on: {
8288
keydown: this.onKeydown // Handle UP, DOWN and ESC
83-
}
89+
},
90+
ref: 'menu'
8491
},
85-
!this.lazy || this.visible ? this.normalizeSlot('default', { hide: this.hide }) : [h()]
92+
!this.lazy || visible ? this.normalizeSlot('default', { hide: this.hide }) : [h()]
8693
)
94+
8795
return h(
8896
'li',
8997
{
9098
staticClass: 'nav-item b-nav-dropdown dropdown',
9199
class: this.dropdownClasses,
92100
attrs: { id: this.safeId() }
93101
},
94-
[button, menu]
102+
[$toggle, $menu]
95103
)
96104
}
97105
})

0 commit comments

Comments
 (0)