feat: add RefreshControl & BlurView & FlashList (#3807)

This commit is contained in:
huhuanming
2023-11-16 10:50:29 +08:00
committed by GitHub
parent c407ee01fe
commit 753d127bff
20 changed files with 324 additions and 55 deletions

View File

@@ -405,7 +405,7 @@ PODS:
- React-Core
- react-native-slider (4.4.3):
- React-Core
- react-native-tab-page-view (1.0.0):
- react-native-tab-page-view (0.1.0):
- React
- react-native-tcp-socket (6.0.6):
- CocoaAsyncSocket
@@ -533,6 +533,8 @@ PODS:
- RNFileLogger (0.4.1):
- CocoaLumberjack
- React
- RNFlashList (1.6.3):
- React-Core
- RNFS (2.20.0):
- React-Core
- RNGestureHandler (2.13.3):
@@ -672,6 +674,7 @@ DEPENDENCIES:
- "RNCAsyncStorage (from `../../../node_modules/@react-native-async-storage/async-storage`)"
- RNDeviceInfo (from `../../../node_modules/react-native-device-info`)
- RNFileLogger (from `../../../node_modules/react-native-file-logger`)
- "RNFlashList (from `../../../node_modules/@shopify/flash-list`)"
- RNFS (from `../../../node_modules/react-native-fs`)
- RNGestureHandler (from `../../../node_modules/react-native-gesture-handler`)
- RNReanimated (from `../../../node_modules/react-native-reanimated`)
@@ -853,6 +856,8 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native-device-info"
RNFileLogger:
:path: "../../../node_modules/react-native-file-logger"
RNFlashList:
:path: "../../../node_modules/@shopify/flash-list"
RNFS:
:path: "../../../node_modules/react-native-fs"
RNGestureHandler:
@@ -932,7 +937,7 @@ SPEC CHECKSUMS:
react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846
react-native-safe-area-context: 7aa8e6d9d0f3100a820efb1a98af68aa747f9284
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
react-native-tab-page-view: 26fe6d391947b236deaae5205e33c39404d6bbd8
react-native-tab-page-view: 35bba61d5d8036fdf23976439d613a43ef1f041c
react-native-tcp-socket: e724380c910c2e704816ec817ed28f1342246ff7
react-native-webview: 6408bc328e65042d240d0ebd62baa76ffd23b1aa
React-NativeModulesApple: 797bc6078d566eef3fb3f74127e6e1d2e945a15f
@@ -956,6 +961,7 @@ SPEC CHECKSUMS:
RNCAsyncStorage: ddc4ee162bfd41b0d2c68bf2d95acd81dd7f1f93
RNDeviceInfo: bf8a32acbcb875f568217285d1793b0e8588c974
RNFileLogger: 9eaf7a6ea709eaaffe646cc485b98ae1dbf1e9f0
RNFlashList: 4b4b6b093afc0df60ae08f9cbf6ccd4c836c667a
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNGestureHandler: fa40ab6b6cafaa7609294d31f8e8fbbd0cce8ea6
RNReanimated: 9633f8c2e214b721e35e371b473fbf3a1522948d

View File

@@ -27,8 +27,10 @@
"@react-native-async-storage/async-storage": "1.18.2",
"@react-native-community/netinfo": "^9.4.1",
"@react-native-community/slider": "^4.4.3",
"@shopify/flash-list": "^1.6.3",
"burnt": "0.12",
"expo": "~49.0.13",
"expo-blur": "~12.3.0",
"expo-device": "~5.4.0",
"expo-haptics": "^12.2.1",
"expo-splash-screen": "~0.20.5",

View File

@@ -0,0 +1,30 @@
import type { ForwardedRef } from 'react';
import { forwardRef } from 'react';
import { usePropsAndStyle } from '@tamagui/core';
import { BlurView as NativeBlurView } from 'expo-blur';
import useTheme from '../Provider/hooks/useTheme';
import type { StackStyleProps } from '@tamagui/web/types/types';
import type { BlurViewProps } from 'expo-blur';
import type { StyleProp, View, ViewStyle } from 'react-native';
export type IBlurViewPros = Omit<BlurViewProps, 'style'> & StackStyleProps;
function BasicBlurView(props: IBlurViewPros, ref: ForwardedRef<View>) {
const { themeVariant } = useTheme();
const [restProps, style] = usePropsAndStyle(props, {
resolveValues: 'auto',
});
return (
<NativeBlurView
style={style as StyleProp<ViewStyle>}
{...restProps}
tint={themeVariant}
ref={ref}
/>
);
}
export const BlurView = forwardRef<View, IBlurViewPros>(BasicBlurView);

View File

@@ -162,6 +162,7 @@ function CheckboxGroup({
removeClippedSubviews
style={listStyle}
data={options}
estimatedItemSize="$10"
renderItem={({
item: { label: labelText, disabled: disabledElement },
index,

View File

@@ -1,38 +1,14 @@
import type { ForwardedRef, MutableRefObject } from 'react';
import type { ForwardedRef } from 'react';
import { forwardRef } from 'react';
import { FlashList } from '@shopify/flash-list';
import { usePropsAndStyle, useStyle } from '@tamagui/core';
import { FlatList } from 'react-native';
import { getTokenValue } from 'tamagui';
import type { StackStyleProps } from '@tamagui/web/types/types';
import type {
FlatListProps,
ListRenderItem,
StyleProp,
ViewStyle,
} from 'react-native';
import { View } from '../View';
export type IListViewRef<T> = FlatList<T>;
export type IListViewProps<T> = Omit<
FlatListProps<T>,
| 'contentContainerStyle'
| 'columnWrapperStyle'
| 'ListHeaderComponentStyle'
| 'ListFooterComponentStyle'
| 'data'
| 'renderItem'
> &
StackStyleProps & {
contentContainerStyle?: StackStyleProps;
columnWrapperStyle?: StackStyleProps;
ListHeaderComponentStyle?: StackStyleProps;
ListFooterComponentStyle?: StackStyleProps;
} & {
data: ArrayLike<T> | null | undefined;
renderItem: ListRenderItem<T> | null | undefined;
ref?: MutableRefObject<IListViewRef<any> | null>;
};
import type { IListViewProps, IListViewRef } from './type';
import type { StyleProp, ViewStyle } from 'react-native';
function BaseListView<T>(
{
@@ -42,6 +18,7 @@ function BaseListView<T>(
columnWrapperStyle,
ListHeaderComponentStyle = {},
ListFooterComponentStyle = {},
estimatedItemSize,
...props
}: IListViewProps<T>,
ref: ForwardedRef<IListViewRef<T>>,
@@ -56,13 +33,6 @@ function BaseListView<T>(
},
);
const columnStyle = useStyle(
(columnWrapperStyle || {}) as Record<string, unknown>,
{
resolveValues: 'auto',
},
);
const listHeaderStyle = useStyle(
ListHeaderComponentStyle as Record<string, unknown>,
{
@@ -76,20 +46,27 @@ function BaseListView<T>(
resolveValues: 'auto',
},
);
const itemSize =
typeof estimatedItemSize === 'number'
? estimatedItemSize
: getTokenValue(estimatedItemSize);
return (
<FlatList<T>
ref={ref}
style={style as StyleProp<ViewStyle>}
columnWrapperStyle={columnWrapperStyle ? columnStyle : undefined}
ListHeaderComponentStyle={listHeaderStyle}
ListFooterComponentStyle={listFooterStyle}
contentContainerStyle={contentStyle}
data={data}
renderItem={renderItem}
{...restProps}
/>
<View style={style as StyleProp<ViewStyle>}>
<FlashList<T>
ref={ref}
ListHeaderComponentStyle={listHeaderStyle}
ListFooterComponentStyle={listFooterStyle}
contentContainerStyle={contentStyle}
data={data}
renderItem={renderItem}
estimatedItemSize={itemSize}
{...restProps}
/>
</View>
);
}
export * from './type';
// forwardRef cannot cast typescript generic
export const ListView = forwardRef(BaseListView) as typeof BaseListView;

View File

@@ -0,0 +1,32 @@
import type { MutableRefObject } from 'react';
import type {
FlashList,
FlashListProps,
ListRenderItem,
} from '@shopify/flash-list';
import type { StackStyleProps, Tokens } from '@tamagui/web/types/types';
export type IListViewRef<T> = FlashList<T>;
export type IListViewProps<T> = Omit<
FlashListProps<T>,
| 'contentContainerStyle'
| 'columnWrapperStyle'
| 'ListHeaderComponentStyle'
| 'ListFooterComponentStyle'
| 'data'
| 'renderItem'
| 'estimatedItemSize'
> &
StackStyleProps & {
contentContainerStyle?: StackStyleProps;
columnWrapperStyle?: StackStyleProps;
ListHeaderComponentStyle?: StackStyleProps;
ListFooterComponentStyle?: StackStyleProps;
} & {
data: ReadonlyArray<T> | null | undefined;
renderItem: ListRenderItem<T> | null | undefined;
ref?: MutableRefObject<IListViewRef<any> | null>;
estimatedItemSize: number | `$${keyof Tokens['size']}`;
};

View File

@@ -0,0 +1,12 @@
import { RefreshControl as NativeRefreshControl } from 'react-native';
import { useThemeValue } from '../Provider/hooks/useThemeValue';
import type { IRefreshControlType } from './type';
export * from './type';
export function RefreshControl(props: IRefreshControlType) {
const color = useThemeValue('bgPrimaryActive');
return <NativeRefreshControl tintColor={color} {...props} />;
}

View File

@@ -0,0 +1,8 @@
import type { IRefreshControlType } from './type';
export * from './type';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function RefreshControl(_: IRefreshControlType) {
return null;
}

View File

@@ -0,0 +1,3 @@
import type { RefreshControlProps } from 'react-native';
export type IRefreshControlType = RefreshControlProps;

View File

@@ -0,0 +1,11 @@
// The View component is not exposed externally, it is only used for component layer optimization.
import type { ElementType } from 'react';
import type { IViewType } from './type';
const ViewNativeComponent: {
default: ElementType<IViewType>;
} = require('react-native/Libraries/Components/View/ViewNativeComponent');
export * from './type';
export const View = ViewNativeComponent.default;

View File

@@ -0,0 +1,7 @@
import { Stack } from '../Stack';
import type { IViewType } from './type';
export function View(props: IViewType) {
return <Stack {...props} />;
}

View File

@@ -0,0 +1,5 @@
import type { PropsWithChildren } from 'react';
import type { StyleProp, ViewStyle } from 'react-native';
export type IViewType = PropsWithChildren<{ style?: StyleProp<ViewStyle> }>;

View File

@@ -49,6 +49,8 @@ export * from './SegmentControl';
export * from './ListView';
export * from './SectionList';
export * as DelayedFreeze from './DelayedFreeze';
export * from './RefreshControl';
export * from './BlurView';
// Navigation
export * from './Navigation/StackNavigator';

View File

@@ -15,7 +15,6 @@
"expo-application": "~5.3.1",
"expo-av": "^13.2.1",
"expo-barcode-scanner": "^12.3.2",
"expo-blur": "~12.3.0",
"expo-camera": "13.2.1",
"expo-clipboard": "^4.1.2",
"expo-image-manipulator": "^11.1.1",

View File

@@ -1,6 +1,7 @@
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';
import BlurViewGallery from '@onekeyhq/kit/src/views/Components/stories/BlurView';
import ButtonGallery from '@onekeyhq/kit/src/views/Components/stories/Button';
import CheckboxGallery from '@onekeyhq/kit/src/views/Components/stories/Checkbox';
import DialogGallery from '@onekeyhq/kit/src/views/Components/stories/Dialog';
@@ -14,6 +15,7 @@ import DemoRootApp from '@onekeyhq/kit/src/views/Components/stories/NavigatorRou
import PopoverGallery from '@onekeyhq/kit/src/views/Components/stories/Popover';
import ProgressGallery from '@onekeyhq/kit/src/views/Components/stories/Progress';
import RadioGallery from '@onekeyhq/kit/src/views/Components/stories/Radio';
import RefreshControlGallery from '@onekeyhq/kit/src/views/Components/stories/RefreshControl';
import SegmentControlGallery from '@onekeyhq/kit/src/views/Components/stories/SegmentControl';
import SelectGallery from '@onekeyhq/kit/src/views/Components/stories/Select';
import ShortcutGallery from '@onekeyhq/kit/src/views/Components/stories/Shortcut';
@@ -39,7 +41,10 @@ import ThemeGallery from '../../../../../views/Components/stories/Theme';
import { EGalleryRoutes } from './routes';
export const galleryScreenList = [
export const galleryScreenList: {
name: EGalleryRoutes;
component: () => React.JSX.Element;
}[] = [
{ name: EGalleryRoutes.Components, component: ComponentsScreen },
{
name: EGalleryRoutes.ComponentTypography,
@@ -105,4 +110,12 @@ export const galleryScreenList = [
name: EGalleryRoutes.ComponentWebview,
component: WebviewGallery,
},
{
name: EGalleryRoutes.ComponentRefreshControl,
component: RefreshControlGallery,
},
{
name: EGalleryRoutes.ComponentBlurView,
component: BlurViewGallery,
},
];

View File

@@ -4,6 +4,8 @@ export enum EGalleryRoutes {
ComponentLottieView = 'component/lottieview',
ComponentTooltip = 'component/tooltip',
ComponentIcon = 'component/icon',
ComponentRefreshControl = 'component/refresh-control',
ComponentBlurView = 'component/BlurView',
ComponentSegmentControl = 'component/segment-control',
ComponentButton = 'component/button',
ComponentSelect = 'component/select',

View File

@@ -0,0 +1,99 @@
import type { PropsWithChildren } from 'react';
import {
BlurView,
ListView,
Stack,
Text,
XStack,
YStack,
} from '@onekeyhq/components';
import { Layout } from './utils/Layout';
const mockData = [...Array(20).keys()];
const colors = [
'orangered',
'gold',
'purple',
'turquoise',
'salmon',
'yellowgreen',
];
const getColor = () => colors[Math.floor(Math.random() * colors.length)];
function Background({ children }: PropsWithChildren<unknown>) {
return (
<YStack
flex={1}
h="$100"
alignItems="center"
justifyContent="center"
position="relative"
>
<XStack
flex={1}
flexWrap="wrap"
position="absolute"
top={0}
left={0}
right={0}
bottom={0}
>
{mockData.map((i) => (
<Stack w="25%" h="$20" key={`box-${i}`} bc={getColor()} />
))}
</XStack>
{children}
</YStack>
);
}
function BasicDemo() {
return (
<Background>
<BlurView w="100%" h="$20" intensity={70} />
</Background>
);
}
function ListDemo() {
return (
<Background>
<BlurView w="100%" h="$20" intensity={70}>
<ListView
p="$2"
horizontal
data={mockData}
showsHorizontalScrollIndicator={false}
estimatedItemSize="$10"
renderItem={({ item }) => (
<BlurView m="$2">
<Text m="$2">{item}</Text>
</BlurView>
)}
/>
</BlurView>
</Background>
);
}
const BlurViewGallery = () => (
<Layout
description="****"
suggestions={['****']}
boundaryConditions={['****']}
elements={[
{
title: 'Default',
element: <BasicDemo />,
},
{
title: 'List',
element: <ListDemo />,
},
]}
/>
);
export default BlurViewGallery;

View File

@@ -5,13 +5,15 @@ import { Button, Divider, ListView, Text, XStack } from '@onekeyhq/components';
import { Layout } from './utils/Layout';
// read it before you use it.
// https://shopify.github.io/flash-list/docs/fundamentals/performant-components
const listData = new Array(100).fill(0).map((_, index) => index);
const ListViewDemo = () => {
const ref = useRef<IListViewRef<any> | null>(null);
return (
<ListView
h="$60"
bg="$backgroundPress"
estimatedItemSize="$10"
contentContainerStyle={{
bg: '$borderLight',
m: '$4',

View File

@@ -0,0 +1,43 @@
import { useCallback, useState } from 'react';
import { RefreshControl, ScrollView, Text } from '@onekeyhq/components';
import { Layout } from './utils/Layout';
const Demo = () => {
const [refreshing, setRefreshing] = useState(false);
const onRefresh = useCallback(() => {
setRefreshing(true);
setTimeout(() => {
setRefreshing(false);
}, 2000);
}, []);
return (
<ScrollView
h="$20"
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
<Text>Pull down to see RefreshControl indicator</Text>
</ScrollView>
);
};
const RefreshControllerGallery = () => (
<Layout
description=".."
suggestions={['...']}
boundaryConditions={['...']}
scrollEnabled={false}
elements={[
{
title: 'Default',
element: <Demo />,
},
]}
/>
);
export default RefreshControllerGallery;

View File

@@ -5058,7 +5058,6 @@ __metadata:
expo-application: ~5.3.1
expo-av: ^13.2.1
expo-barcode-scanner: ^12.3.2
expo-blur: ~12.3.0
expo-camera: 13.2.1
expo-clipboard: ^4.1.2
expo-image-manipulator: ^11.1.1
@@ -5105,8 +5104,10 @@ __metadata:
"@react-native-async-storage/async-storage": 1.18.2
"@react-native-community/netinfo": ^9.4.1
"@react-native-community/slider": ^4.4.3
"@shopify/flash-list": ^1.6.3
burnt: 0.12
expo: ~49.0.13
expo-blur: ~12.3.0
expo-device: ~5.4.0
expo-haptics: ^12.2.1
expo-splash-screen: ~0.20.5
@@ -6263,6 +6264,20 @@ __metadata:
languageName: node
linkType: hard
"@shopify/flash-list@npm:^1.6.3":
version: 1.6.3
resolution: "@shopify/flash-list@npm:1.6.3"
dependencies:
recyclerlistview: 4.2.0
tslib: 2.4.0
peerDependencies:
"@babel/runtime": "*"
react: "*"
react-native: "*"
checksum: 655723d750275c07837099b79ec01a63c40223ccf56a706299e0906cec907fb9617e1135b49d59a05ff550fdaa7565957dee92929b84671cd6d910a145664b2c
languageName: node
linkType: hard
"@sideway/address@npm:^4.1.3":
version: 4.1.4
resolution: "@sideway/address@npm:4.1.4"
@@ -24851,7 +24866,7 @@ __metadata:
languageName: node
linkType: hard
"recyclerlistview@npm:^4.2.0":
"recyclerlistview@npm:4.2.0, recyclerlistview@npm:^4.2.0":
version: 4.2.0
resolution: "recyclerlistview@npm:4.2.0"
dependencies: