diff --git a/packages/app/package.json b/packages/app/package.json index cb841e09d51..7710776a8e0 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -35,11 +35,6 @@ "@react-native-firebase/perf": "^14", "@react-native-google-signin/google-signin": "^9.1.0", "@react-native-segmented-control/segmented-control": "^2.4.1", - "@react-navigation/bottom-tabs": "^6.5.8", - "@react-navigation/drawer": "^6.6.3", - "@react-navigation/native": "6.1.7", - "@react-navigation/native-stack": "^6.9.13", - "@react-navigation/stack": "^6.3.17", "@tamagui/animations-moti": "1.74.13", "@tamagui/babel-plugin": "1.74.13", "@tamagui/config": "1.74.13", diff --git a/packages/components/src/DelayedFreeze/index.tsx b/packages/components/src/DelayedFreeze/index.tsx index f9e9f504b4c..59458428475 100644 --- a/packages/components/src/DelayedFreeze/index.tsx +++ b/packages/components/src/DelayedFreeze/index.tsx @@ -4,7 +4,7 @@ import { useState } from 'react'; import { Freeze } from 'react-freeze'; interface FreezeWrapperProps { - freeze: boolean; + freeze: boolean | undefined; children: ReactNode; placeholder?: ReactNode; } @@ -18,7 +18,7 @@ function DelayedFreeze({ freeze, children, placeholder = null, -}: FreezeWrapperProps) { +}: FreezeWrapperProps): JSX.Element { // flag used for determining whether freeze should be enabled const [freezeState, setFreezeState] = useState(false); @@ -26,7 +26,7 @@ function DelayedFreeze({ // setImmediate is executed at the end of the JS execution block. // Used here for changing the state right after the render. setImmediate(() => { - setFreezeState(freeze); + setFreezeState(!!freeze); }); } diff --git a/packages/components/src/ModalContainer/index.tsx b/packages/components/src/ModalContainer/index.tsx new file mode 100644 index 00000000000..b0842e3016c --- /dev/null +++ b/packages/components/src/ModalContainer/index.tsx @@ -0,0 +1,133 @@ +import { Button } from '../Button'; +import { Checkbox } from '../Checkbox'; +import { Stack, XStack } from '../Stack'; + +import type { GetProps } from 'tamagui'; + +type ModalButtonGroupProps = { + onConfirm?: () => void | Promise; + onCancel?: () => void; + confirmButtonProps?: GetProps; + cancelButtonProps?: GetProps; + confirmButtonTextProps?: GetProps; + cancelButtonTextProps?: GetProps; +}; + +function ModalButtonGroup({ + onCancel, + onConfirm, + confirmButtonProps, + cancelButtonProps, + confirmButtonTextProps, + cancelButtonTextProps, +}: ModalButtonGroupProps) { + return ( + + {(!!cancelButtonProps || !!onCancel) && ( + + )} + {(!!confirmButtonProps || !!onConfirm) && ( + + )} + + ); +} + +type ModalContainerProps = { + children: React.ReactNode; + checkboxProps?: GetProps; +} & ModalButtonGroupProps; + +export function ModalContainer({ + children, + checkboxProps, + onCancel, + onConfirm, + confirmButtonProps, + cancelButtonProps, + confirmButtonTextProps, + cancelButtonTextProps, +}: ModalContainerProps) { + return ( + + {children} + + + {!!checkboxProps && ( + + + + )} + + + + ); +} diff --git a/packages/components/src/Navigation/Header/HeaderSearchBar.tsx b/packages/components/src/Navigation/Header/HeaderSearchBar.tsx new file mode 100644 index 00000000000..bb375b28ff2 --- /dev/null +++ b/packages/components/src/Navigation/Header/HeaderSearchBar.tsx @@ -0,0 +1,93 @@ +import { useCallback } from 'react'; + +import { useMedia } from 'tamagui'; + +import { SearchBar } from '../../SearchBar'; + +import type { + NativeSyntheticEvent, + TargetedEvent, + TextInputFocusEventData, + TextInputSubmitEditingEventData, +} from 'react-native'; + +type HeaderSearchBarProps = { + height?: string; + /** + * A callback that gets called when search bar has lost focus + */ + onBlur?: (e: NativeSyntheticEvent) => void; + /** + * A callback that gets called when the text changes. It receives the current text value of the search bar. + */ + onChangeText?: (e: NativeSyntheticEvent) => void; + /** + * A callback that gets called when search bar has received focus + */ + onFocus?: (e: NativeSyntheticEvent) => void; + /** + * A callback that gets called when the search button is pressed. It receives the current text value of the search bar. + */ + onSearchButtonPress?: ( + e: NativeSyntheticEvent, + ) => void; + /** + * Text displayed when search field is empty + */ + placeholder?: string; +}; + +function HeaderSearchBar({ + onBlur, + onFocus, + onChangeText, + onSearchButtonPress, + placeholder, +}: HeaderSearchBarProps) { + const media = useMedia(); + + const handleChangeCallback = useCallback( + (value: string) => { + onChangeText?.({ + nativeEvent: { + text: value, + }, + } as NativeSyntheticEvent); + }, + [onChangeText], + ); + + const onBlurCallback = useCallback( + (e: NativeSyntheticEvent) => { + onBlur?.(e); + }, + [onBlur], + ); + + const onFocusCallback = useCallback( + (e: NativeSyntheticEvent) => { + onFocus?.(e); // Stub event object + }, + [onFocus], + ); + + const onSubmitEditingCallback = useCallback( + (e: NativeSyntheticEvent) => { + onSearchButtonPress?.(e as NativeSyntheticEvent); + }, + [onSearchButtonPress], + ); + + return ( + + ); +} + +export default HeaderSearchBar; diff --git a/packages/components/src/Navigation/Header/HeaderView.tsx b/packages/components/src/Navigation/Header/HeaderView.tsx index cd7b689695e..82658f0be78 100644 --- a/packages/components/src/Navigation/Header/HeaderView.tsx +++ b/packages/components/src/Navigation/Header/HeaderView.tsx @@ -5,11 +5,11 @@ import * as React from 'react'; import { Header } from '@react-navigation/elements'; import { get } from 'lodash'; import { StyleSheet } from 'react-native'; -import { Input } from 'tamagui'; import { Stack, useThemeValue } from '../../index'; import HeaderButtonBack from './HeaderButtonBack'; +import HeaderSearchBar from './HeaderSearchBar'; import type { StackHeaderProps } from '../ScreenProps'; import type { OneKeyStackHeaderProps } from './HeaderScreenOptions'; @@ -145,19 +145,15 @@ function HeaderView({ py: isModelScreen ? '$0' : '$3.5', pb: isModelScreen ? '$4' : '$0', width: isModelScreen ? '100%' : '$60', + alignItems: isModelScreen ? 'flex-start' : 'center', }} > - {/* Demo SearchBar */} - )} diff --git a/packages/components/src/Navigation/Modal/createModalNavigator.tsx b/packages/components/src/Navigation/Modal/createModalNavigator.tsx index 03f7fbf46b8..5c2360cc286 100644 --- a/packages/components/src/Navigation/Modal/createModalNavigator.tsx +++ b/packages/components/src/Navigation/Modal/createModalNavigator.tsx @@ -4,7 +4,7 @@ import { StackRouter, createNavigatorFactory, useNavigationBuilder, -} from '@react-navigation/native'; +} from '@react-navigation/core'; import ModalStack from './ModalStack'; diff --git a/packages/components/src/Navigation/Modal/types.tsx b/packages/components/src/Navigation/Modal/types.tsx index 2918a4b5aec..a22b8f9f5ce 100644 --- a/packages/components/src/Navigation/Modal/types.tsx +++ b/packages/components/src/Navigation/Modal/types.tsx @@ -19,6 +19,7 @@ export type Scene = { export type ModalNavigationConfig = NonNullable; export type ModalNavigationOptions = StackNavigationOptions & { + allowDisableClose?: boolean; disableClose?: boolean; }; diff --git a/packages/components/src/Navigation/Navigator/ModalFlowNavigator.tsx b/packages/components/src/Navigation/Navigator/ModalFlowNavigator.tsx index 2d11639af42..664d7cbbaf1 100644 --- a/packages/components/src/Navigation/Navigator/ModalFlowNavigator.tsx +++ b/packages/components/src/Navigation/Navigator/ModalFlowNavigator.tsx @@ -1,5 +1,7 @@ import { memo, useCallback } from 'react'; +import { useIntl } from 'react-intl'; + import { useThemeValue } from '../../Provider/hooks/useThemeValue'; import { makeModalStackNavigatorOptions } from '../GlobalScreenOptions'; import createModalNavigator from '../Modal/createModalNavigator'; @@ -7,6 +9,7 @@ import { createStackNavigator } from '../StackNavigator'; import { hasStackNavigatorModal } from './CommonConfig.ts'; +import type { LocaleIds } from '../../locale'; import type { ModalNavigationOptions } from '../ScreenProps'; import type { CommonNavigatorConfig } from './types'; import type { ParamListBase } from '@react-navigation/routers'; @@ -15,7 +18,8 @@ export interface ModalFlowNavigatorConfig< RouteName extends string, P extends ParamListBase, > extends CommonNavigatorConfig { - translationId: string; + translationId: LocaleIds; + allowDisableClose?: boolean; disableClose?: boolean; } @@ -34,6 +38,7 @@ function ModalFlowNavigator({ config, }: ModalFlowNavigatorProps) { const [bgColor, titleColor] = useThemeValue(['bg', 'text']); + const intl = useIntl(); const makeScreenOptions = useCallback( (navInfo) => ({ @@ -50,13 +55,21 @@ function ModalFlowNavigator({ // @ts-expect-error {config.map( - ({ name, component, options, translationId, disableClose }) => { + ({ + name, + component, + options, + translationId, + allowDisableClose, + disableClose, + }) => { const customOptions: ModalNavigationOptions = { ...options, + allowDisableClose, disableClose, - // Fixes: iOS config static configuration disableClose software can not unlock the problem - presentation: disableClose ? 'modal' : undefined, - title: translationId, + title: intl.formatMessage({ + id: translationId, + }), }; return ( diff --git a/packages/components/src/Navigation/Navigator/RootModalNavigator.tsx b/packages/components/src/Navigation/Navigator/RootModalNavigator.tsx index a51ea798673..71b7c5d11ef 100644 --- a/packages/components/src/Navigation/Navigator/RootModalNavigator.tsx +++ b/packages/components/src/Navigation/Navigator/RootModalNavigator.tsx @@ -1,7 +1,11 @@ import { useMemo } from 'react'; import { ThemeProvider } from '@react-navigation/native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import PlatformEnv from '@onekeyhq/shared/src/platformEnv'; + +import { Stack } from '../../Stack'; import { makeRootModalStackOptions } from '../GlobalScreenOptions'; import { createStackNavigator } from '../StackNavigator'; @@ -35,16 +39,27 @@ export function RootModalNavigator({ })), [config], ); + const insets = useSafeAreaInsets(); return ( - - - {modalComponents.map(({ name, children }) => ( - - {children} - - ))} - - + + + + {modalComponents.map(({ name, children }) => ( + + {children} + + ))} + + + ); } diff --git a/packages/components/src/Navigation/Navigator/RootStackNavigator.tsx b/packages/components/src/Navigation/Navigator/RootStackNavigator.tsx index d1e3317dffd..a92d4dec490 100644 --- a/packages/components/src/Navigation/Navigator/RootStackNavigator.tsx +++ b/packages/components/src/Navigation/Navigator/RootStackNavigator.tsx @@ -20,6 +20,7 @@ export interface RootStackNavigatorConfig< > extends CommonNavigatorConfig { initialRoute?: boolean; type?: RootStackType; + disable?: boolean; } interface RootStackNavigatorProps< @@ -63,26 +64,31 @@ export function RootStackNavigator< const renderedScreens = useMemo( () => - config.map(({ name, component, type, options }) => ( - - )), + config + .filter(({ disable }) => !disable) + .map(({ name, component, type, options }) => ( + + )), [config, getOptionsWithType], ); - return ( - - {renderedScreens} - + return useMemo( + () => ( + + {renderedScreens} + + ), + [initialRouteName, presetScreenOptions, renderedScreens, screenOptions], ); } diff --git a/packages/components/src/Navigation/Navigator/TabStackNavigator.tsx b/packages/components/src/Navigation/Navigator/TabStackNavigator.tsx index e620c93bc8a..b32f6608bbc 100644 --- a/packages/components/src/Navigation/Navigator/TabStackNavigator.tsx +++ b/packages/components/src/Navigation/Navigator/TabStackNavigator.tsx @@ -1,6 +1,7 @@ -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { useIntl } from 'react-intl'; import platformEnv from '@onekeyhq/shared/src/platformEnv'; @@ -9,9 +10,9 @@ import { useThemeValue } from '../../Provider/hooks/useThemeValue'; import { makeTabScreenOptions } from '../GlobalScreenOptions'; import { createStackNavigator } from '../StackNavigator'; import NavigationBar from '../Tab/TabBar'; -import { tabRouteWrapper } from '../Tab/tabRouteWrapper'; import type { ICON_NAMES } from '../../Icon'; +import type { LocaleIds } from '../../locale'; import type { CommonNavigatorConfig } from './types'; import type { BottomTabBarProps } from '@react-navigation/bottom-tabs/src/types'; import type { ParamListBase } from '@react-navigation/routers'; @@ -20,14 +21,16 @@ export interface TabSubNavigatorConfig< RouteName extends string, P extends ParamListBase = ParamListBase, > extends CommonNavigatorConfig { - translationId: string; + translationId: LocaleIds; + disable?: boolean; } export interface TabNavigatorConfig { name: RouteName; tabBarIcon: (focused?: boolean) => ICON_NAMES; - translationId: string; + translationId: LocaleIds; children: TabSubNavigatorConfig[]; + freezeOnBlur?: boolean; disable?: boolean; } @@ -38,36 +41,42 @@ export interface TabNavigatorProps { const Stack = createStackNavigator(); function TabSubStackNavigator({ - screens, + config, }: { - screens: TabSubNavigatorConfig[]; + config: TabSubNavigatorConfig[]; }) { const [bgColor, titleColor] = useThemeValue(['bg', 'text']); + const intl = useIntl(); return ( - {screens.map(({ name, component, translationId }) => ( - ({ - // TODO i18n - title: translationId, - ...makeTabScreenOptions({ navigation, bgColor, titleColor }), - })} - /> - ))} + {config + .filter(({ disable }) => !disable) + .map(({ name, component, translationId }) => ( + ({ + freezeOnBlur: true, + title: intl.formatMessage({ id: translationId }), + ...makeTabScreenOptions({ navigation, bgColor, titleColor }), + })} + /> + ))} ); } +const TabSubStackNavigatorMemo = memo(TabSubStackNavigator); + const Tab = createBottomTabNavigator(); export function TabStackNavigator({ config, }: TabNavigatorProps) { const isVerticalLayout = useIsVerticalLayout(); + const intl = useIntl(); const tabBarCallback = useCallback( (props: BottomTabBarProps) => , @@ -78,12 +87,10 @@ export function TabStackNavigator({ () => config .filter(({ disable }) => !disable) - .map(({ name, translationId, tabBarIcon, children }) => ({ - name, - tabBarLabel: translationId, - tabBarIcon, + .map(({ children, ...options }) => ({ + ...options, // eslint-disable-next-line react/no-unstable-nested-components - children: () => , + children: () => , })), [config], ); @@ -105,6 +112,7 @@ export function TabStackNavigator({ name={name} options={{ ...options, + tabBarLabel: intl.formatMessage({ id: options.translationId }), // @ts-expect-error BottomTabBar V7 tabBarPosition: isVerticalLayout ? 'bottom' : 'left', }} diff --git a/packages/components/src/Navigation/Tab/TabBar/MobileBottomTabBar.tsx b/packages/components/src/Navigation/Tab/TabBar/MobileBottomTabBar.tsx index a7d081502e9..796e4e5198a 100644 --- a/packages/components/src/Navigation/Tab/TabBar/MobileBottomTabBar.tsx +++ b/packages/components/src/Navigation/Tab/TabBar/MobileBottomTabBar.tsx @@ -18,8 +18,9 @@ import type { BottomTabBarProps } from '@react-navigation/bottom-tabs/src/types' import type { Animated, StyleProp, ViewStyle } from 'react-native'; import type { EdgeInsets } from 'react-native-safe-area-context'; -const DEFAULT_TABBAR_HEIGHT = 54; -const COMPACT_TABBAR_HEIGHT = 36; +const DEFAULT_TABBAR_HEIGHT = 63; +const COMPACT_TABBAR_HEIGHT = 40; +const COMPACT_PAD_TABBAR_HEIGHT = 54; type Options = { deviceSize: DeviceScreenSize; @@ -29,8 +30,7 @@ type Options = { const shouldUseHorizontalLabels = ({ deviceSize }: Options) => ['NORMAL'].includes(deviceSize); -const getPaddingBottom = (insets: EdgeInsets) => - Math.max(insets.bottom - Platform.select({ ios: 0, default: 0 }), 0); +const getPaddingBottom = (insets: EdgeInsets) => insets.bottom; export const getTabBarHeight = ({ insets, @@ -50,6 +50,9 @@ export const getTabBarHeight = ({ if (Platform.OS === 'ios' && !Platform.isPad) { return COMPACT_TABBAR_HEIGHT + paddingBottom; } + if (Platform.OS === 'ios' && Platform.isPad) { + return COMPACT_PAD_TABBAR_HEIGHT + paddingBottom; + } return DEFAULT_TABBAR_HEIGHT + paddingBottom; }; diff --git a/packages/components/src/Navigation/Tab/tabRouteWrapper.native.tsx b/packages/components/src/Navigation/Tab/tabRouteWrapper.native.tsx new file mode 100644 index 00000000000..7c90d54f9f6 --- /dev/null +++ b/packages/components/src/Navigation/Tab/tabRouteWrapper.native.tsx @@ -0,0 +1,11 @@ +import { Stack } from '../../Stack'; + +export function tabRouteWrapper(WrappedComponent: any): () => JSX.Element { + return function TabLayoutWrapper() { + return ( + + + + ); + }; +} diff --git a/packages/components/src/Navigation/Tab/tabRouteWrapper.tsx b/packages/components/src/Navigation/Tab/tabRouteWrapper.tsx index 7c90d54f9f6..0339c0bc143 100644 --- a/packages/components/src/Navigation/Tab/tabRouteWrapper.tsx +++ b/packages/components/src/Navigation/Tab/tabRouteWrapper.tsx @@ -1,11 +1,22 @@ -import { Stack } from '../../Stack'; +import { useIsFocused } from '@react-navigation/core'; -export function tabRouteWrapper(WrappedComponent: any): () => JSX.Element { +import DelayedFreeze from '../../DelayedFreeze'; + +export function tabRouteWrapper( + WrappedComponent: any, + freezeOnBlur: boolean | undefined, +): () => JSX.Element { return function TabLayoutWrapper() { - return ( - - - - ); + const isFocused = useIsFocused(); + + if (freezeOnBlur) { + return ( + + + + ); + } + + return ; }; } diff --git a/packages/components/src/Provider/ScreenSizeProvider.tsx b/packages/components/src/Provider/ScreenSizeProvider.tsx index bff34f275ca..5740c81952a 100644 --- a/packages/components/src/Provider/ScreenSizeProvider.tsx +++ b/packages/components/src/Provider/ScreenSizeProvider.tsx @@ -5,7 +5,7 @@ import { useWindowDimensions } from 'react-native'; import { getScreenSize } from './device'; import { ContextDeviceScreenSize } from './hooks/useProviderDeviceScreenSize'; -import { ContextScreenLayout } from './hooks/useProviderScreenLayoutValue'; +import { ContextIsVerticalLayout } from './hooks/useProviderIsVerticalLayout'; function ScreenSizeProvider({ children }: { children?: ReactNode }) { const { width } = useWindowDimensions(); @@ -16,25 +16,11 @@ function ScreenSizeProvider({ children }: { children?: ReactNode }) { [deviceScreenSize], ); - const providerDeviceScreenSizeValue = useMemo( - () => ({ - deviceScreenSize, - }), - [deviceScreenSize], - ); - - const providerScreenValue = useMemo( - () => ({ - isVerticalLayout, - }), - [isVerticalLayout], - ); - return ( - - + + {children} - + ); } diff --git a/packages/components/src/Provider/hooks/useDeviceScreenSize.ts b/packages/components/src/Provider/hooks/useDeviceScreenSize.ts index 4ed679d10cd..fe60a960ae9 100644 --- a/packages/components/src/Provider/hooks/useDeviceScreenSize.ts +++ b/packages/components/src/Provider/hooks/useDeviceScreenSize.ts @@ -3,6 +3,5 @@ import { useMemo } from 'react'; import useProviderDeviceScreenSize from './useProviderDeviceScreenSize'; export default function useDeviceScreenSize() { - const context = useProviderDeviceScreenSize(); - return useMemo(() => context.deviceScreenSize, [context.deviceScreenSize]); + return useProviderDeviceScreenSize(); } diff --git a/packages/components/src/Provider/hooks/useIsVerticalLayout.ts b/packages/components/src/Provider/hooks/useIsVerticalLayout.ts index 23b67275c3e..1b90445f18e 100644 --- a/packages/components/src/Provider/hooks/useIsVerticalLayout.ts +++ b/packages/components/src/Provider/hooks/useIsVerticalLayout.ts @@ -5,11 +5,10 @@ import { Dimensions } from 'react-native'; import { getScreenSize } from '../device'; import useDeviceScreenSize from './useDeviceScreenSize'; -import useProviderScreenLayoutValue from './useProviderScreenLayoutValue'; +import useProviderIsVerticalLayout from './useProviderIsVerticalLayout'; export default function useIsVerticalLayout() { - const context = useProviderScreenLayoutValue(); - return context.isVerticalLayout; + return useProviderIsVerticalLayout(); } export function getIsVerticalLayout() { diff --git a/packages/components/src/Provider/hooks/useProviderDeviceScreenSize.ts b/packages/components/src/Provider/hooks/useProviderDeviceScreenSize.ts index 784bb88945e..7423ba92606 100644 --- a/packages/components/src/Provider/hooks/useProviderDeviceScreenSize.ts +++ b/packages/components/src/Provider/hooks/useProviderDeviceScreenSize.ts @@ -2,14 +2,8 @@ import { createContext, useContext } from 'react'; import type { DeviceScreenSize } from '../device'; -export type ContextDeviceScreenSizeValue = { - deviceScreenSize: DeviceScreenSize; -}; - export const ContextDeviceScreenSize = - createContext( - {} as ContextDeviceScreenSizeValue, - ); + createContext('NORMAL'); const useProviderDeviceScreenSize = () => useContext(ContextDeviceScreenSize); export default useProviderDeviceScreenSize; diff --git a/packages/components/src/Provider/hooks/useProviderIsVerticalLayout.ts b/packages/components/src/Provider/hooks/useProviderIsVerticalLayout.ts new file mode 100644 index 00000000000..ef81205531a --- /dev/null +++ b/packages/components/src/Provider/hooks/useProviderIsVerticalLayout.ts @@ -0,0 +1,6 @@ +import { createContext, useContext } from 'react'; + +export const ContextIsVerticalLayout = createContext(false); + +const useProviderIsVerticalLayout = () => useContext(ContextIsVerticalLayout); +export default useProviderIsVerticalLayout; diff --git a/packages/components/src/Provider/hooks/useProviderScreenLayoutValue.ts b/packages/components/src/Provider/hooks/useProviderScreenLayoutValue.ts deleted file mode 100644 index 7154bc986ff..00000000000 --- a/packages/components/src/Provider/hooks/useProviderScreenLayoutValue.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { createContext, useContext } from 'react'; - -export type ContextScreenLayoutValue = { - isVerticalLayout: boolean; -}; - -export const ContextScreenLayout = createContext( - {} as ContextScreenLayoutValue, -); - -const useProviderScreenLayoutValue = () => useContext(ContextScreenLayout); -export default useProviderScreenLayoutValue; diff --git a/packages/components/src/SearchBar/index.tsx b/packages/components/src/SearchBar/index.tsx index ce6e90b41d1..8857a75476c 100644 --- a/packages/components/src/SearchBar/index.tsx +++ b/packages/components/src/SearchBar/index.tsx @@ -6,22 +6,49 @@ import { Icon } from '../Icon'; import { Input } from '../Input'; import { Stack, XStack } from '../Stack'; +import type { + NativeSyntheticEvent, + TextInputFocusEventData, + TextInputSubmitEditingEventData, +} from 'react-native'; + interface SearchBarProps { + height?: string; value?: string; - onBlur?: () => void; + placeholder?: string; + onBlur?: (e: NativeSyntheticEvent) => void; + onFocus?: (e: NativeSyntheticEvent) => void; onChange?: (value: string) => void; + onSubmitEditing?: ( + e: NativeSyntheticEvent, + ) => void; } -export function SearchBar({ value, onChange, onBlur }: SearchBarProps) { +export function SearchBar({ + height, + value, + placeholder, + onChange, + onBlur, + onFocus, + onSubmitEditing, +}: SearchBarProps) { const [isFocus, setIsFocus] = useState(false); - const handleOnFocus = useCallback(() => { - setIsFocus(true); - }, []); + const handleOnFocus = useCallback( + (e: NativeSyntheticEvent) => { + setIsFocus(true); + onFocus?.(e); + }, + [onFocus], + ); - const handleOnBlur = useCallback(() => { - setIsFocus(false); - onBlur?.(); - }, [onBlur]); + const handleOnBlur = useCallback( + (e: NativeSyntheticEvent) => { + setIsFocus(false); + onBlur?.(e); + }, + [onBlur], + ); const handleClearValue = useCallback(() => { onChange?.(''); @@ -29,6 +56,7 @@ export function SearchBar({ value, onChange, onBlur }: SearchBarProps) { return ( + | ModalNavigationProp = PageNavigationProp, +>() { + const navigation = useNavigation

(); + const [bgColor, titleColor] = useThemeValue(['bg', 'text']); + + const popStack = () => { + navigation.getParent()?.goBack?.(); + }; + + const pop = () => { + if (navigation.canGoBack?.()) { + navigation.goBack?.(); + } else { + popStack(); + } + }; + + const switchTab = ( + route: T, + params?: { + screen: keyof TabStackParamList[T]; + params?: TabStackParamList[T][keyof TabStackParamList[T]]; + }, + ) => { + navigation.navigate(RootRoutes.Main, { + screen: route, + params, + }); + }; + + const pushModal = ( + route: T, + params?: { + screen: keyof ModalParamList[T]; + params?: ModalParamList[T][keyof ModalParamList[T]]; + }, + ) => { + navigation.navigate(RootRoutes.Modal, { + screen: route, + params, + }); + }; + + const iosHeaderStyle = Platform.select({ + ios: { + headerStyle: { + backgroundColor: bgColor as string, + }, + headerTintColor: titleColor as string, + }, + }); + + function setOptions(options: Partial) { + const { headerSearchBarOptions, ...otherOptions } = options; + + let newHeaderSearchBarOptions: StackNavigationOptions = {}; + if (headerSearchBarOptions) { + newHeaderSearchBarOptions = { + headerSearchBarOptions: { + // always show search bar on iOS + hideNavigationBar: false, + // @ts-expect-error + hideWhenScrolling: false, + ...headerSearchBarOptions, + }, + }; + } + + navigation.setOptions({ + ...iosHeaderStyle, + ...otherOptions, + ...newHeaderSearchBarOptions, + }); + } + + return { + navigation, + reset: navigation.reset, + dispatch: navigation.dispatch, + push: navigation.navigate, + switchTab, + pushModal, + pop, + popStack, + setOptions, + }; +} + +export default useAppNavigation; diff --git a/packages/kit/src/provider/NavigationProvider.tsx b/packages/kit/src/provider/NavigationProvider.tsx index ca0d16a41e6..85dae1a5ff3 100644 --- a/packages/kit/src/provider/NavigationProvider.tsx +++ b/packages/kit/src/provider/NavigationProvider.tsx @@ -3,9 +3,7 @@ import { createRef, memo } from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { useTheme } from 'tamagui'; -import { DevScreen } from '@onekeyhq/kit/src/routes'; - -import DemoRootApp from '../views/Components/stories/NavigatorRoute'; +import { RootNavigator } from '@onekeyhq/kit/src/routes'; export const navigationRef = createRef(); global.$navigationRef = navigationRef as any; @@ -25,8 +23,7 @@ const NavigationApp = () => { }} ref={navigationRef} > - - {/* */} + ); }; diff --git a/packages/kit/src/routes/Gallery/index.tsx b/packages/kit/src/routes/Gallery/index.tsx index bef4ad0118e..0b7608d9ccb 100644 --- a/packages/kit/src/routes/Gallery/index.tsx +++ b/packages/kit/src/routes/Gallery/index.tsx @@ -1,5 +1,4 @@ -import { createNativeStackNavigator } from '@react-navigation/native-stack'; - +import { createStackNavigator } from '@onekeyhq/components'; import ComponentsScreen from '@onekeyhq/kit/src/views/Components'; import ActionListGallery from '@onekeyhq/kit/src/views/Components/stories/ActionList'; import BadgeGallery from '@onekeyhq/kit/src/views/Components/stories/Badge'; @@ -79,7 +78,7 @@ export const stackScreenList = [ }, ]; -const DevStack = createNativeStackNavigator(); +const DevStack = createStackNavigator(); const DevScreen = () => ( diff --git a/packages/kit/src/routes/Root/Modal/ModalNavigator.tsx b/packages/kit/src/routes/Root/Modal/ModalNavigator.tsx new file mode 100644 index 00000000000..80d33adc232 --- /dev/null +++ b/packages/kit/src/routes/Root/Modal/ModalNavigator.tsx @@ -0,0 +1,16 @@ +import type { ModalRootNavigatorConfig } from '@onekeyhq/components/src/Navigation/Navigator'; +import { RootModalNavigator } from '@onekeyhq/components/src/Navigation/Navigator'; + +import { ModalRoutes } from './Routes'; +import { ModalTestStack } from './TestModal/ModalTestStack'; + +const config: ModalRootNavigatorConfig[] = [ + { + name: ModalRoutes.TestModal, + children: ModalTestStack, + }, +]; + +export default function ModalNavigator() { + return config={config} />; +} diff --git a/packages/kit/src/routes/Root/Modal/Routes.ts b/packages/kit/src/routes/Root/Modal/Routes.ts new file mode 100644 index 00000000000..d19e42eddad --- /dev/null +++ b/packages/kit/src/routes/Root/Modal/Routes.ts @@ -0,0 +1,9 @@ +import type { ModalTestParamList } from './TestModal/Routes'; + +export enum ModalRoutes { + TestModal = 'TestModalStack', +} + +export type ModalParamList = { + [ModalRoutes.TestModal]: ModalTestParamList; +}; diff --git a/packages/kit/src/routes/Root/Modal/TestModal/ModalTestStack.tsx b/packages/kit/src/routes/Root/Modal/TestModal/ModalTestStack.tsx new file mode 100644 index 00000000000..998f0030b55 --- /dev/null +++ b/packages/kit/src/routes/Root/Modal/TestModal/ModalTestStack.tsx @@ -0,0 +1,17 @@ +import type { ModalFlowNavigatorConfig } from '@onekeyhq/components/src/Navigation/Navigator'; + +import { ModalTestRoutes } from './Routes'; +import TestSimpleModal from './TestSimpleModal'; + +import type { ModalTestParamList } from './Routes'; + +export const ModalTestStack: ModalFlowNavigatorConfig< + ModalTestRoutes, + ModalTestParamList +>[] = [ + { + name: ModalTestRoutes.TestSimpleModal, + component: TestSimpleModal, + translationId: 'Locked Modal Demo', + }, +]; diff --git a/packages/kit/src/routes/Root/Modal/TestModal/Routes.ts b/packages/kit/src/routes/Root/Modal/TestModal/Routes.ts new file mode 100644 index 00000000000..fb15f92336b --- /dev/null +++ b/packages/kit/src/routes/Root/Modal/TestModal/Routes.ts @@ -0,0 +1,7 @@ +export enum ModalTestRoutes { + TestSimpleModal = 'TestSimpleModal', +} + +export type ModalTestParamList = { + [ModalTestRoutes.TestSimpleModal]: { question: string }; +}; diff --git a/packages/kit/src/routes/Root/Modal/TestModal/TestSimpleModal.tsx b/packages/kit/src/routes/Root/Modal/TestModal/TestSimpleModal.tsx new file mode 100644 index 00000000000..67dba54a10b --- /dev/null +++ b/packages/kit/src/routes/Root/Modal/TestModal/TestSimpleModal.tsx @@ -0,0 +1,30 @@ +import { useCallback, useLayoutEffect } from 'react'; + +import { ModalContainer, Stack, Text } from '@onekeyhq/components'; +import type { ModalScreenProps } from '@onekeyhq/components/src/Navigation'; +import HeaderButtonIcon from '@onekeyhq/components/src/Navigation/Header/HeaderButtonIcon'; + +import type { ModalTestParamList } from './Routes'; + +export default function TestSimpleModal({ + navigation, +}: ModalScreenProps) { + const headerRightCall = useCallback( + () => , + [], + ); + + useLayoutEffect(() => { + navigation.setOptions({ + headerRight: headerRightCall, + }); + }, [navigation, headerRightCall]); + + return ( + {}}> + + 这是一个普通的 Modal 测试 + + + ); +} diff --git a/packages/kit/src/routes/Root/Modal/types.ts b/packages/kit/src/routes/Root/Modal/types.ts deleted file mode 100644 index 2eb83711a6c..00000000000 --- a/packages/kit/src/routes/Root/Modal/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ComponentType } from 'react'; - -import type { RouteProp } from '@react-navigation/core'; - -export type ModalRoutesType = { - name: T; - component: - | ComponentType<{ - route: RouteProp; - navigation: any; - }> - | ComponentType; -}[]; diff --git a/packages/kit/src/routes/Root/RootNavigator.tsx b/packages/kit/src/routes/Root/RootNavigator.tsx new file mode 100644 index 00000000000..17be81b4e85 --- /dev/null +++ b/packages/kit/src/routes/Root/RootNavigator.tsx @@ -0,0 +1,53 @@ +import { useEffect } from 'react'; + +import { useIntl } from 'react-intl'; +import { Platform } from 'react-native'; +import KeyboardManager from 'react-native-keyboard-manager'; + +import type { RootStackNavigatorConfig } from '@onekeyhq/components/src/Navigation/Navigator'; +import { RootStackNavigator } from '@onekeyhq/components/src/Navigation/Navigator'; + +import ModalNavigator from './Modal/ModalNavigator'; +import { RootRoutes } from './Routes'; +import TabNavigator from './Tab/TabNavigator'; + +const rootConfig: RootStackNavigatorConfig[] = [ + { + name: RootRoutes.Main, + component: TabNavigator, + initialRoute: true, + }, + { + name: RootRoutes.Modal, + component: ModalNavigator, + type: 'modal', + }, + { + name: RootRoutes.Gallery, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + component: require('../Gallery').default, + disable: process.env.NODE_ENV === 'production', + }, +]; + +export const RootNavigator = () => { + const intl = useIntl(); + + useEffect(() => { + if (Platform.OS === 'ios') { + KeyboardManager.setEnable(true); + KeyboardManager.setEnableDebugging(false); + KeyboardManager.setKeyboardDistanceFromTextField(10); + KeyboardManager.setLayoutIfNeededOnUpdate(true); + KeyboardManager.setEnableAutoToolbar(true); + KeyboardManager.setToolbarDoneBarButtonItemText( + intl.formatMessage({ id: 'action__done' }), + ); + KeyboardManager.setToolbarPreviousNextButtonEnable(false); + KeyboardManager.setKeyboardAppearance('default'); + KeyboardManager.setShouldPlayInputClicks(true); + } + }, [intl]); + + return config={rootConfig} />; +}; diff --git a/packages/kit/src/routes/Root/Routes.ts b/packages/kit/src/routes/Root/Routes.ts new file mode 100644 index 00000000000..9ee3447b059 --- /dev/null +++ b/packages/kit/src/routes/Root/Routes.ts @@ -0,0 +1,7 @@ +export enum RootRoutes { + Main = 'main', + + Modal = 'modal', + + Gallery = 'gallery', +} diff --git a/packages/kit/src/routes/Root/Tab/Developer/Routes.ts b/packages/kit/src/routes/Root/Tab/Developer/Routes.ts new file mode 100644 index 00000000000..f2479ab2f1c --- /dev/null +++ b/packages/kit/src/routes/Root/Tab/Developer/Routes.ts @@ -0,0 +1,7 @@ +export enum TabDeveloperRoutes { + TabDeveloper = 'TabDeveloper', +} + +export type TabDeveloperParamList = { + [TabDeveloperRoutes.TabDeveloper]: undefined; +}; diff --git a/packages/kit/src/routes/Root/Tab/Developer/TabDeveloper.tsx b/packages/kit/src/routes/Root/Tab/Developer/TabDeveloper.tsx new file mode 100644 index 00000000000..6cd1f446658 --- /dev/null +++ b/packages/kit/src/routes/Root/Tab/Developer/TabDeveloper.tsx @@ -0,0 +1,174 @@ +import type { ReactNode } from 'react'; +import { useState } from 'react'; + +import { StyleSheet } from 'react-native'; +import { useDispatch } from 'react-redux'; +import useCookie from 'react-use-cookie'; +import { ScrollView } from 'tamagui'; + +import { Button, Stack, Text, XStack, YStack } from '@onekeyhq/components'; +import type { PageNavigationProp } from '@onekeyhq/components/src/Navigation'; +import platformEnv from '@onekeyhq/shared/src/platformEnv'; +import { AppSettingKey } from '@onekeyhq/shared/src/storage/appSetting'; +import appStorage from '@onekeyhq/shared/src/storage/appStorage'; + +import useAppNavigation from '../../../../hooks/useAppNavigation'; +import { setLocale, setTheme } from '../../../../store/reducers/settings'; +import { GalleryRoutes } from '../../../Gallery'; +import { RootRoutes } from '../../Routes'; + +import type { TabDeveloperParamList } from './Routes'; + +const useStorage = platformEnv.isNative + ? (key: AppSettingKey, initialValue?: boolean) => { + const [data, setData] = useState( + initialValue || appStorage.getSettingBoolean(key), + ); + const setNewData = (value: boolean) => { + appStorage.setSetting(key, value); + setData(value); + }; + return [data, setNewData]; + } + : useCookie; + +function PartContainer({ + title, + children, +}: { + title: string; + children: ReactNode; +}) { + return ( + + + {title} + + + + {children} + + + ); +} + +const TabDeveloper = () => { + const navigation = + useAppNavigation>(); + const dispatch = useDispatch(); + + const [rrtStatus, changeRRTStatus] = useStorage(AppSettingKey.rrt); + + return ( + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default TabDeveloper; diff --git a/packages/kit/src/routes/Root/Tab/Home/Routes.ts b/packages/kit/src/routes/Root/Tab/Home/Routes.ts new file mode 100644 index 00000000000..bbfb51fd3e9 --- /dev/null +++ b/packages/kit/src/routes/Root/Tab/Home/Routes.ts @@ -0,0 +1,11 @@ +export enum TabHomeRoutes { + TabHome = 'TabHome', + TabHomeStack1 = 'TabHomeStack1', + TabHomeStack2 = 'TabHomeStack2', +} + +export type TabHomeParamList = { + [TabHomeRoutes.TabHome]: undefined; + [TabHomeRoutes.TabHomeStack1]: undefined; + [TabHomeRoutes.TabHomeStack2]: undefined; +}; diff --git a/packages/kit/src/routes/Root/Tab/Home/TabHome.tsx b/packages/kit/src/routes/Root/Tab/Home/TabHome.tsx new file mode 100644 index 00000000000..c259590b81a --- /dev/null +++ b/packages/kit/src/routes/Root/Tab/Home/TabHome.tsx @@ -0,0 +1,26 @@ +import { Button, YStack } from '@onekeyhq/components'; +import type { PageNavigationProp } from '@onekeyhq/components/src/Navigation'; + +import useAppNavigation from '../../../../hooks/useAppNavigation'; + +import { TabHomeRoutes } from './Routes'; + +import type { TabHomeParamList } from './Routes'; + +const TabHome = () => { + const navigation = useAppNavigation>(); + + return ( + + + + ); +}; + +export default TabHome; diff --git a/packages/kit/src/routes/Root/Tab/Home/TabHomeStack1.tsx b/packages/kit/src/routes/Root/Tab/Home/TabHomeStack1.tsx new file mode 100644 index 00000000000..c7f273cacfe --- /dev/null +++ b/packages/kit/src/routes/Root/Tab/Home/TabHomeStack1.tsx @@ -0,0 +1,33 @@ +import { Button, YStack } from '@onekeyhq/components'; +import type { PageNavigationProp } from '@onekeyhq/components/src/Navigation'; + +import useAppNavigation from '../../../../hooks/useAppNavigation'; + +import { TabHomeRoutes } from './Routes'; + +import type { TabHomeParamList } from './Routes'; + +const TabHomeStack1 = () => { + const navigation = useAppNavigation>(); + + return ( + + + + + ); +}; + +export default TabHomeStack1; diff --git a/packages/kit/src/routes/Root/Tab/Home/TabHomeStack2.tsx b/packages/kit/src/routes/Root/Tab/Home/TabHomeStack2.tsx new file mode 100644 index 00000000000..1dcb20864ab --- /dev/null +++ b/packages/kit/src/routes/Root/Tab/Home/TabHomeStack2.tsx @@ -0,0 +1,33 @@ +import { Button, YStack } from '@onekeyhq/components'; +import type { PageNavigationProp } from '@onekeyhq/components/src/Navigation'; + +import useAppNavigation from '../../../../hooks/useAppNavigation'; + +import { TabHomeRoutes } from './Routes'; + +import type { TabHomeParamList } from './Routes'; + +const TabHomeStack2 = () => { + const navigation = useAppNavigation>(); + + return ( + + + + + ); +}; + +export default TabHomeStack2; diff --git a/packages/kit/src/routes/Root/Tab/Me/Routes.ts b/packages/kit/src/routes/Root/Tab/Me/Routes.ts new file mode 100644 index 00000000000..76543af8c9b --- /dev/null +++ b/packages/kit/src/routes/Root/Tab/Me/Routes.ts @@ -0,0 +1,7 @@ +export enum TabMeRoutes { + TabMe = 'TabMe', +} + +export type TabMeParamList = { + [TabMeRoutes.TabMe]: undefined; +}; diff --git a/packages/kit/src/routes/Root/Tab/Me/TabMe.tsx b/packages/kit/src/routes/Root/Tab/Me/TabMe.tsx new file mode 100644 index 00000000000..34cf450f53f --- /dev/null +++ b/packages/kit/src/routes/Root/Tab/Me/TabMe.tsx @@ -0,0 +1,25 @@ +import { Button, YStack } from '@onekeyhq/components'; +import type { PageNavigationProp } from '@onekeyhq/components/src/Navigation'; + +import useAppNavigation from '../../../../hooks/useAppNavigation'; +import { TabRoutes } from '../Routes'; + +import type { TabMeParamList } from './Routes'; + +const TabMe = () => { + const navigation = useAppNavigation>(); + + return ( + + + + ); +}; + +export default TabMe; diff --git a/packages/kit/src/routes/Root/Tab/Routes.ts b/packages/kit/src/routes/Root/Tab/Routes.ts new file mode 100644 index 00000000000..44a8d1bcc94 --- /dev/null +++ b/packages/kit/src/routes/Root/Tab/Routes.ts @@ -0,0 +1,15 @@ +import type { DemoDeveloperTabParamList } from '../../../views/Components/stories/NavigatorRoute/Tab/RouteParamTypes'; +import type { TabHomeParamList } from './Home/Routes'; +import type { TabMeParamList } from './Me/Routes'; + +export enum TabRoutes { + Home = 'Home', + Me = 'Me', + Developer = 'Developer', +} + +export type TabStackParamList = { + [TabRoutes.Home]: TabHomeParamList; + [TabRoutes.Me]: TabMeParamList; + [TabRoutes.Developer]: DemoDeveloperTabParamList; +}; diff --git a/packages/kit/src/routes/Root/Tab/TabNavigator.tsx b/packages/kit/src/routes/Root/Tab/TabNavigator.tsx new file mode 100644 index 00000000000..44b35218698 --- /dev/null +++ b/packages/kit/src/routes/Root/Tab/TabNavigator.tsx @@ -0,0 +1,73 @@ +import type { TabNavigatorConfig } from '@onekeyhq/components/src/Navigation/Navigator'; +import { TabStackNavigator } from '@onekeyhq/components/src/Navigation/Navigator'; + +import HomePage from '../../../views/Tab/Home/HomePageTabs'; + +import { TabDeveloperRoutes } from './Developer/Routes'; +import { TabHomeRoutes } from './Home/Routes'; +import TabHomeStack1 from './Home/TabHomeStack1'; +import TabHomeStack2 from './Home/TabHomeStack2'; +import { TabMeRoutes } from './Me/Routes'; +import TabMe from './Me/TabMe'; +import { TabRoutes } from './Routes'; + +const config: TabNavigatorConfig[] = [ + { + name: TabRoutes.Home, + tabBarIcon: (focused?: boolean) => + focused ? 'CreditCardSolid' : 'CreditCardOutline', + translationId: 'wallet__wallet', + freezeOnBlur: true, + children: [ + { + name: TabHomeRoutes.TabHome, + component: HomePage, + translationId: 'wallet__wallet', + }, + { + name: TabHomeRoutes.TabHomeStack1, + component: TabHomeStack1, + translationId: 'wallet__wallet', + }, + { + name: TabHomeRoutes.TabHomeStack2, + component: TabHomeStack2, + translationId: 'wallet__wallet', + }, + ], + }, + { + name: TabRoutes.Me, + tabBarIcon: (focused?: boolean) => + focused ? 'MailOpenMini' : 'EmailOutline', + translationId: 'title__me', + freezeOnBlur: true, + children: [ + { + name: TabMeRoutes.TabMe, + component: TabMe, + translationId: 'title__me', + }, + ], + }, + { + name: TabRoutes.Developer, + tabBarIcon: (focused?: boolean) => + focused ? 'CodeBracketSquareMini' : 'CodeBracketMini', + translationId: 'form__dev_mode', + freezeOnBlur: true, + disable: process.env.NODE_ENV === 'production', + children: [ + { + name: TabDeveloperRoutes.TabDeveloper, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + component: require('./Developer/TabDeveloper').default, + translationId: 'form__dev_mode', + }, + ], + }, +]; + +export default function TabNavigator() { + return config={config} />; +} diff --git a/packages/kit/src/routes/index.ts b/packages/kit/src/routes/index.ts index c79f6348fab..3b3b26b7320 100644 --- a/packages/kit/src/routes/index.ts +++ b/packages/kit/src/routes/index.ts @@ -1,3 +1 @@ -import DevScreen from './Gallery'; - -export { DevScreen }; +export { RootNavigator } from './Root/RootNavigator'; diff --git a/packages/kit/src/views/Components/stories/Buttons.tsx b/packages/kit/src/views/Components/stories/Buttons.tsx index 822e3e9afff..21c925d7e24 100644 --- a/packages/kit/src/views/Components/stories/Buttons.tsx +++ b/packages/kit/src/views/Components/stories/Buttons.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; -import { Button, XStack, YStack } from '@onekeyhq/components'; +import { Button, XStack, YStack } from '@onekeyhq/components'; import { Layout } from './utils/Layout'; diff --git a/packages/kit/src/views/Components/stories/CollapsibleTabView.tsx b/packages/kit/src/views/Components/stories/CollapsibleTabView.tsx index 2f82cfe7e79..b36f209b133 100644 --- a/packages/kit/src/views/Components/stories/CollapsibleTabView.tsx +++ b/packages/kit/src/views/Components/stories/CollapsibleTabView.tsx @@ -7,8 +7,9 @@ import { Badge, Button, Icon, Stack, Tabs, Text } from '@onekeyhq/components'; import useIsActiveTab from '@onekeyhq/components/src/CollapsibleTabView/hooks/useIsActiveTab'; import type { ForwardRefHandle } from '@onekeyhq/components/src/CollapsibleTabView/NativeNestedTabView/NestedTabView'; -import { FreezeProbe, useFreezeProbe } from './NavigatorRoute/RenderTools'; import { Layout } from './utils/Layout'; +import { TabsFocusTools } from './utils/NavigationTools'; +import { FreezeProbe, useFreezeProbe } from './utils/RenderTools'; type Network = { id: string; @@ -52,12 +53,15 @@ function TokenList({ networks, name }: { networks: Network[]; name: string }) { [], ); - return ( - `${item.id}-${index}`} - /> + return useMemo( + () => ( + `${item.id}-${index}`} + /> + ), + [networks, renderItem], ); } @@ -104,95 +108,102 @@ function CollapsibleTabView() { [headerHighMode, showNetworks.length, headerHeightCall, loadMoreDataCall], ); - return ( - - - - - - - 我是一个内容 - - ), - }, - ]} - /> - - - - - - - - - - Network 1 - - - Network 2 - - - End - - - - - - {showNetworks.flatMap((item, index) => ( - - {`${item.name}`} - - ))} - - - - - - - - ScrollView Simple - {showNetworks.flatMap((item, index) => ( - {}} key={`tab5-network-${index}`}> - - {item.name} - {item.tokenDisplayDecimals} - - {item.impl.toUpperCase()} - - - ))} - End - - - - + return useMemo( + () => ( + + + + + + + + 我是一个内容 + + ), + }, + ]} + /> + + + + + + + + + + + Network 1 + + + Network 2 + + + End + + + + + + {showNetworks.flatMap((item, index) => ( + + {`${item.name}`} + + ))} + + + + + + + + + + ScrollView Simple + {showNetworks.flatMap((item, index) => ( + {}} key={`tab5-network-${index}`}> + + {item.name} + {item.tokenDisplayDecimals} + + {item.impl.toUpperCase()} + + + ))} + End + + + + + ), + [headerView, showNetworks], ); } diff --git a/packages/kit/src/views/Components/stories/NavigatorRoute/Modal/DemoCoverageModal.tsx b/packages/kit/src/views/Components/stories/NavigatorRoute/Modal/DemoCoverageModal.tsx index ccb9b1403ef..ac3157c9fc7 100644 --- a/packages/kit/src/views/Components/stories/NavigatorRoute/Modal/DemoCoverageModal.tsx +++ b/packages/kit/src/views/Components/stories/NavigatorRoute/Modal/DemoCoverageModal.tsx @@ -1,15 +1,13 @@ /* eslint-disable react/no-unstable-nested-components */ -import { useState } from 'react'; +import { useMemo, useState } from 'react'; -import * as Burnt from 'burnt'; - -import { Button, Dialog } from '@onekeyhq/components'; +import { Button, Dialog, Stack, Toast } from '@onekeyhq/components'; import type { ModalNavigationProp } from '@onekeyhq/components/src/Navigation'; - import type { ModalFlowNavigatorConfig } from '@onekeyhq/components/src/Navigation/Navigator/ModalFlowNavigator'; import { Layout } from '../../utils/Layout'; - +import { NavigationFocusTools } from '../../utils/NavigationTools'; +import { FreezeProbe } from '../../utils/RenderTools'; import useDemoAppNavigation from '../useDemoAppNavigation'; import { DemoCoverageModalRoutes, RootModalRoutes } from './Routes'; @@ -44,6 +42,15 @@ function DemoCoverageModal() { ), }, + { + title: '渲染测试', + element: ( + + + + + ), + }, ]} /> ); @@ -54,25 +61,28 @@ const ControlledDialogByButton = () => { useDemoAppNavigation>(); const [isOpen, changeIsOpen] = useState(false); - return ( - <> - -

{ - changeIsOpen(false); - }} - onConfirm={() => { - navigation.pushModal(RootModalRoutes.DemoLockedModal); - changeIsOpen(false); - }} - /> - + return useMemo( + () => ( + <> + + { + changeIsOpen(false); + }} + onConfirm={() => { + navigation.pushModal(RootModalRoutes.DemoLockedModal); + changeIsOpen(false); + }} + /> + + ), + [], ); }; @@ -106,9 +116,8 @@ function DemoCoverageDialogModal() { element: ( ), }, - { - title: 'Open Modal by Button', - element: , - }, + // { + // title: 'Open Modal by Button', + // element: , + // }, { title: 'Open Modal by Api', element: ( @@ -140,6 +149,15 @@ function DemoCoverageDialogModal() { ), }, + { + title: '渲染测试', + element: ( + + + + + ), + }, ]} /> ); @@ -176,9 +194,8 @@ function DemoCoverageModalModal() { ), }, + { + title: '渲染测试', + element: ( + + + + + ), + }, ]} /> ); diff --git a/packages/kit/src/views/Components/stories/NavigatorRoute/Modal/DemoCreateModal.tsx b/packages/kit/src/views/Components/stories/NavigatorRoute/Modal/DemoCreateModal.tsx index feeb85c05d5..e5712901319 100644 --- a/packages/kit/src/views/Components/stories/NavigatorRoute/Modal/DemoCreateModal.tsx +++ b/packages/kit/src/views/Components/stories/NavigatorRoute/Modal/DemoCreateModal.tsx @@ -1,10 +1,9 @@ /* eslint-disable react/no-unstable-nested-components */ import { useLayoutEffect } from 'react'; -import * as Burnt from 'burnt'; import { Input } from 'tamagui'; -import { Button } from '@onekeyhq/components'; +import { Button, ModalContainer, Stack, Toast } from '@onekeyhq/components'; import type { ModalScreenProps } from '@onekeyhq/components/src/Navigation'; import HeaderButtonGroup from '@onekeyhq/components/src/Navigation/Header/HeaderButtonGroup'; import HeaderButtonIcon from '@onekeyhq/components/src/Navigation/Header/HeaderButtonIcon'; @@ -12,7 +11,8 @@ import type { ModalFlowNavigatorConfig } from '@onekeyhq/components/src/Navigati import IconGallery from '../../Icon'; import { Layout } from '../../utils/Layout'; -import { useFreezeProbe } from '../RenderTools'; +import { NavigationFocusTools } from '../../utils/NavigationTools'; +import { FreezeProbe } from '../../utils/RenderTools'; import { DemoRootRoutes } from '../Routes'; import { DemoCreateModalRoutes, RootModalRoutes } from './Routes'; @@ -28,41 +28,59 @@ function DemoCreateViewModal({ }); }, [navigation]); - useFreezeProbe('DemoCreateViewModal'); - return ( - { - navigation.navigate( - DemoCreateModalRoutes.DemoCreateSearchModal, - { - question: '你好', - }, - ); - }} - > - 开始 Demo - - ), - }, - ]} - /> + {}} + checkboxProps={{ + label: '测试', + }} + > + { + navigation.navigate( + DemoCreateModalRoutes.DemoCreateSearchModal, + { + question: '你好', + }, + ); + }} + > + 开始 Demo + + ), + }, + { + title: '测试输入法', + element: , + }, + { + title: '渲染测试', + element: ( + + + + + ), + }, + ]} + /> + ); } @@ -81,8 +99,6 @@ function DemoCreateSearchModal({ }); }, [navigation]); - useFreezeProbe('DemoCreateSearchModal'); - return ( ), }, + { + title: '渲染测试', + element: ( + + + + + ), + }, ]} /> ); @@ -140,8 +165,6 @@ function DemoCreateOptionsModal({ }); }, [navigation]); - useFreezeProbe('DemoCreateOptionsModal'); - return ( { - Burnt.toast({ + Toast.message({ title: 'Close Modal', - preset: 'none', }); navigation.getParent()?.goBack?.(); }} @@ -183,6 +205,15 @@ function DemoCreateOptionsModal({ ), }, + { + title: '渲染测试', + element: ( + + + + + ), + }, ]} /> ); diff --git a/packages/kit/src/views/Components/stories/NavigatorRoute/Modal/DemoLockedModal.tsx b/packages/kit/src/views/Components/stories/NavigatorRoute/Modal/DemoLockedModal.tsx index a028ec58720..1dce7b9c3a0 100644 --- a/packages/kit/src/views/Components/stories/NavigatorRoute/Modal/DemoLockedModal.tsx +++ b/packages/kit/src/views/Components/stories/NavigatorRoute/Modal/DemoLockedModal.tsx @@ -1,12 +1,13 @@ -import { useCallback, useLayoutEffect } from 'react'; +import { useCallback, useLayoutEffect, useState } from 'react'; -import { Button } from '@onekeyhq/components'; +import { Button, Stack } from '@onekeyhq/components'; import type { ModalNavigationProp } from '@onekeyhq/components/src/Navigation'; import HeaderButtonIcon from '@onekeyhq/components/src/Navigation/Header/HeaderButtonIcon'; import type { ModalFlowNavigatorConfig } from '@onekeyhq/components/src/Navigation/Navigator'; import { Layout } from '../../utils/Layout'; -import { useFreezeProbe } from '../RenderTools'; +import { NavigationFocusTools } from '../../utils/NavigationTools'; +import { FreezeProbe } from '../../utils/RenderTools'; import useDemoAppNavigation from '../useDemoAppNavigation'; import { DemoLockedModalRoutes, RootModalRoutes } from './Routes'; @@ -15,7 +16,6 @@ import type { DemoLockedModalParamList } from './Routes'; const DemoLockedViewModal = () => { const navigation = useDemoAppNavigation(); - useFreezeProbe('DemoLockedViewModal'); return ( { 'Locked 的 Modal 可以通过代码取消锁定或者关闭', ]} boundaryConditions={[ + '可以 Locked 的屏幕一定要在配置里写清楚 allowDisableClose: true,否则 disableClose 属性无效', 'Locked 的 Modal 没有办法保持屏幕常亮,如果有需求需要单独处理', '如果前面有被 Locked 的 Modal,跳转到同级别 Stack 的其他 Modal,返回键会消失,除非将前面 Locked 的 Modal 取消锁定', ]} @@ -48,6 +49,15 @@ const DemoLockedViewModal = () => { ), }, + { + title: '渲染测试', + element: ( + + + + + ), + }, ]} /> ); @@ -57,8 +67,6 @@ const DemoConfigLockedViewModal = () => { const navigation = useDemoAppNavigation>(); - useFreezeProbe('DemoConfigLockedViewModal'); - const headerRightCall = useCallback( () => , [], @@ -74,6 +82,7 @@ const DemoConfigLockedViewModal = () => { { ), }, + { + title: '渲染测试', + element: ( + + + + + ), + }, ]} /> ); @@ -122,8 +140,6 @@ const DemoManualLockedViewModal = () => { const navigation = useDemoAppNavigation>(); - useFreezeProbe('DemoManualLockedViewModal'); - const headerRightCall = useCallback( () => , [], @@ -140,6 +156,7 @@ const DemoManualLockedViewModal = () => { description="这是手动锁定和解锁 Modal 的例子" suggestions={['使用方式设置 navigation.setOptions disableClose 属性']} boundaryConditions={[ + '可以 Locked 的屏幕一定要在配置里写清楚 allowDisableClose: true,否则 disableClose 属性无效', '锁定: navigation.setOptions({\n' + ' disableClose: true,\n' + ' });', @@ -193,6 +210,15 @@ const DemoManualLockedViewModal = () => { ), }, + { + title: '渲染测试', + element: ( + + + + + ), + }, ]} /> ); @@ -201,8 +227,7 @@ const DemoManualLockedViewModal = () => { const DemoRepeatManualLockedViewModal = () => { const navigation = useDemoAppNavigation>(); - - useFreezeProbe('DemoManualLockedViewModal'); + const [locked, setLocked] = useState(true); const headerRightCall = useCallback( () => , @@ -212,8 +237,9 @@ const DemoRepeatManualLockedViewModal = () => { useLayoutEffect(() => { navigation.setOptions({ headerRight: headerRightCall, + disableClose: locked, }); - }, [navigation, headerRightCall]); + }, [navigation, headerRightCall, locked]); return ( { ]} elements={[ { - title: '锁定', + title: '切换锁定', element: ( ), }, { - title: '取消锁定', + title: '关闭', element: ( - ), }, { - title: '关闭', + title: '渲染测试', element: ( - + + + + ), }, ]} @@ -284,16 +301,19 @@ export const LockedModalStack: ModalFlowNavigatorConfig< name: DemoLockedModalRoutes.DemoConfigLockedModal, component: DemoConfigLockedViewModal, translationId: 'Config Locked Modal', + allowDisableClose: true, disableClose: true, }, { name: DemoLockedModalRoutes.DemoManualLockedViewModal, component: DemoManualLockedViewModal, translationId: 'Manual Locked Modal', + allowDisableClose: true, }, { name: DemoLockedModalRoutes.DemoRepeatManualLockedViewModal, component: DemoRepeatManualLockedViewModal, translationId: 'Repeat Manual Locked Modal', + allowDisableClose: true, }, ]; diff --git a/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/DemoTabNavigator.tsx b/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/DemoTabNavigator.tsx index 2e8e18ca418..4587143d533 100644 --- a/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/DemoTabNavigator.tsx +++ b/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/DemoTabNavigator.tsx @@ -24,6 +24,7 @@ const config: TabNavigatorConfig[] = [ tabBarIcon: (focused?: boolean) => focused ? 'CreditCardSolid' : 'CreditCardOutline', translationId: 'wallet__wallet', + freezeOnBlur: true, children: [ { name: DemoHomeTabRoutes.DemoRootHome, @@ -47,6 +48,7 @@ const config: TabNavigatorConfig[] = [ tabBarIcon: (focused?: boolean) => focused ? 'MailOpenMini' : 'EmailOutline', translationId: 'form__me', + freezeOnBlur: true, children: [ { name: DemoMeTabRoutes.DemoRootMe, @@ -60,6 +62,7 @@ const config: TabNavigatorConfig[] = [ tabBarIcon: (focused?: boolean) => focused ? 'ChatGptSolid' : 'ChatGptOutline', translationId: 'form__tabs', + freezeOnBlur: true, children: [ { name: DemoTabsTabRoutes.DemoRootTabs, @@ -73,6 +76,7 @@ const config: TabNavigatorConfig[] = [ tabBarIcon: (focused?: boolean) => focused ? 'CodeBracketSquareMini' : 'CodeBracketMini', translationId: 'form__dev_mode', + freezeOnBlur: true, children: [ { name: DemoDeveloperTabRoutes.DemoRootDeveloper, diff --git a/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootDeveloper.tsx b/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootDeveloper.tsx index 40f9ab6fef8..bdacaa0db6a 100644 --- a/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootDeveloper.tsx +++ b/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootDeveloper.tsx @@ -1,16 +1,17 @@ import { Input } from 'tamagui'; -import { Button } from '@onekeyhq/components'; +import { Button, Stack } from '@onekeyhq/components'; import { Layout } from '../../../utils/Layout'; +import { NavigationFocusTools } from '../../../utils/NavigationTools'; +import { FreezeProbe } from '../../../utils/RenderTools'; import { DemoCreateModalRoutes, RootModalRoutes } from '../../Modal/Routes'; -import { useFreezeProbe } from '../../RenderTools'; import useDemoAppNavigation from '../../useDemoAppNavigation'; import { DemoDeveloperTabRoutes } from '../Routes'; const DemoRootDeveloper = () => { const navigation = useDemoAppNavigation(); - useFreezeProbe('DemoRootDeveloper'); + return ( { ), }, + { + title: '渲染测试', + element: ( + + + + + ), + }, ]} /> ); diff --git a/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootDeveloperOptions.tsx b/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootDeveloperOptions.tsx index 431e57e6fb5..ddc6c95e1e3 100644 --- a/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootDeveloperOptions.tsx +++ b/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootDeveloperOptions.tsx @@ -1,9 +1,11 @@ import { useRoute } from '@react-navigation/core'; -import { Button } from '@onekeyhq/components'; +import { Button, Stack } from '@onekeyhq/components'; import type { PageNavigationProp } from '@onekeyhq/components/src/Navigation'; import { Layout } from '../../../utils/Layout'; +import { NavigationFocusTools } from '../../../utils/NavigationTools'; +import { FreezeProbe } from '../../../utils/RenderTools'; import useDemoAppNavigation from '../../useDemoAppNavigation'; import { DemoDeveloperTabRoutes } from '../Routes'; @@ -55,6 +57,15 @@ const DemoRootDeveloperOptions = () => { ), }, + { + title: '渲染测试', + element: ( + + + + + ), + }, ]} /> ); diff --git a/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootHome.tsx b/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootHome.tsx index 02bdaeecdfb..92b547ad94f 100644 --- a/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootHome.tsx +++ b/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootHome.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import useCookie from 'react-use-cookie'; -import { Button, YStack } from '@onekeyhq/components'; +import { Button, Stack, YStack } from '@onekeyhq/components'; import type { PageNavigationProp } from '@onekeyhq/components/src/Navigation'; import HeaderButtonIcon from '@onekeyhq/components/src/Navigation/Header/HeaderButtonIcon'; import platformEnv from '@onekeyhq/shared/src/platformEnv'; @@ -10,7 +10,8 @@ import { AppSettingKey } from '@onekeyhq/shared/src/storage/appSetting'; import appStorage from '@onekeyhq/shared/src/storage/appStorage'; import { Layout } from '../../../utils/Layout'; -import { useFreezeProbe } from '../../RenderTools'; +import { NavigationFocusTools } from '../../../utils/NavigationTools'; +import { FreezeProbe } from '../../../utils/RenderTools'; import useDemoAppNavigation from '../../useDemoAppNavigation'; import { DemoHomeTabRoutes } from '../Routes'; @@ -35,7 +36,6 @@ const DemoRootHome = () => { const [rrtStatus, changeRRTStatus] = useStorage(AppSettingKey.rrt); - useFreezeProbe('DemoRootHome'); return ( { ), }, + { - title: '开启 ReactRenderTracker', + title: '下一个例子', element: ( + ), + }, + { + title: '渲染测试', + element: ( + + + + + ), + }, + { + title: '开启 ReactRenderTracker', + element: ( + ), }, - { - title: '下一个例子', - element: ( - - ), - }, ]} /> ); diff --git a/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootHomeOptions.tsx b/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootHomeOptions.tsx index d4f844da3de..14bb585534d 100644 --- a/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootHomeOptions.tsx +++ b/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootHomeOptions.tsx @@ -3,12 +3,15 @@ import { useLayoutEffect } from 'react'; import { useNavigation } from '@react-navigation/native'; -import { YStack } from '@onekeyhq/components'; +import { Button, Stack, YStack } from '@onekeyhq/components'; import HeaderButtonGroup from '@onekeyhq/components/src/Navigation/Header/HeaderButtonGroup'; import HeaderButtonIcon from '@onekeyhq/components/src/Navigation/Header/HeaderButtonIcon'; import { Layout } from '../../../utils/Layout'; -import { useFreezeProbe } from '../../RenderTools'; +import { NavigationFocusTools } from '../../../utils/NavigationTools'; +import { FreezeProbe } from '../../../utils/RenderTools'; +import { RootModalRoutes } from '../../Modal/Routes'; +import { DemoRootRoutes } from '../../Routes'; import type { NativeSyntheticEvent, @@ -18,8 +21,6 @@ import type { const DemoRootHomeOptions = () => { const navigation = useNavigation(); - useFreezeProbe('DemoRootHomeOptions'); - useLayoutEffect(() => { navigation.setOptions({ headerRight: () => ( @@ -70,6 +71,30 @@ const DemoRootHomeOptions = () => { ), }, + { + title: '弹出 Modal', + element: ( + + ), + }, + { + title: '渲染测试', + element: ( + + + + + ), + }, ]} /> ); diff --git a/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootHomeSearch.tsx b/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootHomeSearch.tsx index a151da0fb70..ed05601b181 100644 --- a/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootHomeSearch.tsx +++ b/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootHomeSearch.tsx @@ -1,10 +1,11 @@ import { useLayoutEffect } from 'react'; -import { Button, Text } from '@onekeyhq/components'; +import { Button, Stack, Text } from '@onekeyhq/components'; import type { PageNavigationProp } from '@onekeyhq/components/src/Navigation'; import { Layout } from '../../../utils/Layout'; -import { useFreezeProbe } from '../../RenderTools'; +import { NavigationFocusTools } from '../../../utils/NavigationTools'; +import { FreezeProbe } from '../../../utils/RenderTools'; import useDemoAppNavigation from '../../useDemoAppNavigation'; import { DemoHomeTabRoutes } from '../Routes'; @@ -12,14 +13,13 @@ import type { DemoHomeTabParamList } from '../RouteParamTypes'; import type { NativeSyntheticEvent, TextInputChangeEventData, + TextInputSubmitEditingEventData, } from 'react-native'; const DemoRootHomeSearch = () => { const navigation = useDemoAppNavigation>(); - useFreezeProbe('DemoRootHomeSearch'); - useLayoutEffect(() => { navigation.setOptions({ headerSearchBarOptions: { @@ -27,6 +27,9 @@ const DemoRootHomeSearch = () => { inputType: 'text', onChangeText: (event: NativeSyntheticEvent) => console.log('onChangeText', event.nativeEvent.text), + onSearchButtonPress: ( + event: NativeSyntheticEvent, + ) => console.log('onSearchButtonPress', event.nativeEvent.text), }, }); }, [navigation]); @@ -67,6 +70,15 @@ const DemoRootHomeSearch = () => { ), }, + { + title: '渲染测试', + element: ( + + + + + ), + }, ]} /> ); diff --git a/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootMe.tsx b/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootMe.tsx index 973de9869dd..7434497a2d8 100644 --- a/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootMe.tsx +++ b/packages/kit/src/views/Components/stories/NavigatorRoute/Tab/View/DemoRootMe.tsx @@ -1,13 +1,14 @@ -import { Button } from '@onekeyhq/components'; +import { Button, Stack } from '@onekeyhq/components'; import { Layout } from '../../../utils/Layout'; -import { useFreezeProbe } from '../../RenderTools'; +import { NavigationFocusTools } from '../../../utils/NavigationTools'; +import { FreezeProbe } from '../../../utils/RenderTools'; import useDemoAppNavigation from '../../useDemoAppNavigation'; import { DemoDeveloperTabRoutes, DemoTabRoutes } from '../Routes'; const DemoRootMe = () => { const navigation = useDemoAppNavigation(); - useFreezeProbe('DemoRootMe'); + return ( { ), }, + { + title: '渲染测试', + element: ( + + + + + ), + }, ]} /> ); diff --git a/packages/kit/src/views/Components/stories/utils/Layout.tsx b/packages/kit/src/views/Components/stories/utils/Layout.tsx index 5f6a277f69f..0a8f114ca09 100644 --- a/packages/kit/src/views/Components/stories/utils/Layout.tsx +++ b/packages/kit/src/views/Components/stories/utils/Layout.tsx @@ -119,9 +119,7 @@ export function Layout({ )} - - {item.element} - + {item.element} ))} diff --git a/packages/kit/src/views/Components/stories/utils/NavigationTools.tsx b/packages/kit/src/views/Components/stories/utils/NavigationTools.tsx new file mode 100644 index 00000000000..bdf026cf2f2 --- /dev/null +++ b/packages/kit/src/views/Components/stories/utils/NavigationTools.tsx @@ -0,0 +1,44 @@ +import { useEffect } from 'react'; + +import { useIsFocused } from '@react-navigation/core'; + +import { Text } from '@onekeyhq/components'; +import useIsActiveTab from '@onekeyhq/components/src/CollapsibleTabView/hooks/useIsActiveTab'; + +export function NavigationFocusTools({ + componentName, +}: { + componentName: string; +}) { + const isFocused = useIsFocused(); + + useEffect(() => { + console.log( + `<=== NavigationFocus: ${componentName} isFocused: ${ + isFocused ? 'true' : 'false' + }`, + ); + }, [componentName, isFocused]); + + return ( + + {componentName} isFocused: {isFocused ? 'true' : 'false'} + + ); +} + +export function TabsFocusTools({ componentName }: { componentName: string }) { + const isFocused = useIsActiveTab(componentName); + + useEffect(() => { + console.log( + `::> tabs ${componentName} isFocused: ${isFocused ? 'true' : 'false'}`, + ); + }, [componentName, isFocused]); + + return ( + + {componentName} isFocused: {isFocused ? 'true' : 'false'} + + ); +} diff --git a/packages/kit/src/views/Components/stories/NavigatorRoute/RenderTools.tsx b/packages/kit/src/views/Components/stories/utils/RenderTools.tsx similarity index 65% rename from packages/kit/src/views/Components/stories/NavigatorRoute/RenderTools.tsx rename to packages/kit/src/views/Components/stories/utils/RenderTools.tsx index 046069cb9c4..fa45bfa83dd 100644 --- a/packages/kit/src/views/Components/stories/NavigatorRoute/RenderTools.tsx +++ b/packages/kit/src/views/Components/stories/utils/RenderTools.tsx @@ -1,5 +1,7 @@ import { useEffect, useState } from 'react'; +import { Text } from '@onekeyhq/components'; + export function useFreezeProbe( componentName: string, options?: { @@ -19,11 +21,19 @@ export function useFreezeProbe( }, [options?.pause]); useEffect(() => { - console.log(`::> ${componentName} Rerender Count: ${rerenderCount}`); + console.log( + `<== FreezeProbe: ${componentName} Rerender Count: ${rerenderCount}`, + ); }, [componentName, rerenderCount]); + + return rerenderCount; } export function FreezeProbe({ componentName }: { componentName: string }) { - useFreezeProbe(componentName); - return null; + const count = useFreezeProbe(componentName); + return ( + + {componentName} Rerender Count: {count} + + ); } diff --git a/packages/kit/src/views/Tab/Home/HomePageTabs/HeaderView.tsx b/packages/kit/src/views/Tab/Home/HomePageTabs/HeaderView.tsx new file mode 100644 index 00000000000..84e04536bd5 --- /dev/null +++ b/packages/kit/src/views/Tab/Home/HomePageTabs/HeaderView.tsx @@ -0,0 +1,55 @@ +import { useCallback, useMemo, useState } from 'react'; + +import { Button, Stack, Text } from '@onekeyhq/components'; +import type { PageNavigationProp } from '@onekeyhq/components/src/Navigation'; + +import useAppNavigation from '../../../../hooks/useAppNavigation'; +import { TabHomeRoutes } from '../../../../routes/Root/Tab/Home/Routes'; + +import type { TabHomeParamList } from '../../../../routes/Root/Tab/Home/Routes'; + +export default function HomePageHeaderView({ + switchDemoVisible, +}: { + switchDemoVisible: () => void; +}) { + const navigation = useAppNavigation>(); + const [headerHighMode, setHeaderHighMode] = useState(true); + + const headerHeightCall = useCallback(() => { + setHeaderHighMode((pre) => !pre); + }, []); + + const switchDemoVisibleCall = useCallback(() => { + switchDemoVisible?.(); + }, [switchDemoVisible]); + + const onNextPageCall = useCallback(() => { + navigation.push(TabHomeRoutes.TabHomeStack1); + }, [navigation]); + + return useMemo( + () => ( + + Header View Simple + {`Header Height ${headerHighMode.toString()}`} + {headerHighMode && Very high} + + + + + ), + [headerHighMode, headerHeightCall, onNextPageCall, switchDemoVisibleCall], + ); +} diff --git a/packages/kit/src/views/Tab/Home/HomePageTabs/index.tsx b/packages/kit/src/views/Tab/Home/HomePageTabs/index.tsx new file mode 100644 index 00000000000..9a1745e70e1 --- /dev/null +++ b/packages/kit/src/views/Tab/Home/HomePageTabs/index.tsx @@ -0,0 +1,122 @@ +import { memo, useCallback, useMemo, useRef, useState } from 'react'; + +import { useIntl } from 'react-intl'; +import { ScrollView } from 'react-native'; + +import { Tabs, Text } from '@onekeyhq/components'; +import type { ForwardRefHandle } from '@onekeyhq/components/src/CollapsibleTabView/NativeNestedTabView/NestedTabView'; + +import HeaderView from './HeaderView'; +import { HomePageTabsEnum } from './types'; + +function HomePage() { + const tabsViewRef = useRef(null); + const [showDemo3, setShowDemo3] = useState(false); + const intl = useIntl(); + const onRefresh = useCallback(() => { + tabsViewRef?.current?.setRefreshing(true); + }, []); + + const onIndexChange = useCallback((index: number) => {}, []); + + const onPageScrollStateChangeCall = useCallback(() => {}, []); + + const onDemo3VisibleChange = useCallback(() => { + setShowDemo3((pre) => !pre); + }, []); + + const tabDemo1 = useMemo( + () => ( + + + demo1 + + + ), + [intl], + ); + + const tabDemo2 = useMemo( + () => ( + + + demo2 + + + ), + [intl], + ); + + const tabDemo3 = useMemo( + () => ( + + + demo3 + + + ), + [intl], + ); + + const tabsMap = useMemo( + () => ({ + [HomePageTabsEnum.Demo1]: tabDemo1, + [HomePageTabsEnum.Demo2]: tabDemo2, + [HomePageTabsEnum.Demo3]: tabDemo3, + }), + [tabDemo1, tabDemo2, tabDemo3], + ); + + const usedTabs = useMemo(() => { + const defaultTabsKey = Object.keys(tabsMap) as HomePageTabsEnum[]; + + return defaultTabsKey.filter((t) => { + if (t === HomePageTabsEnum.Demo3) { + return !showDemo3; + } + return true; + }); + }, [showDemo3, tabsMap]); + + const tabContents = useMemo( + () => usedTabs.map((t) => tabsMap[t]).filter(Boolean), + [tabsMap, usedTabs], + ); + + return useMemo( + () => ( + } + ref={tabsViewRef} + > + {tabContents} + + ), + [ + onIndexChange, + onPageScrollStateChangeCall, + onRefresh, + tabContents, + onDemo3VisibleChange, + ], + ); +} + +export default memo(HomePage); diff --git a/packages/kit/src/views/Tab/Home/HomePageTabs/types.ts b/packages/kit/src/views/Tab/Home/HomePageTabs/types.ts new file mode 100644 index 00000000000..5d0cccf0e65 --- /dev/null +++ b/packages/kit/src/views/Tab/Home/HomePageTabs/types.ts @@ -0,0 +1,5 @@ +export enum HomePageTabsEnum { + Demo1 = 'Demo1', + Demo2 = 'Demo2', + Demo3 = 'Demo3', +} diff --git a/patches/@react-navigation+native-stack+6.9.14.patch b/patches/@react-navigation+native-stack+6.9.14.patch index b8eca3c3734..d32ebb0f054 100644 --- a/patches/@react-navigation+native-stack+6.9.14.patch +++ b/patches/@react-navigation+native-stack+6.9.14.patch @@ -1,8 +1,5 @@ -diff --git a/node_modules/@react-navigation/native-stack/src/.DS_Store b/node_modules/@react-navigation/native-stack/src/.DS_Store -new file mode 100644 -index 0000000..e69de29 diff --git a/node_modules/@react-navigation/native-stack/src/types.tsx b/node_modules/@react-navigation/native-stack/src/types.tsx -index 206fb0b..cf40587 100644 +index 206fb0b..60d9944 100644 --- a/node_modules/@react-navigation/native-stack/src/types.tsx +++ b/node_modules/@react-navigation/native-stack/src/types.tsx @@ -97,6 +97,8 @@ export type HeaderButtonProps = { @@ -14,12 +11,13 @@ index 206fb0b..cf40587 100644 }; export type HeaderBackButtonProps = HeaderButtonProps & { -@@ -490,6 +492,8 @@ export type NativeStackNavigationOptions = { +@@ -490,6 +492,9 @@ export type NativeStackNavigationOptions = { * Only supported on iOS and Android. */ freezeOnBlur?: boolean; + + disableClose?: boolean; ++ allowDisableClose?: boolean; }; export type NativeStackNavigatorProps = DefaultNavigatorOptions< @@ -52,7 +50,7 @@ index d144a4c..c424d7a 100644 const headerRightElement = headerRight?.({ tintColor, diff --git a/node_modules/@react-navigation/native-stack/src/views/NativeStackView.native.tsx b/node_modules/@react-navigation/native-stack/src/views/NativeStackView.native.tsx -index 5b9fb99..a9d80f6 100644 +index 5b9fb99..04c3c8f 100644 --- a/node_modules/@react-navigation/native-stack/src/views/NativeStackView.native.tsx +++ b/node_modules/@react-navigation/native-stack/src/views/NativeStackView.native.tsx @@ -17,7 +17,7 @@ import { @@ -64,36 +62,34 @@ index 5b9fb99..a9d80f6 100644 import { useSafeAreaFrame, useSafeAreaInsets, -@@ -160,6 +160,7 @@ const SceneView = ({ +@@ -160,6 +160,8 @@ const SceneView = ({ statusBarTranslucent, statusBarColor, freezeOnBlur, + disableClose, ++ allowDisableClose } = options; let { -@@ -170,6 +171,20 @@ const SceneView = ({ +@@ -170,6 +172,17 @@ const SceneView = ({ gestureDirection = presentation === 'card' ? 'horizontal' : 'vertical', } = options; -+ const gestureEnabledOverride = (disableClose && Platform.OS === 'ios') ? false : gestureEnabled; ++ const gestureEnabledOverride = (allowDisableClose && disableClose && Platform.OS === 'ios') ? false : gestureEnabled; ++ let presentationOverride = (allowDisableClose && Platform.OS === 'ios') ? 'modal' : presentation; + -+ const preScreenLocked = previousDescriptor?.options.disableClose && Platform.OS === 'ios'; -+ const currentScreenLocked = disableClose && Platform.OS === 'ios'; -+ let presentationOverride = (currentScreenLocked || preScreenLocked) ? 'modal' : presentation; -+ -+ React.useEffect(()=>{ ++ React.useEffect(() => { + const subscription = BackHandler.addEventListener('hardwareBackPress', () => { -+ if (disableClose) return true; ++ if (disableClose) return true; + }); + + return () => subscription.remove(); -+ },[disableClose]) ++ }, [disableClose]); + if (gestureDirection === 'vertical' && Platform.OS === 'ios') { // for `vertical` direction to work, we need to set `fullScreenGestureEnabled` to `true` // so the screen can be dismissed from any point on screen. -@@ -196,14 +211,14 @@ const SceneView = ({ +@@ -196,14 +209,14 @@ const SceneView = ({ if (index === 0) { // first screen should always be treated as `card`, it resolves problems with no header animation // for navigator with first screen as `modal` and the next as `card` @@ -110,13 +106,11 @@ index 5b9fb99..a9d80f6 100644 // Modals are fullscreen in landscape only on iPhone const isIPhone = Platform.OS === 'ios' && !(Platform.isPad || Platform.isTV); -@@ -251,14 +266,14 @@ const SceneView = ({ - isAndroid +@@ -252,13 +265,13 @@ const SceneView = ({ ? // This prop enables handling of system back gestures on Android // Since we handle them in JS side, we disable this -- false + false - : gestureEnabled -+ false + : gestureEnabledOverride } homeIndicatorHidden={autoHideHomeIndicator} @@ -128,7 +122,7 @@ index 5b9fb99..a9d80f6 100644 stackAnimation={animation} screenOrientation={orientation} statusBarAnimation={statusBarAnimation} -@@ -301,7 +316,7 @@ const SceneView = ({ +@@ -301,7 +314,7 @@ const SceneView = ({