clwy-react-native-tableview-simple 4.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/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "clwy-react-native-tableview-simple",
3
+ "description": "React Native component for TableView made with pure CSS",
4
+ "homepage": "https://github.com/clwy-cn/clwy-react-native-tableview-simple",
5
+ "version": "4.5.0",
6
+ "author": "Patrick Böder <hello@patrickpuritscher.com>",
7
+ "scripts": {
8
+ "clean": "watchman watch-del-all && rm -rf node_modules/ && yarn cache clean && yarn",
9
+ "jest": "jest ./src/",
10
+ "eslint:ci": "eslint index.js ./src/",
11
+ "eslint:fix": "eslint index.js ./src/",
12
+ "test:ci": "yarn run eslint:ci && yarn run jest && yarn tsc --noEmit",
13
+ "tsc": "tsc --skipLibCheck",
14
+ "build:module": "yarn tsc --outDir ./lib/module --sourceMap",
15
+ "build:commonjs": "yarn tsc --module commonjs --outDir ./lib/commonjs --sourceMap",
16
+ "build": "rimraf ./lib && yarn build:module && yarn build:commonjs",
17
+ "release:prepare": "yarn build",
18
+ "release": "standard-version"
19
+ },
20
+ "main": "lib/commonjs/index.js",
21
+ "react-native": "src/index.ts",
22
+ "module": "lib/module/index.js",
23
+ "types": "lib/typescript/index.d.ts",
24
+ "files": [
25
+ "/src",
26
+ "/lib",
27
+ "/README.md",
28
+ "/LICENSE"
29
+ ],
30
+ "jest": {
31
+ "preset": "react-native",
32
+ "testPathIgnorePatterns": [
33
+ "/node_modules/",
34
+ "<rootDir>/example/"
35
+ ],
36
+ "modulePathIgnorePatterns": [
37
+ "<rootDir>/example/"
38
+ ]
39
+ },
40
+ "license": "MIT",
41
+ "keywords": [
42
+ "react-native",
43
+ "react-component",
44
+ "tableview",
45
+ "UITableView",
46
+ "crossplatform",
47
+ "ios",
48
+ "android"
49
+ ],
50
+ "peerDependencies": {
51
+ "react": "*",
52
+ "react-native": ">=0.64",
53
+ "react-native-safe-area-context": "~5.6.0"
54
+ },
55
+ "bugs": {
56
+ "url": "https://github.com/Purii/react-native-tableview-simple/issues"
57
+ },
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "git@github.com:Purii/react-native-tableview-simple.git"
61
+ },
62
+ "devDependencies": {
63
+ "@commitlint/cli": "19.3.0",
64
+ "@commitlint/config-conventional": "19.2.2",
65
+ "@types/jest": "29.5.12",
66
+ "@types/react": "18.3.3",
67
+ "@types/react-native": "0.73.0",
68
+ "@types/react-test-renderer": "18.3.0",
69
+ "@typescript-eslint/eslint-plugin": "7.12.0",
70
+ "@typescript-eslint/parser": "7.12.0",
71
+ "babel-jest": "29.7.0",
72
+ "babel-preset-react-native": "4.0.1",
73
+ "eslint": "8.57.0",
74
+ "eslint-config-prettier": "8.10.0",
75
+ "eslint-plugin-prettier": "5.1.3",
76
+ "eslint-plugin-react": "7.34.2",
77
+ "husky": "9.0.11",
78
+ "jest": "29.7.0",
79
+ "lint-staged": "15.2.5",
80
+ "metro-react-native-babel-preset": "^0.77.0",
81
+ "prettier": "3.3.1",
82
+ "react": "18.3.1",
83
+ "react-native": "0.74.2",
84
+ "react-test-renderer": "18.3.1",
85
+ "rimraf": "5.0.7",
86
+ "standard-version": "9.5.0",
87
+ "typescript": "5.4.5"
88
+ }
89
+ }
@@ -0,0 +1,552 @@
1
+ import React, { useContext } from 'react';
2
+ import {
3
+ PixelRatio,
4
+ Platform,
5
+ StyleSheet,
6
+ Text,
7
+ TouchableHighlight,
8
+ TouchableOpacity,
9
+ View,
10
+ StyleProp,
11
+ ViewStyle,
12
+ TextProps,
13
+ TextStyle,
14
+ ImageProps,
15
+ } from 'react-native';
16
+ import { ThemeContext } from './Theme';
17
+ import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
18
+
19
+ export interface CellInterface {
20
+ accessory?:
21
+ | false
22
+ | 'DisclosureIndicator'
23
+ | 'Detail'
24
+ | 'DetailDisclosure'
25
+ | 'Checkmark';
26
+ accessoryColor?: ViewStyle['borderColor'];
27
+ accessoryColorDisclosureIndicator?: ViewStyle['borderColor'];
28
+ allowFontScaling?: boolean;
29
+ backgroundColor?: ViewStyle['backgroundColor'];
30
+ cellStyle?: 'Basic' | 'RightDetail' | 'LeftDetail' | 'Subtitle';
31
+ cellAccessoryView?: React.ReactNode;
32
+ cellContentView?: React.ReactNode;
33
+ cellImageView?: React.ReactNode;
34
+ children?: React.ReactNode;
35
+ contentContainerStyle?: StyleProp<ViewStyle>;
36
+ detail?: string | number;
37
+ detailTextStyle?: StyleProp<TextStyle>;
38
+ detailTextProps?: TextProps;
39
+ disableImageResize?: boolean;
40
+ hideSeparator?: boolean;
41
+ highlightActiveOpacity?: number;
42
+ highlightUnderlayColor?: ViewStyle['backgroundColor'];
43
+ image?: React.ReactElement;
44
+ isDisabled?: boolean;
45
+ onPress?: () => void | false;
46
+ onLongPress?: () => void | false;
47
+ onPressDetailAccessory?: () => void | false;
48
+
49
+ onUnHighlightRow?(): void;
50
+
51
+ onHighlightRow?(): void;
52
+
53
+ leftDetailColor?: TextStyle['color'];
54
+ rightDetailColor?: TextStyle['color'];
55
+ subtitleColor?: TextStyle['color'];
56
+ subtitleTextStyle?: StyleProp<TextStyle>;
57
+ testID?: string;
58
+ title?: React.ReactNode;
59
+ titleTextProps?: TextProps;
60
+ titleTextStyle?: StyleProp<TextStyle>;
61
+ titleTextStyleDisabled?: StyleProp<TextStyle>;
62
+ titleTextColor?: TextStyle['color'];
63
+ withSafeAreaView?: boolean;
64
+ }
65
+
66
+ const Cell: React.FC<CellInterface> = ({
67
+ accessory = false,
68
+ accessoryColor,
69
+ accessoryColorDisclosureIndicator,
70
+ allowFontScaling = true,
71
+ backgroundColor,
72
+ cellStyle = 'Basic',
73
+ cellContentView,
74
+ cellImageView,
75
+ cellAccessoryView,
76
+ children,
77
+ contentContainerStyle = {},
78
+ detail,
79
+ detailTextStyle = {},
80
+ detailTextProps = {},
81
+ disableImageResize = false,
82
+ highlightActiveOpacity = 0.8,
83
+ highlightUnderlayColor,
84
+ image,
85
+ isDisabled = false,
86
+ onPress,
87
+ onLongPress,
88
+ onPressDetailAccessory,
89
+ onHighlightRow,
90
+ onUnHighlightRow,
91
+ leftDetailColor,
92
+ rightDetailColor,
93
+ subtitleColor,
94
+ subtitleTextStyle = {},
95
+ testID,
96
+ title,
97
+ titleTextProps = {},
98
+ titleTextStyle = {},
99
+ titleTextStyleDisabled = {},
100
+ titleTextColor,
101
+ withSafeAreaView = Platform.OS === 'ios'
102
+ ? parseInt(`${Platform.Version}`, 10) <= 10
103
+ ? false
104
+ : true
105
+ : true,
106
+ }: CellInterface) => {
107
+ const theme = useContext(ThemeContext);
108
+
109
+ const isPressable = !!onPress || !!onLongPress;
110
+
111
+ /**
112
+ * Merge styles with props
113
+ * Type is a Hotfix
114
+ */
115
+ const localStyles = {
116
+ ...styles,
117
+ cellBackgroundColor: {
118
+ backgroundColor: backgroundColor || theme.colors.background,
119
+ },
120
+ cell: [styles.cell, contentContainerStyle],
121
+ cellSafeAreaContainer: styles.cellSafeAreaContainer,
122
+ cellTitle: isDisabled
123
+ ? [styles.cellTitle, styles.cellTextDisabled, titleTextStyleDisabled]
124
+ : [
125
+ styles.cellTitle,
126
+ { color: titleTextColor || theme.colors.body },
127
+ titleTextStyle,
128
+ ],
129
+ cellLeftDetail: [
130
+ styles.cellLeftDetail,
131
+ {
132
+ color: leftDetailColor || theme.colors.primary,
133
+ },
134
+ detailTextStyle,
135
+ ],
136
+ cellLeftDetailTitle: isDisabled
137
+ ? [styles.cellLeftDetailTitle, styles.cellTextDisabled]
138
+ : [
139
+ styles.cellLeftDetailTitle,
140
+ { color: titleTextColor || theme.colors.body },
141
+ ],
142
+ cellRightDetail: [
143
+ styles.cellRightDetail,
144
+ {
145
+ color: rightDetailColor || theme.colors.secondary,
146
+ },
147
+ detailTextStyle,
148
+ ],
149
+ cellSubtitle: [
150
+ styles.cellSubtitle,
151
+ {
152
+ color: subtitleColor || theme.colors.body,
153
+ },
154
+ subtitleTextStyle,
155
+ ],
156
+ accessoryCheckmark: [
157
+ styles.accessoryCheckmark,
158
+ { borderColor: accessoryColor || theme.colors.primary },
159
+ ],
160
+ accessoryDetail: [
161
+ styles.accessoryDetail,
162
+ { borderColor: accessoryColor || theme.colors.primary },
163
+ ],
164
+ accessoryDetailText: [
165
+ styles.accessoryDetailText,
166
+ { color: accessoryColor || theme.colors.primary },
167
+ ],
168
+ accessoryDisclosureIndicator: [
169
+ styles.accessoryDisclosureIndicator,
170
+ { borderColor: accessoryColorDisclosureIndicator || theme.colors.muted },
171
+ ],
172
+ };
173
+
174
+ const renderAccessoryDetail = (): React.ReactNode => {
175
+ if (onPressDetailAccessory) {
176
+ return (
177
+ <TouchableOpacity
178
+ style={localStyles.accessoryDetail}
179
+ onPress={onPressDetailAccessory}
180
+ activeOpacity={0.7}
181
+ disabled={isDisabled}>
182
+ <Text style={localStyles.accessoryDetailText}>i</Text>
183
+ </TouchableOpacity>
184
+ );
185
+ }
186
+ return (
187
+ <View style={localStyles.accessoryDetail}>
188
+ <Text style={localStyles.accessoryDetailText}>i</Text>
189
+ </View>
190
+ );
191
+ };
192
+ /**
193
+ * Render accessoryView
194
+ * Currently available
195
+ * @return {View} View with accessory
196
+ */
197
+ const renderAccessoryView = (): React.ReactNode => {
198
+ switch (accessory) {
199
+ case 'DisclosureIndicator':
200
+ return (
201
+ <View style={localStyles.cellAccessoryView}>
202
+ <View style={localStyles.accessoryDisclosureIndicator} />
203
+ </View>
204
+ );
205
+ case 'Detail':
206
+ return (
207
+ <View style={localStyles.cellAccessoryView}>
208
+ {renderAccessoryDetail()}
209
+ </View>
210
+ );
211
+ case 'DetailDisclosure':
212
+ return (
213
+ <View style={localStyles.cellAccessoryView}>
214
+ <View style={localStyles.accessoryDetailDisclosure}>
215
+ {renderAccessoryDetail()}
216
+ <View style={localStyles.accessoryDisclosureIndicator} />
217
+ </View>
218
+ </View>
219
+ );
220
+ case 'Checkmark':
221
+ return (
222
+ <View style={localStyles.cellAccessoryView}>
223
+ <View style={localStyles.accessoryCheckmark} />
224
+ </View>
225
+ );
226
+ default:
227
+ return null;
228
+ }
229
+ };
230
+
231
+ /**
232
+ * Render imageView
233
+ * @return {Image} Image component with updated props
234
+ */
235
+ const renderImageView = (): React.ReactNode => {
236
+ if (!image) {
237
+ return null;
238
+ }
239
+ const imageStyleProp = (image?.props as ImageProps)?.style;
240
+ const propsToAdd = {
241
+ style: disableImageResize
242
+ ? imageStyleProp
243
+ : [imageStyleProp, localStyles.image],
244
+ };
245
+ return (
246
+ <View style={localStyles.cellImageView}>
247
+ {React.cloneElement(image, propsToAdd)}
248
+ </View>
249
+ );
250
+ };
251
+
252
+ /**
253
+ * Render cell of type Basic
254
+ * @return {View} View with Text and Accessory
255
+ */
256
+ const renderCellBasic = (): React.ReactNode => (
257
+ <View style={localStyles.cellContentView}>
258
+ <Text
259
+ allowFontScaling={allowFontScaling}
260
+ numberOfLines={1}
261
+ style={localStyles.cellTitle}
262
+ {...titleTextProps}>
263
+ {title}
264
+ </Text>
265
+ </View>
266
+ );
267
+
268
+ /**
269
+ * Render cell of type RightDetail
270
+ * @return {View} View with Text, Text and Accessory
271
+ */
272
+ const renderCellRightDetail = (): React.ReactNode => (
273
+ <View style={localStyles.cellContentView}>
274
+ <Text
275
+ allowFontScaling={allowFontScaling}
276
+ numberOfLines={1}
277
+ style={localStyles.cellTitle}
278
+ {...titleTextProps}>
279
+ {title}
280
+ </Text>
281
+ <Text
282
+ allowFontScaling={allowFontScaling}
283
+ numberOfLines={1}
284
+ style={
285
+ isDisabled
286
+ ? [localStyles.cellRightDetail, localStyles.cellTextDisabled]
287
+ : localStyles.cellRightDetail
288
+ }
289
+ {...detailTextProps}>
290
+ {detail}
291
+ </Text>
292
+ </View>
293
+ );
294
+
295
+ /**
296
+ * Render cell of type LeftDetail
297
+ * @return {View} View with Text, Text and Accessory
298
+ */
299
+ const renderCellLeftDetail = (): React.ReactNode => (
300
+ <View style={localStyles.cellContentView}>
301
+ <Text
302
+ allowFontScaling={allowFontScaling}
303
+ numberOfLines={1}
304
+ style={
305
+ isDisabled
306
+ ? [localStyles.cellLeftDetail, localStyles.cellTextDisabled]
307
+ : localStyles.cellLeftDetail
308
+ }
309
+ {...detailTextProps}>
310
+ {detail}
311
+ </Text>
312
+ <Text
313
+ allowFontScaling={allowFontScaling}
314
+ numberOfLines={1}
315
+ style={localStyles.cellLeftDetailTitle}
316
+ {...titleTextProps}>
317
+ {title}
318
+ </Text>
319
+ </View>
320
+ );
321
+
322
+ /**
323
+ * Render cell of type Subtitle
324
+ * @return {View} View with View, Text, Text and Accessory
325
+ */
326
+ const renderCellSubtitle = (): React.ReactNode => (
327
+ <View
328
+ style={[
329
+ localStyles.cellContentView,
330
+ localStyles.cellContentViewTypeSubtitle,
331
+ ]}>
332
+ <View style={localStyles.cellInnerSubtitle}>
333
+ <Text
334
+ allowFontScaling={allowFontScaling}
335
+ numberOfLines={1}
336
+ style={localStyles.cellTitle}
337
+ {...titleTextProps}>
338
+ {title}
339
+ </Text>
340
+ <Text
341
+ allowFontScaling={allowFontScaling}
342
+ numberOfLines={1}
343
+ style={
344
+ isDisabled
345
+ ? [localStyles.cellSubtitle, localStyles.cellTextDisabled]
346
+ : localStyles.cellSubtitle
347
+ }
348
+ {...detailTextProps}>
349
+ {detail}
350
+ </Text>
351
+ </View>
352
+ </View>
353
+ );
354
+
355
+ /**
356
+ * Renders correct contentView
357
+ * @return {View} ContentView
358
+ */
359
+ const renderCellContentView = (): React.ReactNode => {
360
+ switch (cellStyle) {
361
+ case 'Basic':
362
+ return renderCellBasic();
363
+ case 'RightDetail':
364
+ return renderCellRightDetail();
365
+ case 'LeftDetail':
366
+ return renderCellLeftDetail();
367
+ case 'Subtitle':
368
+ return renderCellSubtitle();
369
+ default:
370
+ return renderCellBasic();
371
+ }
372
+ };
373
+
374
+ /**
375
+ * Render content of cell
376
+ * @return {View} Complete View with cell elements
377
+ */
378
+ const renderCell = (): React.ReactNode => {
379
+ return (
380
+ <View style={localStyles.cellBackgroundColor}>
381
+ <View style={localStyles.cell}>
382
+ {cellImageView || renderImageView()}
383
+ {cellContentView || renderCellContentView()}
384
+ {cellAccessoryView || renderAccessoryView()}
385
+ </View>
386
+ {children}
387
+ </View>
388
+ );
389
+ };
390
+
391
+ /**
392
+ * Render content of cell with SafeAreaView
393
+ * Inside view to prevent overwriting styles
394
+ * @return {View} Complete View with cell elements
395
+ */
396
+ const renderCellWithSafeAreaView = (): React.ReactNode => (
397
+ <SafeAreaProvider>
398
+ <SafeAreaView
399
+ style={[
400
+ localStyles.cellBackgroundColor,
401
+ localStyles.cellSafeAreaContainer,
402
+ ]}>
403
+ <View style={localStyles.cell}>
404
+ {cellImageView || renderImageView()}
405
+ {cellContentView || renderCellContentView()}
406
+ {cellAccessoryView || renderAccessoryView()}
407
+ </View>
408
+ {children}
409
+ </SafeAreaView>
410
+ </SafeAreaProvider>
411
+ );
412
+
413
+ if (isPressable && !isDisabled) {
414
+ return (
415
+ <TouchableHighlight
416
+ activeOpacity={highlightActiveOpacity}
417
+ onPress={onPress}
418
+ onLongPress={onLongPress}
419
+ underlayColor={highlightUnderlayColor || theme.colors.body}
420
+ onPressIn={onHighlightRow}
421
+ onPressOut={onUnHighlightRow}
422
+ testID={testID}>
423
+ {withSafeAreaView ? renderCellWithSafeAreaView() : renderCell()}
424
+ </TouchableHighlight>
425
+ );
426
+ }
427
+ return (
428
+ <View testID={testID}>
429
+ {withSafeAreaView ? renderCellWithSafeAreaView() : renderCell()}
430
+ </View>
431
+ );
432
+ };
433
+
434
+ const styles = StyleSheet.create({
435
+ cell: {
436
+ alignItems: 'center',
437
+ paddingLeft: 15,
438
+ paddingRight: 20,
439
+ minHeight: PixelRatio.roundToNearestPixel(44),
440
+ flexDirection: 'row',
441
+ },
442
+ // SafeAreaView only adds padding
443
+ cellSafeAreaContainer: {
444
+ flexGrow: 1,
445
+ },
446
+ cellContentView: {
447
+ alignItems: 'center',
448
+ flexDirection: 'row',
449
+ flexGrow: 1,
450
+ flexBasis: 0,
451
+ justifyContent: 'center',
452
+ // independent from other cellViews
453
+ paddingVertical: 10,
454
+ },
455
+ cellContentViewTypeSubtitle: {
456
+ paddingVertical: 4,
457
+ },
458
+ cellInnerSubtitle: {
459
+ flexDirection: 'column',
460
+ flexGrow: 1,
461
+ },
462
+ cellTitle: {
463
+ fontSize: 16,
464
+ letterSpacing: -0.32,
465
+ flexGrow: 1,
466
+ },
467
+ cellLeftDetailTitle: {
468
+ fontSize: 12,
469
+ flexGrow: 1,
470
+ },
471
+ cellLeftDetail: {
472
+ fontSize: 12,
473
+ alignSelf: 'center',
474
+ textAlign: 'right',
475
+ marginRight: 5,
476
+ width: 75,
477
+ },
478
+ cellRightDetail: {
479
+ fontSize: 16,
480
+ letterSpacing: -0.32,
481
+ alignSelf: 'center',
482
+ color: '#8E8E93',
483
+ },
484
+ cellSubtitle: {
485
+ fontSize: 11,
486
+ letterSpacing: 0.066,
487
+ },
488
+ cellTextDisabled: {
489
+ color: 'gray',
490
+ },
491
+ cellImageView: {
492
+ justifyContent: 'center',
493
+ marginRight: 15,
494
+ },
495
+ image: {
496
+ height: 29,
497
+ width: 29,
498
+ },
499
+ cellAccessoryView: {
500
+ justifyContent: 'center',
501
+ },
502
+ accessoryDisclosureIndicator: {
503
+ width: 10,
504
+ height: 10,
505
+ marginLeft: 7,
506
+ backgroundColor: 'transparent',
507
+ borderTopWidth: 1,
508
+ borderRightWidth: 1,
509
+ borderColor: '#c7c7cc',
510
+ transform: [
511
+ {
512
+ rotate: '45deg',
513
+ },
514
+ ],
515
+ },
516
+ accessoryDetail: {
517
+ width: 20,
518
+ height: 20,
519
+ marginLeft: 10,
520
+ backgroundColor: 'transparent',
521
+ borderWidth: 1,
522
+ borderRadius: 10,
523
+ alignItems: 'center',
524
+ justifyContent: 'center',
525
+ },
526
+ accessoryDetailText: {
527
+ fontSize: 15,
528
+ fontWeight: '400',
529
+ fontFamily: 'Georgia',
530
+ letterSpacing: -0.24,
531
+ },
532
+ accessoryDetailDisclosure: {
533
+ flexDirection: 'row',
534
+ alignItems: 'center',
535
+ },
536
+ accessoryCheckmark: {
537
+ width: 13,
538
+ height: 6,
539
+ marginLeft: 7,
540
+ backgroundColor: 'transparent',
541
+ borderBottomWidth: 2,
542
+ borderLeftWidth: 2,
543
+ borderColor: '#007AFF',
544
+ transform: [
545
+ {
546
+ rotate: '-45deg',
547
+ },
548
+ ],
549
+ },
550
+ });
551
+
552
+ export default Cell;