react95-native-rabbl 0.1.1

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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -0
  3. package/package.json +154 -0
  4. package/src/assets/fonts/src/ms-sans-serif/MS Sans Serif.ttf +0 -0
  5. package/src/assets/fonts/src/ms-sans-serif/license.txt +4 -0
  6. package/src/assets/fonts/src/ms-sans-serif/readme.txt +26 -0
  7. package/src/assets/fonts/src/ms-sans-serif-bold/MS Sans Serif Bold.ttf +0 -0
  8. package/src/assets/fonts/src/ms-sans-serif-bold/license.txt +4 -0
  9. package/src/assets/fonts/src/ms-sans-serif-bold/readme.txt +26 -0
  10. package/src/components/AppBar/AppBar.spec.tsx +140 -0
  11. package/src/components/AppBar/AppBar.tsx +43 -0
  12. package/src/components/AppBar/AppBarBackAction.tsx +20 -0
  13. package/src/components/AppBar/AppBarContent.tsx +84 -0
  14. package/src/components/AppBar/index.ts +1 -0
  15. package/src/components/Button/Button.spec.tsx +59 -0
  16. package/src/components/Button/Button.tsx +236 -0
  17. package/src/components/Button/index.ts +1 -0
  18. package/src/components/Card/Card.spec.tsx +54 -0
  19. package/src/components/Card/Card.tsx +88 -0
  20. package/src/components/Card/CardContent.tsx +23 -0
  21. package/src/components/Card/index.ts +1 -0
  22. package/src/components/Checkbox/Checkbox.tsx +10 -0
  23. package/src/components/Checkbox/index.ts +1 -0
  24. package/src/components/ColorButton/ColorButton.tsx +69 -0
  25. package/src/components/ColorButton/index.ts +1 -0
  26. package/src/components/ColorPicker/ColorPicker.tsx +109 -0
  27. package/src/components/ColorPicker/index.ts +1 -0
  28. package/src/components/Desktop/Desktop.spec.tsx +32 -0
  29. package/src/components/Desktop/Desktop.tsx +132 -0
  30. package/src/components/Desktop/index.tsx +1 -0
  31. package/src/components/Divider/Divider.spec.tsx +47 -0
  32. package/src/components/Divider/Divider.tsx +52 -0
  33. package/src/components/Divider/index.tsx +1 -0
  34. package/src/components/FAB/FAB.tsx +288 -0
  35. package/src/components/FAB/FABGroup.tsx +385 -0
  36. package/src/components/FAB/index.ts +1 -0
  37. package/src/components/Fieldset/Fieldset.spec.tsx +48 -0
  38. package/src/components/Fieldset/Fieldset.tsx +107 -0
  39. package/src/components/Fieldset/index.ts +1 -0
  40. package/src/components/Hourglass/Hourglass.spec.tsx +24 -0
  41. package/src/components/Hourglass/Hourglass.tsx +43 -0
  42. package/src/components/Hourglass/base64hourglass.ts +3 -0
  43. package/src/components/Hourglass/index.ts +1 -0
  44. package/src/components/Icons/ArrowIcon.tsx +85 -0
  45. package/src/components/Icons/CheckmarkIcon.tsx +55 -0
  46. package/src/components/Icons/ChevronIcon.tsx +93 -0
  47. package/src/components/Icons/CloseIcon.tsx +48 -0
  48. package/src/components/Icons/index.ts +4 -0
  49. package/src/components/Label/Label.tsx +77 -0
  50. package/src/components/Label/index.ts +1 -0
  51. package/src/components/List/List.tsx +3 -0
  52. package/src/components/List/ListAccordion.tsx +154 -0
  53. package/src/components/List/ListItem.tsx +74 -0
  54. package/src/components/List/ListSection.tsx +51 -0
  55. package/src/components/List/index.ts +3 -0
  56. package/src/components/Menu/Menu.tsx +100 -0
  57. package/src/components/Menu/MenuItem.tsx +100 -0
  58. package/src/components/Menu/index.ts +1 -0
  59. package/src/components/NumberInput/NumberInput.spec.tsx +119 -0
  60. package/src/components/NumberInput/NumberInput.tsx +144 -0
  61. package/src/components/NumberInput/index.ts +1 -0
  62. package/src/components/Panel/Panel.spec.tsx +29 -0
  63. package/src/components/Panel/Panel.tsx +75 -0
  64. package/src/components/Panel/index.ts +1 -0
  65. package/src/components/Portal/Portal.tsx +52 -0
  66. package/src/components/Portal/PortalConsumer.tsx +48 -0
  67. package/src/components/Portal/PortalHost.tsx +150 -0
  68. package/src/components/Portal/PortalManager.tsx +57 -0
  69. package/src/components/Portal/index.ts +1 -0
  70. package/src/components/Progress/Progress.tsx +125 -0
  71. package/src/components/Progress/index.ts +1 -0
  72. package/src/components/Radio/Radio.tsx +14 -0
  73. package/src/components/Radio/index.ts +1 -0
  74. package/src/components/ScrollPanel/ScrollPanel.tsx +72 -0
  75. package/src/components/ScrollPanel/index.ts +1 -0
  76. package/src/components/ScrollView/ScrollView.tsx +284 -0
  77. package/src/components/ScrollView/index.ts +1 -0
  78. package/src/components/Select/Select.tsx +229 -0
  79. package/src/components/Select/SelectBase.tsx +119 -0
  80. package/src/components/Select/SelectBox.tsx +66 -0
  81. package/src/components/Select/index.ts +2 -0
  82. package/src/components/Slider/Slider.tsx +301 -0
  83. package/src/components/Slider/index.ts +1 -0
  84. package/src/components/Snackbar/Snackbar.tsx +260 -0
  85. package/src/components/Snackbar/SnackbarContent.tsx +23 -0
  86. package/src/components/Snackbar/index.ts +1 -0
  87. package/src/components/SwitchBase/SwitchBase.tsx +193 -0
  88. package/src/components/SwitchBase/index.ts +1 -0
  89. package/src/components/Tabs/Tabs.tsx +208 -0
  90. package/src/components/Tabs/index.ts +1 -0
  91. package/src/components/TextInput/TextInput.tsx +82 -0
  92. package/src/components/TextInput/index.ts +1 -0
  93. package/src/components/Toolbar/Toolbar.tsx +113 -0
  94. package/src/components/Toolbar/index.ts +1 -0
  95. package/src/components/Typography/Anchor.tsx +38 -0
  96. package/src/components/Typography/Text.spec.tsx +30 -0
  97. package/src/components/Typography/Text.tsx +55 -0
  98. package/src/components/Typography/Title.tsx +58 -0
  99. package/src/components/Typography/index.ts +3 -0
  100. package/src/components/Window/Window.tsx +132 -0
  101. package/src/components/Window/index.ts +1 -0
  102. package/src/core/Provider.tsx +52 -0
  103. package/src/core/theming.tsx +8 -0
  104. package/src/hooks/useAsyncReference.ts +22 -0
  105. package/src/hooks/useControlledOrUncontrolled.ts +23 -0
  106. package/src/index.ts +38 -0
  107. package/src/styles/shadow.tsx +36 -0
  108. package/src/styles/styleElements.tsx +105 -0
  109. package/src/styles/styles.ts +129 -0
  110. package/src/styles/themes/aiee.ts +36 -0
  111. package/src/styles/themes/ash.ts +35 -0
  112. package/src/styles/themes/azureOrange.ts +33 -0
  113. package/src/styles/themes/bee.ts +33 -0
  114. package/src/styles/themes/blackAndWhite.ts +33 -0
  115. package/src/styles/themes/blue.ts +36 -0
  116. package/src/styles/themes/brick.ts +33 -0
  117. package/src/styles/themes/candy.ts +33 -0
  118. package/src/styles/themes/cherry.ts +36 -0
  119. package/src/styles/themes/coldGray.ts +34 -0
  120. package/src/styles/themes/counterStrike.ts +33 -0
  121. package/src/styles/themes/darkTeal.ts +36 -0
  122. package/src/styles/themes/eggplant.ts +33 -0
  123. package/src/styles/themes/fxDev.ts +36 -0
  124. package/src/styles/themes/highContrast.ts +33 -0
  125. package/src/styles/themes/hotChocolate.ts +36 -0
  126. package/src/styles/themes/index.ts +103 -0
  127. package/src/styles/themes/lilac.ts +33 -0
  128. package/src/styles/themes/lilacRoseDark.ts +34 -0
  129. package/src/styles/themes/maple.ts +33 -0
  130. package/src/styles/themes/marine.ts +33 -0
  131. package/src/styles/themes/matrix.ts +33 -0
  132. package/src/styles/themes/millenium.ts +33 -0
  133. package/src/styles/themes/modernDark.ts +33 -0
  134. package/src/styles/themes/molecule.ts +33 -0
  135. package/src/styles/themes/monochrome.ts +0 -0
  136. package/src/styles/themes/ninjaTurtles.ts +33 -0
  137. package/src/styles/themes/olive.ts +33 -0
  138. package/src/styles/themes/original.ts +33 -0
  139. package/src/styles/themes/pamelaAnderson.ts +33 -0
  140. package/src/styles/themes/plum.ts +33 -0
  141. package/src/styles/themes/polarized.ts +36 -0
  142. package/src/styles/themes/powerShell.ts +36 -0
  143. package/src/styles/themes/rainyDay.ts +33 -0
  144. package/src/styles/themes/raspberry.ts +36 -0
  145. package/src/styles/themes/redWine.ts +36 -0
  146. package/src/styles/themes/rose.ts +33 -0
  147. package/src/styles/themes/seawater.ts +36 -0
  148. package/src/styles/themes/slate.ts +33 -0
  149. package/src/styles/themes/solarizedDark.ts +36 -0
  150. package/src/styles/themes/solarizedLight.ts +36 -0
  151. package/src/styles/themes/spruce.ts +33 -0
  152. package/src/styles/themes/stormClouds.ts +36 -0
  153. package/src/styles/themes/theSixtiesUSA.ts +33 -0
  154. package/src/styles/themes/tokyoDark.ts +33 -0
  155. package/src/styles/themes/tooSexy.ts +33 -0
  156. package/src/styles/themes/travel.ts +33 -0
  157. package/src/styles/themes/vaporTeal.ts +33 -0
  158. package/src/styles/themes/vermillion.ts +33 -0
  159. package/src/styles/themes/violetDark.ts +33 -0
  160. package/src/styles/themes/water.ts +33 -0
  161. package/src/styles/themes/wmii.ts +36 -0
  162. package/src/types.tsx +55 -0
  163. package/src/utils/index.ts +57 -0
@@ -0,0 +1,193 @@
1
+ import React from 'react';
2
+ import {
3
+ StyleSheet,
4
+ TouchableHighlight,
5
+ ImageBackground,
6
+ View,
7
+ StyleProp,
8
+ ViewStyle,
9
+ } from 'react-native';
10
+
11
+ import type { Theme } from '../../types';
12
+ import { withTheme } from '../../core/theming';
13
+
14
+ import { buildBorderStyles } from '../../styles/styles';
15
+ import { Border } from '../../styles/styleElements';
16
+ import { Text, CheckmarkIcon } from '../..';
17
+
18
+ const checkboxSize = 20;
19
+ const radioSize = 20;
20
+
21
+ export type SwitchStatus = 'checked' | 'unchecked' | 'indeterminate';
22
+
23
+ export type SwitchProps = {
24
+ disabled?: boolean;
25
+ label?: string;
26
+ onPress?: () => void;
27
+ status: SwitchStatus;
28
+ style?: StyleProp<ViewStyle>;
29
+ variant?: 'default' | 'flat';
30
+ theme: Theme;
31
+ };
32
+
33
+ type Props = SwitchProps &
34
+ React.ComponentPropsWithRef<typeof TouchableHighlight> & {
35
+ component: 'radio' | 'checkbox';
36
+ };
37
+
38
+ // TODO: see if ref is passed
39
+
40
+ const SwitchBase = ({
41
+ component,
42
+ disabled = false,
43
+ label = '',
44
+ variant = 'default',
45
+ onPress = () => {},
46
+ status,
47
+ style = {},
48
+ theme,
49
+ ...rest
50
+ }: Props) => {
51
+ const [isPressed, setIsPressed] = React.useState(false);
52
+ const isRadio = component === 'radio';
53
+ const switchSize = !isRadio ? checkboxSize : radioSize;
54
+ const boxSize = variant === 'flat' ? switchSize - 4 : switchSize;
55
+ const borderRadius = isRadio ? boxSize / 2 : 0;
56
+
57
+ const checked = status === 'checked';
58
+
59
+ const borders = buildBorderStyles(theme);
60
+ const renderCheckmark = () => {
61
+ if (checked) {
62
+ return isRadio ? (
63
+ <View
64
+ style={{
65
+ borderRadius: 6,
66
+ height: 6,
67
+ width: 6,
68
+ backgroundColor: disabled
69
+ ? theme.checkmarkDisabled
70
+ : theme.checkmark,
71
+ }}
72
+ />
73
+ ) : (
74
+ <CheckmarkIcon disabled={disabled} />
75
+ );
76
+ }
77
+ if (status === 'indeterminate') {
78
+ return (
79
+ <ImageBackground
80
+ style={[{ width: '100%', height: '100%' }]}
81
+ imageStyle={{
82
+ resizeMode: 'repeat',
83
+ }}
84
+ source={{
85
+ uri: {
86
+ default:
87
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAJElEQVQoU2NkYGD4z4AKGJG5IA4dFKA5AdVKFAdBVaK4iXIFAEiuCAWq9MdHAAAAAElFTkSuQmCC',
88
+ disabled:
89
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAJElEQVQoU2NsaW35z4AEaqprGJH5jHRQgGwfiI1uJYqDaKMAAHKtGjlbjgHwAAAAAElFTkSuQmCC',
90
+ }[disabled ? 'disabled' : 'default'],
91
+ }}
92
+ />
93
+ );
94
+ }
95
+
96
+ return <Text> </Text>;
97
+ };
98
+
99
+ const getBackgroundColor = () => {
100
+ if (variant === 'flat') {
101
+ return disabled ? theme.flatLight : theme.canvas;
102
+ }
103
+ return disabled ? theme.material : theme.canvas;
104
+ };
105
+
106
+ const getAccessibilityComponentType = () => {
107
+ if (isRadio) {
108
+ return checked ? 'radiobutton_checked' : 'radiobutton_unchecked';
109
+ }
110
+ return 'button';
111
+ };
112
+
113
+ return (
114
+ <TouchableHighlight
115
+ style={[styles.wrapper]}
116
+ onPress={onPress}
117
+ activeOpacity={1}
118
+ disabled={disabled}
119
+ onHideUnderlay={() => setIsPressed(false)}
120
+ onShowUnderlay={() => setIsPressed(true)}
121
+ // TODO: check if those accessibility properties are correct
122
+ accessibilityTraits={disabled ? ['button', 'disabled'] : 'button'}
123
+ accessibilityComponentType={getAccessibilityComponentType()}
124
+ accessibilityRole={component}
125
+ accessibilityState={{ disabled, checked }}
126
+ accessibilityLiveRegion='polite'
127
+ underlayColor='none'
128
+ {...rest}
129
+ >
130
+ <View style={[styles.content, style]}>
131
+ <View
132
+ style={[
133
+ styles.switchSymbol,
134
+ {
135
+ width: boxSize,
136
+ height: boxSize,
137
+ backgroundColor: getBackgroundColor(),
138
+ borderRadius,
139
+ overflow: 'hidden',
140
+ },
141
+ ]}
142
+ >
143
+ {renderCheckmark()}
144
+ <Border
145
+ variant={variant === 'flat' ? 'flat' : 'cutout'}
146
+ radius={borderRadius}
147
+ />
148
+ </View>
149
+ {Boolean(label) && (
150
+ <View
151
+ style={[
152
+ styles.labelWrapper,
153
+ !disabled && isPressed
154
+ ? borders.focusOutline
155
+ : { borderWidth: 2, borderColor: 'transparent' },
156
+ ]}
157
+ >
158
+ <Text disabled={disabled} style={[styles.label]}>
159
+ {label}
160
+ </Text>
161
+ </View>
162
+ )}
163
+ </View>
164
+ </TouchableHighlight>
165
+ );
166
+ };
167
+
168
+ const styles = StyleSheet.create({
169
+ wrapper: {
170
+ width: 'auto',
171
+ alignSelf: 'flex-start',
172
+ padding: 4,
173
+ },
174
+ content: {
175
+ flexDirection: 'row',
176
+ alignItems: 'center',
177
+ width: 'auto',
178
+ },
179
+ switchSymbol: {
180
+ marginRight: 4,
181
+ alignItems: 'center',
182
+ justifyContent: 'center',
183
+ },
184
+ labelWrapper: {
185
+ paddingHorizontal: 4,
186
+ },
187
+ label: {
188
+ fontSize: 16,
189
+ },
190
+ });
191
+
192
+ const SwitchBaseWithTheme = withTheme(SwitchBase);
193
+ export { SwitchBaseWithTheme as SwitchBase };
@@ -0,0 +1 @@
1
+ export * from './SwitchBase';
@@ -0,0 +1,208 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ StyleProp,
4
+ StyleSheet,
5
+ ViewStyle,
6
+ TouchableHighlight,
7
+ View,
8
+ } from 'react-native';
9
+
10
+ import type { Theme, AnyValue } from '../../types';
11
+ import { withTheme } from '../../core/theming';
12
+
13
+ import {
14
+ padding,
15
+ margin,
16
+ blockSizes,
17
+ buildBorderStyles,
18
+ } from '../../styles/styles';
19
+ import { Border } from '../../styles/styleElements';
20
+ import { Text, Panel } from '../..';
21
+
22
+ type TabsProps = {
23
+ children?: React.ReactNode;
24
+ onChange?: (value: AnyValue) => void;
25
+ stretch?: boolean;
26
+ style?: StyleProp<ViewStyle>;
27
+ theme: Theme;
28
+ value: AnyValue;
29
+ };
30
+
31
+ const Tabs = ({
32
+ children,
33
+ onChange,
34
+ stretch = false,
35
+ style,
36
+ theme,
37
+ value,
38
+ ...rest
39
+ }: TabsProps) => {
40
+ const childrenWithProps = React.Children.map(children, child => {
41
+ if (!React.isValidElement(child)) {
42
+ return null;
43
+ }
44
+ const tabProps = {
45
+ selected: child.props.value === value,
46
+ onPress: onChange,
47
+ stretch,
48
+ };
49
+ return React.cloneElement(child, tabProps);
50
+ });
51
+
52
+ return (
53
+ <View accessibilityRole='tablist' style={[styles.tabs, style]} {...rest}>
54
+ {childrenWithProps}
55
+ <View
56
+ style={[
57
+ styles.tabBodyBorder,
58
+ {
59
+ backgroundColor: theme.borderLight,
60
+ borderTopColor: theme.borderLightest,
61
+ },
62
+ ]}
63
+ />
64
+ </View>
65
+ );
66
+ };
67
+
68
+ type TabBodyProps = {
69
+ children?: React.ReactNode;
70
+ style?: StyleProp<ViewStyle>;
71
+ theme: Theme;
72
+ };
73
+
74
+ const Body = ({ children, style, ...rest }: TabBodyProps) => {
75
+ return (
76
+ <Panel style={[styles.body, style]} {...rest}>
77
+ {children}
78
+ </Panel>
79
+ );
80
+ };
81
+
82
+ type TabProps = {
83
+ children?: React.ReactNode;
84
+ onPress?: (value: AnyValue) => void;
85
+ selected?: boolean;
86
+ stretch?: boolean;
87
+ style?: StyleProp<ViewStyle>;
88
+ theme: Theme;
89
+ value: AnyValue;
90
+ };
91
+
92
+ const Tab = ({
93
+ children,
94
+ onPress = () => {},
95
+ selected,
96
+ stretch,
97
+ style,
98
+ theme,
99
+ value,
100
+ ...rest
101
+ }: TabProps) => {
102
+ const [isPressed, setIsPressed] = useState(false);
103
+
104
+ const borderStyles = buildBorderStyles(theme);
105
+
106
+ return (
107
+ <TouchableHighlight
108
+ onPress={() => onPress(value)}
109
+ onHideUnderlay={() => setIsPressed(false)}
110
+ onShowUnderlay={() => setIsPressed(true)}
111
+ underlayColor='none'
112
+ style={[
113
+ styles.tab,
114
+ { zIndex: selected ? 1 : 0 },
115
+ stretch ? { flexGrow: 1 } : { width: 'auto' },
116
+ selected ? margin(0, -8) : margin(0, 0),
117
+ style,
118
+ ]}
119
+ accessibilityTraits={selected ? ['button', 'selected'] : 'button'}
120
+ accessibilityComponentType='button'
121
+ accessibilityRole='tab'
122
+ accessibilityState={{
123
+ selected,
124
+ }}
125
+ {...rest}
126
+ >
127
+ <View
128
+ style={[
129
+ styles.tabContent,
130
+ {
131
+ height: selected ? blockSizes.md + 4 : blockSizes.md,
132
+ },
133
+ selected ? padding(0, 16) : padding(0, 10),
134
+ ]}
135
+ >
136
+ <Border
137
+ radius={6}
138
+ style={[
139
+ {
140
+ backgroundColor: theme.material,
141
+ },
142
+ ]}
143
+ sharedStyle={{
144
+ borderBottomWidth: 0,
145
+ borderBottomLeftRadius: 0,
146
+ borderBottomRightRadius: 0,
147
+ }}
148
+ />
149
+ <Text>{children}</Text>
150
+ <View style={[styles.mask, { backgroundColor: theme.material }]} />
151
+ {isPressed && (
152
+ <View style={[styles.focusOutline, borderStyles.focusOutline]} />
153
+ )}
154
+ </View>
155
+ </TouchableHighlight>
156
+ );
157
+ };
158
+
159
+ const styles = StyleSheet.create({
160
+ tabs: {
161
+ display: 'flex',
162
+ flexDirection: 'row',
163
+ alignItems: 'flex-end',
164
+ paddingHorizontal: 8,
165
+ zIndex: 1,
166
+ bottom: -2,
167
+ },
168
+ body: {
169
+ display: 'flex',
170
+ padding: 16,
171
+ },
172
+ tab: {
173
+ alignSelf: 'flex-end',
174
+ },
175
+ tabContent: {
176
+ justifyContent: 'center',
177
+ width: 'auto',
178
+ },
179
+ tabBodyBorder: {
180
+ height: 4,
181
+ position: 'absolute',
182
+ left: 4,
183
+ right: 4,
184
+ bottom: -2,
185
+ borderTopWidth: 2,
186
+ },
187
+ mask: {
188
+ height: 4,
189
+ position: 'absolute',
190
+ left: 4,
191
+ right: 4,
192
+ bottom: -2,
193
+ },
194
+ focusOutline: {
195
+ position: 'absolute',
196
+ left: 6,
197
+ top: 6,
198
+ bottom: 4,
199
+ right: 6,
200
+ },
201
+ });
202
+ const TabWithTheme = withTheme(Tab);
203
+ const BodyWithTheme = withTheme(Body);
204
+
205
+ Tabs.Tab = TabWithTheme;
206
+ Tabs.Body = BodyWithTheme;
207
+
208
+ export default withTheme(Tabs);
@@ -0,0 +1 @@
1
+ export { default } from './Tabs';
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+ import {
3
+ View,
4
+ StyleProp,
5
+ StyleSheet,
6
+ TextInput as NativeTextInput,
7
+ ViewStyle,
8
+ } from 'react-native';
9
+
10
+ import type { Theme } from '../../types';
11
+ import { withTheme } from '../../core/theming';
12
+
13
+ import { blockSizes, builtTextStyles } from '../../styles/styles';
14
+ import { Border } from '../../styles/styleElements';
15
+
16
+ type Props = React.ComponentPropsWithRef<typeof NativeTextInput> & {
17
+ defaultValue?: string;
18
+ disabled?: boolean;
19
+ style?: StyleProp<ViewStyle>;
20
+ theme: Theme;
21
+ value?: string;
22
+ variant?: 'default' | 'flat';
23
+ };
24
+
25
+ // TODO: implement scrollbars in TextInput
26
+
27
+ const TextInput = ({
28
+ defaultValue,
29
+ disabled,
30
+ style = {},
31
+ theme,
32
+ value,
33
+ variant = 'default',
34
+ ...rest
35
+ }: Props) => {
36
+ const hasValue = !!(value || defaultValue);
37
+
38
+ const isFlat = variant === 'flat';
39
+
40
+ const getBackgroundColor = () => {
41
+ if (isFlat) {
42
+ return disabled ? theme.flatLight : 'transparent';
43
+ }
44
+ return disabled ? theme.material : theme.canvas;
45
+ };
46
+
47
+ const textStyles = builtTextStyles(theme);
48
+ return (
49
+ <View style={[styles.wrapper, { padding: isFlat ? 2 : 4 }, style]}>
50
+ <Border theme={theme} variant={isFlat ? 'flat' : 'cutout'} />
51
+ <NativeTextInput
52
+ style={[
53
+ styles.input,
54
+ {
55
+ backgroundColor: getBackgroundColor(),
56
+ },
57
+ textStyles.regular,
58
+ disabled && hasValue ? textStyles.disabled : textStyles.default,
59
+ ]}
60
+ placeholderTextColor={theme.materialTextDisabled}
61
+ defaultValue={defaultValue}
62
+ value={value}
63
+ editable={!disabled}
64
+ // eslint-disable-next-line react/jsx-props-no-spreading
65
+ {...rest}
66
+ />
67
+ </View>
68
+ );
69
+ };
70
+
71
+ const styles = StyleSheet.create({
72
+ wrapper: {
73
+ minHeight: blockSizes.md,
74
+ justifyContent: 'center',
75
+ },
76
+ input: {
77
+ flex: 1,
78
+ paddingHorizontal: 4,
79
+ },
80
+ });
81
+
82
+ export default withTheme(TextInput);
@@ -0,0 +1 @@
1
+ export { default } from './TextInput';
@@ -0,0 +1,113 @@
1
+ import React from 'react';
2
+ import {
3
+ StyleSheet,
4
+ View,
5
+ ScrollView as RNScrollView,
6
+ ViewStyle,
7
+ StyleProp,
8
+ } from 'react-native';
9
+ import type {
10
+ LayoutChangeEvent,
11
+ NativeScrollEvent,
12
+ NativeSyntheticEvent,
13
+ } from 'react-native';
14
+
15
+ import { ChevronIcon } from '../..';
16
+
17
+ const chevronIconSize = 20;
18
+
19
+ type ScrollViewProps = React.ComponentProps<typeof View> & {
20
+ children: React.ReactNode;
21
+ scrollViewProps?: React.ComponentProps<typeof RNScrollView>;
22
+ style?: StyleProp<ViewStyle>;
23
+ };
24
+
25
+ // TODO: performance improvements (callbacks, refs ...etc)
26
+ const ScrollView = ({
27
+ children,
28
+ scrollViewProps = {},
29
+ style,
30
+ ...rest
31
+ }: ScrollViewProps) => {
32
+ const [contentOffset, setContentOffset] = React.useState(0);
33
+ const [contentSize, setContentSize] = React.useState(0);
34
+ const [scrollViewSize, setScrollViewSize] = React.useState(0);
35
+
36
+ // TODO: that's a naive approach. if it causes problems
37
+ // trigger a callback when last child becomes fully visible
38
+ const lastElementVisible =
39
+ contentOffset + scrollViewSize < contentSize - chevronIconSize;
40
+
41
+ const handleScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
42
+ scrollViewProps.onScroll?.(e);
43
+ setContentOffset(e.nativeEvent.contentOffset.x);
44
+ };
45
+
46
+ const handleContentSizeChange = (width: number, height: number) => {
47
+ scrollViewProps.onContentSizeChange?.(width, height);
48
+ setContentSize(width);
49
+ };
50
+
51
+ const handleLayout = (e: LayoutChangeEvent) => {
52
+ scrollViewProps.onLayout?.(e);
53
+ setScrollViewSize(e.nativeEvent.layout.width);
54
+ };
55
+
56
+ return (
57
+ <View
58
+ style={[
59
+ styles.wrapper,
60
+ {
61
+ flexDirection: 'row',
62
+ },
63
+ style,
64
+ ]}
65
+ {...rest}
66
+ >
67
+ <View style={[styles.content]}>
68
+ <RNScrollView
69
+ {...scrollViewProps}
70
+ showsHorizontalScrollIndicator={false}
71
+ scrollEventThrottle={100}
72
+ onScroll={handleScroll}
73
+ onContentSizeChange={handleContentSizeChange}
74
+ onLayout={handleLayout}
75
+ horizontal
76
+ >
77
+ {children}
78
+ </RNScrollView>
79
+ </View>
80
+ <View style={styles.chevronIcon}>
81
+ {lastElementVisible && (
82
+ <>
83
+ <ChevronIcon
84
+ segments={3}
85
+ direction='right'
86
+ style={{ marginRight: 1 }}
87
+ />
88
+ <ChevronIcon segments={3} direction='right' />
89
+ </>
90
+ )}
91
+ </View>
92
+ </View>
93
+ );
94
+ };
95
+
96
+ const styles = StyleSheet.create({
97
+ wrapper: {
98
+ display: 'flex',
99
+ position: 'relative',
100
+ },
101
+ content: {
102
+ flexGrow: 1,
103
+ flexShrink: 1,
104
+ },
105
+ chevronIcon: {
106
+ justifyContent: 'center',
107
+ flexDirection: 'row',
108
+ alignSelf: 'flex-start',
109
+ width: chevronIconSize,
110
+ },
111
+ });
112
+
113
+ export default ScrollView;
@@ -0,0 +1 @@
1
+ export { default } from './Toolbar';
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+
3
+ import type { Theme } from '../../types';
4
+ import { withTheme } from '../../core/theming';
5
+
6
+ import { Text } from '../..';
7
+
8
+ // TODO: separate Anchor props from React95 Text component
9
+
10
+ type Props = React.ComponentProps<typeof Text> & {
11
+ theme: Theme;
12
+ underline?: boolean;
13
+ };
14
+
15
+ const Anchor = ({
16
+ children,
17
+ style,
18
+ theme,
19
+ underline = false,
20
+ ...rest
21
+ }: Props) => {
22
+ return (
23
+ <Text
24
+ style={[
25
+ {
26
+ color: theme.anchor,
27
+ textDecorationLine: underline ? 'underline' : 'none',
28
+ },
29
+ style,
30
+ ]}
31
+ {...rest}
32
+ >
33
+ {children}
34
+ </Text>
35
+ );
36
+ };
37
+
38
+ export default withTheme(Anchor);
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react-native';
3
+
4
+ import { Text } from '../..';
5
+
6
+ const mockOpenUrl = jest.fn(url => Promise.resolve(url));
7
+
8
+ describe('<Text />', () => {
9
+ beforeEach(() => {
10
+ mockOpenUrl.mockClear();
11
+
12
+ jest.doMock('react-native/Libraries/Linking/Linking', () => ({
13
+ openURL: mockOpenUrl,
14
+ }));
15
+ });
16
+
17
+ it('should render children', () => {
18
+ const { getByText } = render(<Text>Potato</Text>);
19
+
20
+ expect(getByText('Potato')).toBeTruthy();
21
+ });
22
+
23
+ it('should render custom styles', () => {
24
+ const style = { color: 'papayawhip' };
25
+
26
+ const { getByText } = render(<Text style={style}>Potato</Text>);
27
+
28
+ expect(getByText('Potato')).toHaveStyle(style);
29
+ });
30
+ });