@codeleap/mobile 4.2.5 → 4.2.7
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/dist/components/SectionFilters/context.d.ts +46 -0
- package/dist/components/SectionFilters/context.js +10 -0
- package/dist/components/SectionFilters/context.js.map +1 -0
- package/dist/components/SectionFilters/index.d.ts +10 -0
- package/dist/components/SectionFilters/index.js +85 -0
- package/dist/components/SectionFilters/index.js.map +1 -0
- package/dist/components/SectionFilters/types.d.ts +19 -0
- package/dist/components/SectionFilters/types.js +3 -0
- package/dist/components/SectionFilters/types.js.map +1 -0
- package/dist/components/SectionFilters/useSectionFilters.d.ts +35 -0
- package/dist/components/SectionFilters/useSectionFilters.js +133 -0
- package/dist/components/SectionFilters/useSectionFilters.js.map +1 -0
- package/dist/components/Sections/index.d.ts +11 -3
- package/dist/components/Sections/index.js +54 -38
- package/dist/components/Sections/index.js.map +1 -1
- package/dist/components/Sections/styles.d.ts +5 -1
- package/dist/components/Sections/types.d.ts +26 -12
- package/dist/components/components.d.ts +1 -0
- package/dist/components/components.js +1 -0
- package/dist/components/components.js.map +1 -1
- package/package.json +5 -5
- package/package.json.bak +1 -1
- package/src/components/SectionFilters/context.tsx +15 -0
- package/src/components/SectionFilters/index.tsx +82 -0
- package/src/components/SectionFilters/types.ts +29 -0
- package/src/components/SectionFilters/useSectionFilters.tsx +166 -0
- package/src/components/Sections/index.tsx +99 -57
- package/src/components/Sections/styles.ts +6 -1
- package/src/components/Sections/types.ts +33 -16
- package/src/components/components.ts +1 -0
|
@@ -56,4 +56,5 @@ __exportStar(require("./SearchInput"), exports);
|
|
|
56
56
|
__exportStar(require("./PaginationIndicator"), exports);
|
|
57
57
|
__exportStar(require("./PlacesAutocomplete"), exports);
|
|
58
58
|
__exportStar(require("./SortablePhotos"), exports);
|
|
59
|
+
__exportStar(require("./SectionFilters"), exports);
|
|
59
60
|
//# sourceMappingURL=components.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"components.js","sourceRoot":"","sources":["../../src/components/components.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,yCAAsB;AACtB,yCAAsB;AACtB,8CAA2B;AAC3B,yCAAsB;AACtB,0CAAuB;AACvB,6CAA0B;AAC1B,8CAA2B;AAC3B,+CAA4B;AAC5B,2CAAwB;AACxB,6CAA0B;AAC1B,2CAAwB;AACxB,8CAA2B;AAC3B,2CAAwB;AACxB,qDAAkC;AAClC,+CAA4B;AAC5B,2CAAwB;AACxB,yCAAsB;AACtB,sDAAmC;AACnC,2CAAwB;AACxB,0CAAuB;AACvB,6CAA0B;AAC1B,8CAA2B;AAC3B,+CAA4B;AAC5B,0CAAuB;AACvB,qDAAkC;AAClC,6CAA0B;AAC1B,qDAAkC;AAClC,iDAA8B;AAC9B,yCAAsB;AACtB,mDAAgC;AAChC,+CAA4B;AAC5B,8CAA2B;AAC3B,oDAAiC;AACjC,mDAAgC;AAChC,2CAAwB;AACxB,gDAA6B;AAC7B,oDAAiC;AACjC,0CAAuB;AACvB,gDAA6B;AAC7B,wDAAqC;AACrC,uDAAoC;AACpC,mDAAgC"}
|
|
1
|
+
{"version":3,"file":"components.js","sourceRoot":"","sources":["../../src/components/components.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,yCAAsB;AACtB,yCAAsB;AACtB,8CAA2B;AAC3B,yCAAsB;AACtB,0CAAuB;AACvB,6CAA0B;AAC1B,8CAA2B;AAC3B,+CAA4B;AAC5B,2CAAwB;AACxB,6CAA0B;AAC1B,2CAAwB;AACxB,8CAA2B;AAC3B,2CAAwB;AACxB,qDAAkC;AAClC,+CAA4B;AAC5B,2CAAwB;AACxB,yCAAsB;AACtB,sDAAmC;AACnC,2CAAwB;AACxB,0CAAuB;AACvB,6CAA0B;AAC1B,8CAA2B;AAC3B,+CAA4B;AAC5B,0CAAuB;AACvB,qDAAkC;AAClC,6CAA0B;AAC1B,qDAAkC;AAClC,iDAA8B;AAC9B,yCAAsB;AACtB,mDAAgC;AAChC,+CAA4B;AAC5B,8CAA2B;AAC3B,oDAAiC;AACjC,mDAAgC;AAChC,2CAAwB;AACxB,gDAA6B;AAC7B,oDAAiC;AACjC,0CAAuB;AACvB,gDAA6B;AAC7B,wDAAqC;AACrC,uDAAoC;AACpC,mDAAgC;AAChC,mDAAgC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codeleap/mobile",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.7",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
"directory": "packages/mobile"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@codeleap/common": "4.2.
|
|
13
|
-
"@codeleap/config": "4.2.
|
|
12
|
+
"@codeleap/common": "4.2.7",
|
|
13
|
+
"@codeleap/config": "4.2.7"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsc --build",
|
|
17
17
|
"lint": "eslint -c .eslintrc.js --fix \"./src/**/*.{ts,tsx,js,jsx}\""
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@codeleap/common": "4.2.
|
|
21
|
-
"@codeleap/styles": "4.2.
|
|
20
|
+
"@codeleap/common": "4.2.7",
|
|
21
|
+
"@codeleap/styles": "4.2.7",
|
|
22
22
|
"@d11/react-native-fast-image": "8.8.0",
|
|
23
23
|
"@react-native-firebase/messaging": "14.4.0",
|
|
24
24
|
"@react-navigation/bottom-tabs": "6.5.3",
|
package/package.json.bak
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react'
|
|
2
|
+
import { SectionFiltersProps } from './types'
|
|
3
|
+
import { TSectionFilterItem, useSectionFilters } from './useSectionFilters'
|
|
4
|
+
|
|
5
|
+
export type SectionFiltersContextProps<T = TSectionFilterItem> = React.PropsWithChildren<SectionFiltersProps<T> & {
|
|
6
|
+
handle?: ReturnType<typeof useSectionFilters<T>>
|
|
7
|
+
}>
|
|
8
|
+
|
|
9
|
+
type TSectionFiltersContext<T = TSectionFilterItem> = ReturnType<typeof useSectionFilters<T>>
|
|
10
|
+
|
|
11
|
+
export const SectionsFilterContext = createContext({} as TSectionFiltersContext)
|
|
12
|
+
|
|
13
|
+
export function useSectionFiltersContext<T = TSectionFilterItem>() {
|
|
14
|
+
return useContext(SectionsFilterContext) as TSectionFiltersContext<T>
|
|
15
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { memoBy, TypeGuards } from '@codeleap/common'
|
|
2
|
+
import { Button } from '../Button'
|
|
3
|
+
import { AugmentedSectionRenderItemInfo, SectionComponentProps, Sections } from '../Sections'
|
|
4
|
+
import { Text } from '../Text'
|
|
5
|
+
import { SectionsFilterContext, useSectionFiltersContext } from './context'
|
|
6
|
+
import { SectionFilterComponentProps, SectionFiltersProps } from './types'
|
|
7
|
+
import { TSectionFilterItem, useSectionFilters } from './useSectionFilters'
|
|
8
|
+
|
|
9
|
+
export * from './types'
|
|
10
|
+
export * from './context'
|
|
11
|
+
export * from './useSectionFilters'
|
|
12
|
+
|
|
13
|
+
const SectionComponent = memoBy((props: SectionComponentProps<TSectionFilterItem> & { renderWith: (props: SectionFilterComponentProps<TSectionFilterItem>) => JSX.Element }) => {
|
|
14
|
+
const { renderWith: Component, index } = props
|
|
15
|
+
|
|
16
|
+
const handle = useSectionFiltersContext()
|
|
17
|
+
|
|
18
|
+
if (!Component) return null
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Component
|
|
22
|
+
{...props}
|
|
23
|
+
selectedItems={handle.selectedItems[index] ?? []}
|
|
24
|
+
clearSelectedItems={() => handle.clearSelectedItemsWithSection(index)}
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
}, ['renderWith'])
|
|
28
|
+
|
|
29
|
+
export function SectionFilters<T extends TSectionFilterItem = TSectionFilterItem>(props: SectionFiltersProps<T>) {
|
|
30
|
+
const {
|
|
31
|
+
sections,
|
|
32
|
+
renderItem: RenderItem,
|
|
33
|
+
renderSectionHeader,
|
|
34
|
+
renderSectionFooter,
|
|
35
|
+
children,
|
|
36
|
+
...rest
|
|
37
|
+
} = {
|
|
38
|
+
...SectionFilters.defaultProps,
|
|
39
|
+
...props,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const handle = props.handle ?? useSectionFilters(props)
|
|
43
|
+
|
|
44
|
+
const renderItem = (section: AugmentedSectionRenderItemInfo<T>) => {
|
|
45
|
+
const hasSection = !TypeGuards.isNil(section?.index) && !TypeGuards.isNil(section)
|
|
46
|
+
const sectionLimitReached = hasSection && handle.sectionLimitReached(section?.index)
|
|
47
|
+
const limitReached = handle.limitReached()
|
|
48
|
+
const disableOnReachLimit = handle.disableItemsOnLimitReached
|
|
49
|
+
const disableNonSelectedItems = (limitReached || sectionLimitReached) && disableOnReachLimit
|
|
50
|
+
|
|
51
|
+
const isSelected = handle.isSelected(section?.item)
|
|
52
|
+
const isDisabled = disableNonSelectedItems && !isSelected
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<RenderItem
|
|
56
|
+
{...section}
|
|
57
|
+
onPress={() => handle.toggleItem(section?.item)}
|
|
58
|
+
selected={isSelected}
|
|
59
|
+
disabled={isDisabled}
|
|
60
|
+
text={section?.item?.label}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<SectionsFilterContext.Provider value={handle}>
|
|
67
|
+
<Sections
|
|
68
|
+
{...rest}
|
|
69
|
+
sections={sections}
|
|
70
|
+
renderItem={renderItem}
|
|
71
|
+
renderSectionHeader={(props) => <SectionComponent {...props} renderWith={renderSectionHeader} />}
|
|
72
|
+
renderSectionFooter={(props) => <SectionComponent {...props} renderWith={renderSectionFooter} />}
|
|
73
|
+
/>
|
|
74
|
+
{children}
|
|
75
|
+
</SectionsFilterContext.Provider>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
SectionFilters.defaultProps = {
|
|
80
|
+
renderItem: Button as unknown,
|
|
81
|
+
renderSectionHeader: ({ title }) => <Text text={title} />
|
|
82
|
+
} as Partial<SectionFiltersProps>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ReactElement } from 'react'
|
|
2
|
+
import { AugmentedSectionRenderItemInfo, SectionComponentProps, SectionProps } from '../Sections'
|
|
3
|
+
import { TSectionFilterItem, UseSectionFilters, useSectionFilters } from './useSectionFilters'
|
|
4
|
+
|
|
5
|
+
export type SectionFilterItemProps<T> =
|
|
6
|
+
AugmentedSectionRenderItemInfo<T> &
|
|
7
|
+
{
|
|
8
|
+
onPress: () => void
|
|
9
|
+
selected: boolean
|
|
10
|
+
disabled: boolean
|
|
11
|
+
text: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type SectionFilterComponentProps<T> =
|
|
15
|
+
SectionComponentProps<T> &
|
|
16
|
+
{
|
|
17
|
+
selectedItems: TSectionFilterItem[]
|
|
18
|
+
clearSelectedItems: () => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type SectionFiltersProps<T = any> =
|
|
22
|
+
UseSectionFilters &
|
|
23
|
+
Omit<SectionProps<T>, 'renderItem' | 'renderSectionFooter' | 'renderSectionHeader'> &
|
|
24
|
+
{
|
|
25
|
+
handle?: ReturnType<typeof useSectionFilters<T>>
|
|
26
|
+
renderItem?: (props: SectionFilterItemProps<T>) => ReactElement
|
|
27
|
+
renderSectionFooter?: (props: SectionFilterComponentProps<T>) => ReactElement
|
|
28
|
+
renderSectionHeader?: (props: SectionFilterComponentProps<T>) => ReactElement
|
|
29
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { deepEqual, TypeGuards } from '@codeleap/common'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
export type TSectionFilterItem = {
|
|
5
|
+
value?: string | number
|
|
6
|
+
label?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type Section<T = TSectionFilterItem> = {
|
|
10
|
+
data: T[]
|
|
11
|
+
title: string
|
|
12
|
+
selectionLimit?: number
|
|
13
|
+
disableItemsOnLimitReached?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type SelectedItemsPerSection<T = TSectionFilterItem> = { [X: number]: T[] }
|
|
17
|
+
|
|
18
|
+
export type UseSectionFilters<T = TSectionFilterItem> = {
|
|
19
|
+
sections: Section<T>[]
|
|
20
|
+
areItemsEqual?: (a: T, b: T) => boolean
|
|
21
|
+
selectionLimit?: number
|
|
22
|
+
sectionSelectionLimit?: number
|
|
23
|
+
disableItemsOnLimitReached?: boolean
|
|
24
|
+
initialSelectedItems?: SelectedItemsPerSection<T>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useSectionFilters<T = TSectionFilterItem>(props: UseSectionFilters<T>) {
|
|
28
|
+
const {
|
|
29
|
+
sections,
|
|
30
|
+
areItemsEqual = deepEqual,
|
|
31
|
+
selectionLimit = 1,
|
|
32
|
+
sectionSelectionLimit = null,
|
|
33
|
+
disableItemsOnLimitReached = selectionLimit > 1 && !sectionSelectionLimit,
|
|
34
|
+
initialSelectedItems = [],
|
|
35
|
+
} = props
|
|
36
|
+
|
|
37
|
+
const [selectedItems, setSelectedItems] = React.useState<SelectedItemsPerSection<T>>(() => {
|
|
38
|
+
if (TypeGuards.isArray(initialSelectedItems)) {
|
|
39
|
+
return {
|
|
40
|
+
0: initialSelectedItems,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return initialSelectedItems ?? {}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const findItemSection = (item: T) => {
|
|
48
|
+
if (!sections) {
|
|
49
|
+
return {
|
|
50
|
+
sectionIndex: 0,
|
|
51
|
+
section: null,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const sectionIndex = sections?.findIndex((section) => {
|
|
56
|
+
return section.data.some((i) => areItemsEqual(item, i))
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
if (sectionIndex === -1) {
|
|
60
|
+
return {
|
|
61
|
+
sectionIndex: null,
|
|
62
|
+
section: null,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const section = sections[sectionIndex]
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
sectionIndex,
|
|
70
|
+
section,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const isSelected = (item: T) => {
|
|
75
|
+
if (sections) {
|
|
76
|
+
const { sectionIndex } = findItemSection(item)
|
|
77
|
+
|
|
78
|
+
return selectedItems[sectionIndex]?.some((i) => areItemsEqual(i, item))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return selectedItems[0]?.some((i) => areItemsEqual(i, item))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const toggleItem = (item: T) => {
|
|
85
|
+
let sectionIndex = -1
|
|
86
|
+
let limit = selectionLimit
|
|
87
|
+
|
|
88
|
+
if (sections) {
|
|
89
|
+
const { sectionIndex: si, section } = findItemSection(item)
|
|
90
|
+
|
|
91
|
+
if (si === null) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
sectionIndex = si
|
|
96
|
+
limit = section.selectionLimit ?? selectionLimit
|
|
97
|
+
} else {
|
|
98
|
+
sectionIndex = 0
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const currentItems = selectedItems[sectionIndex] ?? []
|
|
102
|
+
|
|
103
|
+
const isItemSelected = currentItems.some((i) => areItemsEqual(i, item))
|
|
104
|
+
|
|
105
|
+
const newItems = [...currentItems]
|
|
106
|
+
|
|
107
|
+
if (isItemSelected) {
|
|
108
|
+
const index = newItems.findIndex((i) => areItemsEqual(i, item))
|
|
109
|
+
|
|
110
|
+
newItems.splice(index, 1)
|
|
111
|
+
} else {
|
|
112
|
+
if (newItems.length >= limit) {
|
|
113
|
+
newItems.shift()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
newItems.push(item)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
setSelectedItems({
|
|
120
|
+
...selectedItems,
|
|
121
|
+
[sectionIndex]: newItems,
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function sectionLimitReached(sectionIndex: number) {
|
|
126
|
+
const section = sections[sectionIndex]
|
|
127
|
+
|
|
128
|
+
if (!section) {
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const limit = section.selectionLimit ?? sectionSelectionLimit ?? selectionLimit
|
|
133
|
+
|
|
134
|
+
if (!limit) {
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const nItems = selectedItems[sectionIndex]?.length
|
|
139
|
+
|
|
140
|
+
return nItems >= limit
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function limitReached() {
|
|
144
|
+
const nItems = Object.values(selectedItems).flatMap((i) => i).length
|
|
145
|
+
|
|
146
|
+
return nItems >= selectionLimit
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function clearSelectedItemsWithSection(sectionIndex: number) {
|
|
150
|
+
setSelectedItems({
|
|
151
|
+
...selectedItems,
|
|
152
|
+
[sectionIndex]: [],
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
isSelected,
|
|
158
|
+
toggleItem,
|
|
159
|
+
findItemSection,
|
|
160
|
+
selectedItems,
|
|
161
|
+
sectionLimitReached,
|
|
162
|
+
limitReached,
|
|
163
|
+
disableItemsOnLimitReached,
|
|
164
|
+
clearSelectedItemsWithSection,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -1,118 +1,160 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import { useCallback } from '@codeleap/common'
|
|
3
|
-
import {
|
|
4
|
-
import { View } from '../View'
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import { TypeGuards, useCallback } from '@codeleap/common'
|
|
3
|
+
import { SectionList, SectionListProps as RNSectionProps } from 'react-native'
|
|
4
|
+
import { View, ViewProps } from '../View'
|
|
5
|
+
import { RefreshControl } from '../RefreshControl'
|
|
5
6
|
import { useKeyboardPaddingStyle } from '../../utils'
|
|
6
|
-
import {
|
|
7
|
-
import { AnyRecord, IJSX, StyledComponentProps
|
|
7
|
+
import { AugmentedSectionRenderItemInfo, SectionComponentProps, SectionProps, SectionRenderComponentProps } from './types'
|
|
8
|
+
import { AnyRecord, IJSX, StyledComponentProps } from '@codeleap/styles'
|
|
8
9
|
import { MobileStyleRegistry } from '../../Registry'
|
|
9
10
|
import { useStylesFor } from '../../hooks'
|
|
11
|
+
import { EmptyPlaceholder } from '../EmptyPlaceholder'
|
|
10
12
|
|
|
11
13
|
export * from './styles'
|
|
12
14
|
export * from './types'
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
const RenderSeparator = (props: { separatorStyles: ViewProps['style'] }) => {
|
|
17
|
+
return <View style={props.separatorStyles} />
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const Sections = <T extends any>(sectionsProps: SectionProps<T>) => {
|
|
15
21
|
const {
|
|
16
22
|
style,
|
|
17
23
|
onRefresh,
|
|
18
24
|
component,
|
|
19
25
|
refreshing,
|
|
20
26
|
placeholder,
|
|
21
|
-
keyboardAware,
|
|
22
27
|
refreshControlProps,
|
|
28
|
+
loading,
|
|
29
|
+
keyboardAware,
|
|
30
|
+
fakeEmpty = loading,
|
|
23
31
|
contentContainerStyle,
|
|
24
|
-
fakeEmpty,
|
|
25
32
|
refreshControl,
|
|
33
|
+
renderItem: RenderItem,
|
|
34
|
+
sections: data,
|
|
35
|
+
renderSectionHeader: RenderSectionHeader,
|
|
36
|
+
renderSectionFooter: RenderSectionFooter,
|
|
26
37
|
...props
|
|
27
38
|
} = {
|
|
28
39
|
...Sections.defaultProps,
|
|
29
40
|
...sectionsProps,
|
|
30
41
|
}
|
|
31
42
|
|
|
43
|
+
const sections = useMemo(() => {
|
|
44
|
+
return data?.map((section, index) => ({
|
|
45
|
+
...section,
|
|
46
|
+
index,
|
|
47
|
+
}))
|
|
48
|
+
}, [JSON.stringify(data)])
|
|
49
|
+
|
|
32
50
|
const styles = useStylesFor(Sections.styleRegistryName, style)
|
|
33
51
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
52
|
+
const separator = useCallback(() => {
|
|
53
|
+
if (!props?.separators) return null
|
|
54
|
+
return <RenderSeparator separatorStyles={styles.separator} />
|
|
55
|
+
}, [])
|
|
37
56
|
|
|
38
|
-
const
|
|
39
|
-
const listLength =
|
|
57
|
+
const getSectionProps = (data: SectionRenderComponentProps<T>) => {
|
|
58
|
+
const listLength = sections?.length || 0
|
|
40
59
|
|
|
41
|
-
const isFirst =
|
|
42
|
-
const isLast =
|
|
60
|
+
const isFirst = data?.section?.index === sections?.[0]?.index
|
|
61
|
+
const isLast = data?.section?.index === sections?.[listLength - 1]?.index
|
|
43
62
|
const isOnly = isFirst && isLast
|
|
63
|
+
const title = data?.section?.title
|
|
64
|
+
const index = data?.section?.index
|
|
44
65
|
|
|
45
|
-
return { isFirst, isLast, isOnly }
|
|
66
|
+
return { isFirst, isLast, isOnly, title, index }
|
|
46
67
|
}
|
|
47
68
|
|
|
48
|
-
const
|
|
49
|
-
|
|
69
|
+
const renderSectionHeader = useCallback((data: SectionRenderComponentProps<T>) => {
|
|
70
|
+
if (!RenderSectionHeader) return null
|
|
50
71
|
|
|
51
|
-
const
|
|
52
|
-
const isLast = data.section.key === props.sections[listLength - 1].key
|
|
53
|
-
const isOnly = isFirst && isLast
|
|
72
|
+
const positionProps = getSectionProps(data)
|
|
54
73
|
|
|
55
|
-
return {
|
|
56
|
-
}
|
|
74
|
+
return <RenderSectionHeader {...data.section} {...positionProps} />
|
|
75
|
+
}, [RenderSectionHeader, sections?.length])
|
|
57
76
|
|
|
58
|
-
const
|
|
59
|
-
if (!
|
|
77
|
+
const renderSectionFooter = useCallback((data: SectionRenderComponentProps<T>) => {
|
|
78
|
+
if (!RenderSectionFooter) return null
|
|
60
79
|
|
|
61
|
-
|
|
62
|
-
}, [props?.renderSectionHeader, props?.sections?.length])
|
|
80
|
+
const positionProps = getSectionProps(data)
|
|
63
81
|
|
|
64
|
-
|
|
65
|
-
|
|
82
|
+
return <RenderSectionFooter {...data.section} {...positionProps} />
|
|
83
|
+
}, [RenderSectionFooter, sections?.length])
|
|
66
84
|
|
|
67
|
-
|
|
68
|
-
|
|
85
|
+
const renderItem = useCallback((data: AugmentedSectionRenderItemInfo<T>) => {
|
|
86
|
+
if (!RenderItem) return null
|
|
69
87
|
|
|
70
|
-
|
|
71
|
-
if (!props?.renderItem) return null
|
|
88
|
+
const listLength = data?.section?.data?.length || 0
|
|
72
89
|
|
|
73
|
-
|
|
90
|
+
const isFirst = data?.index === 0
|
|
91
|
+
const isLast = data?.index === listLength - 1
|
|
92
|
+
const isOnly = isFirst && isLast
|
|
74
93
|
|
|
75
|
-
|
|
94
|
+
return (
|
|
95
|
+
<RenderItem
|
|
96
|
+
{...data}
|
|
97
|
+
isFirst={isFirst}
|
|
98
|
+
isLast={isLast}
|
|
99
|
+
isOnly={isOnly}
|
|
100
|
+
/>
|
|
101
|
+
)
|
|
102
|
+
}, [RenderItem])
|
|
103
|
+
|
|
104
|
+
const isEmpty = !sections || !sections?.length
|
|
105
|
+
|
|
106
|
+
const _placeholder = {
|
|
107
|
+
...placeholder,
|
|
108
|
+
loading: TypeGuards.isBoolean(placeholder?.loading) ? placeholder.loading : loading,
|
|
109
|
+
}
|
|
76
110
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
111
|
+
const keyboardStyle = useKeyboardPaddingStyle([
|
|
112
|
+
styles.content,
|
|
113
|
+
contentContainerStyle,
|
|
114
|
+
isEmpty && styles['content:empty'],
|
|
115
|
+
loading && styles['content:loading'],
|
|
116
|
+
], keyboardAware && !props.horizontal)
|
|
80
117
|
|
|
81
|
-
const
|
|
118
|
+
const wrapperStyle = [styles.wrapper, isEmpty && styles['wrapper:empty'], loading && styles['wrapper:loading']]
|
|
82
119
|
|
|
83
120
|
return (
|
|
84
121
|
<SectionList
|
|
85
|
-
contentContainerStyle={keyboardStyle}
|
|
86
|
-
showsVerticalScrollIndicator={false}
|
|
87
|
-
// @ts-ignore
|
|
88
|
-
ref={ref}
|
|
89
122
|
ItemSeparatorComponent={separator}
|
|
123
|
+
refreshControl={!!onRefresh && (
|
|
124
|
+
<RefreshControl
|
|
125
|
+
refreshing={refreshing}
|
|
126
|
+
onRefresh={onRefresh}
|
|
127
|
+
{...refreshControlProps}
|
|
128
|
+
/>
|
|
129
|
+
)}
|
|
130
|
+
ListEmptyComponent={<EmptyPlaceholder {..._placeholder} />}
|
|
131
|
+
showsVerticalScrollIndicator={false}
|
|
132
|
+
showsHorizontalScrollIndicator={false}
|
|
90
133
|
{...props}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
)
|
|
96
|
-
}
|
|
134
|
+
ListHeaderComponentStyle={styles.header}
|
|
135
|
+
style={wrapperStyle}
|
|
136
|
+
contentContainerStyle={keyboardStyle}
|
|
137
|
+
sections={sections}
|
|
97
138
|
renderItem={renderItem}
|
|
98
|
-
renderSectionHeader={renderSectionHeader}
|
|
99
|
-
renderSectionFooter={renderSectionFooter}
|
|
139
|
+
renderSectionHeader={renderSectionHeader as unknown as RNSectionProps<T>['renderSectionHeader']}
|
|
140
|
+
renderSectionFooter={renderSectionFooter as unknown as RNSectionProps<T>['renderSectionHeader']}
|
|
100
141
|
/>
|
|
101
142
|
)
|
|
102
|
-
}
|
|
103
|
-
) as StyledComponentWithProps<SectionListProps>
|
|
143
|
+
}
|
|
104
144
|
|
|
105
145
|
Sections.styleRegistryName = 'Sections'
|
|
106
|
-
Sections.elements = ['wrapper', 'content', 'separator']
|
|
146
|
+
Sections.elements = ['wrapper', 'content', 'separator', 'header', 'refreshControl']
|
|
107
147
|
Sections.rootElement = 'wrapper'
|
|
108
148
|
|
|
109
149
|
Sections.withVariantTypes = <S extends AnyRecord>(styles: S) => {
|
|
110
|
-
return Sections as (props: StyledComponentProps<
|
|
150
|
+
return Sections as <T>(props: StyledComponentProps<SectionProps<T>, typeof styles>) => IJSX
|
|
111
151
|
}
|
|
112
152
|
|
|
113
153
|
Sections.defaultProps = {
|
|
114
154
|
keyboardShouldPersistTaps: 'handled',
|
|
155
|
+
fakeEmpty: false,
|
|
156
|
+
loading: false,
|
|
115
157
|
keyboardAware: true,
|
|
116
|
-
} as Partial<
|
|
158
|
+
} as Partial<SectionProps>
|
|
117
159
|
|
|
118
160
|
MobileStyleRegistry.registerComponent(Sections)
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
+
import { ScrollComposition } from '../Scroll/styles'
|
|
1
2
|
|
|
2
|
-
|
|
3
|
+
type SectionsStates = 'empty' | 'loading'
|
|
4
|
+
|
|
5
|
+
type SectionsParts = ScrollComposition | 'separator' | 'header' | 'refreshControl'
|
|
6
|
+
|
|
7
|
+
export type SectionsComposition = `${SectionsParts}:${SectionsStates}` | SectionsParts
|
|
@@ -1,39 +1,56 @@
|
|
|
1
1
|
import { StyledProp } from '@codeleap/styles'
|
|
2
|
-
import { SectionListRenderItemInfo } from 'react-native'
|
|
3
2
|
import { SectionsComposition } from './styles'
|
|
4
|
-
import { SectionListProps as RNSectionListProps } from 'react-native'
|
|
3
|
+
import { SectionListRenderItemInfo, SectionListProps as RNSectionListProps, SectionListData } from 'react-native'
|
|
5
4
|
import { ViewProps } from '../View'
|
|
6
5
|
import { EmptyPlaceholderProps } from '../EmptyPlaceholder'
|
|
7
6
|
import { RefreshControlProps } from '../RefreshControl'
|
|
8
7
|
|
|
9
|
-
export type DataboundSectionListPropsTypes = '
|
|
8
|
+
export type DataboundSectionListPropsTypes = 'sections' | 'renderItem' | 'keyExtractor' | 'style' | 'renderSectionFooter' | 'renderSectionHeader'
|
|
10
9
|
|
|
11
|
-
export type
|
|
10
|
+
export type SectionInfo = {
|
|
12
11
|
isFirst: boolean
|
|
13
12
|
isLast: boolean
|
|
14
13
|
isOnly: boolean
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
export type
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
export type AugmentedSectionRenderItemInfo<T> = SectionListRenderItemInfo<T> & SectionInfo
|
|
17
|
+
|
|
18
|
+
export type SectionComponentProps<T> = SectionInfo & {
|
|
19
|
+
title: string
|
|
20
|
+
index: number
|
|
21
|
+
data: T[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type SectionRenderComponentProps<T> = {
|
|
25
|
+
section: {
|
|
26
|
+
title: string
|
|
27
|
+
index: number
|
|
28
|
+
data: T[]
|
|
29
|
+
}
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
export type
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
>
|
|
30
|
-
|
|
32
|
+
export type ReplaceSectionListProps<P, T> =
|
|
33
|
+
Omit<P, DataboundSectionListPropsTypes> &
|
|
34
|
+
{
|
|
35
|
+
sections: Array<{ title: string; data: T[] }>
|
|
36
|
+
keyExtractor?: (item: T, index: number) => string
|
|
37
|
+
renderItem: (props: AugmentedSectionRenderItemInfo<T>) => React.ReactElement
|
|
38
|
+
onRefresh?: () => void
|
|
39
|
+
fakeEmpty?: boolean
|
|
40
|
+
loading?: boolean
|
|
41
|
+
renderSectionHeader?: (props: SectionComponentProps<T>) => React.ReactElement
|
|
42
|
+
renderSectionFooter?: (props: SectionComponentProps<T>) => React.ReactElement
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type SectionProps<T = any> =
|
|
46
|
+
ReplaceSectionListProps<RNSectionListProps<T>, T> &
|
|
31
47
|
Omit<ViewProps, 'style'> &
|
|
32
48
|
{
|
|
33
49
|
separators?: boolean
|
|
34
50
|
placeholder?: EmptyPlaceholderProps
|
|
35
51
|
refreshControlProps?: Partial<RefreshControlProps>
|
|
36
52
|
fakeEmpty?: boolean
|
|
53
|
+
loading?: boolean
|
|
37
54
|
keyboardAware?: boolean
|
|
38
55
|
style?: StyledProp<SectionsComposition>
|
|
39
56
|
}
|