Skip to content

Commit aeda058

Browse files
committed
feat(COffcanvas): add new component
1 parent 584cfe9 commit aeda058

File tree

5 files changed

+358
-0
lines changed

5 files changed

+358
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
---
2+
title: React Offcanvas Component
3+
name: Offcanvas
4+
description: React alert component allows build hidden sidebars into your project for navigation, shopping carts.
5+
menu: Components
6+
route: /components/offcanvas
7+
---
8+
9+
import { Playground, Props } from 'docz'
10+
import { useState } from 'react'
11+
import { CCallout } from '../callout/CCallout'
12+
13+
import { CButton } from '../button/CButton'
14+
import { CButtonClose } from '../button/CButtonClose'
15+
import { COffcanvas } from './COffcanvas'
16+
import { COffcanvasBody } from './COffcanvasBody'
17+
import { COffcanvasHeader } from './COffcanvasHeader'
18+
import { COffcanvasTitle } from './COffcanvasTitle'
19+
20+
## Examples
21+
22+
### Offcanvas components
23+
24+
Below is an offcanvas example that is shown by default (via `.show` on `.offcanvas`). 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.
25+
26+
<Playground>
27+
<div className="docs-example-offcanvas bg-light">
28+
<COffcanvas backdrop={false} placement="start" portal={false} visible={true}>
29+
<COffcanvasHeader>
30+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
31+
<CButtonClose className="text-reset" />
32+
</COffcanvasHeader>
33+
<COffcanvasBody>
34+
Content for the offcanvas goes here. You can place just about any Bootstrap component or
35+
custom elements here.
36+
</COffcanvasBody>
37+
</COffcanvas>
38+
</div>
39+
</Playground>
40+
41+
### Live demo
42+
43+
Use the buttons below to show and hide an offcanvas element via JavaScript that toggles the `.show` class on an element with the `.offcanvas` class.
44+
45+
- `.offcanvas` hides content (default)
46+
- `.offcanvas.show` shows content
47+
48+
You can use a link with the `href` attribute, or a button with the `data-coreui-target` attribute. In both cases, the `data-coreui-toggle="offcanvas"` is required.
49+
50+
<Playground>
51+
{() => {
52+
const [visible, setVisible] = useState(false)
53+
return (
54+
<>
55+
<CButton onClick={() => setVisible(true)}>Toggle offcanvas</CButton>
56+
<COffcanvas placement="start" visible={visible} onDismiss={() => setVisible(false)} >
57+
<COffcanvasHeader>
58+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
59+
<CButtonClose className="text-reset" onClick={() => setVisible(false)}/>
60+
</COffcanvasHeader>
61+
<COffcanvasBody>
62+
Content for the offcanvas goes here. You can place just about any Bootstrap component or
63+
custom elements here.
64+
</COffcanvasBody>
65+
</COffcanvas>
66+
</>
67+
)
68+
}}
69+
</Playground>
70+
71+
## Placement
72+
73+
There's no default placement for offcanvas components, so you must add one of the modifier classes below;
74+
75+
- `.offcanvas-start` places offcanvas on the left of the viewport (shown above)
76+
- `.offcanvas-end` places offcanvas on the right of the viewport
77+
- `.offcanvas-top` places offcanvas on the top of the viewport
78+
- `.offcanvas-bottom` places offcanvas on the bottom of the viewport
79+
80+
Try the top, right, and bottom examples out below.
81+
82+
<Playground>
83+
{() => {
84+
const [visible, setVisible] = useState(false)
85+
return (
86+
<>
87+
<CButton onClick={() => setVisible(true)}>Toggle top offcanvas</CButton>
88+
<COffcanvas placement="top" visible={visible} onDismiss={() => setVisible(false)} >
89+
<COffcanvasHeader>
90+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
91+
<CButtonClose className="text-reset" onClick={() => setVisible(false)}/>
92+
</COffcanvasHeader>
93+
<COffcanvasBody>
94+
Content for the offcanvas goes here. You can place just about any Bootstrap component or
95+
custom elements here.
96+
</COffcanvasBody>
97+
</COffcanvas>
98+
</>
99+
)
100+
}}
101+
</Playground>
102+
103+
<Playground>
104+
{() => {
105+
const [visible, setVisible] = useState(false)
106+
return (
107+
<>
108+
<CButton onClick={() => setVisible(true)}>Toggle right offcanvas</CButton>
109+
<COffcanvas placement="end" visible={visible} onDismiss={() => setVisible(false)} >
110+
<COffcanvasHeader>
111+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
112+
<CButtonClose className="text-reset" onClick={() => setVisible(false)}/>
113+
</COffcanvasHeader>
114+
<COffcanvasBody>
115+
Content for the offcanvas goes here. You can place just about any Bootstrap component or
116+
custom elements here.
117+
</COffcanvasBody>
118+
</COffcanvas>
119+
</>
120+
)
121+
}}
122+
</Playground>
123+
124+
<Playground>
125+
{() => {
126+
const [visible, setVisible] = useState(false)
127+
return (
128+
<>
129+
<CButton onClick={() => setVisible(true)}>Toggle bottom offcanvas</CButton>
130+
<COffcanvas placement="bottom" visible={visible} onDismiss={() => setVisible(false)} >
131+
<COffcanvasHeader>
132+
<COffcanvasTitle>Offcanvas</COffcanvasTitle>
133+
<CButtonClose className="text-reset" onClick={() => setVisible(false)}/>
134+
</COffcanvasHeader>
135+
<COffcanvasBody>
136+
Content for the offcanvas goes here. You can place just about any Bootstrap component or
137+
custom elements here.
138+
</COffcanvasBody>
139+
</COffcanvas>
140+
</>
141+
)
142+
}}
143+
</Playground>
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import React, { forwardRef, HTMLAttributes, useCallback, useEffect, useRef, useState } from 'react'
2+
import { createPortal } from 'react-dom'
3+
import { Transition } from 'react-transition-group'
4+
import classNames from 'classnames'
5+
6+
import { useForkedRef } from '../../utils/hooks'
7+
import { CBackdrop } from '../backdrop/CBackdrop'
8+
9+
export interface COffcanvasProps extends HTMLAttributes<HTMLDivElement> {
10+
/**
11+
* Apply a backdrop on body while offcanvas is open. [docs]
12+
* @default true
13+
*/
14+
backdrop?: boolean
15+
/**
16+
* A string of all className you want applied to the base component. [docs]
17+
*/
18+
className?: string
19+
/**
20+
* Closes the offcanvas when escape key is pressed [docs]
21+
* @default true
22+
*/
23+
keyboard?: boolean
24+
/**
25+
* Method called before the dissmiss animation has started. [docs]
26+
*/
27+
onDismiss?: () => void
28+
/**
29+
* Components placement, there’s no default placement. [docs]
30+
* @type 'start' | 'end' | 'top' | 'bottom'
31+
*/
32+
placement: 'start' | 'end' | 'top' | 'bottom'
33+
/**
34+
* Toggle the visibility of offcanvas component. [docs]
35+
*/
36+
portal?: boolean
37+
visible?: boolean
38+
}
39+
40+
export const COffcanvas = forwardRef<HTMLDivElement, COffcanvasProps>(
41+
(
42+
{
43+
children,
44+
backdrop = true,
45+
className,
46+
keyboard = true,
47+
onDismiss,
48+
placement,
49+
portal = true,
50+
visible = false,
51+
...rest
52+
},
53+
ref,
54+
) => {
55+
const [_visible, setVisible] = useState<boolean>(visible)
56+
const offcanvasRef = useRef<HTMLDivElement>(null)
57+
const forkedRef = useForkedRef(ref, offcanvasRef)
58+
59+
useEffect(() => {
60+
setVisible(visible)
61+
}, [visible])
62+
63+
const _className = classNames(
64+
'offcanvas',
65+
{
66+
[`offcanvas-${placement}`]: placement,
67+
show: _visible,
68+
},
69+
className,
70+
)
71+
72+
const transitionStyles = {
73+
entering: { visibility: 'visible' },
74+
entered: { visibility: 'visible' },
75+
exiting: { visibility: 'visible' },
76+
exited: { visibility: 'hidden' },
77+
}
78+
79+
const handleDismiss = () => {
80+
setVisible(false)
81+
return onDismiss && onDismiss()
82+
}
83+
84+
const handleKeyDown = useCallback(
85+
(event) => {
86+
if (event.key === 'Escape' && keyboard) {
87+
return handleDismiss()
88+
}
89+
},
90+
[ref, handleDismiss],
91+
)
92+
93+
const offcanvas = (ref: React.Ref<HTMLDivElement>, state: string) => {
94+
return (
95+
<>
96+
<div
97+
className={_className}
98+
style={{ ...transitionStyles[state] }}
99+
tabIndex={-1}
100+
onKeyDown={handleKeyDown}
101+
{...rest}
102+
ref={ref}
103+
>
104+
{children}
105+
</div>
106+
{backdrop && <CBackdrop visible={_visible} onClick={handleDismiss} />}
107+
</>
108+
)
109+
}
110+
111+
return (
112+
<Transition in={_visible} timeout={300} onEntered={() => offcanvasRef.current?.focus()}>
113+
{(state) => {
114+
return typeof window !== 'undefined' && portal
115+
? createPortal(offcanvas(forkedRef, state), document.body)
116+
: offcanvas(forkedRef, state)
117+
}}
118+
</Transition>
119+
)
120+
},
121+
)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, { forwardRef, HTMLAttributes } from 'react'
2+
import PropTypes from 'prop-types'
3+
import classNames from 'classnames'
4+
5+
export interface COffcanvasBodyProps extends HTMLAttributes<HTMLDivElement> {
6+
/**
7+
* A string of all className you want applied to the base component. [docs]
8+
*/
9+
className?: string
10+
}
11+
12+
export const COffcanvasBody = forwardRef<HTMLDivElement, COffcanvasBodyProps>(
13+
({ children, className, ...rest }, ref) => {
14+
const _className = classNames('offcanvas-body', className)
15+
16+
return (
17+
<div className={_className} {...rest} ref={ref}>
18+
{children}
19+
</div>
20+
)
21+
},
22+
)
23+
24+
COffcanvasBody.propTypes = {
25+
children: PropTypes.node,
26+
className: PropTypes.string,
27+
}
28+
29+
COffcanvasBody.displayName = 'COffcanvasBody'
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, { ElementType, forwardRef, HTMLAttributes } from 'react'
2+
import PropTypes from 'prop-types'
3+
import classNames from 'classnames'
4+
5+
export interface COffcanvasHeaderProps extends HTMLAttributes<HTMLDivElement> {
6+
/**
7+
* A string of all className you want applied to the base component. [docs]
8+
*/
9+
className?: string
10+
}
11+
12+
export const COffcanvasHeader = forwardRef<HTMLDivElement, COffcanvasHeaderProps>(
13+
({ children, className, ...rest }, ref) => {
14+
const _className = classNames('offcanvas-header', className)
15+
16+
return (
17+
<div className={_className} {...rest} ref={ref}>
18+
{children}
19+
</div>
20+
)
21+
},
22+
)
23+
24+
COffcanvasHeader.propTypes = {
25+
children: PropTypes.node,
26+
className: PropTypes.string
27+
}
28+
29+
COffcanvasHeader.displayName = 'COffcanvasHeader'
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React, { ElementType, forwardRef, HTMLAttributes } from 'react'
2+
import PropTypes from 'prop-types'
3+
import classNames from 'classnames'
4+
5+
export interface COffcanvasTitleProps extends HTMLAttributes<HTMLHeadingElement> {
6+
/**
7+
* A string of all className you want applied to the component. [docs]
8+
*/
9+
className?: string
10+
/**
11+
* Component used for the root node. Either a string to use a HTML element or a component. [docs]
12+
*
13+
* @default 'h5'
14+
*/
15+
component?: string | ElementType
16+
}
17+
18+
export const COffcanvasTitle = forwardRef<HTMLHeadingElement, COffcanvasTitleProps>(
19+
({ children, component: Component = 'h5', className, ...rest }, ref) => {
20+
const _className = classNames('offcanvas-title', className)
21+
22+
return (
23+
<Component className={_className} {...rest} ref={ref}>
24+
{children}
25+
</Component>
26+
)
27+
},
28+
)
29+
30+
COffcanvasTitle.propTypes = {
31+
children: PropTypes.node,
32+
className: PropTypes.string,
33+
component: PropTypes.elementType,
34+
}
35+
36+
COffcanvasTitle.displayName = 'COffcanvasTitle'

0 commit comments

Comments
 (0)