diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/common/CenteredView.tsx | 25 | ||||
-rw-r--r-- | src/components/common/LoginInput.tsx | 105 | ||||
-rw-r--r-- | src/components/common/OverlayView.tsx | 19 | ||||
-rw-r--r-- | src/components/common/RadioCheckbox.tsx | 40 | ||||
-rw-r--r-- | src/components/common/TaggInput.tsx | 62 | ||||
-rw-r--r-- | src/components/common/index.ts | 4 | ||||
-rw-r--r-- | src/components/index.ts | 2 | ||||
-rw-r--r-- | src/components/onboarding/ArrowButton.tsx | 23 | ||||
-rw-r--r-- | src/components/onboarding/Background.tsx | 44 | ||||
-rw-r--r-- | src/components/onboarding/RegistrationWizard.tsx | 47 | ||||
-rw-r--r-- | src/components/onboarding/TermsConditions.tsx | 140 | ||||
-rw-r--r-- | src/components/onboarding/index.ts | 4 |
12 files changed, 410 insertions, 105 deletions
diff --git a/src/components/common/CenteredView.tsx b/src/components/common/CenteredView.tsx new file mode 100644 index 00000000..1c5ed399 --- /dev/null +++ b/src/components/common/CenteredView.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import {View, StyleSheet, ViewProps} from 'react-native'; + +interface CenteredViewProps extends ViewProps {} +/** + * A centered view that grows to its parents size. + * @param children - children of this component. + */ +const CenteredView: React.FC<CenteredViewProps> = (props) => { + return ( + <View style={styles.centeredView} {...props}> + {props.children} + </View> + ); +}; + +const styles = StyleSheet.create({ + centeredView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}); + +export default CenteredView; diff --git a/src/components/common/LoginInput.tsx b/src/components/common/LoginInput.tsx deleted file mode 100644 index 2a1768a7..00000000 --- a/src/components/common/LoginInput.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import {TextInput, StyleSheet} from 'react-native'; -import * as Animatable from 'react-native-animatable'; - -const LoginInput = (props: LoginInputProps) => { - return ( - <> - <TextInput - accessibilityLabel={ - props.isUsername - ? 'Username text entry box' - : props.isPassword - ? 'Password text entry box' - : undefined - } - accessibilityHint={ - props.isUsername - ? 'Enter your tagg username here' - : props.isPassword - ? 'Enter your tagg password here' - : undefined - } - style={styles.credentials} - placeholder={ - props.isUsername - ? 'Username' - : props.isPassword - ? 'Password' - : undefined - } - placeholderTextColor="#FFFFFF" - autoCompleteType={ - props.isUsername ? 'username' : props.isPassword ? 'password' : 'off' - } - textContentType={ - props.isUsername ? 'username' : props.isPassword ? 'password' : 'none' - } - returnKeyType={ - props.isUsername ? 'next' : props.isPassword ? 'go' : 'default' - } - keyboardType={ - props.isUsername - ? 'ascii-capable' - : props.isPassword - ? 'default' - : undefined - } - autoCapitalize="none" - onChangeText={(input) => props.onChangeText(input)} - defaultValue={props.type} - onSubmitEditing={props.onSubmitEditing} - blurOnSubmit={ - props.isUsername ? false : props.isPassword ? undefined : undefined - } - secureTextEntry={ - props.isUsername ? false : props.isPassword ? true : false - } - ref={props.input_ref} - /> - {props.attempt_submit && !props.isValid && ( - <Animatable.Text - animation="shake" - duration={500} - style={styles.invalidCredentials}> - {props.validationWarning} - </Animatable.Text> - )} - </> - ); -}; - -const styles = StyleSheet.create({ - credentials: { - top: 175, - width: 248, - height: 40, - fontSize: 20, - color: '#FFFFFF', - borderColor: '#FFFDFD', - borderWidth: 2, - borderRadius: 20, - paddingLeft: 13, - marginVertical: 15, - }, - invalidCredentials: { - top: 165, - color: '#F4DDFF', - paddingHorizontal: 30, - textAlign: 'center', - }, -}); - -interface LoginInputProps { - type: string; - isUsername?: boolean; - isPassword?: boolean; - onChangeText: (input: string) => void; - onSubmitEditing?: () => void; - attempt_submit?: boolean; - input_ref?: object; - isValid?: boolean; - validationWarning?: string; -} - -export default LoginInput; diff --git a/src/components/common/OverlayView.tsx b/src/components/common/OverlayView.tsx new file mode 100644 index 00000000..f0660614 --- /dev/null +++ b/src/components/common/OverlayView.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import {View, StyleSheet} from 'react-native'; + +/** + * A blurred & darkened view that grows to its parents size. Designed to be used with overlaid components. + * @param children - children of this component. + */ +const OverlayView: React.FC = ({children}) => { + return <View style={styles.overlayView}>{children}</View>; +}; + +const styles = StyleSheet.create({ + overlayView: { + flex: 1, + backgroundColor: '#00000080', + }, +}); + +export default OverlayView; diff --git a/src/components/common/RadioCheckbox.tsx b/src/components/common/RadioCheckbox.tsx new file mode 100644 index 00000000..33d50527 --- /dev/null +++ b/src/components/common/RadioCheckbox.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { + View, + StyleSheet, + TouchableOpacity, + TouchableOpacityProps, +} from 'react-native'; + +interface RadioCheckboxProps extends TouchableOpacityProps { + checked: boolean; +} +const RadioCheckbox: React.FC<RadioCheckboxProps> = (props) => { + return ( + <TouchableOpacity {...props}> + <View style={styles.outerCircle}> + {props.checked && <View style={styles.innerCircle} />} + </View> + </TouchableOpacity> + ); +}; + +const styles = StyleSheet.create({ + outerCircle: { + width: 23, + height: 23, + borderRadius: 11.5, + borderWidth: 1, + borderColor: '#fff', + alignItems: 'center', + justifyContent: 'center', + }, + innerCircle: { + width: 17, + height: 17, + borderRadius: 8.5, + backgroundColor: '#04ffff', + }, +}); + +export default RadioCheckbox; diff --git a/src/components/common/TaggInput.tsx b/src/components/common/TaggInput.tsx new file mode 100644 index 00000000..fe11d4f0 --- /dev/null +++ b/src/components/common/TaggInput.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import {View, TextInput, StyleSheet, TextInputProps} from 'react-native'; +import * as Animatable from 'react-native-animatable'; + +interface TaggInputProps extends TextInputProps { + valid?: boolean; + invalidWarning?: string; + attemptedSubmit?: boolean; + width?: number | string; +} +/** + * An input component that receives all props a normal TextInput component does. TaggInput components grow to 60% of their parent's width by default, but this can be set using the `width` prop. + */ +const TaggInput = React.forwardRef((props: TaggInputProps, ref: any) => { + return ( + <View style={styles.container}> + <TextInput + style={[{width: props.width}, styles.input]} + placeholderTextColor="#ddd" + clearButtonMode="while-editing" + ref={ref} + {...props} + /> + {props.attemptedSubmit && !props.valid && ( + <Animatable.Text + animation="shake" + duration={500} + style={styles.warning}> + {props.invalidWarning} + </Animatable.Text> + )} + </View> + ); +}); + +const styles = StyleSheet.create({ + container: { + width: '100%', + alignItems: 'center', + marginVertical: 11, + }, + input: { + minWidth: '60%', + height: 40, + fontSize: 16, + fontWeight: '600', + color: '#fff', + borderColor: '#fffdfd', + borderWidth: 2, + borderRadius: 20, + paddingLeft: 13, + }, + warning: { + fontSize: 14, + marginTop: 5, + color: '#f4ddff', + maxWidth: 350, + textAlign: 'center', + }, +}); + +export default TaggInput; diff --git a/src/components/common/index.ts b/src/components/common/index.ts new file mode 100644 index 00000000..b7041b6d --- /dev/null +++ b/src/components/common/index.ts @@ -0,0 +1,4 @@ +export {default as CenteredView} from './CenteredView'; +export {default as OverlayView} from './OverlayView'; +export {default as RadioCheckbox} from './RadioCheckbox'; +export {default as TaggInput} from './TaggInput'; diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 00000000..724b14ac --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,2 @@ +export * from './common'; +export * from './onboarding'; diff --git a/src/components/onboarding/ArrowButton.tsx b/src/components/onboarding/ArrowButton.tsx new file mode 100644 index 00000000..bf07c6ac --- /dev/null +++ b/src/components/onboarding/ArrowButton.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import {Image, TouchableOpacity, TouchableOpacityProps} from 'react-native'; + +interface ArrowButtonProps extends TouchableOpacityProps { + direction: 'forward' | 'backward'; + disabled?: boolean; +} +const ArrowButton: React.FC<ArrowButtonProps> = (props: ArrowButtonProps) => { + const arrow = + props.direction === 'forward' + ? props.disabled + ? require('../../assets/images/arrow-forward-disabled.png') + : require('../../assets/images/arrow-forward-enabled.png') + : require('../../assets/images/arrow-backward.png'); + + return ( + <TouchableOpacity {...props}> + <Image source={arrow} /> + </TouchableOpacity> + ); +}; + +export default ArrowButton; diff --git a/src/components/onboarding/Background.tsx b/src/components/onboarding/Background.tsx new file mode 100644 index 00000000..98082022 --- /dev/null +++ b/src/components/onboarding/Background.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import LinearGradient from 'react-native-linear-gradient'; +import { + StyleSheet, + TouchableWithoutFeedback, + Keyboard, + ViewProps, + KeyboardAvoidingView, + View, + Platform, +} from 'react-native'; + +interface BackgroundProps extends ViewProps {} +const Background: React.FC<BackgroundProps> = (props) => { + return ( + <LinearGradient + colors={['#9F00FF', '#27EAE9']} + useAngle={true} + angle={154.72} + angleCenter={{x: 0.5, y: 0.5}} + style={styles.container}> + <KeyboardAvoidingView + behavior={Platform.OS === 'ios' ? 'padding' : 'height'} + style={styles.container}> + <TouchableWithoutFeedback accessible={false} onPress={Keyboard.dismiss}> + <View style={[styles.container, styles.view]} {...props}> + {props.children} + </View> + </TouchableWithoutFeedback> + </KeyboardAvoidingView> + </LinearGradient> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + view: { + alignItems: 'center', + }, +}); + +export default Background; diff --git a/src/components/onboarding/RegistrationWizard.tsx b/src/components/onboarding/RegistrationWizard.tsx new file mode 100644 index 00000000..5d7e6ee2 --- /dev/null +++ b/src/components/onboarding/RegistrationWizard.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import {View, StyleSheet, ViewProps} from 'react-native'; + +interface RegistrationWizardProps extends ViewProps { + step: 'one' | 'two' | 'three'; +} + +const RegistrationWizard = (props: RegistrationWizardProps) => { + const stepStyle = styles.step; + const stepActiveStyle = [styles.step, styles.stepActive]; + return ( + <View {...props}> + <View style={styles.container}> + <View style={props.step === 'one' ? stepActiveStyle : stepStyle} /> + <View style={styles.progress} /> + <View style={props.step === 'two' ? stepActiveStyle : stepStyle} /> + <View style={styles.progress} /> + <View style={props.step === 'three' ? stepActiveStyle : stepStyle} /> + </View> + </View> + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + }, + step: { + height: 18, + width: 18, + borderRadius: 9, + borderWidth: 2, + borderColor: '#e1f0ff', + }, + stepActive: { + backgroundColor: '#e1f0ff', + }, + progress: { + width: '30%', + height: 2, + backgroundColor: '#e1f0ff', + }, +}); + +export default RegistrationWizard; diff --git a/src/components/onboarding/TermsConditions.tsx b/src/components/onboarding/TermsConditions.tsx new file mode 100644 index 00000000..5af1b972 --- /dev/null +++ b/src/components/onboarding/TermsConditions.tsx @@ -0,0 +1,140 @@ +import React, {useState} from 'react'; +import { + Modal, + StyleSheet, + View, + Text, + Button, + TouchableOpacity, + ScrollView, + ViewProps, +} from 'react-native'; + +import {RadioCheckbox, CenteredView, OverlayView} from '../common'; +import {dummyTermsConditions} from '../../constants'; + +interface TermsConditionsProps extends ViewProps { + accepted: boolean; + onChange: (accepted: boolean) => void; +} +const TermsConditions: React.FC<TermsConditionsProps> = (props) => { + // boolean representing if modal is visible + const [modalVisible, setModalVisible] = useState(false); + // destructure props + const {accepted, onChange} = props; + /** + * Hides the modal. + */ + const hideModal = (): void => { + if (modalVisible) { + setModalVisible(false); + } + }; + /** + * Sets `accepted` to `true` and hides the modal. + */ + const handleAccept = (): void => { + onChange(true); + hideModal(); + }; + /** + * Toggles the value of `accepted`. + */ + const toggleAccepted = (): void => { + onChange(!accepted); + }; + + return ( + <View {...props}> + <View style={styles.body}> + <RadioCheckbox checked={accepted} onPress={toggleAccepted} /> + <View style={styles.bodyPrompt}> + <Text style={styles.bodyPromptText}>I accept the </Text> + <TouchableOpacity onPress={() => setModalVisible(true)}> + <Text + style={[styles.bodyPromptText, styles.bodyPromptTextUnderline]}> + terms and conditions. + </Text> + </TouchableOpacity> + </View> + </View> + <Modal visible={modalVisible} transparent={true} animationType="fade"> + <OverlayView> + <CenteredView> + <View style={styles.modalView}> + <ScrollView + style={styles.modalScrollView} + contentContainerStyle={styles.modalScrollViewContent}> + <Text style={styles.tcHeader}>Terms and Conditions</Text> + <Text>{dummyTermsConditions}</Text> + </ScrollView> + <View style={styles.modalActions}> + <Button title="Accept" onPress={handleAccept} /> + <View style={styles.modalActionsDivider} /> + <Button title="Close" onPress={hideModal} /> + </View> + </View> + </CenteredView> + </OverlayView> + </Modal> + </View> + ); +}; + +const styles = StyleSheet.create({ + body: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + bodyPrompt: { + flexDirection: 'row', + marginLeft: 10, + }, + bodyPromptText: { + fontSize: 16, + color: '#fff', + }, + bodyPromptTextUnderline: { + textDecorationLine: 'underline', + }, + modalView: { + width: '85%', + height: '55%', + backgroundColor: '#fff', + shadowColor: '#000', + shadowOpacity: 30, + shadowOffset: {width: 0, height: 2}, + shadowRadius: 5, + borderRadius: 8, + paddingTop: 30, + paddingBottom: 15, + paddingHorizontal: 20, + alignItems: 'center', + justifyContent: 'space-between', + }, + modalScrollViewContent: { + justifyContent: 'center', + alignItems: 'center', + }, + modalScrollView: { + marginBottom: 10, + }, + tcHeader: { + fontSize: 18, + fontWeight: 'bold', + }, + modalActions: { + flexDirection: 'row', + justifyContent: 'space-evenly', + alignItems: 'center', + width: '100%', + }, + modalActionsDivider: { + height: '60%', + backgroundColor: '#0160Ca', + width: 1, + }, +}); + +export default TermsConditions; diff --git a/src/components/onboarding/index.ts b/src/components/onboarding/index.ts new file mode 100644 index 00000000..01255c01 --- /dev/null +++ b/src/components/onboarding/index.ts @@ -0,0 +1,4 @@ +export {default as ArrowButton} from './ArrowButton'; +export {default as Background} from './Background'; +export {default as RegistrationWizard} from './RegistrationWizard'; +export {default as TermsConditions} from './TermsConditions'; |