Skip to content

Commit dce754b

Browse files
authored
SelectPanel: Prepare for non-anchored variants (#5230)
* use Overlay instead of AnchoredOverlay * Create fluffy-days-brake.md * the tiniest chnage * move focus trap for easier review * tiny formatting to make it easier to review
1 parent 126cd03 commit dce754b

File tree

2 files changed

+84
-26
lines changed

2 files changed

+84
-26
lines changed

.changeset/fluffy-days-brake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": patch
3+
---
4+
5+
SelectPanel: (Implementation detail, should have no changes for users) Replace AnchoredOverlay with Overlay and useAnchoredPosition

packages/react/src/SelectPanel/SelectPanel.tsx

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {SearchIcon, TriangleDownIcon} from '@primer/octicons-react'
22
import React, {useCallback, useMemo} from 'react'
33
import type {AnchoredOverlayProps} from '../AnchoredOverlay'
4-
import {AnchoredOverlay} from '../AnchoredOverlay'
4+
import Overlay from '../Overlay'
55
import type {AnchoredOverlayWrapperAnchorProps} from '../AnchoredOverlay/AnchoredOverlay'
66
import Box from '../Box'
77
import type {FilteredActionListProps} from '../FilteredActionList'
@@ -12,12 +12,12 @@ import type {TextInputProps} from '../TextInput'
1212
import type {ItemProps, ItemInput} from './types'
1313

1414
import {Button} from '../Button'
15-
import {useProvidedRefOrCreate} from '../hooks'
16-
import type {FocusZoneHookSettings} from '../hooks/useFocusZone'
15+
import {useAnchoredPosition, useProvidedRefOrCreate} from '../hooks'
1716
import {useId} from '../hooks/useId'
1817
import {useProvidedStateOrCreate} from '../hooks/useProvidedStateOrCreate'
1918
import {LiveRegion, LiveRegionOutlet, Message} from '../internal/components/LiveRegion'
2019
import {useFeatureFlag} from '../FeatureFlags'
20+
import {useFocusTrap} from '../hooks/useFocusTrap'
2121

2222
interface 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-
6459
const 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

Comments
 (0)