Skip to content

Commit eb298ad

Browse files
committed
feat: Add 'height: initial' to 'Overlay'
1 parent 2f54845 commit eb298ad

File tree

2 files changed

+90
-6
lines changed

2 files changed

+90
-6
lines changed

src/Overlay.tsx

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import styled from 'styled-components'
2-
import React, {ReactElement, useRef} from 'react'
2+
import React, {ReactElement, useEffect, useRef} from 'react'
33
import {get, COMMON, POSITION, SystemPositionProps, SystemCommonProps} from './constants'
44
import {ComponentProps} from './utils/types'
55
import {useOverlay, TouchOrMouseEvent} from './hooks'
@@ -9,7 +9,7 @@ import {useCombinedRefs} from './hooks/useCombinedRefs'
99

1010
type StyledOverlayProps = {
1111
width?: keyof typeof widthMap
12-
height?: keyof typeof heightMap
12+
height?: string
1313
visibility?: 'visible' | 'hidden'
1414
}
1515

@@ -38,7 +38,7 @@ const StyledOverlay = styled.div<StyledOverlayProps & SystemCommonProps & System
3838
position: absolute;
3939
min-width: 192px;
4040
max-width: 640px;
41-
height: ${props => heightMap[props.height || 'auto']};
41+
height: ${props => props.height};
4242
width: ${props => widthMap[props.width || 'auto']};
4343
border-radius: 12px;
4444
overflow: hidden;
@@ -68,8 +68,9 @@ export type OverlayProps = {
6868
onClickOutside: (e: TouchOrMouseEvent) => void
6969
onEscape: (e: KeyboardEvent) => void
7070
visibility?: 'visible' | 'hidden'
71+
height?: keyof typeof heightMap | 'initial'
7172
[additionalKey: string]: unknown
72-
} & Omit<ComponentProps<typeof StyledOverlay>, 'visibility' | keyof SystemPositionProps>
73+
} & Omit<ComponentProps<typeof StyledOverlay>, 'height' | 'visibility' | keyof SystemPositionProps>
7374

7475
/**
7576
* An `Overlay` is a flexible floating surface, used to display transient content such as menus,
@@ -81,12 +82,22 @@ export type OverlayProps = {
8182
* @param onClickOutside Required. Function to call when clicking outside of the `Overlay`. Typically this function sets the `Overlay` visibility state to `false`.
8283
* @param onEscape Required. Function to call when user presses `Escape`. Typically this function sets the `Overlay` visibility state to `false`.
8384
* @param width Sets the width of the `Overlay`, pick from our set list of widths, or pass `auto` to automatically set the width based on the content of the `Overlay`. `small` corresponds to `256px`, `medium` corresponds to `320px`, `large` corresponds to `480px`, `xlarge` corresponds to `640px`, `xxlarge` corresponds to `960px`.
84-
* @param height Sets the height of the `Overlay`, pick from our set list of heights, or pass `auto` to automatically set the height based on the content of the `Overlay`. `xsmall` corresponds to `192px`, `small` corresponds to `256px`, `medium` corresponds to `320px`, `large` corresponds to `432px`, `xlarge` corresponds to `600px`.
85+
* @param height Sets the height of the `Overlay`, pick from our set list of heights, or pass `auto` to automatically set the height based on the content of the `Overlay`, or pass `initial` to set the height based on the initial content of the `Overlay` (i.e. ignoring content changes). `xsmall` corresponds to `192px`, `small` corresponds to `256px`, `medium` corresponds to `320px`, `large` corresponds to `432px`, `xlarge` corresponds to `600px`.
8586
* @param visibility Sets the visibility of the `Overlay`
8687
*/
8788
const Overlay = React.forwardRef<HTMLDivElement, OverlayProps>(
8889
(
89-
{onClickOutside, role = 'dialog', initialFocusRef, returnFocusRef, ignoreClickRefs, onEscape, visibility, ...rest},
90+
{
91+
onClickOutside,
92+
role = 'dialog',
93+
initialFocusRef,
94+
returnFocusRef,
95+
ignoreClickRefs,
96+
onEscape,
97+
visibility,
98+
height: heightKey,
99+
...rest
100+
},
90101
forwardedRef
91102
): ReactElement => {
92103
const overlayRef = useRef<HTMLDivElement>(null)
@@ -100,12 +111,22 @@ const Overlay = React.forwardRef<HTMLDivElement, OverlayProps>(
100111
onClickOutside,
101112
initialFocusRef
102113
})
114+
115+
const initialHeight = useRef<string>('auto')
116+
useEffect(() => {
117+
if (overlayRef.current?.clientHeight) {
118+
initialHeight.current = `${overlayRef.current.clientHeight}px`
119+
}
120+
}, [overlayRef])
121+
const height = heightKey === 'initial' ? initialHeight.current : heightMap[heightKey || 'auto']
122+
103123
return (
104124
<Portal>
105125
<StyledOverlay
106126
{...overlayProps}
107127
aria-modal="true"
108128
role={role}
129+
height={height}
109130
{...rest}
110131
ref={combinedRef}
111132
visibility={visibility}

src/stories/SelectPanel.stories.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,66 @@ export function SingleSelectStory(): JSX.Element {
106106
)
107107
}
108108
SingleSelectStory.storyName = 'Single Select'
109+
110+
export function SelectPanelHeightInitialWithOverflowingItemsStory(): JSX.Element {
111+
const [selected, setSelected] = React.useState<ItemInput | undefined>(items[0])
112+
const [filter, setFilter] = React.useState('')
113+
const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase()))
114+
const [open, setOpen] = useState(false)
115+
116+
return (
117+
<>
118+
<h1>Single Select Panel</h1>
119+
<div>Please select a label that describe your issue:</div>
120+
<SelectPanel
121+
renderAnchor={({children, 'aria-labelledby': ariaLabelledBy, ...anchorProps}) => (
122+
<DropdownButton aria-labelledby={` ${ariaLabelledBy}`} {...anchorProps}>
123+
{children ?? 'Select Labels'}
124+
</DropdownButton>
125+
)}
126+
placeholderText="Filter Labels"
127+
open={open}
128+
onOpenChange={setOpen}
129+
items={filteredItems}
130+
selected={selected}
131+
onSelectedChange={setSelected}
132+
onFilterChange={setFilter}
133+
showItemDividers={true}
134+
overlayProps={{width: 'small', height: 'initial', sx: {maxHeight: '192px'}}}
135+
/>
136+
</>
137+
)
138+
}
139+
SelectPanelHeightInitialWithOverflowingItemsStory.storyName = 'SelectPanel, Height: Initial, Overflowing Items'
140+
141+
export function SelectPanelHeightInitialWithUnderflowingItemsStory(): JSX.Element {
142+
const underflowingItems = [items[0], items[1]]
143+
const [selected, setSelected] = React.useState<ItemInput | undefined>(underflowingItems[0])
144+
const [filter, setFilter] = React.useState('')
145+
const filteredItems = underflowingItems.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase()))
146+
const [open, setOpen] = useState(false)
147+
148+
return (
149+
<>
150+
<h1>Single Select Panel</h1>
151+
<div>Please select a label that describe your issue:</div>
152+
<SelectPanel
153+
renderAnchor={({children, 'aria-labelledby': ariaLabelledBy, ...anchorProps}) => (
154+
<DropdownButton aria-labelledby={` ${ariaLabelledBy}`} {...anchorProps}>
155+
{children ?? 'Select Labels'}
156+
</DropdownButton>
157+
)}
158+
placeholderText="Filter Labels"
159+
open={open}
160+
onOpenChange={setOpen}
161+
items={filteredItems}
162+
selected={selected}
163+
onSelectedChange={setSelected}
164+
onFilterChange={setFilter}
165+
showItemDividers={true}
166+
overlayProps={{width: 'small', height: 'initial', sx: {maxHeight: '192px'}}}
167+
/>
168+
</>
169+
)
170+
}
171+
SelectPanelHeightInitialWithUnderflowingItemsStory.storyName = 'SelectPanel, Height: Initial, Underflowing Items'

0 commit comments

Comments
 (0)