Issue
I want to use breakdown blocks, they don't necessarily have a text field, but they can open some content and I want to have an effect like onBlur
in TextInput
, usually I could achieve this effect (on the web) with the useOnClickOutside
custom hook, but I don't know how is it possible to achieve this effect or something similar in react-native, can anyone advise on this?
I need this functionality to collapse this block if the user clicks elsewhere.
Solution
I found a solution to my problem, which will help me create popovers of any style without being tied to any library, I found my solution, I studied the work of the react-native-element-dropdown code, I think that since this library is supplied under the MIT license, I can explain the rethinking of them code
The first thing is to create a hook that will determine the placement of the popover:
import { useCallback, useState } from 'react'
import { Dimensions, I18nManager, StatusBar, View } from 'react-native'
export type PositionType = {
width: number
top: number
bottom: number
left: number
height: number
}
const statusBarHeight: number = StatusBar.currentHeight || 0
const usePosition = (ref: React.RefObject<View>) => {
const [position, setPosition] = useState<PositionType>({ width: 0, top: 0, bottom: 0, left: 0, height: 0 })
const { width: W, height: H } = Dimensions.get('window')
const onLayout = useCallback(() => {
if (ref?.current) {
ref.current.measureInWindow((pageX, pageY, width, height) => {
const top = height + pageY
const bottom = H - top + height
const left = I18nManager.isRTL ? W - width - pageX : pageX
setPosition({
width: Math.floor(width),
top: Math.floor(top + statusBarHeight),
bottom: Math.floor(bottom - statusBarHeight),
left: Math.floor(left),
height: Math.floor(height),
})
})
}
}, [H, W])
return { position, onLayout }
}
export default usePosition
The approach through the hook is necessary to correctly work out the miscalculation, since if you transfer this layout to the component of the popover itself, for example, the useEffect
hook, it will not be the correct position, since the layout is calculated after the execution of all internal useEffects
(Although I may be wrong)
After that, we create a component that will be a wrapper for the popover and interact with the hook:
import React from 'react'
import { Modal, View, ViewProps } from 'react-native'
import { TouchableWithoutFeedback } from 'react-native'
import { PositionType } from './usePosition'
export type DropdownProps = React.PropsWithChildren & {
position: PositionType
isOpen: boolean
onClose: () => void
isWidthFromPosition?: boolean
style?: ViewProps['style']
}
const Dropdown: React.FC<DropdownProps> = ({
position,
isOpen,
onClose,
children,
style,
isWidthFromPosition = false,
}): JSX.Element => {
const { width, top, left } = position
return (
<Modal
transparent
statusBarTranslucent
visible={isOpen}
supportedOrientations={['landscape', 'portrait']}
onRequestClose={onClose}
>
<TouchableWithoutFeedback onPress={onClose}>
<View style={{ flex: 1 }}>
<View
style={[
{
position: 'absolute',
left,
top,
},
// If you need the width to be the same as the element to which the popover is attached
isWidthFromPosition ? { width } : undefined,
style,
]}
>
{children}
</View>
</View>
</TouchableWithoutFeedback>
</Modal>
)
}
export default Dropdown
and all this now you can display a popover with the `useOnClickOutside' effect to close it when clicking outside the popover itself, YES THERE IS A NUANCE - since this is an invisible modal window, when you click on some other interaction effect, the modal window will first be closed, and only then will it be possible click on another element, but for me it is acceptable and I think it is an acceptable logic for such a function, maybe in the future it will be possible to invent something better.
Used:
...
const YourComponent = () => {
const [isOpen, setIsOpen] = useState(false)
const ref = useRef<View>(null)
const { position, onLayout } = usePosition(ref)
const handleFocus = () => setIsOpen(true)
const handleBlur = () => setIsOpen(false)
return (
<View ref={ref} onLayout={onLayout}>
<TouchableWithoutFeedback onPress={handleFocus}>
<View style={{ width: 40, height: 40, backgroudnColor: 'red' }} />
</TouchableWithoutFeedback>
<Dropdown position={position} isOpen={isFocus} onClose={handleBlur}>
<TouchableWithoutFeedback onPress={() => console.log('Hi, I am POPOVER')}>
<View style={{ backgroundColor: 'pink' }}>
<Text>Hi, I am POPOVER</Text>
</View>
</TouchableWithoutFeedback>
</Dropdown>
<View>
)
}
...
This is approximately the result already with my use:
Answered By - UKRman
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.