Skip to content

fix: Content Manager, Click outside the filter popover does not close it #982

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 18, 2023
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
7 changes: 4 additions & 3 deletions docs/stories/Popover.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ The popover can be centered relatively to their triggering element.
<Typography>Open popover</Typography>
</button>
{visible && (
<Popover centered source={buttonRef} spacing={16}>
<Popover centered source={buttonRef} spacing={16} onDismiss={() => setVisible(false)}>
<ul style={{ width: '200px' }}>
{Array(15)
.fill(null)
Expand Down Expand Up @@ -63,7 +63,7 @@ The popover can be centered relatively to their triggering element.
<Typography>Open popover</Typography>
</button>
{visible && (
<Popover source={buttonRef} fullWidth spacing={16}>
<Popover source={buttonRef} fullWidth spacing={16} onDismiss={() => setVisible(false)}>
<ul style={{ width: '200px' }}>
{Array(15)
.fill(null)
Expand Down Expand Up @@ -103,6 +103,7 @@ You can define an action to be performed when the end of the popover is reached.
id="on-reach-end"
intersectionId="test-123"
onReachEnd={() => setItems(Array(15).fill(null))}
onDismiss={() => setVisible(false)}
>
<ul style={{ width: '200px' }} id="list" tabIndex={-1}>
{items.map((_, index) => (
Expand Down Expand Up @@ -136,7 +137,7 @@ The popover will overflow over other elements.
<Typography>Open popover</Typography>
</button>
{visible && (
<Popover centered source={buttonRef} spacing={16}>
<Popover centered source={buttonRef} spacing={16} onDismiss={() => setVisible(false)}>
<ul style={{ width: '200px' }}>
{Array(15)
.fill(null)
Expand Down
5 changes: 4 additions & 1 deletion packages/strapi-design-system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
"@internationalized/number": "^3.2.0",
"@radix-ui/react-dismissable-layer": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.4",
"@radix-ui/react-focus-scope": "1.0.2",
"@strapi/ui-primitives": "^1.7.6",
"@uiw/react-codemirror": "^4.19.16",
"aria-hidden": "^1.2.3",
"compute-scroll-into-view": "^3.0.3",
"prop-types": "^15.8.1"
"prop-types": "^15.8.1",
"react-remove-scroll": "^2.5.5"
},
"devDependencies": {
"@playwright/test": "1.33.0",
Expand Down
135 changes: 115 additions & 20 deletions packages/strapi-design-system/src/Popover/Popover.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import * as React from 'react';

import { useFloating, flip, shift, offset, autoUpdate, Placement } from '@floating-ui/react-dom';
import { FocusScope } from '@radix-ui/react-focus-scope';
import { useCallbackRef } from '@strapi/ui-primitives';
import { hideOthers } from 'aria-hidden';
import { RemoveScroll } from 'react-remove-scroll';
import styled from 'styled-components';

import { Box, BoxProps } from '../Box';
import { DismissibleLayer, DismissibleLayerProps } from '../DismissibleLayer';
import { stripReactIdOfColon } from '../helpers/strings';
import { useComposedRefs } from '../hooks/useComposeRefs';
import { useId } from '../hooks/useId';
import { useIntersection } from '../hooks/useIntersection';
import { Portal } from '../Portal';
Expand All @@ -31,7 +37,9 @@ const PopoverWrapper = styled(Box)`
background: ${({ theme }) => theme.colors.neutral0};
`;

interface ContentProps extends BoxProps<HTMLDivElement> {
interface ContentProps
extends BoxProps<HTMLDivElement>,
Pick<DismissibleLayerProps, 'onEscapeKeyDown' | 'onPointerDownOutside' | 'onDismiss'> {
source: React.MutableRefObject<HTMLElement>;
placement?: Placement;
fullWidth?: boolean;
Expand All @@ -46,8 +54,12 @@ export const Content = ({
fullWidth = false,
placement = 'bottom-start',
centered = false,
onEscapeKeyDown,
onPointerDownOutside,
onDismiss,
...props
}: ContentProps) => {
const [content, setContent] = React.useState<HTMLDivElement | null>(null);
const [width, setWidth] = React.useState<number | undefined>(undefined);
const { x, y, reference, floating, strategy } = useFloating({
strategy: 'fixed',
Expand All @@ -72,22 +84,74 @@ export const Content = ({
}
}, [fullWidth, source]);

// aria-hide everything except the content (better supported equivalent to setting aria-modal)
React.useEffect(() => {
if (content) return hideOthers(content);
}, [content]);

const handleDismiss = useCallbackRef(onDismiss);

React.useEffect(() => {
const close = () => {
handleDismiss();
};
window.addEventListener('blur', close);
window.addEventListener('resize', close);

return () => {
window.removeEventListener('blur', close);
window.removeEventListener('resize', close);
};
}, [handleDismiss]);

const composedRefs = useComposedRefs<HTMLDivElement>((node) => setContent(node), floating);

return (
<PopoverWrapper
ref={floating}
style={{
left: x,
top: y,
position: strategy,
width: width || undefined,
}}
hasRadius
background="neutral0"
padding={1}
{...props}
>
{children}
</PopoverWrapper>
<RemoveScroll allowPinchZoom>
<FocusScope
asChild
// we make sure we're not trapping once it's been closed
// (closed !== unmounted when animating out)
trapped
onMountAutoFocus={(event) => {
// we prevent open autofocus because we manually focus the selected item
event.preventDefault();
}}
onUnmountAutoFocus={(event) => {
source.current?.focus({ preventScroll: true });
event.preventDefault();
}}
>
<DismissibleLayer
asChild
disableOutsidePointerEvents
onEscapeKeyDown={onEscapeKeyDown}
onPointerDownOutside={onPointerDownOutside}
// When focus is trapped, a focusout event may still happen.
// We make sure we don't trigger our `onDismiss` in such case.
onFocusOutside={(event) => {
event.preventDefault();
}}
onDismiss={onDismiss}
>
<PopoverWrapper
ref={composedRefs}
style={{
left: x,
top: y,
position: strategy,
width: width || undefined,
}}
hasRadius
background="neutral0"
padding={1}
{...props}
>
{children}
</PopoverWrapper>
</DismissibleLayer>
</FocusScope>
</RemoveScroll>
);
};

Expand Down Expand Up @@ -137,12 +201,43 @@ const PopoverScrollable = styled(Box)`
}
`;

type PopoverProps = ScrollingProps & Pick<ContentProps, 'source' | 'spacing' | 'fullWidth' | 'placement' | 'centered'>;

export const Popover = ({ children, source, spacing, fullWidth, placement, centered, ...restProps }: PopoverProps) => {
type PopoverProps = ScrollingProps &
Pick<
ContentProps,
| 'source'
| 'spacing'
| 'fullWidth'
| 'placement'
| 'centered'
| 'onEscapeKeyDown'
| 'onPointerDownOutside'
| 'onDismiss'
>;

export const Popover = ({
children,
source,
spacing,
fullWidth,
placement,
centered,
onEscapeKeyDown,
onPointerDownOutside,
onDismiss,
...restProps
}: PopoverProps) => {
return (
<Portal>
<Content source={source} spacing={spacing} fullWidth={fullWidth} placement={placement} centered={centered}>
<Content
source={source}
spacing={spacing}
fullWidth={fullWidth}
placement={placement}
centered={centered}
onEscapeKeyDown={onEscapeKeyDown}
onPointerDownOutside={onPointerDownOutside}
onDismiss={onDismiss}
>
<Scrolling {...restProps}>{children}</Scrolling>
</Content>
</Portal>
Expand Down
Loading