Skip to content

Commit e4f0fc6

Browse files
llastflowersCopilotprimer[bot]
authored
Llastflowers/5118/select panel unhide footer (#6170)
Co-authored-by: Copilot <[email protected]> Co-authored-by: primer[bot] <119360173+primer[bot]@users.noreply.github.com>
1 parent 15f942d commit e4f0fc6

File tree

4 files changed

+58
-3
lines changed

4 files changed

+58
-3
lines changed

.changeset/dull-pots-appear.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+
Update SelectPanel so that content isn't hidden behind mobile keyboard

packages/react/src/SelectPanel/SelectPanel.module.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@
140140
display: none;
141141
align-items: center;
142142
padding: var(--base-size-8);
143-
gap: var(--stack-gap-condensed);
144143
justify-content: center;
145144

146145
&:where([data-display-footer='always']) {

packages/react/src/SelectPanel/SelectPanel.tsx

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ const doesItemsIncludeItem = (items: ItemInput[], item: ItemInput) => {
141141
return items.some(i => areItemsEqual(i, item))
142142
}
143143

144-
const defaultRendorAnchor: NonNullable<SelectPanelProps['renderAnchor']> = props => {
144+
const defaultRenderAnchor: NonNullable<SelectPanelProps['renderAnchor']> = props => {
145145
const {children, ...rest} = props
146146
return (
147147
<Button trailingAction={TriangleDownIcon} {...rest}>
@@ -153,7 +153,7 @@ const defaultRendorAnchor: NonNullable<SelectPanelProps['renderAnchor']> = props
153153
function Panel({
154154
open,
155155
onOpenChange,
156-
renderAnchor = defaultRendorAnchor,
156+
renderAnchor = defaultRenderAnchor,
157157
anchorRef: externalAnchorRef,
158158
placeholder,
159159
placeholderText = 'Filter items',
@@ -201,6 +201,11 @@ function Panel({
201201
const [selectedOnSort, setSelectedOnSort] = useState<ItemInput[]>([])
202202
const [prevItems, setPrevItems] = useState<ItemInput[]>([])
203203
const [prevOpen, setPrevOpen] = useState(open)
204+
const initialHeightRef = useRef(0)
205+
const initialScaleRef = useRef(1)
206+
const [isKeyboardVisible, setIsKeyboardVisible] = useState(false)
207+
const [availablePanelHeight, setAvailablePanelHeight] = useState<number | undefined>(undefined)
208+
const KEYBOARD_VISIBILITY_THRESHOLD = 10
204209

205210
const usingModernActionList = useFeatureFlag('primer_react_select_panel_with_modern_action_list')
206211
const featureFlagFullScreenOnNarrow = useFeatureFlag('primer_react_select_panel_fullscreen_on_narrow')
@@ -379,6 +384,46 @@ function Panel({
379384
}
380385
}, [open, dataLoadedOnce, onFilterChange, filterValue, items, loadingManagedExternally, listContainerElement])
381386

387+
useEffect(() => {
388+
if (!window.visualViewport || !open || !isNarrowScreenSize) {
389+
return
390+
}
391+
392+
initialHeightRef.current = window.visualViewport.height
393+
initialScaleRef.current = window.visualViewport.scale
394+
395+
const handleViewportChange = debounce(() => {
396+
if (window.visualViewport) {
397+
const currentScale = window.visualViewport.scale
398+
const isZooming = currentScale !== initialScaleRef.current
399+
if (!isZooming) {
400+
const currentHeight = window.visualViewport.height
401+
const keyboardVisible = initialHeightRef.current - currentHeight > KEYBOARD_VISIBILITY_THRESHOLD
402+
setIsKeyboardVisible(keyboardVisible)
403+
setAvailablePanelHeight(keyboardVisible ? currentHeight : undefined)
404+
}
405+
}
406+
}, 100)
407+
408+
// keeping this check to satisfy typescript but need eslint to ignore redundancy rule
409+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
410+
if (window.visualViewport) {
411+
// Using visualViewport to more reliably detect viewport changes across different browsers, which specifically requires these listeners
412+
// eslint-disable-next-line github/prefer-observers
413+
window.visualViewport.addEventListener('resize', handleViewportChange)
414+
// eslint-disable-next-line github/prefer-observers
415+
window.visualViewport.addEventListener('scroll', handleViewportChange)
416+
}
417+
418+
return () => {
419+
if (window.visualViewport) {
420+
window.visualViewport.removeEventListener('resize', handleViewportChange)
421+
window.visualViewport.removeEventListener('scroll', handleViewportChange)
422+
}
423+
handleViewportChange.cancel()
424+
}
425+
}, [open, isNarrowScreenSize])
426+
382427
const anchorRef = useProvidedRefOrCreate(externalAnchorRef)
383428
const onOpen: AnchoredOverlayProps['onOpen'] = useCallback(
384429
(gesture: Parameters<Exclude<AnchoredOverlayProps['onOpen'], undefined>>[0]) => onOpenChange(true, gesture),
@@ -649,6 +694,12 @@ function Panel({
649694
'--max-height': overlayProps?.maxHeight ? heightMap[overlayProps.maxHeight] : heightMap['large'],
650695
/* override AnchoredOverlay position */
651696
transform: variant === 'modal' ? 'translate(-50%, -50%)' : undefined,
697+
// set maxHeight based on calculated availablePanelHeight when keyboard is visible
698+
...(isKeyboardVisible
699+
? {
700+
maxHeight: availablePanelHeight !== undefined ? `${availablePanelHeight}px` : 'auto',
701+
}
702+
: {}),
652703
} as React.CSSProperties,
653704
}}
654705
focusTrapSettings={focusTrapSettings}

0 commit comments

Comments
 (0)