8000 feat(b-sidebar): add `header` slot · bootstrap-vue/bootstrap-vue@7985dcb · GitHub
[go: up one dir, main page]

Skip to content

Commit 7985dcb

Browse files
committed
feat(b-sidebar): add header slot
1 parent f00f71b commit 7985dcb

File tree

5 files changed

+86
-17
lines changed

5 files changed

+86
-17
lines changed

src/components/sidebar/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ You can apply arbitrary classes to the body section via the `body-class` prop.
185185
By default, `<b-sidebar>` has a header with optional title and a close button. You can supply a
186186
title via the `title` prop, or via the optionally scoped slot `title`.
187187

188+
If you want to provide a completely custom header, you can use the optionally scoped `header` slot.
189+
188190
You can apply arbitrary classes to the header section via the `header-class` prop, to override the
189191
default padding, etc.
190192

src/components/sidebar/package.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,28 @@
175175
}
176176
]
177177
},
178+
{
179+
"name": "header",
180+
"version": "2.21.0",
181+
"description": "Content to place in the header",
182+
"scope": [
183+
{
184+
"prop": "hide",
185+
"type": "Function",
186+
"description": "When called, will close the sidebar"
187+
},
188+
{
189+
"prop": "right",
190+
"type": "Boolean",
191+
"description": "`true` if the sidebar is on the right"
192+
},
193+
{
194+
"prop": "visible",
195+
"type": "Boolean",
196+
"description": "`true` if the sidebar is open"
197+
}
198+
]
199+
},
178200
{
179201
"name": "header-close",
180202
"description": "Content of the header close button. Defaults to `<b-icon-x>`"

src/components/sidebar/sidebar.js

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import {
1414
SLOT_NAME_DEFAULT,
1515
SLOT_NAME_FOOTER,
16+
SLOT_NAME_HEADER,
1617
SLOT_NAME_HEADER_CLOSE,
1718
SLOT_NAME_TITLE
1819
} from '../../constants/slots'
@@ -21,7 +22,6 @@ import { getRootActionEventName, getRootEventName } from '../../utils/events'
2122
import { makeModelMixin } from '../../utils/model'
2223
import { sortKeys } from '../../utils/object'
2324
import { makeProp, makePropsConfigurable } from '../../utils/props'
24-
import { toString } from '../../utils/string'
2525
import { attrsMixin } from '../../mixins/attrs'
2626
import { idMixin, props as idProps } from '../../mixins/id'
2727
import { listenOnRootMixin } from '../../mixins/listen-on-root'
@@ -92,7 +92,7 @@ export const props = makePropsConfigurable(
9292

9393
const renderHeaderTitle = (h, ctx) => {
9494
// Render a empty `<span>` when to title was provided
95-
const title = ctx.computedTile
95+
const title = ctx.normalizeSlot(SLOT_NAME_TITLE, ctx.slotScope) || ctx.title
9696
if (!title) {
9797
return h('span')
9898
}
@@ -123,8 +123,12 @@ const renderHeader = (h, ctx) => {
123123
return h()
124124
}
125125

126-
const $title = renderHeaderTitle(h, ctx)
127-
const $close = renderHeaderClose(h, ctx)
126+
let $content = ctx.normalizeSlot(SLOT_NAME_HEADER, ctx.slotScope)
127+
if (!$content) {
128+
const $title = renderHeaderTitle(h, ctx)
129+
const $close = renderHeaderClose(h, ctx)
130+
$content = ctx.right ? [$close, $title] : [$title, $close]
131+
}
128132

129133
return h(
130134
'header',
@@ -133,7 +137,7 @@ const renderHeader = (h, ctx) => {
133137
class: ctx.headerClass,
134138
key: 'header'
135139
},
136-
ctx.right ? [$close, $title] : [$title, $close]
140+
$content
137141
)
138142
}
139143

@@ -227,11 +231,16 @@ export const BSidebar = /*#__PURE__*/ Vue.extend({
227231
const { hide, right, localShow: visible } = this
228232
return { hide, right, visible }
229233
},
230-
computedTile() {
231-
return this.normalizeSlot(SLOT_NAME_TITLE, this.slotScope) || toString(this.title) || null
234+
hasTitle() {
235+
const { $scopedSlots, $slots } = this
236+
return (
237+
!this.noHeader &&
238+
!this.hasNormalizedSlot(SLOT_NAME_HEADER) &&
239+
!!(this.normalizeSlot(SLOT_NAME_TITLE, this.slotScope, $scopedSlots, $slots) || this.title)
240+
)
232241
},
233242
titleId() {
234-
return this.computedTile ? this.safeId('__title__') : null
243+
return this.hasTitle ? this.safeId('__title__') : null
235244
},
236245
computedAttrs() {
237246
return {

src/components/sidebar/sidebar.spec.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,34 @@ describe('sidebar', () => {
325325
wrapper.destroy()
326326
})
327327

328+
it('should have expected structure when `header` slot provided', async () => {
329+
const wrapper = mount(BSidebar, {
330+
attachTo: createContainer(),
331+
propsData: {
332+
id: 'sidebar-header-slot',
333+
visible: true,
334+
title: 'TITLE'
335+
},
336+
slots: {
337+
header: 'Custom header'
338+
}
339+
})
340+
341+
expect(wrapper.vm).toBeDefined()
342+
expect(wrapper.element.tagName).toBe('DIV')
343+
344+
const $header = wrapper.find('.b-sidebar-header')
345+
expect($header.exists()).toBe(true)
346+
expect($header.find('strong').exists()).toBe(false)
347+
expect($header.find('button').exists()).toBe(false)
348+
expect($header.text()).toContain('Custom header')
349+
expect($header.text()).not.toContain('TITLE')
350+
351+
expect(wrapper.find('.b-sidebar-footer').exists()).toBe(false)
352+
353+
wrapper.destroy()
354+
})
355+
328356
it('should have expected structure when `footer` slot provided', async () => {
329357
const wrapper = mount(BSidebar, {
330358
attachTo: createContainer(),

src/mixins/normalize-slot.js

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
66
// @vue/component
77
export const normalizeSlotMixin = Vue.extend({
88
methods: {
9-
hasNormalizedSlot(name = SLOT_NAME_DEFAULT) {
10-
// Returns true if the either a $scopedSlot or $slot exists with the specified name
11-
// `name` can be a string name or an array of names
12-
return hasNormalizedSlot(name, this.$scopedSlots, this.$slots)
9+
// Returns `true` if the either a `$scopedSlot` or `$slot` exists with the specified name
10+
// `name` can be a string name or an array of names
11+
hasNormalizedSlot(
12+
name = SLOT_NAME_DEFAULT,
13+
scopedSlots = this.$scopedSlots,
14+
slots = this.$slots
15+
) {
16+
return hasNormalizedSlot(name, scopedSlots, slots)
1317
},
14-
normalizeSlot(name = SLOT_NAME_DEFAULT, scope = {}) {
15-
// Returns an array of rendered VNodes if slot found.
16-
// Returns undefined if not found.
17-
// `name` can be a string name or an array of names
18-
const vNodes = normalizeSlot(name, scope, this.$scopedSlots, this.$slots)
18+
// Returns an array of rendered VNodes if slot found, otherwise `undefined`
19+
// `name` can be a string name or an array of names
20+
normalizeSlot(
21+
name = SLOT_NAME_DEFAULT,
22+
scope = {},
23+
scopedSlots = this.$scopedSlots,
24+
slots = this.$slots
25+
) {
26+
const vNodes = normalizeSlot(name, scope, scopedSlots, slots)
1927
return vNodes ? concat(vNodes) : vNodes
2028
}
2129
}

0 commit comments

Comments
 (0)
0