Created
July 18, 2023 16:39
-
-
Save lukebrandonfarrell/534bd927d72c08efd731123b7e023046 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { ReactElement } from 'react'; | |
import { StyleSheet, TouchableOpacity } from 'react-native'; | |
import Icon from 'react-native-vector-icons/MaterialIcons'; | |
import { Subtitle } from '../subtitle/subtitle'; | |
import { defaultStyle } from '../../styles/theme.style'; | |
interface DateSelectProps { | |
label: string | undefined; | |
handlePickerOpen: () => void; | |
testID?: string; | |
isPlaceholder?: boolean; | |
} | |
export const DropdownField = ({ | |
handlePickerOpen, | |
testID, | |
label, | |
isPlaceholder, | |
}: DateSelectProps): ReactElement => { | |
return ( | |
<TouchableOpacity | |
style={styles.selectContainer} | |
onPress={handlePickerOpen} | |
testID={testID}> | |
<Subtitle size="s2" opacity={!isPlaceholder ? 1 : 0.3}> | |
{label} | |
</Subtitle> | |
<Icon | |
name="arrow-drop-down" | |
size={20} | |
color={defaultStyle.colors.richPlum} | |
/> | |
</TouchableOpacity> | |
); | |
}; | |
const styles = StyleSheet.create({ | |
selectContainer: { | |
flexDirection: 'row', | |
alignItems: 'center', | |
justifyContent: 'space-between', | |
backgroundColor: defaultStyle.colors.white, | |
borderWidth: 0.25, | |
borderColor: defaultStyle.colors.borders['0.25'], | |
borderRadius: defaultStyle.borderRadius.xl, | |
paddingHorizontal: 15, | |
paddingVertical: 16, | |
}, | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState, useEffect, ReactElement, useRef } from 'react'; | |
import { Platform, ViewStyle, TextStyle, StyleSheet } from 'react-native'; | |
import { Picker } from '@react-native-picker/picker'; | |
import { ItemValue } from '@react-native-picker/picker/typings/Picker'; | |
import { defaultStyle } from '../../styles/theme.style'; | |
import { SelectFieldItemType } from '../select-field/select-field'; | |
import { PickerSheetIOS } from '../../components/picker-sheet-ios'; | |
interface PickerSheetProps { | |
visible: boolean; | |
selectedValue: string | number | null; | |
onChangeValue: (value: ItemValue) => void; | |
onClose: () => void; | |
options: SelectFieldItemType[]; | |
containerStyle?: ViewStyle; | |
textStyle?: TextStyle; | |
placeholder?: string; | |
} | |
export const PickerSheet = ({ | |
visible, | |
selectedValue, | |
onChangeValue, | |
onClose, | |
options, | |
placeholder = 'Select an option...', | |
containerStyle = {}, | |
textStyle = {}, | |
}: PickerSheetProps): ReactElement => { | |
/** State */ | |
const [localSelectedValue, setLocalSelectedValue] = | |
useState<ItemValue | null>(selectedValue); | |
const pickerRef = useRef<Picker<ItemValue>>(null); | |
/** Effects */ | |
useEffect(() => setLocalSelectedValue(selectedValue), [selectedValue]); | |
useEffect(() => { | |
if (Platform.OS === 'android') { | |
if (!visible) { | |
pickerRef.current?.blur(); | |
} else { | |
pickerRef.current?.focus(); | |
} | |
} | |
}, [visible]); | |
if (Platform.OS === 'ios') { | |
return ( | |
<PickerSheetIOS | |
visible={visible} | |
containerStyle={containerStyle} | |
textStyle={textStyle} | |
onCancel={onClose} | |
onConfirm={onSelectIOS} | |
innerContainerStyle={{}}> | |
<Picker | |
style={styles.pickerStyle} | |
selectedValue={localSelectedValue ? localSelectedValue : undefined} | |
onValueChange={(value: string | number): void => { | |
setLocalSelectedValue(value); | |
}}> | |
{options.map(option => ( | |
<Picker.Item | |
key={option.value} | |
value={option.value} | |
label={option.label} | |
/> | |
))} | |
</Picker> | |
</PickerSheetIOS> | |
); | |
} else { | |
return ( | |
<Picker | |
ref={pickerRef} | |
selectedValue={selectedValue ? selectedValue : undefined} | |
onValueChange={(value: string | number): void => { | |
if (value !== null) { | |
onChangeValue(value); | |
} | |
}} | |
onBlur={(): void => onClose()} | |
// This effectively, hides the in-built picker | |
style={{ | |
display: 'none', | |
}} | |
// For some reason 'transparent' doesn't work here, so we just set it to the same color as our standard background | |
dropdownIconColor={defaultStyle.colors.paper}> | |
<Picker.Item label={placeholder} value={null} /> | |
{options.map(option => ( | |
<Picker.Item | |
key={option.value} | |
label={option.label} | |
value={option.value} | |
style={styles.itemAndroidStyle} | |
/> | |
))} | |
</Picker> | |
); | |
} | |
/** | |
* Changes value of picker via callback | |
* and hides the component | |
*/ | |
function onSelectIOS(): void { | |
// If local value is 'null' then the user picked the first value | |
// they didn't interact with the picker | |
const value = !localSelectedValue ? options[0]?.value : localSelectedValue; | |
if (value) { | |
// Updates the value for this controlled component | |
onChangeValue(value); | |
// We use this line to keep the local picker value | |
// in sync with the control value | |
setLocalSelectedValue(selectedValue); | |
} | |
onClose(); | |
} | |
}; | |
const styles = StyleSheet.create({ | |
pickerStyle: { | |
alignSelf: 'stretch', | |
backgroundColor: defaultStyle.colors.paper, | |
}, | |
itemAndroidStyle: { | |
color: defaultStyle.colors.black, | |
}, | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState, forwardRef, useImperativeHandle } from 'react'; | |
import { ItemValue } from '@react-native-picker/picker/typings/Picker'; | |
import { Keyboard, StyleSheet, View } from 'react-native'; | |
import { find } from 'lodash'; | |
import { useField } from 'formik'; | |
import { PickerSheet } from '../picker-sheet/picker-sheet'; | |
import { Label, LabelProps } from '../label/label'; | |
import { DropdownField } from '../dropdown-field/dropdown-field'; | |
import { Errors } from '../errors/errors'; | |
import { | |
useMarginStyles, | |
MarginProps, | |
} from '../../utils/use-margin-styles/use-margin-styles'; | |
export interface SelectFieldProps extends MarginProps, LabelProps { | |
name: string; | |
label?: string; | |
placeholder: string; | |
options: SelectFieldItemType[]; | |
testID?: string; | |
onChangeValue?: (value: ItemValue) => void; | |
onSubmitEditing?: () => void; | |
} | |
export interface SelectFieldItemType { | |
label?: string; | |
value: string | number; | |
} | |
export interface SelectFieldRef { | |
openPicker: () => void; | |
closePicker: () => void; | |
} | |
export const SelectField = forwardRef<SelectFieldRef, SelectFieldProps>( | |
( | |
{ | |
name, | |
label, | |
labelDescription, | |
labelSuffix, | |
placeholder, | |
options, | |
testID, | |
onChangeValue, | |
onSubmitEditing, | |
...props | |
}, | |
ref, | |
) => { | |
const [visible, setVisible] = useState(false); | |
const [field, meta, helpers] = useField(name); | |
const marginStyles = useMarginStyles(props); | |
const isPlaceholder = !field.value && placeholder; | |
const selectedOptionLabel = find( | |
options, | |
o => o.value == field.value, | |
)?.label; | |
const dropdownLabel = isPlaceholder ? placeholder : selectedOptionLabel; | |
/** Callbacks */ | |
const onPickerClose = (): void => { | |
setVisible(false); | |
onSubmitEditing && onSubmitEditing(); | |
}; | |
/** Effects */ | |
useImperativeHandle(ref, () => ({ | |
openPicker, | |
closePicker: (): void => setVisible(false), | |
})); | |
return ( | |
<View style={[styles.containerStyle, marginStyles]}> | |
<Label | |
label={label} | |
labelDescription={labelDescription} | |
labelSuffix={labelSuffix} | |
/> | |
<DropdownField | |
label={dropdownLabel} | |
handlePickerOpen={openPicker} | |
testID={testID} | |
isPlaceholder={field.value ? false : true} | |
/> | |
<PickerSheet | |
visible={visible} | |
selectedValue={field.value} | |
options={options} | |
onClose={onPickerClose} | |
onChangeValue={handleSelection} | |
placeholder={placeholder} | |
/> | |
{meta.touched && meta.error && <Errors errors={meta.error} />} | |
</View> | |
); | |
/** | |
* Handles the selection of an option. | |
* | |
* @param value - the value of the selected option. | |
*/ | |
function handleSelection(value: ItemValue): void { | |
// Optionally call the onChangeValue prop. | |
if (onChangeValue) { | |
onChangeValue(value); | |
} else if (value) { | |
const valueAsString = value.toString(); | |
helpers.setValue(valueAsString); | |
} | |
} | |
/** | |
* Opens the picker. | |
*/ | |
function openPicker(): void { | |
Keyboard.dismiss(); | |
setVisible(true); | |
} | |
}, | |
); | |
const styles = StyleSheet.create({ | |
containerStyle: { | |
width: '100%', | |
}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment