forked from lawnstarter/react-native-picker-select
-
Notifications
You must be signed in to change notification settings - Fork 10
Add PickerAvoidingView
and PickerStateProvider
for better iOS modal handling
#11
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
Closed
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React from 'react'; | ||
import { StyleSheet, View } from 'react-native'; | ||
import { PickerStateContext } from '../PickerStateProvider'; | ||
import { IOS_MODAL_ANIMATION_DURATION_MS, IOS_MODAL_HEIGHT } from '../constants'; | ||
|
||
function schedule(callback, timeout) { | ||
const handle = setTimeout(callback, timeout); | ||
return () => clearTimeout(handle); | ||
} | ||
|
||
/** | ||
* PickerAvoidingView is a React component that adjusts the view layout to avoid | ||
* being covered by an open iOS UIPickerView modal. It's meant to be similar to | ||
* the built-in KeyboardAvoidingView component, but specifically tailored for | ||
* iOS picker modals. | ||
* | ||
* In order for this component to work correctly, all the pickers and the | ||
* PickerAvoidingView should have a PickerStateProvider ancestor. | ||
* | ||
* @param {React.ReactNode} props.children - The child components that should be | ||
* protected from obstruction by the picker modal | ||
*/ | ||
export function PickerAvoidingView(props) { | ||
const context = React.useContext(PickerStateContext); | ||
const isPickerOpen = context && context.isPickerOpen; | ||
|
||
const [shouldAddSpace, setShouldAddSpace] = React.useState(false); | ||
|
||
React.useEffect(() => { | ||
if (isPickerOpen) { | ||
// Add a delay, as adding the padding before the modal fully expanded gives a visually unpleasant effect | ||
return schedule(() => { | ||
setShouldAddSpace(true); | ||
}, IOS_MODAL_ANIMATION_DURATION_MS); | ||
} else { | ||
setShouldAddSpace(false); | ||
} | ||
}, [isPickerOpen]); | ||
|
||
const style = props.enabled | ||
? StyleSheet.compose(props.style, { | ||
paddingBottom: shouldAddSpace ? IOS_MODAL_HEIGHT : 0, | ||
}) | ||
: props.style; | ||
|
||
return <View style={style}>{props.children}</View>; | ||
} | ||
|
||
PickerAvoidingView.defaultProps = { | ||
enabled: true, | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import React from 'react'; | ||
import { View } from 'react-native'; | ||
|
||
/** | ||
* As, currently, only on iOS the picker's modal resembles the software keyboard | ||
* in any way, the default implementation doesn't have any avoiding logic. | ||
* | ||
* @param {React.ReactNode} props.children - The child components to render | ||
* within the PickerAvoidingView. | ||
*/ | ||
export function PickerAvoidingView(props) { | ||
// eslint-disable-next-line no-unused-vars | ||
const { enabled, ...viewProps } = props; | ||
return <View {...viewProps}>{props.children}</View>; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import React from 'react'; | ||
|
||
/** | ||
* @typedef {Object} PickerStateData | ||
* @property {boolean} isPickerOpen - Indicates whether any picker is currently open | ||
* | ||
* PickerStateContext is a context that gives access to PickerStateData. | ||
*/ | ||
export const PickerStateContext = React.createContext(); | ||
|
||
/** | ||
* PickerStateProvider provides PickerStateContext and manages the necessary | ||
* state. | ||
* | ||
* This component should be used as a single top-level provider for all picker | ||
* instances in your application. | ||
*/ | ||
export function PickerStateProvider(props) { | ||
const [isPickerOpen, setIsPickerOpen] = React.useState(false); | ||
|
||
const context = { | ||
isPickerOpen, | ||
setIsPickerOpen, | ||
}; | ||
|
||
return ( | ||
<PickerStateContext.Provider value={context}> | ||
{props.children} | ||
</PickerStateContext.Provider> | ||
); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// Measuring the modal before rendering is not working reliably, so we need to hardcode the height | ||
// This height was tested thoroughly on several iPhone models (iPhone SE, from iPhone 8 to 14 Pro, and 14 Pro Max) | ||
export const IOS_MODAL_HEIGHT = 262; | ||
|
||
// An approximated duration of the modal opening | ||
export const IOS_MODAL_ANIMATION_DURATION_MS = 500; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we figure out a way of avoiding the setTimeout? Possible via a hook?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR was filed by mistake. It was meant for the upstream. I fixed that. Upstream PR: lawnstarter#507
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's continue this thread here