SafeAreaContext.tsx 3.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import * as React from 'react';
  2. import { StyleSheet, Dimensions } from 'react-native';
  3. import NativeSafeAreaView from './NativeSafeAreaView';
  4. import { EdgeInsets, InsetChangedEvent, Metrics, Rect } from './SafeArea.types';
  5. export const SafeAreaInsetsContext = React.createContext<EdgeInsets | null>(
  6. null,
  7. );
  8. export const SafeAreaFrameContext = React.createContext<Rect | null>(null);
  9. export interface SafeAreaViewProps {
  10. children?: React.ReactNode;
  11. initialMetrics?: Metrics | null;
  12. /**
  13. * @deprecated
  14. */
  15. initialSafeAreaInsets?: EdgeInsets | null;
  16. }
  17. export function SafeAreaProvider({
  18. children,
  19. initialMetrics,
  20. initialSafeAreaInsets,
  21. }: SafeAreaViewProps) {
  22. const parentInsets = useParentSafeAreaInsets();
  23. const parentFrame = useParentSafeAreaFrame();
  24. const [insets, setInsets] = React.useState<EdgeInsets | null>(
  25. initialMetrics?.insets ?? initialSafeAreaInsets ?? parentInsets ?? null,
  26. );
  27. const [frame, setFrame] = React.useState<Rect>(
  28. initialMetrics?.frame ??
  29. parentFrame ?? {
  30. // Backwards compat so we render anyway if we don't have frame.
  31. x: 0,
  32. y: 0,
  33. width: Dimensions.get('window').width,
  34. height: Dimensions.get('window').height,
  35. },
  36. );
  37. const onInsetsChange = React.useCallback((event: InsetChangedEvent) => {
  38. // Backwards compat with old native code that won't send frame.
  39. if (event.nativeEvent.frame != null) {
  40. setFrame(event.nativeEvent.frame);
  41. }
  42. setInsets(event.nativeEvent.insets);
  43. }, []);
  44. return (
  45. <NativeSafeAreaView style={styles.fill} onInsetsChange={onInsetsChange}>
  46. {insets != null ? (
  47. <SafeAreaFrameContext.Provider value={frame}>
  48. <SafeAreaInsetsContext.Provider value={insets}>
  49. {children}
  50. </SafeAreaInsetsContext.Provider>
  51. </SafeAreaFrameContext.Provider>
  52. ) : null}
  53. </NativeSafeAreaView>
  54. );
  55. }
  56. const styles = StyleSheet.create({
  57. fill: { flex: 1 },
  58. });
  59. function useParentSafeAreaInsets(): EdgeInsets | null {
  60. return React.useContext(SafeAreaInsetsContext);
  61. }
  62. function useParentSafeAreaFrame(): Rect | null {
  63. return React.useContext(SafeAreaFrameContext);
  64. }
  65. export function useSafeAreaInsets(): EdgeInsets {
  66. const safeArea = React.useContext(SafeAreaInsetsContext);
  67. if (safeArea == null) {
  68. throw new Error(
  69. 'No safe area insets value available. Make sure you are rendering `<SafeAreaProvider>` at the top of your app.',
  70. );
  71. }
  72. return safeArea;
  73. }
  74. export function useSafeAreaFrame(): Rect {
  75. const frame = React.useContext(SafeAreaFrameContext);
  76. if (frame == null) {
  77. throw new Error(
  78. 'No safe area frame value available. Make sure you are rendering `<SafeAreaProvider>` at the top of your app.',
  79. );
  80. }
  81. return frame;
  82. }
  83. export function withSafeAreaInsets<T>(
  84. WrappedComponent: React.ComponentType<T>,
  85. ) {
  86. return (props: T) => (
  87. <SafeAreaConsumer>
  88. {(insets) => <WrappedComponent {...props} insets={insets} />}
  89. </SafeAreaConsumer>
  90. );
  91. }
  92. /**
  93. * @deprecated
  94. */
  95. export function useSafeArea(): EdgeInsets {
  96. return useSafeAreaInsets();
  97. }
  98. /**
  99. * @deprecated
  100. */
  101. export function SafeAreaConsumer(
  102. props: React.ComponentProps<typeof SafeAreaInsetsContext.Consumer>,
  103. ) {
  104. return <SafeAreaInsetsContext.Consumer {...props} />;
  105. }
  106. /**
  107. * @deprecated
  108. */
  109. export const SafeAreaContext = SafeAreaInsetsContext;