@react-native-ama/lists 1.0.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.
Files changed (82) hide show
  1. package/dist/core/src/components/AMAProvider.d.ts +26 -0
  2. package/dist/core/src/components/AMAProvider.js +147 -0
  3. package/dist/core/src/components/AutofocusContainer.d.ts +9 -0
  4. package/dist/core/src/components/AutofocusContainer.js +58 -0
  5. package/dist/core/src/components/HideChildrenFromAccessibilityTree.d.ts +6 -0
  6. package/dist/core/src/components/HideChildrenFromAccessibilityTree.js +40 -0
  7. package/dist/core/src/hooks/useButtonChecks.d.ts +6 -0
  8. package/dist/core/src/hooks/useButtonChecks.js +51 -0
  9. package/dist/core/src/hooks/useChecks.d.ts +15 -0
  10. package/dist/core/src/hooks/useChecks.js +152 -0
  11. package/dist/core/src/hooks/useFocus.d.ts +4 -0
  12. package/dist/core/src/hooks/useFocus.js +55 -0
  13. package/dist/core/src/hooks/useTimedAction.d.ts +3 -0
  14. package/dist/core/src/hooks/useTimedAction.js +63 -0
  15. package/dist/core/src/index.d.ts +7 -0
  16. package/dist/core/src/index.js +20 -0
  17. package/dist/internal/src/checks/checkAccessibilityRole.d.ts +3 -0
  18. package/dist/internal/src/checks/checkAccessibilityRole.js +32 -0
  19. package/dist/internal/src/checks/checkFocusTrap.d.ts +8 -0
  20. package/dist/internal/src/checks/checkFocusTrap.js +34 -0
  21. package/dist/internal/src/checks/checkForAccessibilityState.d.ts +7 -0
  22. package/dist/internal/src/checks/checkForAccessibilityState.js +48 -0
  23. package/dist/internal/src/checks/checkMinimumSize.d.ts +3 -0
  24. package/dist/internal/src/checks/checkMinimumSize.js +29 -0
  25. package/dist/internal/src/checks/contrastChecker.d.ts +8 -0
  26. package/dist/internal/src/checks/contrastChecker.js +77 -0
  27. package/dist/internal/src/checks/noUndefinedProperty.d.ts +8 -0
  28. package/dist/internal/src/checks/noUndefinedProperty.js +14 -0
  29. package/dist/internal/src/checks/uppercaseChecker.d.ts +8 -0
  30. package/dist/internal/src/checks/uppercaseChecker.js +18 -0
  31. package/dist/internal/src/checks/uppercaseStringChecker.d.ts +8 -0
  32. package/dist/internal/src/checks/uppercaseStringChecker.js +29 -0
  33. package/dist/internal/src/index.d.ts +20 -0
  34. package/dist/internal/src/index.js +56 -0
  35. package/dist/internal/src/types.d.ts +17 -0
  36. package/dist/internal/src/types.js +2 -0
  37. package/dist/internal/src/utils/applyStyle.d.ts +9 -0
  38. package/dist/internal/src/utils/applyStyle.js +33 -0
  39. package/dist/internal/src/utils/constants.d.ts +3 -0
  40. package/dist/internal/src/utils/constants.js +32 -0
  41. package/dist/internal/src/utils/error.style.d.ts +6 -0
  42. package/dist/internal/src/utils/error.style.js +11 -0
  43. package/dist/internal/src/utils/generateAccessibilityStateFromProp.d.ts +9 -0
  44. package/dist/internal/src/utils/generateAccessibilityStateFromProp.js +35 -0
  45. package/dist/internal/src/utils/getPropertyFromStyle.d.ts +2 -0
  46. package/dist/internal/src/utils/getPropertyFromStyle.js +10 -0
  47. package/dist/internal/src/utils/interpolateAnimationStates.d.ts +2 -0
  48. package/dist/internal/src/utils/interpolateAnimationStates.js +43 -0
  49. package/dist/internal/src/utils/isFocused.d.ts +2 -0
  50. package/dist/internal/src/utils/isFocused.js +7 -0
  51. package/dist/internal/src/utils/logger.d.ts +16 -0
  52. package/dist/internal/src/utils/logger.js +53 -0
  53. package/dist/internal/src/utils/logger.rules.d.ts +15 -0
  54. package/dist/internal/src/utils/logger.rules.js +74 -0
  55. package/dist/internal/src/utils/maybeGenerateStringFromElement.d.ts +2 -0
  56. package/dist/internal/src/utils/maybeGenerateStringFromElement.js +51 -0
  57. package/dist/internal/src/utils/minimumTouchableSize.d.ts +3 -0
  58. package/dist/internal/src/utils/minimumTouchableSize.js +9 -0
  59. package/dist/lists/src/components/DynamicFlatList.d.ts +7 -0
  60. package/dist/lists/src/components/DynamicFlatList.js +36 -0
  61. package/dist/lists/src/components/FlatList.d.ts +10 -0
  62. package/dist/lists/src/components/FlatList.js +24 -0
  63. package/dist/lists/src/components/ListWrapper.d.ts +7 -0
  64. package/dist/lists/src/components/ListWrapper.js +45 -0
  65. package/dist/lists/src/components/StaticFlatList.d.ts +7 -0
  66. package/dist/lists/src/components/StaticFlatList.js +34 -0
  67. package/dist/lists/src/hooks/useDynamicList.d.ts +16 -0
  68. package/dist/lists/src/hooks/useDynamicList.js +62 -0
  69. package/dist/lists/src/index.d.ts +5 -0
  70. package/dist/lists/src/index.js +15 -0
  71. package/package.json +58 -0
  72. package/src/components/DynamicFlatList.test.tsx +58 -0
  73. package/src/components/DynamicFlatList.tsx +48 -0
  74. package/src/components/FlatList.test.tsx +378 -0
  75. package/src/components/FlatList.tsx +29 -0
  76. package/src/components/ListWrapper.android.test.tsx +20 -0
  77. package/src/components/ListWrapper.ios.test.tsx +15 -0
  78. package/src/components/ListWrapper.tsx +35 -0
  79. package/src/components/StaticFlatList.tsx +32 -0
  80. package/src/hooks/useDynamicFlatList.test.ts +50 -0
  81. package/src/hooks/useDynamicList.ts +84 -0
  82. package/src/index.ts +8 -0
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@react-native-ama/lists",
3
+ "version": "1.0.0",
4
+ "private": false,
5
+ "react-native": "src/index",
6
+ "types": "dist/index.d.ts",
7
+ "main": "dist/index.js",
8
+ "exports": {
9
+ ".": [
10
+ {
11
+ "imports": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./dist/index.js"
15
+ ]
16
+ },
17
+ "files": [
18
+ "src",
19
+ "dist"
20
+ ],
21
+ "scripts": {
22
+ "build": "rm -rf dist && tsc -p ./tsconfig.build.json",
23
+ "typecheck": "tsc --noEmit"
24
+ },
25
+ "dependencies": {
26
+ "@react-native-ama/internal": "*",
27
+ "@react-native-ama/core": "*"
28
+ },
29
+ "peerDependencies": {
30
+ "expo": ">=47.0.0",
31
+ "react": "*",
32
+ "react-native": "*"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "expo": {
36
+ "optional": true
37
+ }
38
+ },
39
+ "keywords": [
40
+ "react-native",
41
+ "a11y",
42
+ "accessibility"
43
+ ],
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/FormidableLabs/react-native-ama",
47
+ "directory": "packages/lists"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/FormidableLabs/react-native-ama/issues"
51
+ },
52
+ "homepage": "https://github.com/FormidableLabs/react-native-ama#readme",
53
+ "publishConfig": {
54
+ "provenance": true
55
+ },
56
+ "author": "",
57
+ "license": "MIT"
58
+ }
@@ -0,0 +1,58 @@
1
+ import { render } from '@testing-library/react-native';
2
+ import * as React from 'react';
3
+
4
+ import * as UseDynamicList from '../hooks/useDynamicList';
5
+ import { DynamicFlatList } from './DynamicFlatList';
6
+
7
+ beforeEach(() => {
8
+ jest.clearAllMocks();
9
+ });
10
+
11
+ describe('DynamicFlatList', () => {
12
+ it('uses the style provided by useDynamicFlatList when __DEV__ is true', () => {
13
+ jest.spyOn(UseDynamicList, 'useDynamicList').mockReturnValue({
14
+ style: { backgroundColor: 'formidable' },
15
+ } as any);
16
+
17
+ const { getByTestId } = render(
18
+ <DynamicFlatList
19
+ data={[]}
20
+ singularMessage="%count% singular"
21
+ pluralMessage="%count% plurarl"
22
+ renderItem={() => null}
23
+ testID={'test-id'}
24
+ />,
25
+ );
26
+
27
+ expect(getByTestId('test-id').props.style).toEqual({
28
+ backgroundColor: 'formidable',
29
+ });
30
+ });
31
+
32
+ it('does not uses the style provided by useDynamicFlatList when __DEV__ is false', () => {
33
+ // @ts-ignore
34
+ global.__DEV__ = false;
35
+
36
+ jest.spyOn(UseDynamicList, 'useDynamicList').mockReturnValue({
37
+ style: { backgroundColor: 'formidable' },
38
+ } as any);
39
+
40
+ const {
41
+ DynamicFlatList: OriginalDynamicFlatList,
42
+ } = require('./DynamicFlatList');
43
+
44
+ const { getByTestId } = render(
45
+ <OriginalDynamicFlatList
46
+ data={[]}
47
+ singularMessage="%count% singular"
48
+ pluralMessage="%count% plurarl"
49
+ renderItem={() => null}
50
+ testID={'test-id'}
51
+ />,
52
+ );
53
+
54
+ expect(getByTestId('test-id').props.style).toEqual(undefined);
55
+ });
56
+ });
57
+
58
+ jest.mock('../hooks/useDynamicList');
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import { FlatList, FlatListProps } from 'react-native';
3
+
4
+ import { useDynamicList } from '../hooks/useDynamicList';
5
+ import { ListWrapper } from './ListWrapper';
6
+
7
+ type DynamicFlatListProps<T> = FlatListProps<T> & {
8
+ singularMessage: string;
9
+ pluralMessage: string;
10
+ isPlural?: (count: number) => boolean;
11
+ };
12
+
13
+ export const DynamicFlatList = React.forwardRef<
14
+ FlatList,
15
+ DynamicFlatListProps<any>
16
+ >(
17
+ (
18
+ { data, singularMessage, pluralMessage, isPlural, ...rest },
19
+ forwardedRef,
20
+ ) => {
21
+ const dynamicList = useDynamicList({
22
+ data: data ?? [],
23
+ pluralMessage,
24
+ singularMessage,
25
+ numColumns: rest.numColumns,
26
+ isPlural,
27
+ });
28
+
29
+ return __DEV__ ? (
30
+ <ListWrapper
31
+ rowsCount={dynamicList.rowsCount}
32
+ columnsCount={dynamicList.columnsCount}>
33
+ <FlatList
34
+ data={data}
35
+ {...rest}
36
+ ref={forwardedRef}
37
+ style={dynamicList.style}
38
+ />
39
+ </ListWrapper>
40
+ ) : (
41
+ <ListWrapper
42
+ rowsCount={dynamicList.rowsCount}
43
+ columnsCount={dynamicList.columnsCount}>
44
+ <FlatList data={data} {...rest} ref={forwardedRef} />
45
+ </ListWrapper>
46
+ );
47
+ },
48
+ );
@@ -0,0 +1,378 @@
1
+ import { render, waitFor } from '@testing-library/react-native';
2
+ import * as React from 'react';
3
+ import { AccessibilityInfo, Platform } from 'react-native';
4
+
5
+ import { FlatList } from './FlatList';
6
+
7
+ beforeEach(() => {
8
+ jest.clearAllMocks();
9
+
10
+ AccessibilityInfo.announceForAccessibility = jest.fn();
11
+ });
12
+
13
+ Platform.OS = 'android';
14
+
15
+ describe('FlatList', () => {
16
+ describe('listType "dynamic"', () => {
17
+ it('does not announce the number of items on first render', async () => {
18
+ render(
19
+ <FlatList
20
+ data={DATA}
21
+ listType="dynamic"
22
+ singularMessage={'%count% item found'}
23
+ pluralMessage={'%count% items found'}
24
+ renderItem={() => null}
25
+ />,
26
+ );
27
+
28
+ await waitFor(() => {
29
+ expect(
30
+ AccessibilityInfo.announceForAccessibility,
31
+ ).not.toHaveBeenCalled();
32
+ });
33
+ });
34
+
35
+ it.each`
36
+ sliceStart | sliceEnd | count | type
37
+ ${0} | ${2} | ${2} | ${'plural'}
38
+ ${1} | ${2} | ${1} | ${'singular'}
39
+ `(
40
+ 'announce the number of items displayed when the data changes',
41
+ ({ sliceStart, sliceEnd, count, type }) => {
42
+ const messages: Record<string, string> = {
43
+ singular: '%count% item found',
44
+ plural: '%count% items found',
45
+ };
46
+
47
+ const renderAPI = render(
48
+ <FlatList
49
+ data={DATA}
50
+ listType="dynamic"
51
+ singularMessage={messages.singular}
52
+ pluralMessage={messages.plural}
53
+ renderItem={() => null}
54
+ />,
55
+ );
56
+
57
+ renderAPI.update(
58
+ <FlatList
59
+ data={DATA.slice(sliceStart, sliceEnd)}
60
+ listType="dynamic"
61
+ singularMessage={messages.singular}
62
+ pluralMessage={messages.plural}
63
+ renderItem={() => null}
64
+ />,
65
+ );
66
+
67
+ expect(AccessibilityInfo.announceForAccessibility).toHaveBeenCalledWith(
68
+ messages[type].replace('%count%', count),
69
+ );
70
+ },
71
+ );
72
+
73
+ it.each`
74
+ sliceStart | sliceEnd
75
+ ${0} | ${2}
76
+ ${1} | ${2}
77
+ `('resets the count of announced items', ({ sliceStart, sliceEnd }) => {
78
+ const messages: Record<string, string> = {
79
+ singular: '%count% item found',
80
+ plural: '%count% items found',
81
+ };
82
+
83
+ const renderAPI = render(
84
+ <FlatList
85
+ data={DATA}
86
+ listType="dynamic"
87
+ singularMessage={messages.singular}
88
+ pluralMessage={messages.plural}
89
+ renderItem={() => null}
90
+ />,
91
+ );
92
+
93
+ renderAPI.update(
94
+ <FlatList
95
+ data={DATA.slice(sliceStart, sliceEnd)}
96
+ listType="dynamic"
97
+ singularMessage={messages.singular}
98
+ pluralMessage={messages.plural}
99
+ renderItem={() => null}
100
+ />,
101
+ );
102
+
103
+ renderAPI.update(
104
+ <FlatList
105
+ data={DATA}
106
+ listType="dynamic"
107
+ singularMessage={messages.singular}
108
+ pluralMessage={messages.plural}
109
+ renderItem={() => null}
110
+ />,
111
+ );
112
+
113
+ renderAPI.update(
114
+ <FlatList
115
+ data={DATA.slice(sliceStart, sliceEnd)}
116
+ listType="dynamic"
117
+ singularMessage={messages.singular}
118
+ pluralMessage={messages.plural}
119
+ renderItem={() => null}
120
+ />,
121
+ );
122
+
123
+ expect(AccessibilityInfo.announceForAccessibility).toHaveBeenCalledTimes(
124
+ 2,
125
+ );
126
+ });
127
+
128
+ it('does not announce if the list shows again all the items', () => {
129
+ const { update } = render(
130
+ <FlatList
131
+ data={DATA}
132
+ listType="dynamic"
133
+ singularMessage={'%count% item found'}
134
+ pluralMessage={'%count% items found'}
135
+ renderItem={() => null}
136
+ />,
137
+ );
138
+
139
+ update(
140
+ <FlatList
141
+ data={DATA}
142
+ listType="dynamic"
143
+ singularMessage={'%count% item found'}
144
+ pluralMessage={'%count% items found'}
145
+ renderItem={() => null}
146
+ />,
147
+ );
148
+
149
+ expect(AccessibilityInfo.announceForAccessibility).not.toHaveBeenCalled();
150
+ });
151
+
152
+ it('allows using a custom function to detect if the number of items displayed is plural', () => {
153
+ const isPlural = jest.fn().mockReturnValue(false);
154
+
155
+ const { update } = render(
156
+ <FlatList
157
+ data={DATA}
158
+ listType="dynamic"
159
+ singularMessage={'%count% item found'}
160
+ pluralMessage={'%count% items found'}
161
+ isPlural={isPlural}
162
+ renderItem={() => null}
163
+ />,
164
+ );
165
+
166
+ update(
167
+ <FlatList
168
+ data={DATA.slice(1)}
169
+ listType="dynamic"
170
+ singularMessage={'%count% item found'}
171
+ pluralMessage={'%count% items found'}
172
+ isPlural={isPlural}
173
+ renderItem={() => null}
174
+ />,
175
+ );
176
+
177
+ expect(isPlural).toHaveBeenCalledWith(2);
178
+ expect(AccessibilityInfo.announceForAccessibility).toHaveBeenCalledWith(
179
+ '2 item found',
180
+ );
181
+ });
182
+ });
183
+
184
+ describe('listType="static"', () => {
185
+ it.each`
186
+ sliceStart | sliceEnd | count | type
187
+ ${0} | ${2} | ${2} | ${'plural'}
188
+ ${1} | ${2} | ${1} | ${'singular'}
189
+ `(
190
+ 'does not announce the number of items displayed when the data changes',
191
+ ({ sliceStart, sliceEnd }) => {
192
+ const renderAPI = render(
193
+ <FlatList data={DATA} listType="static" renderItem={() => null} />,
194
+ );
195
+
196
+ renderAPI.update(
197
+ <FlatList
198
+ data={DATA.slice(sliceStart, sliceEnd)}
199
+ listType="static"
200
+ renderItem={() => null}
201
+ />,
202
+ );
203
+
204
+ expect(
205
+ AccessibilityInfo.announceForAccessibility,
206
+ ).not.toHaveBeenCalled();
207
+ },
208
+ );
209
+
210
+ it('uses the given rowCount when specified', () => {
211
+ const { toJSON } = render(
212
+ <FlatList
213
+ data={[]}
214
+ listType="static"
215
+ rowsCount={42}
216
+ renderItem={() => null}
217
+ />,
218
+ );
219
+
220
+ expect(toJSON()).toMatchInlineSnapshot(`
221
+ <View
222
+ columnsCount={1}
223
+ rowsCount={42}
224
+ >
225
+ <RCTScrollView
226
+ data={Array []}
227
+ getItem={[Function]}
228
+ getItemCount={[Function]}
229
+ keyExtractor={[Function]}
230
+ onContentSizeChange={[Function]}
231
+ onLayout={[Function]}
232
+ onMomentumScrollBegin={[Function]}
233
+ onMomentumScrollEnd={[Function]}
234
+ onScroll={[Function]}
235
+ onScrollBeginDrag={[Function]}
236
+ onScrollEndDrag={[Function]}
237
+ removeClippedSubviews={true}
238
+ renderItem={[Function]}
239
+ scrollEventThrottle={50}
240
+ stickyHeaderIndices={Array []}
241
+ viewabilityConfigCallbackPairs={Array []}
242
+ >
243
+ <View />
244
+ </RCTScrollView>
245
+ </View>
246
+ `);
247
+ });
248
+
249
+ it('divides the number of items by the number of columns', () => {
250
+ const { toJSON } = render(
251
+ <FlatList
252
+ data={DATA}
253
+ listType="static"
254
+ numColumns={2}
255
+ renderItem={() => null}
256
+ />,
257
+ );
258
+
259
+ expect(toJSON()).toMatchInlineSnapshot(`
260
+ <View
261
+ columnsCount={2}
262
+ rowsCount={2}
263
+ >
264
+ <RCTScrollView
265
+ data={
266
+ Array [
267
+ Object {
268
+ "id": "bd7acbea-c1b1-46c2-aed5-3ad53abb28ba",
269
+ "title": "Formidable",
270
+ },
271
+ Object {
272
+ "id": "3ac68afc-c605-48d3-a4f8-fbd91aa97f63",
273
+ "title": "React Native",
274
+ },
275
+ Object {
276
+ "id": "58694a0f-3da1-471f-bd96-145571e29d72",
277
+ "title": "Typescript",
278
+ },
279
+ ]
280
+ }
281
+ getItem={[Function]}
282
+ getItemCount={[Function]}
283
+ keyExtractor={[Function]}
284
+ onContentSizeChange={[Function]}
285
+ onLayout={[Function]}
286
+ onMomentumScrollBegin={[Function]}
287
+ onMomentumScrollEnd={[Function]}
288
+ onScroll={[Function]}
289
+ onScrollBeginDrag={[Function]}
290
+ onScrollEndDrag={[Function]}
291
+ removeClippedSubviews={true}
292
+ renderItem={[Function]}
293
+ scrollEventThrottle={50}
294
+ stickyHeaderIndices={Array []}
295
+ viewabilityConfigCallbackPairs={Array []}
296
+ >
297
+ <View>
298
+ <View
299
+ onLayout={[Function]}
300
+ style={null}
301
+ />
302
+ <View
303
+ onLayout={[Function]}
304
+ style={null}
305
+ />
306
+ <View
307
+ onLayout={[Function]}
308
+ style={null}
309
+ />
310
+ </View>
311
+ </RCTScrollView>
312
+ </View>
313
+ `);
314
+ });
315
+
316
+ it('sets rowsCount to 0 if the data is undefined', () => {
317
+ const { toJSON } = render(
318
+ <FlatList
319
+ data={undefined}
320
+ listType="static"
321
+ numColumns={2}
322
+ renderItem={() => null}
323
+ />,
324
+ );
325
+
326
+ expect(toJSON()).toMatchInlineSnapshot(`
327
+ <View
328
+ columnsCount={2}
329
+ rowsCount={0}
330
+ >
331
+ <RCTScrollView
332
+ getItem={[Function]}
333
+ getItemCount={[Function]}
334
+ keyExtractor={[Function]}
335
+ onContentSizeChange={[Function]}
336
+ onLayout={[Function]}
337
+ onMomentumScrollBegin={[Function]}
338
+ onMomentumScrollEnd={[Function]}
339
+ onScroll={[Function]}
340
+ onScrollBeginDrag={[Function]}
341
+ onScrollEndDrag={[Function]}
342
+ removeClippedSubviews={true}
343
+ renderItem={[Function]}
344
+ scrollEventThrottle={50}
345
+ stickyHeaderIndices={Array []}
346
+ viewabilityConfigCallbackPairs={Array []}
347
+ >
348
+ <View />
349
+ </RCTScrollView>
350
+ </View>
351
+ `);
352
+ });
353
+ });
354
+ });
355
+
356
+ jest.mock('./ListWrapper', () => {
357
+ const { View } = jest.requireActual('react-native');
358
+
359
+ return {
360
+ // @ts-ignore
361
+ ListWrapper: props => <View {...props} />,
362
+ };
363
+ });
364
+
365
+ const DATA = [
366
+ {
367
+ id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
368
+ title: 'Formidable',
369
+ },
370
+ {
371
+ id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
372
+ title: 'React Native',
373
+ },
374
+ {
375
+ id: '58694a0f-3da1-471f-bd96-145571e29d72',
376
+ title: 'Typescript',
377
+ },
378
+ ];
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import type {
3
+ FlatList as RNFlatList,
4
+ FlatListProps as RNFlatListProps,
5
+ } from 'react-native';
6
+
7
+ import type { UseDynamicList } from '../hooks/useDynamicList';
8
+ import { DynamicFlatList } from './DynamicFlatList';
9
+ import { StaticFlatList, StaticFlatListProps } from './StaticFlatList';
10
+
11
+ export type FlatListProps<T> = RNFlatListProps<T> &
12
+ (
13
+ | ({
14
+ listType: 'static';
15
+ } & StaticFlatListProps)
16
+ | ({
17
+ listType: 'dynamic';
18
+ } & UseDynamicList)
19
+ );
20
+
21
+ export const FlatList = React.forwardRef<RNFlatList, FlatListProps<any>>(
22
+ ({ listType, ...rest }, forwardRef) => {
23
+ return listType === 'dynamic' ? (
24
+ <DynamicFlatList ref={forwardRef} {...(rest as any)} />
25
+ ) : (
26
+ <StaticFlatList ref={forwardRef} {...(rest as any)} />
27
+ );
28
+ },
29
+ );
@@ -0,0 +1,20 @@
1
+ import { render } from '@testing-library/react-native';
2
+ import * as React from 'react';
3
+ import { Platform } from 'react-native';
4
+
5
+ describe('ListWrapper', () => {
6
+ it('renders the AMAFlatListWrapper is the Platform is Android', () => {
7
+ Platform.OS = 'android';
8
+
9
+ const { ListWrapper } = require('./ListWrapper');
10
+
11
+ const result = render(<ListWrapper rowsCount={1} />);
12
+
13
+ expect(result.toJSON()).toMatchInlineSnapshot(`
14
+ <AmaFlatListWrapper
15
+ columnsCount={1}
16
+ rowsCount={1}
17
+ />
18
+ `);
19
+ });
20
+ });
@@ -0,0 +1,15 @@
1
+ import { render } from '@testing-library/react-native';
2
+ import * as React from 'react';
3
+ import { Platform } from 'react-native';
4
+
5
+ describe('ListWrapper', () => {
6
+ it('renders an empty wrapper is the Platform is iOS', () => {
7
+ Platform.OS = 'ios';
8
+
9
+ const { ListWrapper } = require('./ListWrapper');
10
+
11
+ const result = render(<ListWrapper rowsCount={1} />);
12
+
13
+ expect(result.toJSON()).toMatchInlineSnapshot('null');
14
+ });
15
+ });
@@ -0,0 +1,35 @@
1
+ import * as React from 'react';
2
+ import { Platform, requireNativeComponent } from 'react-native';
3
+
4
+ type FlatListWrapperProps = React.PropsWithChildren<{
5
+ rowsCount: number;
6
+ columnsCount?: number;
7
+ }>;
8
+
9
+ const isAndroid = Platform.OS === 'android';
10
+
11
+ export const ListWrapper = ({
12
+ children,
13
+ rowsCount,
14
+ columnsCount = 1,
15
+ }: FlatListWrapperProps) => {
16
+ return (
17
+ <AMAFlatListWrapper rowsCount={rowsCount} columnsCount={columnsCount}>
18
+ {children}
19
+ </AMAFlatListWrapper>
20
+ );
21
+ };
22
+
23
+ /*
24
+ * Used instead of React.Fragment to avoid the warning:
25
+ * ...React.Fragment can only have `key` and `children` props.
26
+ */
27
+ const EmptyComponent: React.FC = ({
28
+ children,
29
+ }: React.PropsWithChildren<{}>) => {
30
+ return <>{children}</>;
31
+ };
32
+
33
+ const AMAFlatListWrapper = isAndroid
34
+ ? requireNativeComponent<FlatListWrapperProps>('AmaFlatListWrapper')
35
+ : EmptyComponent;
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { FlatList, FlatListProps } from 'react-native';
3
+
4
+ import { ListWrapper } from './ListWrapper';
5
+
6
+ export type StaticFlatListProps = {
7
+ rowsCount?: number;
8
+ numColumns?: number;
9
+ };
10
+
11
+ export const StaticFlatList = React.forwardRef<
12
+ FlatList,
13
+ FlatListProps<any> & StaticFlatListProps
14
+ >(({ data, numColumns, rowsCount, ...rest }, ref) => {
15
+ const columns = numColumns || 1;
16
+
17
+ const rows = React.useMemo(() => {
18
+ if (rowsCount) {
19
+ return rowsCount;
20
+ }
21
+
22
+ const length = data?.length || 0;
23
+
24
+ return Math.ceil(columns > 1 && length > 0 ? length / columns : length);
25
+ }, [columns, data?.length, rowsCount]);
26
+
27
+ return (
28
+ <ListWrapper rowsCount={rows} columnsCount={columns}>
29
+ <FlatList data={data} {...rest} ref={ref} />
30
+ </ListWrapper>
31
+ );
32
+ });