├── VERSION ├── index.js ├── lib ├── utils │ ├── constants.js │ └── formStyle.js └── components │ ├── FormGroupLabel.js │ ├── ErrorMessage.js │ ├── Checkbox.js │ ├── Button.js │ ├── TextOnly.js │ ├── CheckboxField.js │ ├── SelectPicker.android.js │ ├── RadioButtons.js │ ├── DatePicker.android.js │ ├── TextModal.js │ ├── InputText.js │ ├── DatePicker.ios.js │ ├── SelectPicker.ios.js │ └── Form.js ├── .eslintrc.js ├── LICENSE ├── package.json ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md └── README.md /VERSION: -------------------------------------------------------------------------------- 1 | 0.11.4 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import Form from './lib/components/Form'; 2 | 3 | export default Form; 4 | -------------------------------------------------------------------------------- /lib/utils/constants.js: -------------------------------------------------------------------------------- 1 | import {Dimensions} from 'react-native'; 2 | 3 | const {width, height} = Dimensions.get('window'); 4 | 5 | export {width, height}; 6 | -------------------------------------------------------------------------------- /lib/components/FormGroupLabel.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Text, StyleSheet, View} from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const FormGroupLabel = ({label, customStyles}) => { 6 | return ( 7 | 12 | 17 | {label} 18 | 19 | 20 | ); 21 | } 22 | 23 | const styles = StyleSheet.create({ 24 | formGroupLabelText: { 25 | paddingVertical: 20, 26 | fontWeight: '800', 27 | fontSize: 18, 28 | }, 29 | }); 30 | 31 | FormGroupLabel.defaultProps = { 32 | customStyles: {}, 33 | }; 34 | 35 | FormGroupLabel.propTypes = { 36 | label: PropTypes.string.isRequired, 37 | customStyles: PropTypes.object, 38 | }; 39 | 40 | export default FormGroupLabel; 41 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "es6": true, 5 | "browser": true, 6 | "commonjs": true, 7 | }, 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:react-hooks/recommended", 11 | ], 12 | "globals": { 13 | "Atomics": "readonly", 14 | "SharedArrayBuffer": "readonly" 15 | }, 16 | "parserOptions": { 17 | "ecmaFeatures": { 18 | "jsx": true 19 | }, 20 | "ecmaVersion": 2018, 21 | "sourceType": "module" 22 | }, 23 | "plugins": [], 24 | "rules": { 25 | "indent": [ 26 | "off", 27 | "tab" 28 | ], 29 | "linebreak-style": [ 30 | "error", 31 | "unix" 32 | ], 33 | "quotes": [ 34 | "error", 35 | "single" 36 | ], 37 | "no-unused-vars": [ 38 | "off" 39 | ], 40 | "semi": [ 41 | "error", 42 | "always" 43 | ] 44 | } 45 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Davide Da Rech 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/components/ErrorMessage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Text, StyleSheet, View} from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const ErrorMessage = ({error, name, customStyles}) => { 6 | return ( 7 | 13 | 19 | {error.message} 20 | 21 | 22 | ); 23 | } 24 | 25 | const styles = StyleSheet.create({ 26 | errorMessageText: { 27 | paddingVertical: 5, 28 | color: 'red', 29 | }, 30 | }); 31 | 32 | ErrorMessage.defaultProps = { 33 | error: {}, 34 | customStyles: {}, 35 | }; 36 | 37 | ErrorMessage.propTypes = { 38 | name: PropTypes.string.isRequired, 39 | error: PropTypes.object, 40 | customStyles: PropTypes.object, 41 | }; 42 | 43 | export default ErrorMessage; 44 | -------------------------------------------------------------------------------- /lib/components/Checkbox.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, TouchableOpacity} from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; 5 | 6 | const Checkbox = ({customStyles, onPress, value, field, customIcon}) => { 7 | const {icon, iconChecked, iconSize, iconColor} = field; 8 | const iconName = icon ? icon : 'checkbox-blank-outline'; 9 | const iconNameChecked = iconChecked ? iconChecked : 'check-box-outline'; 10 | const FormIcon = customIcon || Icon; 11 | return ( 12 | onPress()}> 15 | 20 | 21 | ); 22 | } 23 | 24 | const styles = StyleSheet.create({ 25 | checkbox: { 26 | height: 30, 27 | flex: 1, 28 | alignItems: 'flex-start', 29 | justifyContent: 'flex-start', 30 | }, 31 | }); 32 | 33 | Checkbox.defaultProps = { 34 | onPress: () => {}, 35 | customStyles: {}, 36 | }; 37 | 38 | Checkbox.propTypes = { 39 | value: PropTypes.bool.isRequired, 40 | customStyles: PropTypes.object, 41 | onPress: PropTypes.func, 42 | }; 43 | 44 | export default Checkbox; 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-hook-form-builder", 3 | "version": "0.11.4", 4 | "description": "Form builder with react native components build with react hook form", 5 | "homepage": "https://github.com/davideddr/react-native-hook-form-builder", 6 | "bugs": { 7 | "url": "https://github.com/davideddr/react-native-hook-form-builder/issues", 8 | "email": "davide.darech@gmail.com" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/davideddr/react-native-hook-form-builder" 13 | }, 14 | "main": "index.js", 15 | "directories": { 16 | "lib": "lib" 17 | }, 18 | "author": "Da Rech Davide (https://github.com/davideddr)", 19 | "contributors": [ 20 | "Da Rech Davide (https://github.com/davideddr)", 21 | "Scagno Mattia (https://github.com/mattska)", 22 | "Davide Luchetta (https://github.com/ketz86)" 23 | ], 24 | "license": "MIT", 25 | "keywords": [ 26 | "react", 27 | "react-native", 28 | "react-hook-form" 29 | ], 30 | "peerDependencies": { 31 | "@react-native-community/datetimepicker": "^2.3.2", 32 | "prop-types": "*", 33 | "react-hook-form": "^5.6.0", 34 | "react-native-htmlview": "^0.15.0", 35 | "react-native-vector-icons": "^6.6.0", 36 | "yup": "^0.28.4" 37 | }, 38 | "devDependencies": { 39 | "eslint": "^6.8.0", 40 | "eslint-plugin-react-hooks": "^3.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | # ========================= 31 | # Operating System Files 32 | # ========================= 33 | 34 | # OSX 35 | # ========================= 36 | 37 | .DS_Store 38 | .AppleDouble 39 | .LSOverride 40 | 41 | # Icon must end with two \r 42 | Icon 43 | 44 | # Thumbnails 45 | ._* 46 | 47 | # Files that might appear on external disk 48 | .Spotlight-V100 49 | .Trashes 50 | 51 | # Directories potentially created on remote AFP share 52 | .AppleDB 53 | .AppleDesktop 54 | Network Trash Folder 55 | Temporary Items 56 | .apdisk 57 | 58 | # Windows 59 | # ========================= 60 | 61 | # Windows image file caches 62 | Thumbs.db 63 | ehthumbs.db 64 | 65 | # Folder config file 66 | Desktop.ini 67 | 68 | # Recycle Bin used on file shares 69 | $RECYCLE.BIN/ 70 | 71 | # Windows Installer files 72 | *.cab 73 | *.msi 74 | *.msm 75 | *.msp 76 | 77 | yarn.lock -------------------------------------------------------------------------------- /lib/utils/formStyle.js: -------------------------------------------------------------------------------- 1 | const formStyle = { 2 | // Button component 3 | buttonContainer: {}, 4 | buttonContent: {}, 5 | buttonText: {}, 6 | // Checkbox 7 | checkbox: {}, 8 | checkboxContainer: {}, 9 | checkboxWrapper: {}, 10 | checkboxTextWrapper: {}, 11 | checkboxText: {}, 12 | // Datepicker 13 | datePickerContainer: {}, 14 | datePickerLabel: {}, 15 | datePickerTextWrapper: {}, 16 | datePickerText: {}, 17 | // Datepicker IOS modal 18 | datePickerModalOverlay: {}, 19 | datePickerModalContainer: {}, 20 | datePickerModalWrapper: {}, 21 | datePickerModalConfirmContainer: {}, 22 | // Error message 23 | errorMessageContainer: {}, 24 | errorMessageText: {}, 25 | // Form 26 | formContainer: {}, 27 | submitButtonContainer: {}, 28 | submitButtonText: {}, 29 | // Form group label 30 | formGroupLabelContainer: {}, 31 | formGroupLabelText: {}, 32 | // Input text 33 | inputTextContainer: {}, 34 | inputTextLabel: {}, 35 | inputTextWrapper: {}, 36 | inputText: {}, 37 | inputTextOnFocusBackground: {}, 38 | // Select picker 39 | selectPickerContainer: {}, 40 | selectPickerLabel: {}, 41 | selectPickerWrapper: {}, 42 | selectPickerWrapperWithIcon: {}, 43 | selectPicker: {}, 44 | selectPickerWithIcon: {}, 45 | // Select picker IOS modal 46 | selectPickerTextWrapper: {}, 47 | selectPickerTextTouch: {}, 48 | selectPickerText: {}, 49 | selectPickerModalOverlay: {}, 50 | selectPickerModalContainer: {}, 51 | selectPickerModalWrapper: {}, 52 | selectPickerModalConfirmContainer: {}, 53 | // Text modal 54 | textModalContainer: {}, 55 | textModalWrapper: {}, 56 | closeModal: {}, 57 | }; 58 | 59 | export default formStyle; 60 | -------------------------------------------------------------------------------- /lib/components/Button.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, TouchableOpacity, View, Text} from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const Button = ({onPress, customStyles, styleName, buttonLabel}) => { 6 | return ( 7 | 14 | 20 | 26 | {buttonLabel} 27 | 28 | 29 | 30 | ); 31 | }; 32 | 33 | const styles = StyleSheet.create({ 34 | buttonContainer: { 35 | flexDirection: 'row', 36 | alignItems: 'center', 37 | justifyContent: 'center', 38 | height: 45, 39 | borderWidth: 1, 40 | borderColor: '#000', 41 | borderRadius: 5, 42 | }, 43 | buttonContent: { 44 | width: '100%', 45 | flexDirection: 'row', 46 | alignItems: 'center', 47 | justifyContent: 'center', 48 | paddingHorizontal: 15, 49 | }, 50 | }); 51 | 52 | Button.defaultProps = { 53 | onPress: () => {}, 54 | customStyles: {}, 55 | }; 56 | 57 | Button.propTypes = { 58 | styleName: PropTypes.string.isRequired, 59 | buttonLabel: PropTypes.string.isRequired, 60 | onPress: PropTypes.func, 61 | customStyles: PropTypes.object, 62 | }; 63 | 64 | export default Button; 65 | -------------------------------------------------------------------------------- /lib/components/TextOnly.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Text, StyleSheet, View, ScrollView} from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | import HTMLView from 'react-native-htmlview'; 5 | 6 | const TextOnly = ({ 7 | label, 8 | styleName, 9 | customStyles, 10 | description, 11 | }) => { 12 | return ( 13 | 19 | {label && ( 20 | 26 | {label} 27 | 28 | )} 29 | {description && ( 30 | 36 | 37 | 38 | )} 39 | 40 | ); 41 | }; 42 | 43 | const styles = StyleSheet.create({ 44 | textOnlyContainer: { 45 | marginTop: 15, 46 | }, 47 | textOnlyLabel: { 48 | marginBottom: 15, 49 | fontWeight: '800', 50 | }, 51 | textOnlyTextArea: { 52 | borderBottomWidth: 1, 53 | borderBottomColor: '#000', 54 | height: 100, 55 | marginBottom: 30, 56 | } 57 | }); 58 | 59 | TextOnly.defaultProps = { 60 | customStyles: {}, 61 | description: null, 62 | label: '' 63 | }; 64 | 65 | TextOnly.propTypes = { 66 | styleName: PropTypes.string.isRequired, 67 | customStyles: PropTypes.object, 68 | description: PropTypes.string, 69 | label: PropTypes.string, 70 | }; 71 | 72 | export default TextOnly; 73 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # React Native Hook Form Builder 2 | 3 | ## 0.11.4 (13-07-2021) 4 | - Remove nestedScrollView 5 | - Update Checkbox and TextModal component 6 | 7 | ## 0.11.3 (20-01-2021) 8 | - Fix TextInput default value problem 9 | 10 | ## 0.11.0 (26-04-2020) 11 | - Update dependencies and rewrite all components with react hooks 12 | 13 | ## 0.10.1 (19-02-2020) 14 | - Remove KeyboardAwareScrollView and add nestedScrollView for Android 15 | 16 | ## 0.10.0 (11-02-2020) 17 | - Add new text only component 18 | 19 | ## 0.9.1 (08-02-2020) 20 | - Fix problem with ios device language 21 | 22 | ## 0.9.0 (07-02-2020) 23 | - Add HTML view in radio button description 24 | 25 | ## 0.8.1 (21-01-2020) 26 | - Add missing prop to radio button component 27 | 28 | ## 0.8.0 (21-01-2020) 29 | - Add description text on radio button component 30 | 31 | ## 0.7.0 (14-01-2020) 32 | - Add placeholder to select picker 33 | 34 | ## 0.6.0 (13-01-2020) 35 | - Fix problem with touchable icon in input text and modal components 36 | 37 | ## 0.5.0 (19-12-2019) 38 | - Fix problem with reference in select and datepicker 39 | 40 | ## 0.4.1 (05-12-2019) 41 | - Add custom props for KeyboardAwareScrollView component 42 | 43 | ## 0.4.0 (05-12-2019) 44 | - Remove Lottie dependency for the checkbox component 45 | - Added KeyboardAwareScrollView component for scroll the view on the input on focus 46 | - Added default values for select and modal text 47 | - Possibility to open or not the modal on checkbox field 48 | 49 | ## 0.3.0 (29-11-2019) 50 | - Added custom icon to text input and date picker components 51 | 52 | ## 0.2.0 (28-11-2019) 53 | - Possibility not to render form submit 54 | - If the label is not present, the text field is not rendered 55 | - Added right and left icons for all elements 56 | - Added the maximum and minimum props for datepicker 57 | - Added default value for prop onChangeCustom 58 | - Added possibility to change the background color of "on focus" text input 59 | - Added the possibility to pass default values to the form 60 | 61 | ## 0.1.0 (26-11-2019) 62 | - First release 63 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at davide.darech@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /lib/components/CheckboxField.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react'; 2 | import { 3 | StyleSheet, 4 | View, 5 | TouchableOpacity, 6 | Text, 7 | } from 'react-native'; 8 | import PropTypes from 'prop-types'; 9 | // COMPONENTS 10 | import Checkbox from './Checkbox'; 11 | import TextModal from './TextModal'; 12 | import ErrorMessage from './ErrorMessage'; 13 | 14 | const CheckboxField = ({ 15 | field, 16 | customStyles, 17 | label, 18 | styleName, 19 | errors, 20 | defaultTextModal, 21 | customIcon, 22 | defaultValue, 23 | onChange, 24 | modalHint, 25 | modalText, 26 | }) => { 27 | const {name, openModal = false} = field; 28 | 29 | const [modalVisible, setModalVisible] = useState(false); 30 | const [value, setValue] = useState(false); 31 | 32 | useEffect(() => { 33 | if (defaultValue) { 34 | setValue(defaultValue); 35 | } 36 | }, []); 37 | 38 | const selectAction = () => { 39 | const {openModal = false} = field; 40 | if (value) { 41 | check(false); 42 | } else if (openModal) { 43 | setModalVisible(!modalVisible); 44 | } else { 45 | check(true); 46 | } 47 | }; 48 | 49 | const check = val => { 50 | onChange(val); 51 | setValue(val); 52 | setModalVisible(false); 53 | }; 54 | 55 | return ( 56 | 62 | 68 | selectAction()} 73 | /> 74 | selectAction()} 76 | style={StyleSheet.flatten([ 77 | styles.checkboxTextWrapper, 78 | customStyles.checkboxTextWrapper, 79 | customStyles[`checkboxTextWrapper${styleName}`], 80 | ])}> 81 | 87 | {label} 88 | 89 | 90 | {openModal && ( 91 | 102 | )} 103 | 104 | {errors[name] && ( 105 | 110 | )} 111 | 112 | ); 113 | }; 114 | 115 | const styles = StyleSheet.create({ 116 | checkboxContainer: { 117 | marginBottom: 20, 118 | }, 119 | checkboxWrapper: { 120 | flexDirection: 'row', 121 | alignItems: 'center', 122 | justifyContent: 'space-between', 123 | }, 124 | checkboxTextWrapper: { 125 | width: '90%', 126 | }, 127 | checkboxText: {}, 128 | }); 129 | 130 | CheckboxField.defaultProps = { 131 | onPress: () => {}, 132 | onChange: () => {}, 133 | customStyles: {}, 134 | defaultValue: null, 135 | label: '', 136 | defaultTextModal: '', 137 | modalHint: null, 138 | modalText: null, 139 | }; 140 | 141 | CheckboxField.propTypes = { 142 | field: PropTypes.object.isRequired, 143 | styleName: PropTypes.string.isRequired, 144 | onPress: PropTypes.func, 145 | customStyles: PropTypes.object, 146 | label: PropTypes.string, 147 | defaultTextModal: PropTypes.string, 148 | defaultValue: PropTypes.any, 149 | onChange: PropTypes.func, 150 | modalHint: PropTypes.string, 151 | modalText: PropTypes.string, 152 | }; 153 | 154 | export default CheckboxField; 155 | -------------------------------------------------------------------------------- /lib/components/SelectPicker.android.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react'; 2 | import {Picker, Text, View, StyleSheet} from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | // COMPONENTS 5 | import ErrorMessage from './ErrorMessage'; 6 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; 7 | 8 | const SelectPicker = ({ 9 | defaultValue, 10 | field, 11 | label, 12 | onChange, 13 | errors, 14 | items, 15 | customStyles, 16 | customIcon, 17 | currentLocale, 18 | styleName, 19 | placeholder, 20 | }) => { 21 | const [selectedValue, setSelectedValue] = useState(); 22 | 23 | useEffect(() => { 24 | if (defaultValue) { 25 | setSelectedValue(defaultValue); 26 | } 27 | }, []); 28 | 29 | const FormIcon = customIcon || Icon; 30 | 31 | return ( 32 | 38 | {label && ( 39 | 45 | {label} 46 | 47 | )} 48 | 57 | {(field.icon || field.iconLeft) && ( 58 | 63 | )} 64 | { 75 | setSelectedValue(itemValue); 76 | onChange(itemValue); 77 | }}> 78 | 79 | {items.map(i => { 80 | const l = 81 | i.label instanceof Object ? i.label[currentLocale] : i.label; 82 | return ; 83 | })} 84 | 85 | {field.iconRight && ( 86 | 91 | )} 92 | 93 | {errors[field.name] && ( 94 | 99 | )} 100 | 101 | ); 102 | }; 103 | 104 | const styles = StyleSheet.create({ 105 | selectPickerContainer: { 106 | marginBottom: 20, 107 | }, 108 | selectPickerWrapper: { 109 | borderWidth: 1, 110 | borderColor: '#000', 111 | }, 112 | selectPickerWrapperWithIcon: { 113 | flexDirection: 'row', 114 | justifyContent: 'space-between', 115 | alignItems: 'center', 116 | paddingHorizontal: 5, 117 | }, 118 | selectPickerLabel: { 119 | marginBottom: 15, 120 | fontWeight: '800', 121 | }, 122 | selectPickerWithIcon: { 123 | width: '85%', 124 | backgroundColor: 'white', 125 | }, 126 | }); 127 | 128 | SelectPicker.defaultProps = { 129 | customStyles: {}, 130 | errors: {}, 131 | label: null, 132 | customIcon: null, 133 | }; 134 | 135 | SelectPicker.propTypes = { 136 | field: PropTypes.object.isRequired, 137 | styleName: PropTypes.string.isRequired, 138 | onChange: PropTypes.func.isRequired, 139 | label: PropTypes.string, 140 | value: PropTypes.instanceOf(Date), 141 | errors: PropTypes.object, 142 | customStyles: PropTypes.object, 143 | currentLocale: PropTypes.string, 144 | }; 145 | 146 | export default SelectPicker; 147 | -------------------------------------------------------------------------------- /lib/components/RadioButtons.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react'; 2 | import {Text, StyleSheet, View, TouchableOpacity, ScrollView} from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | import HTMLView from 'react-native-htmlview'; 5 | // COMPONENTS 6 | import ErrorMessage from './ErrorMessage'; 7 | 8 | const RadioButtons = ({ 9 | items, 10 | label, 11 | styleName, 12 | customStyles, 13 | errors, 14 | field, 15 | description, 16 | defaultValue, 17 | currentLocale, 18 | onChange, 19 | }) => { 20 | const [value, setValue] = useState(); 21 | 22 | useEffect(() => { 23 | if (defaultValue) { 24 | setValue(defaultValue); 25 | } 26 | }, []); 27 | 28 | const renderRadioButton = (item, index) => { 29 | const label = 30 | item.label instanceof Object ? item.label[currentLocale] : item.label; 31 | return ( 32 | { 40 | setValue(item.value); 41 | onChange(item.value); 42 | }}> 43 | 49 | {value === item.value && ( 50 | 57 | )} 58 | 59 | 65 | {label} 66 | 67 | 68 | ); 69 | }; 70 | 71 | return ( 72 | 78 | 84 | {label} 85 | 86 | {description && ( 87 | 93 | 94 | {/* 100 | {description} 101 | */} 102 | 103 | )} 104 | {items.map((item, index) => renderRadioButton(item, index))} 105 | {errors[field.name] && ( 106 | 111 | )} 112 | 113 | ); 114 | }; 115 | 116 | const styles = StyleSheet.create({ 117 | radioButtonWrapper: { 118 | flexDirection: 'row', 119 | alignItems: 'center', 120 | marginBottom: 15, 121 | }, 122 | radioButtonLabel: { 123 | marginBottom: 15, 124 | fontWeight: '800', 125 | }, 126 | radioButton: { 127 | height: 20, 128 | width: 20, 129 | borderRadius: 10, 130 | borderWidth: 1, 131 | borderColor: '#000', 132 | alignItems: 'center', 133 | justifyContent: 'center', 134 | }, 135 | radioButtonChecked: { 136 | width: 14, 137 | height: 14, 138 | borderRadius: 7, 139 | backgroundColor: '#000', 140 | }, 141 | radioButtonText: { 142 | marginLeft: 10, 143 | }, 144 | radioButtonTextArea: { 145 | borderBottomWidth: 1, 146 | borderBottomColor: '#000', 147 | height: 100, 148 | marginBottom: 15, 149 | }, 150 | radioButtonDescription: { 151 | paddingBottom: 20, 152 | }, 153 | }); 154 | 155 | RadioButtons.defaultProps = { 156 | error: {}, 157 | customStyles: {}, 158 | description: null, 159 | }; 160 | 161 | RadioButtons.propTypes = { 162 | field: PropTypes.object.isRequired, 163 | styleName: PropTypes.string.isRequired, 164 | error: PropTypes.object, 165 | customStyles: PropTypes.object, 166 | description: PropTypes.string, 167 | }; 168 | 169 | export default RadioButtons; 170 | -------------------------------------------------------------------------------- /lib/components/DatePicker.android.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react'; 2 | import {Text, View, StyleSheet} from 'react-native'; 3 | import DateTimePicker from '@react-native-community/datetimepicker'; 4 | import PropTypes from 'prop-types'; 5 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; 6 | // COMPONENTS 7 | import ErrorMessage from './ErrorMessage'; 8 | 9 | const DatePicker = ({ 10 | field, 11 | label, 12 | onChange, 13 | errors, 14 | customStyles, 15 | currentLocale, 16 | styleName, 17 | customIcon, 18 | defaultValue, 19 | }) => { 20 | const [date, setDate] = useState(new Date()); 21 | const [show, setShow] = useState(false); 22 | 23 | useEffect(() => { 24 | if (defaultValue) { 25 | setDate(new Date(...defaultValue.split('-'))); 26 | } 27 | }, []); 28 | 29 | const FormIcon = customIcon || Icon; 30 | 31 | return ( 32 | 38 | {label && ( 39 | 45 | {label} 46 | 47 | )} 48 | 57 | {(field.icon || field.iconLeft) && ( 58 | 63 | )} 64 | setShow(!show)}> 74 | {date.toLocaleDateString(currentLocale)} 75 | 76 | {field.iconRight && ( 77 | 82 | )} 83 | 84 | {show && ( 85 | { 98 | setShow(!show); 99 | setDate(newDate || date); 100 | onChange(newDate); 101 | }} 102 | /> 103 | )} 104 | {errors[field.name] && ( 105 | 110 | )} 111 | 112 | ); 113 | }; 114 | 115 | const styles = StyleSheet.create({ 116 | datePickerContainer: { 117 | marginBottom: 20, 118 | }, 119 | datePickerTextWrapper: { 120 | borderWidth: 1, 121 | borderColor: '#000', 122 | paddingVertical: 5, 123 | flexDirection: 'row', 124 | justifyContent: 'space-between', 125 | alignItems: 'center', 126 | }, 127 | datePickerText: { 128 | paddingVertical: 10, 129 | width: '80%', 130 | }, 131 | datePickerLabel: { 132 | marginBottom: 15, 133 | fontWeight: '800', 134 | }, 135 | inputTextWrapper: { 136 | flexDirection: 'row', 137 | alignItems: 'center', 138 | justifyContent: 'space-between', 139 | borderWidth: 1, 140 | borderColor: '#000', 141 | }, 142 | inputText: { 143 | flex: 1, 144 | padding: 10, 145 | }, 146 | }); 147 | 148 | DatePicker.defaultProps = { 149 | value: new Date(), 150 | customStyles: {}, 151 | errors: {}, 152 | currentLocale: '', 153 | label: null, 154 | defaultValue: null, 155 | }; 156 | 157 | DatePicker.propTypes = { 158 | field: PropTypes.object.isRequired, 159 | styleName: PropTypes.string.isRequired, 160 | onChange: PropTypes.func.isRequired, 161 | label: PropTypes.string, 162 | value: PropTypes.instanceOf(Date), 163 | errors: PropTypes.object, 164 | customStyles: PropTypes.object, 165 | currentLocale: PropTypes.string, 166 | }; 167 | 168 | export default DatePicker; 169 | -------------------------------------------------------------------------------- /lib/components/TextModal.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import { 3 | Modal, 4 | ScrollView, 5 | StyleSheet, 6 | View, 7 | Text, 8 | TouchableOpacity, 9 | } from 'react-native'; 10 | import PropTypes from 'prop-types'; 11 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; 12 | // COMPONENTS 13 | import Button from './Button'; 14 | // UTILS 15 | import {height} from '../utils/constants'; 16 | 17 | const TextModal = ({ 18 | field, 19 | visible, 20 | toggleModal, 21 | type, 22 | check, 23 | customStyles, 24 | styleName, 25 | defaultTextModal, 26 | modalHint, 27 | modalText, 28 | }) => { 29 | const [disabledButton, setDisabledButton] = useState(true); 30 | const [screenHeight, setScreenHeight] = useState(0); 31 | 32 | const isCloseToBottom = ({layoutMeasurement, contentOffset, contentSize}) => { 33 | const paddingToBottom = 20; 34 | return ( 35 | layoutMeasurement.height + contentOffset.y >= 36 | contentSize.height - paddingToBottom 37 | ); 38 | }; 39 | 40 | const onContentSizeChange = (contentWidth, contentHeight) => { 41 | setScreenHeight(contentHeight); 42 | }; 43 | 44 | const scrollEnabled = screenHeight > (height - 100); 45 | const disBtn = field.forceEnableButton ? false : disabledButton && scrollEnabled; 46 | 47 | return ( 48 | toggleModal()}> 53 | 59 | 65 | 71 | toggleModal()}> 72 | 73 | 74 | 75 | { 79 | if (isCloseToBottom(nativeEvent)) { 80 | setDisabledButton(false); 81 | } 82 | }} 83 | scrollEventThrottle={400} 84 | onContentSizeChange={onContentSizeChange}> 85 | {modalHint && ( 91 | {modalHint} 92 | )} 93 | {modalText || defaultTextModal} 94 | 95 | 96 | 121 | 122 | 123 | 124 | 125 | ); 126 | }; 127 | 128 | const styles = StyleSheet.create({ 129 | textModalContainer: { 130 | flex: 1, 131 | alignItems: 'center', 132 | justifyContent: 'center', 133 | backgroundColor: 'rgba(60, 60, 60, 0.5)', 134 | paddingVertical: 50, 135 | paddingHorizontal: 30, 136 | }, 137 | textModalWrapper: { 138 | flex: 1, 139 | backgroundColor: '#fff', 140 | padding: 15, 141 | width: '100%', 142 | alignItems: 'center', 143 | justifyContent: 'space-between', 144 | }, 145 | textModalScrollView: { 146 | flexGrow: 1, 147 | }, 148 | textModalHint: { 149 | marginBottom: 10, 150 | fontWeight: '800', 151 | fontSize: 20, 152 | }, 153 | buttonTextModal: { 154 | marginTop: 20, 155 | }, 156 | acceptButtonActive: { 157 | elevation: 1, 158 | backgroundColor: 'blue', 159 | }, 160 | acceptButtonDisabled: { 161 | elevation: 0, 162 | backgroundColor: 'grey', 163 | }, 164 | closeModal: { 165 | flexDirection: 'row', 166 | justifyContent: 'flex-end', 167 | alignItems: 'center', 168 | width: '100%', 169 | marginBottom: 5, 170 | }, 171 | }); 172 | 173 | TextModal.defaultProps = { 174 | customStyles: {}, 175 | defaultTextModal: null, 176 | modalHint: null, 177 | modalText: null, 178 | }; 179 | 180 | TextModal.propTypes = { 181 | field: PropTypes.object.isRequired, 182 | customStyles: PropTypes.object, 183 | defaultTextModal: PropTypes.string, 184 | modalHint: PropTypes.string, 185 | modalText: PropTypes.string, 186 | }; 187 | 188 | export default TextModal; 189 | -------------------------------------------------------------------------------- /lib/components/InputText.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import { 3 | Keyboard, 4 | StyleSheet, 5 | Text, 6 | TextInput, 7 | TouchableOpacity, 8 | View, 9 | } from 'react-native'; 10 | import PropTypes from 'prop-types'; 11 | // COMPONENTS 12 | import ErrorMessage from './ErrorMessage'; 13 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; 14 | 15 | const InputText = ({ 16 | showPasswordIconColor, 17 | showPasswordIconSize, 18 | showPasswordIconName, 19 | hidePasswordIconName, 20 | onBlur, 21 | field, 22 | label, 23 | onChange, 24 | errors, 25 | iconSize, 26 | iconColor, 27 | customStyles, 28 | styleName, 29 | placeholder, 30 | changeBackgroundOnFocus, 31 | defaultValue, 32 | customIcon, 33 | }) => { 34 | const [showPassword, setShowPassword] = useState(false); 35 | const [changeBg, setChangeBg] = useState(false); 36 | 37 | const onBlurInput = async () => { 38 | if (changeBg) { 39 | setChangeBg(false); 40 | } 41 | 42 | await onBlur(); 43 | }; 44 | 45 | const FormIcon = customIcon || Icon; 46 | 47 | const renderShowPassword = () => { 48 | return ( 49 | { 51 | setShowPassword(!showPassword); 52 | }}> 53 | 58 | 59 | ); 60 | }; 61 | 62 | const changeBackground = changeBackgroundOnFocus => { 63 | if (changeBackgroundOnFocus) { 64 | setChangeBg(true); 65 | } 66 | }; 67 | 68 | let iconName = null; 69 | if (field.icon) { 70 | iconName = field.icon.name !== undefined ? field.icon.name : field.icon; 71 | } 72 | if (field.iconLeft) { 73 | iconName = 74 | field.iconLeft.name !== undefined 75 | ? field.iconLeft.name 76 | : field.iconLeft; 77 | } 78 | if (field.iconRight) { 79 | iconName = 80 | field.iconRight.name !== undefined 81 | ? field.iconRight.name 82 | : field.iconRight; 83 | } 84 | 85 | return ( 86 | 92 | {label && ( 93 | 99 | {label} 100 | 101 | )} 102 | 116 | {(field.icon || field.iconLeft) && ( 117 | 118 | )} 119 | { 126 | onChange(text); 127 | }} 128 | onSubmitEditing={()=> Keyboard.dismiss()} 129 | blurOnSubmit={false} 130 | onBlur={() => onBlurInput()} 131 | onFocus={() => changeBackground(changeBackgroundOnFocus)} 132 | style={StyleSheet.flatten([ 133 | styles.inputText, 134 | customStyles.inputText, 135 | customStyles[`inputText${styleName}`], 136 | (field.icon || field.iconLeft || field.iconRight) && { 137 | marginHorizontal: 5, 138 | }, 139 | changeBg && [ 140 | styles.inputTextOnFocusBackground, 141 | customStyles.inputTextOnFocusBackground, 142 | customStyles[`inputTextOnFocusBackground${styleName}`], 143 | ], 144 | field.editable != null && field.editable === false && [ 145 | styles.inputTextNotEditable, 146 | customStyles.inputTextNotEditable, 147 | customStyles[`inputTextNotEditable${styleName}`], 148 | ] 149 | ])} 150 | /> 151 | {field.type === 'password' && renderShowPassword()} 152 | {field.type !== 'password' && field.iconRight && ( 153 | 154 | )} 155 | 156 | {errors[field.name] && ( 157 | 162 | )} 163 | 164 | ); 165 | }; 166 | 167 | const styles = StyleSheet.create({ 168 | inputTextContainer: { 169 | marginBottom: 20, 170 | }, 171 | inputTextLabel: { 172 | marginBottom: 15, 173 | fontWeight: '800', 174 | }, 175 | inputTextWrapper: { 176 | flexDirection: 'row', 177 | alignItems: 'center', 178 | justifyContent: 'space-between', 179 | borderWidth: 1, 180 | borderColor: '#000', 181 | }, 182 | inputText: { 183 | flex: 1, 184 | padding: 10, 185 | }, 186 | inputTextOnFocusBackground: { 187 | backgroundColor: 'gray', 188 | color: 'white', 189 | }, 190 | inputTextNotEditable: { 191 | backgroundColor: 'gray', 192 | } 193 | }); 194 | 195 | InputText.defaultProps = { 196 | showPasswordIconColor: '#000', 197 | showPasswordIconSize: 25, 198 | iconSize: 25, 199 | iconColor: '#000', 200 | customStyles: {}, 201 | onBlur: () => {}, 202 | changeBackgroundOnFocus: false, 203 | placeholder: null, 204 | label: null, 205 | defaultValue: null, 206 | customIcon: null, 207 | showPasswordIconName: 'eye-outline', 208 | hidePasswordIconName: 'eye-off', 209 | }; 210 | 211 | InputText.propTypes = { 212 | field: PropTypes.object.isRequired, 213 | styleName: PropTypes.string.isRequired, 214 | onChange: PropTypes.func.isRequired, 215 | label: PropTypes.string, 216 | onBlur: PropTypes.func, 217 | errors: PropTypes.object, 218 | showPasswordIconColor: PropTypes.string, 219 | showPasswordIconSize: PropTypes.number, 220 | iconSize: PropTypes.number, 221 | iconColor: PropTypes.string, 222 | showPasswordIconName: PropTypes.string, 223 | hidePasswordIconName: PropTypes.string, 224 | placeholder: PropTypes.string, 225 | customStyles: PropTypes.object, 226 | }; 227 | 228 | export default InputText; 229 | -------------------------------------------------------------------------------- /lib/components/DatePicker.ios.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react'; 2 | import {Text, View, StyleSheet, Modal, TouchableOpacity} from 'react-native'; 3 | import DateTimePicker from '@react-native-community/datetimepicker'; 4 | import PropTypes from 'prop-types'; 5 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; 6 | // COMPONENTS 7 | import ErrorMessage from './ErrorMessage'; 8 | // UTILS 9 | import {height, width} from '../utils/constants'; 10 | 11 | const DatePicker = ({ 12 | field, 13 | label, 14 | onChange, 15 | errors, 16 | customStyles, 17 | currentLocale, 18 | styleName, 19 | customIcon, 20 | defaultValue, 21 | }) => { 22 | const [date, setDate] = useState(new Date()); 23 | const [newDate, setNewDate] = useState(new Date()); 24 | const [show, setShow] = useState(false); 25 | 26 | useEffect(() => { 27 | if (defaultValue) { 28 | setDate(new Date(...defaultValue.split('-'))); 29 | setNewDate(new Date(...defaultValue.split('-'))); 30 | } 31 | }, []); 32 | 33 | const toggleModal = () => { 34 | setShow(!show); 35 | setNewDate(date); 36 | }; 37 | 38 | const FormIcon = customIcon || Icon; 39 | 40 | return ( 41 | 47 | {label && ( 48 | 54 | {label} 55 | 56 | )} 57 | 66 | {(field.icon || field.iconLeft) && ( 67 | 72 | )} 73 | toggleModal()}> 83 | {date.toLocaleDateString(currentLocale)} 84 | 85 | {field.iconRight && ( 86 | 91 | )} 92 | 93 | toggleModal()}> 98 | toggleModal()} 106 | /> 107 | 113 | 119 | 125 | { 127 | setDate(newDate); 128 | onChange(newDate); 129 | toggleModal(); 130 | }}> 131 | Done 132 | 133 | 134 | { 147 | setNewDate(d); 148 | }} 149 | /> 150 | 151 | 152 | 153 | {errors[field.name] && ( 154 | 159 | )} 160 | 161 | ); 162 | }; 163 | 164 | const styles = StyleSheet.create({ 165 | datePickerContainer: { 166 | marginBottom: 20, 167 | }, 168 | datePickerTextWrapper: { 169 | borderWidth: 1, 170 | borderColor: '#000', 171 | flexDirection: 'row', 172 | justifyContent: 'space-between', 173 | alignItems: 'center', 174 | padding: 10, 175 | }, 176 | datePickerText: { 177 | width: '90%', 178 | }, 179 | datePickerLabel: { 180 | marginBottom: 15, 181 | fontWeight: '800', 182 | }, 183 | datePickerModalOverlay: { 184 | height: (height * 2) / 3, 185 | backgroundColor: 'rgba(60, 60, 60, 0.5)', 186 | }, 187 | datePickerModalContainer: { 188 | flex: 1, 189 | alignItems: 'center', 190 | justifyContent: 'flex-end', 191 | marginBottom: 80, 192 | backgroundColor: '#fff', 193 | }, 194 | datePickerModalWrapper: { 195 | flex: 1, 196 | padding: 15, 197 | width, 198 | }, 199 | datePickerModalConfirmContainer: { 200 | flexDirection: 'row', 201 | alignItems: 'center', 202 | justifyContent: 'flex-end', 203 | }, 204 | }); 205 | 206 | DatePicker.defaultProps = { 207 | value: new Date(), 208 | customStyles: {}, 209 | errors: {}, 210 | currentLocale: null, 211 | label: null, 212 | defaultValue: null, 213 | customIcon: null, 214 | }; 215 | 216 | DatePicker.propTypes = { 217 | field: PropTypes.object.isRequired, 218 | styleName: PropTypes.string.isRequired, 219 | onChange: PropTypes.func.isRequired, 220 | label: PropTypes.string, 221 | value: PropTypes.instanceOf(Date), 222 | errors: PropTypes.object, 223 | customStyles: PropTypes.object, 224 | currentLocale: PropTypes.string, 225 | }; 226 | 227 | export default DatePicker; 228 | -------------------------------------------------------------------------------- /lib/components/SelectPicker.ios.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react'; 2 | import { 3 | Picker, 4 | Text, 5 | View, 6 | StyleSheet, 7 | Modal, 8 | TouchableOpacity, 9 | } from 'react-native'; 10 | import PropTypes from 'prop-types'; 11 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; 12 | // COMPONENTS 13 | import ErrorMessage from './ErrorMessage'; 14 | // UTILS 15 | import {height, width} from '../utils/constants'; 16 | 17 | const SelectPicker = ({ 18 | field, 19 | label, 20 | onChange, 21 | errors, 22 | items, 23 | customStyles, 24 | styleName, 25 | currentLocale, 26 | confirmLabel, 27 | customIcon, 28 | placeholder, 29 | defaultValue 30 | }) => { 31 | const [value, setValue] = useState(null); 32 | const [valueIndex, setValueIndex] = useState(null); 33 | const [selectedValue, setSelectedValue] = useState(null); 34 | const [selectedIndex, setSelectedIndex] = useState(null); 35 | const [show, setShow] = useState(false); 36 | 37 | useEffect(() => { 38 | if (defaultValue) { 39 | const index = items.map(i => i.value).indexOf(defaultValue); 40 | onChange(defaultValue); 41 | setValue(defaultValue); 42 | setValueIndex(index); 43 | setSelectedValue(defaultValue); 44 | setSelectedIndex(index); 45 | } 46 | }, []); 47 | 48 | const toggleModal = (type = 'out') => { 49 | if (show && !selectedValue && type === 'done') { 50 | onChange(items[0].value); 51 | setValue(items[0].value); 52 | setValueIndex(0); 53 | } 54 | setShow(!show); 55 | setSelectedValue(value); 56 | setSelectedIndex(valueIndex); 57 | }; 58 | 59 | const FormIcon = customIcon || Icon; 60 | 61 | return ( 62 | 68 | {label && ( 69 | 75 | {label} 76 | 77 | )} 78 | 84 | toggleModal()}> 91 | {(field.icon || field.iconLeft) && ( 92 | 97 | )} 98 | 112 | { 113 | items[valueIndex] 114 | ? (items[valueIndex].label instanceof Object ? items[valueIndex].label[currentLocale] : items[valueIndex].label) 115 | : placeholder 116 | } 117 | 118 | {field.iconRight && ( 119 | 124 | )} 125 | 126 | 127 | toggleModal()}> 132 | toggleModal()} 140 | /> 141 | 147 | 153 | 159 | { 161 | setValue(selectedValue); 162 | setValueIndex(selectedIndex); 163 | onChange(selectedValue); 164 | toggleModal('done'); 165 | }}> 166 | {confirmLabel} 167 | 168 | 169 | { 172 | setSelectedValue(itemValue); 173 | setSelectedIndex(itemIndex); 174 | }}> 175 | {items.map(i => { 176 | const l = 177 | i.label instanceof Object 178 | ? i.label[currentLocale] 179 | : i.label; 180 | return ( 181 | 182 | ); 183 | })} 184 | 185 | 186 | 187 | 188 | {errors[field.name] && ( 189 | 194 | )} 195 | 196 | ); 197 | }; 198 | 199 | const styles = StyleSheet.create({ 200 | selectPickerContainer: { 201 | marginBottom: 20, 202 | }, 203 | selectPickerTextWrapper: { 204 | borderWidth: 1, 205 | borderColor: '#000', 206 | }, 207 | selectPickerTextTouch: { 208 | flexDirection: 'row', 209 | justifyContent: 'space-between', 210 | alignItems: 'center', 211 | paddingVertical: 10, 212 | }, 213 | selectPickerText: { 214 | width: '90%', 215 | }, 216 | selectPickerLabel: { 217 | marginBottom: 15, 218 | fontWeight: '800', 219 | }, 220 | selectPickerModalOverlay: { 221 | height: (height * 2) / 3, 222 | backgroundColor: 'rgba(60, 60, 60, 0.5)', 223 | }, 224 | selectPickerModalContainer: { 225 | flex: 1, 226 | alignItems: 'center', 227 | justifyContent: 'flex-end', 228 | marginBottom: 80, 229 | backgroundColor: '#fff', 230 | }, 231 | selectPickerModalWrapper: { 232 | flex: 1, 233 | padding: 15, 234 | width, 235 | }, 236 | selectPickerModalConfirmContainer: { 237 | flexDirection: 'row', 238 | alignItems: 'center', 239 | justifyContent: 'flex-end', 240 | }, 241 | }); 242 | 243 | SelectPicker.defaultProps = { 244 | customStyles: {}, 245 | errors: {}, 246 | label: null, 247 | customIcon: null, 248 | }; 249 | 250 | SelectPicker.propTypes = { 251 | field: PropTypes.object.isRequired, 252 | styleName: PropTypes.string.isRequired, 253 | onChange: PropTypes.func.isRequired, 254 | label: PropTypes.string, 255 | value: PropTypes.instanceOf(Date), 256 | errors: PropTypes.object, 257 | customStyles: PropTypes.object, 258 | currentLocale: PropTypes.string, 259 | }; 260 | 261 | export default SelectPicker; 262 | -------------------------------------------------------------------------------- /lib/components/Form.js: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from 'react'; 2 | import { 3 | View, 4 | NativeModules, 5 | Platform, 6 | StyleSheet, 7 | TouchableOpacity, 8 | Text, 9 | ScrollView 10 | } from 'react-native'; 11 | import {useForm} from 'react-hook-form'; 12 | import * as yup from 'yup'; 13 | // COMPONENTS 14 | import InputText from './InputText'; 15 | import DatePicker from './DatePicker'; 16 | import SelectPicker from './SelectPicker'; 17 | import CheckboxField from './CheckboxField'; 18 | import FormGroupLabel from './FormGroupLabel'; 19 | import RadioButtons from './RadioButtons'; 20 | import TextOnly from './TextOnly'; 21 | 22 | const Form = ({ 23 | formConfig, 24 | mode = 'onSubmit', 25 | onSubmit, 26 | onChangeCustom = (field, value) => value, 27 | customStyles = {}, 28 | currentLocale, 29 | customIcon, 30 | defaultValues = {}, 31 | defaultSelectValues = {}, 32 | defaultTextModals = {}, 33 | showPasswordIconName = 'eye-outline', 34 | hidePasswordIconName = 'eye-off', 35 | }) => { 36 | let deviceLanguage = null; 37 | let lang = null; 38 | if (Platform.OS === 'ios') { 39 | lang = NativeModules.SettingsManager.settings.AppleLanguages[0]; 40 | deviceLanguage = lang.split('-')[0]; 41 | } else { 42 | lang = NativeModules.I18nManager.localeIdentifier; 43 | deviceLanguage = lang.split('_')[0]; 44 | } 45 | const validations = () => { 46 | const validator = {}; 47 | if (formConfig.groups instanceof Array !== true) { 48 | throw new Error('Configuration expects groups key to be a list of objects'); 49 | } 50 | formConfig.groups.forEach(g => { 51 | if (g.children instanceof Array !== true) { 52 | throw new Error('Configuration expects groups key to consist of list of children objects'); 53 | } 54 | g.children.forEach(f => { 55 | if (f.validations) { 56 | validator[f.name] = yup; 57 | f.validations.forEach(v => { 58 | let params = []; 59 | if (v.params) { 60 | Object.keys(v.params).forEach(k => { 61 | let val = v.params[k]; 62 | if (k === 'message' && val && val instanceof Object) { 63 | val = 64 | val[currentLocale] || val[deviceLanguage] || val.default; 65 | } 66 | params.push(val); 67 | }); 68 | } 69 | validator[f.name] = validator[f.name][v.name].apply( 70 | validator[f.name], 71 | params, 72 | ); 73 | }); 74 | if ( 75 | ['password-confirmation', 'passwordConfirmation'].includes(f.name) 76 | ) { 77 | validator[f.name] = validator[f.name].oneOf( 78 | [yup.ref('password'), null], 79 | f.passwordConfirmationMessage[currentLocale] || 80 | f.passwordConfirmationMessage[deviceLanguage] || 81 | f.passwordConfirmationMessage.default, 82 | ); 83 | } 84 | } 85 | }); 86 | }); 87 | return validator; 88 | }; 89 | 90 | const validationSchema = yup.object().shape(validations()); 91 | 92 | const { 93 | register, 94 | watch, 95 | setValue, 96 | handleSubmit, 97 | errors, 98 | triggerValidation, 99 | } = useForm({mode, validationSchema}); 100 | 101 | // NOTE: Old methods to watch only the fileds with the showIf property 102 | // let fields = []; 103 | // formConfig.groups.forEach(g => { 104 | // fields.push( 105 | // g.children.filter(c => c.showIf).map(c => c.showIf.split('=')[0]), 106 | // ); 107 | // }); 108 | // const watchFields = watch(fields.reduce((acc, val) => acc.concat(val), [])); 109 | const watchFields = watch(); 110 | 111 | useEffect(() => { 112 | formConfig.groups.forEach(g => { 113 | if (g.children instanceof Array !== true) { 114 | throw new Error('Configuration expects groups key to consist of list of children objects'); 115 | } 116 | g.children.forEach(f => { 117 | register({name: f.name}); 118 | }); 119 | }); 120 | // NOTE: Use for add default values 121 | Object.keys(defaultValues).forEach(key => { 122 | setValue(key, defaultValues[key]); 123 | }); 124 | }, [register]); 125 | 126 | const formComponents = () => { 127 | if (formConfig.groups instanceof Array !== true) { 128 | throw new Error('Configuration expects groups key to be a list of objects'); 129 | } 130 | return formConfig.groups.map((g, index) => ( 131 | 132 | {g.label && ( 133 | 140 | )} 141 | {g.children instanceof Array === true && g.children.map(f => { 142 | const styleName = f.name 143 | .split(/_|-/i) 144 | .map(l => `${l[0].toUpperCase()}${l.slice(1)}`) 145 | .join(''); 146 | const label = f.label 147 | ? f.label[currentLocale] || 148 | f.label[deviceLanguage] || 149 | f.label.default 150 | : null; 151 | const placeholder = f.placeholder 152 | ? f.placeholder[currentLocale] || 153 | f.placeholder[deviceLanguage] || 154 | f.placeholder.default 155 | : null; 156 | const description = f.description 157 | ? f.description[currentLocale] || 158 | f.description[deviceLanguage] || 159 | f.description.default 160 | : null; 161 | const modalHint = f.hint 162 | ? f.hint[currentLocale] || 163 | f.hint[deviceLanguage] || 164 | f.hint.default || 165 | f.hint 166 | : null; 167 | const modalText = f.text 168 | ? f.text[currentLocale] || 169 | f.text[deviceLanguage] || 170 | f.text.default || 171 | f.text 172 | : null; 173 | let conditionName = null; 174 | if (f.showIf) { 175 | conditionName = f.showIf.split('=')[0]; 176 | } 177 | if ( 178 | conditionName && 179 | f.showIf !== `${conditionName}=${watchFields[conditionName]}` 180 | ) { 181 | return null; 182 | } 183 | if (['text', 'password', 'email'].includes(f.type)) { 184 | return ( 185 | { 197 | const customText = onChangeCustom(f.name, text); 198 | setValue(f.name, customText, f.require); 199 | }} 200 | onBlur={async () => { 201 | if (mode === 'onBlur') { 202 | await triggerValidation(); 203 | } 204 | }} 205 | errors={errors} 206 | customStyles={customStyles} 207 | /> 208 | ); 209 | } 210 | if (f.type === 'date') { 211 | return ( 212 | { 220 | const customDate = onChangeCustom(f.name, date); 221 | setValue(f.name, customDate, f.require); 222 | }} 223 | errors={errors} 224 | customStyles={customStyles} 225 | placeholder={placeholder} 226 | /> 227 | ); 228 | } 229 | if (f.type === 'select') { 230 | const confirmLabel = f.confirmLabel 231 | ? f.confirmLabel[currentLocale] || 232 | f.confirmLabel[deviceLanguage] || 233 | f.confirmLabel.default 234 | : 'Done'; 235 | return ( 236 | { 245 | const customDate = onChangeCustom(f.name, date); 246 | setValue(f.name, customDate, f.require); 247 | }} 248 | errors={errors} 249 | customStyles={customStyles} 250 | currentLocale={currentLocale || deviceLanguage} 251 | confirmLabel={confirmLabel} 252 | placeholder={placeholder} 253 | /> 254 | ); 255 | } 256 | if (f.type === 'checkbox') { 257 | return ( 258 | { 266 | const customValue = onChangeCustom(f.name, text); 267 | setValue(f.name, customValue, f.require); 268 | }} 269 | errors={errors} 270 | // customIcon={customIcon} 271 | customStyles={customStyles} 272 | modalHint={modalHint} 273 | modalText={modalText} 274 | /> 275 | ); 276 | } 277 | if (f.type === 'radio') { 278 | return ( 279 | { 287 | const customText = onChangeCustom(f.name, text); 288 | setValue(f.name, customText, f.require); 289 | }} 290 | onBlur={async () => { 291 | if (mode === 'onBlur') { 292 | await triggerValidation(); 293 | } 294 | }} 295 | errors={errors} 296 | customStyles={customStyles} 297 | items={f.items} 298 | description={description} 299 | currentLocale={currentLocale || deviceLanguage} 300 | /> 301 | ); 302 | } 303 | if (f.type === 'text-only') { 304 | return ( 305 | 313 | ); 314 | } 315 | return null; 316 | })} 317 | 318 | )); 319 | }; 320 | const showSubmit = 321 | formConfig.showSubmit === undefined ? true : formConfig.showSubmit; 322 | return ( 323 | 324 | 329 | {formComponents()} 330 | {showSubmit && ( 331 | 337 | 342 | {formConfig.submitText[currentLocale] || 343 | formConfig.submitText[deviceLanguage] || 344 | formConfig.submitText.default} 345 | 346 | 347 | )} 348 | 349 | 350 | ); 351 | }; 352 | 353 | const styles = StyleSheet.create({ 354 | formContainer: { 355 | flex: 1, 356 | backgroundColor: '#fff', 357 | paddingTop: 20, 358 | paddingBottom: 0, 359 | paddingLeft: 15, 360 | paddingRight: 15, 361 | }, 362 | submitButtonContainer: { 363 | backgroundColor: '#34baeb', 364 | borderRadius: 5, 365 | padding: 10, 366 | justifyContent: 'center', 367 | alignItems: 'center', 368 | marginTop: 20, 369 | }, 370 | submitButtonText: { 371 | color: '#fff', 372 | fontWeight: '800', 373 | }, 374 | }); 375 | 376 | export default Form; 377 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Hook Form Builder 2 | 3 | Simple and configurable form builder with react native components build with: 4 | 5 | ![Supports Android and iOS](https://img.shields.io/badge/platforms-android%20|%20ios-lightgrey.svg) ![MIT License](https://img.shields.io/npm/l/@react-native-community/slider.svg) 6 | 7 | [![develop: React Hook Form](https://img.shields.io/badge/develop-🧾%20React%20Hook%20Form-2d2d2d.svg)](https://github.com/react-hook-form/react-hook-form) 8 | [![develop: Yup](https://img.shields.io/badge/validation%20schema-🚨%20Yup-2d2d2d.svg)](https://github.com/jquense/yup) 9 | 10 | ## Getting started 11 | 12 | ```bash 13 | yarn add react-native-hook-form-builder 14 | ``` 15 | 16 | or 17 | 18 | ```bash 19 | npm install react-native-hook-form-builder 20 | ``` 21 | 22 | Follow the installation instructions of the dependencies: 23 | 24 | - [Datetimepicker](https://github.com/react-native-community/react-native-datetimepicker) 25 | - [React Native Vector Icons](https://github.com/oblador/react-native-vector-icons) 26 | 27 | ## General Usage 28 | 29 | ```js 30 | import Form from "react-native-hook-form-builder"; 31 | ``` 32 | 33 | or 34 | 35 | ```js 36 | const Form = require("react-native-hook-form-builder"); 37 | ``` 38 | 39 | ### Base usage 40 | 41 | ```js 42 | import React from "react"; 43 | import { Alert, Text, SafeAreaView, ScrollView } from "react-native"; 44 | import Form from "react-native-hook-form-builder"; 45 | // JSON WITH FORM CONFIGURATION 46 | import appFormConfig from "../../utils/appFormConfig.json"; 47 | // CUSTOM FORM STYLE 48 | import formStyle from "../../utils/formStyle"; 49 | 50 | export default class App extends React.Component { 51 | onSubmit = data => Alert.alert("Form Data", JSON.stringify(data)); 52 | 53 | onChangeCustom = (field, value) => { 54 | if (field === "firstName") { 55 | return value.toUpperCase(); 56 | } 57 | return value; 58 | }; 59 | 60 | render() { 61 | return ( 62 | 63 | 64 | Form Builder! 65 |
72 | 73 | 74 | ); 75 | } 76 | } 77 | ``` 78 | 79 | ## Props 80 | 81 | ### `formConfig (required)` 82 | 83 | JSON file for form configuration retrieved by a static file or via API. 84 | 85 | ### `onSubmit (required)` 86 | 87 | This function is called when submited the form. 88 | 89 | ### `mode` 90 | 91 | Event to trigger form submit. 92 | Possible values are: 93 | 94 | - `"onSubmit"` 95 | - `"onBlur"` 96 | - `"onChange"` 97 | 98 | You can find more informations about the [React Hook Form](https://github.com/react-hook-form/react-hook-form) configuration at this [page](https://react-hook-form.com/api/#useForm). 99 | 100 | ### `onChangeCustom` 101 | 102 | This function is called before set value of the field. 103 | 104 | ### `customStyles` 105 | 106 | JSON file with custom style rules. 107 | 108 | In `lib/utils/formStyle.js` file there is the list with the possible values to override. 109 | 110 | You can override a single field style by adding configuration field key at the end of the name. 111 | 112 | For example if you have a field with name `email` and you want to change the style of input text only for this element, in the custom styles file you can add `inputTextEmail` key with custom style rules. 113 | 114 | ```js 115 | const formStyle = { 116 | inputText: { 117 | color: "red", 118 | marginBottom: 10 119 | } 120 | }; 121 | 122 | export default formStyle; 123 | ``` 124 | 125 | ### `currentLocale` 126 | 127 | App current locale. 128 | 129 | Default value is device language. 130 | 131 | ### `customIcon` 132 | 133 | Custom icon component. 134 | 135 | For example, if you want to use FontAwesome5 instead of MaterialCommunityIcons, 136 | 137 | ```js 138 | import Icon from 'react-native-vector-icons/FontAwesome5' 139 | 140 | 147 | ``` 148 | Additionally since we expect MaterialCommunityIcons as default, 149 | showPasswordIcon and hidePasswordIcon may have different names, thus to override 150 | those, you can specify the same to showPasswordIconName and hidePasswordIconName 151 | props. 152 | 153 | ### `defaultValues` 154 | 155 | Object with default values. 156 | 157 | The keys are the name of fields in configuration file. 158 | 159 | ```js 160 | { 161 | email: 'test@test.com', 162 | firstName: 'Mario', 163 | age: 21, 164 | privacy: true, 165 | } 166 | ``` 167 | 168 | ### `defaultSelectValues` 169 | 170 | Object with default array of values for the select. 171 | 172 | You have to remove `items` key from configuration of the field. 173 | 174 | The keys are the name of fields in configuration file. 175 | 176 | ```js 177 | { 178 | eventDate: [ 179 | {label: 'Day 1', value: 18}, 180 | {label: 'Day 2', value: 19}, 181 | {label: 'Day 3', value: 20}, 182 | {label: 'Day 4', value: 21}, 183 | ] 184 | } 185 | ``` 186 | 187 | ### `defaultTextModals` 188 | 189 | Object with default string for text of the modal. 190 | 191 | You have to remove `text` key from configuration of the field. 192 | 193 | The keys are the name of fields in configuration file. 194 | 195 | ```js 196 | { 197 | privacy: "Static text" 198 | } 199 | ``` 200 | 201 | ## Configuration 202 | 203 | Supported type of fileds: 204 | 205 | - email 206 | - password 207 | - text input 208 | - date 209 | - select 210 | - checkbox 211 | - radio 212 | - only text (for disclaimer or privacy policy text) 213 | 214 | You can dinamicaly show/hide fields via `showIf` property. 215 | 216 | In the value you have to add the name of the field you want to watch and the corrispondent value. Es=`"showIf": "terms-and-conditions=true"` to show a field when terms and conditions are accepted. 217 | 218 | ## Example 219 | 220 |
Example with a registration form 221 | 222 | ```json 223 | { 224 | "showSubmit": true, 225 | "submitText": { 226 | "it": "Invia", 227 | "en": "Send", 228 | "default": "Submit" 229 | }, 230 | "groups": [ 231 | { 232 | "label": { 233 | "it": "Dati utente", 234 | "en": "User data", 235 | "default": "User data" 236 | }, 237 | "children": [ 238 | { 239 | "type": "email", 240 | "name": "email", 241 | "autoCompleteType": "email", 242 | "keyboardType": "email-address", 243 | "textContentType": "emailAddress", 244 | "changeBackgroundOnFocus": true, 245 | "required": false, 246 | "icon": "at", 247 | "label": { 248 | "it": "email", 249 | "en": "email", 250 | "default": "email" 251 | }, 252 | "placeholder": { 253 | "it": "Inserisci la tua email", 254 | "en": "Insert your email", 255 | "default": "Insert your email" 256 | }, 257 | "validations": [ 258 | { "name": "string" }, 259 | { "name": "required", "params": { "message": "Test" } }, 260 | { "name": "email" } 261 | ], 262 | "showIf": "terms-and-conditions=true" 263 | }, 264 | { 265 | "type": "text", 266 | "name": "username", 267 | "autoCompleteType": "email", 268 | "keyboardType": "email-address", 269 | "textContentType": "emailAddress", 270 | "required": false, 271 | "iconLeft": "at", 272 | "label": { 273 | "it": "Username", 274 | "en": "Username", 275 | "default": "Username" 276 | }, 277 | "placeholder": { 278 | "it": "Username placeholder", 279 | "en": "Username placeholder", 280 | "default": "Username placeholder" 281 | }, 282 | "validations": [ 283 | { "name": "string" }, 284 | { "name": "required", "params": { "message": "Test" } }, 285 | { "name": "email" } 286 | ] 287 | }, 288 | { 289 | "type": "password", 290 | "name": "password", 291 | "autoCompleteType": "password", 292 | "textContentType": "password", 293 | "secureTextEntry": true, 294 | "required": false, 295 | "iconLeft": "lock", 296 | "label": { 297 | "it": "Password", 298 | "en": "Password", 299 | "default": "Password" 300 | }, 301 | "placeholder": { 302 | "it": "Password", 303 | "en": "Password", 304 | "default": "Password" 305 | }, 306 | "validations": [ 307 | { "name": "string" }, 308 | { "name": "required" }, 309 | { "name": "min", "params": { "limit": 6 } } 310 | ] 311 | }, 312 | { 313 | "type": "password", 314 | "name": "password-confirmation", 315 | "autoCompleteType": "password", 316 | "textContentType": "password", 317 | "secureTextEntry": true, 318 | "required": false, 319 | "iconLeft": "lock", 320 | "label": { 321 | "it": "Password Confirmation", 322 | "en": "Password Confirmation", 323 | "default": "Password Confirmation" 324 | }, 325 | "passwordConfirmationMessage": { 326 | "it": "Le password devono coincidere", 327 | "en": "Passwords must match", 328 | "default": "Passwords must match" 329 | }, 330 | "placeholder": { 331 | "it": "Password Confirmation", 332 | "en": "Password Confirmation", 333 | "default": "Password Confirmation" 334 | }, 335 | "validations": [ 336 | { "name": "string" }, 337 | { "name": "required" }, 338 | { "name": "min", "params": { "limit": 6 } } 339 | ] 340 | } 341 | ] 342 | }, 343 | { 344 | "label": { 345 | "it": "Informazioi personali", 346 | "en": "Informazioi personali", 347 | "default": "Informazioi personali" 348 | }, 349 | "children": [ 350 | { 351 | "type": "text", 352 | "name": "firstName", 353 | "required": false, 354 | "label": { 355 | "it": "Nome", 356 | "en": "First name", 357 | "default": "First name" 358 | }, 359 | "placeholder": { 360 | "it": "Nome", 361 | "en": "First name", 362 | "default": "First name" 363 | } 364 | }, 365 | { 366 | "type": "text", 367 | "name": "lastName", 368 | "required": false, 369 | "label": { 370 | "it": "Cognome", 371 | "en": "Last name", 372 | "default": "Last name" 373 | }, 374 | "placeholder": { 375 | "it": "Cognome", 376 | "en": "Last name", 377 | "default": "Last name" 378 | } 379 | }, 380 | { 381 | "type": "date", 382 | "name": "birthdayDate", 383 | "required": false, 384 | "label": { 385 | "it": "Data di nascita", 386 | "en": "Birthday date", 387 | "default": "Birthday date" 388 | }, 389 | "placeholder": { 390 | "it": "Data di nascita", 391 | "en": "Birthday date", 392 | "default": "Birthday date" 393 | }, 394 | "minimumDate": "2019-10-25", 395 | "maximumDate": "2019-10-30", 396 | "iconRight": "arrow-down", 397 | "iconSize": 15, 398 | "iconColor": "#000" 399 | }, 400 | { 401 | "type": "select", 402 | "name": "age", 403 | "required": false, 404 | "label": { 405 | "it": "Etá", 406 | "en": "Age", 407 | "default": "Age" 408 | }, 409 | "confirmLabel": { 410 | "it": "Conferma", 411 | "en": "Done", 412 | "default": "Done" 413 | }, 414 | "placeholder": { 415 | "it": "Etá", 416 | "en": "Age", 417 | "default": "Age" 418 | }, 419 | "items": [ 420 | { "label": "18", "value": 18 }, 421 | { "label": "19", "value": 19 }, 422 | { "label": "20", "value": 20 }, 423 | { "label": "21", "value": 21 }, 424 | { "label": "22", "value": 22 } 425 | ], 426 | "validations": [ 427 | { "name": "string" }, 428 | { "name": "required" } 429 | ], 430 | "iconRight": "arrow-down", 431 | "iconSize": 15, 432 | "iconColor": "#000" 433 | }, 434 | { 435 | "type": "select", 436 | "name": "sex", 437 | "required": false, 438 | "label": { 439 | "it": "Sesso", 440 | "en": "Sex", 441 | "default": "Sex" 442 | }, 443 | "confirmLabel": { 444 | "it": "Conferma", 445 | "en": "Done", 446 | "default": "Done" 447 | }, 448 | "placeholder": { 449 | "it": "Sesso", 450 | "en": "Sex", 451 | "default": "Sex" 452 | }, 453 | "items": [ 454 | { "label": "Male", "value": "m" }, 455 | { "label": "Female", "value": "f" }, 456 | { "label": "Other", "value": "o" } 457 | ] 458 | }, 459 | { 460 | "type": "checkbox", 461 | "name": "privacy", 462 | "required": true, 463 | "openModal": true, 464 | "label": { 465 | "it": "Privacy", 466 | "en": "Privacy", 467 | "default": "Privacy" 468 | }, 469 | "text": "Privacy text", 470 | "buttonAcceptLabel": "Accept", 471 | "buttonDeclineLabel": "Decline", 472 | "validations": [ 473 | { "name": "bool" }, 474 | { "name": "required" } 475 | ] 476 | }, 477 | { 478 | "type": "checkbox", 479 | "name": "terms-and-conditions", 480 | "required": true, 481 | "label": { 482 | "it": "Terms", 483 | "en": "Terms", 484 | "default": "Terms" 485 | }, 486 | "text": "Terms text", 487 | "buttonAcceptLabel": "Accept", 488 | "buttonDeclineLabel": "Decline", 489 | "validations": [ 490 | { "name": "bool" }, 491 | { "name": "required" } 492 | ] 493 | }, 494 | { 495 | "type": "radio", 496 | "name": "newsletter", 497 | "label": { 498 | "it": "Newsletter", 499 | "en": "Newsletter", 500 | "default": "Newsletter" 501 | }, 502 | "items": [ 503 | { "label": "Yes", "value": true }, 504 | { "label": "No", "value": false } 505 | ] 506 | } 507 | ] 508 | } 509 | ] 510 | } 511 | ``` 512 |
513 | 514 | ## Contributors 515 | 516 | 517 | 518 | 519 | 520 | 527 | 534 | 541 | 542 |
521 | 522 | Davide Da Rech 523 |
524 | Davide Da Rech 525 |
526 |
528 | 529 | Mattia Scagno 530 |
531 | Mattia Scagno 532 |
533 |
535 | 536 | Davide Luchetta 537 |
538 | Davide Luchetta 539 |
540 |
543 | 544 | --------------------------------------------------------------------------------