Skip to content

Commit d3d35ee

Browse files
committed
refactor: migrate to forwardRef
1 parent 2018bde commit d3d35ee

File tree

9 files changed

+213
-199
lines changed

9 files changed

+213
-199
lines changed

src/components/dropdown/CDropdownDivider.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FC, HTMLAttributes } from 'react'
1+
import React, { forwardRef, HTMLAttributes } from 'react'
22
import PropTypes from 'prop-types'
33
import classNames from 'classnames'
44

@@ -9,11 +9,13 @@ export interface CDropdownDividerProps extends HTMLAttributes<HTMLHRElement> {
99
className?: string
1010
}
1111

12-
export const CDropdownDivider: FC<CDropdownDividerProps> = ({ className, ...rest }) => {
13-
const _className = classNames('dropdown-divider', className)
12+
export const CDropdownDivider = forwardRef<HTMLHRElement, CDropdownDividerProps>(
13+
({ className, ...rest }, ref) => {
14+
const _className = classNames('dropdown-divider', className)
1415

15-
return <hr className={_className} {...rest} />
16-
}
16+
return <hr className={_className} {...rest} ref={ref} />
17+
},
18+
)
1719

1820
CDropdownDivider.propTypes = {
1921
className: PropTypes.string,

src/components/header/CHeaderDivider.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FC, HTMLAttributes } from 'react'
1+
import React, { forwardRef, HTMLAttributes } from 'react'
22
import PropTypes from 'prop-types'
33
import classNames from 'classnames'
44

@@ -9,11 +9,13 @@ export interface CHeaderDividerProps extends HTMLAttributes<HTMLDivElement> {
99
className?: string
1010
}
1111

12-
export const CHeaderDivider: FC<CHeaderDividerProps> = ({ className, ...rest }) => {
13-
const _className = classNames('header-divider', className)
12+
export const CHeaderDivider = forwardRef<HTMLDivElement, CHeaderDividerProps>(
13+
({ className, ...rest }, ref) => {
14+
const _className = classNames('header-divider', className)
1415

15-
return <div className={_className} {...rest} />
16-
}
16+
return <div className={_className} {...rest} ref={ref} />
17+
},
18+
)
1719

1820
CHeaderDivider.propTypes = {
1921
className: PropTypes.string,

src/components/modal/CModal.tsx

Lines changed: 121 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1-
import React, { FC, HTMLAttributes, useCallback, useLayoutEffect, useRef, useState } from 'react'
1+
import React, {
2+
forwardRef,
3+
HTMLAttributes,
4+
useCallback,
5+
useLayoutEffect,
6+
useRef,
7+
useState,
8+
} from 'react'
29
import { createPortal } from 'react-dom'
310
import PropTypes from 'prop-types'
411
import classNames from 'classnames'
512
import { CSSTransition } from 'react-transition-group'
613

14+
import { useForkedRef } from '../../utils/hooks'
15+
716
import { CBackdrop } from '../backdrop/CBackdrop'
817
import { CModalContent } from './CModalContent'
918
import { CModalDialog } from './CModalDialog'
@@ -64,121 +73,128 @@ export interface CModalProps extends HTMLAttributes<HTMLDivElement> {
6473
visible?: boolean
6574
}
6675

67-
export const CModal: FC<CModalProps> = ({
68-
children,
69-
alignment,
70-
backdrop = true,
71-
className,
72-
duration = 150,
73-
fullscreen,
74-
keyboard = true,
75-
onDismiss,
76-
portal = true,
77-
scrollable,
78-
size,
79-
transition = true,
80-
visible,
81-
}) => {
82-
const ref = useRef<HTMLDivElement>(null)
83-
const [staticBackdrop, setStaticBackdrop] = useState(false)
76+
export const CModal = forwardRef<HTMLDivElement, CModalProps>(
77+
(
78+
{
79+
children,
80+
alignment,
81+
backdrop = true,
82+
className,
83+
duration = 150,
84+
fullscreen,
85+
keyboard = true,
86+
onDismiss,
87+
portal = true,
88+
scrollable,
89+
size,
90+
transition = true,
91+
visible,
92+
},
93+
ref,
94+
) => {
95+
const [staticBackdrop, setStaticBackdrop] = useState(false)
8496

85-
const handleDismiss = () => {
86-
if (typeof onDismiss === 'undefined') {
87-
return setStaticBackdrop(true)
88-
}
89-
return onDismiss && onDismiss()
90-
}
97+
const modalRef = useRef<HTMLDivElement>(null)
98+
const forkedRef = useForkedRef(ref, modalRef)
9199

92-
useLayoutEffect(() => {
93-
setTimeout(() => setStaticBackdrop(false), duration)
94-
}, [staticBackdrop])
100+
const handleDismiss = () => {
101+
if (typeof onDismiss === 'undefined') {
102+
return setStaticBackdrop(true)
103+
}
104+
return onDismiss && onDismiss()
105+
}
95106

96-
const getTransitionClass = (state: string) => {
97-
return state === 'entering'
98-
? 'd-block'
99-
: state === 'entered'
100-
? 'show d-block'
101-
: state === 'exiting'
102-
? 'd-block'
103-
: ''
104-
}
105-
const _className = classNames(
106-
'modal',
107-
{
108-
'modal-static': staticBackdrop,
109-
fade: transition,
110-
},
111-
className,
112-
)
107+
useLayoutEffect(() => {
108+
setTimeout(() => setStaticBackdrop(false), duration)
109+
}, [staticBackdrop])
113110

114-
// Set focus to modal after open
115-
useLayoutEffect(() => {
116-
if (visible) {
117-
document.body.classList.add('modal-open')
118-
setTimeout(
119-
() => {
120-
ref.current && ref.current.focus()
121-
},
122-
!transition ? 0 : duration,
123-
)
124-
} else {
125-
document.body.classList.remove('modal-open')
111+
const getTransitionClass = (state: string) => {
112+
return state === 'entering'
113+
? 'd-block'
114+
: state === 'entered'
115+
? 'show d-block'
116+
: state === 'exiting'
117+
? 'd-block'
118+
: ''
126119
}
127-
return () => document.body.classList.remove('modal-open')
128-
}, [visible])
120+
const _className = classNames(
121+
'modal',
122+
{
123+
'modal-static': staticBackdrop,
124+
fade: transition,
125+
},
126+
className,
127+
)
129128

130-
const handleKeyDown = useCallback(
131-
(event) => {
132-
if (event.key === 'Escape' && keyboard) {
133-
return handleDismiss()
129+
// Set focus to modal after open
130+
useLayoutEffect(() => {
131+
if (visible) {
132+
document.body.classList.add('modal-open')
133+
setTimeout(
134+
() => {
135+
modalRef.current && modalRef.current.focus()
136+
},
137+
!transition ? 0 : duration,
138+
)
139+
} else {
140+
document.body.classList.remove('modal-open')
134141
}
135-
},
136-
[ref, handleDismiss],
137-
)
142+
return () => document.body.classList.remove('modal-open')
143+
}, [visible])
144+
145+
const handleKeyDown = useCallback(
146+
(event) => {
147+
if (event.key === 'Escape' && keyboard) {
148+
return handleDismiss()
149+
}
150+
},
151+
[modalRef, handleDismiss],
152+
)
153+
154+
const modal = (ref?: React.Ref<HTMLDivElement>, transitionClass?: string) => {
155+
return (
156+
<>
157+
<div
158+
className={classNames(_className, transitionClass)}
159+
tabIndex={-1}
160+
role="dialog"
161+
ref={ref}
162+
>
163+
<CModalDialog
164+
alignment={alignment}
165+
fullscreen={fullscreen}
166+
scrollable={scrollable}
167+
size={size}
168+
onClick={(event) => event.stopPropagation()}
169+
>
170+
<CModalContent>{children}</CModalContent>
171+
</CModalDialog>
172+
</div>
173+
{backdrop && <CBackdrop visible={visible} />}
174+
</>
175+
)
176+
}
138177

139-
const modal = (ref?: React.Ref<HTMLDivElement>, transitionClass?: string) => {
140178
return (
141-
<>
142-
<div
143-
className={classNames(_className, transitionClass)}
144-
tabIndex={-1}
145-
role="dialog"
146-
ref={ref}
179+
<div onClick={handleDismiss} onKeyDown={handleKeyDown}>
180+
<CSSTransition
181+
in={visible}
182+
timeout={!transition ? 0 : duration}
183+
onExit={onDismiss}
184+
mountOnEnter
185+
unmountOnExit
147186
>
148-
<CModalDialog
149-
alignment={alignment}
150-
fullscreen={fullscreen}
151-
scrollable={scrollable}
152-
size={size}
153-
onClick={(event) => event.stopPropagation()}
154-
>
155-
<CModalContent>{children}</CModalContent>
156-
</CModalDialog>
157-
</div>
158-
{backdrop && <CBackdrop visible={visible} />}
159-
</>
187+
{(state) => {
188+
const transitionClass = getTransitionClass(state)
189+
return typeof window !== 'undefined' && portal
190+
? createPortal(modal(forkedRef, transitionClass), document.body)
191+
: modal(forkedRef, transitionClass)
192+
}}
193+
</CSSTransition>
194+
</div>
160195
)
161-
}
162-
163-
return (
164-
<div onClick={handleDismiss} onKeyDown={handleKeyDown}>
165-
<CSSTransition
166-
in={visible}
167-
timeout={!transition ? 0 : duration}
168-
onExit={onDismiss}
169-
mountOnEnter
170-
unmountOnExit
171-
>
172-
{(state) => {
173-
const transitionClass = getTransitionClass(state)
174-
return typeof window !== 'undefined' && portal
175-
? createPortal(modal(ref, transitionClass), document.body)
176-
: modal(ref, transitionClass)
177-
}}
178-
</CSSTransition>
179-
</div>
180-
)
181-
}
196+
},
197+
)
182198

183199
CModal.propTypes = {
184200
alignment: PropTypes.oneOf(['top', 'center']),

src/components/modal/CModalBody.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FC, HTMLAttributes } from 'react'
1+
import React, { forwardRef, HTMLAttributes } from 'react'
22
import PropTypes from 'prop-types'
33
import classNames from 'classnames'
44

@@ -9,15 +9,17 @@ export interface CModalBodyProps extends HTMLAttributes<HTMLDivElement> {
99
className?: string
1010
}
1111

12-
export const CModalBody: FC<CModalBodyProps> = ({ children, className, ...rest }) => {
13-
const _className = classNames('modal-body', className)
12+
export const CModalBody = forwardRef<HTMLDivElement, CModalBodyProps>(
13+
({ children, className, ...rest }, ref) => {
14+
const _className = classNames('modal-body', className)
1415

15-
return (
16-
<div className={_className} {...rest}>
17-
{children}
18-
</div>
19-
)
20-
}
16+
return (
17+
<div className={_className} {...rest} ref={ref}>
18+
{children}
19+
</div>
20+
)
21+
},
22+
)
2123

2224
CModalBody.propTypes = {
2325
children: PropTypes.node,

src/components/modal/CModalContent.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FC, HTMLAttributes } from 'react'
1+
import React, { forwardRef, HTMLAttributes } from 'react'
22
import PropTypes from 'prop-types'
33
import classNames from 'classnames'
44

@@ -9,15 +9,17 @@ export interface CModalContentProps extends HTMLAttributes<HTMLDivElement> {
99
className?: string
1010
}
1111

12-
export const CModalContent: FC<CModalContentProps> = ({ children, className, ...rest }) => {
13-
const _className = classNames('modal-content', className)
12+
export const CModalContent = forwardRef<HTMLDivElement, CModalContentProps>(
13+
({ children, className, ...rest }, ref) => {
14+
const _className = classNames('modal-content', className)
1415

15-
return (
16-
<div className={_className} {...rest}>
17-
{children}
18-
</div>
19-
)
20-
}
16+
return (
17+
<div className={_className} {...rest} ref={ref}>
18+
{children}
19+
</div>
20+
)
21+
},
22+
)
2123

2224
CModalContent.propTypes = {
2325
children: PropTypes.node,

0 commit comments

Comments
 (0)