Skip to content

Commit d20a599

Browse files
authored
AnchoredOverlay and ActionList fixes for SelectPanel (#1209)
1 parent bba66fd commit d20a599

File tree

4 files changed

+35
-15
lines changed

4 files changed

+35
-15
lines changed

.changeset/chatty-nails-end.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@primer/components": patch
3+
---
4+
5+
Allow Overlay height and width to be set through AnchoredOverlay
6+
Allow ActionList Items to supply an `id` instead of `key`
7+
Performance imporvements when ActionList is not given any groups
8+
Enable focus zone as soon as AnchoredOverlay opens

src/ActionList/Item.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import styled from 'styled-components'
88
/**
99
* Contract for props passed to the `Item` component.
1010
*/
11-
export interface ItemProps extends React.ComponentPropsWithoutRef<'div'>, SxProp {
11+
export interface ItemProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'id'>, SxProp {
1212
/**
1313
* Primary text which names an `Item`.
1414
*/
@@ -69,6 +69,11 @@ export interface ItemProps extends React.ComponentPropsWithoutRef<'div'>, SxProp
6969
* Callback that will trigger both on click selection and keyboard selection.
7070
*/
7171
onAction?: (item: ItemProps, event: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void
72+
73+
/**
74+
* An id associated with this item. Should be unique between items
75+
*/
76+
id?: number | string
7277
}
7378

7479
const getItemVariant = (variant = 'default', disabled?: boolean) => {
@@ -180,6 +185,7 @@ export function Item(itemProps: Partial<ItemProps> & {item?: ItemInput}): JSX.El
180185
onKeyPress,
181186
children,
182187
onClick,
188+
id,
183189
...props
184190
} = itemProps
185191

@@ -215,6 +221,7 @@ export function Item(itemProps: Partial<ItemProps> & {item?: ItemInput}): JSX.El
215221
variant={variant}
216222
aria-selected={selected}
217223
{...props}
224+
data-id={id}
218225
onKeyPress={keyPressHandler}
219226
onClick={clickHandler}
220227
>

src/ActionList/List.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react'
1+
import React, {useMemo} from 'react'
22
import type {AriaRole} from '../utils/types'
33
import {Group, GroupProps} from './Group'
44
import {Item, ItemProps} from './Item'
@@ -137,14 +137,8 @@ export function List(props: ListProps): JSX.Element {
137137
*/
138138
const renderItem = (itemProps: ItemInput, item: ItemInput) => {
139139
const ItemComponent = ('renderItem' in itemProps && itemProps.renderItem) || props.renderItem || Item
140-
return (
141-
<ItemComponent
142-
{...itemProps}
143-
key={itemProps.key || uniqueId()}
144-
sx={{...itemStyle, ...itemProps.sx}}
145-
item={item}
146-
/>
147-
)
140+
const key = itemProps.key ?? itemProps.id?.toString() ?? uniqueId()
141+
return <ItemComponent {...itemProps} key={key} sx={{...itemStyle, ...itemProps.sx}} item={item} />
148142
}
149143

150144
/**
@@ -153,10 +147,11 @@ export function List(props: ListProps): JSX.Element {
153147
let groups: (GroupProps | (Partial<GroupProps> & {renderItem?: typeof Item; renderGroup?: typeof Group}))[] = []
154148

155149
// Collect rendered `Item`s into `Group`s, avoiding excess iteration over the lists of `items` and `groupMetadata`:
150+
const singleGroupId = useMemo(uniqueId, [])
156151

157152
if (!isGroupedListProps(props)) {
158153
// When no `groupMetadata`s is provided, collect rendered `Item`s into a single anonymous `Group`.
159-
groups = [{items: props.items?.map(item => renderItem(item, item)), groupId: uniqueId()}]
154+
groups = [{items: props.items?.map(item => renderItem(item, item)), groupId: singleGroupId}]
160155
} else {
161156
// When `groupMetadata` is provided, collect rendered `Item`s into their associated `Group`s.
162157

src/AnchoredOverlay/AnchoredOverlay.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'
2-
import Overlay from '../Overlay'
2+
import Overlay, {OverlayProps} from '../Overlay'
33
import {useFocusTrap} from '../hooks/useFocusTrap'
44
import {useFocusZone} from '../hooks/useFocusZone'
55
import {useAnchoredPosition, useRenderForcingRef} from '../hooks'
@@ -9,7 +9,7 @@ function stopPropagation(event: React.UIEvent) {
99
event.stopPropagation()
1010
}
1111

12-
export interface AnchoredOverlayProps {
12+
export interface AnchoredOverlayProps extends Pick<OverlayProps, 'height' | 'width'> {
1313
/**
1414
* A custom function component used to render the anchor element.
1515
* Will receive the selected text as `children` prop when an item is activated.
@@ -36,7 +36,15 @@ export interface AnchoredOverlayProps {
3636
* An `AnchoredOverlay` provides an anchor that will open a floating overlay positioned relative to the anchor.
3737
* The overlay can be opened and navigated using keyboard or mouse.
3838
*/
39-
export const AnchoredOverlay: React.FC<AnchoredOverlayProps> = ({renderAnchor, children, open, onOpen, onClose}) => {
39+
export const AnchoredOverlay: React.FC<AnchoredOverlayProps> = ({
40+
renderAnchor,
41+
children,
42+
open,
43+
onOpen,
44+
onClose,
45+
height,
46+
width
47+
}) => {
4048
const anchorRef = useRef<HTMLElement>(null)
4149
const [overlayRef, updateOverlayRef] = useRenderForcingRef<HTMLDivElement>()
4250
const [focusType, setFocusType] = useState<null | 'anchor' | 'list'>(open ? 'list' : null)
@@ -98,7 +106,7 @@ export const AnchoredOverlay: React.FC<AnchoredOverlayProps> = ({renderAnchor, c
98106
return position && {top: `${position.top}px`, left: `${position.left}px`}
99107
}, [position])
100108

101-
useFocusZone({containerRef: overlayRef, disabled: !open || focusType !== 'list' || !position})
109+
useFocusZone({containerRef: overlayRef, disabled: !open || !position})
102110
useFocusTrap({containerRef: overlayRef, disabled: !open || focusType !== 'list' || !position})
103111

104112
return (
@@ -123,6 +131,8 @@ export const AnchoredOverlay: React.FC<AnchoredOverlayProps> = ({renderAnchor, c
123131
visibility={position ? 'visible' : 'hidden'}
124132
onMouseDown={stopPropagation}
125133
onClick={stopPropagation}
134+
height={height}
135+
width={width}
126136
{...overlayPosition}
127137
>
128138
{children}

0 commit comments

Comments
 (0)