diff --git a/package.json b/package.json index 948b52b..8574255 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "release": "release-it", "example": "yarn --cwd example", "pods": "cd example && pod-install --quiet", - "bootstrap": "yarn example && yarn && yarn pods" + "bootstrap": "yarn example && yarn && yarn pods", + "format": "prettier --write \"src/**/*.{js,jsx,json,md,ts,tsx}\"" }, "keywords": [ "react-native", diff --git a/src/Backdrop/Backdrop.tsx b/src/Backdrop/Backdrop.tsx index 43bc83c..7e1417f 100644 --- a/src/Backdrop/Backdrop.tsx +++ b/src/Backdrop/Backdrop.tsx @@ -6,7 +6,7 @@ import { Pressable, StyleSheet, } from 'react-native'; -import { ANIMATION_DURATION } from '../constants'; +// import { ANIMATION_DURATION } from '../constants'; import type { BackdropProps } from './Backdrop.d'; // On iOS, Modal orientations need to be manually specified @@ -34,7 +34,8 @@ export default function Backdrop({ if (visible) { setDelayedVisible(true); } else { - setTimeout(() => setDelayedVisible(false), ANIMATION_DURATION); + setDelayedVisible(false); + // setTimeout(() => setDelayedVisible(false), ANIMATION_DURATION); } }, [visible]); diff --git a/src/Caret.tsx b/src/Caret.tsx index 23a328f..657007a 100644 --- a/src/Caret.tsx +++ b/src/Caret.tsx @@ -14,50 +14,76 @@ export type CaretProps = { style?: ViewProps['style']; }; -export default ({ align, backgroundColor, position, style }: CaretProps) => { +export default function Caret({ + align, + backgroundColor, + position, + style, +}: CaretProps) { return ( ); -}; +} const styles = StyleSheet.create({ container: { - width: CARET_SIDE_SIZE, - height: CARET_SIDE_SIZE, - backgroundColor: POPOVER_BACKGROUND_COLOR, - transform: [{ rotate: '45deg' }], - borderRadius: BORDER_RADIUS, + // borderColor: "transparent", + borderBottomColor: 'transparent', + borderLeftColor: 'transparent', + borderRightColor: 'transparent', + borderTopColor: 'transparent', + borderWidth: CARET_SIDE_SIZE, + height: CARET_SIDE_SIZE * 2, + width: CARET_SIDE_SIZE * 2, }, - containerPositionTop: { - marginTop: (CARET_SIDE_SIZE / 2 + BORDER_RADIUS / 2) * -1, - marginBottom: CARET_SIDE_SIZE / 2 + BORDER_RADIUS / 2, + containerCenter: { + alignSelf: 'center', }, + containerLeft: {}, containerPositionBottom: { + bottom: CARET_SIDE_SIZE / 2, marginBottom: (CARET_SIDE_SIZE / 2 + BORDER_RADIUS / 2) * -1, marginTop: CARET_SIDE_SIZE / 2 + BORDER_RADIUS / 2, }, containerPositionLeft: { + left: CARET_SIDE_SIZE / 2, marginLeft: (CARET_SIDE_SIZE / 2 + BORDER_RADIUS / 2) * -1, marginRight: CARET_SIDE_SIZE / 2 + BORDER_RADIUS / 2, }, containerPositionRight: { - marginRight: (CARET_SIDE_SIZE / 2 + BORDER_RADIUS / 2) * -1, marginLeft: CARET_SIDE_SIZE / 2 + BORDER_RADIUS / 2, + marginRight: (CARET_SIDE_SIZE / 2 + BORDER_RADIUS / 2) * -1, + right: CARET_SIDE_SIZE / 2, }, - containerCenter: { - alignSelf: 'center', + containerPositionTop: { + marginBottom: CARET_SIDE_SIZE / 2 + BORDER_RADIUS / 2, + marginTop: (CARET_SIDE_SIZE / 2 + BORDER_RADIUS / 2) * -1, + top: CARET_SIDE_SIZE / 2, }, containerRight: { alignSelf: 'flex-end', diff --git a/src/Popable.tsx b/src/Popable.tsx index 75f214e..d2ed0ad 100644 --- a/src/Popable.tsx +++ b/src/Popable.tsx @@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useImperativeHandle, + useMemo, useRef, useState, } from 'react'; @@ -26,6 +27,7 @@ export type PopableProps = { backgroundColor?: PopoverProps['backgroundColor']; caret?: PopoverProps['caret']; caretPosition?: PopoverProps['caretPosition']; + caretStyle?: ViewProps['style']; children: any; content: PopoverProps['children']; numberOfLines?: PopoverProps['numberOfLines']; @@ -35,6 +37,10 @@ export type PopableProps = { style?: PopoverProps['style']; visible?: boolean; wrapperStyle?: ViewProps['style']; + manualPopupPosition?: { + left: number; + top: number; + }; }; const DEFAULT_LAYOUT = { @@ -53,6 +59,7 @@ const Popable = forwardRef(function Popable( children, caret, caretPosition, + caretStyle, content, numberOfLines, onAction, @@ -61,6 +68,10 @@ const Popable = forwardRef(function Popable( style, visible, wrapperStyle, + manualPopupPosition = { + left: 0, + top: 0, + }, }, ref ) { @@ -75,12 +86,19 @@ const Popable = forwardRef(function Popable( const [childrenLayout, setChildrenLayout] = useState(DEFAULT_LAYOUT); const [computedPosition, setComputedPosition] = useState(position); const isInteractive = typeof visible === 'undefined'; - const isPopoverVisible = isInteractive ? popoverVisible : visible; + const isPopoverVisible = useMemo(() => { + return isInteractive ? popoverVisible : visible; + }, [isInteractive, popoverVisible, visible]); const childrenRef = useRef(null); const popoverRef = useRef(null); useImperativeHandle(ref, () => ({ - show: () => setPopoverVisible(true), + show: () => { + popoverRef.current?.measureInWindow((_x, _y, _width, _height) => { + setPopoverPagePosition({ left: _x, top: _y }); + }); + setPopoverVisible(true); + }, hide: () => setPopoverVisible(false), })); @@ -103,15 +121,15 @@ const Popable = forwardRef(function Popable( ) { handlers.onPress = () => { if (!visible) { - popoverRef.current?.measure( - (_x, _y, _width, _height, pageX, pageY) => { - setPopoverPagePosition({ left: pageX, top: pageY }); - } - ); + popoverRef.current?.measureInWindow((_x, _y, _width, _height) => { + setPopoverPagePosition({ left: _x, top: _y }); + }); } onAction?.(!visible); - setPopoverVisible(!visible); + if (!ref) { + setPopoverVisible(!visible); + } }; } else { handlers.onLongPress = () => { @@ -197,6 +215,7 @@ const Popable = forwardRef(function Popable( backgroundColor, caret, caretPosition, + caretStyle, children: content, numberOfLines, position: computedPosition, @@ -228,8 +247,14 @@ const Popable = forwardRef(function Popable( { position: 'absolute', transform: [ - { translateX: popoverPagePosition.left }, - { translateY: popoverPagePosition.top }, + { + translateX: + popoverPagePosition.left + manualPopupPosition.left, + }, + { + translateY: + popoverPagePosition.top + manualPopupPosition.top, + }, ], }, style, @@ -242,7 +267,11 @@ const Popable = forwardRef(function Popable( { + setTimeout(() => { + handlePopoverLayout(); + }, 100); + }} visible={Platform.OS === 'web' ? isPopoverVisible : false} style={[ computedPosition === 'top' && styles.popoverTop, @@ -265,7 +294,11 @@ const Popable = forwardRef(function Popable( { + setTimeout(() => { + handleChildrenLayout(); + }, 100); + }} {...handlers} > {children} @@ -279,12 +312,12 @@ const styles = StyleSheet.create({ position: 'relative', zIndex: 1, }, - popoverTop: { - bottom: '100%', - }, popoverBottom: { top: '100%', }, + popoverTop: { + bottom: '100%', + }, }); export default Popable; diff --git a/src/Popover.tsx b/src/Popover.tsx index 6b5833f..9bac359 100644 --- a/src/Popover.tsx +++ b/src/Popover.tsx @@ -17,6 +17,7 @@ export type PopoverProps = { backgroundColor?: string; caret?: boolean; caretPosition?: 'left' | 'center' | 'right'; + caretStyle?: ViewProps['style']; children: string | React.ReactElement; forceInitialAnimation?: boolean; numberOfLines?: number; @@ -31,6 +32,7 @@ const Popover = React.forwardRef(function Popover( backgroundColor, caret: withCaret = true, caretPosition = 'center', + caretStyle = {}, children, forceInitialAnimation = false, numberOfLines, @@ -50,41 +52,38 @@ const Popover = React.forwardRef(function Popover( ) ).current; - useEffect( - () => { - let animation: Animated.CompositeAnimation | undefined; + useEffect(() => { + let animation: Animated.CompositeAnimation | undefined; - if (animated) { - if (visible && (!prevVisible.current || forceInitialAnimation)) { - animation = Animated[animationType](opacity, { - toValue: 1, - duration: ANIMATION_DURATION, - useNativeDriver: true, - }); - } else if (!visible && (prevVisible.current || forceInitialAnimation)) { - animation = Animated[animationType](opacity, { - toValue: 0, - duration: ANIMATION_DURATION, - useNativeDriver: true, - }); - } - - animation?.start(); + if (animated) { + if (visible && (!prevVisible.current || forceInitialAnimation)) { + animation = Animated[animationType](opacity, { + toValue: 1, + duration: ANIMATION_DURATION, + useNativeDriver: true, + }); + } else if (!visible && (prevVisible.current || forceInitialAnimation)) { + animation = Animated[animationType](opacity, { + toValue: 0, + duration: ANIMATION_DURATION, + useNativeDriver: true, + }); } - prevVisible.current = visible; + animation?.start(); + } - return () => animation?.stop(); - }, - [visible] // eslint-disable-line react-hooks/exhaustive-deps - ); + prevVisible.current = visible; + + return () => animation?.stop(); + }, [visible]); const caret = ( ); @@ -144,22 +143,22 @@ const Popover = React.forwardRef(function Popover( }); const styles = StyleSheet.create({ + caret: { + zIndex: 0, + }, container: { - width: POPOVER_WIDTH, overflow: 'hidden', + width: POPOVER_WIDTH, }, containerHorizontal: { flexDirection: 'row', }, content: { - flex: 1, - zIndex: 1, backgroundColor: POPOVER_BACKGROUND_COLOR, borderRadius: BORDER_RADIUS * 2, + flex: 1, overflow: 'hidden', - }, - contentTextOnly: { - padding: POPOVER_PADDING, + zIndex: 1, }, contentText: { color: POPOVER_FONT_COLOR, @@ -167,8 +166,8 @@ const styles = StyleSheet.create({ fontWeight: 'bold', textAlign: 'center', }, - caret: { - zIndex: 0, + contentTextOnly: { + padding: POPOVER_PADDING, }, });