Skip to content

Commit ad61c5d

Browse files
committed
refactor(CToast): simplify props; update animations
1 parent 1a4ceb2 commit ad61c5d

File tree

4 files changed

+116
-154
lines changed

4 files changed

+116
-154
lines changed

src/components/toast/CToast.tsx

Lines changed: 46 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
11
import React, {
2-
CSSProperties,
3-
ElementType,
2+
createContext,
43
forwardRef,
54
HTMLAttributes,
65
useEffect,
76
useRef,
87
useState,
98
} from 'react'
10-
import { Transition } from 'react-transition-group'
9+
import { CSSTransition } from 'react-transition-group'
1110
import PropTypes from 'prop-types'
1211
import classNames from 'classnames'
1312

1413
import { Colors, colorPropType } from '../Types'
15-
import { useForkedRef } from '../../utils/hooks'
16-
17-
import { CButtonClose } from '../button/CButtonClose'
18-
import { CToastBody } from './CToastBody'
19-
import { CToastHeader } from './CToastHeader'
2014

2115
export interface CToastProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
2216
/**
@@ -25,10 +19,18 @@ export interface CToastProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title
2519
* @default true
2620
*/
2721
autohide?: boolean
22+
/**
23+
* A string of all className you want applied to the body wrapper. [docs]
24+
*/
25+
bodyWrapperClassName?: string
2826
/**
2927
* A string of all className you want applied to the base component. [docs]
3028
*/
3129
className?: string
30+
/**
31+
* A string of all className you want applied to the close button. [docs]
32+
*/
33+
closeButtonClassName?: string
3234
/**
3335
* Sets the color context of the component to one of CoreUI’s themed colors. [docs]
3436
*
@@ -40,28 +42,10 @@ export interface CToastProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title
4042
* Delay hiding the toast (ms). [docs]
4143
*/
4244
delay?: number
43-
/**
44-
* Optionally add a close button to component and allow it to self dismiss. [docs]
45-
*
46-
* @default true
47-
*/
48-
dismissible?: boolean
49-
/**
50-
* Set component's icon. [docs]
51-
*/
52-
icon?: string | ElementType
5345
/**
5446
* @ignore
5547
*/
5648
key?: number
57-
/**
58-
* Time node for your component. [docs]
59-
*/
60-
time?: string | ElementType
61-
/**
62-
* Title node for your component. [docs]
63-
*/
64-
title?: string | ElementType
6549
/**
6650
* Toggle the visibility of component. [docs]
6751
*
@@ -74,32 +58,40 @@ export interface CToastProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title
7458
onDismiss?: () => void
7559
}
7660

61+
interface ContextProps extends CToastProps {
62+
visible?: boolean
63+
setVisible: React.Dispatch<React.SetStateAction<boolean | undefined>>
64+
}
65+
66+
export const CToastContext = createContext({} as ContextProps)
67+
7768
export const CToast = forwardRef<HTMLDivElement, CToastProps>(
7869
(
7970
{
8071
children,
8172
autohide = true,
82-
delay = 5000,
83-
dismissible = true,
73+
bodyWrapperClassName,
8474
className,
75+
closeButtonClassName,
8576
color,
86-
icon,
77+
delay = 5000,
8778
key,
88-
time,
89-
title,
9079
visible = true,
9180
onDismiss,
9281
...rest
9382
},
9483
ref,
9584
) => {
9685
const [_visible, setVisible] = useState(visible)
97-
const [height, setHeight] = useState<number>()
98-
99-
const toastRef = useRef<HTMLDivElement>(null)
100-
const forkedRef = useForkedRef(ref, toastRef)
86+
// const toastRef = useRef<HTMLDivElement>(null)
87+
// const forkedRef = useForkedRef(ref, toastRef)
10188
const timeout = useRef<number>()
10289

90+
const contextValues = {
91+
visible: _visible,
92+
setVisible,
93+
}
94+
10395
//triggered on mount and destroy
10496
useEffect(() => () => clearTimeout(timeout.current), [])
10597

@@ -116,88 +108,37 @@ export const CToast = forwardRef<HTMLDivElement, CToastProps>(
116108
}
117109
}
118110

119-
const duration = 150
120-
121-
const defaultStyle: CSSProperties = {
122-
transition: `opacity ${duration}ms linear, height ${duration}ms linear`,
123-
opacity: 1,
124-
}
125-
126-
const transitionStyles = {
127-
entering: { opacity: 0, height: 0 },
128-
entered: { opacity: 1, height: height },
129-
exiting: { opacity: 1, height: height },
130-
exited: { opacity: 0, height: 0 },
131-
}
132-
133-
const onEntering = () => {
134-
toastRef && toastRef.current && setHeight(toastRef.current.scrollHeight)
135-
}
136-
137-
const onEntered = () => {
138-
setHeight(0)
139-
}
140-
141-
const onExit = () => {
142-
toastRef && toastRef.current && setHeight(toastRef.current.scrollHeight)
143-
onDismiss && onDismiss()
144-
}
145-
146-
const onExiting = () => {
147-
// @ts-expect-error
148-
const reflow = toastRef && toastRef.current && toastRef.current.offsetHeight
149-
setHeight(0)
150-
}
151-
152-
const onExited = () => {
153-
setHeight(0)
154-
}
155-
156111
const _className = classNames(
157112
'toast fade',
158113
{
159114
show: _visible,
160115
[`bg-${color}`]: color,
116+
'border-0': color,
161117
},
162118
className,
163119
)
164120
return (
165-
<Transition
121+
<CSSTransition
166122
in={_visible}
167-
timeout={duration}
168-
onEntering={onEntering}
169-
onEntered={onEntered}
170-
onExit={onExit}
171-
onExiting={onExiting}
172-
onExited={onExited}
123+
timeout={250}
173124
unmountOnExit
174125
>
175-
{(state) => {
176-
const currentHeight = height === 0 ? null : { height }
177-
return (
178-
<div
179-
className={_className}
180-
aria-live="assertive"
181-
aria-atomic="true"
182-
role="alert"
183-
style={{ ...defaultStyle, ...transitionStyles[state], ...currentHeight }}
184-
onMouseEnter={() => clearTimeout(timeout.current)}
185-
onMouseLeave={() => _autohide}
186-
{...rest}
187-
key={key}
188-
ref={forkedRef}
189-
>
190-
{title && (
191-
<CToastHeader icon={icon} title={title} time={time}>
192-
{dismissible && <CButtonClose onClick={() => setVisible(false)} />}
193-
</CToastHeader>
194-
)}
195-
<CToastBody>{children}</CToastBody>
196-
{!title && dismissible && <CButtonClose onClick={() => setVisible(false)} />}
197-
</div>
198-
)
199-
}}
200-
</Transition>
126+
<CToastContext.Provider value={contextValues}>
127+
<div
128+
className={_className}
129+
aria-live="assertive"
130+
aria-atomic="true"
131+
role="alert"
132+
onMouseEnter={() => clearTimeout(timeout.current)}
133+
onMouseLeave={() => _autohide}
134+
{...rest}
135+
key={key}
136+
ref={ref}
137+
>
138+
{children}
139+
</div>
140+
</CToastContext.Provider>
141+
</CSSTransition>
201142
)
202143
},
203144
)
@@ -208,12 +149,8 @@ CToast.propTypes = {
208149
className: PropTypes.string,
209150
color: colorPropType,
210151
delay: PropTypes.number,
211-
dismissible: PropTypes.bool,
212-
icon: PropTypes.oneOfType([PropTypes.string, PropTypes.elementType]),
213152
key: PropTypes.number,
214153
onDismiss: PropTypes.func,
215-
time: PropTypes.oneOfType([PropTypes.string, PropTypes.elementType]),
216-
title: PropTypes.oneOfType([PropTypes.string, PropTypes.elementType]),
217154
visible: PropTypes.bool,
218155
}
219156

src/components/toast/CToastClose.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React, { ElementType, forwardRef, useContext } from 'react'
2+
import PropTypes from 'prop-types'
3+
// import classNames from 'classnames'
4+
import { CToastContext } from './CToast'
5+
import { CButtonClose, CButtonCloseProps } from '../button/CButtonClose'
6+
7+
export interface CToastCloseProps extends CButtonCloseProps {
8+
/**
9+
* Component used for the root node. Either a string to use a HTML element or a component. [docs]
10+
*/
11+
component?: string | ElementType
12+
}
13+
14+
export const CToastClose = forwardRef<HTMLButtonElement, CToastCloseProps>(
15+
({ children, component: Component, ...rest }, ref) => {
16+
const { setVisible } = useContext(CToastContext)
17+
return Component ? (
18+
<Component onClick={() => setVisible(false)} {...rest} ref={ref}>
19+
{children}
20+
</Component>
21+
) : (
22+
<CButtonClose onClick={() => setVisible(false)} {...rest} ref={ref} />
23+
)
24+
},
25+
)
26+
27+
CToastClose.propTypes = {
28+
...CButtonClose.propTypes,
29+
component: PropTypes.elementType,
30+
}
31+
32+
CToastClose.displayName = 'CToastClose'

src/components/toast/CToastHeader.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,27 @@
1-
import React, { ElementType, forwardRef, HTMLAttributes } from 'react'
1+
import React, { forwardRef, HTMLAttributes } from 'react'
22
import PropTypes from 'prop-types'
33
import classNames from 'classnames'
44

5+
import { CToastClose } from './CToastClose'
6+
57
export interface CToastHeaderProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
68
/**
79
* A string of all className you want applied to the base component. [docs]
810
*/
911
className?: string
1012
/**
11-
* Set component's icon. [docs]
12-
*/
13-
icon?: string | ElementType
14-
/**
15-
* Time node for your component. [docs]
16-
*/
17-
time?: string | ElementType
18-
/**
19-
* Title node for your component. [docs]
13+
* Automatically add a close button to the header.
2014
*/
21-
title?: string | ElementType
15+
close?: boolean
2216
}
2317

2418
export const CToastHeader = forwardRef<HTMLDivElement, CToastHeaderProps>(
25-
({ children, className, icon, time, title, ...rest }, ref) => {
19+
({ children, className, close, ...rest }, ref) => {
2620
const _className = classNames('toast-header', className)
2721
return (
2822
<div className={_className} {...rest} ref={ref}>
29-
{icon}
30-
{title && <span className="fw-semibold me-auto">{title}</span>}
31-
{time && <span className="text-medium-emphasis small">{time}</span>}
3223
{children}
24+
{close && <CToastClose/>}
3325
</div>
3426
)
3527
},
@@ -38,9 +30,7 @@ export const CToastHeader = forwardRef<HTMLDivElement, CToastHeaderProps>(
3830
CToastHeader.propTypes = {
3931
children: PropTypes.node,
4032
className: PropTypes.string,
41-
icon: PropTypes.oneOfType([PropTypes.string, PropTypes.elementType]),
42-
time: PropTypes.oneOfType([PropTypes.string, PropTypes.elementType]),
43-
title: PropTypes.oneOfType([PropTypes.string, PropTypes.elementType]),
33+
close: PropTypes.bool
4434
}
4535

4636
CToastHeader.displayName = 'CToastHeader'

0 commit comments

Comments
 (0)