11import { SearchIcon , TriangleDownIcon } from '@primer/octicons-react'
22import React , { useCallback , useMemo } from 'react'
33import type { AnchoredOverlayProps } from '../AnchoredOverlay'
4- import { AnchoredOverlay } from '../AnchoredOverlay '
4+ import Overlay from '../Overlay '
55import type { AnchoredOverlayWrapperAnchorProps } from '../AnchoredOverlay/AnchoredOverlay'
66import Box from '../Box'
77import type { FilteredActionListProps } from '../FilteredActionList'
@@ -12,12 +12,12 @@ import type {TextInputProps} from '../TextInput'
1212import type { ItemProps , ItemInput } from './types'
1313
1414import { Button } from '../Button'
15- import { useProvidedRefOrCreate } from '../hooks'
16- import type { FocusZoneHookSettings } from '../hooks/useFocusZone'
15+ import { useAnchoredPosition , useProvidedRefOrCreate } from '../hooks'
1716import { useId } from '../hooks/useId'
1817import { useProvidedStateOrCreate } from '../hooks/useProvidedStateOrCreate'
1918import { LiveRegion , LiveRegionOutlet , Message } from '../internal/components/LiveRegion'
2019import { useFeatureFlag } from '../FeatureFlags'
20+ import { useFocusTrap } from '../hooks/useFocusTrap'
2121
2222interface SelectPanelSingleSelection {
2323 selected : ItemInput | undefined
@@ -56,11 +56,6 @@ function isMultiSelectVariant(
5656 return Array . isArray ( selected )
5757}
5858
59- const focusZoneSettings : Partial < FocusZoneHookSettings > = {
60- // Let FilteredActionList handle focus zone
61- disabled : true ,
62- }
63-
6459const areItemsEqual = ( itemA : ItemInput , itemB : ItemInput ) => {
6560 // prefer checking equivality by item.id
6661 if ( typeof itemA . id !== 'undefined' ) return itemA . id === itemB . id
@@ -137,6 +132,57 @@ export function SelectPanel({
137132 }
138133 } , [ placeholder , renderAnchor , selected ] )
139134
135+ /* Anchoring logic */
136+ const overlayRef = React . useRef < HTMLDivElement > ( null )
137+ const inputRef = React . useRef < HTMLInputElement > ( null )
138+
139+ const { position} = useAnchoredPosition (
140+ {
141+ anchorElementRef : anchorRef ,
142+ floatingElementRef : overlayRef ,
143+ side : 'outside-bottom' ,
144+ align : 'start' ,
145+ } ,
146+ [ open , anchorRef . current , overlayRef . current ] ,
147+ )
148+
149+ const onAnchorClick = useCallback (
150+ ( event : React . MouseEvent < HTMLElement > ) => {
151+ if ( event . defaultPrevented || event . button !== 0 ) {
152+ return
153+ }
154+
155+ if ( ! open ) {
156+ onOpen ( 'anchor-click' )
157+ } else {
158+ onClose ( 'anchor-click' )
159+ }
160+ } ,
161+ [ open , onOpen , onClose ] ,
162+ )
163+
164+ const onAnchorKeyDown = useCallback (
165+ ( event : React . KeyboardEvent < HTMLElement > ) => {
166+ if ( ! event . defaultPrevented ) {
167+ if ( ! open && [ 'ArrowDown' , 'ArrowUp' , ' ' , 'Enter' ] . includes ( event . key ) ) {
168+ onOpen ( 'anchor-key-press' , event )
169+ event . preventDefault ( )
170+ }
171+ }
172+ } ,
173+ [ open , onOpen ] ,
174+ )
175+
176+ const anchorProps = {
177+ ref : anchorRef ,
178+ 'aria-haspopup' : true ,
179+ 'aria-expanded' : open ,
180+ onClick : onAnchorClick ,
181+ onKeyDown : onAnchorKeyDown ,
182+ }
183+ // TODO: anchor should be called button because it's not an anchor anymore
184+ const anchor = renderMenuAnchor ? renderMenuAnchor ( anchorProps ) : null
185+
140186 const itemsToRender = useMemo ( ( ) => {
141187 return items . map ( item => {
142188 const isItemSelected = isMultiSelectVariant ( selected ) ? doesItemsIncludeItem ( selected , item ) : selected === item
@@ -172,10 +218,13 @@ export function SelectPanel({
172218 } )
173219 } , [ onClose , onSelectedChange , items , selected ] )
174220
175- const inputRef = React . useRef < HTMLInputElement > ( null )
176- const focusTrapSettings = {
221+ /** Focus trap */
222+ useFocusTrap ( {
223+ containerRef : overlayRef ,
224+ disabled : ! open || ! position ,
177225 initialFocusRef : inputRef ,
178- }
226+ returnFocusRef : anchorRef ,
227+ } )
179228
180229 const extendedTextInputProps : Partial < TextInputProps > = useMemo ( ( ) => {
181230 return {
@@ -189,22 +238,26 @@ export function SelectPanel({
189238
190239 const usingModernActionList = useFeatureFlag ( 'primer_react_select_panel_with_modern_action_list' )
191240
241+ if ( ! open ) return < > { anchor } </ >
242+
192243 return (
193244 < LiveRegion >
194- < AnchoredOverlay
195- renderAnchor = { renderMenuAnchor }
196- anchorRef = { anchorRef }
197- open = { open }
198- onOpen = { onOpen }
199- onClose = { onClose }
200- overlayProps = { {
201- role : 'dialog' ,
202- 'aria-labelledby' : titleId ,
203- 'aria-describedby' : subtitle ? subtitleId : undefined ,
204- ...overlayProps ,
205- } }
206- focusTrapSettings = { focusTrapSettings }
207- focusZoneSettings = { focusZoneSettings }
245+ { anchor }
246+
247+ < Overlay
248+ role = "dialog"
249+ aria-labelledby = { titleId }
250+ aria-describedby = { subtitle ? subtitleId : undefined }
251+ ref = { overlayRef }
252+ returnFocusRef = { anchorRef }
253+ onEscape = { ( ) => onClose ( 'escape' ) }
254+ onClickOutside = { ( ) => onClose ( 'click-outside' ) }
255+ ignoreClickRefs = {
256+ /* this is required so that clicking the button while the panel is open does not re-open the panel */
257+ [ anchorRef ]
258+ }
259+ { ...position }
260+ { ...overlayProps }
208261 >
209262 < LiveRegionOutlet />
210263 { usingModernActionList ? null : (
@@ -260,7 +313,7 @@ export function SelectPanel({
260313 </ Box >
261314 ) }
262315 </ Box >
263- </ AnchoredOverlay >
316+ </ Overlay >
264317 </ LiveRegion >
265318 )
266319}
0 commit comments