1
1
import { cx } from '@emotion/css' ;
2
- import React , { useCallback , useContext } from 'react' ;
2
+ import React , { useCallback , useContext , useRef } from 'react' ;
3
3
import { useAccessibility } from '../../context/Accessibility' ;
4
4
import { useComponentId } from '../../hooks/useComponentId' ;
5
5
import {
@@ -21,6 +21,8 @@ export type AccordionListItemProps = React.HTMLAttributes<HTMLDivElement> &
21
21
index : number ;
22
22
/** The parent ref is used to get its dimensions so the content can be rendered at the right size */
23
23
parentRef : React . RefObject < HTMLDivElement > ;
24
+ /** Manually triggers the focused state of the useDeepFocus hook */
25
+ setDeepFocus : React . Dispatch < React . SetStateAction < boolean > > ;
24
26
unstyled ?: boolean ;
25
27
} ;
26
28
@@ -48,12 +50,14 @@ export const AccordionListItem = React.forwardRef<HTMLDivElement, AccordionListI
48
50
labelProps,
49
51
orientation,
50
52
parentRef,
53
+ setDeepFocus,
51
54
unstyled,
52
55
} ,
53
56
forwardedRef ,
54
57
) : React . ReactElement < AccordionListItemProps > => {
55
58
const accessible = useAccessibility ( ) ;
56
59
const { generateComponentId } = useComponentId ( componentId ) ;
60
+ const contentRef = useRef < HTMLDivElement | null > ( null ) ;
57
61
58
62
const { handleItemClick, isSelected, focusedIndex, setFocused } =
59
63
useContext < InteractiveGroupContextValue < string , HTMLDivElement , HTMLDivElement > > ( InteractiveGroupContext ) ;
@@ -78,6 +82,30 @@ export const AccordionListItem = React.forwardRef<HTMLDivElement, AccordionListI
78
82
[ id , setFocused ] ,
79
83
) ;
80
84
85
+ const handleContentFocus = useCallback < React . FocusEventHandler < HTMLDivElement > > (
86
+ event => {
87
+ event . stopPropagation ( ) ;
88
+
89
+ // set this item as focused
90
+ setFocused ( id , { event } ) ;
91
+
92
+ /**
93
+ * Set the entire accordion as having a focused state, which is
94
+ * necessary because the event.stopPropagation() call here stops
95
+ * the natural setting of this state by the useDeepFocus hook.
96
+ * The reason we need to do all this manually is that if you
97
+ * click into the expanded content item of an accordion while the
98
+ * accordion itself doesn't have focus, if we don't stopPropagation
99
+ * then the accordion steals focus from the item that was clicked on.
100
+ * And if we do stop propagation without manually reconciling the
101
+ * focused state then the accordion will not appear to be focused
102
+ * when in fact it is.
103
+ */
104
+ setDeepFocus ?.( true ) ;
105
+ } ,
106
+ [ id , setFocused , setDeepFocus ] ,
107
+ ) ;
108
+
81
109
const itemFocused = focusedIndex === index ;
82
110
const itemSelected = isSelected ( id ) ;
83
111
const stateProps = { disabled, focused : focused && itemFocused , selected : itemSelected } ;
@@ -98,6 +126,7 @@ export const AccordionListItem = React.forwardRef<HTMLDivElement, AccordionListI
98
126
aria-controls = { generateComponentId ( id , 'panel' ) }
99
127
aria-disabled = { disabled }
100
128
aria-expanded = { ! ! stateProps . selected }
129
+ contentRef = { contentRef }
101
130
flush = { flush }
102
131
iconOnly = { iconOnly }
103
132
id = { generateComponentId ( id ) }
@@ -117,8 +146,11 @@ export const AccordionListItem = React.forwardRef<HTMLDivElement, AccordionListI
117
146
duration = { duration }
118
147
easing = { easing }
119
148
id = { generateComponentId ( id , 'panel' ) }
149
+ // if something within the content gets the focus this prevents the accordion from stealing it
150
+ onFocus = { handleContentFocus }
120
151
orientation = { orientation }
121
152
parentRef = { parentRef }
153
+ ref = { contentRef }
122
154
role = "tabpanel"
123
155
{ ...contentProps }
124
156
{ ...stateProps }
0 commit comments