diff --git a/src/components/Pressable/PressableWithDelayToggle.tsx b/src/components/Pressable/PressableWithDelayToggle.tsx index 6237ce3e4660..b0abe0a91c6c 100644 --- a/src/components/Pressable/PressableWithDelayToggle.tsx +++ b/src/components/Pressable/PressableWithDelayToggle.tsx @@ -105,7 +105,6 @@ function PressableWithDelayToggle( <> {inline && labelText} diff --git a/src/components/Tooltip/BaseTooltip.native.js b/src/components/Tooltip/BaseTooltip.native.js deleted file mode 100644 index fb2e3eba41c0..000000000000 --- a/src/components/Tooltip/BaseTooltip.native.js +++ /dev/null @@ -1,21 +0,0 @@ -import PropTypes from 'prop-types'; - -// We can't use the common component for the Tooltip as Web implementation uses DOM specific method to -// render the View which is not present on the Mobile. -const propTypes = { - /** Children to wrap with Tooltip. */ - children: PropTypes.node.isRequired, -}; - -/** - * @param {propTypes} props - * @returns {ReactNodeLike} - */ -function Tooltip(props) { - return props.children; -} - -Tooltip.propTypes = propTypes; -Tooltip.displayName = 'Tooltip'; - -export default Tooltip; diff --git a/src/components/Tooltip/BaseTooltip/index.native.tsx b/src/components/Tooltip/BaseTooltip/index.native.tsx new file mode 100644 index 000000000000..6d9eb24aaec5 --- /dev/null +++ b/src/components/Tooltip/BaseTooltip/index.native.tsx @@ -0,0 +1,12 @@ +import {forwardRef} from 'react'; +import ChildrenProps from '@src/types/utils/ChildrenProps'; + +// We can't use the common component for the Tooltip as Web implementation uses DOM specific method +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function Tooltip({children}: ChildrenProps, ref: unknown) { + return children; +} + +Tooltip.displayName = 'Tooltip'; + +export default forwardRef(Tooltip); diff --git a/src/components/Tooltip/BaseTooltip.js b/src/components/Tooltip/BaseTooltip/index.tsx similarity index 81% rename from src/components/Tooltip/BaseTooltip.js rename to src/components/Tooltip/BaseTooltip/index.tsx index 3eb905e7a3e5..e2a2c46e4546 100644 --- a/src/components/Tooltip/BaseTooltip.js +++ b/src/components/Tooltip/BaseTooltip/index.tsx @@ -1,16 +1,18 @@ import {BoundsObserver} from '@react-ng/bounds-observer'; -import Str from 'expensify-common/lib/str'; -import React, {memo, useCallback, useEffect, useRef, useState} from 'react'; +import React, {ForwardedRef, forwardRef, memo, useCallback, useEffect, useRef, useState} from 'react'; import {Animated} from 'react-native'; -import _ from 'underscore'; import Hoverable from '@components/Hoverable'; +import TooltipRenderedOnPageBody from '@components/Tooltip/TooltipRenderedOnPageBody'; +import TooltipSense from '@components/Tooltip/TooltipSense'; +import TooltipProps from '@components/Tooltip/types'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as tooltipPropTypes from './tooltipPropTypes'; -import TooltipRenderedOnPageBody from './TooltipRenderedOnPageBody'; -import TooltipSense from './TooltipSense'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import StringUtils from '@src/libs/StringUtils'; +import callOrReturn from '@src/types/utils/callOrReturn'; const hasHoverSupport = DeviceCapabilities.hasHoverSupport(); @@ -33,7 +35,7 @@ const hasHoverSupport = DeviceCapabilities.hasHoverSupport(); * @return {DOMRect} The chosen bounding box. */ -function chooseBoundingBox(target, clientX, clientY) { +function chooseBoundingBox(target: HTMLElement, clientX: number, clientY: number): DOMRect { const slop = 5; const bbs = target.getClientRects(); const clientXMin = clientX - slop; @@ -41,6 +43,7 @@ function chooseBoundingBox(target, clientX, clientY) { const clientYMin = clientY - slop; const clientYMax = clientY + slop; + // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let i = 0; i < bbs.length; i++) { const bb = bbs[i]; if (clientXMin <= bb.right && clientXMax >= bb.left && clientYMin <= bb.bottom && clientYMax >= bb.top) { @@ -52,7 +55,20 @@ function chooseBoundingBox(target, clientX, clientY) { return target.getBoundingClientRect(); } -function Tooltip({children, numberOfLines, maxWidth, text, renderTooltipContent, renderTooltipContentKey, shouldHandleScroll, shiftHorizontal, shiftVertical, tooltipRef}) { +function Tooltip( + { + children, + numberOfLines = CONST.TOOLTIP_MAX_LINES, + maxWidth = variables.sideBarWidth, + text = '', + renderTooltipContent, + renderTooltipContentKey = [], + shouldHandleScroll = false, + shiftHorizontal = 0, + shiftVertical = 0, + }: TooltipProps, + ref: ForwardedRef, +) { const {preferredLocale} = useLocalize(); const {windowWidth} = useWindowDimensions(); @@ -74,10 +90,13 @@ function Tooltip({children, numberOfLines, maxWidth, text, renderTooltipContent, const isAnimationCanceled = useRef(false); const prevText = usePrevious(text); - const target = useRef(null); + const target = useRef(null); const initialMousePosition = useRef({x: 0, y: 0}); - const updateTargetAndMousePosition = useCallback((e) => { + const updateTargetAndMousePosition = useCallback((e: MouseEvent) => { + if (!(e.currentTarget instanceof HTMLElement)) { + return; + } target.current = e.currentTarget; initialMousePosition.current = {x: e.clientX, y: e.clientY}; }, []); @@ -120,10 +139,8 @@ function Tooltip({children, numberOfLines, maxWidth, text, renderTooltipContent, /** * Update the tooltip bounding rectangle - * - * @param {Object} bounds - updated bounds */ - const updateBounds = (bounds) => { + const updateBounds = (bounds: DOMRect) => { if (bounds.width === 0) { setIsRendered(false); } @@ -169,7 +186,7 @@ function Tooltip({children, numberOfLines, maxWidth, text, renderTooltipContent, // Skip the tooltip and return the children if the text is empty, // we don't have a render function or the device does not support hovering - if ((_.isEmpty(text) && renderTooltipContent == null) || !hasHoverSupport) { + if ((StringUtils.isEmptyString(text) && renderTooltipContent == null) || !hasHoverSupport) { return children; } @@ -183,21 +200,21 @@ function Tooltip({children, numberOfLines, maxWidth, text, renderTooltipContent, yOffset={yOffset} targetWidth={wrapperWidth} targetHeight={wrapperHeight} - shiftHorizontal={Str.result(shiftHorizontal)} - shiftVertical={Str.result(shiftVertical)} + shiftHorizontal={callOrReturn(shiftHorizontal)} + shiftVertical={callOrReturn(shiftVertical)} text={text} maxWidth={maxWidth} numberOfLines={numberOfLines} renderTooltipContent={renderTooltipContent} // We pass a key, so whenever the content changes this component will completely remount with a fresh state. // This prevents flickering/moving while remaining performant. - key={[text, ...renderTooltipContentKey, preferredLocale]} + key={[text, ...renderTooltipContentKey, preferredLocale].join('-')} /> )} { - // eslint-disable-next-line - const tooltipNode = tooltipRef.current ? tooltipRef.current._childNode : null; - if ( - isOpen && - popover && - popover.anchorRef && - popover.anchorRef.current && - tooltipNode && - (tooltipNode.contains(popover.anchorRef.current) || tooltipNode === popover.anchorRef.current) - ) { - return true; - } - - return false; - }, [isOpen, popover]); - - if (!shouldRender || isPopoverRelatedToTooltipOpen) { - return children; - } - - return ( - - {children} - - ); -} - -PopoverAnchorTooltip.displayName = 'PopoverAnchorTooltip'; -PopoverAnchorTooltip.propTypes = propTypes; -PopoverAnchorTooltip.defaultProps = defaultProps; - -export default PopoverAnchorTooltip; diff --git a/src/components/Tooltip/PopoverAnchorTooltip.tsx b/src/components/Tooltip/PopoverAnchorTooltip.tsx new file mode 100644 index 000000000000..e524c55f6d2f --- /dev/null +++ b/src/components/Tooltip/PopoverAnchorTooltip.tsx @@ -0,0 +1,38 @@ +import {BoundsObserver} from '@react-ng/bounds-observer'; +import React, {useContext, useMemo, useRef} from 'react'; +import {PopoverContext} from '@components/PopoverProvider'; +import BaseTooltip from './BaseTooltip'; +import {TooltipExtendedProps} from './types'; + +function PopoverAnchorTooltip({shouldRender = true, children, ...props}: TooltipExtendedProps) { + const {isOpen, popover} = useContext(PopoverContext); + const tooltipRef = useRef(null); + + const isPopoverRelatedToTooltipOpen = useMemo(() => { + // eslint-disable-next-line @typescript-eslint/dot-notation + const tooltipNode = tooltipRef.current?.['_childNode'] ?? null; + if (isOpen && popover?.anchorRef?.current && tooltipNode && (tooltipNode.contains(popover.anchorRef.current) || tooltipNode === popover.anchorRef.current)) { + return true; + } + + return false; + }, [isOpen, popover]); + + if (!shouldRender || isPopoverRelatedToTooltipOpen) { + return children; + } + + return ( + + {children} + + ); +} + +PopoverAnchorTooltip.displayName = 'PopoverAnchorTooltip'; + +export default PopoverAnchorTooltip; diff --git a/src/components/Tooltip/TooltipRenderedOnPageBody.js b/src/components/Tooltip/TooltipRenderedOnPageBody.tsx similarity index 70% rename from src/components/Tooltip/TooltipRenderedOnPageBody.js rename to src/components/Tooltip/TooltipRenderedOnPageBody.tsx index 10e82cc94c30..2ed885c9d3d7 100644 --- a/src/components/Tooltip/TooltipRenderedOnPageBody.js +++ b/src/components/Tooltip/TooltipRenderedOnPageBody.tsx @@ -1,58 +1,40 @@ -import PropTypes from 'prop-types'; import React, {useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react'; import ReactDOM from 'react-dom'; import {Animated, View} from 'react-native'; import Text from '@components/Text'; import Log from '@libs/Log'; import useStyleUtils from '@styles/useStyleUtils'; +import textRef from '@src/types/utils/textRef'; +import viewRef from '@src/types/utils/viewRef'; +import TooltipProps from './types'; -const propTypes = { +type TooltipRenderedOnPageBodyProps = { /** Window width */ - windowWidth: PropTypes.number.isRequired, + windowWidth: number; /** Tooltip Animation value */ - // eslint-disable-next-line react/forbid-prop-types - animation: PropTypes.object.isRequired, + animation: Animated.Value; /** The distance between the left side of the wrapper view and the left side of the window */ - xOffset: PropTypes.number.isRequired, + xOffset: number; /** The distance between the top of the wrapper view and the top of the window */ - yOffset: PropTypes.number.isRequired, + yOffset: number; /** The width of the tooltip's target */ - targetWidth: PropTypes.number.isRequired, + targetWidth: number; /** The height of the tooltip's target */ - targetHeight: PropTypes.number.isRequired, + targetHeight: number; /** Any additional amount to manually adjust the horizontal position of the tooltip. A positive value shifts the tooltip to the right, and a negative value shifts it to the left. */ - shiftHorizontal: PropTypes.number, + shiftHorizontal?: number; /** Any additional amount to manually adjust the vertical position of the tooltip. A positive value shifts the tooltip down, and a negative value shifts it up. */ - shiftVertical: PropTypes.number, - - /** Text to be shown in the tooltip */ - text: PropTypes.string.isRequired, - - /** Maximum number of lines to show in tooltip */ - numberOfLines: PropTypes.number.isRequired, - - /** Number of pixels to set max-width on tooltip */ - maxWidth: PropTypes.number, - - /** Render custom content inside the tooltip. Note: This cannot be used together with the text props. */ - renderTooltipContent: PropTypes.func, -}; - -const defaultProps = { - shiftHorizontal: 0, - shiftVertical: 0, - renderTooltipContent: undefined, - maxWidth: 0, -}; + shiftVertical?: number; +} & Pick; // Props will change frequently. // On every tooltip hover, we update the position in state which will result in re-rendering. @@ -66,21 +48,21 @@ function TooltipRenderedOnPageBody({ yOffset, targetWidth, targetHeight, - maxWidth, - shiftHorizontal, - shiftVertical, + shiftHorizontal = 0, + shiftVertical = 0, text, numberOfLines, + maxWidth = 0, renderTooltipContent, -}) { +}: TooltipRenderedOnPageBodyProps) { // The width of tooltip's inner content. Has to be undefined in the beginning // as a width of 0 will cause the content to be rendered of a width of 0, // which prevents us from measuring it correctly. - const [contentMeasuredWidth, setContentMeasuredWidth] = useState(undefined); + const [contentMeasuredWidth, setContentMeasuredWidth] = useState(); // The height of tooltip's wrapper. - const [wrapperMeasuredHeight, setWrapperMeasuredHeight] = useState(undefined); - const contentRef = useRef(); - const rootWrapper = useRef(); + const [wrapperMeasuredHeight, setWrapperMeasuredHeight] = useState(); + const contentRef = useRef(null); + const rootWrapper = useRef(null); const StyleUtils = useStyleUtils(); @@ -94,8 +76,8 @@ function TooltipRenderedOnPageBody({ useLayoutEffect(() => { // Calculate the tooltip width and height before the browser repaints the screen to prevent flicker // because of the late update of the width and the height from onLayout. - setContentMeasuredWidth(contentRef.current.getBoundingClientRect().width); - setWrapperMeasuredHeight(rootWrapper.current.getBoundingClientRect().height); + setContentMeasuredWidth(contentRef.current?.getBoundingClientRect().width); + setWrapperMeasuredHeight(rootWrapper.current?.getBoundingClientRect().height); }, []); const {animationStyle, rootWrapperStyle, textStyle, pointerWrapperStyle, pointerStyle} = useMemo( @@ -111,15 +93,15 @@ function TooltipRenderedOnPageBody({ maxWidth, tooltipContentWidth: contentMeasuredWidth, tooltipWrapperHeight: wrapperMeasuredHeight, - shiftHorizontal, - shiftVertical, + manualShiftHorizontal: shiftHorizontal, + manualShiftVertical: shiftVertical, }), [StyleUtils, animation, windowWidth, xOffset, yOffset, targetWidth, targetHeight, maxWidth, contentMeasuredWidth, wrapperMeasuredHeight, shiftHorizontal, shiftVertical], ); let content; if (renderTooltipContent) { - content = {renderTooltipContent()}; + content = {renderTooltipContent()}; } else { content = ( {text} @@ -136,9 +118,15 @@ function TooltipRenderedOnPageBody({ ); } + const body = document.querySelector('body'); + + if (!body) { + return null; + } + return ReactDOM.createPortal( {content} @@ -146,12 +134,10 @@ function TooltipRenderedOnPageBody({ , - document.querySelector('body'), + body, ); } -TooltipRenderedOnPageBody.propTypes = propTypes; -TooltipRenderedOnPageBody.defaultProps = defaultProps; TooltipRenderedOnPageBody.displayName = 'TooltipRenderedOnPageBody'; export default React.memo(TooltipRenderedOnPageBody); diff --git a/src/components/Tooltip/TooltipSense.js b/src/components/Tooltip/TooltipSense.tsx similarity index 82% rename from src/components/Tooltip/TooltipSense.js rename to src/components/Tooltip/TooltipSense.tsx index 0e551e687cf3..bc16df3f8c7e 100644 --- a/src/components/Tooltip/TooltipSense.js +++ b/src/components/Tooltip/TooltipSense.tsx @@ -1,4 +1,4 @@ -import _ from 'underscore'; +import lodashDebounce from 'lodash/debounce'; import CONST from '@src/CONST'; let active = false; @@ -6,7 +6,7 @@ let active = false; /** * Debounced function to deactive the TooltipSense after a specific time */ -const debouncedDeactivate = _.debounce(() => { +const debouncedDeactivate = lodashDebounce(() => { active = false; }, CONST.TIMING.TOOLTIP_SENSE); diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js deleted file mode 100644 index c7c4428e19a3..000000000000 --- a/src/components/Tooltip/index.js +++ /dev/null @@ -1,37 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import BaseTooltip from './BaseTooltip'; -import {defaultProps as tooltipDefaultProps, propTypes as tooltipPropTypes} from './tooltipPropTypes'; - -const propTypes = { - ...tooltipPropTypes, - - /** Whether the actual Tooltip should be rendered. If false, it's just going to return the children */ - shouldRender: PropTypes.bool, -}; - -const defaultProps = { - ...tooltipDefaultProps, - shouldRender: true, -}; - -function Tooltip({shouldRender = true, children, ...props}) { - if (!shouldRender) { - return children; - } - - return ( - - {children} - - ); -} - -Tooltip.displayName = 'Tooltip'; -Tooltip.propTypes = propTypes; -Tooltip.defaultProps = defaultProps; - -export default Tooltip; diff --git a/src/components/Tooltip/index.tsx b/src/components/Tooltip/index.tsx new file mode 100644 index 000000000000..6d274b87badb --- /dev/null +++ b/src/components/Tooltip/index.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import BaseTooltip from './BaseTooltip'; +import {TooltipExtendedProps} from './types'; + +function Tooltip({shouldRender = true, children, ...props}: TooltipExtendedProps) { + if (!shouldRender) { + return children; + } + + return ( + + {children} + + ); +} + +Tooltip.displayName = 'Tooltip'; + +export default Tooltip; diff --git a/src/components/Tooltip/tooltipPropTypes.js b/src/components/Tooltip/tooltipPropTypes.js deleted file mode 100644 index 21df0df07f0d..000000000000 --- a/src/components/Tooltip/tooltipPropTypes.js +++ /dev/null @@ -1,52 +0,0 @@ -import PropTypes from 'prop-types'; -import refPropTypes from '@components/refPropTypes'; -import variables from '@styles/variables'; -import CONST from '@src/CONST'; - -const propTypes = { - /** The text to display in the tooltip. If text is ommitted, only children will be rendered. */ - text: PropTypes.string, - - /** Maximum number of lines to show in tooltip */ - numberOfLines: PropTypes.number, - - /** Children to wrap with Tooltip. */ - children: PropTypes.node.isRequired, - - /** Any additional amount to manually adjust the horizontal position of the tooltip. - A positive value shifts the tooltip to the right, and a negative value shifts it to the left. */ - shiftHorizontal: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - - /** Any additional amount to manually adjust the vertical position of the tooltip. - A positive value shifts the tooltip down, and a negative value shifts it up. */ - shiftVertical: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - - /** Number of pixels to set max-width on tooltip */ - maxWidth: PropTypes.number, - - /** Render custom content inside the tooltip. Note: This cannot be used together with the text props. */ - renderTooltipContent: PropTypes.func, - - /** Unique key of renderTooltipContent to rerender the tooltip when one of the key changes */ - renderTooltipContentKey: PropTypes.arrayOf(PropTypes.string), - - /** passes this down to Hoverable component to decide whether to handle the scroll behaviour to show hover once the scroll ends */ - shouldHandleScroll: PropTypes.bool, - - /** Reference to the tooltip container */ - tooltipRef: refPropTypes, -}; - -const defaultProps = { - shiftHorizontal: 0, - shiftVertical: 0, - text: '', - maxWidth: variables.sideBarWidth, - numberOfLines: CONST.TOOLTIP_MAX_LINES, - renderTooltipContent: undefined, - renderTooltipContentKey: [], - shouldHandleScroll: false, - tooltipRef: () => {}, -}; - -export {propTypes, defaultProps}; diff --git a/src/components/Tooltip/types.ts b/src/components/Tooltip/types.ts new file mode 100644 index 000000000000..df95c3ea5979 --- /dev/null +++ b/src/components/Tooltip/types.ts @@ -0,0 +1,40 @@ +import {ReactElement, ReactNode} from 'react'; + +type TooltipProps = { + /** The text to display in the tooltip. If text is ommitted, only children will be rendered. */ + text?: string; + + /** Maximum number of lines to show in tooltip */ + numberOfLines?: number; + + /** Children to wrap with Tooltip. */ + children: ReactElement; + + /** Any additional amount to manually adjust the horizontal position of the tooltip. + A positive value shifts the tooltip to the right, and a negative value shifts it to the left. */ + shiftHorizontal?: number | (() => number); + + /** Any additional amount to manually adjust the vertical position of the tooltip. + A positive value shifts the tooltip down, and a negative value shifts it up. */ + shiftVertical?: number | (() => number); + + /** Number of pixels to set max-width on tooltip */ + maxWidth?: number; + + /** Render custom content inside the tooltip. Note: This cannot be used together with the text props. */ + renderTooltipContent?: () => ReactNode; + + /** Unique key of renderTooltipContent to rerender the tooltip when one of the key changes */ + renderTooltipContentKey?: string[]; + + /** passes this down to Hoverable component to decide whether to handle the scroll behaviour to show hover once the scroll ends */ + shouldHandleScroll?: boolean; +}; + +type TooltipExtendedProps = TooltipProps & { + /** Whether the actual Tooltip should be rendered. If false, it's just going to return the children */ + shouldRender?: boolean; +}; + +export default TooltipProps; +export type {TooltipExtendedProps}; diff --git a/src/styles/utils/TooltipStyleUtils.ts b/src/styles/utils/TooltipStyleUtils.ts index aefd6ae54d2f..5c12a27ee981 100644 --- a/src/styles/utils/TooltipStyleUtils.ts +++ b/src/styles/utils/TooltipStyleUtils.ts @@ -1,4 +1,4 @@ -import {TextStyle, View, ViewStyle} from 'react-native'; +import {Animated, TextStyle, View, ViewStyle} from 'react-native'; import fontFamily from '@styles/fontFamily'; import {type ThemeStyles} from '@styles/styles'; import {type ThemeColors} from '@styles/themes/types'; @@ -102,18 +102,16 @@ type TooltipStyles = { }; type TooltipParams = { - tooltip: View | HTMLDivElement; - currentSize: number; + tooltip: View | HTMLDivElement | null; + currentSize: Animated.Value; windowWidth: number; xOffset: number; yOffset: number; tooltipTargetWidth: number; tooltipTargetHeight: number; maxWidth: number; - tooltipContentWidth: number; - tooltipWrapperHeight: number; - theme: ThemeColors; - styles: ThemeStyles; + tooltipContentWidth?: number; + tooltipWrapperHeight?: number; manualShiftHorizontal?: number; manualShiftVertical?: number; }; @@ -165,7 +163,7 @@ const createTooltipStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ const isTooltipSizeReady = tooltipWidth !== undefined && tooltipHeight !== undefined; // Set the scale to 1 to be able to measure the tooltip size correctly when it's not ready yet. - let scale = 1; + let scale = new Animated.Value(1); let shouldShowBelow = false; let horizontalShift = 0; let horizontalShiftPointer = 0; @@ -180,7 +178,7 @@ const createTooltipStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ // If either a tooltip will try to render within GUTTER_WIDTH logical pixels of the top of the screen, // Or the wrapped component is overlapping at top-center with another element // we'll display it beneath its wrapped component rather than above it as usual. - shouldShowBelow = yOffset - tooltipHeight < GUTTER_WIDTH || isOverlappingAtTop(tooltip, xOffset, yOffset, tooltipTargetWidth, tooltipTargetHeight); + shouldShowBelow = yOffset - tooltipHeight < GUTTER_WIDTH || !!(tooltip && isOverlappingAtTop(tooltip, xOffset, yOffset, tooltipTargetWidth, tooltipTargetHeight)); // When the tooltip size is ready, we can start animating the scale. scale = currentSize; diff --git a/src/types/utils/callOrReturn.ts b/src/types/utils/callOrReturn.ts new file mode 100644 index 000000000000..d51226544962 --- /dev/null +++ b/src/types/utils/callOrReturn.ts @@ -0,0 +1,9 @@ +function callOrReturn(value: TValue | (() => TValue)): TValue { + if (typeof value === 'function') { + return (value as () => TValue)(); + } + + return value; +} + +export default callOrReturn; diff --git a/src/types/utils/textRef.ts b/src/types/utils/textRef.ts new file mode 100644 index 000000000000..df0235f4ac0b --- /dev/null +++ b/src/types/utils/textRef.ts @@ -0,0 +1,5 @@ +import {Text} from 'react-native'; + +const textRef = (ref: React.RefObject) => ref as React.RefObject; + +export default textRef; diff --git a/src/types/utils/viewRef.ts b/src/types/utils/viewRef.ts new file mode 100644 index 000000000000..015d88cc5a8b --- /dev/null +++ b/src/types/utils/viewRef.ts @@ -0,0 +1,5 @@ +import {View} from 'react-native'; + +const viewRef = (ref: React.RefObject) => ref as React.RefObject; + +export default viewRef;