@wq/material-native 2.0.0-alpha.0 → 2.0.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.
Files changed (40) hide show
  1. package/babel.config.cjs +1 -1
  2. package/jest.config.js +1 -5
  3. package/package.json +16 -17
  4. package/src/App.js +23 -5
  5. package/src/components/{ExpansionPanel.js → Accordion.js} +2 -2
  6. package/src/components/BottomNavigation.js +3 -0
  7. package/src/components/BottomNavigationAction.js +3 -0
  8. package/src/components/CheckboxButton.js +25 -2
  9. package/src/components/Fab.js +0 -1
  10. package/src/components/IconButton.js +1 -1
  11. package/src/components/Img.js +102 -0
  12. package/src/components/ListItem.js +17 -1
  13. package/src/components/NavMenuFixed.js +3 -0
  14. package/src/components/NavMenuPopup.js +3 -0
  15. package/src/components/Popup.js +18 -2
  16. package/src/components/RadioButton.js +25 -2
  17. package/src/components/SidePanel.js +93 -2
  18. package/src/components/Switch.js +17 -2
  19. package/src/components/TabGroup.js +38 -0
  20. package/src/components/TabItem.js +16 -0
  21. package/src/components/TableCell.js +23 -1
  22. package/src/components/index.js +17 -10
  23. package/src/icons.js +16 -1
  24. package/src/inputs/Checkbox.js +22 -13
  25. package/src/inputs/DateTime.js +8 -5
  26. package/src/inputs/File.js +175 -2
  27. package/src/inputs/FileArray.js +12 -0
  28. package/src/inputs/HelperText.js +28 -2
  29. package/src/inputs/Hidden.js +5 -4
  30. package/src/inputs/Image.js +5 -2
  31. package/src/inputs/Input.js +17 -11
  32. package/src/inputs/Radio.js +31 -2
  33. package/src/inputs/Select.js +22 -16
  34. package/src/inputs/Toggle.js +37 -2
  35. package/src/inputs/index.js +8 -0
  36. package/setup-jest.js +0 -5
  37. package/src/components/FileArray.js +0 -3
  38. /package/src/{components → inputs}/Fieldset.js +0 -0
  39. /package/src/{components → inputs}/FieldsetArray.js +0 -0
  40. /package/src/{components → inputs}/FlatFieldset.js +0 -0
@@ -1,5 +1,7 @@
1
1
  import Container from "./Container.js";
2
2
  import Header from "./Header.js";
3
+ import NavMenuPopup from "./NavMenuPopup.js";
4
+ import NavMenuFixed from "./NavMenuFixed.js";
3
5
  import Main from "./Main.js";
4
6
  import Footer from "./Footer.js";
5
7
  import FooterContent from "./FooterContent.js";
@@ -7,6 +9,7 @@ import View from "./View.js";
7
9
  import ScrollView from "./ScrollView.js";
8
10
  import HorizontalView from "./HorizontalView.js";
9
11
  import Text from "./Text.js";
12
+ import Img from "./Img.js";
10
13
  import Typography from "./Typography.js";
11
14
  import FormatJson from "./FormatJson.js";
12
15
 
@@ -16,6 +19,10 @@ import ButtonLink from "./ButtonLink.js";
16
19
  import HomeLink from "./HomeLink.js";
17
20
  import IconButton from "./IconButton.js";
18
21
  import Fab from "./Fab.js";
22
+ import TabGroup from "./TabGroup.js";
23
+ import TabItem from "./TabItem.js";
24
+ import BottomNavigation from "./BottomNavigation.js";
25
+ import BottomNavigationAction from "./BottomNavigationAction.js";
19
26
  import Chip from "./Chip.js";
20
27
  import Switch from "./Switch.js";
21
28
  import CheckboxButton from "./CheckboxButton.js";
@@ -25,7 +32,7 @@ import List from "./List.js";
25
32
  import ListItem from "./ListItem.js";
26
33
  import ListItemLink from "./ListItemLink.js";
27
34
  import ListSubheader from "./ListSubheader.js";
28
- import ExpansionPanel from "./ExpansionPanel.js";
35
+ import Accordion from "./Accordion.js";
29
36
  import ExpandableListItem from "./ExpandableListItem.js";
30
37
  import Divider from "./Divider.js";
31
38
 
@@ -40,10 +47,6 @@ import TablePagination from "./TablePagination.js";
40
47
 
41
48
  import FormRoot from "./FormRoot.js";
42
49
  import FormError from "./FormError.js";
43
- import Fieldset from "./Fieldset.js";
44
- import FlatFieldset from "./FlatFieldset.js";
45
- import FieldsetArray from "./FieldsetArray.js";
46
- import FileArray from "./FileArray.js";
47
50
  import CancelButton from "./CancelButton.js";
48
51
  import SubmitButton from "./SubmitButton.js";
49
52
  import IconSubmitButton from "./IconSubmitButton.js";
@@ -58,6 +61,8 @@ import Pagination from "./Pagination.js";
58
61
  export {
59
62
  Container,
60
63
  Header,
64
+ NavMenuPopup,
65
+ NavMenuFixed,
61
66
  Main,
62
67
  Footer,
63
68
  FooterContent,
@@ -65,6 +70,7 @@ export {
65
70
  ScrollView,
66
71
  HorizontalView,
67
72
  Text,
73
+ Img,
68
74
  Typography,
69
75
  FormatJson,
70
76
  };
@@ -76,6 +82,10 @@ export {
76
82
  HomeLink,
77
83
  IconButton,
78
84
  Fab,
85
+ TabGroup,
86
+ TabItem,
87
+ BottomNavigation,
88
+ BottomNavigationAction,
79
89
  Chip,
80
90
  Switch,
81
91
  CheckboxButton,
@@ -87,7 +97,8 @@ export {
87
97
  ListItem,
88
98
  ListItemLink,
89
99
  ListSubheader,
90
- ExpansionPanel,
100
+ Accordion,
101
+ Accordion as ExpansionPanel,
91
102
  ExpandableListItem,
92
103
  Divider,
93
104
  };
@@ -106,10 +117,6 @@ export {
106
117
  export {
107
118
  FormRoot,
108
119
  FormError,
109
- Fieldset,
110
- FlatFieldset,
111
- FieldsetArray,
112
- FileArray,
113
120
  CancelButton,
114
121
  SubmitButton,
115
122
  IconSubmitButton,
package/src/icons.js CHANGED
@@ -4,6 +4,21 @@ export const Add = "plus",
4
4
  Success = "check",
5
5
  Error = "alert-circle",
6
6
  Pending = "sync",
7
+ Close = "close",
8
+ Expand = "chevron-down",
9
+ Collapse = "chevron-up",
10
+ PanelOpen = "chevron-right",
11
+ PanelClose = "chevron-left",
7
12
  GpsStart = "crosshairs-gps",
8
13
  GpsStop = "crosshairs-off",
9
- Search = "magnify";
14
+ Search = "magnify",
15
+ List = "view-list",
16
+ Detail = "information",
17
+ Map = "map",
18
+ Home = "home",
19
+ BreadcrumbSeparator = "chevron-right",
20
+ Menu = "menu",
21
+ Login = "login",
22
+ Logout = "logout",
23
+ Config = "cog",
24
+ External = "open-in-new";
@@ -2,9 +2,10 @@ import React from "react";
2
2
  import { useField } from "formik";
3
3
  import { Checkbox as PaperCheckbox, useTheme } from "react-native-paper";
4
4
  import { Text, View } from "react-native";
5
+ import HelperText from "./HelperText.js";
5
6
  import PropTypes from "prop-types";
6
7
 
7
- export default function Checkbox({ name, label }) {
8
+ export default function Checkbox({ name, label, hint }) {
8
9
  const theme = useTheme(),
9
10
  [, meta, helpers] = useField(name),
10
11
  { value } = meta,
@@ -15,19 +16,26 @@ export default function Checkbox({ name, label }) {
15
16
  }
16
17
 
17
18
  return (
18
- <View
19
- style={{ flexDirection: "row", alignItems: "center", padding: 8 }}
20
- >
21
- <PaperCheckbox.Android
22
- status={value ? "checked" : "unchecked"}
23
- onPress={toggleChecked}
24
- />
25
- <Text
26
- style={{ color: theme.colors.text, fontSize: 16, flex: 1 }}
27
- onPress={toggleChecked}
19
+ <View>
20
+ <View
21
+ style={{
22
+ flexDirection: "row",
23
+ alignItems: "center",
24
+ padding: 8,
25
+ }}
28
26
  >
29
- {label}
30
- </Text>
27
+ <PaperCheckbox.Android
28
+ status={value ? "checked" : "unchecked"}
29
+ onPress={toggleChecked}
30
+ />
31
+ <Text
32
+ style={{ color: theme.colors.text, fontSize: 16, flex: 1 }}
33
+ onPress={toggleChecked}
34
+ >
35
+ {label}
36
+ </Text>
37
+ </View>
38
+ <HelperText name={name} hint={hint} />
31
39
  </View>
32
40
  );
33
41
  }
@@ -35,4 +43,5 @@ export default function Checkbox({ name, label }) {
35
43
  Checkbox.propTypes = {
36
44
  name: PropTypes.string,
37
45
  label: PropTypes.string,
46
+ hint: PropTypes.string,
38
47
  };
@@ -1,10 +1,11 @@
1
1
  import React, { useState } from "react";
2
2
  import DateTimePickerModal from "react-native-modal-datetime-picker";
3
- import { Text } from "react-native";
3
+ import { Text, View } from "react-native";
4
4
  import { TouchableRipple, TextInput } from "react-native-paper";
5
5
  import { useField } from "formik";
6
- import PropTypes from "prop-types";
7
6
  import { format, parse } from "./date-utils.js";
7
+ import HelperText from "./HelperText.js";
8
+ import PropTypes from "prop-types";
8
9
 
9
10
  const displayFormat = {
10
11
  date: (value) => parse.date(value).toLocaleDateString(),
@@ -12,7 +13,7 @@ const displayFormat = {
12
13
  datetime: (value) => parse.datetime(value).toLocaleString(),
13
14
  };
14
15
 
15
- export default function DateTime({ name, type, label }) {
16
+ export default function DateTime({ name, type, label, hint }) {
16
17
  type = type.toLowerCase();
17
18
 
18
19
  const [, meta, helpers] = useField(name),
@@ -33,7 +34,7 @@ export default function DateTime({ name, type, label }) {
33
34
  }
34
35
 
35
36
  return (
36
- <>
37
+ <View>
37
38
  <TextInput
38
39
  label={label}
39
40
  value={value ? displayFormat[type](value) : ""}
@@ -43,6 +44,7 @@ export default function DateTime({ name, type, label }) {
43
44
  </TouchableRipple>
44
45
  )}
45
46
  />
47
+ <HelperText name={name} hint={hint} />
46
48
  <DateTimePickerModal
47
49
  isVisible={show}
48
50
  mode={type}
@@ -50,7 +52,7 @@ export default function DateTime({ name, type, label }) {
50
52
  onConfirm={onConfirm}
51
53
  onCancel={hidePicker}
52
54
  />
53
- </>
55
+ </View>
54
56
  );
55
57
  }
56
58
 
@@ -58,4 +60,5 @@ DateTime.propTypes = {
58
60
  name: PropTypes.string,
59
61
  type: PropTypes.string,
60
62
  label: PropTypes.string,
63
+ hint: PropTypes.string,
61
64
  };
@@ -1,3 +1,176 @@
1
- export default function NotImplemented() {
2
- return null; // FIXME
1
+ import React, { useState } from "react";
2
+ import { useField } from "formik";
3
+ import { useComponents } from "@wq/react";
4
+ import { View, Text } from "react-native";
5
+ import { Button, List, Menu } from "react-native-paper";
6
+ import * as Application from "expo-application";
7
+ import * as DocumentPicker from "expo-document-picker";
8
+ import * as ImagePicker from "expo-image-picker";
9
+ import { format } from "./date-utils.js";
10
+ import HelperText from "./HelperText.js";
11
+ import PropTypes from "prop-types";
12
+
13
+ export default function File({ name, accept, capture, required, label, hint }) {
14
+ const [, { value }, { setValue }] = useField(name),
15
+ [imageOpen, setImageOpen] = useState(false),
16
+ [menuOpen, setMenuOpen] = useState(false),
17
+ { Img, Popup } = useComponents(),
18
+ acceptImage = accept && accept.startsWith("image/"),
19
+ showImage = () => setImageOpen(true),
20
+ hideImage = () => setImageOpen(false),
21
+ showMenu = () => setMenuOpen(true),
22
+ hideMenu = () => setMenuOpen(false);
23
+
24
+ async function handleFile(mode) {
25
+ const value = await mode(accept, capture);
26
+ if (value || mode === clear) {
27
+ setValue(value);
28
+ }
29
+ hideMenu();
30
+ }
31
+
32
+ const modes = capture
33
+ ? [takePhoto]
34
+ : acceptImage
35
+ ? [takePhoto, pickFile]
36
+ : [pickFile];
37
+
38
+ if (value && !required) {
39
+ modes.push(clear);
40
+ }
41
+
42
+ return (
43
+ <View>
44
+ {label && <List.Subheader>{label}</List.Subheader>}
45
+ <View
46
+ style={{
47
+ flexDirection: "row",
48
+ alignItems: "center",
49
+ padding: 8,
50
+ }}
51
+ >
52
+ {isImage(value) && (
53
+ <Img
54
+ onPress={showImage}
55
+ style={{ width: 64, height: 64 }}
56
+ src={typeof value === "string" ? value : value.uri}
57
+ />
58
+ )}
59
+ {value && !isImage(value) && <List.Icon icon="attachment" />}
60
+ <Text style={{ flex: 1, padding: 8 }}>
61
+ {value ? value.name : "No file selected"}
62
+ </Text>
63
+ <Menu
64
+ visible={menuOpen}
65
+ onDismiss={hideMenu}
66
+ anchor={
67
+ <Button
68
+ onPress={
69
+ modes.length > 1
70
+ ? showMenu
71
+ : () => handleFile(modes[0])
72
+ }
73
+ icon={value ? "pencil" : "plus"}
74
+ mode="contained-tonal"
75
+ >
76
+ {value
77
+ ? "Change"
78
+ : capture
79
+ ? "Add Photo"
80
+ : acceptImage
81
+ ? "Add Image"
82
+ : "Add File"}
83
+ </Button>
84
+ }
85
+ >
86
+ {modes.map((mode) => (
87
+ <Menu.Item
88
+ key={mode.icon}
89
+ onPress={() => handleFile(mode)}
90
+ leadingIcon={mode.icon}
91
+ title={mode.label}
92
+ />
93
+ ))}
94
+ </Menu>
95
+ </View>
96
+ <HelperText name={name} hint={hint} />
97
+ <Popup open={value && imageOpen} onClose={hideImage}>
98
+ <Img
99
+ onPress={hideImage}
100
+ resizeMode="contain"
101
+ style={{
102
+ width: "90%",
103
+ height: "90%",
104
+ margin: "5%",
105
+ }}
106
+ src={typeof value === "string" ? value : value && value.uri}
107
+ />
108
+ </Popup>
109
+ </View>
110
+ );
3
111
  }
112
+ File.propTypes = {
113
+ name: PropTypes.string,
114
+ accept: PropTypes.string,
115
+ capture: PropTypes.string,
116
+ required: PropTypes.bool,
117
+ label: PropTypes.string,
118
+ hint: PropTypes.string,
119
+ };
120
+
121
+ function isImage(value) {
122
+ if (typeof value === "string") {
123
+ return IMAGE_EXTENSIONS.some((ext) =>
124
+ value.toLowerCase().endsWith(ext)
125
+ );
126
+ } else {
127
+ return (
128
+ value && value.type && value.type.startsWith("image/") && value.uri
129
+ );
130
+ }
131
+ }
132
+
133
+ function generateName() {
134
+ const appName = Application.applicationName.replace(/ /, ""),
135
+ timestamp = new Date(),
136
+ date = format.date(timestamp).replace(/-/g, ""),
137
+ time = format.time(timestamp).replace(/:/g, "");
138
+ return `${appName}_${date}_${time}.jpg`;
139
+ }
140
+
141
+ async function pickFile(accept) {
142
+ const result = await DocumentPicker.getDocumentAsync({ type: accept });
143
+ if (result.assets && result.assets.length > 0) {
144
+ const { name, mimeType: type, uri } = result.assets[0];
145
+ return { name, type, uri };
146
+ }
147
+ }
148
+ pickFile.icon = "folder";
149
+ pickFile.label = "Pick File";
150
+
151
+ async function takePhoto(accept, capture) {
152
+ const options = {};
153
+ if (capture) {
154
+ options.cameraType =
155
+ capture === "user"
156
+ ? ImagePicker.CameraType.front
157
+ : ImagePicker.CameraType.back;
158
+ }
159
+ const result = await ImagePicker.launchCameraAsync(options);
160
+ if (result.assets && result.assets.length > 0) {
161
+ const name = generateName(),
162
+ type = "image/jpeg",
163
+ { uri } = result.assets[0];
164
+ return { name, type, uri };
165
+ }
166
+ }
167
+ takePhoto.icon = "camera";
168
+ takePhoto.label = "Take Photo";
169
+
170
+ function clear() {
171
+ return null;
172
+ }
173
+ clear.icon = "delete";
174
+ clear.label = "Remove";
175
+
176
+ const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif"];
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import { List } from "react-native-paper";
3
+ import FieldsetArray from "./FieldsetArray.js";
4
+
5
+ export default function FileArray(props) {
6
+ return (
7
+ <>
8
+ {props.label && <List.Subheader>{props.label}</List.Subheader>}
9
+ <FieldsetArray {...props} />
10
+ </>
11
+ );
12
+ }
@@ -1,3 +1,29 @@
1
- import { HelperText } from "react-native-paper";
1
+ import React from "react";
2
+ import { useFormikContext, getIn } from "formik";
3
+ import { HelperText as FormHelperText } from "react-native-paper";
4
+ import PropTypes from "prop-types";
2
5
 
3
- export default HelperText;
6
+ export default function HelperText({ name, hint }) {
7
+ const { errors, touched } = useFormikContext(),
8
+ error = getIn(errors, name),
9
+ showError = !!error && !!getIn(touched, name);
10
+
11
+ if (showError) {
12
+ hint = error;
13
+ }
14
+
15
+ if (!hint) {
16
+ return null;
17
+ }
18
+
19
+ return (
20
+ <FormHelperText type={showError ? "error" : "info"}>
21
+ {hint}
22
+ </FormHelperText>
23
+ );
24
+ }
25
+
26
+ HelperText.propTypes = {
27
+ name: PropTypes.string,
28
+ hint: PropTypes.string,
29
+ };
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
- import { Field, ErrorMessage } from "formik";
3
- import { HelperText } from "react-native-paper";
2
+ import { Field } from "formik";
3
+ import HelperText from "./HelperText.js";
4
4
  import PropTypes from "prop-types";
5
5
 
6
6
  function Empty() {
@@ -8,15 +8,16 @@ function Empty() {
8
8
  }
9
9
 
10
10
  export default function Hidden(props) {
11
- const { name } = props;
11
+ const { name, hint } = props;
12
12
  return (
13
13
  <>
14
14
  <Field {...props} component={Empty} />
15
- <ErrorMessage type="error" component={HelperText} name={name} />
15
+ <HelperText name={name} hint={hint} />
16
16
  </>
17
17
  );
18
18
  }
19
19
 
20
20
  Hidden.propTypes = {
21
21
  name: PropTypes.string,
22
+ hint: PropTypes.string,
22
23
  };
@@ -1,3 +1,6 @@
1
- export default function NotImplemented() {
2
- return null; // FIXME
1
+ import React from "react";
2
+ import File from "./File.js";
3
+
4
+ export default function Image(props) {
5
+ return <File accept="image/*" {...props} />;
3
6
  }
@@ -1,7 +1,9 @@
1
1
  import React from "react";
2
+ import { View } from "react-native";
2
3
  import { TextInput } from "react-native-paper";
3
4
  import { useField } from "formik";
4
5
  import { useHtmlInput } from "@wq/react";
6
+ import HelperText from "./HelperText.js";
5
7
  import PropTypes from "prop-types";
6
8
 
7
9
  const keyboards = {
@@ -12,7 +14,7 @@ const keyboards = {
12
14
  };
13
15
 
14
16
  export default function Input(props) {
15
- const { name, type, label, style, min, max } = props,
17
+ const { name, type, label, hint, style, min, max } = props,
16
18
  { maxLength } = useHtmlInput(props),
17
19
  [, meta, helpers] = useField(name),
18
20
  { value } = meta,
@@ -49,16 +51,19 @@ export default function Input(props) {
49
51
  }
50
52
 
51
53
  return (
52
- <TextInput
53
- label={label}
54
- multiline={type === "text"}
55
- keyboardType={keyboards[type] || "default"}
56
- maxLength={maxLength}
57
- onChangeText={handleChange}
58
- onBlur={() => setTouched(true)}
59
- value={formatValue}
60
- style={style}
61
- />
54
+ <View style={{ flex: (style || {}).flex }}>
55
+ <TextInput
56
+ label={label}
57
+ multiline={type === "text"}
58
+ keyboardType={keyboards[type] || "default"}
59
+ maxLength={maxLength}
60
+ onChangeText={handleChange}
61
+ onBlur={() => setTouched(true)}
62
+ value={formatValue}
63
+ style={style}
64
+ />
65
+ <HelperText name={name} hint={hint} />
66
+ </View>
62
67
  );
63
68
  }
64
69
 
@@ -66,6 +71,7 @@ Input.propTypes = {
66
71
  name: PropTypes.string,
67
72
  type: PropTypes.string,
68
73
  label: PropTypes.string,
74
+ hint: PropTypes.string,
69
75
  style: PropTypes.object,
70
76
  min: PropTypes.number,
71
77
  max: PropTypes.number,
@@ -1,3 +1,32 @@
1
- export default function NotImplemented() {
2
- return null; // FIXME
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import { RadioButton, List } from "react-native-paper";
4
+ import HelperText from "./HelperText.js";
5
+ import { useField } from "formik";
6
+ import PropTypes from "prop-types";
7
+
8
+ export default function Radio({ name, choices, label, hint }) {
9
+ const [, { value }, { setValue }] = useField(name);
10
+ return (
11
+ <View>
12
+ <List.Subheader>{label}</List.Subheader>
13
+ <RadioButton.Group onValueChange={setValue} value={value}>
14
+ {choices.map((choice) => (
15
+ <RadioButton.Item
16
+ key={choice.name}
17
+ value={choice.name}
18
+ label={choice.label}
19
+ />
20
+ ))}
21
+ </RadioButton.Group>
22
+ <HelperText name={name} hint={hint} />
23
+ </View>
24
+ );
3
25
  }
26
+
27
+ Radio.propTypes = {
28
+ name: PropTypes.string,
29
+ label: PropTypes.string,
30
+ hint: PropTypes.string,
31
+ choices: PropTypes.arrayOf(PropTypes.object),
32
+ };
@@ -1,10 +1,12 @@
1
1
  import React from "react";
2
2
  import RNPickerSelect from "react-native-picker-select";
3
+ import { View } from "react-native";
3
4
  import { TextInput, useTheme } from "react-native-paper";
4
5
  import { useField } from "formik";
6
+ import HelperText from "./HelperText.js";
5
7
  import PropTypes from "prop-types";
6
8
 
7
- export default function Select({ name, choices, label }) {
9
+ export default function Select({ name, choices, label, hint }) {
8
10
  const theme = useTheme(),
9
11
  [, meta, helpers] = useField(name),
10
12
  { value } = meta,
@@ -27,26 +29,30 @@ export default function Select({ name, choices, label }) {
27
29
  };
28
30
 
29
31
  return (
30
- <TextInput
31
- label={label}
32
- value={"-"}
33
- render={() => (
34
- <RNPickerSelect
35
- value={value}
36
- onValueChange={setValue}
37
- items={choices.map(({ name, label }) => ({
38
- value: name,
39
- label,
40
- }))}
41
- style={styles}
42
- />
43
- )}
44
- />
32
+ <View>
33
+ <TextInput
34
+ label={label}
35
+ value={"-"}
36
+ render={() => (
37
+ <RNPickerSelect
38
+ value={value}
39
+ onValueChange={setValue}
40
+ items={choices.map(({ name, label }) => ({
41
+ value: name,
42
+ label,
43
+ }))}
44
+ style={styles}
45
+ />
46
+ )}
47
+ />
48
+ <HelperText name={name} hint={hint} />
49
+ </View>
45
50
  );
46
51
  }
47
52
 
48
53
  Select.propTypes = {
49
54
  name: PropTypes.string,
50
55
  label: PropTypes.string,
56
+ hint: PropTypes.string,
51
57
  choices: PropTypes.arrayOf(PropTypes.object),
52
58
  };
@@ -1,3 +1,38 @@
1
- export default function NotImplemented() {
2
- return null; // FIXME
1
+ import React from "react";
2
+ import { View, Text } from "react-native";
3
+ import { ToggleButton, List } from "react-native-paper";
4
+ import HelperText from "./HelperText.js";
5
+ import { useField } from "formik";
6
+ import PropTypes from "prop-types";
7
+
8
+ export default function Toggle({ name, choices, label, hint, style }) {
9
+ const [, { value }, { setValue }] = useField(name);
10
+ return (
11
+ <View>
12
+ <List.Subheader>{label}</List.Subheader>
13
+ <ToggleButton.Row
14
+ onValueChange={setValue}
15
+ value={value}
16
+ style={{ paddingLeft: 16, paddingBottom: 8, ...style }}
17
+ >
18
+ {choices.map((choice) => (
19
+ <ToggleButton
20
+ key={choice.name}
21
+ value={choice.name}
22
+ icon={() => <Text>{choice.label}</Text>}
23
+ style={{ width: undefined, padding: 8 }}
24
+ />
25
+ ))}
26
+ </ToggleButton.Row>
27
+ <HelperText name={name} hint={hint} />
28
+ </View>
29
+ );
3
30
  }
31
+
32
+ Toggle.propTypes = {
33
+ name: PropTypes.string,
34
+ label: PropTypes.string,
35
+ hint: PropTypes.string,
36
+ choices: PropTypes.arrayOf(PropTypes.object),
37
+ style: PropTypes.object,
38
+ };