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.
@@ -0,0 +1,284 @@
1
+ import React, { useContext, useState } from 'react';
2
+ import {
3
+ Platform,
4
+ StyleSheet,
5
+ Text,
6
+ View,
7
+ TextStyle,
8
+ ViewStyle,
9
+ } from 'react-native';
10
+ import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
11
+
12
+ import Separator from './Separator';
13
+ import { CellInterface } from './Cell';
14
+ import { ThemeContext } from './Theme';
15
+
16
+ export interface SectionInterface {
17
+ allowFontScaling?: boolean;
18
+ children?: React.ReactNode;
19
+ footerComponent?: React.ReactNode;
20
+ headerComponent?: React.ReactNode;
21
+ footer?: string;
22
+ footerTextColor?: TextStyle['color'];
23
+ footerTextStyle?: TextStyle;
24
+ header?: string;
25
+ headerTextColor?: TextStyle['color'];
26
+ headerTextStyle?: TextStyle;
27
+ hideSeparator?: boolean;
28
+ hideSurroundingSeparators?: boolean;
29
+ roundedCorners?: boolean;
30
+ sectionPaddingBottom?: ViewStyle['paddingBottom'];
31
+ sectionPaddingTop?: ViewStyle['paddingTop'];
32
+ sectionTintColor?: ViewStyle['backgroundColor'];
33
+ separatorInsetLeft?: ViewStyle['marginLeft'];
34
+ separatorInsetRight?: ViewStyle['marginRight'];
35
+ separatorTintColor?: ViewStyle['backgroundColor'];
36
+ withSafeAreaView?: boolean;
37
+ }
38
+
39
+ const Section: React.FC<SectionInterface> = ({
40
+ allowFontScaling = true,
41
+ children,
42
+ footerComponent,
43
+ headerComponent,
44
+ footer,
45
+ footerTextColor,
46
+ footerTextStyle = {},
47
+ header,
48
+ headerTextColor,
49
+ headerTextStyle = {},
50
+ hideSeparator = false,
51
+ hideSurroundingSeparators = false,
52
+ roundedCorners = false,
53
+ sectionPaddingBottom = 15,
54
+ sectionPaddingTop = 15,
55
+ sectionTintColor,
56
+ separatorInsetLeft = 15,
57
+ separatorInsetRight = 0,
58
+ separatorTintColor,
59
+ withSafeAreaView = Platform.OS === 'ios'
60
+ ? parseInt(`${Platform.Version}`, 10) <= 10
61
+ ? false
62
+ : true
63
+ : true,
64
+ }: SectionInterface) => {
65
+ const theme = useContext(ThemeContext);
66
+ const [highlightedRowIndex, setHighlightedRowIndex] = useState<number>();
67
+
68
+ const highlightRow = (index: number): void => {
69
+ setHighlightedRowIndex(index);
70
+ };
71
+ const unhighlightRow = (): void => {
72
+ setHighlightedRowIndex(undefined);
73
+ };
74
+
75
+ /**
76
+ * Merge styles with props
77
+ */
78
+ const localStyles = {
79
+ ...styles,
80
+ section: [
81
+ styles.section,
82
+ {
83
+ backgroundColor: sectionTintColor,
84
+ paddingBottom: sectionPaddingBottom,
85
+ paddingTop: sectionPaddingTop,
86
+ },
87
+ ],
88
+ sectionChildren: [
89
+ styles.sectionChildren,
90
+ roundedCorners && styles.sectionChildrenRoundedCorners,
91
+ ],
92
+
93
+ sectionheaderText: [
94
+ styles.sectionheaderText,
95
+ { color: headerTextColor || theme.colors.secondary },
96
+ headerTextStyle,
97
+ ],
98
+ sectionfooterText: [
99
+ styles.sectionfooterText,
100
+ { color: footerTextColor || theme.colors.secondary },
101
+ footerTextStyle,
102
+ ],
103
+ };
104
+
105
+ // Need until .count is fixed: https://github.com/facebook/react/issues/7685
106
+ const childrenWithoutEmpty = React.Children.toArray(children);
107
+ const sumOfChildren = childrenWithoutEmpty.length;
108
+
109
+ /**
110
+ * Render Cell and add Border
111
+ */
112
+ const renderChild = (
113
+ child: string | number | React.ReactNode,
114
+ index: number,
115
+ ): React.ReactNode => {
116
+ if (!child) {
117
+ return null;
118
+ }
119
+ if (!React.isValidElement(child)) {
120
+ return null;
121
+ }
122
+
123
+ const propsToAdd = {
124
+ onHighlightRow: (): void => highlightRow(index),
125
+ onUnHighlightRow: unhighlightRow,
126
+ sectionRoundedCorners: roundedCorners,
127
+ isFirstChild: index === 0,
128
+ isLastChild: index === sumOfChildren - 1,
129
+ };
130
+
131
+ const childProps = child?.props as CellInterface;
132
+
133
+ // Skip rendering of separator
134
+ if (
135
+ hideSeparator ||
136
+ !Array.isArray(children) ||
137
+ sumOfChildren === 1 ||
138
+ index === sumOfChildren - 1 ||
139
+ childProps?.hideSeparator
140
+ ) {
141
+ return React.cloneElement(child, propsToAdd);
142
+ }
143
+
144
+ const invisibleSeparator =
145
+ highlightedRowIndex === index || highlightedRowIndex === index + 1;
146
+
147
+ // Add margin, if Image is provided
148
+ let separatorInsetLeftSupportImage = separatorInsetLeft;
149
+ // only update if separatorInsetLeft is default
150
+ if (childProps?.image && separatorInsetLeft === 15) {
151
+ separatorInsetLeftSupportImage = 60;
152
+ }
153
+
154
+ return (
155
+ <View key={child?.key ?? undefined}>
156
+ {React.cloneElement(child, propsToAdd)}
157
+ <Separator
158
+ isHidden={invisibleSeparator}
159
+ backgroundColor={childProps?.backgroundColor}
160
+ tintColor={separatorTintColor}
161
+ insetLeft={separatorInsetLeftSupportImage}
162
+ insetRight={separatorInsetRight}
163
+ />
164
+ </View>
165
+ );
166
+ };
167
+
168
+ /**
169
+ * Render header if defined
170
+ */
171
+ const renderHeader = (): React.ReactNode | null => {
172
+ if (header && withSafeAreaView) {
173
+ return (
174
+ <View style={styles.sectionheader}>
175
+ <SafeAreaProvider>
176
+ <SafeAreaView>
177
+ <Text
178
+ allowFontScaling={allowFontScaling}
179
+ style={localStyles.sectionheaderText}>
180
+ {header}
181
+ </Text>
182
+ </SafeAreaView>
183
+ </SafeAreaProvider>
184
+ </View>
185
+ );
186
+ }
187
+ if (header) {
188
+ return (
189
+ <View style={styles.sectionheader}>
190
+ <Text
191
+ allowFontScaling={allowFontScaling}
192
+ style={localStyles.sectionheaderText}>
193
+ {header}
194
+ </Text>
195
+ </View>
196
+ );
197
+ }
198
+ return null;
199
+ };
200
+
201
+ /**
202
+ * Render footer if defined
203
+ */
204
+ const renderFooter = (): React.ReactNode | null => {
205
+ if (footer && withSafeAreaView) {
206
+ return (
207
+ <View style={styles.sectionfooter}>
208
+ <SafeAreaView>
209
+ <Text
210
+ allowFontScaling={allowFontScaling}
211
+ style={localStyles.sectionfooterText}>
212
+ {footer}
213
+ </Text>
214
+ </SafeAreaView>
215
+ </View>
216
+ );
217
+ }
218
+ if (footer) {
219
+ return (
220
+ <View style={styles.sectionfooter}>
221
+ <Text
222
+ allowFontScaling={allowFontScaling}
223
+ style={localStyles.sectionfooterText}>
224
+ {footer}
225
+ </Text>
226
+ </View>
227
+ );
228
+ }
229
+ return null;
230
+ };
231
+
232
+ return (
233
+ <View style={localStyles.section}>
234
+ {headerComponent || renderHeader()}
235
+ {hideSurroundingSeparators || (
236
+ <Separator
237
+ insetLeft={0}
238
+ tintColor={separatorTintColor}
239
+ withSafeAreaView={false}
240
+ />
241
+ )}
242
+ <View style={localStyles.sectionChildren}>
243
+ {childrenWithoutEmpty.map(renderChild)}
244
+ </View>
245
+ {hideSurroundingSeparators || (
246
+ <Separator
247
+ insetLeft={0}
248
+ tintColor={separatorTintColor}
249
+ withSafeAreaView={false}
250
+ />
251
+ )}
252
+ {footerComponent || renderFooter()}
253
+ </View>
254
+ );
255
+ };
256
+
257
+ const styles = StyleSheet.create({
258
+ section: {},
259
+ sectionChildren: {},
260
+ sectionChildrenRoundedCorners: {
261
+ borderRadius: 10,
262
+ overflow: 'hidden',
263
+ },
264
+ sectionheader: {
265
+ paddingLeft: 15,
266
+ paddingRight: 15,
267
+ paddingBottom: 5,
268
+ },
269
+ sectionheaderText: {
270
+ fontSize: 13,
271
+ letterSpacing: -0.078,
272
+ },
273
+ sectionfooter: {
274
+ paddingLeft: 15,
275
+ paddingRight: 15,
276
+ paddingTop: 10,
277
+ },
278
+ sectionfooterText: {
279
+ fontSize: 13,
280
+ letterSpacing: -0.078,
281
+ },
282
+ });
283
+
284
+ export default Section;
@@ -0,0 +1,73 @@
1
+ import React, { useContext } from 'react';
2
+ import {
3
+ Platform,
4
+ StyleSheet,
5
+ View,
6
+ ViewStyle,
7
+ } from 'react-native';
8
+ import { ThemeContext } from './Theme';
9
+ import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
10
+
11
+ export interface SeparatorInterface {
12
+ backgroundColor?: ViewStyle['backgroundColor'];
13
+ tintColor?: ViewStyle['backgroundColor'];
14
+ isHidden?: boolean;
15
+ insetLeft?: ViewStyle['marginLeft'];
16
+ insetRight?: ViewStyle['marginRight'];
17
+ withSafeAreaView?: boolean;
18
+ }
19
+
20
+ const Separator: React.FC<SeparatorInterface> = ({
21
+ backgroundColor,
22
+ tintColor,
23
+ isHidden = false,
24
+ insetLeft = 15,
25
+ insetRight = 0,
26
+ withSafeAreaView = Platform.OS === 'ios'
27
+ ? parseInt(`${Platform.Version}`, 10) <= 10
28
+ ? false
29
+ : true
30
+ : true,
31
+ }) => {
32
+ const theme = useContext(ThemeContext);
33
+ const localStyles = {
34
+ separator: [
35
+ styles.separator,
36
+ { backgroundColor: backgroundColor || theme.colors.background },
37
+ ],
38
+ separatorInner: [
39
+ styles.separatorInner,
40
+ {
41
+ backgroundColor: isHidden
42
+ ? 'transparent'
43
+ : tintColor || theme.colors.separatorColor,
44
+ marginLeft: insetLeft,
45
+ marginRight: insetRight,
46
+ },
47
+ ],
48
+ };
49
+
50
+ if (withSafeAreaView) {
51
+ return (
52
+ <SafeAreaProvider>
53
+ <SafeAreaView style={localStyles.separator}>
54
+ <View style={localStyles.separatorInner} />
55
+ </SafeAreaView>
56
+ </SafeAreaProvider>
57
+ );
58
+ }
59
+ return (
60
+ <View style={localStyles.separator}>
61
+ <View style={localStyles.separatorInner} />
62
+ </View>
63
+ );
64
+ };
65
+
66
+ const styles = StyleSheet.create({
67
+ separator: {},
68
+ separatorInner: {
69
+ height: StyleSheet.hairlineWidth,
70
+ },
71
+ });
72
+
73
+ export default Separator;
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import {
3
+ useColorScheme,
4
+ StyleSheet,
5
+ View,
6
+ StyleProp,
7
+ ViewStyle,
8
+ } from 'react-native';
9
+ import { THEMES, ThemeContext, THEME_APPEARANCE } from './Theme';
10
+ import { StringWithAutocomplete } from '../../CustomTypes';
11
+
12
+ export interface TableViewInterface {
13
+ children?: React.ReactNode;
14
+ appearance?: StringWithAutocomplete<'auto' | 'dark' | 'light'>;
15
+ customAppearances?: {
16
+ [key: string]: THEME_APPEARANCE;
17
+ };
18
+ style?: StyleProp<ViewStyle>;
19
+ }
20
+
21
+ const TableView: React.FC<TableViewInterface> = ({
22
+ children,
23
+ appearance = 'auto',
24
+ customAppearances,
25
+ style,
26
+ }: TableViewInterface) => {
27
+ let themeMode: THEME_APPEARANCE = THEMES?.appearances?.light;
28
+ const systemColorScheme = useColorScheme();
29
+ if (
30
+ appearance === 'auto' &&
31
+ (systemColorScheme === 'dark' || systemColorScheme === 'light')
32
+ ) {
33
+ themeMode = THEMES?.appearances?.[systemColorScheme];
34
+ } else if (appearance === 'light' || appearance === 'dark') {
35
+ themeMode = THEMES?.appearances?.[appearance];
36
+ } else if (
37
+ customAppearances &&
38
+ appearance &&
39
+ Object.prototype.hasOwnProperty.call(customAppearances, appearance)
40
+ ) {
41
+ themeMode = customAppearances[appearance];
42
+ }
43
+ return (
44
+ <ThemeContext.Provider value={themeMode}>
45
+ <View style={[styles.tableView, style]}>{children}</View>
46
+ </ThemeContext.Provider>
47
+ );
48
+ };
49
+
50
+ const styles = StyleSheet.create({
51
+ tableView: {
52
+ flexDirection: 'column',
53
+ },
54
+ });
55
+
56
+ export default TableView;
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import { TextStyle, ViewStyle } from 'react-native';
3
+
4
+ export interface THEME_APPEARANCE {
5
+ colors: {
6
+ background: ViewStyle['backgroundColor'];
7
+ muted: ViewStyle['backgroundColor'];
8
+ separatorColor: ViewStyle['backgroundColor'];
9
+ body: TextStyle['color'];
10
+ primary: TextStyle['color'];
11
+ secondary: TextStyle['color'];
12
+ };
13
+ }
14
+
15
+ export const THEMES: {
16
+ appearances: {
17
+ [key: string]: THEME_APPEARANCE;
18
+ };
19
+ } = {
20
+ appearances: {
21
+ light: {
22
+ colors: {
23
+ background: '#FFF',
24
+ muted: '#C7C7CC',
25
+ separatorColor: '#C8C7CC',
26
+ body: '#000',
27
+ primary: '#007AFF',
28
+ secondary: '#8E8E93',
29
+ },
30
+ },
31
+ dark: {
32
+ colors: {
33
+ background: '#121312',
34
+ muted: '#59595d',
35
+ separatorColor: '#2b2b2d',
36
+ body: '#FFF',
37
+ primary: '#0f64ee',
38
+ secondary: '#aeaeae',
39
+ },
40
+ },
41
+ },
42
+ };
43
+
44
+ export const ThemeContext = React.createContext(THEMES.appearances.light);
@@ -0,0 +1,28 @@
1
+ /* eslint-env node, jest */
2
+ import 'react-native';
3
+ import React from 'react';
4
+ import renderer from 'react-test-renderer';
5
+ import Cell from '../Cell';
6
+
7
+ it('renders basic', () => {
8
+ const tree = renderer
9
+ .create(<Cell cellStyle="Basic" title="Basic" />)
10
+ .toJSON();
11
+ expect(tree).toMatchSnapshot();
12
+ });
13
+
14
+ it('renders with testID', () => {
15
+ const tree = renderer
16
+ .create(<Cell cellStyle="Basic" title="Basic" testID="testID" />)
17
+ .toJSON();
18
+ expect(tree).toMatchSnapshot();
19
+ });
20
+
21
+ it('renders with accessory', () => {
22
+ const tree = renderer
23
+ .create(
24
+ <Cell cellStyle="Basic" title="Basic" accessory="DisclosureIndicator" />,
25
+ )
26
+ .toJSON();
27
+ expect(tree).toMatchSnapshot();
28
+ });
@@ -0,0 +1,41 @@
1
+ /* eslint-env node, jest */
2
+ import 'react-native';
3
+ import React from 'react';
4
+ import renderer from 'react-test-renderer';
5
+ import Cell from '../Cell';
6
+
7
+ it('renders with left detail', () => {
8
+ const tree = renderer
9
+ .create(<Cell cellStyle="LeftDetail" title="LeftDetail" detail="Detail" />)
10
+ .toJSON();
11
+ expect(tree).toMatchSnapshot();
12
+ });
13
+
14
+ it('renders with accessory', () => {
15
+ const tree = renderer
16
+ .create(
17
+ <Cell
18
+ cellStyle="LeftDetail"
19
+ title="LeftDetail"
20
+ detail="Detail"
21
+ accessory="DisclosureIndicator"
22
+ />,
23
+ )
24
+ .toJSON();
25
+ expect(tree).toMatchSnapshot();
26
+ });
27
+
28
+ it('renders with accessory and onPressDetailAccessory', () => {
29
+ const tree = renderer
30
+ .create(
31
+ <Cell
32
+ cellStyle="LeftDetail"
33
+ title="LeftDetail"
34
+ detail="Detail"
35
+ accessory="DisclosureIndicator"
36
+ onPressDetailAccessory={console.log}
37
+ />,
38
+ )
39
+ .toJSON();
40
+ expect(tree).toMatchSnapshot();
41
+ });
@@ -0,0 +1,28 @@
1
+ /* eslint-env node, jest */
2
+ import 'react-native';
3
+ import React from 'react';
4
+ import renderer from 'react-test-renderer';
5
+ import Cell from '../Cell';
6
+
7
+ it('renders with right detail', () => {
8
+ const tree = renderer
9
+ .create(
10
+ <Cell cellStyle="RightDetail" title="RightDetail" detail="Detail" />,
11
+ )
12
+ .toJSON();
13
+ expect(tree).toMatchSnapshot();
14
+ });
15
+
16
+ it('renders with accessory', () => {
17
+ const tree = renderer
18
+ .create(
19
+ <Cell
20
+ cellStyle="RightDetail"
21
+ title="RightDetail"
22
+ detail="Detail"
23
+ accessory="DisclosureIndicator"
24
+ />,
25
+ )
26
+ .toJSON();
27
+ expect(tree).toMatchSnapshot();
28
+ });
@@ -0,0 +1,32 @@
1
+ /* eslint-env node, jest */
2
+ import 'react-native';
3
+ import React from 'react';
4
+ import renderer from 'react-test-renderer';
5
+ import Cell from '../Cell';
6
+
7
+ it('renders with subtitle', () => {
8
+ const tree = renderer
9
+ .create(
10
+ <Cell
11
+ cellStyle="Subtitle"
12
+ title="Subtitle"
13
+ detail="No linebreakkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk"
14
+ />,
15
+ )
16
+ .toJSON();
17
+ expect(tree).toMatchSnapshot();
18
+ });
19
+
20
+ it('renders with accessory', () => {
21
+ const tree = renderer
22
+ .create(
23
+ <Cell
24
+ cellStyle="Subtitle"
25
+ title="Subtitle"
26
+ detail="No linebreakkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk"
27
+ accessory="DisclosureIndicator"
28
+ />,
29
+ )
30
+ .toJSON();
31
+ expect(tree).toMatchSnapshot();
32
+ });
@@ -0,0 +1,97 @@
1
+ /* eslint-env node, jest */
2
+ import 'react-native';
3
+ import React from 'react';
4
+ import renderer from 'react-test-renderer';
5
+ import { Cell, Section, TableView } from '../../index';
6
+
7
+ it('renders basic', () => {
8
+ const tree = renderer
9
+ .create(
10
+ <TableView>
11
+ <Section footer="All rights reserved.">
12
+ <Cell
13
+ title="Help / FAQ"
14
+ titleTextColor="#007AFF"
15
+ onPress={(): void => console.log('open Help/FAQ')}
16
+ />
17
+ <Cell
18
+ title="Contact Us"
19
+ titleTextColor="#007AFF"
20
+ onPress={(): void => console.log('open Contact Us')}
21
+ />
22
+ {null}
23
+ </Section>
24
+ </TableView>,
25
+ )
26
+ .toJSON();
27
+ expect(tree).toMatchSnapshot();
28
+ });
29
+
30
+ it('renders with appearance auto', () => {
31
+ const tree = renderer
32
+ .create(
33
+ <TableView appearance="auto">
34
+ <Section footer="All rights reserved.">
35
+ <Cell
36
+ title="Help / FAQ"
37
+ titleTextColor="#007AFF"
38
+ onPress={(): void => console.log('open Help/FAQ')}
39
+ />
40
+ <Cell
41
+ title="Contact Us"
42
+ titleTextColor="#007AFF"
43
+ onPress={(): void => console.log('open Contact Us')}
44
+ />
45
+ {null}
46
+ </Section>
47
+ </TableView>,
48
+ )
49
+ .toJSON();
50
+ expect(tree).toMatchSnapshot();
51
+ });
52
+
53
+ it('renders with appearance light', () => {
54
+ const tree = renderer
55
+ .create(
56
+ <TableView appearance="light">
57
+ <Section footer="All rights reserved.">
58
+ <Cell
59
+ title="Help / FAQ"
60
+ titleTextColor="#007AFF"
61
+ onPress={(): void => console.log('open Help/FAQ')}
62
+ />
63
+ <Cell
64
+ title="Contact Us"
65
+ titleTextColor="#007AFF"
66
+ onPress={(): void => console.log('open Contact Us')}
67
+ />
68
+ {null}
69
+ </Section>
70
+ </TableView>,
71
+ )
72
+ .toJSON();
73
+ expect(tree).toMatchSnapshot();
74
+ });
75
+
76
+ it('renders with appearance dark', () => {
77
+ const tree = renderer
78
+ .create(
79
+ <TableView appearance="dark">
80
+ <Section footer="All rights reserved.">
81
+ <Cell
82
+ title="Help / FAQ"
83
+ titleTextColor="#007AFF"
84
+ onPress={(): void => console.log('open Help/FAQ')}
85
+ />
86
+ <Cell
87
+ title="Contact Us"
88
+ titleTextColor="#007AFF"
89
+ onPress={(): void => console.log('open Contact Us')}
90
+ />
91
+ {null}
92
+ </Section>
93
+ </TableView>,
94
+ )
95
+ .toJSON();
96
+ expect(tree).toMatchSnapshot();
97
+ });