Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow custom StickyHeader in ScrollView-based components #25428

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions Libraries/Components/ScrollView/ScrollView.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import type {NativeMethodsMixinType} from '../../Renderer/shims/ReactNativeTypes
import type {ViewStyleProp} from '../../StyleSheet/StyleSheet';
import type {ViewProps} from '../View/ViewPropTypes';
import type {PointProp} from '../../StyleSheet/PointPropType';
import type {Props as ScrollViewStickyHeaderProps} from './ScrollViewStickyHeader';

import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
import type {State as ScrollResponderState} from '../ScrollResponder';
Expand Down Expand Up @@ -353,6 +354,10 @@ type VRProps = $ReadOnly<{|
scrollBarThumbImage?: ?($ReadOnly<{||}> | number), // Opaque type returned by import IMAGE from './image.jpg'
|}>;

type StickyHeaderComponentType = React.ComponentType<ScrollViewStickyHeaderProps> & {
setNextHeaderY: number => void,
};

export type Props = $ReadOnly<{|
...ViewProps,
...TouchableProps,
Expand Down Expand Up @@ -501,6 +506,13 @@ export type Props = $ReadOnly<{|
* with `horizontal={true}`.
*/
stickyHeaderIndices?: ?$ReadOnlyArray<number>,
/**
* A React Component that will be used to render sticky headers.
* To be used together with `stickyHeaderIndices` or with `SectionList`, defaults to `ScrollViewStickyHeader`.
* You may need to set this if your sticky header uses custom transforms (eg. translation),
* for example when you want your list to have an animated hidable header.
*/
StickyHeaderComponent: StickyHeaderComponentType,
/**
* When set, causes the scroll view to stop at multiples of the value of
* `snapToInterval`. This can be used for paginating through children
Expand Down Expand Up @@ -623,6 +635,10 @@ class ScrollView extends React.Component<Props, State> {
*/
_scrollResponder: typeof ScrollResponder.Mixin = createScrollResponder(this);

static defaultProps = {
StickyHeaderComponent: ScrollViewStickyHeader,
};

constructor(props: Props) {
super(props);

Expand Down Expand Up @@ -665,7 +681,7 @@ class ScrollView extends React.Component<Props, State> {
0,
);
_scrollAnimatedValueAttachment: ?{detach: () => void} = null;
_stickyHeaderRefs: Map<number, ScrollViewStickyHeader> = new Map();
_stickyHeaderRefs: Map<string, StickyHeaderComponentType> = new Map();
_headerLayoutYs: Map<string, number> = new Map();

state = {
Expand Down Expand Up @@ -835,7 +851,7 @@ class ScrollView extends React.Component<Props, State> {
}
}

_setStickyHeaderRef(key, ref) {
_setStickyHeaderRef(key: string, ref: ?StickyHeaderComponentType) {
if (ref) {
this._stickyHeaderRefs.set(key, ref);
} else {
Expand Down Expand Up @@ -863,7 +879,9 @@ class ScrollView extends React.Component<Props, State> {
const previousHeader = this._stickyHeaderRefs.get(
this._getKeyForIndex(previousHeaderIndex, childArray),
);
previousHeader && previousHeader.setNextHeaderY(layoutY);
previousHeader &&
previousHeader.setNextHeaderY &&
previousHeader.setNextHeaderY(layoutY);
}
}

Expand Down Expand Up @@ -980,9 +998,11 @@ class ScrollView extends React.Component<Props, State> {
if (indexOfIndex > -1) {
const key = child.key;
const nextIndex = stickyHeaderIndices[indexOfIndex + 1];
const {StickyHeaderComponent} = this.props;
return (
<ScrollViewStickyHeader
<StickyHeaderComponent
key={key}
// $FlowFixMe - inexact mixed is incompatible with exact React.Element
ref={ref => this._setStickyHeaderRef(key, ref)}
nextHeaderLayoutY={this._headerLayoutYs.get(
this._getKeyForIndex(nextIndex, childArray),
Expand All @@ -992,7 +1012,7 @@ class ScrollView extends React.Component<Props, State> {
inverted={this.props.invertStickyHeaders}
scrollViewHeight={this.state.layoutHeight}>
{child}
</ScrollViewStickyHeader>
</StickyHeaderComponent>
);
} else {
return child;
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Components/ScrollView/ScrollViewStickyHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type {LayoutEvent} from '../../Types/CoreEventTypes';

const AnimatedView = AnimatedImplementation.createAnimatedComponent(View);

type Props = {
export type Props = {
children?: React.Element<any>,
nextHeaderLayoutY: ?number,
onLayout: (event: LayoutEvent) => void,
Expand Down
7 changes: 7 additions & 0 deletions Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ exports[`FlatList renders all the bells and whistles 1`] = `
ListEmptyComponent={[Function]}
ListFooterComponent={[Function]}
ListHeaderComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -128,6 +129,7 @@ exports[`FlatList renders all the bells and whistles 1`] = `

exports[`FlatList renders empty list 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={Array []}
disableVirtualization={false}
getItem={[Function]}
Expand Down Expand Up @@ -158,6 +160,7 @@ exports[`FlatList renders empty list 1`] = `

exports[`FlatList renders null list 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
disableVirtualization={false}
getItem={[Function]}
getItemCount={[Function]}
Expand Down Expand Up @@ -187,6 +190,7 @@ exports[`FlatList renders null list 1`] = `

exports[`FlatList renders simple list (multiple columns) 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -265,6 +269,7 @@ exports[`FlatList renders simple list (multiple columns) 1`] = `

exports[`FlatList renders simple list 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -333,6 +338,7 @@ exports[`FlatList renders simple list 1`] = `
exports[`FlatList renders simple list using ListItemComponent (multiple columns) 1`] = `
<RCTScrollView
ListItemComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -411,6 +417,7 @@ exports[`FlatList renders simple list using ListItemComponent (multiple columns)
exports[`FlatList renders simple list using ListItemComponent 1`] = `
<RCTScrollView
ListItemComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

exports[`SectionList rendering empty section headers is fine 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -89,6 +90,7 @@ exports[`SectionList rendering empty section headers is fine 1`] = `

exports[`SectionList renders a footer when there is no data 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -155,6 +157,7 @@ exports[`SectionList renders a footer when there is no data 1`] = `

exports[`SectionList renders a footer when there is no data and no header 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -220,6 +223,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
ListFooterComponent={[Function]}
ListHeaderComponent={[Function]}
SectionSeparatorComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -494,6 +498,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `

exports[`SectionList renders empty list 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={Array []}
disableVirtualization={false}
getItem={[Function]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

exports[`VirtualizedList handles nested lists 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -91,6 +92,7 @@ exports[`VirtualizedList handles nested lists 1`] = `
style={null}
>
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -162,6 +164,7 @@ exports[`VirtualizedList handles nested lists 1`] = `
exports[`VirtualizedList handles separators correctly 1`] = `
<RCTScrollView
ItemSeparatorComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -243,6 +246,7 @@ exports[`VirtualizedList handles separators correctly 1`] = `
exports[`VirtualizedList handles separators correctly 2`] = `
<RCTScrollView
ItemSeparatorComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -324,6 +328,7 @@ exports[`VirtualizedList handles separators correctly 2`] = `
exports[`VirtualizedList handles separators correctly 3`] = `
<RCTScrollView
ItemSeparatorComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -409,6 +414,7 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
ListEmptyComponent={[Function]}
ListFooterComponent={[Function]}
ListHeaderComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -611,6 +617,7 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `

exports[`VirtualizedList renders empty list 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={Array []}
disableVirtualization={false}
getItem={[Function]}
Expand Down Expand Up @@ -641,6 +648,7 @@ exports[`VirtualizedList renders empty list with empty component 1`] = `
ListEmptyComponent={[Function]}
ListFooterComponent={[Function]}
ListHeaderComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={Array []}
disableVirtualization={false}
getItem={[Function]}
Expand Down Expand Up @@ -681,6 +689,7 @@ exports[`VirtualizedList renders empty list with empty component 1`] = `
exports[`VirtualizedList renders list with empty component 1`] = `
<RCTScrollView
ListEmptyComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -723,6 +732,7 @@ exports[`VirtualizedList renders list with empty component 1`] = `

exports[`VirtualizedList renders null list 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
disableVirtualization={false}
getItem={[Function]}
getItemCount={[Function]}
Expand All @@ -749,6 +759,7 @@ exports[`VirtualizedList renders null list 1`] = `

exports[`VirtualizedList renders simple list 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -814,6 +825,7 @@ exports[`VirtualizedList renders simple list 1`] = `
exports[`VirtualizedList renders simple list using ListItemComponent 1`] = `
<RCTScrollView
ListItemComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -877,6 +889,7 @@ exports[`VirtualizedList renders simple list using ListItemComponent 1`] = `

exports[`VirtualizedList test getItem functionality where data is not an Array 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Map {
"id_0" => Object {
Expand Down Expand Up @@ -920,6 +933,7 @@ exports[`VirtualizedList test getItem functionality where data is not an Array 1
exports[`VirtualizedList warns if both renderItem or ListItemComponent are specified. Uses ListItemComponent 1`] = `
<RCTScrollView
ListItemComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

exports[`VirtualizedSectionList handles nested lists 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -143,6 +144,7 @@ exports[`VirtualizedSectionList handles nested lists 1`] = `
style={null}
>
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -259,6 +261,7 @@ exports[`VirtualizedSectionList handles nested lists 1`] = `

exports[`VirtualizedSectionList handles separators correctly 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -416,6 +419,7 @@ exports[`VirtualizedSectionList handles separators correctly 1`] = `

exports[`VirtualizedSectionList handles separators correctly 2`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -573,6 +577,7 @@ exports[`VirtualizedSectionList handles separators correctly 2`] = `

exports[`VirtualizedSectionList handles separators correctly 3`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -734,6 +739,7 @@ exports[`VirtualizedSectionList renders all the bells and whistles 1`] = `
ListEmptyComponent={[Function]}
ListFooterComponent={[Function]}
ListHeaderComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -1005,6 +1011,7 @@ exports[`VirtualizedSectionList renders all the bells and whistles 1`] = `

exports[`VirtualizedSectionList renders empty list 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={Array []}
disableVirtualization={false}
getItem={[Function]}
Expand Down Expand Up @@ -1036,6 +1043,7 @@ exports[`VirtualizedSectionList renders empty list with empty component 1`] = `
ListEmptyComponent={[Function]}
ListFooterComponent={[Function]}
ListHeaderComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={Array []}
disableVirtualization={false}
getItem={[Function]}
Expand Down Expand Up @@ -1077,6 +1085,7 @@ exports[`VirtualizedSectionList renders empty list with empty component 1`] = `
exports[`VirtualizedSectionList renders list with empty component 1`] = `
<RCTScrollView
ListEmptyComponent={[Function]}
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down Expand Up @@ -1144,6 +1153,7 @@ exports[`VirtualizedSectionList renders list with empty component 1`] = `

exports[`VirtualizedSectionList renders null list 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
disableVirtualization={false}
getItem={[Function]}
getItemCount={[Function]}
Expand All @@ -1170,6 +1180,7 @@ exports[`VirtualizedSectionList renders null list 1`] = `

exports[`VirtualizedSectionList renders simple list 1`] = `
<RCTScrollView
StickyHeaderComponent={[MockFunction]}
data={
Array [
Object {
Expand Down