Skip to content

Commit 1e51cb6

Browse files
committed
feat(Offcanvas): add responsive variations
1 parent d59ae10 commit 1e51cb6

File tree

2 files changed

+75
-14
lines changed

2 files changed

+75
-14
lines changed

packages/coreui-react/src/components/offcanvas/COffcanvas.tsx

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ export interface COffcanvasProps extends HTMLAttributes<HTMLDivElement> {
3636
* Generates modal using createPortal.
3737
*/
3838
portal?: boolean
39+
/**
40+
* Responsive offcanvas property hide content outside the viewport from a specified breakpoint and down.
41+
*
42+
* @since 4.6.0
43+
*/
44+
responsive?: boolean | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'
3945
/**
4046
* Allow body scrolling while offcanvas is open
4147
*/
@@ -56,7 +62,8 @@ export const COffcanvas = forwardRef<HTMLDivElement, COffcanvasProps>(
5662
onHide,
5763
onShow,
5864
placement,
59-
portal = true,
65+
portal = false,
66+
responsive = true,
6067
scroll = false,
6168
visible = false,
6269
...rest
@@ -86,26 +93,28 @@ export const COffcanvas = forwardRef<HTMLDivElement, COffcanvasProps>(
8693
}
8794
}, [_visible])
8895

96+
const getTransitionClass = (state: string) => {
97+
return state === 'entering'
98+
? 'showing'
99+
: state === 'entered'
100+
? 'show'
101+
: state === 'exiting'
102+
? 'show hiding'
103+
: ''
104+
}
105+
89106
const _className = classNames(
90-
'offcanvas',
91107
{
108+
[`offcanvas${typeof responsive !== 'boolean' ? '-' + responsive : ''}`]: responsive,
92109
[`offcanvas-${placement}`]: placement,
93-
show: _visible,
94110
},
95111
className,
96112
)
97113

98-
const transitionStyles = {
99-
entering: { visibility: 'visible' },
100-
entered: { visibility: 'visible' },
101-
exiting: { visibility: 'visible' },
102-
exited: { visibility: 'hidden' },
103-
}
104-
105114
const handleDismiss = () => {
106115
setVisible(false)
107116
}
108-
117+
109118
const handleBackdropDismiss = () => {
110119
if (backdrop !== 'static') {
111120
setVisible(false)
@@ -125,9 +134,8 @@ export const COffcanvas = forwardRef<HTMLDivElement, COffcanvasProps>(
125134
return (
126135
<>
127136
<div
128-
className={_className}
137+
className={classNames(_className, getTransitionClass(state))}
129138
role="dialog"
130-
style={{ ...transitionStyles[state] }}
131139
tabIndex={-1}
132140
onKeyDown={handleKeyDown}
133141
{...rest}
@@ -187,6 +195,10 @@ COffcanvas.propTypes = {
187195
placement: PropTypes.oneOf<'start' | 'end' | 'top' | 'bottom'>(['start', 'end', 'top', 'bottom'])
188196
.isRequired,
189197
portal: PropTypes.bool,
198+
responsive: PropTypes.oneOfType([
199+
PropTypes.bool,
200+
PropTypes.oneOf<'sm' | 'md' | 'lg' | 'xl' | 'xxl'>(['sm', 'md', 'lg', 'xl', 'xxl']),
201+
]),
190202
scroll: PropTypes.bool,
191203
visible: PropTypes.bool,
192204
}

packages/docs/content/components/offcanvas.mdx

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ other_frameworks: offcanvas
1010
import { useState } from 'react'
1111

1212
import {
13+
CAlert,
1314
CButton,
1415
CCloseButton,
1516
CCallout,
@@ -26,7 +27,7 @@ import {
2627
Below is an offcanvas example that is shown by default (via `visible={true}`). Offcanvas includes support for a header with a close button and an optional body class for some initial `padding`. We suggest that you include offcanvas headers with dismiss actions whenever possible, or provide an explicit dismiss action.
2728

2829
```jsx preview className="docs-example-offcanvas bg-light p-0"
29-
<COffcanvas backdrop={false} placement="start" portal={false} visible={true}>
30+
<COffcanvas backdrop={false} placement="start" visible={true}>
3031
<COffcanvasHeader>
3132
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
3233
<CCloseButton className="text-reset" />
@@ -218,6 +219,54 @@ return (
218219
)
219220
```
220221

222+
## Responsive
223+
224+
Responsive offcanvas properties hide content outside the viewport from a specified breakpoint and down.
225+
Above that breakpoint, the contents within will behave as usual.
226+
For example, `responsive="lg"` hides content in an offcanvas below the lg breakpoint, but shows the content above the lg breakpoint.
227+
228+
export const ResponsiveExample = () => {
229+
const [visible, setVisible] = useState(false)
230+
return (
231+
<>
232+
<CButton className="d-lg-none" onClick={() => setVisible(true)}>Toggle offcanvas</CButton>
233+
<CAlert className="d-none d-lg-block" color="info">Resize your browser to show the responsive offcanvas toggle.</CAlert>
234+
<COffcanvas placement="start" responsive="lg" visible={visible} onHide={() => setVisible(false)}>
235+
<COffcanvasHeader>
236+
<COffcanvasTitle>Responsive offcanvas</COffcanvasTitle>
237+
<CCloseButton className="text-reset" onClick={() => setVisible(false)} />
238+
</COffcanvasHeader>
239+
<COffcanvasBody>
240+
<p>This is content within an <code>.offcanvas-lg</code></p>.
241+
</COffcanvasBody>
242+
</COffcanvas>
243+
</>
244+
)
245+
}
246+
247+
<Example>
248+
<ResponsiveExample />
249+
</Example>
250+
251+
```jsx
252+
const [visible, setVisible] = useState(false)
253+
return (
254+
<>
255+
<CButton className="d-lg-none" onClick={() => setVisible(true)}>Toggle offcanvas</CButton>
256+
<CAlert className="d-none d-lg-block" color="info">Resize your browser to show the responsive offcanvas toggle.</CAlert>
257+
<COffcanvas backdrop="static" placement="start" visible={visible} onHide={() => setVisible(false)}>
258+
<COffcanvasHeader>
259+
<COffcanvasTitle>Responsive offcanvas</COffcanvasTitle>
260+
<CCloseButton className="text-reset" onClick={() => setVisible(false)} />
261+
</COffcanvasHeader>
262+
<COffcanvasBody>
263+
This is content within an <code>.offcanvas-lg</code>.
264+
</COffcanvasBody>
265+
</COffcanvas>
266+
</>
267+
)
268+
```
269+
221270

222271
## Placement
223272

0 commit comments

Comments
 (0)