react-native-terra-ui 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/lib/module/components/header/parts/HeaderGradientOverlay.js +1 -1
- package/lib/module/components/header/parts/HeaderGradientOverlay.js.map +1 -1
- package/lib/module/components/screen/Screen.js +3 -3
- package/lib/module/components/screen/Screen.js.map +1 -1
- package/lib/module/components/screen/ScreenFlatList.js +52 -31
- package/lib/module/components/screen/ScreenFlatList.js.map +1 -1
- package/lib/module/components/screen/ScreenList.js +160 -0
- package/lib/module/components/screen/ScreenList.js.map +1 -0
- package/lib/module/components/screen/ScreenScrollView.js +26 -14
- package/lib/module/components/screen/ScreenScrollView.js.map +1 -1
- package/lib/module/components/screen/index.js +1 -1
- package/lib/module/components/screen/index.js.map +1 -1
- package/lib/module/components/toast/Toast.js +2 -2
- package/lib/module/components/toolbar/Toolbar.js +3 -1
- package/lib/module/components/toolbar/Toolbar.js.map +1 -1
- package/lib/module/theme/tokens/primitives.js +11 -6
- package/lib/module/theme/tokens/primitives.js.map +1 -1
- package/lib/typescript/src/components/screen/Screen.d.ts +3 -2
- package/lib/typescript/src/components/screen/Screen.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenContext.d.ts +2 -2
- package/lib/typescript/src/components/screen/ScreenFlatList.d.ts +11 -0
- package/lib/typescript/src/components/screen/ScreenFlatList.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenList.d.ts +65 -0
- package/lib/typescript/src/components/screen/ScreenList.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/index.d.ts +2 -2
- package/lib/typescript/src/components/screen/index.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/types.d.ts +2 -3
- package/lib/typescript/src/components/screen/types.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/primitives.d.ts +5 -0
- package/lib/typescript/src/theme/tokens/primitives.d.ts.map +1 -1
- package/lib/typescript/src/theme/types.d.ts +1 -1
- package/lib/typescript/src/theme/types.d.ts.map +1 -1
- package/package.json +2 -4
- package/src/components/header/parts/HeaderGradientOverlay.tsx +1 -1
- package/src/components/screen/Screen.tsx +5 -4
- package/src/components/screen/ScreenContext.tsx +2 -2
- package/src/components/screen/ScreenFlatList.tsx +52 -30
- package/src/components/screen/ScreenList.tsx +270 -0
- package/src/components/screen/ScreenScrollView.tsx +19 -10
- package/src/components/screen/index.ts +2 -2
- package/src/components/screen/types.ts +2 -3
- package/src/components/toast/Toast.tsx +2 -2
- package/src/components/toolbar/Toolbar.tsx +2 -0
- package/src/theme/tokens/primitives.ts +12 -6
- package/src/theme/types.ts +1 -1
- package/lib/module/components/screen/ScreenFlashList.js +0 -153
- package/lib/module/components/screen/ScreenFlashList.js.map +0 -1
- package/lib/typescript/src/components/screen/ScreenFlashList.d.ts +0 -43
- package/lib/typescript/src/components/screen/ScreenFlashList.d.ts.map +0 -1
- package/src/components/screen/ScreenFlashList.tsx +0 -203
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import { FlashList, type FlashListProps } from '@shopify/flash-list';
|
|
2
|
-
import type { ComponentType } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
type NativeScrollEvent,
|
|
5
|
-
type NativeSyntheticEvent,
|
|
6
|
-
View,
|
|
7
|
-
} from 'react-native';
|
|
8
|
-
import Animated, {
|
|
9
|
-
useAnimatedScrollHandler,
|
|
10
|
-
useComposedEventHandler,
|
|
11
|
-
} from 'react-native-reanimated';
|
|
12
|
-
|
|
13
|
-
import { StyleSheet } from 'react-native-unistyles';
|
|
14
|
-
|
|
15
|
-
import { resolveScreenMargin } from '#theme/screen-margin';
|
|
16
|
-
import { PortalHost } from '../portal';
|
|
17
|
-
import { BottomSafeArea } from './parts/BottomSafeArea';
|
|
18
|
-
import { renderScreenPlaceholder } from './parts/ScreenPlaceholder';
|
|
19
|
-
import { SCREEN_HEADER_PORTAL_HOST, useScreen } from './ScreenContext';
|
|
20
|
-
import type { ScreenMargins, ScreenScrollableProps } from './types';
|
|
21
|
-
import { hasXMargin, hasYMargin, renderListHeader } from './utils';
|
|
22
|
-
|
|
23
|
-
const AnimatedFlashList = Animated.createAnimatedComponent(
|
|
24
|
-
FlashList as unknown as ComponentType<FlashListProps<unknown>>
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
export interface ScreenFlashListProps<T>
|
|
28
|
-
extends Omit<FlashListProps<T>, 'horizontal'>,
|
|
29
|
-
ScreenScrollableProps {
|
|
30
|
-
/**
|
|
31
|
-
* Extra bottom padding on the list content — clearance for a tab bar or home
|
|
32
|
-
* indicator. Defaults to the `layout.screen.margin.y` token when margins are
|
|
33
|
-
* enabled, otherwise `0`.
|
|
34
|
-
*/
|
|
35
|
-
bottomInset?: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* `FlashList` variant of `Screen.ScrollView`: mirrors scroll offset into the
|
|
40
|
-
* screen's shared `scrollY`, hosts the portal target above the list, and
|
|
41
|
-
* applies token-driven content padding.
|
|
42
|
-
*
|
|
43
|
-
* Requires `@shopify/flash-list` — it is a peer dependency of
|
|
44
|
-
* `react-native-terra-ui` and must be installed in the consuming app.
|
|
45
|
-
*
|
|
46
|
-
* Note: unlike `Screen.FlatList`, this does not bind the screen's shared
|
|
47
|
-
* imperative `scrollRef` — `FlashList`'s ref exposes a different (non
|
|
48
|
-
* Reanimated-compatible) imperative API.
|
|
49
|
-
*
|
|
50
|
-
* Shares `loading` / `LoadingComponent` / `EmptyComponent` / `margins` /
|
|
51
|
-
* `horizontal` with `Screen.ScrollView`, so a screen can swap one container for
|
|
52
|
-
* another without relearning its API.
|
|
53
|
-
*
|
|
54
|
-
* @example
|
|
55
|
-
* ```tsx
|
|
56
|
-
* <Screen.FlashList data={items} renderItem={({ item }) => <Row item={item} />} />
|
|
57
|
-
* ```
|
|
58
|
-
*
|
|
59
|
-
* @example Loading and empty placeholders
|
|
60
|
-
* ```tsx
|
|
61
|
-
* <Screen.FlashList
|
|
62
|
-
* data={items}
|
|
63
|
-
* loading={isLoading}
|
|
64
|
-
* EmptyComponent={<EmptyState icon="status.info" title="Nothing yet" fill />}
|
|
65
|
-
* renderItem={({ item }) => <Row item={item} />}
|
|
66
|
-
* />
|
|
67
|
-
* ```
|
|
68
|
-
*/
|
|
69
|
-
export function ScreenFlashList<T>({
|
|
70
|
-
data,
|
|
71
|
-
style,
|
|
72
|
-
contentContainerStyle,
|
|
73
|
-
ListHeaderComponent,
|
|
74
|
-
ListFooterComponent,
|
|
75
|
-
ListEmptyComponent,
|
|
76
|
-
horizontal = false,
|
|
77
|
-
loading = false,
|
|
78
|
-
LoadingComponent,
|
|
79
|
-
EmptyComponent,
|
|
80
|
-
margins,
|
|
81
|
-
bottomInset,
|
|
82
|
-
onScroll,
|
|
83
|
-
...rest
|
|
84
|
-
}: ScreenFlashListProps<T>) {
|
|
85
|
-
if (FlashList == null) {
|
|
86
|
-
throw new Error(
|
|
87
|
-
'[react-native-terra-ui] Screen.FlashList requires "@shopify/flash-list" to be installed in your app. Run `yarn add @shopify/flash-list` (or the npm/pnpm equivalent) and rebuild.'
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const {
|
|
92
|
-
background,
|
|
93
|
-
scrollHandler,
|
|
94
|
-
headerSnapOffsets,
|
|
95
|
-
margins: screenMargins,
|
|
96
|
-
hasHeader,
|
|
97
|
-
} = useScreen();
|
|
98
|
-
|
|
99
|
-
const resolvedMargins = margins ?? screenMargins;
|
|
100
|
-
// The placeholder rides in on `ListEmptyComponent` rather than replacing the
|
|
101
|
-
// list, so the header portal and snap offsets stay put. While loading the
|
|
102
|
-
// list is fed an empty `data` so that slot is the one that renders.
|
|
103
|
-
const placeholder = renderScreenPlaceholder({
|
|
104
|
-
loading,
|
|
105
|
-
LoadingComponent,
|
|
106
|
-
EmptyComponent,
|
|
107
|
-
isEmpty: !data || data.length === 0,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const jsScrollHandler = useAnimatedScrollHandler(
|
|
111
|
-
(event) => {
|
|
112
|
-
if (onScroll)
|
|
113
|
-
onScroll(event as unknown as NativeSyntheticEvent<NativeScrollEvent>);
|
|
114
|
-
},
|
|
115
|
-
[onScroll]
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
const composedHandler = useComposedEventHandler([
|
|
119
|
-
scrollHandler,
|
|
120
|
-
jsScrollHandler,
|
|
121
|
-
]);
|
|
122
|
-
|
|
123
|
-
// `AnimatedFlashList` is typed for `unknown` items, so the remaining props
|
|
124
|
-
// are cast back to its prop type — minus the ones set explicitly below,
|
|
125
|
-
// which the spread would otherwise re-supply.
|
|
126
|
-
const listProps = rest as Omit<FlashListProps<unknown>, 'data' | 'onScroll'>;
|
|
127
|
-
|
|
128
|
-
// A caller naming its own `bottomInset` has taken the bottom over entirely,
|
|
129
|
-
// and a horizontal list has no bottom edge to clear.
|
|
130
|
-
const needsBottomSafeArea = bottomInset === undefined && !horizontal;
|
|
131
|
-
|
|
132
|
-
const footerComponent = (
|
|
133
|
-
<>
|
|
134
|
-
{renderListHeader(ListFooterComponent)}
|
|
135
|
-
{needsBottomSafeArea ? <BottomSafeArea /> : null}
|
|
136
|
-
</>
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
const headerComponent = (
|
|
140
|
-
<View style={styles.headerWrapper(resolvedMargins)}>
|
|
141
|
-
<PortalHost name={SCREEN_HEADER_PORTAL_HOST} />
|
|
142
|
-
{renderListHeader(ListHeaderComponent)}
|
|
143
|
-
</View>
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
return (
|
|
147
|
-
<AnimatedFlashList
|
|
148
|
-
data={loading ? [] : (data as readonly unknown[] | null | undefined)}
|
|
149
|
-
style={[{ backgroundColor: background }, style]}
|
|
150
|
-
horizontal={horizontal}
|
|
151
|
-
contentInsetAdjustmentBehavior={hasHeader ? 'never' : undefined}
|
|
152
|
-
contentContainerStyle={[
|
|
153
|
-
styles.content(resolvedMargins, hasHeader, bottomInset),
|
|
154
|
-
contentContainerStyle,
|
|
155
|
-
]}
|
|
156
|
-
ListHeaderComponent={headerComponent}
|
|
157
|
-
ListFooterComponent={footerComponent}
|
|
158
|
-
ListEmptyComponent={placeholder ?? ListEmptyComponent}
|
|
159
|
-
showsVerticalScrollIndicator={false}
|
|
160
|
-
scrollEventThrottle={16}
|
|
161
|
-
snapToOffsets={headerSnapOffsets}
|
|
162
|
-
{...listProps}
|
|
163
|
-
onScroll={composedHandler}
|
|
164
|
-
/>
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const styles = StyleSheet.create((theme, runtime) => {
|
|
169
|
-
const { x: marginX, y: marginY } = resolveScreenMargin(
|
|
170
|
-
theme,
|
|
171
|
-
runtime.breakpoint
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
content: (
|
|
176
|
-
margins: ScreenMargins,
|
|
177
|
-
hasHeader: boolean,
|
|
178
|
-
bottomInset: number | undefined
|
|
179
|
-
) => {
|
|
180
|
-
return {
|
|
181
|
-
flexGrow: 1,
|
|
182
|
-
// The header is absolutely positioned, so the list reserves its full
|
|
183
|
-
// height — bar plus top safe area. This assumes the scroll view does not
|
|
184
|
-
// also get UIKit's automatic inset adjustment: on a native tab screen
|
|
185
|
-
// that means `disableAutomaticContentInsets` on the tab, or the two
|
|
186
|
-
// insets are counted twice.
|
|
187
|
-
paddingTop:
|
|
188
|
-
(hasHeader ? theme.layout.header.height + runtime.insets.top : 0) +
|
|
189
|
-
(hasYMargin(margins) ? marginY : 0),
|
|
190
|
-
paddingHorizontal: hasXMargin(margins) ? marginX : 0,
|
|
191
|
-
// The safe area is `BottomSafeArea`'s job; counting it here as well
|
|
192
|
-
// would leave a gap the height of the gesture bar.
|
|
193
|
-
paddingBottom: bottomInset ?? (hasYMargin(margins) ? marginY : 0),
|
|
194
|
-
};
|
|
195
|
-
},
|
|
196
|
-
safeArea: {
|
|
197
|
-
height: runtime.insets.top,
|
|
198
|
-
},
|
|
199
|
-
headerWrapper: (margins: ScreenMargins) => ({
|
|
200
|
-
marginHorizontal: hasXMargin(margins) ? -marginX : 0,
|
|
201
|
-
}),
|
|
202
|
-
};
|
|
203
|
-
});
|