Skip to content

Commit ec40ca6

Browse files
authored
Merge pull request #843 from devtron-labs/fix/focus-trap-misc
fix: focus trap misc
2 parents c0886a2 + b4283ef commit ec40ca6

11 files changed

Lines changed: 99 additions & 61 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtron-labs/devtron-fe-common-lib",
3-
"version": "1.18.1-pre-5",
3+
"version": "1.18.1-pre-7",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",

src/Common/Modals/VisibleModal.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import React, { SyntheticEvent } from 'react'
1919
import { DTFocusTrapType } from '@Shared/Components/DTFocusTrap'
2020

2121
import { Backdrop, POP_UP_MENU_MODAL_ID } from '../../Shared'
22-
import { stopPropagation } from '@Common/Helper'
2322

2423
export class VisibleModal extends React.Component<{
2524
className?: string

src/Common/Modals/VisibleModal2.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import React, { SyntheticEvent } from 'react'
1818
import { Backdrop } from '@Shared/Components'
1919
import { DTFocusTrapType } from '@Shared/Components/DTFocusTrap'
20-
import { stopPropagation } from '@Common/Helper'
2120

2221
export class VisibleModal2 extends React.Component<{
2322
className?: string

src/Shared/Components/DTFocusTrap/DTFocusTrap.tsx

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,36 @@
1-
import { useCallback, useEffect } from 'react'
1+
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
22
import FocusTrap from 'focus-trap-react'
33

4+
import { noop } from '@Common/Helper'
45
import { ALLOW_ACTION_OUTSIDE_FOCUS_TRAP } from '@Shared/constants'
56
import { preventBodyScroll } from '@Shared/Helpers'
67

78
import { DTFocusTrapType } from './types'
89

10+
const FocusTrapControlContext = createContext<{
11+
disableFocusTrap: () => void
12+
resumeFocusTrap: () => void
13+
}>(null)
14+
15+
export const useFocusTrapControl = () => {
16+
const context = useContext(FocusTrapControlContext)
17+
if (!context) {
18+
return {
19+
disableFocusTrap: noop,
20+
resumeFocusTrap: noop,
21+
}
22+
}
23+
return context
24+
}
25+
926
const DTFocusTrap = ({
1027
onEscape,
1128
deactivateFocusOnEscape = true,
1229
children,
1330
initialFocus = undefined,
1431
}: DTFocusTrapType) => {
32+
const [isFocusEnabled, setIsFocusEnabled] = useState(true)
33+
1534
const handleEscape = useCallback(
1635
(e?: KeyboardEvent | MouseEvent) => {
1736
onEscape(e)
@@ -28,28 +47,39 @@ const DTFocusTrap = ({
2847
}
2948
}, [])
3049

50+
const focusContextValue = useMemo(
51+
() => ({
52+
disableFocusTrap: () => setIsFocusEnabled(false),
53+
resumeFocusTrap: () => setIsFocusEnabled(true),
54+
}),
55+
[],
56+
)
57+
3158
return (
32-
<FocusTrap
33-
focusTrapOptions={{
34-
escapeDeactivates: handleEscape,
35-
initialFocus,
36-
allowOutsideClick: (event) => {
37-
// Allow up to 3 parent levels to check for the allowed class
38-
let el = event.target as Element | null
39-
let depth = 0
40-
while (el && depth < 4) {
41-
if (el.classList && el.classList.contains(ALLOW_ACTION_OUTSIDE_FOCUS_TRAP)) {
42-
return true
59+
<FocusTrapControlContext.Provider value={focusContextValue}>
60+
<FocusTrap
61+
active={isFocusEnabled}
62+
focusTrapOptions={{
63+
escapeDeactivates: handleEscape,
64+
initialFocus,
65+
allowOutsideClick: (event) => {
66+
// Allow up to 3 parent levels to check for the allowed class
67+
let el = event.target as Element | null
68+
let depth = 0
69+
while (el && depth < 4) {
70+
if (el.classList && el.classList.contains(ALLOW_ACTION_OUTSIDE_FOCUS_TRAP)) {
71+
return true
72+
}
73+
el = el.parentElement
74+
depth += 1
4375
}
44-
el = el.parentElement
45-
depth += 1
46-
}
47-
return false
48-
},
49-
}}
50-
>
51-
{children}
52-
</FocusTrap>
76+
return false
77+
},
78+
}}
79+
>
80+
{children}
81+
</FocusTrap>
82+
</FocusTrapControlContext.Provider>
5383
)
5484
}
5585

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { default as DTFocusTrap } from './DTFocusTrap'
1+
export { default as DTFocusTrap, useFocusTrapControl } from './DTFocusTrap'
22
export type { DTFocusTrapType } from './types'

src/Shared/Components/DatePicker/DateTimePicker.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { useState } from 'react'
17+
import { useEffect, useState } from 'react'
1818
import { SingleDatePicker } from 'react-dates'
1919
import CustomizableCalendarDay from 'react-dates/esm/components/CustomizableCalendarDay'
2020
import { SelectInstance } from 'react-select'
@@ -28,6 +28,7 @@ import { ComponentSizeType } from '@Shared/constants'
2828
import 'react-dates/initialize'
2929

3030
import { DATE_TIME_FORMATS } from '../../../Common'
31+
import { useFocusTrapControl } from '../DTFocusTrap'
3132
import { SelectPicker } from '../SelectPicker'
3233
import { customDayStyles, DATE_PICKER_IDS, DATE_PICKER_PLACEHOLDER } from './constants'
3334
import { DateTimePickerProps } from './types'
@@ -56,6 +57,8 @@ const DateTimePicker = ({
5657
const selectedTimeOption = DEFAULT_TIME_OPTIONS.find((option) => option.value === time) ?? DEFAULT_TIME_OPTIONS[0]
5758
const [isFocused, setFocused] = useState(false)
5859

60+
const { disableFocusTrap, resumeFocusTrap } = useFocusTrapControl()
61+
5962
const handleFocusChange = ({ focused }) => {
6063
setFocused(focused)
6164
}
@@ -71,6 +74,14 @@ const DateTimePicker = ({
7174
// Function to disable dates including today and all past dates
7275
const isDayBlocked = (day) => isTodayBlocked && !day.isAfter(today)
7376

77+
useEffect(() => {
78+
if (isFocused) {
79+
disableFocusTrap()
80+
return
81+
}
82+
resumeFocusTrap()
83+
}, [isFocused])
84+
7485
return (
7586
<div className="date-time-picker">
7687
{label && (
Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { MouseEvent } from 'react'
21
import { AnimatePresence, motion } from 'framer-motion'
32

43
import { Backdrop } from '../Backdrop'
@@ -19,35 +18,25 @@ export const Popover = ({
1918
buttonProps,
2019
triggerElement,
2120
children,
22-
}: PopoverProps) => {
23-
const handleTriggerClick = (e: MouseEvent<HTMLDivElement>) => {
24-
e.stopPropagation()
25-
triggerProps.onClick(e)
26-
}
21+
}: PopoverProps) => (
22+
<>
23+
<div {...triggerProps}>{triggerElement || <Button {...buttonProps} />}</div>
2724

28-
return (
29-
<>
30-
<div {...triggerProps}>{triggerElement || <Button {...buttonProps} />}</div>
31-
32-
<AnimatePresence>
33-
{open && (
34-
<Backdrop {...overlayProps}>
35-
<div
36-
className="dc__position-abs"
37-
style={{ left: bounds.left, top: bounds.top }}
38-
onClick={handleTriggerClick}
39-
>
40-
<div
41-
className="dc__visibility-hidden"
42-
style={{ width: bounds.width, height: bounds.height }}
43-
/>
44-
<motion.div {...popoverProps} data-testid={popoverProps.id}>
45-
{children}
46-
</motion.div>
47-
</div>
48-
</Backdrop>
49-
)}
50-
</AnimatePresence>
51-
</>
52-
)
53-
}
25+
<AnimatePresence>
26+
{open && (
27+
<Backdrop {...overlayProps}>
28+
<div
29+
className="dc__position-abs"
30+
style={{ left: bounds.left, top: bounds.top }}
31+
onClick={triggerProps.onClick}
32+
>
33+
<div className="dc__visibility-hidden" style={{ width: bounds.width, height: bounds.height }} />
34+
<motion.div {...popoverProps} data-testid={popoverProps.id}>
35+
{children}
36+
</motion.div>
37+
</div>
38+
</Backdrop>
39+
)}
40+
</AnimatePresence>
41+
</>
42+
)

src/Shared/Components/Popover/usePopover.hook.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { useLayoutEffect, useRef, useState } from 'react'
22

3+
import { stopPropagation } from '@Common/Helper'
4+
35
import { UsePopoverProps, UsePopoverReturnType } from './types'
46
import {
57
getPopoverActualPositionAlignment,
@@ -148,6 +150,7 @@ export const usePopover = ({
148150
ref: popover,
149151
role: 'listbox',
150152
className: `dc__position-abs dc__outline-none-imp ${variant === 'menu' ? 'bg__menu--primary shadow__menu' : 'bg__overlay--primary shadow__overlay'} border__primary br-6 dc__overflow-hidden ${isAutoWidth ? 'dc_width-max-content dc__mxw-250' : ''}`,
153+
onClick: stopPropagation,
151154
onKeyDown: handlePopoverKeyDown,
152155
style: {
153156
width: !isAutoWidth ? `${width}px` : undefined,

src/Shared/Components/SelectPicker/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,10 @@ export const getCommonSelectStyle = <OptionValue, IsMulti extends boolean>({
371371
lineHeight: '20px',
372372
...(singleValue(base, state) || {}),
373373
}),
374+
menuPortal: (base) => ({
375+
...base,
376+
zIndex: 2,
377+
}),
374378
}
375379
}
376380

0 commit comments

Comments
 (0)