feat: component's updates from Discover page (#3869)

* feat: component's updates from Discover page

* docs: update component stories

* chore: add react-native-view-shot
This commit is contained in:
huhuanming
2023-11-28 14:33:31 +08:00
committed by GitHub
parent 86def58b02
commit 2a6f24b528
45 changed files with 799 additions and 455 deletions

View File

@@ -410,6 +410,8 @@ PODS:
- react-native-tcp-socket (6.0.6):
- CocoaAsyncSocket
- React-Core
- react-native-view-shot (3.1.2):
- React
- react-native-webview (11.24.0):
- React-Core
- React-NativeModulesApple (0.72.5):
@@ -654,6 +656,7 @@ DEPENDENCIES:
- "react-native-slider (from `../../../node_modules/@react-native-community/slider`)"
- react-native-tab-page-view (from `../../../node_modules/react-native-tab-page-view`)
- react-native-tcp-socket (from `../../../node_modules/react-native-tcp-socket`)
- react-native-view-shot (from `../../../node_modules/react-native-view-shot`)
- react-native-webview (from `../../../node_modules/react-native-webview`)
- React-NativeModulesApple (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-perflogger (from `../../../node_modules/react-native/ReactCommon/reactperflogger`)
@@ -815,6 +818,8 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native-tab-page-view"
react-native-tcp-socket:
:path: "../../../node_modules/react-native-tcp-socket"
react-native-view-shot:
:path: "../../../node_modules/react-native-view-shot"
react-native-webview:
:path: "../../../node_modules/react-native-webview"
React-NativeModulesApple:
@@ -944,6 +949,7 @@ SPEC CHECKSUMS:
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
react-native-tab-page-view: 35bba61d5d8036fdf23976439d613a43ef1f041c
react-native-tcp-socket: e724380c910c2e704816ec817ed28f1342246ff7
react-native-view-shot: 4475fde003fe8a210053d1f98fb9e06c1d834e1c
react-native-webview: 6408bc328e65042d240d0ebd62baa76ffd23b1aa
React-NativeModulesApple: 797bc6078d566eef3fb3f74127e6e1d2e945a15f
React-perflogger: cd8886513f68e1c135a1e79d20575c6489641597

View File

@@ -59,6 +59,7 @@
"react-native-svg": "^13.9.0",
"react-native-tab-page-view": "https://github.com/OneKeyHQ/react-native-tab-page-view#90ea54bfb01021c4e54e27cb9fc72a3702df0aa3",
"react-native-tcp-socket": "^6.0.6",
"react-native-view-shot": "3.1.2",
"react-native-webview": "https://github.com/OneKeyHQ/react-native-webview.git#5036d59cba4f5609c32b7eb5dec560e738ce7115",
"react-native-zip-archive": "^6.0.9",
"readable-stream": "^3.6.0",

View File

@@ -53,15 +53,14 @@ function ActionListItem({
opacity={disabled ? 0.5 : 1}
disabled={disabled}
{...(!disabled && {
mb: 2,
hoverStyle: { bg: '$bgHover' },
pressStyle: { bg: '$bgActive' },
focusable: true,
focusStyle: {
outlineColor: '$focusRing',
outlineStyle: 'solid',
outlineWidth: 2,
},
// focusable: true,
// focusStyle: {
// outlineColor: '$focusRing',
// outlineStyle: 'solid',
// outlineWidth: 2,
// },
})}
onPress={handlePress}
>
@@ -86,7 +85,7 @@ function ActionListItem({
);
}
interface IActionListSection {
export interface IActionListSection {
title?: string;
items: IActionListItemProps[];
}

View File

@@ -347,6 +347,7 @@ const icons = {
CommandOutline: () => import("./react/outline/Command"),
CommandKeyOutline: () => import("./react/outline/CommandKey"),
CompassOutline: () => import("./react/outline/Compass"),
CompassCircleOutline: () => import("./react/outline/CompassCircle"),
CompassSquareOutline: () => import("./react/outline/CompassSquare"),
ComputerOutline: () => import("./react/outline/Computer"),
ConsoleOutline: () => import("./react/outline/Console"),
@@ -854,6 +855,7 @@ const icons = {
VolumeUpOutline: () => import("./react/outline/VolumeUp"),
WalletOutline: () => import("./react/outline/Wallet"),
WalletCardOutline: () => import("./react/outline/WalletCard"),
WalletCryptoOutline: () => import("./react/outline/WalletCrypto"),
WatchRoundOutline: () => import("./react/outline/WatchRound"),
WatchSquareOutline: () => import("./react/outline/WatchSquare"),
WcPaperToiletOutline: () => import("./react/outline/WcPaperToilet"),
@@ -1122,6 +1124,7 @@ const icons = {
CommandSolid: () => import("./react/solid/Command"),
CommandKeySolid: () => import("./react/solid/CommandKey"),
CompassSolid: () => import("./react/solid/Compass"),
CompassCircleSolid: () => import("./react/solid/CompassCircle"),
CompassSquareSolid: () => import("./react/solid/CompassSquare"),
ComputerSolid: () => import("./react/solid/Computer"),
ConsoleSolid: () => import("./react/solid/Console"),
@@ -1620,6 +1623,7 @@ const icons = {
VolumeUpSolid: () => import("./react/solid/VolumeUp"),
WalletSolid: () => import("./react/solid/Wallet"),
WalletCardSolid: () => import("./react/solid/WalletCard"),
WalletCryptoSolid: () => import("./react/solid/WalletCrypto"),
WatchRoundSolid: () => import("./react/solid/WatchRound"),
WatchSquareSolid: () => import("./react/solid/WatchSquare"),
WcPaperToiletSolid: () => import("./react/solid/WcPaperToilet"),

View File

@@ -9,6 +9,7 @@ import type { IKeyOfIcons } from './Icons';
import { GetProps, styled, withStaticProperties } from 'tamagui';
import type { Svg, SvgProps } from 'react-native-svg';
import { TextStyle } from 'react-native';
import { View } from '../View';
export type IIconContainerProps = Omit<SvgProps, 'color' | 'style'> & {
name?: IKeyOfIcons;
@@ -68,7 +69,10 @@ const IconContainer = forwardRef(({ name, style }: IIconContainerProps, _) => {
color={componentColor}
/>
) : (
<Suspense fallback={null}>
<Suspense fallback={<View style={{
width: componentWidth,
height: componentHeight,
}} />}>
<IconLoader
width={componentWidth}
height={componentHeight}

View File

@@ -0,0 +1,12 @@
import Svg, { SvgProps, Path } from 'react-native-svg';
const SvgCompassCircle = (props: SvgProps) => (
<Svg fill="none" viewBox="0 0 24 24" accessibilityRole="image" {...props}>
<Path
fill="currentColor"
fillRule="evenodd"
d="M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm12.075-2.075-3.26.89-.89 3.26 3.26-.89.89-3.26Zm.318-2.16a1.5 1.5 0 0 1 1.841 1.842l-1.119 4.105a2 2 0 0 1-1.403 1.403l-4.105 1.12a1.5 1.5 0 0 1-1.842-1.842l1.12-4.105a2 2 0 0 1 1.403-1.403l4.105-1.12Z"
clipRule="evenodd"
/>
</Svg>
);
export default SvgCompassCircle;

View File

@@ -3,13 +3,13 @@ const SvgWallet = (props: SvgProps) => (
<Svg fill="none" viewBox="0 0 24 24" accessibilityRole="image" {...props}>
<Path
fill="currentColor"
fillRule="evenodd"
d="M3 6.5A3.5 3.5 0 0 1 6.5 3h8.75c.966 0 1.75.784 1.75 1.75V8h1a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3h-5a1 1 0 1 1 0-2h5a1 1 0 0 0 1-1v-7a1 1 0 0 0-1-1H6.5c-.537 0-1.045-.12-1.5-.337v.837a1 1 0 1 1-2 0v-4Zm2 0A1.5 1.5 0 0 0 6.5 8H15V5H6.5A1.5 1.5 0 0 0 5 6.5Zm-.008 6.68a2 2 0 0 1 2.016 0l2 1.167A2 2 0 0 1 10 16.074v2.352a2 2 0 0 1-.992 1.727l-2 1.167a2 2 0 0 1-2.016 0l-2-1.167A2 2 0 0 1 2 18.426v-2.352a2 2 0 0 1 .992-1.727l2-1.167ZM8 16.074l-2-1.166-2 1.166v2.352l2 1.166 2-1.166v-2.352Z"
clipRule="evenodd"
d="M15.5 15.75a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z"
/>
<Path
fill="currentColor"
d="M17 14.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z"
fillRule="evenodd"
d="M3 6.5A3.5 3.5 0 0 1 6.5 3h8.088A2.412 2.412 0 0 1 17 5.412V8h1a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3H7a4 4 0 0 1-4-4V6.5Zm2 3.163V17a2 2 0 0 0 2 2h11a1 1 0 0 0 1-1v-7a1 1 0 0 0-1-1H6.5c-.537 0-1.045-.12-1.5-.337ZM5 6.5A1.5 1.5 0 0 0 6.5 8H15V5.412A.412.412 0 0 0 14.588 5H6.5A1.5 1.5 0 0 0 5 6.5Z"
clipRule="evenodd"
/>
</Svg>
);

View File

@@ -0,0 +1,13 @@
import Svg, { SvgProps, Path } from 'react-native-svg';
const SvgWalletCrypto = (props: SvgProps) => (
<Svg fill="none" viewBox="0 0 24 24" accessibilityRole="image" {...props}>
<Path
fill="currentColor"
fillRule="evenodd"
d="M3 6.5A3.5 3.5 0 0 1 6.5 3h8.75c.966 0 1.75.784 1.75 1.75V8h1a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3h-5a1 1 0 1 1 0-2h5a1 1 0 0 0 1-1v-7a1 1 0 0 0-1-1H6.5c-.537 0-1.045-.12-1.5-.337v.837a1 1 0 1 1-2 0v-4Zm2 0A1.5 1.5 0 0 0 6.5 8H15V5H6.5A1.5 1.5 0 0 0 5 6.5Zm-.008 6.68a2 2 0 0 1 2.016 0l2 1.167A2 2 0 0 1 10 16.074v2.352a2 2 0 0 1-.992 1.727l-2 1.167a2 2 0 0 1-2.016 0l-2-1.167A2 2 0 0 1 2 18.426v-2.352a2 2 0 0 1 .992-1.727l2-1.167ZM8 16.074l-2-1.166-2 1.166v2.352l2 1.166 2-1.166v-2.352Z"
clipRule="evenodd"
/>
<Path fill="#000" d="M17 14.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z" />
</Svg>
);
export default SvgWalletCrypto;

View File

@@ -245,6 +245,7 @@ export { default as Color } from './Color';
export { default as ColumnWide } from './ColumnWide';
export { default as CommandKey } from './CommandKey';
export { default as Command } from './Command';
export { default as CompassCircle } from './CompassCircle';
export { default as CompassSquare } from './CompassSquare';
export { default as Compass } from './Compass';
export { default as Computer } from './Computer';
@@ -741,6 +742,7 @@ export { default as VolumeOffMute } from './VolumeOffMute';
export { default as VolumeOff } from './VolumeOff';
export { default as VolumeUp } from './VolumeUp';
export { default as WalletCard } from './WalletCard';
export { default as WalletCrypto } from './WalletCrypto';
export { default as Wallet } from './Wallet';
export { default as WatchRound } from './WatchRound';
export { default as WatchSquare } from './WatchSquare';

View File

@@ -0,0 +1,12 @@
import Svg, { SvgProps, Path } from 'react-native-svg';
const SvgCompassCircle = (props: SvgProps) => (
<Svg fill="none" viewBox="0 0 24 24" accessibilityRole="image" {...props}>
<Path
fill="currentColor"
fillRule="evenodd"
d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm12.524-3.753a1 1 0 0 1 1.228 1.228l-1.12 4.105a1.5 1.5 0 0 1-1.052 1.052l-4.105 1.12a1 1 0 0 1-1.228-1.228l1.12-4.105a1.5 1.5 0 0 1 1.052-1.052l4.105-1.12Z"
clipRule="evenodd"
/>
</Svg>
);
export default SvgCompassCircle;

View File

@@ -4,13 +4,7 @@ const SvgWallet = (props: SvgProps) => (
<Path
fill="currentColor"
fillRule="evenodd"
d="M5.496 12.886a1 1 0 0 1 1.008 0l3 1.75A1 1 0 0 1 10 15.5V19a1 1 0 0 1-.496.864l-3 1.75a1 1 0 0 1-1.008 0l-3-1.75A1 1 0 0 1 2 19v-3.5a1 1 0 0 1 .496-.864l3-1.75ZM4 16.074v2.352l2 1.166 2-1.166v-2.352l-2-1.166-2 1.166Z"
clipRule="evenodd"
/>
<Path
fill="currentColor"
fillRule="evenodd"
d="M3 6.5v5.527l1.488-.868a3 3 0 0 1 3.024 0l3 1.75A3 3 0 0 1 12 15.5V21h6a3 3 0 0 0 3-3v-7a3 3 0 0 0-3-3h-1V5.412A2.412 2.412 0 0 0 14.588 3H6.5A3.5 3.5 0 0 0 3 6.5ZM6.5 8a1.5 1.5 0 1 1 0-3h8.088c.228 0 .412.184.412.412V8H6.5Zm9 8a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"
d="M6.5 3A3.5 3.5 0 0 0 3 6.5V17a4 4 0 0 0 4 4h11a3 3 0 0 0 3-3v-7a3 3 0 0 0-3-3h-1V5.412A2.412 2.412 0 0 0 14.588 3H6.5ZM15 8V5.412A.412.412 0 0 0 14.588 5H6.5a1.5 1.5 0 1 0 0 3H15Zm.5 7.75a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z"
clipRule="evenodd"
/>
</Svg>

View File

@@ -0,0 +1,18 @@
import Svg, { SvgProps, Path } from 'react-native-svg';
const SvgWalletCrypto = (props: SvgProps) => (
<Svg fill="none" viewBox="0 0 24 24" accessibilityRole="image" {...props}>
<Path
fill="currentColor"
fillRule="evenodd"
d="M5.496 12.886a1 1 0 0 1 1.008 0l3 1.75A1 1 0 0 1 10 15.5V19a1 1 0 0 1-.496.864l-3 1.75a1 1 0 0 1-1.008 0l-3-1.75A1 1 0 0 1 2 19v-3.5a1 1 0 0 1 .496-.864l3-1.75ZM4 16.074v2.352l2 1.166 2-1.166v-2.352l-2-1.166-2 1.166Z"
clipRule="evenodd"
/>
<Path
fill="#000"
fillRule="evenodd"
d="M3 6.5v5.527l1.488-.868a3 3 0 0 1 3.024 0l3 1.75A3 3 0 0 1 12 15.5V21h6a3 3 0 0 0 3-3v-7a3 3 0 0 0-3-3h-1V5.412A2.412 2.412 0 0 0 14.588 3H6.5A3.5 3.5 0 0 0 3 6.5ZM6.5 8a1.5 1.5 0 1 1 0-3h8.088c.228 0 .412.184.412.412V8H6.5Zm9 8a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"
clipRule="evenodd"
/>
</Svg>
);
export default SvgWalletCrypto;

View File

@@ -245,6 +245,7 @@ export { default as Color } from './Color';
export { default as ColumnWide } from './ColumnWide';
export { default as CommandKey } from './CommandKey';
export { default as Command } from './Command';
export { default as CompassCircle } from './CompassCircle';
export { default as CompassSquare } from './CompassSquare';
export { default as Compass } from './Compass';
export { default as Computer } from './Computer';
@@ -741,6 +742,7 @@ export { default as VolumeOffMute } from './VolumeOffMute';
export { default as VolumeOff } from './VolumeOff';
export { default as VolumeUp } from './VolumeUp';
export { default as WalletCard } from './WalletCard';
export { default as WalletCrypto } from './WalletCrypto';
export { default as Wallet } from './Wallet';
export { default as WatchRound } from './WatchRound';
export { default as WatchSquare } from './WatchSquare';

View File

@@ -65,6 +65,7 @@ export const IconButton = (props: IIconButtonProps) => {
{...(variant === 'tertiary' && {
m: negativeMargin,
})}
{...(size === 'small' && { hitSlop: 8 })}
{...sharedFrameStyles}
{...rest}
>

View File

@@ -10,7 +10,7 @@ import { Text } from '../Text';
import { getSharedInputStyles } from './sharedStyles';
import type { IKeyOfIcons } from '../Icon';
import type { GetProps } from 'tamagui';
import type { ColorTokens, GetProps } from 'tamagui';
type ITMInputProps = GetProps<typeof TMInput>;
@@ -21,6 +21,7 @@ export type IInputProps = {
error?: boolean;
addOns?: {
iconName?: IKeyOfIcons;
iconColor?: ColorTokens;
label?: string;
onPress?: () => void;
loading?: boolean;
@@ -75,6 +76,7 @@ function BaseInput(
} = SIZE_MAPPINGS[size];
const sharedStyles = getSharedInputStyles({ disabled, editable, error });
return (
<Group
orientation="horizontal"
@@ -140,8 +142,20 @@ function BaseInput(
borderColor={sharedStyles.borderColor}
bg={sharedStyles.backgroundColor}
disabled={disabled}
disablePassBorderRadius="start"
>
{addOns.map(({ iconName, label, onPress, loading }) => (
{addOns.map(({ iconName, iconColor, label, onPress, loading }) => {
const getIconColor = () => {
if (disabled) {
return '$iconDisabled';
}
if (iconColor) {
return iconColor;
}
return '$iconSubdued';
};
return (
<Group.Item>
<XStack
onPress={onPress}
@@ -168,7 +182,7 @@ function BaseInput(
iconName && (
<Icon
name={iconName}
color={disabled ? '$iconDisabled' : '$icon'}
color={getIconColor()}
size={size === 'small' ? '$5' : '$6'}
/>
)
@@ -178,14 +192,15 @@ function BaseInput(
userSelect="none"
variant={size === 'small' ? '$bodyMd' : '$bodyLg'}
ml={iconName ? '$2' : '$0'}
color={sharedStyles.color}
color={disabled ? '$textDisabled' : '$textSubdued'}
>
{label}
</Text>
)}
</XStack>
</Group.Item>
))}
);
})}
</Group>
</Group.Item>
)}

View File

@@ -211,6 +211,7 @@ function ListItem(props: IListItemProps) {
focusable: true,
focusStyle: {
outlineWidth: 2,
outlineOffset: -2,
outlineStyle: 'solid',
outlineColor: '$focusRing',
},

View File

@@ -3,7 +3,7 @@ import { IconButton } from '../../IconButton';
import type { IIconButtonProps } from '../../IconButton';
function HeaderIconButton(props: IIconButtonProps) {
return <IconButton variant="tertiary" {...props} />;
return <IconButton variant="tertiary" focusStyle={undefined} {...props} />;
}
export default HeaderIconButton;

View File

@@ -13,6 +13,7 @@ import type {
type IHeaderSearchBarProps = {
height?: string;
autoFocus?: boolean;
/**
* A callback that gets called when search bar has lost focus
*/
@@ -38,6 +39,7 @@ type IHeaderSearchBarProps = {
};
function HeaderSearchBar({
autoFocus,
onBlur,
onFocus,
onChangeText,
@@ -93,6 +95,7 @@ function HeaderSearchBar({
{...(media.gtMd && {
size: 'small',
})}
autoFocus={autoFocus}
onBlur={onBlurCallback}
onFocus={onFocusCallback}
onChangeText={handleChangeCallback}

View File

@@ -133,6 +133,7 @@ function HeaderView({
</Stack>
{!!headerSearchBarOptions && (
<HeaderSearchBar
autoFocus={headerSearchBarOptions?.autoFocus}
placeholder={headerSearchBarOptions?.placeholder}
onChangeText={headerSearchBarOptions?.onChangeText}
onBlur={headerSearchBarOptions?.onBlur}

View File

@@ -41,15 +41,21 @@ export function RootModalNavigator<RouteName extends string>({
);
const insets = useSafeAreaInsets();
// iOS Pad Modal not is full screen
const modalStyle =
PlatformEnv.isNative && !PlatformEnv.isNativeIOSPad
? {
paddingBottom: insets.bottom,
bg: '$bgApp',
}
: {};
return (
<Stack
flex={1}
paddingBottom={
// iOS Pad Modal not is full screen
PlatformEnv.isNative && !PlatformEnv.isNativeIOSPad ? insets.bottom : 0
}
paddingLeft={insets.left}
paddingRight={insets.right}
{...modalStyle}
>
<ThemeProvider value={TransparentModalTheme}>
<ModalStack.Navigator screenOptions={screenOptions}>

View File

@@ -1,5 +1,6 @@
import type { ComponentType } from 'react';
import type { IActionListSection } from '../../ActionList';
import type { IKeyOfIcons } from '../../Icon';
import type { ILocaleIds } from '../../locale';
import type { RouteProp } from '@react-navigation/core/lib/typescript/src/types';
@@ -27,6 +28,7 @@ export interface ITabNavigatorConfig<RouteName extends string> {
freezeOnBlur?: boolean;
disable?: boolean;
tabBarStyle?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
actionList?: IActionListSection[];
}
export interface ICommonNavigatorConfig<

View File

@@ -84,6 +84,9 @@ export interface ISearchBarProps {
*/
shouldShowHintSearchIcon?: boolean;
hideNavigationBar?: boolean;
hideWhenScrolling?: boolean;
autoFocus?: boolean;
cancelButtonText?: string;
}
export type IStackHeaderProps = {

View File

@@ -14,8 +14,9 @@ import { Portal } from '../../../Portal';
import useProviderSideBarValue from '../../../Provider/hooks/useProviderSideBarValue';
import { YStack } from '../../../Stack';
import { TabItem } from './TabItem';
import { DesktopTabItem } from './DesktopTabItem';
import type { IActionListSection } from '../../../ActionList';
import type { IKeyOfIcons } from '../../../Icon';
import type { ITabNavigatorExtraConfig } from '../../Navigator/types';
import type {
@@ -33,7 +34,9 @@ function TabItemView({
isActive: boolean;
route: NavigationState['routes'][0];
onPress: () => void;
options: BottomTabNavigationOptions;
options: BottomTabNavigationOptions & {
actionList?: IActionListSection[];
};
isCollapse?: boolean;
}) {
useMemo(() => {
@@ -46,7 +49,7 @@ function TabItemView({
}, [options]);
const contentMemo = useMemo(
() => (
<TabItem
<DesktopTabItem
onPress={onPress}
aria-current={isActive ? 'page' : undefined}
selected={isActive}
@@ -54,6 +57,7 @@ function TabItemView({
// @ts-expect-error
icon={options?.tabBarIcon?.(isActive) as IKeyOfIcons}
label={(options.tabBarLabel ?? route.name) as string}
actionList={options.actionList}
/>
),
[isActive, onPress, options, route.name],
@@ -104,6 +108,8 @@ export function DesktopLeftSideBar({
if (route.name === extraConfig?.name) {
return (
<YStack
flex={1}
key={route.key}
onPress={() => {
// Avoid re-rendering by checking if it's the current route.
if (state.routeNames[state.index] !== extraConfig?.name) {
@@ -169,10 +175,15 @@ export function DesktopLeftSideBar({
/>
)}
<YStack
testID="Desktop-AppSideBar-Content-Container"
flex={1}
pt={platformEnv.isDesktopMac ? undefined : '$3'}
px="$3"
pb="$3"
testID="Desktop-AppSideBar-Content-Container"
// Need to replaced by HeaderHeightContext
pt={platformEnv.isDesktopMac ? undefined : '$3'}
$platform-web={{
h: platformEnv.isDesktopMac ? 'calc(100vh - 64px)' : '100vh',
}}
>
{tabs}
</YStack>

View File

@@ -0,0 +1,104 @@
import { ActionList, type IActionListSection } from '../../../ActionList';
import { Avatar } from '../../../Avatar';
import { Icon } from '../../../Icon';
import { IconButton } from '../../../IconButton';
import { Stack, XStack } from '../../../Stack';
import { Text } from '../../../Text';
import type { IKeyOfIcons } from '../../../Icon';
import type { Animated, StyleProp, ViewStyle } from 'react-native';
import type { AvatarImage, GetProps } from 'tamagui';
export interface IDesktopTabItemProps {
icon?: IKeyOfIcons;
avatarSrc?: GetProps<typeof AvatarImage>['src'];
label?: string;
selected?: boolean;
tabBarStyle?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
actionList?: IActionListSection[];
onActionListOpenChange?: (isOpen: boolean) => void;
}
export function DesktopTabItem(
props: IDesktopTabItemProps & GetProps<typeof Stack>,
) {
const {
icon,
label,
selected,
tabBarStyle,
actionList,
avatarSrc,
onActionListOpenChange,
...rest
} = props;
return (
<XStack
alignItems="center"
py="$1.5"
$gtMd={{
flexDirection: 'row',
px: '$2',
bg: selected ? '$bgActive' : undefined,
borderRadius: '$2',
}}
style={tabBarStyle as ViewStyle}
{...rest}
>
{icon && (
<Icon
flexShrink={0}
name={icon}
color={selected ? '$iconActive' : '$iconSubdued'}
size="$5"
/>
)}
{avatarSrc && (
<Avatar borderRadius="$1" size="$4.5" m="$px">
<Avatar.Image src={avatarSrc} />
<Avatar.Fallback>
<Icon
size="$4.5"
name="GlobusOutline"
color={selected ? '$iconActive' : '$iconSubdued'}
/>
</Avatar.Fallback>
</Avatar>
)}
{label && (
<Text
flex={1}
numberOfLines={1}
ml="$2"
color="$text"
variant="$bodyMd"
userSelect="none"
>
{label}
</Text>
)}
{actionList && (
<ActionList
title="Action List"
placement="right-start"
onOpenChange={onActionListOpenChange}
renderTrigger={
selected && (
<Stack>
<IconButton
size="small"
icon="DotHorOutline"
variant="tertiary"
focusStyle={undefined}
p="$0.5"
m={-3}
/>
</Stack>
)
}
sections={actionList}
/>
)}
</XStack>
);
}

View File

@@ -7,7 +7,7 @@ import { useIsKeyboardShown } from '../../../hooks';
import useSafeAreaInsets from '../../../Provider/hooks/useSafeAreaInsets';
import { Stack } from '../../../Stack';
import { TabItem } from './TabItem';
import { MobileTabItem } from './MobileTabItem';
import type { IKeyOfIcons } from '../../../Icon';
import type { BottomTabBarProps } from '@react-navigation/bottom-tabs/src/types';
@@ -51,7 +51,7 @@ export default function MobileBottomTabBar({
};
const renderItemContent = (renderActive: boolean) => (
<TabItem
<MobileTabItem
testID="Mobile-AppTabBar-TabItem-Icon"
// @ts-expect-error
icon={options?.tabBarIcon?.(renderActive) as IKeyOfIcons}

View File

@@ -1,44 +1,31 @@
import { type GetProps } from 'tamagui';
import { Icon } from '../../../Icon';
import { Stack } from '../../../Stack';
import { YStack } from '../../../Stack';
import { Text } from '../../../Text';
import type { IKeyOfIcons } from '../../../Icon';
import type { Animated, StyleProp, ViewStyle } from 'react-native';
interface ITabItemProps {
interface IMobileTabItemProps {
icon?: IKeyOfIcons;
label?: string;
selected?: boolean;
tabBarStyle?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
}
export function TabItem(props: ITabItemProps & GetProps<typeof Stack>) {
export function MobileTabItem(
props: IMobileTabItemProps & GetProps<typeof YStack>,
) {
const { icon, label, selected, tabBarStyle, ...rest } = props;
return (
<Stack
alignItems="center"
py="$1.5"
$gtMd={{
flexDirection: 'row',
px: '$2',
bg: selected ? '$bgActive' : undefined,
borderRadius: '$2',
}}
style={tabBarStyle as ViewStyle}
{...rest}
>
<YStack alignItems="center" py="$1.5" {...rest}>
{icon && (
<Icon
flexShrink={0}
name={icon}
color={selected ? '$iconActive' : '$iconSubdued'}
size="$7"
$gtMd={{
size: '$5',
}}
/>
)}
{label && (
@@ -47,17 +34,11 @@ export function TabItem(props: ITabItemProps & GetProps<typeof Stack>) {
mt="$0.5"
variant="$headingXxs"
color={selected ? '$text' : '$textSubdued'}
$gtMd={{
mt: '$0',
ml: '$2',
color: '$text',
variant: '$bodyMd',
}}
userSelect="none"
>
{label}
</Text>
)}
</Stack>
</YStack>
);
}

View File

@@ -1,8 +1,8 @@
import { useEffect, useRef } from 'react';
import { useLayoutEffect } from 'react';
import { useNavigation } from '@react-navigation/native';
import { navigationRef } from '../Navigation/Navigator/NavigationContainer';
import { Stack } from '../Stack';
import type { IStackNavigationOptions } from '../Navigation';
@@ -10,18 +10,9 @@ export type IPageHeaderProps = IStackNavigationOptions;
export function PageHeader(props: IPageHeaderProps) {
const navigation = useNavigation();
const ref = useRef<IPageHeaderProps | undefined>();
useEffect(() => {
if (!ref.current) {
ref.current = navigationRef.current?.getCurrentOptions();
}
useLayoutEffect(() => {
navigation.setOptions(props);
return () => {
if (ref.current) {
navigation.setOptions(ref.current);
}
};
}, [navigation, props]);
return null;
return <Stack />;
}

View File

@@ -5,7 +5,7 @@ import type { ProgressProps as TMProgressProps } from 'tamagui';
type IProgressProps = TMProgressProps;
export const Progress = ({ size, ...props }: IProgressProps) => (
<TMProgress value={50} backgroundColor="$neutral5" {...props} h="$1">
<TMProgress backgroundColor="$neutral5" h="$0.5" {...props}>
<TMProgress.Indicator
animation="quick"
backgroundColor="$bgPrimary"

View File

@@ -0,0 +1,158 @@
import { forwardRef, useCallback, useMemo } from 'react';
import type { ForwardedRef } from 'react';
import { useProps, useStyle } from '@tamagui/core';
import { PageHeaderView } from 'react-native-tab-page-view';
import { useThemeValue } from '../Provider/hooks/useThemeValue';
import type { StackStyleProps, TextStyleProps } from '@tamagui/web/types/types';
import type { GetProps } from 'tamagui';
export type IHeaderProps = Omit<
GetProps<typeof PageHeaderView>,
| 'style'
| 'itemContainerStyle'
| 'itemTitleStyle'
| 'itemTitleNormalStyle'
| 'itemTitleSelectedStyle'
| 'cursorStyle'
> &
StackStyleProps & {
style?: StackStyleProps;
contentContainerStyle?: StackStyleProps;
scrollContainerStyle?: StackStyleProps;
containerStyle?: StackStyleProps;
itemContainerStyle?: StackStyleProps;
itemTitleStyle?: TextStyleProps;
itemTitleNormalStyle?: TextStyleProps & { color: string };
itemTitleSelectedStyle?: TextStyleProps & { color: string };
cursorStyle?: StackStyleProps;
};
const HeaderComponent = (
{
style,
titleFromItem = (item: { title: string }) => item.title,
contentContainerStyle = {},
scrollContainerStyle = {},
containerStyle = {},
itemContainerStyle = { px: '$3', mr: '$2' },
itemTitleStyle = { fontSize: 16 },
itemTitleNormalStyle = { color: '$textSubdued' },
itemTitleSelectedStyle = { color: '$text' },
cursorStyle = {
left: '$3',
right: '$3',
h: '$0.5',
bg: '$text',
},
...props
}: IHeaderProps,
ref: ForwardedRef<PageHeaderView>,
) => {
const normalColor = itemTitleNormalStyle.color;
const selectedColor = itemTitleSelectedStyle.color;
const [rawNormalColor, rawSelectedColor] = useThemeValue(
// @ts-expect-error
[normalColor, selectedColor],
undefined,
true,
);
const data = useMemo(
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
() => props.data,
// eslint-disable-next-line react-hooks/exhaustive-deps
[props.data, rawNormalColor, rawSelectedColor],
);
const reloadWebPxNumber = useCallback((value: any) => {
if (typeof value === 'string') {
return Number(value?.replace(/px/, ''));
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return value;
}, []);
const rawProps = useProps(props, {
resolveValues: 'value',
});
const rawStyle = useStyle(
{ ...{ h: '$14', bg: '$bgApp' }, ...style } as Record<string, unknown>,
{
resolveValues: 'value',
},
);
const rawContentContainerStyle = useStyle(
contentContainerStyle as Record<string, unknown>,
{
resolveValues: 'value',
},
);
const rawScrollContainerStyle = useStyle(
scrollContainerStyle as Record<string, unknown>,
{
resolveValues: 'value',
},
);
const rawContainerStyle = useStyle(
containerStyle as Record<string, unknown>,
{
resolveValues: 'value',
},
);
const rawItemContainerStyle = {
...useStyle(itemContainerStyle as Record<string, unknown>, {
resolveValues: 'value',
}),
...{ flex: itemContainerStyle.flex },
};
const rawItemTitleStyle = {
...useStyle(itemTitleStyle as Record<string, unknown>, {
resolveValues: 'value',
}),
...{ fontSize: itemTitleStyle.fontSize },
};
const rawItemTitleNormalStyle = {
...useStyle(itemTitleNormalStyle as Record<string, unknown>, {
resolveValues: 'value',
}),
...{
color: rawNormalColor,
fontSize: itemTitleNormalStyle.fontSize ?? itemTitleStyle.fontSize,
},
};
const rawItemTitleSelectedStyle = {
...useStyle(itemTitleSelectedStyle as Record<string, unknown>, {
resolveValues: 'value',
}),
...{
color: rawSelectedColor,
fontSize: itemTitleSelectedStyle.fontSize ?? itemTitleStyle.fontSize,
},
};
const rawCursorStyle = useStyle(cursorStyle as Record<string, unknown>, {
resolveValues: 'value',
});
rawCursorStyle.left = reloadWebPxNumber(rawCursorStyle?.left);
rawCursorStyle.right = reloadWebPxNumber(rawCursorStyle?.right);
rawCursorStyle.width = reloadWebPxNumber(rawCursorStyle?.width);
return (
<PageHeaderView
ref={ref}
titleFromItem={titleFromItem}
style={rawStyle as any}
contentContainerStyle={rawContentContainerStyle}
scrollContainerStyle={rawScrollContainerStyle}
containerStyle={rawContainerStyle}
itemContainerStyle={rawItemContainerStyle}
itemTitleStyle={rawItemTitleStyle}
itemTitleNormalStyle={rawItemTitleNormalStyle}
itemTitleSelectedStyle={rawItemTitleSelectedStyle}
cursorStyle={rawCursorStyle}
data={data}
{...rawProps}
/>
);
};
export const Header = forwardRef(HeaderComponent);

View File

@@ -1,24 +1,195 @@
import {
PageContentView,
PageHeaderView,
PageManager,
SelectedLabel,
} from 'react-native-tab-page-view';
import { forwardRef, useCallback, useMemo, useRef, useState } from 'react';
import type { ReactElement } from 'react';
PageHeaderView.defaultProps = {
...PageHeaderView.defaultProps,
titleFromItem: (item: any) => (item as { title: string }).title,
itemContainerStyle: { paddingHorizontal: 12, marginRight: 8 },
itemTitleStyle: { fontSize: 15 },
itemTitleNormalStyle: { color: '#333' },
itemTitleSelectedStyle: { color: 'orange', fontSize: 17 },
cursorStyle: {
width: null,
// width: 30,
// borderRadius: 1,
height: 2,
backgroundColor: 'orange',
import { PageContentView, PageManager } from 'react-native-tab-page-view';
import { withStaticProperties } from 'tamagui';
import platformEnv from '@onekeyhq/shared/src/platformEnv';
import { ScrollView } from '../ScrollView';
import { Stack } from '../Stack';
import { Header } from './Header';
import type { IHeaderProps } from './Header';
import type { IScrollViewProps, IScrollViewRef } from '../ScrollView';
type IPageType = ({
onContentSizeChange,
}: {
onContentSizeChange: (width: number, height: number) => void;
}) => ReactElement | null;
export interface ITabProps extends IScrollViewProps {
data: { title: string; page: IPageType }[];
initialScrollIndex?: number;
ListHeaderComponent?: ReactElement;
headerProps?: IHeaderProps;
}
const TabComponent = ({
data,
initialScrollIndex,
ListHeaderComponent,
headerProps,
...props
}: ITabProps) => {
const [contentHeight, setContentHeight] = useState<number | undefined>(1);
const scrollViewRef = useRef<IScrollViewRef | null>(null);
const pageContainerRef = useRef<any | null>(null);
const stickyConfig = useMemo(
() => ({
lastIndex: -1,
scrollViewHeight: 0,
headerViewY: 0,
headerViewHeight: 0,
data: data.map(() => ({
contentHeight: 0,
contentOffsetY: 0,
})),
}),
[data],
);
const reloadContentHeight = useCallback(
(index: number) => {
if (stickyConfig.scrollViewHeight * stickyConfig.headerViewHeight <= 0) {
return;
}
const minHeight =
stickyConfig.scrollViewHeight - stickyConfig.headerViewHeight;
const height = Math.max(
stickyConfig.data[index].contentHeight,
minHeight,
);
if (platformEnv.isNative) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
pageContainerRef?.current?.setNativeProps?.({
height,
});
} else {
if (height === contentHeight) {
return;
}
setContentHeight(height);
}
},
[stickyConfig, pageContainerRef, contentHeight],
);
const pageManagerProps = useMemo(
() => ({
data,
initialScrollIndex,
onSelectedPageIndex: (index: number) => {
if (index >= stickyConfig.data.length) {
return;
}
reloadContentHeight(index);
const { contentOffsetY } = stickyConfig.data[index];
const lastContentOffsetY =
stickyConfig.data?.[stickyConfig.lastIndex]?.contentOffsetY ?? 0;
if (
Math.round(lastContentOffsetY) < Math.round(stickyConfig.headerViewY)
) {
stickyConfig.data[index].contentOffsetY = lastContentOffsetY;
} else if (
Math.round(contentOffsetY) <= Math.round(stickyConfig.headerViewY)
) {
stickyConfig.data[index].contentOffsetY = stickyConfig.headerViewY;
}
// Need to wait for contentHeight to be updated on android and web
setTimeout(() => {
if (platformEnv.isNative) {
scrollViewRef?.current?.setNativeProps({
contentOffset: { y: stickyConfig.data[index].contentOffsetY },
});
} else {
scrollViewRef?.current?.scrollTo({
y: stickyConfig.data[index].contentOffsetY,
animated: false,
});
}
});
stickyConfig.lastIndex = index;
},
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[stickyConfig, data, initialScrollIndex],
);
const pageManager = useMemo(
() => new PageManager(pageManagerProps),
[pageManagerProps],
);
const renderContentItem = useCallback(
({
item,
index,
}: {
item: {
page: IPageType;
};
index: number;
}) => (
<item.page
onContentSizeChange={(_: number, height: number) => {
stickyConfig.data[index].contentHeight = height;
if (index === pageManager.pageIndex) {
reloadContentHeight(index);
}
}}
/>
),
[stickyConfig.data, pageManager, reloadContentHeight],
);
const Content = pageManager.renderContentView;
return (
<ScrollView
ref={scrollViewRef}
onLayout={(event) => {
stickyConfig.scrollViewHeight = event.nativeEvent.layout.height;
}}
stickyHeaderIndices={[1]}
scrollEventThrottle={16}
onScroll={(event) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
stickyConfig.data[pageManager.pageIndex].contentOffsetY = (
event as any
).nativeEvent.contentOffset.y;
}}
{...props}
>
{ListHeaderComponent}
<Header
ref={pageManager.headerView}
{...pageManagerProps}
{...headerProps}
onLayout={(event) => {
stickyConfig.headerViewHeight = event.nativeEvent.layout.height;
}}
onSelectedPageIndex={(pageIndex: number) => {
pageManager?.contentView?.current?.scrollPageIndex(pageIndex);
}}
/>
<Stack
ref={pageContainerRef}
onLayout={(event) => {
stickyConfig.headerViewY =
event.nativeEvent.layout.y - stickyConfig.headerViewHeight;
}}
h={contentHeight}
>
<Content
windowSize={5}
scrollEnabled={platformEnv.isNative}
shouldSelectedPageAnimation={platformEnv.isNative}
renderItem={renderContentItem}
/>
</Stack>
</ScrollView>
);
};
export { PageHeaderView, PageContentView, PageManager, SelectedLabel };
export const Tab = withStaticProperties(forwardRef(TabComponent), {
Header,
Manager: PageManager,
Content: PageContentView,
});

View File

@@ -0,0 +1,55 @@
import { Children, cloneElement, isValidElement } from 'react';
import { Icon } from '../Icon';
import { Stack, XStack } from '../Stack';
import { Text } from '../Text';
import type { IIconProps } from '../Icon';
import type { StackProps, XStackProps } from 'tamagui';
interface IUnOrderedListItemProps extends XStackProps {
icon?: IIconProps['name'];
iconProps?: IIconProps;
}
export function UnOrderedListItem({
children,
icon,
iconProps,
...rest
}: IUnOrderedListItemProps) {
return (
<XStack tag="li" role="listitem" {...rest}>
<XStack w="$5" h="$6" justifyContent="center" alignItems="center">
{icon ? (
<Icon name={icon} {...iconProps} />
) : (
<XStack w="$1.5" h="$1.5" borderRadius="$full" bg="$textSubdued" />
)}
</XStack>
<Text pl="$2" tag="p" variant="$bodyLg">
{children}
</Text>
</XStack>
);
}
export function UnOrderedList({ children, ...rest }: StackProps) {
let isFirstItem = true;
const enhanceChildren = Children.map(children, (child) => {
if (isValidElement<IUnOrderedListItemProps>(child) && isFirstItem) {
isFirstItem = false;
return child;
}
return cloneElement(child, { pt: '$1' });
});
return (
<Stack p="$0" m="$0" tag="ul" role="list" {...rest}>
{enhanceChildren}
</Stack>
);
}
UnOrderedList.Item = UnOrderedListItem;

View File

@@ -23,6 +23,7 @@ export * from './Select';
export * from './Dialog';
export * from './ActionList';
export * from './Slider';
export * from './UnorderedList';
export * from './Popover';
export * from './Shortcut';
export * from './Progress';

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path fill="currentColor" fill-rule="evenodd"
d="M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm12.075-2.075-3.26.89-.89 3.26 3.26-.89.89-3.26Zm.318-2.16a1.5 1.5 0 0 1 1.841 1.842l-1.119 4.105a2 2 0 0 1-1.403 1.403l-4.105 1.12a1.5 1.5 0 0 1-1.842-1.842l1.12-4.105a2 2 0 0 1 1.403-1.403l4.105-1.12Z"
clip-rule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 495 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path fill="currentColor" fill-rule="evenodd"
d="M3 6.5A3.5 3.5 0 0 1 6.5 3h8.75c.966 0 1.75.784 1.75 1.75V8h1a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3h-5a1 1 0 1 1 0-2h5a1 1 0 0 0 1-1v-7a1 1 0 0 0-1-1H6.5c-.537 0-1.045-.12-1.5-.337v.837a1 1 0 1 1-2 0v-4Zm2 0A1.5 1.5 0 0 0 6.5 8H15V5H6.5A1.5 1.5 0 0 0 5 6.5Zm-.008 6.68a2 2 0 0 1 2.016 0l2 1.167A2 2 0 0 1 10 16.074v2.352a2 2 0 0 1-.992 1.727l-2 1.167a2 2 0 0 1-2.016 0l-2-1.167A2 2 0 0 1 2 18.426v-2.352a2 2 0 0 1 .992-1.727l2-1.167ZM8 16.074l-2-1.166-2 1.166v2.352l2 1.166 2-1.166v-2.352Z"
clip-rule="evenodd" />
<path fill="#000" d="M17 14.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z" />
</svg>

After

Width:  |  Height:  |  Size: 740 B

View File

@@ -1,6 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path fill="currentColor" d="M15.5 15.75a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z" />
<path fill="currentColor" fill-rule="evenodd"
d="M3 6.5A3.5 3.5 0 0 1 6.5 3h8.75c.966 0 1.75.784 1.75 1.75V8h1a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3h-5a1 1 0 1 1 0-2h5a1 1 0 0 0 1-1v-7a1 1 0 0 0-1-1H6.5c-.537 0-1.045-.12-1.5-.337v.837a1 1 0 1 1-2 0v-4Zm2 0A1.5 1.5 0 0 0 6.5 8H15V5H6.5A1.5 1.5 0 0 0 5 6.5Zm-.008 6.68a2 2 0 0 1 2.016 0l2 1.167A2 2 0 0 1 10 16.074v2.352a2 2 0 0 1-.992 1.727l-2 1.167a2 2 0 0 1-2.016 0l-2-1.167A2 2 0 0 1 2 18.426v-2.352a2 2 0 0 1 .992-1.727l2-1.167ZM8 16.074l-2-1.166-2 1.166v2.352l2 1.166 2-1.166v-2.352Z"
d="M3 6.5A3.5 3.5 0 0 1 6.5 3h8.088A2.412 2.412 0 0 1 17 5.412V8h1a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3H7a4 4 0 0 1-4-4V6.5Zm2 3.163V17a2 2 0 0 0 2 2h11a1 1 0 0 0 1-1v-7a1 1 0 0 0-1-1H6.5c-.537 0-1.045-.12-1.5-.337ZM5 6.5A1.5 1.5 0 0 0 6.5 8H15V5.412A.412.412 0 0 0 14.588 5H6.5A1.5 1.5 0 0 0 5 6.5Z"
clip-rule="evenodd" />
<path fill="currentColor" d="M17 14.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z" />
</svg>

Before

Width:  |  Height:  |  Size: 748 B

After

Width:  |  Height:  |  Size: 567 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path fill="currentColor" fill-rule="evenodd"
d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm12.524-3.753a1 1 0 0 1 1.228 1.228l-1.12 4.105a1.5 1.5 0 0 1-1.052 1.052l-4.105 1.12a1 1 0 0 1-1.228-1.228l1.12-4.105a1.5 1.5 0 0 1 1.052-1.052l4.105-1.12Z"
clip-rule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 413 B

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path fill="currentColor" fill-rule="evenodd"
d="M5.496 12.886a1 1 0 0 1 1.008 0l3 1.75A1 1 0 0 1 10 15.5V19a1 1 0 0 1-.496.864l-3 1.75a1 1 0 0 1-1.008 0l-3-1.75A1 1 0 0 1 2 19v-3.5a1 1 0 0 1 .496-.864l3-1.75ZM4 16.074v2.352l2 1.166 2-1.166v-2.352l-2-1.166-2 1.166Z"
clip-rule="evenodd" />
<path fill="#000" fill-rule="evenodd"
d="M3 6.5v5.527l1.488-.868a3 3 0 0 1 3.024 0l3 1.75A3 3 0 0 1 12 15.5V21h6a3 3 0 0 0 3-3v-7a3 3 0 0 0-3-3h-1V5.412A2.412 2.412 0 0 0 14.588 3H6.5A3.5 3.5 0 0 0 3 6.5ZM6.5 8a1.5 1.5 0 1 1 0-3h8.088c.228 0 .412.184.412.412V8H6.5Zm9 8a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"
clip-rule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 743 B

View File

@@ -1,8 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path fill="currentColor" fill-rule="evenodd"
d="M5.496 12.886a1 1 0 0 1 1.008 0l3 1.75A1 1 0 0 1 10 15.5V19a1 1 0 0 1-.496.864l-3 1.75a1 1 0 0 1-1.008 0l-3-1.75A1 1 0 0 1 2 19v-3.5a1 1 0 0 1 .496-.864l3-1.75ZM4 16.074v2.352l2 1.166 2-1.166v-2.352l-2-1.166-2 1.166Z"
clip-rule="evenodd" />
<path fill="currentColor" fill-rule="evenodd"
d="M3 6.5v5.527l1.488-.868a3 3 0 0 1 3.024 0l3 1.75A3 3 0 0 1 12 15.5V21h6a3 3 0 0 0 3-3v-7a3 3 0 0 0-3-3h-1V5.412A2.412 2.412 0 0 0 14.588 3H6.5A3.5 3.5 0 0 0 3 6.5ZM6.5 8a1.5 1.5 0 1 1 0-3h8.088c.228 0 .412.184.412.412V8H6.5Zm9 8a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"
d="M6.5 3A3.5 3.5 0 0 0 3 6.5V17a4 4 0 0 0 4 4h11a3 3 0 0 0 3-3v-7a3 3 0 0 0-3-3h-1V5.412A2.412 2.412 0 0 0 14.588 3H6.5ZM15 8V5.412A.412.412 0 0 0 14.588 5H6.5a1.5 1.5 0 1 0 0 3H15Zm.5 7.75a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z"
clip-rule="evenodd" />
</svg>

Before

Width:  |  Height:  |  Size: 751 B

After

Width:  |  Height:  |  Size: 418 B

View File

@@ -441,7 +441,7 @@ const mergedTokens = createTokens({
28: 112,
32: 128,
'-px': -1,
'-0.5': 2,
'-0.5': -2,
'-1': -4,
'-1.5': -6,
'-2': -8,

View File

@@ -85,7 +85,6 @@ function useAppNavigation<
headerSearchBarOptions: {
// always show search bar on iOS
hideNavigationBar: false,
// @ts-expect-error
hideWhenScrolling: false,
cancelButtonText: searchCancelText,
textColor: searchTextColor,

View File

@@ -34,7 +34,6 @@ const DemoRootHomeOptions = () => {
),
headerSearchBarOptions: {
hideNavigationBar: true,
// @ts-expect-error
hideWhenScrolling: true,
placeholder: intl.formatMessage({
id: 'content__search_dapps_or_type_url',

View File

@@ -82,7 +82,6 @@ function useDemoAppNavigation<
newHeaderSearchBarOptions = {
headerSearchBarOptions: {
hideNavigationBar: false,
// @ts-expect-error
hideWhenScrolling: false,
cancelButtonText: searchCancelText,
textColor: searchTextColor,

View File

@@ -1,34 +1,10 @@
import { useMemo, useState } from 'react';
import { useMemo } from 'react';
import {
ListView,
RefreshControl,
ScrollView,
Stack,
Text,
} from '@onekeyhq/components';
import { PageHeaderView, PageManager } from '@onekeyhq/components/src/TabView';
import { ListView, Stack, Text } from '@onekeyhq/components';
import { Tab } from '@onekeyhq/components/src/TabView';
import { Layout } from './utils/Layout';
const HeaderPropsList = {
data: [
{ title: '标签1' },
{ title: '标签2' },
{ title: '标签标签3' },
{ title: '标签4' },
],
style: {
height: 50,
backgroundColor: 'white',
borderBottomColor: '#F5F5F5',
borderBottomWidth: 1,
},
onSelectedPageIndex: (index: number) => {
console.log('选中', index);
},
};
const FirstRoute = ({
onContentSizeChange,
}: {
@@ -66,85 +42,26 @@ const SecondRoute = ({
);
const TabViewScrollStickyDemo = () => {
const onRefresh = () => {};
const [contentHeight, setContentHeight] = useState<number | undefined>(1);
const data = useMemo(
() => [
{
title: '标签1',
backgroundColor: 'skyblue',
contentHeight: undefined,
page: FirstRoute,
},
{
title: '标签2',
backgroundColor: 'coral',
contentHeight: undefined,
page: SecondRoute,
},
],
[],
);
const pageManager = useMemo(
() =>
new PageManager({
data,
initialScrollIndex: 1,
onSelectedPageIndex: (index: number) => {
setContentHeight(data[index]?.contentHeight);
},
}),
[data],
);
const Header = pageManager.renderHeaderView;
const Content = pageManager.renderContentView;
return (
<ScrollView
style={{ height: 600, backgroundColor: 'black' }}
nestedScrollEnabled
refreshControl={
<RefreshControl refreshing={false} onRefresh={onRefresh} />
}
stickyHeaderIndices={[1]}
>
<Stack style={{ height: 100 }} />
<Header
// HTPageHeaderView.defaultProps in TabView
style={HeaderPropsList.style}
itemContainerStyle={{ flex: 1 }}
<Tab
data={data}
initialScrollIndex={1}
// style={{ width: 400, height: 600, backgroundColor: 'black' }}
h={600}
/>
<Stack style={{ height: contentHeight }}>
<Content
renderItem={({
item,
index,
}: {
item: {
backgroundColor: string;
contentHeight: number | undefined;
page: any;
};
index: number;
}) => (
<Stack
style={{
flex: 1,
backgroundColor: item.backgroundColor,
}}
>
<item.page
onContentSizeChange={(width: number, height: number) => {
item.contentHeight = height;
if (index === pageManager.pageIndex) {
setContentHeight(height);
}
}}
/>
</Stack>
)}
/>
</Stack>
</ScrollView>
);
};
@@ -156,20 +73,42 @@ const TabViewGallery = () => (
elements={[
{
title: 'Header 单独使用',
element: <PageHeaderView {...HeaderPropsList} />,
element: (
<Tab.Header
data={[
{ title: '标签1' },
{ title: '标签2' },
{ title: '标签标签3' },
{ title: '标签4' },
]}
onSelectedPageIndex={(index: number) => {
console.log('选中', index);
}}
/>
),
},
{
title: 'Header 自定义1',
element: (
<PageHeaderView
{...HeaderPropsList}
<Tab.Header
data={[
{ title: '标签1' },
{ title: '标签2' },
{ title: '标签标签3' },
{ title: '标签4' },
]}
itemContainerStyle={{ flex: 1 }}
itemTitleNormalStyle={{ color: '$text', fontSize: 13 }}
itemTitleSelectedStyle={{ color: '$textInverse', fontSize: 15 }}
cursorStyle={{
width: 88,
height: 48,
borderRadius: 48 / 2.0,
top: 0.5,
backgroundColor: 'black',
height: 44,
borderRadius: 44 / 2.0,
top: 5,
bg: '$bgInfoStrong',
}}
onSelectedPageIndex={(index: number) => {
console.log('选中', index);
}}
/>
),

View File

@@ -1,14 +1,11 @@
import { memo, useCallback, useMemo, useRef, useState } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { useIntl } from 'react-intl';
import { RefreshControl, useWindowDimensions } from 'react-native';
import type { IScrollViewRef } from '@onekeyhq/components';
import { ListView, Page, ScrollView, Stack, Text } from '@onekeyhq/components';
import { getTokens } from '@onekeyhq/components/src/hooks';
import { useThemeValue } from '@onekeyhq/components/src/Provider/hooks/useThemeValue';
import { PageManager } from '@onekeyhq/components/src/TabView';
import platformEnv from '@onekeyhq/shared/src/platformEnv';
import { Tab } from '@onekeyhq/components/src/TabView';
import HeaderView from './HeaderView';
@@ -81,12 +78,6 @@ const ListRoute = ({
/>
);
// const renderScene = SceneMap({
// [EHomePageTabsEnum.Demo1]: FirstRoute,
// [EHomePageTabsEnum.Demo2]: SecondRoute,
// [EHomePageTabsEnum.Demo3]: OtherRoute,
// });
function HomePage() {
const screenWidth = useWindowDimensions().width;
const sideBarWidth = getTokens().size.sideBarWidth.val;
@@ -96,163 +87,41 @@ function HomePage() {
// tabsViewRef?.current?.setRefreshing(true);
}, []);
const [contentHeight, setContentHeight] = useState<number | undefined>(1);
const scrollView = useRef<IScrollViewRef | null>(null);
const container = useRef<any | null>(null);
const config = useMemo(
() => ({
lastIndex: -1,
scrollViewHeight: 0,
headerLayoutY: 0,
headerViewHeight: 0,
}),
[],
);
const [bgAppColor, textColor, textSubduedColor] = useThemeValue(
['bgApp', 'text', 'textSubdued'],
undefined,
true,
);
const data = useMemo(
() => [
{
title: 'Label',
backgroundColor: 'skyblue',
contentHeight: undefined,
contentOffsetY: 0,
page: memo(FirstRoute, () => true),
},
{
title: intl.formatMessage({
id: 'action__default_chain',
}),
backgroundColor: 'coral',
contentHeight: undefined,
contentOffsetY: 0,
page: memo(SecondRoute, () => true),
},
{
title: 'Label',
backgroundColor: 'turquoise',
contentHeight: undefined,
contentOffsetY: 0,
page: memo(ListRoute, () => true),
},
{
title: 'Label',
backgroundColor: 'pink',
contentHeight: undefined,
contentOffsetY: 0,
page: memo(OtherRoute, () => true),
},
],
// eslint-disable-next-line react-hooks/exhaustive-deps
[intl, bgAppColor, textColor, textSubduedColor],
[intl],
);
const reloadContentHeight = useCallback(
(index: number) => {
if (config.scrollViewHeight * config.headerViewHeight <= 0) {
return;
}
const minHeight = config.scrollViewHeight - config.headerViewHeight;
const height = Math.max(data[index].contentHeight ?? 0, minHeight);
if (height === contentHeight) {
return;
}
if (platformEnv.isNative) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
container?.current?.setNativeProps?.({
height,
});
} else {
setContentHeight(height);
}
},
[data, config, container, contentHeight],
);
const pageManager = useMemo(
() =>
new PageManager({
data,
initialScrollIndex: 3,
onSelectedPageIndex: (index: number) => {
reloadContentHeight(index);
const { contentOffsetY } = data[index];
const lastContentOffsetY =
data?.[config.lastIndex]?.contentOffsetY ?? 0;
if (
Math.round(lastContentOffsetY) < Math.round(config.headerLayoutY)
) {
data[index].contentOffsetY = lastContentOffsetY;
} else if (
Math.round(contentOffsetY) <= Math.round(config.headerLayoutY)
) {
data[index].contentOffsetY = config.headerLayoutY;
}
// Need to wait for contentHeight to be updated
setTimeout(() => {
if (platformEnv.isNative) {
scrollView?.current?.setNativeProps({
contentOffset: { y: data[index].contentOffsetY },
});
} else {
scrollView?.current?.scrollTo({
y: data[index].contentOffsetY,
animated: false,
});
}
});
config.lastIndex = index;
},
}),
[data, config, scrollView, reloadContentHeight],
);
const Header = pageManager.renderHeaderView;
const Content = pageManager.renderContentView;
const renderHeaderView = useCallback(() => <HeaderView />, []);
const renderContentItem = useCallback(
({
item,
index,
}: {
item: {
backgroundColor: string;
contentHeight: number | undefined;
page: any;
};
index: number;
}) => (
<Stack
style={{
flex: 1,
// backgroundColor: item.backgroundColor,
}}
>
<item.page
onContentSizeChange={(_: number, height: number) => {
item.contentHeight = height;
if (index === pageManager.pageIndex) {
reloadContentHeight(index);
}
}}
/>
</Stack>
),
[pageManager, reloadContentHeight],
);
return useMemo(
() => (
<Page>
<Page.Body alignItems="center">
<ScrollView
<Tab
// @ts-expect-error
data={data}
ListHeaderComponent={<>{renderHeaderView()}</>}
initialScrollIndex={3}
$md={{
width: '100%',
}}
@@ -262,76 +131,12 @@ function HomePage() {
refreshControl={
<RefreshControl refreshing={false} onRefresh={onRefresh} />
}
stickyHeaderIndices={[1]}
ref={scrollView}
showsVerticalScrollIndicator={false}
scrollEventThrottle={16}
onLayout={(event) => {
config.scrollViewHeight = event.nativeEvent.layout.height;
}}
onScroll={(event) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
data[pageManager.pageIndex].contentOffsetY = (
event as any
).nativeEvent.contentOffset.y;
}}
>
{renderHeaderView()}
<Header
// HTPageHeaderView.defaultProps in TabView
style={{
height: 54,
backgroundColor: bgAppColor,
}}
onLayout={(event: any) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
config.headerViewHeight = event.nativeEvent.layout.height;
}}
itemTitleStyle={{ fontSize: 16 }}
itemTitleNormalStyle={{ color: textSubduedColor }}
itemTitleSelectedStyle={{ color: textColor, fontSize: 16 }}
cursorStyle={{
left: 12,
right: 12,
height: 2,
backgroundColor: textColor,
}}
/>
<Stack
ref={container}
onLayout={(event) => {
config.headerLayoutY =
event.nativeEvent.layout.y - config.headerViewHeight;
}}
style={{ height: contentHeight }}
>
<Content
windowSize={5}
scrollEnabled={platformEnv.isNative}
shouldSelectedPageAnimation={platformEnv.isNative}
renderItem={renderContentItem}
/>
</Stack>
</ScrollView>
</Page.Body>
</Page>
),
[
screenWidth,
sideBarWidth,
bgAppColor,
textColor,
textSubduedColor,
contentHeight,
Header,
Content,
onRefresh,
renderHeaderView,
renderContentItem,
data,
config,
pageManager,
],
[screenWidth, sideBarWidth, onRefresh, renderHeaderView, data],
);
}

View File

@@ -5181,6 +5181,7 @@ __metadata:
react-native-svg: ^13.9.0
react-native-tab-page-view: "https://github.com/OneKeyHQ/react-native-tab-page-view#90ea54bfb01021c4e54e27cb9fc72a3702df0aa3"
react-native-tcp-socket: ^6.0.6
react-native-view-shot: 3.1.2
react-native-webview: "https://github.com/OneKeyHQ/react-native-webview.git#5036d59cba4f5609c32b7eb5dec560e738ce7115"
react-native-zip-archive: ^6.0.9
readable-stream: ^3.6.0
@@ -24230,6 +24231,16 @@ __metadata:
languageName: node
linkType: hard
"react-native-view-shot@npm:3.1.2":
version: 3.1.2
resolution: "react-native-view-shot@npm:3.1.2"
peerDependencies:
react: "*"
react-native: "*"
checksum: 8f5e148830bd27887736310805c4f385c6a2df0f53827fb727fbe7f5cdb35dae951b90139cee752a14133bc8b32795434d4f9dbf33f9d9b56f007b99caa661f0
languageName: node
linkType: hard
"react-native-web-internals@npm:1.71.0":
version: 1.71.0
resolution: "react-native-web-internals@npm:1.71.0"