@expo/vector-icons#Feather JavaScript Examples
The following examples show how to use
@expo/vector-icons#Feather.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: TouchableRow.js From expo-soundcloud-clone with MIT License | 6 votes |
TouchableRow = ({ icon, label }) => {
return (
<TouchableOpacity
style={{
flexDirection: "row",
alignItems: "center",
backgroundColor: Colors.white,
paddingHorizontal: 13,
paddingVertical: 20,
justifyContent: "space-between",
borderBottomColor: Colors.grey,
borderBottomWidth: StyleSheet.hairlineWidth
}}
>
<View>
<Feather name={icon} size={20} />
</View>
<View style={{ marginLeft: 10, flex: 1 }}>
<Text style={{ fontSize: 16 }}>{label}</Text>
</View>
<View>
<Ionicons name="ios-arrow-forward" size={20} color={Colors.grey} />
</View>
</TouchableOpacity>
);
}
Example #2
Source File: index.js From atendimento-e-agilidade-medica-AAMed with MIT License | 6 votes |
Header = ({ flag, navigation, label }) => {
return (
<Container>
{flag ? (
<>
<ButtonLeft onPress={() => navigation.toggleDrawer()}>
<Ionicons name="md-menu" size={35} color="#fff" />
</ButtonLeft>
<ImgCenter source={require('../../assets/icon_.png')} />
<ButtonRight onPress={() => navigation.navigate('Help')}>
<Feather name="help-circle" size={35} color="#fff" />
</ButtonRight>
</>
) : (
<>
<ButtonLeft
onPress={() => navigation.dispatch(CommonActions.goBack())}
>
<Ionicons name="md-arrow-back" size={30} color="#fff" />
</ButtonLeft>
<Label>{label}</Label>
<ImgLeft source={require('../../assets/icon_.png')} />
</>
)}
</Container>
);
}
Example #3
Source File: Blocked.js From InstagramClone with Apache License 2.0 | 6 votes |
export default function Blocked() {
useEffect(() => {
BackHandler.addEventListener('hardwareBackPress', () => true)
return () =>
BackHandler.removeEventListener('hardwareBackPress', () => true)
}, [])
return (
<View style={{ height: '100%' }}>
<View style={{ justifyContent: 'center', alignItems: 'center', height: '90%' }}>
<Feather name="stop-circle" size={150} color="red" style={{ position: 'absolute' }} />
<Text style={[{ textAlign: 'center', paddingHorizontal: 40, fontSize: 20, marginTop: 400, width: '100%' }]}>Your account has been blocked</Text>
</View>
</View>
)
}
Example #4
Source File: CustomDrawerContent.js From atendimento-e-agilidade-medica-AAMed with MIT License | 5 votes |
export default function CustomDrawerContent(props) {
const [, { logout }] = useAuth();
const [user, setUser] = useState(null || '');
// A função getUserLogged recupera os dados do usuário por meio de uma Promise
// que é executada assim que o componente e montado
useEffect(() => {
function getUserLogged() {
// O uso da Promise + setTmeout foi necessário pois leva um tempo até os dados do AsyncStorage sejam recuperados
return new Promise((resolve, reject) => {
setTimeout(() => {
// A função resolve() irá conter os dados do usuário após 2s definidos no setTimeout()
resolve(AsyncStorage.getItem('store'));
}, 2000);
});
}
getUserLogged()
.then(data => {
// Para acessar os dados recuperados é usado o .then()
// Como os dados armazenados estão em formato de string, é utilizado o JSON.parse() para tranforma-los em objeto
const dataParse = JSON.parse(data);
// Após esse processo teremos um objeto que terá dentro outro objeto "auth", nele está os dados do usuário além do token
// Como só é necessário apenas o usuário, o estado é setado apenas com os dados do mesmo (id, nome, bio, etc.)
setUser(dataParse.auth.user);
})
.catch(err => console.log(err)); // Por fim é usado um .catch() para tratar algum erro
}, []);
return (
<DrawerContentScrollView {...props}>
<View style={styles.topDrawer}>
<View style={styles.viewAvatar}>
<Avatar
avatarStyle={styles.avatarStyle}
containerStyle={styles.avatarContainerStyle}
onPress={() => props.navigation.navigate('EditProfile')}
activeOpacity={0.7}
size="medium"
rounded
title={user ? JSON.stringify(user.name[0]) : 'a'}
/>
</View>
<View style={styles.viewDados}>
<Text style={styles.nameUser}>{user.name}</Text>
<Text style={styles.bioUser}>{user.bio}</Text>
</View>
</View>
<DrawerItemList {...props} />
<View style={styles.separator} />
<DrawerItem
onPress={logout}
label="SAIR"
style={styles.drawerItem}
labelStyle={styles.drawerItemLabel}
icon={() => <Feather name="log-out" size={20} color="#E53935" />}
/>
</DrawerContentScrollView>
);
}
Example #5
Source File: index.js From semana-omnistack-11 with MIT License | 5 votes |
export default function Detail() {
const navigation = useNavigation();
const route = useRoute();
const incident = route.params.incident;
const message = `Olá ${incident.name}, estou entrando em contato pois gostaria de ajudar no caso "${incident.title}" com o valor de ${Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(incident.value)}`;
function navigateBack() {
navigation.goBack()
}
function sendMail() {
MailComposer.composeAsync({
subject: `Herói do caso: ${incident.title}`,
recipients: [incident.email],
body: message,
})
}
function sendWhatsapp() {
Linking.openURL(`whatsapp://send?phone=${incident.whatsapp}&text=${message}`);
}
return (
<View style={styles.container}>
<View style={styles.header}>
<Image source={logoImg} />
<TouchableOpacity onPress={navigateBack}>
<Feather name="arrow-left" size={28} color="#E82041" />
</TouchableOpacity>
</View>
<View style={styles.incident}>
<Text style={[styles.incidentProperty, { marginTop: 0 }]}>ONG:</Text>
<Text style={styles.incidentValue}>{incident.name} de {incident.city}/{incident.uf}</Text>
<Text style={styles.incidentProperty}>CASO:</Text>
<Text style={styles.incidentValue}>{incident.title}</Text>
<Text style={styles.incidentProperty}>VALOR:</Text>
<Text style={styles.incidentValue}>
{Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
}).format(incident.value)}
</Text>
</View>
<View style={styles.contactBox}>
<Text style={styles.heroTitle}>Salve o dia!</Text>
<Text style={styles.heroTitle}>Seja o herói desse caso.</Text>
<Text style={styles.heroDescription}>Entre em contato:</Text>
<View style={styles.actions}>
<TouchableOpacity style={styles.action} onPress={sendWhatsapp}>
<Text style={styles.actionText}>WhatsApp</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.action} onPress={sendMail}>
<Text style={styles.actionText}>E-mail</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
}
Example #6
Source File: index.js From be-the-hero with MIT License | 5 votes |
export default function Details() {
const navigation = useNavigation()
const route = useRoute()
const incident = route.params.incident
const messageToSend = `Olá ${incident.name}, estou entrando em contato pois gostaria de ajudar no caso "${incident.title}" com o valor de R$ ${
Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL' }
).format(incident.value)
}`
function navigateBack() {
navigation.goBack()
}
function sendEmail() {
MailComposer.composeAsync({
subject: `Herói do caso: ${incident.title}`,
recipients: [ incident.email ],
body: messageToSend
})
}
function sendWhatsapp() {
Linking.openURL(`whatsapp://send?phone=${incident.whatsapp}&text=${messageToSend}`)
}
return (
<View style={ styles.detailsContainer }>
<View style={ styles.headerContainer }>
<Image source={ logoImage }/>
<TouchableOpacity style={ styles.headerButton } onPress={ navigateBack }>
<Feather name="arrow-left" size={ 28 } color="#E02041"/>
<Text style={ styles.headerButtonText }>Voltar</Text>
</TouchableOpacity>
</View>
<View style={ styles.incident }>
<Text style={ styles.incidentOng }>
{ incident.name } de { incident.city }/{ incident.uf }
</Text>
<Text style={ styles.incidentDescription }>
{ incident.description }
</Text>
<Text style={ styles.incidentValue }>
{
Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL' }
).format(incident.value)
}
</Text>
</View>
<View style={ styles.contact }>
<Text style={ styles.heroTitle }>Salve o dia!</Text>
<Text style={ styles.heroTitle }>Seja o herói desse caso</Text>
<Text style={ styles.heroDescription }> Entre em contato:</Text>
<View style={ styles.contactButtons }>
<TouchableOpacity onPress={ sendWhatsapp } style={ styles.buttonWhatsapp }>
<FontAwesome name="whatsapp" size={ 32 } color="#E9FAEF"/>
<Text style={[ styles.buttonText, styles.buttonTextWhatsapp ]}>Whatsapp</Text>
</TouchableOpacity>
<TouchableOpacity onPress={ sendEmail } style={ styles.buttonEmail }>
<FontAwesome name="envelope-o" size={ 30 } color="#FBE8EC"/>
<Text style={[ styles.buttonText, styles.buttonTextEmail ]}>E-mail</Text>
</TouchableOpacity>
</View>
</View>
</View>
)
}
Example #7
Source File: index.js From be-the-hero with MIT License | 5 votes |
export default function Incidents() {
const [incidents, setIncidents] = useState([])
const [total, setTotal] = useState(0)
const [page, setPage] = useState(1)
const [loading, setLoading] = useState(false)
const navigation = useNavigation()
function navigateToDetail(incident) {
navigation.navigate('Detail', { incident })
}
async function loadIncidents() {
if (loading) {
return
}
if (total > 0 && incidents.length == total) {
return
}
setLoading(true)
const response = await api.get('incidents', {
params: { page }
})
setIncidents([... incidents, ... response.data])
setTotal(response.headers['x-total-count'])
setPage(page + 1)
setLoading(false)
}
useEffect(() => {
loadIncidents()
}, [])
return (
<View style={styles.container}>
<View style={styles.header}>
<Image source={logoImg} />
<Text style={styles.headerText}>
Total de <Text style={styles.headerTextBold}>{total} casos</Text>.
</Text>
</View>
<Text style={styles.title}> Bem-vindo! </Text>
<Text style={styles.description}> Escolha um dos casos abaixo e salve o dia. </Text>
<FlatList data={incidents} style={styles.incidentList} keyExtractor={incident => String(incident.id)} onEndReached={loadIncidents} onEndReachedThreshold={0.2} renderItem={({ item: incident }) => (
<View style={styles.incident}>
<Text style={styles.incidentProperty}> ONG: </Text>
<Text style={styles.incidentValue}> {incident.name} </Text>
<Text style={styles.incidentProperty}> CASO: </Text>
<Text style={styles.incidentValue}> {incident.title} </Text>
<Text style={styles.incidentProperty}> VALOR: </Text>
<Text style={styles.incidentValue}> {Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(incident.value)} </Text>
<TouchableOpacity style={styles.detailsButton} onPress={() => navigateToDetail(incident)}>
<Text style={styles.detailsButtonText}> Ver mais detalhes </Text>
<Feather name="arrow-right" size={16} color='#e02041' />
</TouchableOpacity>
</View>
)} />
</View>
)
}
Example #8
Source File: index.js From be-the-hero with MIT License | 5 votes |
export default function Detail() {
const navigation = useNavigation()
const route = useRoute()
const incident = route.params.incident
const message = `Olá ${incident.name}, estou entrando em contato pois gostaria de ajudar no caso "${incident.title}" com o valor de "${Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(incident.value)}" `
function navigateBack() {
navigation.goBack()
}
function sendMail() {
MailComposer.composeAsync({
subject: `Herói do caso: ${incident.title}`,
recipients: [incident.email],
body: message,
})
}
function sendWhatsApp() {
Linking.openURL(`whatsapp://send?phone=${incident.whatsapp}&text=${message}`)
}
return (
<View style={styles.container}>
<View style={styles.header}>
<Image source={logoImg} />
<TouchableOpacity onPress={navigateBack}>
<Feather name="arrow-left" size={28} color="#e02041" />
</TouchableOpacity>
</View>
<View style={styles.incident}>
<Text style={[styles.incidentProperty, { marginTop: 0 }]}> ONG: </Text>
<Text style={styles.incidentValue}> {incident.name} de {incident.city}/{incident.uf} </Text>
<Text style={styles.incidentProperty}> CASO: </Text>
<Text style={styles.incidentValue}> {incident.title} </Text>
<Text style={styles.incidentProperty}> VALOR: </Text>
<Text style={styles.incidentValue}> {Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(incident.value)} </Text>
</View>
<View style={styles.contactBox}>
<Text style={styles.heroTitle}> Salve o dia! </Text>
<Text style={styles.heroTitle}> Seja o herói desse caso. </Text>
<Text style={styles.heroDescription}> Entre em contato: </Text>
<View style={styles.actions}>
<TouchableOpacity style={styles.action} onPress={sendWhatsApp}>
<Text style={styles.actionText}> WhatsApp </Text>
</TouchableOpacity>
<TouchableOpacity style={styles.action} onPress={sendMail}>
<Text style={styles.actionText}> Email </Text>
</TouchableOpacity>
</View>
</View>
</View>
)
}
Example #9
Source File: CollectionsIndexHeader.js From discovery-mobile-ui with MIT License | 5 votes |
AddCollectionInputButton = ({ onPress }) => (
<TouchableOpacity onPress={onPress}>
<Feather name="plus-square" size={24} color={Colors.headerIcon} />
</TouchableOpacity>
)
Example #10
Source File: routes.js From atendimento-e-agilidade-medica-AAMed with MIT License | 5 votes |
AuthDrawerScreen = () => (
<AuthDrawer.Navigator
drawerType="slide"
drawerContentOptions={{
labelStyle: { fontWeight: 'bold' },
activeBackgroundColor: '#006bad',
activeTintColor: '#fff',
itemStyle: { padding: 7 },
contentContainerStyle: { backgroundColor: '#fff' },
}}
drawerContent={props => <CustomDrawerContent {...props} />}
>
<AuthDrawer.Screen
name="Home"
component={Home}
labelStyle={{ fontSize: 23 }}
options={{
drawerLabel: 'HOME',
drawerIcon: ({ color }) => (
<Feather name="home" size={20} color={color} />
),
}}
/>
<AuthDrawer.Screen
name="Historic"
component={History}
options={{
drawerLabel: 'HISTÓRICO',
drawerIcon: ({ color }) => (
<Feather name="calendar" size={20} color={color} />
),
}}
/>
<AuthDrawer.Screen
name="EditProfile"
component={EditProfile}
options={{
drawerLabel: 'EDITAR PERFIL',
drawerIcon: ({ color }) => (
<Feather name="edit-2" size={20} color={color} />
),
}}
/>
<AuthDrawer.Screen
name="Help"
component={HelpStackScreen}
options={{
drawerLabel: 'AJUDA',
drawerIcon: ({ color }) => (
<Feather name="help-circle" size={20} color={color} />
),
}}
/>
<AuthDrawer.Screen
name="Settings"
component={Setting}
options={{
drawerLabel: 'CONFIGURAÇÕES',
drawerIcon: ({ color }) => (
<Feather name="settings" size={20} color={color} />
),
}}
/>
</AuthDrawer.Navigator>
)
Example #11
Source File: CollectionsIndexHeader.js From discovery-mobile-ui with MIT License | 5 votes |
SearchInputButton = ({ onPress }) => (
<TouchableOpacity onPress={onPress}>
<Feather name="search" size={24} color={Colors.headerIcon} />
</TouchableOpacity>
)
Example #12
Source File: RenderBadgeItem.js From discovery-mobile-ui with MIT License | 5 votes |
function RenderBadge({
rtl,
label,
value,
textStyle,
badgeStyle,
badgeTextStyle,
badgeDotStyle,
getBadgeColor,
getBadgeDotColor,
showBadgeDot,
onPress,
THEME,
}) {
/**
* onPress.
*/
const __onPress = useCallback(() => onPress(value), [onPress, value]);
/**
* The badge style.
* @returns {object}
*/
const _badgeStyle = useMemo(() => ([
RTL_DIRECTION(rtl, THEME.badgeStyle),
...[badgeStyle].flat(), {
backgroundColor: getBadgeColor(value),
},
]), [THEME, rtl, badgeStyle, getBadgeColor]);
/**
* The badge dot style.
* @return {object}
*/
const _badgeDotStyle = useMemo(() => ([
RTL_STYLE(rtl, THEME.badgeDotStyle),
...[badgeDotStyle].flat(), {
backgroundColor: getBadgeDotColor(value),
},
]), [THEME, rtl, badgeDotStyle, getBadgeDotColor]);
/**
* The badge text style.
* @returns {object}
*/
const _badgeTextStyle = useMemo(() => ([
...[textStyle].flat(),
...[badgeTextStyle].flat(),
]), [textStyle, badgeTextStyle]);
return (
<TouchableOpacity style={[_badgeStyle, { backgroundColor: Colors.YELLOW3 }]} onPress={__onPress}>
<Feather name="x" size={16} color={Colors.currentCollectionColor} />
{/** showBadgeDot && <View style={_badgeDotStyle} /> */}
<Text style={_badgeTextStyle}>{label}</Text>
</TouchableOpacity>
);
}
Example #13
Source File: Settings_Screen.js From Reminder-App with MIT License | 5 votes |
function Settings() {
const { state, toggle_darkmode } = useContext(Context);
const { colors } = useTheme();
return (
<View style={styles.container}>
<View style={styles.TitleContainer}>
<Text style={[styles.text, { fontSize: 45, color: colors.text }]}>
Settings
</Text>
</View>
<View
style={{
marginHorizontal: 20,
}}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
marginVertical: 15,
}}
>
<Text style={[styles.text, { color: colors.text }]}>Theme</Text>
<TouchableOpacity onPress={toggle_darkmode}>
<Feather
name={state.Theme ? "moon" : "sun"}
size={40}
color={colors.text}
/>
</TouchableOpacity>
</View>
</View>
<View style={styles.TitleContainer}>
<Text style={[styles.text, { fontSize: 45, color: colors.text }]}>
About
</Text>
</View>
<Text
style={[
styles.text,
{ color: colors.text, fontSize: 16, marginHorizontal: 20 },
]}
>
Its a simple open source app created with react-native and expo
</Text>
</View>
);
}
Example #14
Source File: index.js From be-the-hero with MIT License | 5 votes |
export default function Detail() {
const navigation = useNavigation();
const route = useRoute();
const { incident } = route.params;
const message = `Olá ${incident.ong.name}, estou entrando em contato pois gostaria de ajudar no caso "${incident.title}" com o valor de ${incident.valueFormated}.`;
function sendMail() {
MailComposer.composeAsync({
subject: `Herói do caso: ${incident.title}`,
recipients: [`${incident.ong.email}`],
body: message,
});
}
function sendWhatsapp() {
Linking.openURL(`whatsapp://send?phone=+55${incident.ong.whatsapp}&text=${message}`);
}
return (
<Container>
<Header>
<Image source={logoImg} />
<TouchableOpacity onPress={() => navigation.goBack()}>
<Feather name="arrow-left" size={28} color={colors.redHero} />
</TouchableOpacity>
</Header>
<Incident>
<GroupRow>
<View style={{ marginRight: 16, width: '50%' }}>
<IncidentProperty style={{ marginTop: 0 }}>
CASO:
</IncidentProperty>
<IncidentValue>{incident.title}</IncidentValue>
</View>
<View style={{ flex: 1 }} >
<IncidentProperty style={{ marginTop: 0 }}>
ONG:
</IncidentProperty>
<IncidentValue ellipsizeMode="tail" >
{incident.ong.name} de {incident.ong.city}/{incident.ong.uf}
</IncidentValue>
</View>
</GroupRow>
<IncidentProperty>DESCRIÇÃO:</IncidentProperty>
<IncidentValue>{incident.description}</IncidentValue>
<IncidentProperty>VALOR:</IncidentProperty>
<IncidentValue>{incident.valueFormated}</IncidentValue>
</Incident>
<ContactBox>
<HeroTitle>Salve o dia!</HeroTitle>
<HeroTitle>Seja o herói desse caso.</HeroTitle>
<HeroDescription>Entre em contato:</HeroDescription>
<Actions>
<Action onPress={sendWhatsapp}>
<ActionText>WhatsApp</ActionText>
</Action>
<Action onPress={sendMail}>
<ActionText>E-mail</ActionText>
</Action>
</Actions>
</ContactBox>
</Container>
);
}
Example #15
Source File: index.js From SemanaOmnistack11 with MIT License | 5 votes |
export default function Detail() {
const navigation = useNavigation();
const route = useRoute();
const incident = route.params.incident;
const message = `Olá ${
incident.name
}, estou entrando em contato pois gostaria de ajudar no caso "${
incident.title
}" com o valor de ${Intl.NumberFormat("pt-BR", {
style: "currency",
currency: "BRL"
}).format(incident.value)}`;
function navigateBack() {
navigation.goBack();
}
function sendMail() {
MailComposer.composeAsync({
subject: `Herói do caso: ${incident.title}`,
recipients: [incident.email],
body: message
});
}
function sendWhatsapp() {
Linking.openURL(
`whatsapp://send?phone=${incident.whatsapp}&text=${message}`
);
}
return (
<View style={styles.container}>
<View style={styles.header}>
<Image source={logoImg} />
<TouchableOpacity onPress={navigateBack}>
<Feather name="arrow-left" size={28} color="#E82041" />
</TouchableOpacity>
</View>
<View style={styles.incident}>
<Text style={[styles.incidentProperty, { marginTop: 0 }]}>ONG:</Text>
<Text style={styles.incidentValue}>
{incident.name} de {incident.city}/{incident.uf}
</Text>
<Text style={styles.incidentProperty}>CASO:</Text>
<Text style={styles.incidentValue}>{incident.title}</Text>
<Text style={styles.incidentProperty}>VALOR:</Text>
<Text style={styles.incidentValue}>
{Intl.NumberFormat("pt-BR", {
style: "currency",
currency: "BRL"
}).format(incident.value)}
</Text>
</View>
<View style={styles.contactBox}>
<Text style={styles.heroTitle}>Salve o dia!</Text>
<Text style={styles.heroTitle}>Seja o herói desse caso.</Text>
<Text style={styles.heroDescription}>Entre em contato:</Text>
<View style={styles.actions}>
<TouchableOpacity style={styles.action} onPress={sendWhatsapp}>
<Text style={styles.actionText}>WhatsApp</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.action} onPress={sendMail}>
<Text style={styles.actionText}>E-mail</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
}
Example #16
Source File: NotesScreen.js From discovery-mobile-ui with MIT License | 4 votes |
NotesScreen = ({
resource,
createRecordNoteAction,
editRecordNoteAction,
collection,
createCollectionNoteAction,
editCollectionNoteAction,
}) => {
const navigation = useNavigation();
const route = useRoute();
const editingNote = route?.params?.editingNote;
const [text, onChangeText] = useState('');
const [editNoteId, setEditNoteId] = useState(null);
const [showNoteInput, setShowNoteInput] = useState(false);
const isResourceNotes = !!resource;
const closeInput = () => {
onChangeText('');
setEditNoteId(null);
setShowNoteInput(false);
};
const discardInputAlert = () => {
Alert.alert(
'Discard Edits',
'Are you sure you want to discard edits to this note?',
[
{
text: 'Cancel',
onPress: () => {},
style: 'cancel',
},
{
text: 'Discard',
onPress: () => closeInput(),
style: 'destructive',
},
],
);
};
const handleCloseInput = ({ alert }) => {
if (alert) {
discardInputAlert();
} else {
closeInput();
}
};
const handleSave = () => {
if (isResourceNotes) {
if (editNoteId) {
editRecordNoteAction(resource.id, text, editNoteId);
} else {
createRecordNoteAction(resource.id, text);
}
} else if (editNoteId) {
editCollectionNoteAction(editNoteId, text);
} else {
createCollectionNoteAction(text);
}
closeInput();
};
const handleCreateNote = () => {
setShowNoteInput(true);
};
const handleEditNote = (noteId, noteText) => {
setEditNoteId(noteId);
onChangeText(noteText);
setShowNoteInput(true);
};
useEffect(() => {
if (editingNote) {
handleEditNote(editingNote.id, editingNote.text);
} else {
handleCreateNote();
}
}, []);
const hasTextValue = text.length > 0;
const saveButtonTextStyle = hasTextValue ? styles.saveButtonText : styles.disabledSaveButtonText;
const noteCount = isResourceNotes
? collection.records[resource.id]?.notes && Object.keys(collection.records[resource?.id]?.notes).length // eslint-disable-line max-len
: Object.keys(collection.notes).length;
return (
<SafeAreaView style={styles.root}>
<Header style={styles.header}>
<Left>
<TouchableOpacity onPress={() => navigation.goBack()}>
<Entypo name="chevron-thin-left" size={20} color="black" />
</TouchableOpacity>
</Left>
<View style={styles.headerTitleContainer}>
{noteCount > 0 && <HeaderCountIcon count={noteCount} outline />}
<Title style={styles.headerText}><Text>Notes</Text></Title>
</View>
<Right>
{!showNoteInput && (
<TouchableOpacity onPress={handleCreateNote} disabled={showNoteInput}>
<Feather name="plus-square" size={24} color="black" />
</TouchableOpacity>
)}
</Right>
</Header>
<ScrollView>
{isResourceNotes && (
<View style={styles.resourceCardContainer}>
<ResourceCard
resourceId={resource.id}
resource={resource}
handleEditNote={handleEditNote}
editNoteId={editNoteId}
fromNotesScreen
/>
</View>
)}
{!isResourceNotes && (
<>
<View style={styles.collectionHeaderContainer}>
<View style={styles.collectionLabelContainer}>
<Text>{collection.label}</Text>
</View>
</View>
<CollectionNotes
editNoteId={editNoteId}
handleEditNote={handleEditNote}
fromNotesScreen
/>
</>
)}
</ScrollView>
{showNoteInput && (
<KeyboardAvoidingView behavior="padding">
<View style={styles.noteEditingActions}>
<TouchableOpacity onPress={() => handleCloseInput({ alert: true })}>
<Ionicons name="ios-close-outline" size={24} color="black" />
</TouchableOpacity>
<TouchableOpacity style={styles.saveButton} onPress={handleSave} disabled={!hasTextValue}>
<BaseText variant="title" style={saveButtonTextStyle}>Save</BaseText>
</TouchableOpacity>
</View>
<View style={styles.textInputContainer}>
<TextInput
style={styles.textInput}
onChangeText={onChangeText}
multiline
value={text}
autoFocus
/>
</View>
</KeyboardAvoidingView>
)}
</SafeAreaView>
);
}
Example #17
Source File: TagInputView.js From discovery-mobile-ui with MIT License | 4 votes |
function Picker({
value = null,
items = [],
open,
placeholder = null,
closeAfterSelecting = true,
labelProps = {},
disabled = false,
disabledStyle = {},
placeholderStyle = {},
containerStyle = {},
style = {},
textStyle = {},
labelStyle = {},
arrowIconStyle = {},
tickIconStyle = {},
closeIconStyle = {},
badgeStyle = {},
badgeTextStyle = {},
badgeDotStyle = {},
iconContainerStyle = {},
searchContainerStyle = {},
searchTextInputStyle = {},
searchPlaceholderTextColor = Colors.GREY,
dropDownContainerStyle = {},
modalContentContainerStyle = {},
arrowIconContainerStyle = {},
closeIconContainerStyle = {},
tickIconContainerStyle = {},
listItemContainerStyle = {},
listItemLabelStyle = {},
listChildContainerStyle = {},
listChildLabelStyle = {},
listParentContainerStyle = {},
listParentLabelStyle = {},
selectedItemContainerStyle = {},
selectedItemLabelStyle = {},
disabledItemContainerStyle = {},
disabledItemLabelStyle = {},
customItemContainerStyle = {},
customItemLabelStyle = {},
listMessageContainerStyle = {},
listMessageTextStyle = {},
itemSeparatorStyle = {},
badgeSeparatorStyle = {},
listMode = 'FLATLIST',
categorySelectable = true,
searchable = false,
searchPlaceholder = null,
schema = {},
translation = {},
multiple = false,
multipleText = null,
mode = 'BADGE',
itemKey = null,
maxHeight = 200,
renderBadgeItem = null,
renderListItem = null,
itemSeparator = false,
bottomOffset = 0,
badgeColors = BADGE_COLORS,
badgeDotColors = BADGE_DOT_COLORS,
showArrowIcon = true,
showBadgeDot = true,
showTickIcon = true,
ArrowUpIconComponent = null,
ArrowDownIconComponent = null,
TickIconComponent = null,
CloseIconComponent = null,
ListEmptyComponent = null,
ActivityIndicatorComponent = null,
activityIndicatorSize = 30,
activityIndicatorColor = Colors.GREY,
props = {},
modalProps = {},
flatListProps = {},
scrollViewProps = {},
searchTextInputProps = {},
loading = false,
min = null,
max = null,
addCustomItem = false,
setOpen = () => {},
setItems = () => {},
disableBorderRadius = true,
containerProps = {},
onLayout = (e) => {},
onPress = (open) => {},
onOpen = () => {},
onClose = () => {},
setValue = (callback) => {},
onChangeValue = (value) => {},
onChangeSearchText = (text) => {},
zIndex = 5000,
zIndexInverse = 6000,
rtl = false,
dropDownDirection = DROPDOWN_DIRECTION.DEFAULT,
disableLocalSearch = false,
theme = THEMES.DEFAULT,
}) {
const [necessaryItems, setNecessaryItems] = useState([]);
const [searchText, setSearchText] = useState('');
const [pickerHeight, setPickerHeight] = useState(0);
const [direction, setDirection] = useState(GET_DROPDOWN_DIRECTION(dropDownDirection));
const badgeFlatListRef = useRef();
const pickerRef = useRef(null);
const initialization = useRef(false);
const [disablePlus, setDisablePlus] = useState(false);
const THEME = useMemo(() => THEMES[theme].default, [theme]);
const ICON = useMemo(() => THEMES[theme].ICONS, [theme]);
const AddCollectionInputButton = ({ onPress }) => (
<TouchableOpacity onPress={onPress} disabled={disablePlus}>
<Feather name="plus-square" size={24} color={disablePlus ? Colors.GREY : Colors.headerIcon} />
</TouchableOpacity>
);
const handleNewCollectionPress = () => {
const temp_items = items.slice();
temp_items.push({ label: searchText, value: searchText });
setItems(temp_items);
let temp_values = [];
if (value != null) {
temp_values = value.slice();
}
temp_values.push(searchText);
setValue(temp_values);
_onChangeSearchText('');
};
useEffect(() => {
setDisablePlus(false);
if (searchText.length == 0) {
setDisablePlus(true);
}
for (const [key, value] of Object.entries(items)) {
if (searchText.toLowerCase() == value.value.toLowerCase()) {
setDisablePlus(true);
}
}
},
[searchText]);
/**
* The item schema.
* @returns {object}
*/
const _schema = useMemo(() => ({ ...SCHEMA, ...schema }), [schema]);
/**
* componentDidMount.
*/
useEffect(() => {
// LogBox.ignoreLogs(['VirtualizedLists should never be nested']);
// Get initial seleted items
let initialSelectedItems = [];
const valueNotNull = value !== null && (Array.isArray(value) && value.length !== 0);
if (valueNotNull) {
if (multiple) {
initialSelectedItems = items.filter((item) => value.includes(item[_schema.value]));
} else {
initialSelectedItems = items.find((item) => item[_schema.value] === value);
}
}
setNecessaryItems(initialSelectedItems);
}, []);
/**
* Update necessary items.
*/
useEffect(() => {
setNecessaryItems((state) => [...state].map((item) => {
const _item = items.find((x) => x[_schema.value] === item[_schema.value]);
if (_item) {
return { ...item, ..._item };
}
return item;
}));
}, [items]);
/**
* Sync necessary items.
*/
useEffect(() => {
if (multiple) {
setNecessaryItems((state) => {
if (value === null || (Array.isArray(value) && value.length === 0)) return [];
const _state = [...state].filter((item) => value.includes(item[_schema.value]));
const newItems = value.reduce((accumulator, currentValue) => {
const index = _state.findIndex((item) => item[_schema.value] === currentValue);
if (index === -1) {
const item = items.find((item) => item[_schema.value] === currentValue);
if (item) {
return [...accumulator, item];
}
return accumulator;
}
return accumulator;
}, []);
return [..._state, ...newItems];
});
} else {
const state = [];
if (value !== null) {
const item = items.find((item) => item[_schema.value] === value);
if (item) {
state.push(item);
}
}
setNecessaryItems(state);
}
if (initialization.current) {
onChangeValue(value);
} else {
initialization.current = true;
}
}, [value, items]);
/**
* dropDownDirection changed.
*/
useEffect(() => {
setDirection(GET_DROPDOWN_DIRECTION(dropDownDirection));
}, [dropDownDirection]);
/**
* mode changed.
*/
useEffect(() => {
if (mode === MODE.SIMPLE) badgeFlatListRef.current = null;
}, [mode]);
/**
* onPressClose.
*/
const onPressClose = useCallback(() => {
setOpen(false);
setSearchText('');
onClose();
}, [setOpen, onClose]);
/**
* onPressClose.
*/
const onPressOpen = useCallback(() => {
setOpen(true);
onOpen();
}, [setOpen, onOpen]);
/**
* onPressToggle.
*/
const onPressToggle = useCallback(() => {
const isOpen = !open;
setOpen(isOpen);
setSearchText('');
if (isOpen) onOpen();
else onClose();
return isOpen;
}, [open, setOpen, onOpen, onClose]);
/**
* The sorted items.
* @returns {object}
*/
const sortedItems = useMemo(() => {
const sortedItems = items.filter((item) => item[_schema.parent] === undefined || item[_schema.parent] === null);
const children = items.filter((item) => item[_schema.parent] !== undefined && item[_schema.parent] !== null);
children.forEach((child) => {
const index = items.findIndex((item) => item[_schema.parent] === child[_schema.parent] || item[_schema.value] === child[_schema.parent]);
if (index > -1) {
sortedItems.splice(index + 1, 0, child);
}
});
return sortedItems;
}, [items, _schema]);
/**
* The items.
* @returns {object}
*/
const _items = useMemo(() => {
if (searchText.length === 0) {
return sortedItems;
}
if (disableLocalSearch) return sortedItems;
const values = [];
const results = sortedItems.filter((item) => {
if (item[_schema.label].toLowerCase().includes(searchText.toLowerCase())) {
values.push(item[_schema.value]);
return true;
}
return false;
});
results.forEach((item, index) => {
if (item[_schema.parent] === undefined || item[_schema.parent] === null || values.includes(item[_schema.parent])) return;
const parent = sortedItems.find((x) => x[_schema.value] === item[_schema.parent]);
values.push(item[_schema.parent]);
results.splice(index, 0, parent);
});
if ((results.length === 0 || results.findIndex((item) => item[_schema.label].toLowerCase() === searchText.toLowerCase()) === -1) && addCustomItem) {
results.push({
[_schema.label]: searchText,
[_schema.value]: searchText.replace(' ', '-'),
custom: true,
});
}
return results;
}, [sortedItems, _schema, searchText, addCustomItem]);
/**
* The value.
* @returns {string|object|null}}
*/
const _value = useMemo(() => {
if (multiple) {
if (value === null) return [];
return [...new Set(value)];
}
return value;
}, [value, multiple]);
/**
* Selected items only for multiple items.
* @returns {object}
*/
const selectedItems = useMemo(() => {
if (!multiple) return [];
return necessaryItems.filter((item) => _value.includes(item[_schema.value]));
}, [necessaryItems, _value, _schema, multiple]);
/**
* The placeholder.
* @returns {string}
*/
const _placeholder = useMemo(() => placeholder ?? ('PLACEHOLDER'), [placeholder, 'PLACEHOLDER']);
/**
* The multiple text.
* @returns {string}
*/
const _multipleText = useMemo(() => multipleText ?? ('SELECTED_ITEMS_COUNT_TEXT'), [multipleText, 'PLACEHOLDER']);
/**
* The mode.
* @returns {string}
*/
const _mode = useMemo(() => {
try {
return mode;
} catch (e) {
return MODE.SIMPLE;
}
}, [mode]);
/**
* Indicates whether the value is null.
* @returns {boolean}
*/
const isNull = useMemo(() => {
if (_value === null || (Array.isArray(_value) && _value.length === 0)) return true;
return necessaryItems.length === 0;
}, [necessaryItems, _value]);
/**
* Get the selected item.
* @returns {object}
*/
const getSelectedItem = useCallback(() => {
if (multiple) return _value;
if (isNull) return null;
try {
return necessaryItems.find((item) => item[_schema.value] === _value);
} catch (e) {
return null;
}
}, [_value, necessaryItems, isNull, multiple]);
/**
* Get the label of the selected item.
* @param {string|null} fallback
* @returns {string}
*/
const getLabel = useCallback((fallback = null) => {
const item = getSelectedItem();
if (multiple) {
if (item.length > 0) return _multipleText.replace('{count}', item.length);
return fallback;
}
try {
return item[_schema.label];
} catch (e) {
return fallback;
}
}, [getSelectedItem, multiple, _multipleText, _schema]);
/**
* The label of the selected item / placeholder.
*/
const _selectedItemLabel = useMemo(() => getLabel(_placeholder), [getLabel, _placeholder]);
/**
* The icon of the selected item.
*/
const _selectedItemIcon = useCallback(() => {
if (multiple) return null;
const item = getSelectedItem();
try {
return item[_schema.icon] ?? null;
} catch (e) {
return null;
}
}, [getSelectedItem, multiple, _schema]);
/**
* onPress.
*/
const __onPress = useCallback(async () => {
const isOpen = !open;
onPress(isOpen);
if (isOpen && dropDownDirection === DROPDOWN_DIRECTION.AUTO) {
const [, y] = await new Promise((resolve) => pickerRef.current.measureInWindow((...args) => resolve(args)));
const size = y + maxHeight + pickerHeight + bottomOffset;
setDirection(size < WINDOW_HEIGHT ? 'top' : 'bottom');
}
onPressToggle();
}, [
open,
onPressToggle,
onPress,
pickerRef,
maxHeight,
pickerHeight,
bottomOffset,
dropDownDirection,
]);
/**
* onLayout.
*/
const __onLayout = useCallback((e) => {
e.persist();
onLayout(e);
setPickerHeight(e.nativeEvent.layout.height);
}, [onLayout]);
/**
* Disable borderRadius for the picker.
* @returns {object}
*/
const pickerNoBorderRadius = useMemo(() => {
if (listMode === LIST_MODE.MODAL) return null;
if (disableBorderRadius && open) {
return direction === 'top' ? {
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
} : {
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
};
}
return {};
}, [disableBorderRadius, open, direction, listMode]);
/**
* Disable borderRadius for the drop down.
* @returns {object}
*/
const dropDownNoBorderRadius = useMemo(() => {
if (listMode === LIST_MODE.MODAL) return null;
if (disableBorderRadius && open) {
return direction === 'top' ? {
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
} : {
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
};
}
}, [disableBorderRadius, open, direction, listMode]);
/**
* The disabled style.
* @returns {object}
*/
const _disabledStyle = useMemo(() => disabled && disabledStyle, [disabled, disabledStyle]);
/**
* The zIndex.
* @returns {number}
*/
const _zIndex = useMemo(() => {
if (open) {
return direction === 'top' ? zIndex : zIndexInverse;
}
return zIndex;
}, [zIndex, zIndexInverse, direction, open]);
/**
* The style.
* @returns {object}
*/
const _style = useMemo(() => ([
RTL_DIRECTION(rtl, THEME.style), {
zIndex: _zIndex,
},
...[style].flat(),
pickerNoBorderRadius,
]), [rtl, style, pickerNoBorderRadius, _zIndex, THEME]);
/**
* The placeholder style.
* @returns {object}
*/
const _placeholderStyle = useMemo(() => isNull && placeholderStyle, [isNull, placeholderStyle]);
/**
* The style of the label.
* @returns {object}
*/
const _labelStyle = useMemo(() => ([
THEME.label,
...[textStyle].flat(),
...[!isNull && labelStyle].flat(),
...[_placeholderStyle].flat(),
]), [textStyle, _placeholderStyle, labelStyle, isNull, THEME]);
/**
* The arrow icon style.
* @returns {object}
*/
const _arrowIconStyle = useMemo(() => ([
THEME.arrowIcon,
...[arrowIconStyle].flat(),
]), [arrowIconStyle, THEME]);
/**
* The dropdown container style.
* @returns {object}
*/
const _dropDownContainerStyle = useMemo(() => ([
THEME.dropDownContainer, {
[direction]: pickerHeight - 1,
maxHeight,
zIndex: _zIndex,
},
...[dropDownContainerStyle].flat(),
dropDownNoBorderRadius,
]), [
dropDownContainerStyle,
pickerHeight,
maxHeight,
dropDownNoBorderRadius,
direction,
_zIndex,
THEME,
]);
/**
* The modal content container style.
* @returns {object}
*/
const _modalContentContainerStyle = useMemo(() => ([
THEME.modalContentContainer,
...[modalContentContainerStyle].flat(),
]), [modalContentContainerStyle, THEME]);
/**
* The zIndex of the container.
* @returns {object}
*/
const zIndexContainer = useMemo(() => Platform.OS !== 'android' && {
zIndex: _zIndex,
}, [_zIndex]);
/**
* The container style.
* @returns {object}
*/
const _containerStyle = useMemo(() => ([
THEME.container,
zIndexContainer,
...[containerStyle].flat(),
...[_disabledStyle].flat(),
]), [zIndexContainer, containerStyle, _disabledStyle, THEME]);
/**
* The arrow icon container style.
* @returns {object}
*/
const _arrowIconContainerStyle = useMemo(() => ([
RTL_STYLE(rtl, THEME.arrowIconContainer),
...[arrowIconContainerStyle].flat(),
]), [rtl, arrowIconContainerStyle, THEME]);
/**
* The arrow component.
* @returns {JSX.Element}
*/
const _ArrowComponent = useMemo(() => {
if (!showArrowIcon) return null;
let Component;
if (open && ArrowUpIconComponent !== null) Component = <ArrowUpIconComponent style={_arrowIconStyle} />;
else if (!open && ArrowDownIconComponent !== null) Component = <ArrowDownIconComponent style={_arrowIconStyle} />;
else Component = <Image source={open ? ICON.ARROW_UP : ICON.ARROW_DOWN} style={_arrowIconStyle} />;
return (
<View style={_arrowIconContainerStyle}>
{Component}
</View>
);
}, [
showArrowIcon,
open,
ArrowUpIconComponent,
ArrowDownIconComponent,
_arrowIconStyle,
_arrowIconContainerStyle,
ICON,
]);
/**
* The icon container style.
* @returns {object}
*/
const _iconContainerStyle = useMemo(() => ([
RTL_STYLE(rtl, THEME.iconContainer),
...[iconContainerStyle].flat(),
]), [rtl, iconContainerStyle, THEME]);
/**
* The selected item icon component.
* @returns {JSX.Element|null}
*/
const SelectedItemIconComponent = useMemo(() => {
const Component = _selectedItemIcon();
return Component !== null && (
<View style={_iconContainerStyle}>
<Component />
</View>
);
}, [_selectedItemIcon, _iconContainerStyle]);
/**
* The simple body component.
* @returns {JSX.Element}
*/
const SimpleBodyComponent = useMemo(() => (
<>
{SelectedItemIconComponent}
<Text style={_labelStyle} {...labelProps}>
{_selectedItemLabel}
</Text>
</>
), [SelectedItemIconComponent, _labelStyle, labelProps, _selectedItemLabel]);
/**
* onPress badge.
*/
const onPressBadge = useCallback((value) => {
setValue((state) => {
const _state = [...state];
const index = _state.findIndex((item) => item === value);
_state.splice(index, 1);
return _state;
});
}, [setValue]);
/**
* The badge colors.
* @returns {object}
*/
const _badgeColors = useMemo(() => {
if (typeof badgeColors === 'string') return [badgeColors];
return badgeColors;
}, [badgeColors]);
/**
* The badge dot colors.
* @returns {object}
*/
const _badgeDotColors = useMemo(() => {
if (typeof badgeDotColors === 'string') return [badgeDotColors];
return badgeDotColors;
}, [badgeDotColors]);
/**
* Get badge color.
* @param {string} str
* @returns {string}
*/
const getBadgeColor = useCallback((str) => {
str = `${str}`;
const index = Math.abs(ASCII_CODE(str)) % _badgeColors.length;
return _badgeColors[index];
}, [_badgeColors]);
/**
* Get badge dot color.
* @param {string} str
* @returns {string}
*/
const getBadgeDotColor = useCallback((str) => {
str = `${str}`;
const index = Math.abs(ASCII_CODE(str)) % _badgeDotColors.length;
return _badgeDotColors[index];
}, [_badgeDotColors]);
/**
* The render badge component.
* @returns {JSX.Element}
*/
const RenderBadgeComponent = useMemo(() => (renderBadgeItem !== null ? renderBadgeItem : RenderBadgeItem), [renderBadgeItem]);
/**
* Render badge.
* @returns {JSX.Element}
*/
const __renderBadge = useCallback(({ item }) => (
<RenderBadgeComponent
rtl={rtl}
label={item[_schema.label]}
value={item[_schema.value]}
IconComponent={item[_schema.icon] ?? null}
textStyle={textStyle}
badgeStyle={badgeStyle}
badgeTextStyle={badgeTextStyle}
badgeDotStyle={badgeDotStyle}
getBadgeColor={getBadgeColor}
getBadgeDotColor={getBadgeDotColor}
showBadgeDot={showBadgeDot}
onPress={onPressBadge}
theme={theme}
THEME={THEME}
/>
), [
rtl,
_schema,
textStyle,
badgeStyle,
badgeTextStyle,
badgeDotStyle,
getBadgeColor,
getBadgeDotColor,
showBadgeDot,
onPressBadge,
theme,
THEME,
]);
/**
* The badge key.
* @returns {string}
*/
const _itemKey = useMemo(() => {
if (itemKey === null) return _schema.value;
return itemKey;
}, [itemKey, _schema]);
/**
* The key extractor.
* @returns {string}
*/
const keyExtractor = useCallback((item) => `${item[_itemKey]}`, [_itemKey]);
/**
* The badge separator style.
* @returns {object}
*/
const _badgeSeparatorStyle = useMemo(() => ([
THEME.badgeSeparator,
...[badgeSeparatorStyle].flat(),
]), [badgeSeparatorStyle, THEME]);
/**
* The badge separator component.
* @returns {JSX.Element}
*/
const BadgeSeparatorComponent = useCallback(() => (
<View style={_badgeSeparatorStyle} />
), [_badgeSeparatorStyle]);
/**
* The label container.
* @returns {object}
*/
const labelContainer = useMemo(() => ([
THEME.labelContainer, rtl && {
transform: [
{ scaleX: -1 },
],
},
]), [rtl, THEME]);
/**
* Badge list empty component.
* @returns {JSX.Element}
*/
const BadgeListEmptyComponent = useCallback(() => (
<View style={labelContainer}>
<Text style={_labelStyle} {...labelProps}>
{_placeholder}
</Text>
</View>
), [_labelStyle, labelContainer, labelProps, _placeholder]);
/**
* Set ref.
*/
const setBadgeFlatListRef = useCallback((ref) => {
badgeFlatListRef.current = ref;
}, []);
/**
* The badge body component.
* @returns {JSX.Element}
*/
const BadgeBodyComponent = useMemo(() => (
<FlatList
ref={setBadgeFlatListRef}
data={selectedItems}
renderItem={__renderBadge}
horizontal
showsHorizontalScrollIndicator={false}
keyExtractor={keyExtractor}
ItemSeparatorComponent={BadgeSeparatorComponent}
ListEmptyComponent={BadgeListEmptyComponent}
style={THEME.listBody}
contentContainerStyle={THEME.listBodyContainer}
inverted={rtl}
/>
), [
rtl,
selectedItems,
__renderBadge,
keyExtractor,
BadgeSeparatorComponent,
BadgeListEmptyComponent,
THEME,
]);
/**
* The body component.
*/
const _BodyComponent = useMemo(() => {
switch (_mode) {
case MODE.SIMPLE: return SimpleBodyComponent;
case MODE.BADGE: return multiple ? BadgeBodyComponent : SimpleBodyComponent;
default: //
}
}, [_mode, SimpleBodyComponent, BadgeBodyComponent, multiple]);
/**
* The list item container style.
* @returns {object}
*/
const _listItemContainerStyle = useMemo(() => ([
RTL_DIRECTION(rtl, THEME.listItemContainer),
...[listItemContainerStyle].flat(),
]), [rtl, listItemContainerStyle, THEME]);
const _listItemContainerStyle2 = useMemo(() => ([
RTL_DIRECTION(rtl, THEME.listItemContainer2),
...[listItemContainerStyle].flat(),
]), [rtl, listItemContainerStyle, THEME]);
/**
* The tick icon container style.
* @returns {object}
*/
const _tickIconContainerStyle = useMemo(() => ([
RTL_STYLE(rtl, THEME.tickIconContainer),
...[tickIconContainerStyle].flat(),
]), [rtl, tickIconContainerStyle, THEME]);
/**
* The list item label style.
* @returns {object}
*/
const _listItemLabelStyle = useMemo(() => ([
THEME.listItemLabel,
...[textStyle].flat(),
...[listItemLabelStyle].flat(),
]), [textStyle, listItemLabelStyle, THEME]);
/**
* The tick icon style.
* @returns {object}
*/
const _tickIconStyle = useMemo(() => ([
THEME.tickIcon,
...[tickIconStyle].flat(),
]), [tickIconStyle, THEME]);
/**
* The search container style.
* @returns {object}
*/
const _searchContainerStyle = useMemo(() => ([
RTL_DIRECTION(rtl, THEME.searchContainer),
...[searchContainerStyle].flat(), !searchable && listMode === LIST_MODE.MODAL && {
flexDirection: 'row-reverse',
},
]), [rtl, listMode, searchable, searchContainerStyle, THEME]);
/**
* The search text input style.
* @returns {object}
*/
const _searchTextInputStyle = useMemo(() => ([
textStyle,
THEME.searchTextInput,
...[searchTextInputStyle].flat(),
]), [textStyle, searchTextInputStyle, THEME]);
/**
* The close icon container style.
* @returns {object}
*/
const _closeIconContainerStyle = useMemo(() => ([
RTL_STYLE(rtl, THEME.closeIconContainer),
...[closeIconContainerStyle].flat(),
]), [rtl, closeIconContainerStyle, THEME]);
/**
* The close icon style.
* @returns {object}
*/
const _closeIconStyle = useMemo(() => ([
THEME.closeIcon,
...[closeIconStyle].flat(),
]), [closeIconStyle, THEME]);
/**
* The list message container style.
* @returns {objects}
*/
const _listMessageContainerStyle = useMemo(() => ([
THEME.listMessageContainer,
...[listMessageContainerStyle].flat(),
]), [listMessageContainerStyle, THEME]);
/**
* The list message text style.
* @returns {object}
*/
const _listMessageTextStyle = useMemo(() => ([
THEME.listMessageText,
...[listMessageTextStyle].flat(),
]), [listMessageTextStyle, THEME]);
/**
* onPress item.
*/
const onPressItem = useCallback((item, customItem = false) => {
if (customItem !== false) {
setItems((state) => [...state, item]);
}
setValue((state) => {
if (multiple) {
const _state = state !== null ? [...state] : [];
if (_state.includes(item[_schema.value])) {
// Remove the value
if (Number.isInteger(min) && min >= _state.length) {
return state;
}
const index = _state.findIndex((x) => x === item[_schema.value]);
_state.splice(index, 1);
} else {
// Add the value
if (Number.isInteger(max) && max <= _state.length) {
return state;
}
_state.push(item[_schema.value]);
}
return _state;
}
return item[_schema.value];
});
setNecessaryItems((state) => {
if (multiple) {
const _state = [...state];
if (_state.findIndex((x) => x[_schema.value] === item[_schema.value]) > -1) {
// Remove the item
if (Number.isInteger(min) && min >= _state.length) {
return state;
}
const index = _state.findIndex((x) => x[_schema.value] === item[_schema.value]);
_state.splice(index, 1);
return _state;
}
// Add the item
if (Number.isInteger(max) && max <= _state.length) {
return state;
}
_state.push(item);
return _state;
}
return [item];
});
if (closeAfterSelecting && !multiple) onPressClose();
}, [
setValue,
multiple,
min,
max,
onPressClose,
closeAfterSelecting,
multiple,
setItems,
_schema,
]);
/**
* The tick icon component.
* @returns {JSX.Element}
*/
const _TickIconComponent = useCallback(() => {
if (!showTickIcon) return null;
let Component;
if (TickIconComponent !== null) Component = <TickIconComponent style={_tickIconStyle} />;
else Component = <Image source={ICON.TICK} style={_tickIconStyle} />;
return (
<View style={_tickIconContainerStyle}>
{Component}
</View>
);
}, [TickIconComponent, _tickIconStyle, _tickIconContainerStyle, showTickIcon, ICON]);
/**
* The renderItem component.
* @returns {JSX.Element}
*/
const RenderItemComponent = useMemo(() => (renderListItem !== null ? renderListItem : RenderListItem), [renderListItem]);
/**
* The selected item container style.
* @returns {object}
*/
const _selectedItemContainerStyle = useMemo(() => ([
THEME.selectedItemContainer,
selectedItemContainerStyle,
]), [THEME, selectedItemContainerStyle]);
/**
* The selected item label style.
* @returns {object}
*/
const _selectedItemLabelStyle = useMemo(() => ([
THEME.selectedItemLabel,
selectedItemLabelStyle,
]), [THEME, selectedItemLabelStyle]);
/**
* The disabled item container style.
* @returns {object}
*/
const _disabledItemContainerStyle = useMemo(() => ([
THEME.disabledItemContainer,
disabledItemContainerStyle,
]), [THEME, disabledItemContainerStyle]);
/**
* The disabled item label style.
* @returns {object}
*/
const _disabledItemLabelStyle = useMemo(() => ([
THEME.disabledItemContainer,
disabledItemLabelStyle,
]), [THEME, disabledItemLabelStyle]);
/**
* Render list item.
* @returns {JSX.Element}
*/
const __renderListItem = useCallback(({ item }) => {
let IconComponent = item[_schema.icon] ?? null;
if (IconComponent) {
IconComponent = (
<View style={_iconContainerStyle}>
<IconComponent />
</View>
);
}
let isSelected;
if (multiple) {
isSelected = _value.includes(item[_schema.value]);
} else {
isSelected = _value === item[_schema.value];
}
return (
<RenderItemComponent
rtl={rtl}
item={item}
label={item[_schema.label]}
value={item[_schema.value]}
parent={item[_schema.parent] ?? null}
selectable={item[_schema.selectable] ?? null}
disabled={item[_schema.disabled] ?? false}
custom={item.custom ?? false}
isSelected={isSelected}
IconComponent={IconComponent}
TickIconComponent={_TickIconComponent}
listItemContainerStyle={_listItemContainerStyle}
listItemContainerStyle2={_listItemContainerStyle2}
listItemLabelStyle={_listItemLabelStyle}
listChildContainerStyle={listChildContainerStyle}
listChildLabelStyle={listChildLabelStyle}
listParentContainerStyle={listParentContainerStyle}
listParentLabelStyle={listParentLabelStyle}
customItemContainerStyle={customItemContainerStyle}
customItemLabelStyle={customItemLabelStyle}
selectedItemContainerStyle={_selectedItemContainerStyle}
selectedItemLabelStyle={_selectedItemLabelStyle}
disabledItemContainerStyle={_disabledItemContainerStyle}
disabledItemLabelStyle={_disabledItemLabelStyle}
categorySelectable={categorySelectable}
onPress={onPressItem}
theme={theme}
THEME={THEME}
/>
);
}, [
rtl,
RenderItemComponent,
_listItemLabelStyle,
_iconContainerStyle,
listChildContainerStyle,
listChildLabelStyle,
listParentContainerStyle,
listParentLabelStyle,
_listItemContainerStyle,
_listItemLabelStyle,
customItemContainerStyle,
customItemLabelStyle,
_selectedItemContainerStyle,
_selectedItemLabelStyle,
_disabledItemContainerStyle,
_disabledItemLabelStyle,
_TickIconComponent,
_schema,
_value,
multiple,
categorySelectable,
onPressItem,
theme,
THEME,
]);
/**
* The item separator.
* @returns {JSX.Element|null}
*/
const ItemSeparatorComponent = useCallback(() => {
if (!itemSeparator) return null;
return (
<View style={[
THEME.itemSeparator,
...[itemSeparatorStyle].flat(),
]}
/>
);
}, [itemSeparator, itemSeparatorStyle, THEME]);
/**
* The search placeholder.
* @returns {string}
*/
const _searchPlaceholder = useMemo(() => {
if (searchPlaceholder !== null) return searchPlaceholder;
return ('search for or create new tags');
}, [searchPlaceholder, 'add tags']);
/**
* onChangeSearchText.
* @param {string} text
*/
const _onChangeSearchText = useCallback((text) => {
setSearchText(text);
onChangeSearchText(text);
}, [onChangeSearchText]);
/**
* The close icon component.
* @returns {JSX.Element}
*/
const _CloseIconComponent = useMemo(() => {
if (listMode !== LIST_MODE.MODAL) return null;
let Component;
if (CloseIconComponent !== null) Component = <CloseIconComponent style={_closeIconStyle} />;
else Component = <Image source={ICON.CLOSE} style={_closeIconStyle} />;
return (
<TouchableOpacity style={_closeIconContainerStyle} onPress={onPressClose}>
{Component}
</TouchableOpacity>
);
}, [listMode, CloseIconComponent, _closeIconStyle, _closeIconContainerStyle, onPressClose, ICON]);
/**
* Indicates if the search component is visible.
* @returns {boolean}
*/
const isSearchComponentVisible = useMemo(() => {
if (listMode === LIST_MODE.MODAL) return true;
return searchable;
}, [listMode, searchable]);
/**
* The search component.
* @returns {JSX.Element}
*/
const SearchComponent = useMemo(() => isSearchComponentVisible && (
<View style={_searchContainerStyle}>
{
searchable && (
<TextInput
value={searchText}
onChangeText={_onChangeSearchText}
style={_searchTextInputStyle}
placeholder={_searchPlaceholder}
placeholderTextColor={searchPlaceholderTextColor}
{...searchTextInputProps}
/>
)
}
{_CloseIconComponent}
<AddCollectionInputButton onPress={handleNewCollectionPress} />
</View>
), [
searchable,
isSearchComponentVisible,
_onChangeSearchText,
_searchContainerStyle,
_searchTextInputStyle,
_searchPlaceholder,
searchPlaceholderTextColor,
searchText,
searchTextInputProps,
]);
/**
* The dropdown component wrapper.
* @returns {JSX.Element}
*/
const DropDownComponentWrapper = useCallback((Component) => (
<View style={_dropDownContainerStyle}>
{SearchComponent}
{Component}
</View>
), [_dropDownContainerStyle, SearchComponent]);
/**
* The ActivityIndicatorComponent.
* @returns {JSX.Element}
*/
const _ActivityIndicatorComponent = useCallback(() => {
let Component;
if (ActivityIndicatorComponent !== null) Component = ActivityIndicatorComponent;
else Component = ActivityIndicator;
return <Component size={activityIndicatorSize} color={activityIndicatorColor} />;
}, [ActivityIndicatorComponent, activityIndicatorSize, activityIndicatorColor]);
/**
* The ListEmptyComponent.
* @returns {JSX.Element}
*/
const _ListEmptyComponent = useCallback(() => {
let Component;
const message = ('');
if (ListEmptyComponent !== null) Component = ListEmptyComponent;
else Component = ListEmpty;
return (
<Component
listMessageContainerStyle={_listMessageContainerStyle}
listMessageTextStyle={_listMessageTextStyle}
ActivityIndicatorComponent={_ActivityIndicatorComponent}
loading={loading}
message={message}
/>
);
}, [
'message',
_listMessageContainerStyle,
_listMessageTextStyle,
ListEmptyComponent,
_ActivityIndicatorComponent,
loading,
]);
/**
* The dropdown flatlist component.
* @returns {JSX.Element}
*/
const DropDownFlatListComponent = useMemo(() => (
<FlatList
style={styles.flex}
contentContainerStyle={THEME.flatListContentContainer}
ListEmptyComponent={_ListEmptyComponent}
data={_items}
renderItem={__renderListItem}
keyExtractor={keyExtractor}
extraData={_value}
ItemSeparatorComponent={ItemSeparatorComponent}
{...flatListProps}
/>
), [
_items,
_value,
__renderListItem,
keyExtractor,
ItemSeparatorComponent,
flatListProps,
_ListEmptyComponent,
THEME,
]);
/**
* The dropdown scrollview component.
* @returns {JSX.Element}
*/
const DropDownScrollViewComponent = useMemo(() => (
<ScrollView nestedScrollEnabled {...scrollViewProps}>
{_items.map((item, index) => (
<Fragment key={item[_itemKey]}>
{index > 0 && ItemSeparatorComponent()}
{__renderListItem({ item })}
</Fragment>
))}
{_items.length === 0 && _ListEmptyComponent()}
</ScrollView>
), [__renderListItem, _itemKey, scrollViewProps, _ListEmptyComponent]);
/**
* The dropdown modal component.
* @returns {JSX.Element}
*/
const DropDownModalComponent = useMemo(() => (
<Modal visible={open} presentationStyle="fullScreen" {...modalProps}>
<SafeAreaView style={[_modalContentContainerStyle]}>
{SearchComponent}
{DropDownFlatListComponent}
</SafeAreaView>
</Modal>
), [open, SearchComponent, _modalContentContainerStyle, modalProps]);
/**
* The dropdown component.
* @returns {JSX.Element}
*/
const DropDownComponent = useMemo(() => {
switch (listMode) {
case LIST_MODE.FLATLIST: return DropDownComponentWrapper(DropDownFlatListComponent);
case LIST_MODE.SCROLLVIEW: return DropDownComponentWrapper(DropDownScrollViewComponent);
case LIST_MODE.MODAL: return DropDownModalComponent;
default: //
}
}, [
listMode,
DropDownFlatListComponent,
DropDownScrollViewComponent,
DropDownModalComponent,
DropDownComponentWrapper,
]);
/**
* The body of the dropdown component.
* @returns {JSX.Element}
*/
const DropDownBodyComponent = useMemo(() => {
if (open || listMode === LIST_MODE.MODAL) return DropDownComponent;
return null;
}, [open, listMode, DropDownComponent]);
/**
* onRef.
*/
const onRef = useCallback((ref) => {
pickerRef.current = ref;
}, []);
/**
* Pointer events.
* @returns {string}
*/
const pointerEvents = useMemo(() => (disabled ? 'none' : 'auto'), [disabled]);
return (
<View style={_containerStyle} {...containerProps}>
<TouchableOpacity style={_style} onPress={__onPress} onLayout={__onLayout} {...props} ref={onRef} pointerEvents={pointerEvents} disabled={disabled}>
{_BodyComponent}
{_ArrowComponent}
</TouchableOpacity>
{DropDownBodyComponent}
</View>
);
}
Example #18
Source File: TagSearchView.js From discovery-mobile-ui with MIT License | 4 votes |
function Picker({
value = null,
items = [],
open,
placeholder = null,
closeAfterSelecting = true,
labelProps = {},
disabled = false,
disabledStyle = {},
placeholderStyle = {},
containerStyle = {},
style = {},
textStyle = {},
labelStyle = {},
arrowIconStyle = {},
tickIconStyle = {},
closeIconStyle = {},
badgeStyle = {},
badgeTextStyle = {},
badgeDotStyle = {},
iconContainerStyle = {},
searchContainerStyle = {},
searchTextInputStyle = {},
searchPlaceholderTextColor = Colors.GREY,
dropDownContainerStyle = {},
modalContentContainerStyle = {},
arrowIconContainerStyle = {},
closeIconContainerStyle = {},
tickIconContainerStyle = {},
listItemContainerStyle = {},
listItemLabelStyle = {},
listChildContainerStyle = {},
listChildLabelStyle = {},
listParentContainerStyle = {},
listParentLabelStyle = {},
selectedItemContainerStyle = {},
selectedItemLabelStyle = {},
disabledItemContainerStyle = {},
disabledItemLabelStyle = {},
customItemContainerStyle = {},
customItemLabelStyle = {},
listMessageContainerStyle = {},
listMessageTextStyle = {},
itemSeparatorStyle = {},
badgeSeparatorStyle = {},
listMode = 'FLATLIST',
categorySelectable = true,
searchable = false,
searchPlaceholder = null,
translation = {},
multiple = false,
multipleText = null,
mode = 'BADGE',
maxHeight = 200,
renderBadgeItem = null,
renderListItem = null,
itemSeparator = false,
bottomOffset = 0,
badgeColors = BADGE_COLORS,
badgeDotColors = BADGE_DOT_COLORS,
showArrowIcon = true,
showBadgeDot = true,
showTickIcon = true,
ArrowUpIconComponent = null,
ArrowDownIconComponent = null,
TickIconComponent = null,
CloseIconComponent = null,
ListEmptyComponent = null,
ActivityIndicatorComponent = null,
activityIndicatorSize = 30,
activityIndicatorColor = Colors.GREY,
props = {},
modalProps = {},
flatListProps = {},
scrollViewProps = {},
searchTextInputProps = {},
loading = false,
min = null,
max = null,
addCustomItem = false,
setOpen = () => {},
setItems = () => {},
disableBorderRadius = true,
containerProps = {},
onLayout = (e) => {},
onPress = (open) => {},
onOpen = () => {},
onClose = () => {},
setValue = (callback) => {},
onChangeValue = (value) => {},
onChangeSearchText = (text) => {},
zIndex = 5000,
zIndexInverse = 6000,
rtl = false,
dropDownDirection = DROPDOWN_DIRECTION.DEFAULT,
disableLocalSearch = false,
theme = THEMES.DEFAULT,
}) {
const [necessaryItems, setNecessaryItems] = useState([]);
const [searchText, setSearchText] = useState('');
const [pickerHeight, setPickerHeight] = useState(0);
const [direction, setDirection] = useState(GET_DROPDOWN_DIRECTION(dropDownDirection));
const badgeFlatListRef = useRef();
const pickerRef = useRef(null);
const initialization = useRef(false);
const THEME = useMemo(() => THEMES[theme].default, [theme]);
const ICON = useMemo(() => THEMES[theme].ICONS, [theme]);
const AddCollectionInputButton = ({ onPress }) => (
<TouchableOpacity onPress={onPress}>
<Feather name="plus-square" size={24} color={Colors.headerIcon} />
</TouchableOpacity>
);
const handleNewCollectionPress = () => {
const temp_items = items.slice();
temp_items.push({ label: searchText, value: searchText });
setItems(temp_items);
let temp_values = [];
if (value != null) {
temp_values = value.slice();
}
temp_values.push(searchText);
setValue(temp_values);
};
useEffect(() => {
}, [items]);
/**
* The item schema.
* @returns {object}
*/
const schema = useMemo(() => ({ ...SCHEMA, ...{} }), [{}]);
/**
* componentDidMount.
*/
useEffect(() => {
// LogBox.ignoreLogs(['VirtualizedLists should never be nested']);
// Get initial seleted items
let initialSelectedItems = [];
const valueNotNull = value !== null && (Array.isArray(value) && value.length !== 0);
if (valueNotNull) {
if (multiple) {
initialSelectedItems = items.filter((item) => value.includes(item[schema.value]));
} else {
initialSelectedItems = items.find((item) => item[schema.value] === value);
}
}
setNecessaryItems(initialSelectedItems);
}, []);
/**
* Update necessary items.
*/
useEffect(() => {
setNecessaryItems((state) => [...state].map((item) => {
const _item = items.find((x) => x[schema.value] === item[schema.value]);
if (_item) {
return { ...item, ..._item };
}
return item;
}));
}, [items]);
/**
* Sync necessary items.
*/
useEffect(() => {
if (multiple) {
setNecessaryItems((state) => {
if (value === null || (Array.isArray(value) && value.length === 0)) return [];
const _state = [...state].filter((item) => value.includes(item[schema.value]));
const newItems = value.reduce((accumulator, currentValue) => {
const index = _state.findIndex((item) => item[schema.value] === currentValue);
if (index === -1) {
const item = items.find((item) => item[schema.value] === currentValue);
if (item) {
return [...accumulator, item];
}
return accumulator;
}
return accumulator;
}, []);
return [..._state, ...newItems];
});
} else {
const state = [];
if (value !== null) {
const item = items.find((item) => item[schema.value] === value);
if (item) {
state.push(item);
}
}
setNecessaryItems(state);
}
if (initialization.current) {
onChangeValue(value);
} else {
initialization.current = true;
}
}, [value, items]);
/**
* dropDownDirection changed.
*/
useEffect(() => {
setDirection(GET_DROPDOWN_DIRECTION(dropDownDirection));
}, [dropDownDirection]);
/**
* mode changed.
*/
useEffect(() => {
if (mode === MODE.SIMPLE) badgeFlatListRef.current = null;
}, [mode]);
/**
* onPressClose.
*/
const onPressClose = useCallback(() => {
setOpen(false);
setSearchText('');
onClose();
}, [setOpen, onClose]);
/**
* onPressClose.
*/
const onPressOpen = useCallback(() => {
setOpen(true);
onOpen();
}, [setOpen, onOpen]);
/**
* onPressToggle.
*/
const onPressToggle = useCallback(() => {
const isOpen = !open;
setOpen(isOpen);
setSearchText('');
if (isOpen) onOpen();
else onClose();
return isOpen;
}, [open, setOpen, onOpen, onClose]);
/**
* The sorted items.
* @returns {object}
*/
const sortedItems = useMemo(() => {
const sortedItems = items.filter((item) => item[schema.parent] === undefined || item[schema.parent] === null);
const children = items.filter((item) => item[schema.parent] !== undefined && item[schema.parent] !== null);
children.forEach((child) => {
const index = items.findIndex((item) => item[schema.parent] === child[schema.parent] || item[schema.value] === child[schema.parent]);
if (index > -1) {
sortedItems.splice(index + 1, 0, child);
}
});
return sortedItems;
}, [items, schema]);
/**
* The items.
* @returns {object}
*/
const _items = useMemo(() => {
if (searchText.length === 0) {
return sortedItems;
}
if (disableLocalSearch) return sortedItems;
const values = [];
const results = sortedItems.filter((item) => {
if (item[schema.label].toLowerCase().includes(searchText.toLowerCase())) {
values.push(item[schema.value]);
return true;
}
return false;
});
results.forEach((item, index) => {
if (item[schema.parent] === undefined || item[schema.parent] === null || values.includes(item[schema.parent])) return;
const parent = sortedItems.find((x) => x[schema.value] === item[schema.parent]);
values.push(item[schema.parent]);
results.splice(index, 0, parent);
});
if ((results.length === 0 || results.findIndex((item) => item[schema.label].toLowerCase() === searchText.toLowerCase()) === -1) && addCustomItem) {
results.push({
[schema.label]: searchText,
[schema.value]: searchText.replace(' ', '-'),
custom: true,
});
}
return results;
}, [sortedItems, schema, searchText, addCustomItem]);
/**
* The value.
* @returns {string|object|null}}
*/
const _value = useMemo(() => {
if (multiple) {
if (value === null) return [];
return [...new Set(value)];
}
return value;
}, [value, multiple]);
/**
* Selected items only for multiple items.
* @returns {object}
*/
const selectedItems = useMemo(() => {
if (!multiple) return [];
return necessaryItems.filter((item) => _value.includes(item[schema.value]));
}, [necessaryItems, _value, schema, multiple]);
/**
* The placeholder.
* @returns {string}
*/
const _placeholder = useMemo(() => placeholder ?? ('PLACEHOLDER'), [placeholder, 'PLACEHOLDER']);
/**
* The multiple text.
* @returns {string}
*/
const _multipleText = useMemo(() => multipleText ?? ('SELECTED_ITEMS_COUNT_TEXT'), [multipleText, 'PLACEHOLDER']);
/**
* The mode.
* @returns {string}
*/
const _mode = useMemo(() => {
try {
return mode;
} catch (e) {
return MODE.SIMPLE;
}
}, [mode]);
/**
* Indicates whether the value is null.
* @returns {boolean}
*/
const isNull = useMemo(() => {
if (_value === null || (Array.isArray(_value) && _value.length === 0)) return true;
return necessaryItems.length === 0;
}, [necessaryItems, _value]);
/**
* Get the selected item.
* @returns {object}
*/
const getSelectedItem = useCallback(() => {
if (multiple) return _value;
if (isNull) return null;
try {
return necessaryItems.find((item) => item[schema.value] === _value);
} catch (e) {
return null;
}
}, [_value, necessaryItems, isNull, multiple]);
/**
* Get the label of the selected item.
* @param {string|null} fallback
* @returns {string}
*/
const getLabel = useCallback((fallback = null) => {
const item = getSelectedItem();
if (multiple) {
if (item.length > 0) return _multipleText.replace('{count}', item.length);
return fallback;
}
try {
return item[schema.label];
} catch (e) {
return fallback;
}
}, [getSelectedItem, multiple, _multipleText, schema]);
/**
* The label of the selected item / placeholder.
*/
const _selectedItemLabel = useMemo(() => getLabel(_placeholder), [getLabel, _placeholder]);
/**
* The icon of the selected item.
*/
const _selectedItemIcon = useCallback(() => {
if (multiple) return null;
const item = getSelectedItem();
try {
return item[schema.icon] ?? null;
} catch (e) {
return null;
}
}, [getSelectedItem, multiple, schema]);
/**
* onPress.
*/
const __onPress = useCallback(async () => {
const isOpen = !open;
onPress(isOpen);
if (isOpen && dropDownDirection === DROPDOWN_DIRECTION.AUTO) {
const [, y] = await new Promise((resolve) => pickerRef.current.measureInWindow((...args) => resolve(args)));
const size = y + maxHeight + pickerHeight + bottomOffset;
setDirection(size < WINDOW_HEIGHT ? 'top' : 'bottom');
}
onPressToggle();
}, [
open,
onPressToggle,
onPress,
pickerRef,
maxHeight,
pickerHeight,
bottomOffset,
dropDownDirection,
]);
/**
* onLayout.
*/
const __onLayout = useCallback((e) => {
e.persist();
onLayout(e);
setPickerHeight(e.nativeEvent.layout.height);
}, [onLayout]);
/**
* Disable borderRadius for the picker.
* @returns {object}
*/
const pickerNoBorderRadius = useMemo(() => {
if (listMode === LIST_MODE.MODAL) return null;
if (disableBorderRadius && open) {
return direction === 'top' ? {
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
} : {
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
};
}
return {};
}, [disableBorderRadius, open, direction, listMode]);
/**
* Disable borderRadius for the drop down.
* @returns {object}
*/
const dropDownNoBorderRadius = useMemo(() => {
if (listMode === LIST_MODE.MODAL) return null;
if (disableBorderRadius && open) {
return direction === 'top' ? {
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
} : {
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
};
}
}, [disableBorderRadius, open, direction, listMode]);
/**
* The disabled style.
* @returns {object}
*/
const _disabledStyle = useMemo(() => disabled && disabledStyle, [disabled, disabledStyle]);
/**
* The zIndex.
* @returns {number}
*/
const _zIndex = useMemo(() => {
if (open) {
return direction === 'top' ? zIndex : zIndexInverse;
}
return zIndex;
}, [zIndex, zIndexInverse, direction, open]);
/**
* The style.
* @returns {object}
*/
const _style = useMemo(() => ([
RTL_DIRECTION(rtl, THEME.style), {
zIndex: _zIndex,
},
...[style].flat(),
pickerNoBorderRadius,
]), [rtl, style, pickerNoBorderRadius, _zIndex, THEME]);
/**
* The placeholder style.
* @returns {object}
*/
const _placeholderStyle = useMemo(() => isNull && placeholderStyle, [isNull, placeholderStyle]);
/**
* The style of the label.
* @returns {object}
*/
const _labelStyle = useMemo(() => ([
THEME.label,
...[textStyle].flat(),
...[!isNull && labelStyle].flat(),
...[_placeholderStyle].flat(),
]), [textStyle, _placeholderStyle, labelStyle, isNull, THEME]);
/**
* The arrow icon style.
* @returns {object}
*/
const _arrowIconStyle = useMemo(() => ([
THEME.arrowIcon,
...[arrowIconStyle].flat(),
]), [arrowIconStyle, THEME]);
/**
* The dropdown container style.
* @returns {object}
*/
const _dropDownContainerStyle = useMemo(() => ([
THEME.dropDownContainer, {
[direction]: pickerHeight - 1,
maxHeight,
zIndex: _zIndex,
},
...[dropDownContainerStyle].flat(),
dropDownNoBorderRadius,
]), [
dropDownContainerStyle,
pickerHeight,
maxHeight,
dropDownNoBorderRadius,
direction,
_zIndex,
THEME,
]);
/**
* The modal content container style.
* @returns {object}
*/
const _modalContentContainerStyle = useMemo(() => ([
THEME.modalContentContainer,
...[modalContentContainerStyle].flat(),
]), [modalContentContainerStyle, THEME]);
/**
* The zIndex of the container.
* @returns {object}
*/
const zIndexContainer = useMemo(() => Platform.OS !== 'android' && {
zIndex: _zIndex,
}, [_zIndex]);
/**
* The container style.
* @returns {object}
*/
const _containerStyle = useMemo(() => ([
THEME.container,
zIndexContainer,
...[containerStyle].flat(),
...[_disabledStyle].flat(),
]), [zIndexContainer, containerStyle, _disabledStyle, THEME]);
/**
* The arrow icon container style.
* @returns {object}
*/
const _arrowIconContainerStyle = useMemo(() => ([
RTL_STYLE(rtl, THEME.arrowIconContainer),
...[arrowIconContainerStyle].flat(),
]), [rtl, arrowIconContainerStyle, THEME]);
/**
* The arrow component.
* @returns {JSX.Element}
*/
const _ArrowComponent = useMemo(() => {
if (!showArrowIcon) return null;
let Component;
if (open && ArrowUpIconComponent !== null) Component = <ArrowUpIconComponent style={_arrowIconStyle} />;
else if (!open && ArrowDownIconComponent !== null) Component = <ArrowDownIconComponent style={_arrowIconStyle} />;
else Component = <Image source={open ? ICON.ARROW_UP : ICON.ARROW_DOWN} style={_arrowIconStyle} />;
return (
<View style={_arrowIconContainerStyle}>
{Component}
</View>
);
}, [
showArrowIcon,
open,
ArrowUpIconComponent,
ArrowDownIconComponent,
_arrowIconStyle,
_arrowIconContainerStyle,
ICON,
]);
/**
* The icon container style.
* @returns {object}
*/
const _iconContainerStyle = useMemo(() => ([
RTL_STYLE(rtl, THEME.iconContainer),
...[iconContainerStyle].flat(),
]), [rtl, iconContainerStyle, THEME]);
/**
* The selected item icon component.
* @returns {JSX.Element|null}
*/
const SelectedItemIconComponent = useMemo(() => {
const Component = _selectedItemIcon();
return Component !== null && (
<View style={_iconContainerStyle}>
<Component />
</View>
);
}, [_selectedItemIcon, _iconContainerStyle]);
/**
* The simple body component.
* @returns {JSX.Element}
*/
const SimpleBodyComponent = useMemo(() => (
<>
{SelectedItemIconComponent}
<Text style={_labelStyle} {...labelProps}>
{_selectedItemLabel}
</Text>
</>
), [SelectedItemIconComponent, _labelStyle, labelProps, _selectedItemLabel]);
/**
* onPress badge.
*/
const onPressBadge = useCallback((value) => {
setValue((state) => {
const _state = [...state];
const index = _state.findIndex((item) => item === value);
_state.splice(index, 1);
return _state;
});
}, [setValue]);
/**
* The badge colors.
* @returns {object}
*/
const _badgeColors = useMemo(() => {
if (typeof badgeColors === 'string') return [badgeColors];
return badgeColors;
}, [badgeColors]);
/**
* The badge dot colors.
* @returns {object}
*/
const _badgeDotColors = useMemo(() => {
if (typeof badgeDotColors === 'string') return [badgeDotColors];
return badgeDotColors;
}, [badgeDotColors]);
/**
* Get badge color.
* @param {string} str
* @returns {string}
*/
const getBadgeColor = useCallback((str) => {
str = `${str}`;
const index = Math.abs(ASCII_CODE(str)) % _badgeColors.length;
return _badgeColors[index];
}, [_badgeColors]);
/**
* Get badge dot color.
* @param {string} str
* @returns {string}
*/
const getBadgeDotColor = useCallback((str) => {
str = `${str}`;
const index = Math.abs(ASCII_CODE(str)) % _badgeDotColors.length;
return _badgeDotColors[index];
}, [_badgeDotColors]);
/**
* The render badge component.
* @returns {JSX.Element}
*/
const RenderBadgeComponent = useMemo(() => (renderBadgeItem !== null ? renderBadgeItem : RenderBadgeItem), [renderBadgeItem]);
/**
* Render badge.
* @returns {JSX.Element}
*/
const __renderBadge = useCallback(({ item }) => (
<RenderBadgeComponent
rtl={rtl}
label={item[schema.label]}
value={item[schema.value]}
IconComponent={item[schema.icon] ?? null}
textStyle={textStyle}
badgeStyle={badgeStyle}
badgeTextStyle={badgeTextStyle}
badgeDotStyle={badgeDotStyle}
getBadgeColor={getBadgeColor}
getBadgeDotColor={getBadgeDotColor}
showBadgeDot={showBadgeDot}
onPress={onPressBadge}
theme={theme}
THEME={THEME}
/>
), [
rtl,
schema,
textStyle,
badgeStyle,
badgeTextStyle,
badgeDotStyle,
getBadgeColor,
getBadgeDotColor,
showBadgeDot,
onPressBadge,
theme,
THEME,
]);
/**
* The badge key.
* @returns {string}
*/
const itemKey = useMemo(() => schema.value, [null, schema]);
/**
* The key extractor.
* @returns {string}
*/
const keyExtractor = useCallback((item) => `${item[itemKey]}`, [itemKey]);
/**
* The badge separator style.
* @returns {object}
*/
const _badgeSeparatorStyle = useMemo(() => ([
THEME.badgeSeparator,
...[badgeSeparatorStyle].flat(),
]), [badgeSeparatorStyle, THEME]);
/**
* The badge separator component.
* @returns {JSX.Element}
*/
const BadgeSeparatorComponent = useCallback(() => (
<View style={_badgeSeparatorStyle} />
), [_badgeSeparatorStyle]);
/**
* The label container.
* @returns {object}
*/
const labelContainer = useMemo(() => ([
THEME.labelContainer, rtl && {
transform: [
{ scaleX: -1 },
],
},
]), [rtl, THEME]);
/**
* Badge list empty component.
* @returns {JSX.Element}
*/
const BadgeListEmptyComponent = useCallback(() => (
<View style={labelContainer}>
<Text style={_labelStyle} {...labelProps}>
{_placeholder}
</Text>
</View>
), [_labelStyle, labelContainer, labelProps, _placeholder]);
/**
* Set ref.
*/
const setBadgeFlatListRef = useCallback((ref) => {
badgeFlatListRef.current = ref;
}, []);
/**
* The badge body component.
* @returns {JSX.Element}
*/
const BadgeBodyComponent = useMemo(() => (
<FlatList
ref={setBadgeFlatListRef}
data={selectedItems}
renderItem={__renderBadge}
horizontal
showsHorizontalScrollIndicator={false}
keyExtractor={keyExtractor}
ItemSeparatorComponent={BadgeSeparatorComponent}
ListEmptyComponent={BadgeListEmptyComponent}
style={THEME.listBody}
contentContainerStyle={THEME.listBodyContainer}
inverted={rtl}
/>
), [
rtl,
selectedItems,
__renderBadge,
keyExtractor,
BadgeSeparatorComponent,
BadgeListEmptyComponent,
THEME,
]);
/**
* The body component.
*/
const _BodyComponent = useMemo(() => {
switch (_mode) {
case MODE.SIMPLE: return SimpleBodyComponent;
case MODE.BADGE: return multiple ? BadgeBodyComponent : SimpleBodyComponent;
default: //
}
}, [_mode, SimpleBodyComponent, BadgeBodyComponent, multiple]);
/**
* The list item container style.
* @returns {object}
*/
const _listItemContainerStyle = useMemo(() => ([
RTL_DIRECTION(rtl, THEME.listItemContainer),
...[listItemContainerStyle].flat(),
]), [rtl, listItemContainerStyle, THEME]);
const _listItemContainerStyle2 = useMemo(() => ([
RTL_DIRECTION(rtl, THEME.listItemContainer2),
...[listItemContainerStyle].flat(),
]), [rtl, listItemContainerStyle, THEME]);
/**
* The tick icon container style.
* @returns {object}
*/
const _tickIconContainerStyle = useMemo(() => ([
RTL_STYLE(rtl, THEME.tickIconContainer),
...[tickIconContainerStyle].flat(),
]), [rtl, tickIconContainerStyle, THEME]);
/**
* The list item label style.
* @returns {object}
*/
const _listItemLabelStyle = useMemo(() => ([
THEME.listItemLabel,
...[textStyle].flat(),
...[listItemLabelStyle].flat(),
]), [textStyle, listItemLabelStyle, THEME]);
/**
* The tick icon style.
* @returns {object}
*/
const _tickIconStyle = useMemo(() => ([
THEME.tickIcon,
...[tickIconStyle].flat(),
]), [tickIconStyle, THEME]);
/**
* The search container style.
* @returns {object}
*/
const _searchContainerStyle = useMemo(() => ([
RTL_DIRECTION(rtl, THEME.searchContainer),
...[searchContainerStyle].flat(), !searchable && listMode === LIST_MODE.MODAL && {
flexDirection: 'row-reverse',
},
]), [rtl, listMode, searchable, searchContainerStyle, THEME]);
/**
* The search text input style.
* @returns {object}
*/
const _searchTextInputStyle = useMemo(() => ([
textStyle,
THEME.searchTextInput,
...[searchTextInputStyle].flat(),
]), [textStyle, searchTextInputStyle, THEME]);
/**
* The close icon container style.
* @returns {object}
*/
const _closeIconContainerStyle = useMemo(() => ([
RTL_STYLE(rtl, THEME.closeIconContainer),
...[closeIconContainerStyle].flat(),
]), [rtl, closeIconContainerStyle, THEME]);
/**
* The close icon style.
* @returns {object}
*/
const _closeIconStyle = useMemo(() => ([
THEME.closeIcon,
...[closeIconStyle].flat(),
]), [closeIconStyle, THEME]);
/**
* The list message container style.
* @returns {objects}
*/
const _listMessageContainerStyle = useMemo(() => ([
THEME.listMessageContainer,
...[listMessageContainerStyle].flat(),
]), [listMessageContainerStyle, THEME]);
/**
* The list message text style.
* @returns {object}
*/
const _listMessageTextStyle = useMemo(() => ([
THEME.listMessageText,
...[listMessageTextStyle].flat(),
]), [listMessageTextStyle, THEME]);
/**
* onPress item.
*/
const onPressItem = useCallback((item, customItem = false) => {
if (customItem !== false) {
setItems((state) => [...state, item]);
}
setValue((state) => {
if (multiple) {
const _state = state !== null ? [...state] : [];
if (_state.includes(item[schema.value])) {
// Remove the value
if (Number.isInteger(min) && min >= _state.length) {
return state;
}
const index = _state.findIndex((x) => x === item[schema.value]);
_state.splice(index, 1);
} else {
// Add the value
if (Number.isInteger(max) && max <= _state.length) {
return state;
}
_state.push(item[schema.value]);
}
return _state;
}
return item[schema.value];
});
setNecessaryItems((state) => {
if (multiple) {
const _state = [...state];
if (_state.findIndex((x) => x[schema.value] === item[schema.value]) > -1) {
// Remove the item
if (Number.isInteger(min) && min >= _state.length) {
return state;
}
const index = _state.findIndex((x) => x[schema.value] === item[schema.value]);
_state.splice(index, 1);
return _state;
}
// Add the item
if (Number.isInteger(max) && max <= _state.length) {
return state;
}
_state.push(item);
return _state;
}
return [item];
});
if (closeAfterSelecting && !multiple) onPressClose();
}, [
setValue,
multiple,
min,
max,
onPressClose,
closeAfterSelecting,
multiple,
setItems,
schema,
]);
/**
* The tick icon component.
* @returns {JSX.Element}
*/
const _TickIconComponent = useCallback(() => {
if (!showTickIcon) return null;
let Component;
if (TickIconComponent !== null) Component = <TickIconComponent style={_tickIconStyle} />;
else Component = <Image source={ICON.TICK} style={_tickIconStyle} />;
return (
<View style={_tickIconContainerStyle}>
{Component}
</View>
);
}, [TickIconComponent, _tickIconStyle, _tickIconContainerStyle, showTickIcon, ICON]);
/**
* The renderItem component.
* @returns {JSX.Element}
*/
const RenderItemComponent = useMemo(() => (renderListItem !== null ? renderListItem : RenderListItem), [renderListItem]);
/**
* The selected item container style.
* @returns {object}
*/
const _selectedItemContainerStyle = useMemo(() => ([
THEME.selectedItemContainer,
selectedItemContainerStyle,
]), [THEME, selectedItemContainerStyle]);
/**
* The selected item label style.
* @returns {object}
*/
const _selectedItemLabelStyle = useMemo(() => ([
THEME.selectedItemLabel,
selectedItemLabelStyle,
]), [THEME, selectedItemLabelStyle]);
/**
* The disabled item container style.
* @returns {object}
*/
const _disabledItemContainerStyle = useMemo(() => ([
THEME.disabledItemContainer,
disabledItemContainerStyle,
]), [THEME, disabledItemContainerStyle]);
/**
* The disabled item label style.
* @returns {object}
*/
const _disabledItemLabelStyle = useMemo(() => ([
THEME.disabledItemContainer,
disabledItemLabelStyle,
]), [THEME, disabledItemLabelStyle]);
/**
* Render list item.
* @returns {JSX.Element}
*/
const __renderListItem = useCallback(({ item }) => {
let IconComponent = item[schema.icon] ?? null;
if (IconComponent) {
IconComponent = (
<View style={_iconContainerStyle}>
<IconComponent />
</View>
);
}
let isSelected;
if (multiple) {
isSelected = _value.includes(item[schema.value]);
} else {
isSelected = _value === item[schema.value];
}
return (
<RenderItemComponent
rtl={rtl}
item={item}
label={item[schema.label]}
value={item[schema.value]}
parent={item[schema.parent] ?? null}
selectable={item[schema.selectable] ?? null}
disabled={item[schema.disabled] ?? false}
custom={item.custom ?? false}
isSelected={isSelected}
IconComponent={IconComponent}
TickIconComponent={_TickIconComponent}
listItemContainerStyle={_listItemContainerStyle}
listItemContainerStyle2={_listItemContainerStyle2}
listItemLabelStyle={_listItemLabelStyle}
listChildContainerStyle={listChildContainerStyle}
listChildLabelStyle={listChildLabelStyle}
listParentContainerStyle={listParentContainerStyle}
listParentLabelStyle={listParentLabelStyle}
customItemContainerStyle={customItemContainerStyle}
customItemLabelStyle={customItemLabelStyle}
selectedItemContainerStyle={_selectedItemContainerStyle}
selectedItemLabelStyle={_selectedItemLabelStyle}
disabledItemContainerStyle={_disabledItemContainerStyle}
disabledItemLabelStyle={_disabledItemLabelStyle}
categorySelectable={categorySelectable}
onPress={onPressItem}
theme={theme}
THEME={THEME}
/>
);
}, [
rtl,
RenderItemComponent,
_listItemLabelStyle,
_iconContainerStyle,
listChildContainerStyle,
listChildLabelStyle,
listParentContainerStyle,
listParentLabelStyle,
_listItemContainerStyle,
_listItemLabelStyle,
customItemContainerStyle,
customItemLabelStyle,
_selectedItemContainerStyle,
_selectedItemLabelStyle,
_disabledItemContainerStyle,
_disabledItemLabelStyle,
_TickIconComponent,
schema,
_value,
multiple,
categorySelectable,
onPressItem,
theme,
THEME,
]);
/**
* The item separator.
* @returns {JSX.Element|null}
*/
const ItemSeparatorComponent = useCallback(() => {
if (!itemSeparator) return null;
return (
<View style={[
THEME.itemSeparator,
...[itemSeparatorStyle].flat(),
]}
/>
);
}, [itemSeparator, itemSeparatorStyle, THEME]);
/**
* The search placeholder.
* @returns {string}
*/
const _searchPlaceholder = useMemo(() => {
if (searchPlaceholder !== null) return searchPlaceholder;
return ('search for tags');
}, [searchPlaceholder, 'search for tags']);
/**
* onChangeSearchText.
* @param {string} text
*/
const _onChangeSearchText = useCallback((text) => {
setSearchText(text);
onChangeSearchText(text);
}, [onChangeSearchText]);
/**
* The close icon component.
* @returns {JSX.Element}
*/
const _CloseIconComponent = useMemo(() => {
if (listMode !== LIST_MODE.MODAL) return null;
let Component;
if (CloseIconComponent !== null) Component = <CloseIconComponent style={_closeIconStyle} />;
else Component = <Image source={ICON.CLOSE} style={_closeIconStyle} />;
return (
<TouchableOpacity style={_closeIconContainerStyle} onPress={onPressClose}>
{Component}
</TouchableOpacity>
);
}, [listMode, CloseIconComponent, _closeIconStyle, _closeIconContainerStyle, onPressClose, ICON]);
/**
* Indicates if the search component is visible.
* @returns {boolean}
*/
const isSearchComponentVisible = useMemo(() => {
if (listMode === LIST_MODE.MODAL) return true;
return searchable;
}, [listMode, searchable]);
/**
* The search component.
* @returns {JSX.Element}
*/
const SearchComponent = useMemo(() => isSearchComponentVisible && (
<View style={_searchContainerStyle}>
{
searchable && (
<TextInput
value={searchText}
onChangeText={_onChangeSearchText}
style={_searchTextInputStyle}
placeholder={_searchPlaceholder}
placeholderTextColor={searchPlaceholderTextColor}
{...searchTextInputProps}
/>
)
}
{_CloseIconComponent}
</View>
), [
searchable,
isSearchComponentVisible,
_onChangeSearchText,
_searchContainerStyle,
_searchTextInputStyle,
_searchPlaceholder,
searchPlaceholderTextColor,
searchText,
searchTextInputProps,
]);
/**
* The dropdown component wrapper.
* @returns {JSX.Element}
*/
const DropDownComponentWrapper = useCallback((Component) => (
<View style={_dropDownContainerStyle}>
{SearchComponent}
{Component}
</View>
), [_dropDownContainerStyle, SearchComponent]);
/**
* The ActivityIndicatorComponent.
* @returns {JSX.Element}
*/
const _ActivityIndicatorComponent = useCallback(() => {
let Component;
if (ActivityIndicatorComponent !== null) Component = ActivityIndicatorComponent;
else Component = ActivityIndicator;
return <Component size={activityIndicatorSize} color={activityIndicatorColor} />;
}, [ActivityIndicatorComponent, activityIndicatorSize, activityIndicatorColor]);
/**
* The ListEmptyComponent.
* @returns {JSX.Element}
*/
const _ListEmptyComponent = useCallback(() => {
let Component;
const message = ('');
if (ListEmptyComponent !== null) Component = ListEmptyComponent;
else Component = ListEmpty;
return (
<Component
listMessageContainerStyle={_listMessageContainerStyle}
listMessageTextStyle={_listMessageTextStyle}
ActivityIndicatorComponent={_ActivityIndicatorComponent}
loading={loading}
message={message}
/>
);
}, [
'message',
_listMessageContainerStyle,
_listMessageTextStyle,
ListEmptyComponent,
_ActivityIndicatorComponent,
loading,
]);
/**
* The dropdown flatlist component.
* @returns {JSX.Element}
*/
const DropDownFlatListComponent = useMemo(() => (
<FlatList
style={styles.flex}
contentContainerStyle={THEME.flatListContentContainer}
ListEmptyComponent={_ListEmptyComponent}
data={_items}
renderItem={__renderListItem}
keyExtractor={keyExtractor}
extraData={_value}
ItemSeparatorComponent={ItemSeparatorComponent}
{...flatListProps}
/>
), [
_items,
_value,
__renderListItem,
keyExtractor,
ItemSeparatorComponent,
flatListProps,
_ListEmptyComponent,
THEME,
]);
/**
* The dropdown scrollview component.
* @returns {JSX.Element}
*/
const DropDownScrollViewComponent = useMemo(() => (
<ScrollView nestedScrollEnabled {...scrollViewProps}>
{_items.map((item, index) => (
<Fragment key={item[itemKey]}>
{index > 0 && ItemSeparatorComponent()}
{__renderListItem({ item })}
</Fragment>
))}
{_items.length === 0 && _ListEmptyComponent()}
</ScrollView>
), [__renderListItem, itemKey, scrollViewProps, _ListEmptyComponent]);
/**
* The dropdown modal component.
* @returns {JSX.Element}
*/
const DropDownModalComponent = useMemo(() => (
<Modal visible={open} presentationStyle="fullScreen" {...modalProps}>
<SafeAreaView style={_modalContentContainerStyle}>
{SearchComponent}
{DropDownFlatListComponent}
</SafeAreaView>
</Modal>
), [open, SearchComponent, _modalContentContainerStyle, modalProps]);
/**
* The dropdown component.
* @returns {JSX.Element}
*/
const DropDownComponent = useMemo(() => {
switch (listMode) {
case LIST_MODE.FLATLIST: return DropDownComponentWrapper(DropDownFlatListComponent);
case LIST_MODE.SCROLLVIEW: return DropDownComponentWrapper(DropDownScrollViewComponent);
case LIST_MODE.MODAL: return DropDownModalComponent;
default: //
}
}, [
listMode,
DropDownFlatListComponent,
DropDownScrollViewComponent,
DropDownModalComponent,
DropDownComponentWrapper,
]);
/**
* The body of the dropdown component.
* @returns {JSX.Element}
*/
const DropDownBodyComponent = useMemo(() => {
if (open || listMode === LIST_MODE.MODAL) return DropDownComponent;
return null;
}, [open, listMode, DropDownComponent]);
/**
* onRef.
*/
const onRef = useCallback((ref) => {
pickerRef.current = ref;
}, []);
/**
* Pointer events.
* @returns {string}
*/
const pointerEvents = useMemo(() => (disabled ? 'none' : 'auto'), [disabled]);
return (
<View style={_containerStyle} {...containerProps}>
<TouchableOpacity style={_style} onPress={__onPress} onLayout={__onLayout} {...props} ref={onRef} pointerEvents={pointerEvents} disabled={disabled}>
{_BodyComponent}
{_ArrowComponent}
</TouchableOpacity>
{DropDownBodyComponent}
</View>
);
}
Example #19
Source File: CollectionInputScreen.js From discovery-mobile-ui with MIT License | 4 votes |
CollectionInputScreen = ({
collection,
createCollectionAction,
selectCollectionAction,
editCollectionDetailsAction,
creatingCollection,
collectionsLabels,
collections,
renameCollectionAction,
}) => {
const navigation = useNavigation();
const [title, onChangeTitle] = useState(creatingCollection ? DEFAULT_COLLECTION_NAME : collection.label); // eslint-disable-line max-len
const [purpose, onChangePurpose] = useState(creatingCollection ? '' : collection?.purpose);
const [current, currentSelection] = useState(creatingCollection ? false : collection?.current);
const [urgent, urgentSelection] = useState(creatingCollection ? false : collection?.urgent);
const [newCollectionID, setCollectionID] = useState('');
const [isAddingCollection, setIsAddingCollection] = useState(false);
const [collectionsDialogText, setCollectionsDialogText] = useState(null);
const [open, setOpen] = useState(false);
const [hasTextValue, setHasTextValue] = useState(false);
const [sameName, setSameName] = useState(false);
const [moveToCatalog, setMoveToCatalog] = useState(false);
const itemsList = [
];
const itemNames = [];
const collectionNames = [];
if (Object.keys(collections).length > 0) {
Object.keys(collections).forEach((key) => {
if (collections[key] != null) {
collectionNames.push(collections[key].label);
for (let j = 0; j < collections[key].tags.length; j += 1) {
if (!itemNames.includes(collections[key].tags[j])) {
itemNames.push(collections[key].tags[j]);
}
}
}
});
}
for (let i = 0; i < itemNames.length; i += 1) {
itemsList.push({ label: itemNames[i], value: itemNames[i] });
}
const [items, setItems] = useState(itemsList);
const [value, setValue] = useState(creatingCollection ? [] : collection.tags);
const discardInputAlert = () => {
Alert.alert(
'Discard Edits',
'Are you sure you want to discard edits to this collection?',
[
{
text: 'Cancel',
onPress: () => {},
style: 'cancel',
},
{
text: 'Discard',
onPress: () => handleCloseInput(),
style: 'destructive',
},
],
);
};
const handleCloseInput = ({ alert }) => {
if (alert) {
discardInputAlert();
}
};
const handleSave = () => {
if (creatingCollection) {
if (!collectionNames.includes(title)) {
if (hasTextValue) {
if (hasInputErrors({ text: title, isRename: false, label: title })) {
return;
}
createCollectionAction(title);
setIsAddingCollection(true);
}
}
} else {
if (hasInputErrors({ text: title, isRename: true, label: title })) {
return;
}
renameCollectionAction(newCollectionID, title);
editCollectionDetailsAction(purpose, value, (current || urgent), urgent);
}
};
const saveCollection = () => {
handleSave();
navigation.navigate('CollectionsList');
};
const saveAndContinue = () => {
if (creatingCollection) {
if (!collectionNames.includes(title)) {
if (hasTextValue) {
if (hasInputErrors({ text: title, isRename: false, label: title })) {
return;
}
createCollectionAction(title);
setIsAddingCollection(true);
}
}
} else {
if (hasInputErrors({ text: title, isRename: true, label: title })) {
return;
}
renameCollectionAction(newCollectionID, title);
editCollectionDetailsAction(purpose, value, (current || urgent), urgent);
}
setMoveToCatalog(true);
//
};
const discardChanges = () => {
setCollectionsDialogText(CollectionsDialogText[COLLECTIONS_DIALOG_ACTIONS.DISCARD]);
};
const discardChangesCreate = () => {
setCollectionsDialogText(CollectionsDialogText[COLLECTIONS_DIALOG_ACTIONS.DISCARD_CREATE]);
};
// selectCollectionAction(title);
// console.log(collection)
// collection.label = title
// collection.tags = tags
useEffect(() => {
if (Object.keys(collections).length > 0) {
setCollectionID(Object.keys(collections)[Object.keys(collections).length - 1]);
if (isAddingCollection) {
selectCollectionAction(Object.keys(collections)[Object.keys(collections).length - 1]);
editCollectionDetailsAction(purpose, value, (current || urgent), urgent);
setIsAddingCollection(false);
}
}
if (moveToCatalog) {
navigation.navigate('Catalog');
}
// if (useState(collections )!== collections) {
// }
}, [collections, isAddingCollection, moveToCatalog]);
useEffect(() => {
setSameName(false);
if (title.length > 0) {
setHasTextValue(true);
}
if (creatingCollection) {
for (let i = 0; i < collectionNames.length; i += 1) {
if (collectionNames[i].toLowerCase() === title.toLowerCase()) {
setHasTextValue(false);
setSameName(true);
}
}
}
}, [title]);
const saveButtonTextStyle = hasTextValue ? styles.saveButtonText : styles.disabledSaveButtonText;
// PLACEHOLDERS
const placeholderTitle = creatingCollection ? '' : collection.label;
const isUniqueName = ({ text, isRename, label }) => {
// if action is rename, new label can be same as old label
if (isRename && (text.toLowerCase() === label.toLowerCase())) {
return true;
}
return !((collectionsLabels).includes(text.toLowerCase()));
};
const hasMinLength = (text) => text.length > 0;
const hasInputErrors = ({ text, isRename, label }) => {
if (!hasMinLength(text)) {
return true;
}
if (!isUniqueName({ text, isRename, label })) {
return true;
}
return false;
};
const reduceInputs = () => {
Keyboard.dismiss();
setOpen(false);
};
return (
<SafeAreaView style={styles.root}>
<Header style={styles.header}>
<Left>
<TouchableOpacity onPress={() => navigation.goBack()}>
<Entypo name="chevron-thin-left" size={20} color="black" />
</TouchableOpacity>
</Left>
<TouchableWithoutFeedback onPress={reduceInputs}>
<View style={styles.headerTitleContainer}>
<Title style={styles.headerText}><Text>{title}</Text></Title>
</View>
</TouchableWithoutFeedback>
<Right>
<TouchableWithoutFeedback style={styles.empty_toucable} onPress={reduceInputs}>
<View style={styles.headerTitleContainer}>
<Text style={styles.header_empty_text}> </Text>
</View>
</TouchableWithoutFeedback>
</Right>
</Header>
<View style={styles.inputField}>
<KeyboardAvoidingView behavior="padding">
<TouchableWithoutFeedback onPress={reduceInputs}>
<View style={styles.textInputDiv}>
<Text variant="title" style={styles.formHeader}>Title</Text>
</View>
</TouchableWithoutFeedback>
<View style={styles.titleTextInputContainer}>
<TextInput
style={styles.textInput}
onChangeText={onChangeTitle}
placeholder={placeholderTitle}
value={title}
onTouchStart={() => setOpen(false)}
multiline={false}
autoFocus
/>
</View>
<View style={styles.titleFooter}>
{sameName
&& (
<View style={styles.sameNameAlertContainer}>
<Text style={{ color: Colors.destructive }}>Collection name must be unique</Text>
</View>
)}
</View>
</KeyboardAvoidingView>
<KeyboardAvoidingView behavior="padding">
<TouchableWithoutFeedback onPress={reduceInputs}>
<View style={styles.textInputDiv}>
<TouchableOpacity style={styles.textInputHeader} disabled>
<Text variant="title" style={styles.formHeader}>Purpose</Text>
</TouchableOpacity>
</View>
</TouchableWithoutFeedback>
<View style={styles.purposeTextInputContainer}>
<TextInput
style={styles.textInput}
onChangeText={onChangePurpose}
placeholder="add purpose"
onSubmitEditing={Keyboard.dismiss}
value={purpose}
onTouchStart={() => setOpen(false)}
multiline={false}
autoFocus
/>
</View>
</KeyboardAvoidingView>
<View style={styles.tagTextHeader}>
<TouchableWithoutFeedback disabled onPress={reduceInputs}>
<Text variant="title" style={styles.formHeader}>Collection Tags</Text>
</TouchableWithoutFeedback>
</View>
<View style={{ zIndex: 100, backgroundColor: '#fff' }}>
<Picker
multiple
min={0}
max={5}
open={open}
value={value}
setOpen={setOpen}
setValue={setValue}
items={items}
setItems={setItems}
searchable
placeholder="add new or existing tags "
/>
</View>
<View style={styles.switchTextHeader}>
<TouchableWithoutFeedback disabled onPress={reduceInputs}>
<Text variant="title" style={styles.formHeader}>Priority</Text>
</TouchableWithoutFeedback>
</View>
<View style={styles.switchRow}>
<View style={styles.currentTextField}>
<Feather name="watch" size={18} color={Colors.currentCollectionColor} />
<Text style={styles.switchText}>Current</Text>
</View>
<Switch
trackColor={{
false: Colors.mediumgrey,
true: Platform.OS === 'ios' ? Colors.primary : Colors.primaryLight,
}}
thumbColor={(Platform.OS === 'ios') ? 'white' : Colors[(current ? 'primary' : 'primaryLight')]}
onValueChange={() => currentSelection(!current)}
value={current || urgent}
disabled={urgent}
/>
<View style={styles.leftRightPadding}>
<Feather name="alert-triangle" size={18} color={Colors.destructive} />
<Text variant="title" style={styles.switchText}>Urgent</Text>
</View>
<Switch
trackColor={{
false: Colors.mediumgrey,
true: Platform.OS === 'ios' ? Colors.primary : Colors.primaryLight,
}}
thumbColor={(Platform.OS === 'ios') ? 'white' : Colors[(urgent ? 'primary' : 'primaryLight')]}
onValueChange={() => urgentSelection(!urgent)}
value={urgent}
/>
</View>
</View>
<KeyboardAvoidingView style={styles.textRow}>
<TouchableOpacity
style={styles.saveButton}
onPress={() => {
if (creatingCollection) {
discardChangesCreate();
} else {
discardChanges();
}
}}
>
<BaseText variant="title" style={styles.discardButtonText}>Discard</BaseText>
</TouchableOpacity>
{collectionsDialogText && (
<CollectionsDialog
collectionsDialogText={collectionsDialogText}
setCollectionsDialogText={setCollectionsDialogText}
/>
)}
<View style={styles.saveCol}>
<TouchableOpacity
style={styles.saveButton}
onPress={saveCollection}
disabled={!hasTextValue}
>
<BaseText variant="title" style={saveButtonTextStyle}>Save</BaseText>
</TouchableOpacity>
<TouchableOpacity
style={styles.saveButton}
onPress={saveAndContinue}
disabled={!hasTextValue}
>
<BaseText variant="title" style={saveButtonTextStyle}>Save and Continue</BaseText>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
Example #20
Source File: CollectionRow.js From discovery-mobile-ui with MIT License | 4 votes |
CollectionRow = ({
collection,
collectionId,
label,
navigation,
selectCollectionAction,
updateIsAddingNewCollectionAction,
}) => {
const [showDetails, setShowDetails] = useState(false);
const handlePress = () => {
selectCollectionAction(collectionId);
updateIsAddingNewCollectionAction(false);
navigation.navigate('Catalog');
};
const createdDate = formatDateShort(collection.created);
const modifiedDate = formatDateShort(collection.lastUpdated);
const collectionNotesCount = Object.keys(collection.notes).length;
const collectionRecords = Object.values(collection.records);
const recordNotesCount = collectionRecords.reduce((acc, { notes }) => (
notes ? acc.concat(Object.keys(notes)) : acc), []).length;
const savedRecordsCount = collectionRecords.filter((record) => record.saved === true).length;
const showPurpose = (collection?.purpose.length) > 0;
return (
<View style={styles.collectionRowContainer}>
<View style={styles.dateInfoRow}>
<View style={styles.dateInfoMargin}>
<DateInfo date={modifiedDate} />
</View>
<DateInfo date={createdDate} color={Colors.darkgrey} />
</View>
<TouchableOpacity style={styles.collectionRow} onPress={handlePress}>
<View style={styles.collectionRowCountIconsContainer}>
<CountInfo count={savedRecordsCount} color={Colors.collectionYellow} />
<CountInfo count={collectionNotesCount + recordNotesCount} color={Colors.mediumgrey} />
<Text style={styles.labelText}>{label}</Text>
</View>
<View style={styles.iconContainer}>
{collection?.current
&& (
<View style={styles.iconPadding}>
<Feather name="watch" size={20} color={Colors.currentCollectionColor} />
</View>
)}
{collection?.urgent
&& (
<View style={styles.iconPadding}>
<Feather name="alert-triangle" size={20} color={Colors.destructive} />
</View>
)}
<TouchableOpacity style={styles.infoIcon} onPress={() => setShowDetails(!showDetails)}>
<Ionicons name="information-circle-outline" size={24} color="black" />
</TouchableOpacity>
<CollectionRowActionIcon collectionId={collectionId} collectionLabel={label} />
</View>
</TouchableOpacity>
{showDetails && (
<View style={styles.detailsContainer}>
{collection.preBuilt && (
<View style={styles.descriptionContainer}>
<Text>
<PreBuiltDescriptionText collectionId={collectionId} />
</Text>
</View>
)}
<View>
{showPurpose
&& (
<View>
<Text style={styles.purposeText}>
{collection?.purpose}
</Text>
</View>
)}
{collection?.current
&& (
<View style={styles.currentTextField}>
<Feather name="watch" size={18} color={Colors.currentCollectionColor} />
<Text style={styles.switchText}>Current Collection</Text>
</View>
)}
{collection?.urgent
&& (
<View style={styles.currentTextField}>
<Feather name="alert-triangle" size={18} color={Colors.destructive} />
<Text variant="title" style={styles.switchText}>Urgent Collection</Text>
</View>
)}
<View style={styles.badgeRow}>
{Object.entries(collection.tags).map((item, index) => (
<TouchableOpacity style={styles.badgeStyle}>
<Text>{collection.tags[index]}</Text>
</TouchableOpacity>
))}
</View>
<CountInfo count={savedRecordsCount} label="Records In Collection" color={Colors.collectionYellow} />
<CountInfo count={collectionNotesCount} label="Collection Notes" color={Colors.mediumgrey} />
<CountInfo count={recordNotesCount} label="Record Notes" color={Colors.mediumgrey} />
<View style={styles.dateInfoContainer}>
<DateInfo date={modifiedDate} label="Last Modified" />
<View style={styles.dateInfoContainer}>
<DateInfo date={createdDate} label="Created" color={Colors.darkgrey2} />
</View>
</View>
</View>
</View>
)}
</View>
);
}
Example #21
Source File: index.js From semana-omnistack-11 with MIT License | 4 votes |
export default function Incidents() {
const [incidents, setIncidents] = useState([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const navigation = useNavigation();
function navigateToDetail(incident) {
navigation.navigate('Detail', { incident });
}
async function loadIncidents() {
if (loading) {
return;
}
if (total > 0 && incidents.length === total) {
return;
}
setLoading(true);
const response = await api.get('incidents', {
params: { page }
});
setIncidents([...incidents, ...response.data]);
setTotal(response.headers['x-total-count']);
setPage(page + 1);
setLoading(false);
}
useEffect(() => {
loadIncidents();
}, []);
return (
<View style={styles.container}>
<View style={styles.header}>
<Image source={logoImg} />
<Text style={styles.headerText}>
Total de <Text style={styles.headerTextBold}>{total} casos</Text>.
</Text>
</View>
<Text style={styles.title}>Bem-vindo!</Text>
<Text style={styles.description}>Escolha um dos casos abaixo e salve o dia.</Text>
<FlatList
data={incidents}
style={styles.incidentList}
keyExtractor={incident => String(incident.id)}
// showsVerticalScrollIndicator={false}
onEndReached={loadIncidents}
onEndReachedThreshold={0.2}
renderItem={({ item: incident }) => (
<View style={styles.incident}>
<Text style={styles.incidentProperty}>ONG:</Text>
<Text style={styles.incidentValue}>{incident.name}</Text>
<Text style={styles.incidentProperty}>CASO:</Text>
<Text style={styles.incidentValue}>{incident.title}</Text>
<Text style={styles.incidentProperty}>VALOR:</Text>
<Text style={styles.incidentValue}>
{Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
}).format(incident.value)}
</Text>
<TouchableOpacity
style={styles.detailsButton}
onPress={() => navigateToDetail(incident)}
>
<Text style={styles.detailsButtonText}>Ver mais detalhes</Text>
<Feather name="arrow-right" size={16} color="#E02041" />
</TouchableOpacity>
</View>
)}
/>
</View>
);
}
Example #22
Source File: ReviewTopBar.js From juken with MIT License | 4 votes |
ReviewTopBar = ({
submissionQueue,
submissionErrors,
ignoreSubmissionErrors,
retrySubmission,
isQueueClear,
setMenuOpen,
}) => {
const isInternetReachable = useNetworkListener();
const { showActionSheetWithOptions } = useActionSheet();
const uploadSuccess = submissionQueue.length === 0;
const uploadFail = submissionErrors.length > 0;
const uploadQueue = submissionQueue.length;
const uploadErrors = submissionErrors.length;
let badgeColor = uploadFail
? theme.palette.red
: 'rgba(0, 0, 0, .1)';
let badgeIcon = null;
if (uploadSuccess) badgeIcon = <AntDesign name="check" size={10} color="white" />;
if (uploadQueue > 0) badgeIcon = <AntDesign name="arrowup" size={10} color="white" />;
let badgeText = null;
if (uploadQueue > 0) badgeText = uploadQueue;
if (uploadFail) badgeText = uploadErrors;
return (
<>
{/** top bar */}
<TopBar
style={styles.wrapper}
centerText={isQueueClear ? '' : 'Reviews'}
left={<Entypo name="menu" size={20} color="white" />}
leftOnPress={() => setMenuOpen(true)}
right={
<>
{!isInternetReachable && (
<Badge
style={{ marginRight: 6, backgroundColor: theme.palette.red }}
icon={<Feather name="wifi-off" size={10} color="white" />}
/>
)}
<Badge
style={{ backgroundColor: badgeColor }}
text={badgeText}
icon={badgeIcon}
/>
</>
}
rightOnPress={!uploadFail ? null : () => {
showActionSheetWithOptions({
options: [
'Cancel',
'Retry',
'Ignore',
],
destructiveButtonIndex: 2,
title: `Failed to submit ${uploadErrors} review${uploadErrors === 1 ? '' : 's'}`,
message: (
'You can retry submission after making sure your device ' +
'has an active internet connection. If you submitted the reviews ' +
'from another application, please use the Ignore button to dismiss ' +
'the errors.'
),
}, buttonIndex => {
if (buttonIndex === 1) {
retrySubmission();
} else if (buttonIndex === 2) {
dialog({
webTitle: 'Unless you submitted your reviews elsewhere, your unsubmitted reviews will be lost. Are you sure ?',
mobileTitle: 'Are you sure ?',
mobileMessage: 'Unless you submitted your reviews elsewhere, your unsubmitted reviews will be lost.',
onConfirm: ignoreSubmissionErrors
});
}
})
}}
/>
</>
)
}
Example #23
Source File: index.js From be-the-hero with MIT License | 4 votes |
export default function Incidents() {
const [ incidents, setIncidents ] = useState([])
const [ totalIncidents, setTotalIncidents ] = useState(0)
const [ currentPage, setCurrentPage ] = useState(1)
const [ loading, setLoading ] = useState(false)
const navigation = useNavigation()
function navigateToDetails(incident) {
navigation.navigate('Details', { incident })
}
async function loadIncidents() {
if (loading) {
return
}
if (totalIncidents > 0 && incidents.length === totalIncidents) {
return
}
setLoading(true)
const response = await api.get('/incidents', {
params: {
page: currentPage
}
})
setIncidents([ ...incidents, ...response.data ])
setTotalIncidents(response.headers['X-Total-Count'])
setCurrentPage(currentPage + 1)
setLoading(false)
}
useEffect(() => {
loadIncidents()
}, [])
return (
<View style={ styles.incidentsContainer }>
<View style={ styles.headerContainer }>
<Image source={ logoImage }/>
<Text style={ styles.headerText }>
Total de <Text style={ styles.headerTextBold }>{ totalIncidents }</Text> casos
</Text>
</View>
<Text style={ styles.mainTitle }>Bem-vindo</Text>
<Text style={ styles.mainDescription }>Escolha um dos casos abaixo e salve o dia!</Text>
<FlatList style={ styles.incidentsList } data={ incidents }
keyExtractor={ incident => String(incident.id) }
showsVerticalScrollIndicator={ false }
onEndReached={ loadIncidents }
onEndReachedThreshold={ 0.2 }
renderItem={({ item: incident }) => (
<View style={ styles.incident }>
<Text style={ styles.incidentOng }>
{ incident.name } de { incident.city }/{ incident.uf }
</Text>
<Text style={ styles.incidentDescription }>
{ incident.description }
</Text>
<Text style={ styles.incidentValue }>
{
Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
}).format(incident.value)
}
</Text>
<TouchableOpacity onPress={ () => navigateToDetails(incident) }
style={ styles.incidentButton }>
<Text style={ styles.incidentButtonText }>Ver mais detalhes</Text>
<Feather name="arrow-right" size={ 16 } color="#E02041"/>
</TouchableOpacity>
</View>
)}/>
</View>
)
}
Example #24
Source File: index.js From be-the-hero with MIT License | 4 votes |
export default function Incidents(){
const [incidents, setIncidents] = useState([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const navigation = useNavigation();
function navigateToDetail(incident){
navigation.navigate('Detail', { incident });
}
async function loadIcidents(){
if (loading) {
return;
}
if (total > 0 && incidents.length === total ) {
return;
}
setLoading(true);
const response = await api.get('incidents', {
params: { page }
});
setIncidents([...incidents, ...response.data]);
setTotal(response.headers['x-total-count']);
setPage(page + 1);
setLoading(false);
}
useEffect(() => {
loadIcidents();
}, []);
return(
<View style={styles.container}>
<View style={styles.header}>
<Image source={logoImg} />
<Text style={styles.headerText}>
Total de <Text style={styles.headerTextBold}>{total} casos</Text>.
</Text>
</View>
<Text style={styles.title}>Bem-vindo!</Text>
<Text style={styles.description}>Escolha um dos casos abaixo e salve o dia.</Text>
<FlatList
data={incidents}
style={styles.incidentList}
keyExtractor={incident => String(incident.id)}
showsVerticalScrollIndicator={false}
onEndReached={loadIcidents}
onEndReachedThreshold={0.2}
renderItem={({ item: incident }) => (
<View style={styles.incident}>
<Text style={styles.incidentProperty}>ONG:</Text>
<Text style={styles.incidentValue}>{incident.name}</Text>
<Text style={styles.incidentProperty}>CASO:</Text>
<Text style={styles.incidentValue}>{incident.title}</Text>
<Text style={styles.incidentProperty}>VALOR:</Text>
<Text style={styles.incidentValue}>
{Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL' }).format(incident.value)}</Text>
<TouchableOpacity style={styles.detailsButton} onPress={() => navigateToDetail(incident)}>
<Text style={styles.detailsButtonText}>Ver mais detalhes</Text>
<Feather name="arrow-right" size={16} color="#e02041" />
</TouchableOpacity>
</View>
)}
/>
</View>
);
}
Example #25
Source File: index.js From be-the-hero with MIT License | 4 votes |
export default function Detail() {
const navigation = useNavigation();
const route = useRoute();
const incident = route.params.incident;
const messege = `Olá ${
incident.name
}, estou entrando em contato pois gostaria de ajudar no caso "${
incident.title
}" com o valor de ${Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL',
}).format(incident.value)}`;
function navigateBack() {
navigation.goBack();
}
function sendMail() {
MailComposer.composeAsync({
subject: `Herói do caso: ${incident.title}`,
recipients: [incident.email],
body: messege,
});
}
function sendWhatsapp() {
Linking.openURL(
`whatsapp://send?phone=+55${incident.whatsapp}&text=${messege}`
);
}
return (
<View style={styles.container}>
<View style={styles.header}>
<Image source={logoImg} />
<TouchableOpacity onPress={navigateBack}>
<Feather name="arrow-left" size={20} color="#E02041" />
</TouchableOpacity>
</View>
<View style={styles.incident}>
<Text style={[styles.incidentProperty, { marginTop: 0 }]}>ONG:</Text>
<Text style={styles.incidentValue}>
{incident.name} de {incident.city}/{incident.uf}
</Text>
<Text style={styles.incidentProperty}>CASO:</Text>
<Text style={styles.incidentValue}>{incident.description}</Text>
<Text style={styles.incidentProperty}>VALOR:</Text>
<Text style={styles.incidentValue}>
{Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL',
}).format(incident.value)}
</Text>
</View>
<View style={styles.contactBox}>
<Text style={styles.heroTitle}>Salve o dia!</Text>
<Text style={styles.heroTitle}>Seja o herói desse caso.</Text>
<Text style={styles.heroDescription}>Entre em contato:</Text>
<View style={styles.actions}>
<TouchableOpacity style={styles.action} onPress={sendWhatsapp}>
<FontAwesome name="whatsapp" size={20} color="#fff" />
<Text style={styles.actionText}>Whatsapp</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.action} onPress={sendMail}>
<Icon name="mail" size={20} color="#fff" />
<Text style={styles.actionText}>Email</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
}
Example #26
Source File: ViewProfile.js From salyd with GNU General Public License v3.0 | 4 votes |
ViewProfile = ({ navigation }) => {
const { user, token } = useContext(GlobalContext);
const { _id, name, email, phone, image } = user;
const logout = async () => {
const token = await AsyncStorage.removeItem("token")
const user = await AsyncStorage.removeItem("user")
if (!token) {
navigation.replace("Login");
}
}
console.log(image)
return (
<React.Fragment>
<Header navigation={navigation} isBack> View Profile </Header>
<View style={styles.root}>
<View style={{ alignItems: "center" }}>
<Image
style={{ width: 140, height: 140, borderRadius: 140 / 2, marginTop: 50 }}
source={{ uri: (image ? image : "https://sanjaymotels.com/wp-content/uploads/2019/01/testimony.png") }}
/>
</View>
<View style={{ alignItems: "center", margin: 15 }}>
<Text style={styles.name}> {name} </Text>
</View>
<TouchableOpacity onPress={() => {
navigation.navigate('EditProfile', {
name, email, phone
})
}}>
<View style={styles.cardContainer}>
<View style={styles.mycard}>
<View style={styles.cardContent}>
<FontAwesome name="user-circle" style={styles.icon} />
<Text style={styles.mytext}>Account Details</Text>
<Ionicons name="ios-arrow-forward" style={styles.arrow} />
</View>
</View>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => {
navigation.navigate('RecentOrders')
}}>
<View style={styles.mycard}>
<View style={styles.cardContent}>
<MaterialCommunityIcons name="food-variant" style={styles.icon} />
<Text style={styles.mytext}>Order History</Text>
<Ionicons name="ios-arrow-forward" style={styles.arrow} />
</View>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => {
navigation.navigate('Contact')
}}>
<View style={styles.mycard} onPress={() => {
navigation.navigate('Contact')
}}>
<View style={styles.cardContent}>
<Feather name="phone-call" style={styles.icon} />
<Text style={styles.mytext}>Contact Us</Text>
<Ionicons name="ios-arrow-forward" style={styles.arrow} />
</View>
</View>
</TouchableOpacity>
<View style={{
justifyContent: "center",
alignItems: "center"
}}>
<Button onPressFunction={() => logout()}>Logout</Button>
</View>
</View>
</React.Fragment>
)
}
Example #27
Source File: Edit.js From InstagramClone with Apache License 2.0 | 4 votes |
function Edit(props) {
const [name, setName] = useState(props.currentUser.name);
const [description, setDescription] = useState("");
const [image, setImage] = useState(props.currentUser.image);
const [imageChanged, setImageChanged] = useState(false);
const [hasGalleryPermission, setHasGalleryPermission] = useState(null);
const onLogout = async () => {
firebase.auth().signOut();
Updates.reloadAsync()
}
useEffect(() => {
(async () => {
if (props.currentUser.description !== undefined) {
setDescription(props.currentUser.description)
}
})();
}, []);
useLayoutEffect(() => {
props.navigation.setOptions({
headerRight: () => (
<Feather style={navbar.image} name="check" size={24} color="green" onPress={() => { console.log({ name, description }); Save() }} />
),
});
}, [props.navigation, name, description, image, imageChanged]);
const pickImage = async () => {
if (true) {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 1,
});
if (!result.cancelled) {
setImage(result.uri);
setImageChanged(true);
}
}
};
const Save = async () => {
if (imageChanged) {
const uri = image;
const childPath = `profile/${firebase.auth().currentUser.uid}`;
const response = await fetch(uri);
const blob = await response.blob();
const task = firebase
.storage()
.ref()
.child(childPath)
.put(blob);
const taskProgress = snapshot => {
console.log(`transferred: ${snapshot.bytesTransferred}`)
}
const taskCompleted = () => {
task.snapshot.ref.getDownloadURL().then((snapshot) => {
firebase.firestore().collection("users")
.doc(firebase.auth().currentUser.uid)
.update({
name,
description,
image: snapshot,
}).then(() => {
props.updateUserFeedPosts();
props.navigation.goBack()
})
})
}
const taskError = snapshot => {
console.log(snapshot)
}
task.on("state_changed", taskProgress, taskError, taskCompleted);
} else {
saveData({
name,
description,
})
}
}
const saveData = (data) => {
firebase.firestore().collection("users")
.doc(firebase.auth().currentUser.uid)
.update(data).then(() => {
props.updateUserFeedPosts();
props.navigation.goBack()
})
}
return (
<View style={container.form}>
<TouchableOpacity style={[utils.centerHorizontal, utils.marginBottom]} onPress={() => pickImage()} >
{image == 'default' ?
(
<FontAwesome5
style={[utils.profileImageBig, utils.marginBottomSmall]}
name="user-circle" size={80} color="black" />
)
:
(
<Image
style={[utils.profileImageBig, utils.marginBottomSmall]}
source={{
uri: image
}}
/>
)
}
<Text style={text.changePhoto}>Change Profile Photo</Text>
</TouchableOpacity>
<TextInput
value={name}
style={form.textInput}
placeholder="Name"
onChangeText={(name) => setName(name)}
/>
<TextInput
value={description}
style={[form.textInput]}
placeholderTextColor={"#e8e8e8"}
placeholder="Description"
onChangeText={(description) => { setDescription(description); }}
/>
<Button
title="Logout"
onPress={() => onLogout()} />
</View>
)
}
Example #28
Source File: Post.js From InstagramClone with Apache License 2.0 | 4 votes |
function Post(props) {
const [item, setItem] = useState(props.route.params.item)
const [user, setUser] = useState(props.route.params.user)
const [currentUserLike, setCurrentUserLike] = useState(false)
const [unmutted, setUnmutted] = useState(true)
const [videoref, setvideoref] = useState(null)
const [sheetRef, setSheetRef] = useState(useRef(null))
const [modalShow, setModalShow] = useState({ visible: false, item: null })
const [isValid, setIsValid] = useState(true);
const [exists, setExists] = useState(false);
const [loaded, setLoaded] = useState(false);
const isFocused = useIsFocused();
useEffect(() => {
if (props.route.params.notification != undefined) {
firebase.firestore()
.collection("users")
.doc(props.route.params.user)
.get()
.then((snapshot) => {
if (snapshot.exists) {
let user = snapshot.data();
user.uid = snapshot.id;
setUser(user)
}
})
firebase.firestore()
.collection("posts")
.doc(props.route.params.user)
.collection("userPosts")
.doc(props.route.params.item)
.get()
.then((snapshot) => {
if (snapshot.exists) {
let post = snapshot.data();
post.id = snapshot.id;
setItem(post)
setLoaded(true)
setExists(true)
}
})
firebase.firestore()
.collection("posts")
.doc(props.route.params.user)
.collection("userPosts")
.doc(props.route.params.item)
.collection("likes")
.doc(firebase.auth().currentUser.uid)
.onSnapshot((snapshot) => {
let currentUserLike = false;
if (snapshot.exists) {
currentUserLike = true;
}
setCurrentUserLike(currentUserLike)
})
}
else {
firebase.firestore()
.collection("posts")
.doc(props.route.params.user.uid)
.collection("userPosts")
.doc(props.route.params.item.id)
.collection("likes")
.doc(firebase.auth().currentUser.uid)
.onSnapshot((snapshot) => {
let currentUserLike = false;
if (snapshot.exists) {
currentUserLike = true;
}
setCurrentUserLike(currentUserLike)
})
setItem(props.route.params.item)
setUser(props.route.params.user)
setLoaded(true)
setExists(true)
}
}, [props.route.params.notification, props.route.params.item])
useEffect(() => {
if (videoref !== null) {
videoref.setIsMutedAsync(props.route.params.unmutted)
}
setUnmutted(props.route.params.unmutted)
}, [props.route.params.unmutted])
useEffect(() => {
if (videoref !== null) {
if (isFocused) {
videoref.playAsync()
} else {
videoref.stopAsync()
}
}
}, [props.route.params.index, props.route.params.inViewPort])
const onUsernamePress = (username, matchIndex) => {
props.navigation.navigate("ProfileOther", { username, uid: undefined })
}
const onLikePress = (userId, postId, item) => {
item.likesCount += 1;
setCurrentUserLike(true)
firebase.firestore()
.collection("posts")
.doc(userId)
.collection("userPosts")
.doc(postId)
.collection("likes")
.doc(firebase.auth().currentUser.uid)
.set({})
.then()
props.sendNotification(user.notificationToken, "New Like", `${props.currentUser.name} liked your post`, { type: 0, postId, user: firebase.auth().currentUser.uid })
}
const onDislikePress = (userId, postId, item) => {
item.likesCount -= 1;
setCurrentUserLike(false)
firebase.firestore()
.collection("posts")
.doc(userId)
.collection("userPosts")
.doc(postId)
.collection("likes")
.doc(firebase.auth().currentUser.uid)
.delete()
}
if (!exists && loaded) {
return (
<View style={{ height: '100%', justifyContent: 'center', margin: 'auto' }}>
<FontAwesome5 style={{ alignSelf: 'center', marginBottom: 20 }} name="dizzy" size={40} color="black" />
<Text style={[text.notAvailable]}>Post does not exist</Text>
</View>
)
}
if (!loaded) {
return (<View></View>)
}
if (user == undefined) {
return (<View></View>)
}
if (item == null) {
return (<View />)
}
const _handleVideoRef = (component) => {
setvideoref(component);
if (component !== null) {
component.setIsMutedAsync(props.route.params.unmutted)
}
}
if (videoref !== null) {
videoref.setIsMutedAsync(unmutted)
if (isFocused && props.route.params.index == props.route.params.inViewPort) {
videoref.playAsync()
} else {
videoref.stopAsync()
}
}
if (sheetRef.current !== null && !props.route.params.feed) {
if (modalShow.visible) {
sheetRef.snapTo(0)
} else {
sheetRef.snapTo(1)
}
}
return (
<View style={[container.container, utils.backgroundWhite]}>
<View>
<View style={[container.horizontal, { alignItems: 'center', padding: 10 }]}>
<TouchableOpacity
style={[container.horizontal, { alignItems: 'center' }]}
onPress={() => props.navigation.navigate("ProfileOther", { uid: user.uid, username: undefined })}>
{user.image == 'default' ?
(
<FontAwesome5
style={[utils.profileImageSmall]}
name="user-circle" size={35} color="black" />
)
:
(
<Image
style={[utils.profileImageSmall]}
source={{
uri: user.image
}}
/>
)
}
<View style={{ alignSelf: 'center' }}>
<Text style={[text.bold, text.medium, { marginBottom: 0 }]} >{user.name}</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
style={[{ marginLeft: 'auto' }]}
onPress={() => {
if (props.route.params.feed) {
props.route.params.setModalShow({ visible: true, item })
} else {
setModalShow({ visible: true, item })
}
}}>
<Feather
name="more-vertical" size={20} color="black" />
</TouchableOpacity>
</View>
{item.type == 0 ?
<View>
{props.route.params.index == props.route.params.inViewPort && isFocused ?
<View>
<VideoPlayer
videoProps={{
isLooping: true,
shouldPlay: true,
resizeMode: Video.RESIZE_MODE_COVER,
source: {
uri: item.downloadURL,
},
videoRef: _handleVideoRef,
}}
inFullscreen={false}
showControlsOnLoad={true}
showFullscreenButton={false}
height={WINDOW_WIDTH}
width={WINDOW_WIDTH}
shouldPlay={true}
isLooping={true}
style={{
aspectRatio: 1 / 1, height: WINDOW_WIDTH,
width: WINDOW_WIDTH, backgroundColor: 'black'
}}
/>
<TouchableOpacity
style={{ position: 'absolute', borderRadius: 500, backgroundColor: 'black', width: 40, height: 40, alignItems: 'center', justifyContent: 'center', margin: 10, right: 0 }}
activeOpacity={1}
onPress={() => {
if (videoref == null) {
return;
}
if (unmutted) {
if (props.route.params.setUnmuttedMain == undefined) {
setUnmutted(false)
} else {
props.route.params.setUnmuttedMain(false)
}
} else {
if (props.route.params.setUnmuttedMain == undefined) {
setUnmutted(true)
} else {
props.route.params.setUnmuttedMain(true)
}
}
}}>
{!unmutted ?
<Feather name="volume-2" size={20} color="white" />
:
<Feather name="volume-x" size={20} color="white" />
}
</TouchableOpacity>
</View>
:
<View style={{ marginTop: 4 }}>
<CachedImage
cacheKey={item.id}
style={[container.image]}
source={{ uri: item.downloadURLStill }}
/>
</View>
}
</View>
:
<CachedImage
cacheKey={item.id}
style={container.image}
source={{ uri: item.downloadURL }}
/>
}
<View style={[utils.padding10, container.horizontal]}>
{currentUserLike ?
(
<Entypo name="heart" size={30} color="red" onPress={() => onDislikePress(user.uid, item.id, item)} />
)
:
(
<Feather name="heart" size={30} color="black" onPress={() => onLikePress(user.uid, item.id, item)} />
)
}
<Feather style={utils.margin15Left} name="message-square" size={30} color="black" onPress={() => props.navigation.navigate('Comment', { postId: item.id, uid: user.uid, user })} />
<Feather style={utils.margin15Left} name="share" size={26} color="black" onPress={() => props.navigation.navigate('ChatList', { postId: item.id, post: { ...item, user: user }, share: true })} />
</View>
<View style={[container.container, utils.padding10Sides]}>
<Text style={[text.bold, text.medium]}>
{item.likesCount} likes
</Text>
<Text style={[utils.margin15Right, utils.margin5Bottom]}>
<Text style={[text.bold]}
onPress={() => props.navigation.navigate("ProfileOther", { uid: user.uid, username: undefined })}>
{user.name}
</Text>
<Text> </Text>
<ParsedText
parse={
[
{ pattern: /@(\w+)/, style: { color: 'green', fontWeight: 'bold' }, onPress: onUsernamePress },
]
}
>{item.caption}</ParsedText>
</Text>
<Text
style={[text.grey, utils.margin5Bottom]} onPress={() => props.navigation.navigate('Comment', { postId: item.id, uid: user.uid, user })}>
View all {item.commentsCount} Comments
</Text>
<Text
style={[text.grey, text.small, utils.margin5Bottom]}>
{timeDifference(new Date(), item.creation.toDate())}
</Text>
</View>
</View>
<BottomSheet
bottomSheerColor="#FFFFFF"
ref={setSheetRef}
initialPosition={0} //200, 300
snapPoints={[300, 0]}
isBackDrop={true}
isBackDropDismissByPress={true}
isRoundBorderWithTipHeader={true}
backDropColor="black"
isModal
containerStyle={{ backgroundColor: "white" }}
tipStyle={{ backgroundColor: "white" }}
headerStyle={{ backgroundColor: "white", flex: 1 }}
bodyStyle={{ backgroundColor: "white", flex: 1, borderRadius: 20 }}
body={
<View>
{modalShow.item != null ?
<View>
<TouchableOpacity style={{ padding: 20 }}
onPress={() => {
props.navigation.navigate("ProfileOther", { uid: modalShow.item.user.uid, username: undefined });
setModalShow({ visible: false, item: null });
}}>
<Text >Profile</Text>
</TouchableOpacity>
<Divider />
{props.route.params.user.uid == firebase.auth().currentUser.uid ?
<TouchableOpacity style={{ padding: 20 }}
onPress={() => {
props.deletePost(modalShow.item).then(() => {
props.fetchUserPosts()
props.navigation.popToTop()
})
setModalShow({ visible: false, item: null });
}}>
<Text >Delete</Text>
</TouchableOpacity>
: null}
<Divider />
<TouchableOpacity style={{ padding: 20 }} onPress={() => setModalShow({ visible: false, item: null })}>
<Text >Cancel</Text>
</TouchableOpacity>
</View>
: null}
</View>
}
/>
<Snackbar
visible={isValid.boolSnack}
duration={2000}
onDismiss={() => { setIsValid({ boolSnack: false }) }}>
{isValid.message}
</Snackbar>
</View>
)
}
Example #29
Source File: Save.js From InstagramClone with Apache License 2.0 | 4 votes |
function Save(props) {
const [caption, setCaption] = useState("")
const [uploading, setUploading] = useState(false)
const [error, setError] = useState(false)
const [data, setData] = useState("")
const [keyword, setKeyword] = useState("")
useLayoutEffect(() => {
props.navigation.setOptions({
headerRight: () => (
<Feather style={navbar.image} name="check" size={24} color="green" onPress={() => { uploadImage() }} />
),
});
}, [caption]);
const uploadImage = async () => {
if (uploading) {
return;
}
setUploading(true)
let downloadURLStill = null
let downloadURL = await SaveStorage(props.route.params.source, `post/${firebase.auth().currentUser.uid}/${Math.random().toString(36)}`)
if (props.route.params.imageSource != null) {
downloadURLStill = await SaveStorage(props.route.params.imageSource, `post/${firebase.auth().currentUser.uid}/${Math.random().toString(36)}`)
}
savePostData(downloadURL, downloadURLStill);
}
const SaveStorage = async (image, path) => {
if (image == 'default') {
return '';
}
const fileRef = firebase.storage().ref()
.child(path);
const response = await fetch(image);
const blob = await response.blob();
const task = await fileRef.put(blob);
const downloadURL = await task.ref.getDownloadURL();
return downloadURL;
}
const savePostData = (downloadURL, downloadURLStill) => {
let object = {
downloadURL,
caption,
likesCount: 0,
commentsCount: 0,
type: props.route.params.type,
creation: firebase.firestore.FieldValue.serverTimestamp()
}
if (downloadURLStill != null) {
object.downloadURLStill = downloadURLStill
}
firebase.firestore()
.collection('posts')
.doc(firebase.auth().currentUser.uid)
.collection("userPosts")
.add(object).then((result) => {
props.fetchUserPosts()
props.navigation.popToTop()
}).catch((error) => {
setUploading(false)
setError(true)
})
var pattern = /\B@[a-z0-9_-]+/gi;
let array = caption.match(pattern);
if (array !== null) {
for (let i = 0; i < array.length; i++) {
firebase.firestore()
.collection("users")
.where("username", "==", array[i].substring(1))
.get()
.then((snapshot) => {
snapshot.forEach((doc) => {
props.sendNotification(doc.data().notificationToken, "New tag", `${props.currentUser.name} Tagged you in a post`, { type: 0, user: firebase.auth().currentUser.uid })
});
})
}
}
}
const renderSuggestionsRow = ({ item }, hidePanel) => {
return (
<TouchableOpacity onPress={() => onSuggestionTap(item.username, hidePanel)}>
<View style={styles.suggestionsRowContainer}>
<View style={styles.userIconBox}>
<Image
style={{ aspectRatio: 1 / 1, height: 45 }}
source={{
uri: item.image
}}
/>
</View>
<View style={styles.userDetailsBox}>
<Text style={styles.displayNameText}>{item.name}</Text>
<Text style={styles.usernameText}>@{item.username}</Text>
</View>
</View>
</TouchableOpacity>
)
}
const onSuggestionTap = (username, hidePanel) => {
hidePanel();
const comment = caption.slice(0, - keyword.length)
setCaption(comment + '@' + username + " ");
}
const callback = (keyword) => {
setKeyword(keyword)
firebase.firestore()
.collection("users")
.where("username", ">=", keyword.substring(1))
.limit(10)
.get()
.then((snapshot) => {
let result = snapshot.docs.map(doc => {
const data = doc.data();
const id = doc.id;
return { id, ...data }
});
setData(result)
})
}
return (
<View style={[container.container, utils.backgroundWhite]}>
{uploading ? (
<View style={[container.container, utils.justifyCenter, utils.alignItemsCenter]}>
<ActivityIndicator style={utils.marginBottom} size="large" />
<Text style={[text.bold, text.large]}>Upload in progress...</Text>
</View>
) : (
<View style={[container.container]}>
<View style={[container.container, utils.backgroundWhite, utils.padding15]}>
<View style={[{ marginBottom: 20, width: '100%' }]}>
<MentionsTextInput
textInputStyle={{ borderColor: '#ebebeb', borderWidth: 1, padding: 5, fontSize: 15, width: '100%' }}
suggestionsPanelStyle={{ backgroundColor: 'rgba(100,100,100,0.1)' }}
loadingComponent={() => <View style={{ flex: 1, width: 200, justifyContent: 'center', alignItems: 'center' }}><ActivityIndicator /></View>}
textInputMinHeight={30}
textInputMaxHeight={80}
trigger={'@'}
triggerLocation={'new-word-only'} // 'new-word-only', 'anywhere'
value={caption}
onChangeText={setCaption}
triggerCallback={callback.bind(this)}
renderSuggestionsRow={renderSuggestionsRow.bind(this)}
suggestionsData={data}
keyExtractor={(item, index) => item.username}
suggestionRowHeight={45}
horizontal={true}
MaxVisibleRowCount={3}
/>
</View>
<View>
{props.route.params.type ?
<Image
style={container.image}
source={{ uri: props.route.params.source }}
style={{ aspectRatio: 1 / 1, backgroundColor: 'black' }}
/>
:
<Video
source={{ uri: props.route.params.source }}
shouldPlay={true}
isLooping={true}
resizeMode="cover"
style={{ aspectRatio: 1 / 1, backgroundColor: 'black' }}
/>
}
</View>
</View>
<Snackbar
visible={error}
duration={2000}
onDismiss={() => setError(false)}>
Something Went Wrong!
</Snackbar>
</View>
)}
</View>
)
}