Skip to content

Commit 804e8b2

Browse files
committed
feat(BAlert): slot dismissible overwrites close
refactor(BModal): slot header-close is either or on close icon refactor(BOffcanvas): button with static class btn-close is refactored to use BCloseButton refactor(BFormTag): button with static class btn-close is refactored to use BCloseButton feat(BCloseButton): add in optional prop type to override type=button default test(close-button): include test for optional prop type test(offcanvas): refactor to include BCloseButton changes test(container): include enableAutoUnmount test(alert): refactor to include BCloseButtonChanges chore(_button.scss): remove unused class after close button changes
1 parent 49de7e5 commit 804e8b2

File tree

10 files changed

+158
-83
lines changed

10 files changed

+158
-83
lines changed

packages/bootstrap-vue-3/src/components/BAlert.vue

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
<template>
22
<div v-if="isAlertVisible" ref="element" class="alert" role="alert" :class="classes">
33
<slot />
4-
<button
5-
v-if="dismissibleBoolean"
6-
type="button"
7-
class="btn-close"
8-
data-bs-dismiss="alert"
9-
:aria-label="dismissLabel"
10-
@click="dismissClicked"
11-
/>
4+
<template v-if="dismissibleBoolean">
5+
<button
6+
v-if="$slots.dismissible"
7+
type="button"
8+
data-bs-dismiss="alert"
9+
@click="dismissClicked"
10+
>
11+
<slot name="dismissible" />
12+
</button>
13+
<b-close-button
14+
v-else
15+
:aria-label="dismissLabel"
16+
data-bs-dismiss="alert"
17+
@click="dismissClicked"
18+
/>
19+
</template>
1220
</div>
1321
</template>
1422

@@ -19,6 +27,7 @@ import {computed, onBeforeUnmount, ref, toRef, watch} from 'vue'
1927
import {Alert} from 'bootstrap'
2028
import {toInteger} from '../utils'
2129
import {useBooleanish} from '../composables'
30+
import BCloseButton from './BButton/BCloseButton.vue'
2231
2332
interface BAlertProps {
2433
dismissLabel?: string

packages/bootstrap-vue-3/src/components/BButton/BCloseButton.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<button
3-
type="button"
3+
:type="type"
44
class="btn-close"
55
:disabled="disabledBoolean"
66
:class="classes"
@@ -11,19 +11,21 @@
1111
<script setup lang="ts">
1212
// import type {BCloseButtonProps} from '../../types/components'
1313
import {computed, toRef} from 'vue'
14-
import type {Booleanish} from '../../types'
14+
import type {Booleanish, ButtonType} from '../../types'
1515
import {useBooleanish} from '../../composables'
1616
1717
interface BCloseButtonProps {
1818
disabled?: Booleanish
1919
white?: Booleanish
2020
ariaLabel?: string
21+
type?: ButtonType
2122
}
2223
2324
const props = withDefaults(defineProps<BCloseButtonProps>(), {
2425
ariaLabel: 'Close',
2526
disabled: false,
2627
white: false,
28+
type: 'button',
2729
})
2830
2931
const disabledBoolean = useBooleanish(toRef(props, 'disabled'))

packages/bootstrap-vue-3/src/components/BButton/_button.scss

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
.btn-close.btn-close-content {
2-
background: transparent;
3-
padding: 0px;
4-
}
5-
61
.btn {
72
position: relative;
83

packages/bootstrap-vue-3/src/components/BButton/close-button.spec.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,18 @@ describe('close-button', () => {
1010
expect(wrapper.classes()).toContain('btn-close')
1111
})
1212

13-
it('has static attr type as button', () => {
13+
it('has attr type to be button by default', () => {
1414
const wrapper = mount(BCloseButton)
1515
expect(wrapper.attributes('type')).toBe('button')
1616
})
1717

18+
it('has attr type to be prop type', () => {
19+
const wrapper = mount(BCloseButton, {
20+
props: {type: 'submit'},
21+
})
22+
expect(wrapper.attributes('type')).toBe('submit')
23+
})
24+
1825
it('has attr aria-label when prop ariaLabel', async () => {
1926
const wrapper = mount(BCloseButton, {
2027
props: {ariaLabel: 'foobar'},

packages/bootstrap-vue-3/src/components/BFormTags/BFormTag.vue

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,15 @@
1010
<span :id="taglabelId" class="b-form-tag-content flex-grow-1 text-truncate">
1111
<slot>{{ tagText }}</slot>
1212
</span>
13-
<button
13+
<b-close-button
1414
v-if="!disabledBoolean && !noRemoveBoolean"
1515
aria-keyshortcuts="Delete"
1616
type="button"
1717
:aria-label="removeLabel"
18-
class="btn-close b-form-tag-remove"
19-
:class="{
20-
'btn-close-white': !['warning', 'info', 'light'].includes(variant),
21-
}"
22-
:aria-controls="id"
18+
class="b-form-tag-remove"
19+
:white="!['warning', 'info', 'light'].includes(variant)"
2320
:aria-describedby="taglabelId"
21+
:aria-controls="id"
2422
@click="emit('remove', tagText)"
2523
/>
2624
</component>
@@ -31,6 +29,7 @@
3129
import {computed, toRef, useSlots, VNodeNormalizedChildren} from 'vue'
3230
import {useBooleanish, useId} from '../../composables'
3331
import type {Booleanish, ColorVariant} from '../../types'
32+
import BCloseButton from '../BButton/BCloseButton.vue'
3433
3534
interface BFormTagProps {
3635
id?: string

packages/bootstrap-vue-3/src/components/BModal.vue

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,24 @@
1717
{{ title }}
1818
</slot>
1919
</component>
20-
<button
21-
v-if="!hideHeaderCloseBoolean"
22-
type="button"
23-
class="btn-close"
24-
:class="computedCloseButtonClasses"
25-
data-bs-dismiss="modal"
26-
:aria-label="headerCloseLabel"
27-
@click="hide()"
28-
>
29-
<slot name="header-close" />
30-
</button>
20+
<template v-if="!hideHeaderCloseBoolean">
21+
<button
22+
v-if="$slots['header-close']"
23+
type="button"
24+
data-bs-dismiss="modal"
25+
@click="hide()"
26+
>
27+
<slot name="header-close" />
28+
</button>
29+
<b-close-button
30+
v-else
31+
type="button"
32+
:aria-label="headerCloseLabel"
33+
data-bs-dismiss="modal"
34+
:white="headerCloseWhiteBoolean"
35+
@click="hide()"
36+
/>
37+
</template>
3138
</div>
3239
<div class="modal-body" :class="computedBodyClasses">
3340
<slot />
@@ -245,15 +252,6 @@ const computedTitleClasses = computed(() => [
245252
props.titleClass,
246253
])
247254
248-
const hasHeaderCloseSlot = computed<boolean>(() => !!slots['header-close'])
249-
const computedCloseButtonClasses = computed(() => [
250-
{
251-
[`btn-close-content`]: hasHeaderCloseSlot.value,
252-
[`d-flex`]: hasHeaderCloseSlot.value,
253-
[`btn-close-white`]: !hasHeaderCloseSlot.value && headerCloseWhiteBoolean.value,
254-
},
255-
])
256-
257255
const disableCancel = computed<boolean>(() => cancelDisabledBoolean.value || busyBoolean.value)
258256
const disableOk = computed<boolean>(() => okDisabledBoolean.value || busyBoolean.value)
259257

packages/bootstrap-vue-3/src/components/BOffcanvas.vue

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,12 @@
1414
{{ title }}
1515
</slot>
1616
</h5>
17-
<button
17+
<b-close-button
1818
type="button"
19-
class="btn-close text-reset"
19+
class="text-reset"
2020
data-bs-dismiss="offcanvas"
2121
:aria-label="dismissLabel"
2222
/>
23-
<!-- TODO in v0.2.10 this was fixed to include a dynamic aria-label -->
24-
<!-- My note still persists, that perhaps we should include native multilanguage support similar to Vuetify -->
25-
<!-- Regardless, if native multilanguage support is included or not, -->
26-
<!-- It will need to be reviewed through and ensure that any aria-{type} can be modified by a user -->
27-
<!-- of course, ignoring true static aria tags like the above aria-labelledby -->
2823
</div>
2924
<div class="offcanvas-body">
3025
<slot />
@@ -38,6 +33,7 @@ import {computed, onMounted, ref, toRef, watch} from 'vue'
3833
import {Offcanvas} from 'bootstrap'
3934
import {useBooleanish, useEventListener} from '../composables'
4035
import type {Booleanish} from '../types'
36+
import BCloseButton from './BButton/BCloseButton.vue'
4137
4238
interface BOffcanvasProps {
4339
dismissLabel?: string

packages/bootstrap-vue-3/src/components/alert.spec.ts

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {enableAutoUnmount, mount} from '@vue/test-utils'
22
import {afterEach, describe, expect, it} from 'vitest'
33
import BAlert from './BAlert.vue'
4+
import BCloseButton from './BButton/BCloseButton.vue'
45

56
describe('alert', () => {
67
enableAutoUnmount(afterEach)
@@ -95,65 +96,127 @@ describe('alert', () => {
9596
expect(wrapper.text()).toBe('foobar')
9697
})
9798

98-
it('has button child if prop dismissible', () => {
99+
it('does not have BCloseButton when prop dismissible is false', () => {
99100
const wrapper = mount(BAlert, {
100-
props: {modelValue: true, dismissible: true},
101+
props: {dismissible: false},
101102
})
102-
const $button = wrapper.find('button')
103-
expect($button.exists()).toBe(true)
103+
const $closebutton = wrapper.findComponent(BCloseButton)
104+
expect($closebutton.exists()).toBe(false)
104105
})
105106

106-
it('does not have button child if prop dismissible false', () => {
107+
it('button is BCloseButton when not slot dismissible', () => {
107108
const wrapper = mount(BAlert, {
108-
props: {modelValue: true, dismissible: false},
109+
props: {dismissible: true, modelValue: true},
109110
})
110-
const $button = wrapper.find('button')
111-
expect($button.exists()).toBe(false)
111+
const $closebutton = wrapper.findComponent(BCloseButton)
112+
expect($closebutton.exists()).toBe(true)
112113
})
113114

114-
it('button child has static attribute type button', () => {
115+
it('BCloseButton is given prop ariaLabel to be dismissLabel', () => {
116+
const wrapper = mount(BAlert, {
117+
props: {dismissible: true, modelValue: true, dismissLabel: 'foobar'},
118+
})
119+
const $closebutton = wrapper.getComponent(BCloseButton)
120+
expect($closebutton.props('ariaLabel')).toBe('foobar')
121+
})
122+
123+
it('BCloseButton has attr data-bs-dismiss to be alert', () => {
124+
const wrapper = mount(BAlert, {
125+
props: {dismissible: true, modelValue: true},
126+
})
127+
const $closebutton = wrapper.getComponent(BCloseButton)
128+
expect($closebutton.attributes('data-bs-dismiss')).toBe('alert')
129+
})
130+
131+
it('button child on click emits update:modelValue', async () => {
115132
const wrapper = mount(BAlert, {
116133
props: {modelValue: true, dismissible: true},
117134
})
118135
const $button = wrapper.get('button')
119-
expect($button.attributes('type')).toBe('button')
136+
await $button.trigger('click')
137+
expect(wrapper.emitted()).toHaveProperty('update:modelValue')
120138
})
121139

122-
it('button child has static class btn-close', () => {
140+
it('button child on click emits dismissed', async () => {
123141
const wrapper = mount(BAlert, {
124142
props: {modelValue: true, dismissible: true},
125143
})
126144
const $button = wrapper.get('button')
127-
expect($button.classes()).toContain('btn-close')
145+
await $button.trigger('click')
146+
expect(wrapper.emitted()).toHaveProperty('dismissed')
128147
})
129148

130-
it('button child has static attribute data-bs-dismiss alert', () => {
149+
it('button child on click emits update:modelValue and gives value false if prop modelValue boolean', async () => {
131150
const wrapper = mount(BAlert, {
132151
props: {modelValue: true, dismissible: true},
133152
})
134153
const $button = wrapper.get('button')
135-
expect($button.attributes('data-bs-dismiss')).toBe('alert')
154+
await $button.trigger('click')
155+
expect(wrapper.emitted('update:modelValue')).toHaveLength(1)
156+
const [event] = wrapper.emitted('update:modelValue') ?? []
157+
expect(event).toEqual([false])
136158
})
137159

138-
it('button child has aria-label Close by default', () => {
160+
it('button child on click emits update:modelValue and gives value 0 if prop modelValue number', async () => {
161+
const wrapper = mount(BAlert, {
162+
props: {modelValue: 1000, dismissible: true},
163+
})
164+
const $button = wrapper.get('button')
165+
await $button.trigger('click')
166+
expect(wrapper.emitted('update:modelValue')).toHaveLength(1)
167+
const [event] = wrapper.emitted('update:modelValue') ?? []
168+
expect(event).toEqual([0])
169+
})
170+
171+
it('does not have BCloseButton when slot dismissible', () => {
172+
const wrapper = mount(BAlert, {
173+
props: {dismissible: true, modelValue: true},
174+
slots: {dismissible: 'foobar'},
175+
})
176+
const $closebutton = wrapper.findComponent(BCloseButton)
177+
expect($closebutton.exists()).toBe(false)
178+
})
179+
180+
it('has button child if prop dismissible and slot dismissible', () => {
181+
const wrapper = mount(BAlert, {
182+
props: {modelValue: true, dismissible: true},
183+
slots: {dismissible: 'foobar'},
184+
})
185+
const $button = wrapper.find('button')
186+
expect($button.exists()).toBe(true)
187+
})
188+
189+
it('does not have button child if prop dismissible false', () => {
190+
const wrapper = mount(BAlert, {
191+
props: {modelValue: true, dismissible: false},
192+
slots: {dismissible: 'foobar'},
193+
})
194+
const $button = wrapper.find('button')
195+
expect($button.exists()).toBe(false)
196+
})
197+
198+
it('button child has static attribute type button', () => {
139199
const wrapper = mount(BAlert, {
140200
props: {modelValue: true, dismissible: true},
201+
slots: {dismissible: 'foobar'},
141202
})
142203
const $button = wrapper.get('button')
143-
expect($button.attributes('aria-label')).toBe('Close')
204+
expect($button.attributes('type')).toBe('button')
144205
})
145206

146-
it('button child has aria-label prop dismissLabel', () => {
207+
it('button child has static attribute data-bs-dismiss alert', () => {
147208
const wrapper = mount(BAlert, {
148-
props: {modelValue: true, dismissible: true, dismissLabel: 'foobar'},
209+
props: {modelValue: true, dismissible: true},
210+
slots: {dismissible: 'foobar'},
149211
})
150212
const $button = wrapper.get('button')
151-
expect($button.attributes('aria-label')).toBe('foobar')
213+
expect($button.attributes('data-bs-dismiss')).toBe('alert')
152214
})
153215

154216
it('button child on click emits update:modelValue', async () => {
155217
const wrapper = mount(BAlert, {
156218
props: {modelValue: true, dismissible: true},
219+
slots: {dismissible: 'foobar'},
157220
})
158221
const $button = wrapper.get('button')
159222
await $button.trigger('click')
@@ -163,6 +226,7 @@ describe('alert', () => {
163226
it('button child on click emits dismissed', async () => {
164227
const wrapper = mount(BAlert, {
165228
props: {modelValue: true, dismissible: true},
229+
slots: {dismissible: 'foobar'},
166230
})
167231
const $button = wrapper.get('button')
168232
await $button.trigger('click')
@@ -172,6 +236,7 @@ describe('alert', () => {
172236
it('button child on click emits update:modelValue and gives value false if prop modelValue boolean', async () => {
173237
const wrapper = mount(BAlert, {
174238
props: {modelValue: true, dismissible: true},
239+
slots: {dismissible: 'foobar'},
175240
})
176241
const $button = wrapper.get('button')
177242
await $button.trigger('click')
@@ -183,6 +248,7 @@ describe('alert', () => {
183248
it('button child on click emits update:modelValue and gives value 0 if prop modelValue number', async () => {
184249
const wrapper = mount(BAlert, {
185250
props: {modelValue: 1000, dismissible: true},
251+
slots: {dismissible: 'foobar'},
186252
})
187253
const $button = wrapper.get('button')
188254
await $button.trigger('click')

packages/bootstrap-vue-3/src/components/container.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {afterEach, describe, expect, it} from 'vitest'
33
import BContainer from './BContainer.vue'
44

55
describe('container', () => {
6+
enableAutoUnmount(afterEach)
7+
68
it('renders default slot', () => {
79
const wrapper = mount(BContainer, {
810
slots: {default: 'foobar'},

0 commit comments

Comments
 (0)