Skip to content

Commit 1eea87d

Browse files
committed
feat(COffcanvas): add scroll property
1 parent add2d16 commit 1eea87d

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-0
lines changed

docs/4.0/api/COffcanvas.api.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
| **onDismiss** | Method called before the dissmiss animation has started. | `() => void` | - |
77
| **placement** | Components placement, there’s no default placement. | `'start' | 'end' | 'top' | 'bottom'` | - |
88
| **portal** | Generates modal using createPortal. | `boolean` | true |
9+
| **scroll** | Allow body scrolling while offcanvas is open | `boolean` | false |
910
| **visible** | Toggle the visibility of offcanvas component. | `boolean` | false |

docs/4.0/components/offcanvas.mdx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,98 @@ return (
236236
)
237237
```
238238

239+
export const BackdropExample = () => {
240+
const [visibleScrolling, setVisibleScrolling] = useState(false)
241+
const [visibleWithBackdrop, setVisibleWithBackdrop] = useState(false)
242+
const [visibleWithBothOptions, setVisibleWithBothOptions] = useState(false)
243+
return (
244+
<>
245+
<CButton color="primary" onClick={() => setVisibleScrolling(true)}>Enable body scrolling</CButton>
246+
<CButton color="primary" onClick={() => setVisibleWithBackdrop(true)}>Enable backdrop (default)</CButton>
247+
<CButton color="primary" onClick={() => setVisibleWithBothOptions(true)}>Enable both scrolling &amp; backdrop</CButton>
248+
<COffcanvas backdrop={false} placement="start" scroll visible={visibleScrolling} onDismiss={() => setVisibleScrolling(false)}>
249+
<COffcanvasHeader>
250+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
251+
<CCloseButton className="text-reset" onClick={() => setVisibleScrolling(false)}/>
252+
</COffcanvasHeader>
253+
<COffcanvasBody>
254+
<p>Try scrolling the rest of the page to see this option in action.</p>
255+
</COffcanvasBody>
256+
</COffcanvas>
257+
<COffcanvas placement="start" visible={visibleWithBackdrop} onDismiss={() => setVisibleWithBackdrop(false)}>
258+
<COffcanvasHeader>
259+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
260+
<CCloseButton className="text-reset" onClick={() => setVisibleWithBackdrop(false)}/>
261+
</COffcanvasHeader>
262+
<COffcanvasBody>
263+
<p>.....</p>
264+
</COffcanvasBody>
265+
</COffcanvas>
266+
<COffcanvas placement="start" scroll visible={visibleWithBothOptions} onDismiss={() => setVisibleWithBothOptions(false)}>
267+
<COffcanvasHeader>
268+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
269+
<CCloseButton className="text-reset" onClick={() => setVisibleWithBothOptions(false)}/>
270+
</COffcanvasHeader>
271+
<COffcanvasBody>
272+
<p>Try scrolling the rest of the page to see this option in action.</p>
273+
</COffcanvasBody>
274+
</COffcanvas>
275+
</>
276+
)
277+
}
278+
279+
## Backdrop
280+
281+
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.
282+
283+
<Example>
284+
<BackdropExample />
285+
</Example>
286+
287+
```jsx
288+
const [visibleScrolling, setVisibleScrolling] = useState(false)
289+
const [visibleWithBackdrop, setVisibleWithBackdrop] = useState(false)
290+
const [visibleWithBothOptions, setVisibleWithBothOptions] = useState(false)
291+
return (
292+
<>
293+
<CButton color="primary" onClick={() => setVisibleScrolling(true)}>Enable body scrolling</CButton>
294+
<CButton color="primary" onClick={() => setVisibleWithBackdrop(true)}>Enable backdrop (default)</CButton>
295+
<CButton color="primary" onClick={() => setVisibleWithBothOptions(true)}>Enable both scrolling &amp; backdrop</CButton>
296+
<COffcanvas backdrop={false} placement="start" scroll visible={visibleScrolling} onDismiss={() => setVisibleScrolling(false)}>
297+
<COffcanvasHeader>
298+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
299+
<CCloseButton className="text-reset" onClick={() => setVisibleScrolling(false)}/>
300+
</COffcanvasHeader>
301+
<COffcanvasBody>
302+
<p>Try scrolling the rest of the page to see this option in action.</p>
303+
</COffcanvasBody>
304+
</COffcanvas>
305+
<COffcanvas placement="start" visible={visibleWithBackdrop} onDismiss={() => setVisibleWithBackdrop(false)}>
306+
<COffcanvasHeader>
307+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
308+
<CCloseButton className="text-reset" onClick={() => setVisibleWithBackdrop(false)}/>
309+
</COffcanvasHeader>
310+
<COffcanvasBody>
311+
<p>.....</p>
312+
</COffcanvasBody>
313+
</COffcanvas>
314+
<COffcanvas placement="start" scroll visible={visibleWithBothOptions} onDismiss={() => setVisibleWithBothOptions(false)}>
315+
<COffcanvasHeader>
316+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
317+
<CCloseButton className="text-reset" onClick={() => setVisibleWithBothOptions(false)}/>
318+
</COffcanvasHeader>
319+
<COffcanvasBody>
320+
<p>Try scrolling the rest of the page to see this option in action.</p>
321+
</COffcanvasBody>
322+
</COffcanvas>
323+
</>
324+
)
325+
```
326+
327+
## Accessibility
328+
329+
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.
330+
239331
## API
240332

241333
### COffcanvas

src/components/offcanvas/COffcanvas.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ export interface COffcanvasProps extends HTMLAttributes<HTMLDivElement> {
3737
* Generates modal using createPortal. [docs]
3838
*/
3939
portal?: boolean
40+
/**
41+
* Allow body scrolling while offcanvas is open
42+
*
43+
* @default false
44+
*/
45+
scroll?: boolean
4046
/**
4147
* Toggle the visibility of offcanvas component. [docs]
4248
*/
@@ -53,6 +59,7 @@ export const COffcanvas = forwardRef<HTMLDivElement, COffcanvasProps>(
5359
onDismiss,
5460
placement,
5561
portal = true,
62+
scroll = false,
5663
visible = false,
5764
...rest
5865
},
@@ -66,6 +73,21 @@ export const COffcanvas = forwardRef<HTMLDivElement, COffcanvasProps>(
6673
setVisible(visible)
6774
}, [visible])
6875

76+
useEffect(() => {
77+
if (_visible) {
78+
if (!scroll) {
79+
document.body.style.overflow = 'hidden'
80+
document.body.style.paddingRight = '0px'
81+
}
82+
return
83+
}
84+
85+
if (!scroll) {
86+
document.body.style.removeProperty('overflow')
87+
document.body.style.removeProperty('padding-right')
88+
}
89+
}, [_visible])
90+
6991
const _className = classNames(
7092
'offcanvas',
7193
{
@@ -101,6 +123,7 @@ export const COffcanvas = forwardRef<HTMLDivElement, COffcanvasProps>(
101123
<>
102124
<div
103125
className={_className}
126+
role="dialog"
104127
style={{ ...transitionStyles[state] }}
105128
tabIndex={-1}
106129
onKeyDown={handleKeyDown}
@@ -140,6 +163,7 @@ COffcanvas.propTypes = {
140163
placement: PropTypes.oneOf<'start' | 'end' | 'top' | 'bottom'>(['start', 'end', 'top', 'bottom'])
141164
.isRequired,
142165
portal: PropTypes.bool,
166+
scroll: PropTypes.bool,
143167
visible: PropTypes.bool,
144168
}
145169

0 commit comments

Comments
 (0)