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,284 @@
1
+ import React, { useRef } from 'react';
2
+ import {
3
+ StyleSheet,
4
+ View,
5
+ ScrollView as RNScrollView,
6
+ ViewStyle,
7
+ StyleProp,
8
+ ImageBackground,
9
+ PanResponder,
10
+ TouchableWithoutFeedback,
11
+ } from 'react-native';
12
+ import type {
13
+ LayoutChangeEvent,
14
+ NativeScrollEvent,
15
+ NativeSyntheticEvent,
16
+ } from 'react-native';
17
+ import useAsyncReference from '../../hooks/useAsyncReference';
18
+
19
+ import type { Theme } from '../../types';
20
+ import { withTheme } from '../../core/theming';
21
+
22
+ import { Panel, Button, ArrowIcon } from '../..';
23
+
24
+ type Direction = -1 | 1;
25
+
26
+ type ScrollViewProps = React.ComponentProps<typeof View> & {
27
+ alwaysShowScrollbars?: boolean;
28
+ children: React.ReactNode;
29
+ horizontal?: boolean;
30
+ small?: boolean;
31
+ scrollViewProps?: React.ComponentProps<typeof RNScrollView>;
32
+ style?: StyleProp<ViewStyle>;
33
+ theme: Theme;
34
+ };
35
+
36
+ // TODO: performance improvements (callbacks, refs ...etc)
37
+ // TODO: disable scroll buttons when scroll position reached min or max
38
+ // TODO: add 'scrollIncrement' prop (granularity of scroll buttons)
39
+ const ScrollView = ({
40
+ alwaysShowScrollbars = false,
41
+ children,
42
+ horizontal = false,
43
+ small = false,
44
+ scrollViewProps = {},
45
+ style,
46
+ theme,
47
+ ...rest
48
+ }: ScrollViewProps) => {
49
+ const scrollViewRef = useRef<RNScrollView>(null);
50
+
51
+ const [contentOffset, setContentOffset] = useAsyncReference(0);
52
+ const [contentSize, setContentSize] = useAsyncReference(0);
53
+ const [scrollViewSize, setScrollViewSize] = useAsyncReference(0);
54
+
55
+ const scrollbarThickness = small ? 26 : 30;
56
+ const scrollbarButtonSize = scrollbarThickness;
57
+ const scrollbarAxis = horizontal ? 'x' : 'y';
58
+ const scrollbarLengthDimension = horizontal ? 'width' : 'height';
59
+ const scrollbarThicknessDimension = horizontal ? 'height' : 'width';
60
+
61
+ const contentFullyVisible = contentSize.current <= scrollViewSize.current;
62
+
63
+ const visibleContentRatio = scrollViewSize.current / contentSize.current;
64
+
65
+ const scrolledContentRatio = contentOffset.current / contentSize.current;
66
+
67
+ const thumbPosition =
68
+ Math.max(
69
+ 0,
70
+ Math.min(
71
+ 1 - visibleContentRatio,
72
+ parseFloat(scrolledContentRatio.toFixed(3)),
73
+ ),
74
+ ) * 100;
75
+
76
+ // CALLBACKS
77
+
78
+ const scrollTo = (distance: number, animated = false) => {
79
+ if (scrollViewRef.current) {
80
+ scrollViewRef.current.scrollTo({
81
+ [scrollbarAxis]: distance,
82
+ animated,
83
+ });
84
+ }
85
+ };
86
+
87
+ const handleScrollButtonPress = (direction: Direction) => {
88
+ scrollTo(contentOffset.current + 24 * direction, true);
89
+ };
90
+
91
+ const handleTrackPress = (direction: Direction) => {
92
+ scrollTo(contentOffset.current + scrollViewSize.current * direction);
93
+ };
94
+
95
+ const handleScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
96
+ scrollViewProps.onScroll?.(e);
97
+ setContentOffset(e.nativeEvent.contentOffset[scrollbarAxis]);
98
+ };
99
+
100
+ const handleContentSizeChange = (width: number, height: number) => {
101
+ scrollViewProps.onContentSizeChange?.(width, height);
102
+ setContentSize(horizontal ? width : height);
103
+ };
104
+
105
+ const handleLayout = (e: LayoutChangeEvent) => {
106
+ scrollViewProps.onLayout?.(e);
107
+ setScrollViewSize(e.nativeEvent.layout[scrollbarLengthDimension]);
108
+ };
109
+
110
+ const dragStartScrollPositionRef = useRef(0);
111
+
112
+ const panResponder = useRef(
113
+ PanResponder.create({
114
+ onStartShouldSetPanResponder: () => true,
115
+ onStartShouldSetPanResponderCapture: () => true,
116
+ onMoveShouldSetPanResponder: () => true,
117
+ onMoveShouldSetPanResponderCapture: () => true,
118
+
119
+ onPanResponderGrant: () => {
120
+ dragStartScrollPositionRef.current = contentOffset.current;
121
+ },
122
+ onPanResponderMove: (_evt, gestureState) => {
123
+ const scrollTrackLength =
124
+ scrollViewSize.current - 2 * scrollbarButtonSize;
125
+ const scrollDistanceChange =
126
+ gestureState[horizontal ? 'dx' : 'dy'] / scrollTrackLength;
127
+
128
+ const translatedDistance = scrollDistanceChange * contentSize.current;
129
+ scrollTo(dragStartScrollPositionRef.current + translatedDistance);
130
+ },
131
+ onPanResponderTerminationRequest: () => true,
132
+ }),
133
+ ).current;
134
+
135
+ return (
136
+ <View
137
+ style={[
138
+ styles.wrapper,
139
+ {
140
+ flexDirection: horizontal ? 'column' : 'row',
141
+ },
142
+ style,
143
+ ]}
144
+ {...rest}
145
+ >
146
+ <View style={[styles.content]}>
147
+ <RNScrollView
148
+ {...scrollViewProps}
149
+ showsVerticalScrollIndicator={false}
150
+ showsHorizontalScrollIndicator={false}
151
+ scrollEventThrottle={10}
152
+ ref={scrollViewRef}
153
+ onScroll={handleScroll}
154
+ onContentSizeChange={handleContentSizeChange}
155
+ onLayout={handleLayout}
156
+ horizontal={horizontal}
157
+ >
158
+ {children}
159
+ </RNScrollView>
160
+ </View>
161
+ {(!contentFullyVisible || alwaysShowScrollbars) && (
162
+ <View
163
+ accessibilityRole='scrollbar'
164
+ style={[
165
+ {
166
+ flexDirection: horizontal ? 'row' : 'column',
167
+ [scrollbarLengthDimension]: '100%',
168
+ [scrollbarThicknessDimension]: scrollbarThickness,
169
+ backgroundColor: theme.material,
170
+ },
171
+ ]}
172
+ >
173
+ <ImageBackground
174
+ style={[styles.background]}
175
+ imageStyle={{
176
+ resizeMode: 'repeat',
177
+ }}
178
+ source={{
179
+ // TODO: create util function for generating checkered background
180
+ uri:
181
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAIUlEQVQoU2P8////fwYkwMjIyIjCp4MCZPtAbAwraa8AAEGrH/nfAIhgAAAAAElFTkSuQmCC',
182
+ }}
183
+ />
184
+ <Button
185
+ theme={theme}
186
+ variant='raised'
187
+ onPress={() => handleScrollButtonPress(-1)}
188
+ disabled={contentFullyVisible}
189
+ style={{
190
+ height: scrollbarButtonSize,
191
+ width: scrollbarButtonSize,
192
+ padding: 0,
193
+ }}
194
+ >
195
+ <ArrowIcon
196
+ theme={theme}
197
+ direction={horizontal ? 'left' : 'up'}
198
+ disabled={contentFullyVisible}
199
+ segments={small ? 3 : 4}
200
+ />
201
+ </Button>
202
+ <View style={[styles.scrollbarTrack]}>
203
+ {!contentFullyVisible && (
204
+ <View
205
+ style={{
206
+ flex: 1,
207
+ flexDirection: horizontal ? 'row' : 'column',
208
+ }}
209
+ >
210
+ <TouchableWithoutFeedback
211
+ onPressIn={() => handleTrackPress(-1)}
212
+ >
213
+ <View
214
+ style={{
215
+ [scrollbarLengthDimension]: `${thumbPosition}%`,
216
+ }}
217
+ />
218
+ </TouchableWithoutFeedback>
219
+ {/* SCROLLBAR THUMB */}
220
+ <Panel
221
+ theme={theme}
222
+ variant='raised'
223
+ style={{
224
+ [scrollbarLengthDimension]: `${visibleContentRatio * 100}%`,
225
+ }}
226
+ {...panResponder.panHandlers}
227
+ />
228
+ <TouchableWithoutFeedback onPressIn={() => handleTrackPress(1)}>
229
+ <View
230
+ style={{
231
+ flex: 1,
232
+ }}
233
+ />
234
+ </TouchableWithoutFeedback>
235
+ </View>
236
+ )}
237
+ </View>
238
+ <Button
239
+ theme={theme}
240
+ variant='raised'
241
+ onPress={() => handleScrollButtonPress(1)}
242
+ disabled={contentFullyVisible}
243
+ style={{
244
+ height: scrollbarButtonSize,
245
+ width: scrollbarButtonSize,
246
+ padding: 0,
247
+ }}
248
+ >
249
+ <ArrowIcon
250
+ theme={theme}
251
+ direction={horizontal ? 'right' : 'down'}
252
+ disabled={contentFullyVisible}
253
+ segments={small ? 3 : 4}
254
+ />
255
+ </Button>
256
+ </View>
257
+ )}
258
+ </View>
259
+ );
260
+ };
261
+
262
+ const styles = StyleSheet.create({
263
+ wrapper: {
264
+ display: 'flex',
265
+ position: 'relative',
266
+ },
267
+ content: {
268
+ flexGrow: 1,
269
+ flexShrink: 1,
270
+ },
271
+ scrollbarTrack: {
272
+ overflow: 'hidden',
273
+ flex: 1,
274
+ },
275
+ background: {
276
+ position: 'absolute',
277
+ top: 0,
278
+ right: 0,
279
+ bottom: 0,
280
+ left: 0,
281
+ },
282
+ });
283
+
284
+ export default withTheme(ScrollView);
@@ -0,0 +1 @@
1
+ export { default } from './ScrollView';
@@ -0,0 +1,229 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ StyleSheet,
4
+ View,
5
+ TouchableHighlight,
6
+ StyleProp,
7
+ ViewStyle,
8
+ } from 'react-native';
9
+
10
+ import type { Theme, AnyValue } from '../../types';
11
+ import { withTheme } from '../../core/theming';
12
+
13
+ import {
14
+ blockSizes,
15
+ buildBorderStyles,
16
+ builtTextStyles,
17
+ } from '../../styles/styles';
18
+ import shadow from '../../styles/shadow';
19
+
20
+ import getSelectOptions, { Option } from './SelectBase';
21
+ import { ScrollView, Text, ArrowIcon, Panel } from '../..';
22
+
23
+ type Props = {
24
+ disabled?: boolean;
25
+ menuMaxHeight?: number;
26
+ // TODO: what to put below?
27
+ onChange: (value: AnyValue) => void;
28
+ options: Array<Option>;
29
+ style?: StyleProp<ViewStyle>;
30
+ theme: Theme;
31
+ value: AnyValue;
32
+ };
33
+
34
+ const Select = ({
35
+ disabled = false,
36
+ menuMaxHeight,
37
+ onChange,
38
+ options = [],
39
+ style,
40
+ theme,
41
+ value,
42
+ ...rest
43
+ }: Props) => {
44
+ const [isOpen, setIsOpen] = useState(false);
45
+ const [isPressed, setIsPressed] = useState(false);
46
+
47
+ function handleOptionSelect(option: Option) {
48
+ onChange(option.value);
49
+ setIsOpen(false);
50
+ }
51
+
52
+ function getLabelContainerBackgroundColor() {
53
+ if (disabled) {
54
+ return theme.material;
55
+ }
56
+
57
+ if (isPressed) {
58
+ return theme.hoverBackground;
59
+ }
60
+
61
+ return theme.canvas;
62
+ }
63
+
64
+ const [selectedOptions, selectOptions] = getSelectOptions({
65
+ options,
66
+ values: [value],
67
+ onChange: handleOptionSelect,
68
+ theme,
69
+ });
70
+
71
+ const selectedOption = selectedOptions[0];
72
+ // TODO: close dropdown when user touches outside of component
73
+ // TODO: native prop to use native select
74
+
75
+ const borderStyles = buildBorderStyles(theme);
76
+ const textStyles = builtTextStyles(theme);
77
+
78
+ return (
79
+ <Panel
80
+ theme={theme}
81
+ variant='cutout'
82
+ background={disabled ? 'material' : 'canvas'}
83
+ style={[styles.wrapper, style]}
84
+ {...rest}
85
+ >
86
+ <TouchableHighlight
87
+ onPress={() => setIsOpen(currentIsOpen => !currentIsOpen)}
88
+ activeOpacity={1}
89
+ disabled={disabled}
90
+ onHideUnderlay={() => setIsPressed(false)}
91
+ onShowUnderlay={() => setIsPressed(true)}
92
+ // TODO: accessibility
93
+ // accessibilityTraits
94
+ // accessibilityComponentType
95
+ // accessibilityRole
96
+ // accessibilityState
97
+ underlayColor='none'
98
+ >
99
+ <View style={[styles.inner]}>
100
+ <View style={[styles.flex]}>
101
+ <View
102
+ style={[
103
+ styles.selectValue,
104
+ { backgroundColor: disabled ? theme.material : theme.canvas },
105
+ ]}
106
+ >
107
+ <View
108
+ style={[
109
+ styles.center,
110
+ {
111
+ borderWidth: 2,
112
+ borderColor: disabled ? theme.material : theme.canvas,
113
+ backgroundColor: getLabelContainerBackgroundColor(),
114
+ },
115
+
116
+ isPressed && borderStyles.focusSecondaryOutline,
117
+ ]}
118
+ >
119
+ <Text
120
+ theme={theme}
121
+ style={[
122
+ styles.textValue,
123
+ disabled ? textStyles.disabled : textStyles.default,
124
+ !disabled &&
125
+ isPressed && {
126
+ color: isPressed
127
+ ? theme.canvasTextInvert
128
+ : theme.canvasText,
129
+ },
130
+ ]}
131
+ >
132
+ {selectedOption.label}
133
+ </Text>
134
+ </View>
135
+ </View>
136
+ <Panel
137
+ theme={theme}
138
+ variant={isPressed ? 'default' : 'raised'}
139
+ invert={isPressed}
140
+ style={[styles.fakeButton]}
141
+ >
142
+ <ArrowIcon
143
+ theme={theme}
144
+ segments={4}
145
+ direction='down'
146
+ disabled={disabled}
147
+ style={{
148
+ paddingTop: isPressed ? 2 : 0,
149
+ }}
150
+ />
151
+ </Panel>
152
+ </View>
153
+ {isOpen && (
154
+ <View
155
+ style={[
156
+ styles.options,
157
+ {
158
+ height: menuMaxHeight || 'auto',
159
+ backgroundColor: theme.canvas,
160
+ borderColor: theme.borderDarkest,
161
+ },
162
+ ]}
163
+ >
164
+ <ScrollView theme={theme}>{selectOptions}</ScrollView>
165
+ </View>
166
+ )}
167
+ </View>
168
+ </TouchableHighlight>
169
+ </Panel>
170
+ );
171
+ };
172
+
173
+ const selectHeight = blockSizes.md;
174
+
175
+ const styles = StyleSheet.create({
176
+ wrapper: {
177
+ height: selectHeight,
178
+ alignSelf: 'flex-start',
179
+ padding: 0,
180
+ },
181
+ inner: {
182
+ position: 'relative',
183
+ padding: 4,
184
+ },
185
+ flex: {
186
+ display: 'flex',
187
+ flexDirection: 'row',
188
+ justifyContent: 'space-between',
189
+ alignContent: 'center',
190
+ alignItems: 'center',
191
+ height: '100%',
192
+ },
193
+ selectValue: {
194
+ flexGrow: 1,
195
+ flex: 1,
196
+ height: '100%',
197
+ padding: 2,
198
+ },
199
+ center: {
200
+ flexGrow: 1,
201
+ flex: 1,
202
+ height: '100%',
203
+ justifyContent: 'center',
204
+ },
205
+ textValue: {
206
+ fontSize: 16,
207
+ paddingHorizontal: 4,
208
+ },
209
+ fakeButton: {
210
+ position: 'relative',
211
+ height: '100%',
212
+ width: 30,
213
+ padding: 4,
214
+ alignItems: 'center',
215
+ justifyContent: 'center',
216
+ },
217
+ options: {
218
+ position: 'absolute',
219
+ top: selectHeight,
220
+ left: 0,
221
+ right: 0,
222
+ borderWidth: 2,
223
+ padding: 2,
224
+ display: 'flex',
225
+ ...shadow(2),
226
+ },
227
+ });
228
+
229
+ export default withTheme(Select);
@@ -0,0 +1,119 @@
1
+ import React, { useState } from 'react';
2
+ import { StyleSheet, View, TouchableHighlight } from 'react-native';
3
+
4
+ import type { Theme, AnyValue } from '../../types';
5
+ import { withTheme } from '../../core/theming';
6
+
7
+ import { blockSizes } from '../../styles/styles';
8
+
9
+ import { Text } from '../..';
10
+ // TODO: allow for no option selected
11
+ export type Option = {
12
+ label: React.ReactNode;
13
+ value: AnyValue;
14
+ };
15
+
16
+ type SelectItemProps = {
17
+ isSelected: boolean;
18
+ onPress: (option: Option) => void;
19
+ option: Option;
20
+ theme: Theme;
21
+ };
22
+
23
+ const SelectItem = ({
24
+ isSelected,
25
+ onPress,
26
+ option,
27
+ theme,
28
+ }: SelectItemProps) => {
29
+ const [isPressed, setIsPressed] = useState(false);
30
+
31
+ return (
32
+ <TouchableHighlight
33
+ onPress={() => onPress(option)}
34
+ onHideUnderlay={() => setIsPressed(false)}
35
+ onShowUnderlay={() => setIsPressed(true)}
36
+ underlayColor='none'
37
+ // delay to prevent item highlighting on scroll
38
+ delayPressIn={70}
39
+ activeOpacity={1}
40
+ >
41
+ <View
42
+ style={[
43
+ styles.center,
44
+ styles.optionWrapper,
45
+ {
46
+ borderColor: isPressed ? theme.focusSecondary : 'transparent',
47
+ backgroundColor:
48
+ isPressed || isSelected ? theme.hoverBackground : theme.canvas,
49
+ },
50
+ ]}
51
+ >
52
+ <Text
53
+ style={[
54
+ styles.optionText,
55
+ {
56
+ color:
57
+ isPressed || isSelected
58
+ ? theme.canvasTextInvert
59
+ : theme.canvasText,
60
+ },
61
+ ]}
62
+ >
63
+ {option.label}
64
+ </Text>
65
+ </View>
66
+ </TouchableHighlight>
67
+ );
68
+ };
69
+
70
+ const SelectItemWithTheme = withTheme(SelectItem);
71
+
72
+ const selectHeight = blockSizes.md + 2;
73
+
74
+ const styles = StyleSheet.create({
75
+ center: {
76
+ flexGrow: 1,
77
+ flex: 1,
78
+ height: '100%',
79
+ justifyContent: 'center',
80
+ },
81
+ optionWrapper: {
82
+ height: selectHeight - 4,
83
+ borderWidth: 2,
84
+ borderStyle: 'dotted',
85
+ },
86
+ optionText: {
87
+ fontSize: 16,
88
+ paddingLeft: 6,
89
+ },
90
+ });
91
+
92
+ type SelectOptionsProps = {
93
+ options: Array<Option>;
94
+ values: [AnyValue];
95
+ onChange: (option: Option) => void;
96
+ theme: Theme;
97
+ };
98
+
99
+ export default function getSelectOptions({
100
+ options,
101
+ values,
102
+ onChange,
103
+ theme,
104
+ }: SelectOptionsProps): [Option[], JSX.Element[]] {
105
+ const selectedOptions = options.filter(option =>
106
+ values.includes(option.value),
107
+ );
108
+
109
+ const optionItems = options.map(option => (
110
+ <SelectItemWithTheme
111
+ theme={theme}
112
+ key={option.value}
113
+ option={option}
114
+ isSelected={selectedOptions.includes(option)}
115
+ onPress={onChange}
116
+ />
117
+ ));
118
+ return [selectedOptions, optionItems];
119
+ }
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
3
+
4
+ import type { Theme, AnyValue } from '../../types';
5
+ import { withTheme } from '../../core/theming';
6
+
7
+ import getSelectOptions, { Option } from './SelectBase';
8
+ import { ScrollView, Panel } from '../..';
9
+
10
+ // TODO: multiselect
11
+
12
+ type Props = {
13
+ onChange: (value: AnyValue) => void;
14
+ options: Array<Option>;
15
+ style?: StyleProp<ViewStyle>;
16
+ theme: Theme;
17
+ value: [AnyValue] | AnyValue;
18
+ };
19
+
20
+ const SelectBox = ({
21
+ onChange,
22
+ options = [],
23
+ style,
24
+ theme,
25
+ value,
26
+ ...rest
27
+ }: Props) => {
28
+ const [, selectOptions] = getSelectOptions({
29
+ theme,
30
+ options,
31
+ values: [value],
32
+ onChange: handleOptionSelect,
33
+ });
34
+
35
+ function handleOptionSelect(option: Option) {
36
+ onChange(option.value);
37
+ }
38
+
39
+ return (
40
+ <Panel
41
+ theme={theme}
42
+ variant='cutout'
43
+ background='canvas'
44
+ style={[styles.wrapper, style]}
45
+ {...rest}
46
+ >
47
+ <ScrollView theme={theme}>
48
+ <View style={[styles.content, { backgroundColor: theme.canvas }]}>
49
+ {selectOptions}
50
+ </View>
51
+ </ScrollView>
52
+ </Panel>
53
+ );
54
+ };
55
+
56
+ export default withTheme(SelectBox);
57
+
58
+ const styles = StyleSheet.create({
59
+ wrapper: {
60
+ paddingVertical: 4,
61
+ paddingHorizontal: 4,
62
+ },
63
+ content: {
64
+ padding: 2,
65
+ },
66
+ });
@@ -0,0 +1,2 @@
1
+ export { default as Select } from './Select';
2
+ export { default as SelectBox } from './SelectBox';