Skip to content

Commit 9bed47c

Browse files
committed
feat(COffcanvas): add scroll property; automatically add role="dialog
1 parent 7f3c5d5 commit 9bed47c

File tree

3 files changed

+128
-3
lines changed

3 files changed

+128
-3
lines changed

docs/api/offcanvas/COffcanvas.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
| <code>backdrop</code> | Apply a backdrop on body while offcanvas is open. | boolean | - | true |
88
| <code>keyboard</code> | Closes the offcanvas when escape key is pressed. | boolean | - | true |
99
| <code>placement</code> | Components placement, there’s no default placement. | string | `'start'`, `'end'`, `'top'`, `'bottom'` | - |
10+
| <code>scroll</code> | Allow body scrolling while offcanvas is open | boolean | - | false |
1011
| <code>visible</code> | Toggle the visibility of offcanvas component. | boolean | - | |
1112

1213
#### Events

docs/components/offcanvas.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,93 @@ Try the top, right, and bottom examples out below.
209209
}
210210
</script>
211211
```
212+
213+
## Backdrop
214+
215+
Scrolling the `<body>` element is disabled when an offcanvas and its backdrop are visible. Use the `scroll` property to toggle `<body>` scrolling and `backdrop` to toggle the backdrop.
216+
217+
::: demo
218+
<CButton color="primary" @click="() => { visibleScrolling = !visibleScrolling }">Enable body scrolling</CButton>
219+
<CButton color="primary" @click="() => { visibleWithBackdrop = !visibleWithBackdrop }">Enable backdrop (default)</CButton>
220+
<CButton color="primary" @click="() => { visibleWithBothOptions = !visibleWithBothOptions }">Enable both scrolling & backdrop</CButton>
221+
<COffcanvas :backdrop="false" placement="start" scroll :visible="visibleScrolling" @dismiss="() => { visibleScrolling = !visibleScrolling }">
222+
<COffcanvasHeader>
223+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
224+
<CCloseButton class="text-reset" @click="() => { visibleScrolling = false }"/>
225+
</COffcanvasHeader>
226+
<COffcanvasBody>
227+
<p>Try scrolling the rest of the page to see this option in action.</p>
228+
</COffcanvasBody>
229+
</COffcanvas>
230+
<COffcanvas placement="start" :visible="visibleWithBackdrop" @dismiss="() => { visibleWithBackdrop = !visibleWithBackdrop }">
231+
<COffcanvasHeader>
232+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
233+
<CCloseButton class="text-reset" @click="() => { visibleWithBackdrop = false }"/>
234+
</COffcanvasHeader>
235+
<COffcanvasBody>
236+
<p>.....</p>
237+
</COffcanvasBody>
238+
</COffcanvas>
239+
<COffcanvas placement="start" scroll :visible="visibleWithBothOptions" @dismiss="() => { visibleWithBothOptions = !visibleWithBothOptions }">
240+
<COffcanvasHeader>
241+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
242+
<CCloseButton class="text-reset" @click="() => { visibleWithBothOptions = false }"/>
243+
</COffcanvasHeader>
244+
<COffcanvasBody>
245+
<p>Try scrolling the rest of the page to see this option in action.</p>
246+
</COffcanvasBody>
247+
</COffcanvas>
248+
:::
249+
```vue
250+
<template>
251+
<CButton color="primary" @click="() => { visibleScrolling = !visibleScrolling }">Enable body scrolling</CButton>
252+
<CButton color="primary" @click="() => { visibleWithBackdrop = !visibleWithBackdrop }">Enable backdrop (default)</CButton>
253+
<CButton color="primary" @click="() => { visibleWithBothOptions = !visibleWithBothOptions }">Enable both scrolling &amp; backdrop</CButton>
254+
<COffcanvas :backdrop="false" placement="start" scroll :visible="visibleScrolling" @dismiss="() => { visibleScrolling = !visibleScrolling }">
255+
<COffcanvasHeader>
256+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
257+
<CCloseButton class="text-reset" @click="() => { visibleScrolling = false }"/>
258+
</COffcanvasHeader>
259+
<COffcanvasBody>
260+
<p>Try scrolling the rest of the page to see this option in action.</p>
261+
</COffcanvasBody>
262+
</COffcanvas>
263+
<COffcanvas placement="start" :visible="visibleWithBackdrop" @dismiss="() => { visibleWithBackdrop = !visibleWithBackdrop }">
264+
<COffcanvasHeader>
265+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
266+
<CCloseButton class="text-reset" @click="() => { visibleWithBackdrop = false }"/>
267+
</COffcanvasHeader>
268+
<COffcanvasBody>
269+
<p>.....</p>
270+
</COffcanvasBody>
271+
</COffcanvas>
272+
<COffcanvas placement="start" scroll :visible="visibleWithBothOptions" @dismiss="() => { visibleWithBothOptions = !visibleWithBothOptions }">
273+
<COffcanvasHeader>
274+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
275+
<CCloseButton class="text-reset" @click="() => { visibleWithBothOptions = false }"/>
276+
</COffcanvasHeader>
277+
<COffcanvasBody>
278+
<p>Try scrolling the rest of the page to see this option in action.</p>
279+
</COffcanvasBody>
280+
</COffcanvas>
281+
</template>
282+
<script>
283+
export default {
284+
data() {
285+
return {
286+
visibleScrolling: false,
287+
visibleWithBackdrop: false,
288+
visibleWithBothOptions: false,
289+
}
290+
}
291+
}
292+
</script>
293+
```
294+
295+
## Accessibility
296+
297+
Since the offcanvas panel is conceptually a modal dialog, be sure to add `aria-labelledby="..."`—referencing the offcanvas title—to `<COffcanvas>`. Note that you don’t need to add `role="dialog"` since we already add it automatically.
298+
212299
<script>
213300
export default {
214301
data() {
@@ -217,6 +304,9 @@ Try the top, right, and bottom examples out below.
217304
visibleTop: false,
218305
visibleEnd: false,
219306
visibleBottom: false,
307+
visibleScrolling: false,
308+
visibleWithBackdrop: false,
309+
visibleWithBothOptions: false,
220310
}
221311
}
222312
}

src/components/offcanvas/COffcanvas.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineComponent, h, ref, RendererElement, Transition } from 'vue'
1+
import { defineComponent, h, ref, RendererElement, Transition, watch } from 'vue'
22
import { CBackdrop } from '../backdrop'
33

44
const COffcanvas = defineComponent({
@@ -33,6 +33,14 @@ const COffcanvas = defineComponent({
3333
return ['start', 'end', 'top', 'bottom'].includes(value)
3434
},
3535
},
36+
/**
37+
* Allow body scrolling while offcanvas is open
38+
*/
39+
scroll: {
40+
type: Boolean,
41+
default: false,
42+
required: false,
43+
},
3644
/**
3745
* Toggle the visibility of offcanvas component.
3846
*/
@@ -49,6 +57,30 @@ const COffcanvas = defineComponent({
4957
],
5058
setup(props, { slots, emit }) {
5159
const offcanvasRef = ref()
60+
const visible = ref(props.visible)
61+
62+
watch(
63+
() => props.visible,
64+
() => {
65+
visible.value = props.visible
66+
},
67+
)
68+
69+
watch(visible, () => {
70+
if (visible.value) {
71+
if (!props.scroll) {
72+
document.body.style.overflow = 'hidden'
73+
document.body.style.paddingRight = '0px'
74+
}
75+
return
76+
}
77+
78+
if (!props.scroll) {
79+
document.body.style.removeProperty('overflow')
80+
document.body.style.removeProperty('padding-right')
81+
}
82+
})
83+
5284
const handleEnter = (el: RendererElement, done: () => void) => {
5385
el.addEventListener('transitionend', () => {
5486
done()
@@ -75,6 +107,7 @@ const COffcanvas = defineComponent({
75107
}
76108

77109
const handleDismiss = () => {
110+
visible.value = false
78111
emit('dismiss')
79112
}
80113

@@ -101,7 +134,7 @@ const COffcanvas = defineComponent({
101134
onAfterLeave: (el) => handleAfterLeave(el),
102135
},
103136
() =>
104-
props.visible &&
137+
visible.value &&
105138
h(
106139
'div',
107140
{
@@ -112,14 +145,15 @@ const COffcanvas = defineComponent({
112145
},
113146
],
114147
ref: offcanvasRef,
148+
role: 'dialog',
115149
},
116150
slots.default && slots.default(),
117151
),
118152
),
119153
props.backdrop &&
120154
h(CBackdrop, {
121155
class: 'modal-backdrop',
122-
visible: props.visible,
156+
visible: visible.value,
123157
}),
124158
]
125159
},

0 commit comments

Comments
 (0)