Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/content/Overlay.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,4 @@ System props are deprecated in all components except [Box](/Box). Please use the
| width | `'small' │ 'medium' │ 'large' │ 'xlarge' │ 'xxlarge' │ 'auto'` | `auto` | 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`. |
| height | `'xsmall', 'small', 'medium', 'large', 'xlarge', 'auto'` | `auto` | 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`. |
| visibility | `'visible', 'hidden'` | `visible` | Sets the visibility of the `Overlay`. |
| anchorSide | `AnchorSide` | undefined | Optional. If provided, the Overlay will slide into position from the side of the anchor with a brief animation |
1 change: 1 addition & 0 deletions src/AnchoredOverlay/AnchoredOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export const AnchoredOverlay: React.FC<AnchoredOverlayProps> = ({
onEscape={onEscape}
ref={updateOverlayRef}
role="none"
visibility={position ? 'visible' : 'hidden'}
height={height}
width={width}
{...overlayPosition}
Expand Down
32 changes: 19 additions & 13 deletions src/FilteredActionList/FilteredActionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,21 +88,27 @@ export function FilteredActionList({
[activeDescendantRef]
)

useFocusZone({
containerRef: listContainerRef,
focusOutBehavior: 'wrap',
focusableElementFilter: element => {
return !(element instanceof HTMLInputElement)
},
activeDescendantFocus: inputRef,
onActiveDescendantChanged: (current, previous, directlyActivated) => {
activeDescendantRef.current = current
useFocusZone(
{
containerRef: listContainerRef,
focusOutBehavior: 'wrap',
focusableElementFilter: element => {
return !(element instanceof HTMLInputElement)
},
activeDescendantFocus: inputRef,
onActiveDescendantChanged: (current, previous, directlyActivated) => {
activeDescendantRef.current = current

if (current && scrollContainerRef.current && directlyActivated) {
scrollIntoViewingArea(current, scrollContainerRef.current)
if (current && scrollContainerRef.current && directlyActivated) {
scrollIntoViewingArea(current, scrollContainerRef.current)
}
}
}
})
},
[
// List ref isn't set while loading. Need to re-bind focus zone when it changes
loading
]
)

useEffect(() => {
// if items changed, we want to instantly move active descendant into view
Expand Down
24 changes: 19 additions & 5 deletions src/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type StyledOverlayProps = {
width?: keyof typeof widthMap
height?: keyof typeof heightMap
maxHeight?: keyof Omit<typeof heightMap, 'auto' | 'initial'>
visibility?: 'visible' | 'hidden'
anchorSide?: AnchorSide
}

Expand Down Expand Up @@ -71,7 +72,7 @@ const StyledOverlay = styled.div<StyledOverlayProps & SystemCommonProps & System
opacity: 1;
}
}

visibility: var(--styled-overlay-visibility);
:focus {
outline: none;
}
Expand All @@ -85,8 +86,9 @@ export type OverlayProps = {
returnFocusRef: React.RefObject<HTMLElement>
onClickOutside: (e: TouchOrMouseEvent) => void
onEscape: (e: KeyboardEvent) => void
visibility?: 'visible' | 'hidden'
[additionalKey: string]: unknown
} & Omit<ComponentProps<typeof StyledOverlay>, keyof SystemPositionProps>
} & Omit<ComponentProps<typeof StyledOverlay>, 'visibility' | keyof SystemPositionProps>

/**
* An `Overlay` is a flexible floating surface, used to display transient content such as menus,
Expand All @@ -111,6 +113,7 @@ const Overlay = React.forwardRef<HTMLDivElement, OverlayProps>(
returnFocusRef,
ignoreClickRefs,
onEscape,
visibility = 'visible',
height,
anchorSide,
...rest
Expand Down Expand Up @@ -140,7 +143,7 @@ const Overlay = React.forwardRef<HTMLDivElement, OverlayProps>(

useLayoutEffect(() => {
const {x, y} = getSlideAnimationStartingVector(anchorSide)
if ((!x && !y) || !overlayRef.current?.animate) {
if ((!x && !y) || !overlayRef.current?.animate || visibility === 'hidden') {
return
}

Expand All @@ -152,11 +155,22 @@ const Overlay = React.forwardRef<HTMLDivElement, OverlayProps>(
easing: slideAnimationEasing
}
)
}, [anchorSide, slideAnimationDistance, slideAnimationEasing])
}, [anchorSide, slideAnimationDistance, slideAnimationEasing, visibility])

return (
<Portal>
<StyledOverlay height={height} role={role} {...rest} ref={combinedRef} />
<StyledOverlay
height={height}
role={role}
{...rest}
ref={combinedRef}
style={
{
...rest.style,
'--styled-overlay-visibility': visibility
} as React.CSSProperties
}
/>
</Portal>
)
}
Expand Down
5 changes: 5 additions & 0 deletions src/__tests__/AnchoredOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,9 @@ describe('AnchoredOverlay', () => {
expect(mockCloseCallback).toHaveBeenCalledTimes(1)
expect(mockCloseCallback).toHaveBeenCalledWith('escape')
})

it('should render consistently when open', () => {
const anchoredOverlay = HTMLRender(<AnchoredOverlayTestComponent initiallyOpen={true} />)
expect(anchoredOverlay).toMatchSnapshot()
})
})
238 changes: 238 additions & 0 deletions src/__tests__/__snapshots__/AnchoredOverlay.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,241 @@ exports[`AnchoredOverlay renders consistently 1`] = `
</button>
</div>
`;

exports[`AnchoredOverlay should render consistently when open 1`] = `
Object {
"asFragment": [Function],
"baseElement": .c0 {
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
line-height: 1.5;
color: #24292e;
}

.c1 {
position: relative;
display: inline-block;
padding: 6px 16px;
font-family: inherit;
font-weight: 600;
line-height: 20px;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border-radius: 6px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
-webkit-text-decoration: none;
text-decoration: none;
text-align: center;
font-size: 14px;
color: #24292e;
background-color: #fafbfc;
border: 1px solid rgba(27,31,35,0.15);
box-shadow: 0 1px 0 rgba(27,31,35,0.04),inset 0 1px 0 rgba(255,255,255,0.25);
}

.c1:hover {
-webkit-text-decoration: none;
text-decoration: none;
}

.c1:focus {
outline: none;
}

.c1:disabled {
cursor: default;
}

.c1:disabled svg {
opacity: 0.6;
}

.c1:hover {
background-color: #f3f4f6;
border-color: rgba(27,31,35,0.15);
}

.c1:focus {
border-color: rgba(27,31,35,0.15);
box-shadow: 0 0 0 3px rgba(3,102,214,0.3);
}

.c1:active {
background-color: hsla(220,14%,94%,1);
box-shadow: inset 0 0.15em 0.3em rgba(27,31,35,0.15);
}

.c1:disabled {
color: #959da5;
background-color: #fafbfc;
border-color: rgba(27,31,35,0.15);
}

.c2 {
background-color: #ffffff;
box-shadow: 0 1px 3px rgba(27,31,35,0.12),0 8px 24px rgba(68,77,86,0.12);
position: absolute;
min-width: 192px;
max-width: 640px;
height: auto;
width: auto;
border-radius: 12px;
overflow: hidden;
-webkit-animation: overlay-appear 200ms cubic-bezier(0.33,1,0.68,1);
animation: overlay-appear 200ms cubic-bezier(0.33,1,0.68,1);
visibility: var(--styled-overlay-visibility);
top: 4px;
left: 0px;
}

.c2:focus {
outline: none;
}

<body>
<div>
<div
class="c0"
color="text.primary"
data-portal-root="true"
font-family="normal"
>
<button
aria-haspopup="true"
aria-labelledby="__primer_id_10007"
class="c1"
id="__primer_id_10007"
tabindex="0"
>
Anchor Button
</button>
<div
id="__primerPortalRoot__"
>
<div
style="position: relative; z-index: 1;"
>
<div
class="c2"
data-focus-trap="active"
height="auto"
role="none"
style="--styled-overlay-visibility: visible;"
width="auto"
>
<button
class="focus-visible"
data-focus-visible-added=""
tabindex="0"
type="button"
>
Focusable Child
</button>
</div>
</div>
</div>
</div>
</div>
</body>,
"container": <div>
<div
class="BaseStyles__Base-qvuaww-0 dhupSQ"
color="text.primary"
data-portal-root="true"
font-family="normal"
>
<button
aria-haspopup="true"
aria-labelledby="__primer_id_10007"
class="ButtonBase-sc-181ps9o-0 Button-xjtz72-0 jhgDtb"
id="__primer_id_10007"
tabindex="0"
>
Anchor Button
</button>
<div
id="__primerPortalRoot__"
>
<div
style="position: relative; z-index: 1;"
>
<div
class="Overlay__StyledOverlay-jhwkzw-0 leFwDU"
data-focus-trap="active"
height="auto"
role="none"
style="--styled-overlay-visibility: visible;"
width="auto"
>
<button
class="focus-visible"
data-focus-visible-added=""
tabindex="0"
type="button"
>
Focusable Child
</button>
</div>
</div>
</div>
</div>
</div>,
"debug": [Function],
"findAllByAltText": [Function],
"findAllByDisplayValue": [Function],
"findAllByLabelText": [Function],
"findAllByPlaceholderText": [Function],
"findAllByRole": [Function],
"findAllByTestId": [Function],
"findAllByText": [Function],
"findAllByTitle": [Function],
"findByAltText": [Function],
"findByDisplayValue": [Function],
"findByLabelText": [Function],
"findByPlaceholderText": [Function],
"findByRole": [Function],
"findByTestId": [Function],
"findByText": [Function],
"findByTitle": [Function],
"getAllByAltText": [Function],
"getAllByDisplayValue": [Function],
"getAllByLabelText": [Function],
"getAllByPlaceholderText": [Function],
"getAllByRole": [Function],
"getAllByTestId": [Function],
"getAllByText": [Function],
"getAllByTitle": [Function],
"getByAltText": [Function],
"getByDisplayValue": [Function],
"getByLabelText": [Function],
"getByPlaceholderText": [Function],
"getByRole": [Function],
"getByTestId": [Function],
"getByText": [Function],
"getByTitle": [Function],
"queryAllByAltText": [Function],
"queryAllByDisplayValue": [Function],
"queryAllByLabelText": [Function],
"queryAllByPlaceholderText": [Function],
"queryAllByRole": [Function],
"queryAllByTestId": [Function],
"queryAllByText": [Function],
"queryAllByTitle": [Function],
"queryByAltText": [Function],
"queryByDisplayValue": [Function],
"queryByLabelText": [Function],
"queryByPlaceholderText": [Function],
"queryByRole": [Function],
"queryByTestId": [Function],
"queryByText": [Function],
"queryByTitle": [Function],
"rerender": [Function],
"unmount": [Function],
}
`;
Loading