mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-04-23 20:10:49 +08:00
feat: initial implementation of @react-navigation/elements
This commit is contained in:
@@ -8,9 +8,9 @@ import {
|
||||
StyleSheet,
|
||||
LayoutChangeEvent,
|
||||
} from 'react-native';
|
||||
import { PlatformPressable } from '@react-navigation/elements';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
import MaskedView from '../MaskedView';
|
||||
import TouchableItem from '../TouchableItem';
|
||||
import type { StackHeaderLeftButtonProps } from '../../types';
|
||||
|
||||
type Props = StackHeaderLeftButtonProps;
|
||||
@@ -151,7 +151,7 @@ export default function HeaderBackButton({
|
||||
const handlePress = () => onPress && requestAnimationFrame(onPress);
|
||||
|
||||
return (
|
||||
<TouchableItem
|
||||
<PlatformPressable
|
||||
disabled={disabled}
|
||||
accessible
|
||||
accessibilityRole="button"
|
||||
@@ -171,7 +171,7 @@ export default function HeaderBackButton({
|
||||
{renderBackImage()}
|
||||
{renderLabel()}
|
||||
</React.Fragment>
|
||||
</TouchableItem>
|
||||
</PlatformPressable>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { Dimensions, Platform } from 'react-native';
|
||||
import {
|
||||
SafeAreaProvider,
|
||||
SafeAreaInsetsContext,
|
||||
initialWindowMetrics,
|
||||
} from 'react-native-safe-area-context';
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const { width = 0, height = 0 } = Dimensions.get('window');
|
||||
|
||||
// To support SSR on web, we need to have empty insets for initial values
|
||||
// Otherwise there can be mismatch between SSR and client output
|
||||
// We also need to specify empty values to support tests environments
|
||||
export const initialMetrics =
|
||||
Platform.OS === 'web' || initialWindowMetrics == null
|
||||
? {
|
||||
frame: { x: 0, y: 0, width, height },
|
||||
insets: { top: 0, left: 0, right: 0, bottom: 0 },
|
||||
}
|
||||
: initialWindowMetrics;
|
||||
|
||||
export default function SafeAreaProviderCompat({ children }: Props) {
|
||||
return (
|
||||
<SafeAreaInsetsContext.Consumer>
|
||||
{(insets) => {
|
||||
if (insets) {
|
||||
// If we already have insets, don't wrap the stack in another safe area provider
|
||||
// This avoids an issue with updates at the cost of potentially incorrect values
|
||||
// https://github.com/react-navigation/react-navigation/issues/174
|
||||
return children;
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaProvider initialMetrics={initialMetrics}>
|
||||
{children}
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}}
|
||||
</SafeAreaInsetsContext.Consumer>
|
||||
);
|
||||
}
|
||||
@@ -11,13 +11,13 @@ import type {
|
||||
Route,
|
||||
StackNavigationState,
|
||||
} from '@react-navigation/native';
|
||||
import { SafeAreaProviderCompat } from '@react-navigation/elements';
|
||||
|
||||
import {
|
||||
MaybeScreenContainer,
|
||||
MaybeScreen,
|
||||
shouldUseActivityState,
|
||||
} from '../Screens';
|
||||
import { initialMetrics } from '../SafeAreaProviderCompat';
|
||||
import { getDefaultHeaderHeight } from '../Header/HeaderSegment';
|
||||
import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
||||
import CardContainer from './CardContainer';
|
||||
@@ -290,7 +290,7 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
routes: [],
|
||||
scenes: [],
|
||||
gestures: {},
|
||||
layout: initialMetrics.frame,
|
||||
layout: SafeAreaProviderCompat.initialMetrics.frame,
|
||||
descriptors: this.props.descriptors,
|
||||
// Used when card's header is null and mode is float to make transition
|
||||
// between screens with headers and those without headers smooth.
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Route,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/native';
|
||||
import { SafeAreaProviderCompat } from '@react-navigation/elements';
|
||||
|
||||
import { GestureHandlerRootView } from '../GestureHandler';
|
||||
import CardStack from './CardStack';
|
||||
@@ -18,7 +19,6 @@ import KeyboardManager from '../KeyboardManager';
|
||||
import HeaderContainer, {
|
||||
Props as HeaderContainerProps,
|
||||
} from '../Header/HeaderContainer';
|
||||
import SafeAreaProviderCompat from '../SafeAreaProviderCompat';
|
||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||
import type {
|
||||
StackNavigationHelpers,
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { Animated, Platform } from 'react-native';
|
||||
import { BaseButton, BaseButtonProperties } from 'react-native-gesture-handler';
|
||||
|
||||
const AnimatedBaseButton = Animated.createAnimatedComponent(BaseButton);
|
||||
|
||||
type Props = BaseButtonProperties & {
|
||||
pressOpacity: number;
|
||||
};
|
||||
|
||||
const useNativeDriver = Platform.OS !== 'web';
|
||||
|
||||
export default class TouchableItem extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
activeOpacity: 0.3,
|
||||
borderless: true,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
private opacity = new Animated.Value(1);
|
||||
|
||||
private handleActiveStateChange = (active: boolean) => {
|
||||
Animated.spring(this.opacity, {
|
||||
stiffness: 1000,
|
||||
damping: 500,
|
||||
mass: 3,
|
||||
overshootClamping: true,
|
||||
restDisplacementThreshold: 0.01,
|
||||
restSpeedThreshold: 0.01,
|
||||
toValue: active ? this.props.pressOpacity : 1,
|
||||
useNativeDriver,
|
||||
}).start();
|
||||
|
||||
this.props.onActiveStateChange?.(active);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, style, enabled, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
// @ts-expect-error: error seems like false positive
|
||||
<AnimatedBaseButton
|
||||
{...rest}
|
||||
onActiveStateChange={this.handleActiveStateChange}
|
||||
style={[style, enabled && { opacity: this.opacity }]}
|
||||
>
|
||||
{children}
|
||||
</AnimatedBaseButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* TouchableItem provides an abstraction on top of TouchableNativeFeedback and
|
||||
* TouchableOpacity to handle platform differences.
|
||||
*
|
||||
* On Android, you can pass the props of TouchableNativeFeedback.
|
||||
* On other platforms, you can pass the props of TouchableOpacity.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Platform,
|
||||
TouchableNativeFeedback,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ViewProps,
|
||||
} from 'react-native';
|
||||
|
||||
export type Props = ViewProps & {
|
||||
pressColor?: string;
|
||||
disabled?: boolean;
|
||||
borderless?: boolean;
|
||||
delayPressIn?: number;
|
||||
onPress?: () => void;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const ANDROID_VERSION_LOLLIPOP = 21;
|
||||
|
||||
export default function TouchableItem({
|
||||
borderless = false,
|
||||
pressColor = 'rgba(0, 0, 0, .32)',
|
||||
style,
|
||||
children,
|
||||
...rest
|
||||
}: Props) {
|
||||
/*
|
||||
* TouchableNativeFeedback.Ripple causes a crash on old Android versions,
|
||||
* therefore only enable it on Android Lollipop and above.
|
||||
*
|
||||
* All touchables on Android should have the ripple effect according to
|
||||
* platform design guidelines.
|
||||
* We need to pass the background prop to specify a borderless ripple effect.
|
||||
*/
|
||||
if (
|
||||
Platform.OS === 'android' &&
|
||||
Platform.Version >= ANDROID_VERSION_LOLLIPOP
|
||||
) {
|
||||
return (
|
||||
<TouchableNativeFeedback
|
||||
{...rest}
|
||||
useForeground={TouchableNativeFeedback.canUseNativeForeground()}
|
||||
background={TouchableNativeFeedback.Ripple(pressColor, borderless)}
|
||||
>
|
||||
<View style={style}>{React.Children.only(children)}</View>
|
||||
</TouchableNativeFeedback>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<TouchableOpacity style={style} {...rest}>
|
||||
{children}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
"references": [
|
||||
{ "path": "../core" },
|
||||
{ "path": "../routers" },
|
||||
{ "path": "../native" }
|
||||
{ "path": "../native" },
|
||||
{ "path": "../elements" }
|
||||
],
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib/typescript"
|
||||
|
||||
Reference in New Issue
Block a user