Skip to content

Commit

Permalink
Merge pull request #1677 from gluestack/feat/popover-arrow-support
Browse files Browse the repository at this point in the history
Feat/popover arrow support
  • Loading branch information
ankit-tailor authored Jan 22, 2024
2 parents 3c95354 + 65330f9 commit 10242a6
Show file tree
Hide file tree
Showing 10 changed files with 480 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
AvatarFallbackText,
CircleIcon,
AddIcon,
PopoverArrow,
} from '@gluestack-ui/themed';

import { PhoneIcon, Clock3Icon, MailIcon } from 'lucide-react-native';
Expand Down Expand Up @@ -209,4 +210,5 @@ export {
Clock3Icon,
MailIcon,
useState,
PopoverArrow,
};
133 changes: 132 additions & 1 deletion example/storybook/src/ui/components/Overlay/Popover/index.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
PopoverBody,
PopoverFooter,
PopoverCloseButton,
PopoverArrow,
Pressable,
Text as PopoverText,
} from './Popover';
Expand Down Expand Up @@ -171,6 +172,126 @@ This is an illustration of a **Themed Popover** component with default configura
/>
</AppProvider>

### Popover with arrow

We have exported `PopoverArrow` component which can be used to render an arrow for the popover.

<AppProvider>
<CodePreview
showComponentRenderer={true}
showArgsController={true}
metaData={{
code: `
function App() {
const [isOpen, setIsOpen] = useState(false);
const handleOpen = () => {
setIsOpen(true);
};
const handleClose = () => {
setIsOpen(false);
};
return (
<Popover
isOpen={isOpen}
onClose={handleClose}
onOpen={handleOpen}
{...props}
trigger={(triggerProps) => {
return (
<Button
{...triggerProps}
>
<ButtonText>
Popover
</ButtonText>
</Button>
);
}}
>
<PopoverBackdrop/>
<PopoverContent>
<PopoverArrow />
<PopoverHeader>
<Heading size='lg'>Welcome!</Heading>
<PopoverCloseButton>
<Icon as={CloseIcon}/>
</PopoverCloseButton>
</PopoverHeader>
<PopoverBody>
<Text size='sm'>
Join the product tour and start creating your own checklist. Are you ready to jump in?
</Text>
</PopoverBody>
<PopoverFooter>
<Text size='xs' flex={1}>
Step 2 of 3
</Text>
<ButtonGroup space='md'>
<Button variant="outline" action='secondary' onPress={handleClose}>
<ButtonText>Back</ButtonText>
</Button>
<Button onPress={handleClose}>
<ButtonText>Next</ButtonText>
</Button>
</ButtonGroup>
</PopoverFooter>
</PopoverContent>
</Popover>
)
}
`,
transformCode: (code) => {
return transformedCode(code, 'function', 'App');
},
scope: {
Wrapper,
Center,
Text: PopoverText,
Heading,
Popover,
PopoverBackdrop,
PopoverContent,
PopoverHeader,
PopoverBody,
PopoverFooter,
PopoverCloseButton,
Button,
ButtonGroup,
ButtonText,
CloseIcon,
Icon,
useState,
PopoverArrow
},
argsType: {
placement: {
control: 'select',
options: [
'bottom',
'bottom left',
'bottom right',
'top',
'top left',
'top right',
'left',
'left bottom',
'left right',
'right',
'right bottom',
'right top',
],
default: 'bottom',
},
size: {
control: 'select',
options: ['xs', 'sm', 'md', 'lg', 'full'],
default: 'md',
},
},
}}
/>
</AppProvider>

<br />

## API Reference
Expand All @@ -180,7 +301,16 @@ This is an illustration of a **Themed Popover** component with default configura
To use this component in your project, include the following import statement in your file.

```jsx
import { Popover } from '@gluestack-ui/themed';
import {
Popover,
PopoverBackdrop,
PopoverContent,
PopoverArrow,
PopoverHeader,
PopoverCloseButton,
PopoverBody,
PopoverFooter,
} from '@gluestack-ui/themed';
```

### Anatomy
Expand All @@ -192,6 +322,7 @@ export default () => (
<Popover>
<PopoverBackdrop />
<PopoverContent>
<PopoverArrow />
<PopoverHeader>
<PopoverCloseButton />
</PopoverHeader>
Expand Down
26 changes: 25 additions & 1 deletion packages/config/src/theme/PopoverArrow.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
import { createStyle } from '@gluestack-style/react';

export const PopoverArrow = createStyle({});
export const PopoverArrow = createStyle({
'bg': '$backgroundLight50',
'zIndex': 1,
'position': 'absolute',
'overflow': 'hidden',
'_dark': {
bg: '$backgroundDark900',
},
'h': '$3.5',
'w': '$3.5',
':transition': {
type: 'spring',
damping: 18,
stiffness: 250,
mass: 0.9,
opacity: {
type: 'timing',
duration: 50,
delay: 50,
},
},
'props': {
softShadow: '3',
},
});
10 changes: 5 additions & 5 deletions packages/config/src/theme/PopoverBackdrop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@ export const PopoverBackdrop = createStyle({
opacity: 0,
},
':animate': {
opacity: 0.5,
opacity: 0.1,
},
':exit': {
opacity: 0,
},
':transition': {
type: 'spring',
damping: 18,
stiffness: 250,
stiffness: 450,
mass: 0.9,
opacity: {
type: 'timing',
duration: 250,
duration: 50,
delay: 50,
},
},
'position': 'absolute',
Expand All @@ -25,11 +27,9 @@ export const PopoverBackdrop = createStyle({
'right': 0,
'bottom': 0,
'bg': '$backgroundLight950',
// @ts-ignore
'_dark': {
bg: '$backgroundDark950',
},
// @ts-ignore
'_web': {
cursor: 'default',
},
Expand Down
14 changes: 3 additions & 11 deletions packages/config/src/theme/PopoverContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,17 @@ export const PopoverContent = createStyle({
'bg': '$backgroundLight50',
'rounded': '$lg',
'overflow': 'hidden',
':initial': {
opacity: 0,
},
':animate': {
opacity: 1,
},
':exit': {
opacity: 0,
},
':transition': {
type: 'spring',
damping: 18,
stiffness: 250,
mass: 0.9,
opacity: {
type: 'timing',
duration: 250,
duration: 50,
delay: 50,
},
},
// @ts-ignore
'_dark': {
bg: '$backgroundDark900',
},
Expand Down
54 changes: 37 additions & 17 deletions packages/react-native-aria/overlays/src/useOverlayPosition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,32 @@ type IMeasureResult = {
width: number;
};

const getArrowPropsWithStatusBarHeight = ({
top,
left,
}: {
top: number;
left: number;
}) => {
let topWithStatusBarHeight = top;

if (
typeof top !== 'undefined' &&
typeof APPROX_STATUSBAR_HEIGHT !== 'undefined'
) {
topWithStatusBarHeight = top + APPROX_STATUSBAR_HEIGHT;
} else {
topWithStatusBarHeight = undefined;
}

return {
style: {
left: left,
top: topWithStatusBarHeight,
},
};
};

export function useOverlayPosition(props: AriaPositionProps) {
let {
targetRef,
Expand Down Expand Up @@ -106,9 +132,8 @@ export function useOverlayPosition(props: AriaPositionProps) {
return;
}

const { height: windowHeight, width: windowWidth } = Dimensions.get(
'window'
);
const { height: windowHeight, width: windowWidth } =
Dimensions.get('window');

const positions = calculatePosition({
placement: translateRTL(placement),
Expand Down Expand Up @@ -138,6 +163,7 @@ export function useOverlayPosition(props: AriaPositionProps) {

React.useLayoutEffect(() => {
updatePosition();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
placement,
isOpen,
Expand All @@ -155,18 +181,18 @@ export function useOverlayPosition(props: AriaPositionProps) {
style.top = (position?.position?.top || 0) + (APPROX_STATUSBAR_HEIGHT || 0);
}

const arrowPropsWithStatusBarHeight = getArrowPropsWithStatusBarHeight({
left: position?.arrowOffsetLeft,
top: position?.arrowOffsetTop,
});

const returnProps = {
rendered,
overlayProps: {
style,
},
placement: position.placement,
arrowProps: {
style: {
left: position.arrowOffsetLeft,
top: (position?.arrowOffsetTop || 0) + (APPROX_STATUSBAR_HEIGHT || 0),
},
},
arrowProps: arrowPropsWithStatusBarHeight,
updatePosition,
};

Expand Down Expand Up @@ -438,14 +464,8 @@ function computePosition(
_containerOffsetWithBoundary: Offset,
_isContainerPositioned: boolean
) {
let {
placement,
crossPlacement,
axis,
crossAxis,
size,
crossSize,
} = placementInfo;
let { placement, crossPlacement, axis, crossAxis, size, crossSize } =
placementInfo;
let position: any = {};
//@ts-ignore
position[crossAxis] = childOffset[crossAxis];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { View } from 'react-native';
import { AnimatedView } from '@gluestack-style/animation-resolver';
import { styled } from '@gluestack-style/react';

export default styled(View, {}, {
export default styled(AnimatedView, {}, {
componentName: 'PopoverArrow',
} as const);
Loading

0 comments on commit 10242a6

Please sign in to comment.