@plusscommunities/pluss-feature-builder-app-d 1.0.1-beta.3

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 (118) hide show
  1. package/dist/module/actions/featureBuilderActions.js +106 -0
  2. package/dist/module/actions/featureBuilderActions.js.map +1 -0
  3. package/dist/module/actions/featureBuilderStringsActions.js +106 -0
  4. package/dist/module/actions/featureBuilderStringsActions.js.map +1 -0
  5. package/dist/module/actions/index.js +12 -0
  6. package/dist/module/actions/index.js.map +1 -0
  7. package/dist/module/actions/types.js +7 -0
  8. package/dist/module/actions/types.js.map +1 -0
  9. package/dist/module/components/FeatureDetailScreen.js +725 -0
  10. package/dist/module/components/FeatureDetailScreen.js.map +1 -0
  11. package/dist/module/components/FeatureListItem.js +174 -0
  12. package/dist/module/components/FeatureListItem.js.map +1 -0
  13. package/dist/module/components/FeatureListScreen.js +159 -0
  14. package/dist/module/components/FeatureListScreen.js.map +1 -0
  15. package/dist/module/components/FieldRenderer.js +218 -0
  16. package/dist/module/components/FieldRenderer.js.map +1 -0
  17. package/dist/module/components/FileDownload.js +74 -0
  18. package/dist/module/components/FileDownload.js.map +1 -0
  19. package/dist/module/components/WidgetGrid.js +158 -0
  20. package/dist/module/components/WidgetGrid.js.map +1 -0
  21. package/dist/module/components/WidgetLarge.js +274 -0
  22. package/dist/module/components/WidgetLarge.js.map +1 -0
  23. package/dist/module/components/WidgetSmall.js +315 -0
  24. package/dist/module/components/WidgetSmall.js.map +1 -0
  25. package/dist/module/components/common/index.js +25 -0
  26. package/dist/module/components/common/index.js.map +1 -0
  27. package/dist/module/components/layouts/CondensedList.js +195 -0
  28. package/dist/module/components/layouts/CondensedList.js.map +1 -0
  29. package/dist/module/components/layouts/FeatureImageList.js +172 -0
  30. package/dist/module/components/layouts/FeatureImageList.js.map +1 -0
  31. package/dist/module/components/layouts/RoundImageList.js +198 -0
  32. package/dist/module/components/layouts/RoundImageList.js.map +1 -0
  33. package/dist/module/components/layouts/SquareImageList.js +185 -0
  34. package/dist/module/components/layouts/SquareImageList.js.map +1 -0
  35. package/dist/module/config/index.js +10 -0
  36. package/dist/module/config/index.js.map +1 -0
  37. package/dist/module/core.config.js +17 -0
  38. package/dist/module/core.config.js.map +1 -0
  39. package/dist/module/feature.config.js +113 -0
  40. package/dist/module/feature.config.js.map +1 -0
  41. package/dist/module/index.js +24 -0
  42. package/dist/module/index.js.map +1 -0
  43. package/dist/module/js/Colors.js +25 -0
  44. package/dist/module/js/Colors.js.map +1 -0
  45. package/dist/module/js/FieldTypes.js +123 -0
  46. package/dist/module/js/FieldTypes.js.map +1 -0
  47. package/dist/module/js/NavigationService.js +10 -0
  48. package/dist/module/js/NavigationService.js.map +1 -0
  49. package/dist/module/js/Styles.js +3 -0
  50. package/dist/module/js/Styles.js.map +1 -0
  51. package/dist/module/js/helpers.js +29 -0
  52. package/dist/module/js/helpers.js.map +1 -0
  53. package/dist/module/js/index.js +24 -0
  54. package/dist/module/js/index.js.map +1 -0
  55. package/dist/module/js/spacing.js +29 -0
  56. package/dist/module/js/spacing.js.map +1 -0
  57. package/dist/module/js/types.js +254 -0
  58. package/dist/module/js/types.js.map +1 -0
  59. package/dist/module/reducers/featureBuilderReducer.js +75 -0
  60. package/dist/module/reducers/featureBuilderReducer.js.map +1 -0
  61. package/dist/module/utils/featureSelectors.js +9 -0
  62. package/dist/module/utils/featureSelectors.js.map +1 -0
  63. package/dist/module/values.config.a.js +96 -0
  64. package/dist/module/values.config.a.js.map +1 -0
  65. package/dist/module/values.config.b.js +96 -0
  66. package/dist/module/values.config.b.js.map +1 -0
  67. package/dist/module/values.config.c.js +96 -0
  68. package/dist/module/values.config.c.js.map +1 -0
  69. package/dist/module/values.config.d.js +96 -0
  70. package/dist/module/values.config.d.js.map +1 -0
  71. package/dist/module/values.config.js +96 -0
  72. package/dist/module/values.config.js.map +1 -0
  73. package/dist/module/webapi/featureBuilderAPI.js +59 -0
  74. package/dist/module/webapi/featureBuilderAPI.js.map +1 -0
  75. package/dist/module/webapi/helper.js +4 -0
  76. package/dist/module/webapi/helper.js.map +1 -0
  77. package/dist/module/webapi/index.js +8 -0
  78. package/dist/module/webapi/index.js.map +1 -0
  79. package/package.json +62 -0
  80. package/src/actions/featureBuilderActions.js +112 -0
  81. package/src/actions/featureBuilderStringsActions.js +114 -0
  82. package/src/actions/index.js +12 -0
  83. package/src/actions/types.js +7 -0
  84. package/src/components/FeatureDetailScreen.js +817 -0
  85. package/src/components/FeatureListItem.js +198 -0
  86. package/src/components/FeatureListScreen.js +160 -0
  87. package/src/components/FieldRenderer.js +272 -0
  88. package/src/components/FileDownload.js +79 -0
  89. package/src/components/WidgetGrid.js +181 -0
  90. package/src/components/WidgetLarge.js +305 -0
  91. package/src/components/WidgetSmall.js +344 -0
  92. package/src/components/common/index.js +25 -0
  93. package/src/components/layouts/CondensedList.js +230 -0
  94. package/src/components/layouts/FeatureImageList.js +193 -0
  95. package/src/components/layouts/RoundImageList.js +219 -0
  96. package/src/components/layouts/SquareImageList.js +205 -0
  97. package/src/config/index.js +10 -0
  98. package/src/core.config.js +29 -0
  99. package/src/feature.config.js +127 -0
  100. package/src/index.js +27 -0
  101. package/src/js/Colors.js +30 -0
  102. package/src/js/FieldTypes.js +131 -0
  103. package/src/js/NavigationService.js +12 -0
  104. package/src/js/Styles.js +3 -0
  105. package/src/js/helpers.js +30 -0
  106. package/src/js/index.js +24 -0
  107. package/src/js/spacing.js +30 -0
  108. package/src/js/types.js +253 -0
  109. package/src/reducers/featureBuilderReducer.js +64 -0
  110. package/src/utils/featureSelectors.js +8 -0
  111. package/src/values.config.a.js +104 -0
  112. package/src/values.config.b.js +104 -0
  113. package/src/values.config.c.js +104 -0
  114. package/src/values.config.d.js +104 -0
  115. package/src/values.config.js +104 -0
  116. package/src/webapi/featureBuilderAPI.js +65 -0
  117. package/src/webapi/helper.js +4 -0
  118. package/src/webapi/index.js +8 -0
@@ -0,0 +1,344 @@
1
+ import React, { Component } from "react";
2
+ import {
3
+ Text,
4
+ View,
5
+ ScrollView,
6
+ StyleSheet,
7
+ TouchableOpacity,
8
+ Image,
9
+ } from "react-native";
10
+ import { connect } from "react-redux";
11
+ import _ from "lodash";
12
+ import { Icon } from "react-native-elements";
13
+ import { Services } from "../feature.config";
14
+ import { Components, Colours } from "../core.config";
15
+ import { values } from "../values.config";
16
+ import { selectListings, selectFeatureDefinition } from "../reducers/featureBuilderReducer";
17
+ import { SPACING } from "../js/spacing";
18
+ import { getMainBrandingColourFromState } from "../js";
19
+ import { getSummaryFieldValue } from "../js/helpers";
20
+
21
+ class WidgetSmall extends Component {
22
+ constructor(props) {
23
+ super(props);
24
+ this.state = { loading: false };
25
+ }
26
+
27
+ componentDidMount() {
28
+ this.refresh();
29
+ }
30
+
31
+ componentDidUpdate(prevProps) {
32
+ if (!prevProps.dataUpdated && this.props.dataUpdated) {
33
+ this.refresh();
34
+ }
35
+ }
36
+
37
+ getTitle = () => {
38
+ const { options, featureDefinition } = this.props;
39
+ if (options && !_.isEmpty(options.Title)) return options.Title;
40
+ // Use feature definition title
41
+ if (
42
+ featureDefinition?.title ||
43
+ featureDefinition?.displayName
44
+ ) {
45
+ return (
46
+ featureDefinition.title || featureDefinition.displayName
47
+ );
48
+ }
49
+ return values.featureName || "Features";
50
+ };
51
+
52
+ getEmptyStateText = () => {
53
+ const { options } = this.props;
54
+ if (options && !_.isEmpty(options.EmptyText)) return options.EmptyText;
55
+ return values.labels.emptyState;
56
+ };
57
+
58
+ refresh = () => {
59
+ this.onLoadingChanged(true, async () => {
60
+ try {
61
+ // Import actions dynamically to avoid circular dependencies
62
+ const { initializeFeatureBuilder } = await import(
63
+ "../actions/featureBuilderActions"
64
+ );
65
+ await initializeFeatureBuilder()(
66
+ this.props.dispatch,
67
+ () => this.props.getState,
68
+ );
69
+ } finally {
70
+ this.onLoadingChanged(false);
71
+ }
72
+ });
73
+ };
74
+
75
+ onLoadingChanged = (loading, callback) => {
76
+ this.setState({ loading }, () => {
77
+ if (this.props.onLoadingChanged)
78
+ this.props.onLoadingChanged(this.state.loading);
79
+ if (callback) callback();
80
+ });
81
+ };
82
+
83
+ onPressAll = () => {
84
+ Services.navigation.navigate(values.screens.featureList, {
85
+ options: this.props.options,
86
+ });
87
+ };
88
+
89
+ getImageSource = (imageField) => {
90
+ if (!imageField) return null;
91
+
92
+ if (typeof imageField === "string") {
93
+ return { uri: imageField };
94
+ }
95
+
96
+ if (typeof imageField === "object" && imageField.uri) {
97
+ return imageField;
98
+ }
99
+
100
+ if (typeof imageField === "object" && imageField.url) {
101
+ return { uri: imageField.url };
102
+ }
103
+
104
+ return null;
105
+ };
106
+
107
+ onListingPress = (listing) => {
108
+ const { featureDefinition } = this.props;
109
+ const title = this.getTitle();
110
+ Services.navigation.navigate(values.screens.featureDetail, {
111
+ listing: listing,
112
+ featureDefinition: featureDefinition,
113
+ featureTitle: title,
114
+ });
115
+ };
116
+
117
+ renderListingCard = (listing) => {
118
+ const { colourBrandingMain, featureDefinition } = this.props;
119
+ const title = listing.fields?.[values.mandatoryFields.title] || "Untitled";
120
+ const summary = getSummaryFieldValue(listing, featureDefinition);
121
+ const imageField = listing.fields?.[values.mandatoryFields.featureImage];
122
+ const imageSource = this.getImageSource(imageField);
123
+
124
+ return (
125
+ <TouchableOpacity
126
+ key={listing.id}
127
+ style={styles.cardContainer}
128
+ onPress={() => this.onListingPress(listing)}
129
+ >
130
+ <View style={styles.borderContainer}>
131
+ {imageSource ? (
132
+ <Image
133
+ source={imageSource}
134
+ style={styles.cardImage}
135
+ resizeMode="cover"
136
+ />
137
+ ) : (
138
+ <View style={[styles.cardImage, styles.placeholderImage]}>
139
+ <Icon
140
+ name="image"
141
+ type="font-awesome"
142
+ size={24}
143
+ color={Colours.TEXT_LIGHT}
144
+ />
145
+ </View>
146
+ )}
147
+ <Text style={styles.cardTitle} numberOfLines={1}>
148
+ {title}
149
+ </Text>
150
+ {summary && (
151
+ <Text style={styles.cardSummary} numberOfLines={1}>
152
+ {summary}
153
+ </Text>
154
+ )}
155
+ </View>
156
+ </TouchableOpacity>
157
+ );
158
+ };
159
+
160
+ renderContent() {
161
+ const { listings } = this.props;
162
+
163
+ // Don't render widget if no listings are available (and not loading)
164
+ if (_.isEmpty(listings) && !this.state.loading) {
165
+ return null;
166
+ }
167
+
168
+ if (_.isEmpty(listings)) {
169
+ if (this.state.loading) {
170
+ return (
171
+ <View style={styles.loadingPadding}>
172
+ <Components.LoadingStateWidget height={180} />
173
+ </View>
174
+ );
175
+ }
176
+ return null;
177
+ }
178
+
179
+ // Show first 3 listings with peeking indicator
180
+ const listingsToShow = listings.slice(0, values.widget.maxItems);
181
+
182
+ return (
183
+ <ScrollView
184
+ horizontal
185
+ contentContainerStyle={{
186
+ paddingLeft: SPACING.XS + 2,
187
+ paddingRight: SPACING.SM,
188
+ }}
189
+ showsHorizontalScrollIndicator={false}
190
+ >
191
+ {listingsToShow.map((listing) => this.renderListingCard(listing))}
192
+ {listings.length > values.widget.maxItems && (
193
+ <TouchableOpacity
194
+ style={[styles.cardContainer, styles.viewMoreCard]}
195
+ onPress={this.onPressAll}
196
+ >
197
+ <View style={[styles.borderContainer, styles.viewMoreContainer]}>
198
+ <Text style={styles.viewMoreText}>View all</Text>
199
+ <Text style={styles.viewMoreCount}>
200
+ {listings.length} items
201
+ </Text>
202
+ </View>
203
+ </TouchableOpacity>
204
+ )}
205
+ </ScrollView>
206
+ );
207
+ }
208
+
209
+ render() {
210
+ const { colourBrandingMain } = this.props;
211
+
212
+ const content = this.renderContent();
213
+ // Don't render widget section if content is null (no features available)
214
+ if (content === null) {
215
+ return null;
216
+ }
217
+
218
+ return (
219
+ <View style={styles.sectionContainer}>
220
+ <View style={styles.sectionPadding}>
221
+ <View style={styles.sectionHeading}>
222
+ <Text style={styles.sectionTitle}>{this.getTitle()}</Text>
223
+ <Components.InlineButton
224
+ onPress={this.onPressAll}
225
+ color={colourBrandingMain}
226
+ touchableStyle={{ paddingTop: SPACING.XS + 2 }}
227
+ textStyle={{ color: "#fff" }}
228
+ >
229
+ {values.labels.viewAll}
230
+ </Components.InlineButton>
231
+ </View>
232
+ </View>
233
+ {content}
234
+ </View>
235
+ );
236
+ }
237
+ }
238
+
239
+ const styles = StyleSheet.create({
240
+ sectionContainer: {
241
+ backgroundColor: "#fff",
242
+ paddingTop: SPACING.MD,
243
+ },
244
+ sectionPadding: {
245
+ paddingHorizontal: SPACING.MD,
246
+ paddingBottom: SPACING.XS + 2,
247
+ },
248
+ loadingPadding: {
249
+ paddingHorizontal: SPACING.MD,
250
+ },
251
+ sectionHeading: {
252
+ marginBottom: SPACING.XS,
253
+ flexDirection: "row",
254
+ alignContent: "flex-start",
255
+ justifyContent: "space-between",
256
+ },
257
+ sectionTitle: {
258
+ fontFamily: "sf-bold",
259
+ fontSize: 24,
260
+ color: "#000",
261
+ },
262
+ // Listing card styles
263
+ cardContainer: {
264
+ marginRight: SPACING.SM + 4,
265
+ width: values.widget.itemMaxWidth,
266
+ },
267
+ borderContainer: {
268
+ borderRadius: SPACING.SM,
269
+ backgroundColor: "#fff",
270
+ shadowColor: "#000",
271
+ shadowOffset: {
272
+ width: 0,
273
+ height: 2,
274
+ },
275
+ shadowOpacity: 0.1,
276
+ shadowRadius: 3.84,
277
+ elevation: 4,
278
+ padding: SPACING.SM,
279
+ minHeight: values.widget.itemMinWidth,
280
+ },
281
+ cardImage: {
282
+ width: 88,
283
+ height: 88,
284
+ borderRadius: 44,
285
+ backgroundColor: "#f0f0f0",
286
+ alignSelf: "center",
287
+ marginBottom: SPACING.XS,
288
+ },
289
+ placeholderImage: {
290
+ justifyContent: "center",
291
+ alignItems: "center",
292
+ backgroundColor: "#f0f0f0",
293
+ },
294
+ cardTitle: {
295
+ fontFamily: "sf-semibold",
296
+ fontSize: 14,
297
+ color: "#000",
298
+ textAlign: "center",
299
+ marginBottom: 4,
300
+ },
301
+ cardSummary: {
302
+ fontFamily: "sf-regular",
303
+ fontSize: 12,
304
+ color: "#666",
305
+ textAlign: "center",
306
+ },
307
+ // View more card
308
+ viewMoreCard: {
309
+ justifyContent: "center",
310
+ },
311
+ viewMoreContainer: {
312
+ justifyContent: "center",
313
+ alignItems: "center",
314
+ minHeight: values.widget.itemMinWidth,
315
+ },
316
+ viewMoreText: {
317
+ fontFamily: "sf-semibold",
318
+ fontSize: 14,
319
+ color: Colours.TEXT_DARK || "#000",
320
+ },
321
+ viewMoreCount: {
322
+ fontFamily: "sf-regular",
323
+ fontSize: 12,
324
+ color: "#666",
325
+ marginTop: 2,
326
+ },
327
+ });
328
+
329
+ const mapStateToProps = (state) => {
330
+ const { user, notifications } = state;
331
+
332
+ return {
333
+ colourBrandingMain: getMainBrandingColourFromState(state),
334
+ listings: selectListings(state),
335
+ featureDefinition: selectFeatureDefinition(state),
336
+ site: user.site,
337
+ dataUpdated: notifications.dataUpdated[values.notificationKey],
338
+ strings: state.strings?.config || {},
339
+ };
340
+ };
341
+
342
+ export default connect(mapStateToProps, null, null, { forwardRef: true })(
343
+ WidgetSmall,
344
+ );
@@ -0,0 +1,25 @@
1
+ import { Components } from "../../core.config";
2
+
3
+ export const InlineButton = Components.InlineButton;
4
+ export const ProfilePic = Components.ProfilePic;
5
+ export const EmptyStateMain = Components.EmptyStateMain;
6
+ export const Spinner = Components.Spinner;
7
+ export const MiddlePopup = Components.MiddlePopup;
8
+ export const BackButton = Components.BackButton;
9
+ export const UserListing = Components.UserListing;
10
+ export const GenericInput = Components.GenericInput;
11
+ export const Toggle = Components.Toggle;
12
+ export const ImageUploader = Components.ImageUploader;
13
+ export const Header = Components.Header;
14
+ export const FormCard = Components.FormCard;
15
+ export const FormCardSection = Components.FormCardSection;
16
+ export const Input = Components.Input;
17
+ export const StickyFooter = Components.StickyFooter;
18
+ export const LoadingIndicator = Components.LoadingIndicator;
19
+ export const ConfirmPopup = Components.ConfirmPopup;
20
+ export const DropDownMenu = Components.DropDownMenu;
21
+ export const DropDownItem = Components.DropDownItem;
22
+ export const PlussChat = Components.PlussChat;
23
+ export const Attachment = Components.Attachment;
24
+
25
+ // TODO: Add core components as required
@@ -0,0 +1,230 @@
1
+ import React from "react";
2
+ import {
3
+ View,
4
+ StyleSheet,
5
+ FlatList,
6
+ Text,
7
+ TouchableOpacity,
8
+ Image,
9
+ } from "react-native";
10
+ import { Icon } from "react-native-elements";
11
+ import { Components, Colours, Helper } from "../../core.config";
12
+ import { values } from "../../values.config";
13
+ import { getSummaryFieldValue } from "../../js/helpers";
14
+
15
+ const CondensedListItem = ({
16
+ listing,
17
+ onItemPress,
18
+ colourBrandingMain,
19
+ featureDefinition,
20
+ }) => {
21
+ const getImageSource = (imageField) => {
22
+ if (!imageField) return null;
23
+
24
+ if (typeof imageField === "string") {
25
+ return { uri: imageField };
26
+ }
27
+
28
+ if (typeof imageField === "object" && imageField.uri) {
29
+ return imageField;
30
+ }
31
+
32
+ if (typeof imageField === "object" && imageField.url) {
33
+ return { uri: imageField.url };
34
+ }
35
+
36
+ return null;
37
+ };
38
+
39
+ const onPress = () => {
40
+ if (onItemPress) {
41
+ onItemPress(listing);
42
+ }
43
+ };
44
+
45
+ // Get data from feature builder fields
46
+ const title = listing.fields?.[values.mandatoryFields.title] || "Untitled";
47
+ const summary = getSummaryFieldValue(listing, featureDefinition);
48
+ const imageField = listing.fields?.[values.mandatoryFields.featureImage];
49
+ const imageSource = getImageSource(imageField);
50
+
51
+ return (
52
+ <TouchableOpacity activeOpacity={0.9} onPress={onPress}>
53
+ <View style={styles.listItem}>
54
+ {/* Small circular avatar image */}
55
+ <View style={styles.avatarContainer}>
56
+ {imageSource ? (
57
+ <Image
58
+ source={imageSource}
59
+ style={styles.avatar}
60
+ resizeMode="cover"
61
+ />
62
+ ) : (
63
+ <View style={[styles.avatar, styles.placeholderAvatar]}>
64
+ <Icon
65
+ name="image"
66
+ type="font-awesome"
67
+ size={16}
68
+ color={Colours.TEXT_LIGHT}
69
+ />
70
+ </View>
71
+ )}
72
+ </View>
73
+
74
+ {/* Text content */}
75
+ <View style={styles.textContainer}>
76
+ <Text style={styles.titleText} numberOfLines={1}>
77
+ {title}
78
+ </Text>
79
+ {summary && (
80
+ <Text style={styles.descriptionText} numberOfLines={1}>
81
+ {summary}
82
+ </Text>
83
+ )}
84
+ </View>
85
+
86
+ {/* Chevron icon */}
87
+ <View style={styles.chevronContainer}>
88
+ <Icon
89
+ name="chevron-right"
90
+ type="font-awesome"
91
+ size={14}
92
+ color={Colours.TEXT_LIGHT}
93
+ />
94
+ </View>
95
+ </View>
96
+
97
+ {/* Divider line */}
98
+ <View style={styles.divider} />
99
+ </TouchableOpacity>
100
+ );
101
+ };
102
+
103
+ const getEmptyStateMessage = (title) => {
104
+ const featureName = title || "Feature";
105
+ return `${featureName} listings will appear here once they're available.\nCome back soon to see what's new!`;
106
+ };
107
+
108
+ const CondensedList = ({
109
+ listings,
110
+ onItemPress,
111
+ colourBrandingMain,
112
+ refreshing,
113
+ onRefresh,
114
+ featureDefinition,
115
+ title,
116
+ }) => {
117
+ const renderListingItem = ({ item: listing }) => {
118
+ // Get feature definition for this listing
119
+ const listingFeatureDefinition = featureDefinition;
120
+
121
+ return (
122
+ <CondensedListItem
123
+ listing={listing}
124
+ onItemPress={onItemPress}
125
+ colourBrandingMain={colourBrandingMain}
126
+ featureDefinition={listingFeatureDefinition}
127
+ />
128
+ );
129
+ };
130
+
131
+ if (!listings || listings.length === 0) {
132
+ return (
133
+ <View style={styles.emptyContainer}>
134
+ <Components.EmptyStateWidget
135
+ title={getEmptyStateMessage(title)}
136
+ height={120}
137
+ />
138
+ </View>
139
+ );
140
+ }
141
+
142
+ if (onRefresh) {
143
+ return (
144
+ <View style={styles.container}>
145
+ <Components.LoadingIndicator visible={refreshing} />
146
+ <FlatList
147
+ data={listings}
148
+ keyExtractor={(item) => item.id}
149
+ renderItem={renderListingItem}
150
+ style={styles.scrollContainer}
151
+ showsVerticalScrollIndicator={false}
152
+ />
153
+ </View>
154
+ );
155
+ }
156
+
157
+ return (
158
+ <View style={styles.container}>
159
+ <FlatList
160
+ data={listings}
161
+ keyExtractor={(item) => item.id}
162
+ renderItem={renderListingItem}
163
+ showsVerticalScrollIndicator={false}
164
+ />
165
+ </View>
166
+ );
167
+ };
168
+
169
+ const styles = StyleSheet.create({
170
+ container: {
171
+ flex: 1,
172
+ backgroundColor: Colours.BACKGROUND_WHITE || "#fff",
173
+ },
174
+ scrollContainer: {
175
+ flex: 1,
176
+ },
177
+ emptyContainer: {
178
+ paddingVertical: 16,
179
+ },
180
+ listItem: {
181
+ flexDirection: "row",
182
+ alignItems: "center",
183
+ paddingHorizontal: 16,
184
+ paddingVertical: 8,
185
+ backgroundColor: Colours.BACKGROUND_WHITE || "#fff",
186
+ },
187
+ avatarContainer: {
188
+ width: 40,
189
+ height: 40,
190
+ borderRadius: 20,
191
+ marginRight: 12,
192
+ },
193
+ avatar: {
194
+ width: 40,
195
+ height: 40,
196
+ borderRadius: 20,
197
+ backgroundColor: Colours.BACKGROUND_LIGHT,
198
+ },
199
+ placeholderAvatar: {
200
+ backgroundColor: Colours.BACKGROUND_LIGHT,
201
+ justifyContent: "center",
202
+ alignItems: "center",
203
+ },
204
+ textContainer: {
205
+ flex: 1,
206
+ justifyContent: "center",
207
+ },
208
+ titleText: {
209
+ fontFamily: "sf-semibold",
210
+ fontSize: 16,
211
+ color: Colours.TEXT_DARK,
212
+ marginBottom: 2,
213
+ },
214
+ descriptionText: {
215
+ fontFamily: "sf-regular",
216
+ fontSize: 14,
217
+ color: Colours.TEXT_LIGHT,
218
+ lineHeight: 18,
219
+ },
220
+ chevronContainer: {
221
+ paddingLeft: 8,
222
+ },
223
+ divider: {
224
+ height: 1,
225
+ backgroundColor: Colours.LINE_GREY || "#e0e0e0",
226
+ marginLeft: 0, // Full width border
227
+ },
228
+ });
229
+
230
+ export default CondensedList;