perf: optimize icon selector

This commit is contained in:
Xc165543337 2024-04-12 10:29:23 +02:00
parent cf959c7088
commit 5e3cee079b
3 changed files with 126 additions and 91 deletions

View File

@ -228,16 +228,8 @@ export const HabitCreateForm: React.FC<HabitCreateFormProps> = ({ user }) => {
<Controller <Controller
control={control} control={control}
render={({ field: { onChange, onBlur, value } }) => { render={({ field: { onChange, value } }) => {
return ( return (
// <TextInput
// placeholder="Icon"
// onBlur={onBlur}
// onChangeText={onChange}
// value={value}
// style={[styles.spacing, { width: "90%" }]}
// mode="outlined"
// />
<HabitIconSelectorModal <HabitIconSelectorModal
visible visible
value={value} value={value}

View File

@ -0,0 +1,56 @@
import type { IconName } from "@fortawesome/fontawesome-svg-core"
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
import React, { memo } from "react"
import { View, StyleSheet } from "react-native"
import { IconButton, Text } from "react-native-paper"
export interface HabitIconListProps {
selectedIcon?: string
possibleIcons: string[]
handleIconSelect: (icon: string) => void
}
const HabitIconListWithoutMemo: React.FC<HabitIconListProps> = (props) => {
const { selectedIcon, possibleIcons, handleIconSelect } = props
if (possibleIcons.length > 0) {
return (
<View>
{possibleIcons.map((icon) => {
return (
<IconButton
key={icon}
containerColor="white"
icon={({ size }) => {
return (
<FontAwesomeIcon
icon={icon as IconName}
size={size}
color={selectedIcon === icon ? "blue" : "black"}
/>
)
}}
size={30}
onPress={() => {
handleIconSelect(icon)
}}
/>
)
})}
</View>
)
}
return (
<View style={styles.noResults}>
<Text>No results found</Text>
</View>
)
}
const styles = StyleSheet.create({
noResults: {
marginTop: 20,
alignItems: "center",
},
})
export const HabitIconList = memo(HabitIconListWithoutMemo)

View File

@ -1,22 +1,16 @@
import React, { useState, useEffect } from "react" import { library } from "@fortawesome/fontawesome-svg-core"
import {
Modal,
IconButton,
Portal,
List,
Button,
TextInput,
Text,
} from "react-native-paper"
import { ScrollView, View, StyleSheet } from "react-native"
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
import type {
IconDefinition,
IconName,
} from "@fortawesome/fontawesome-svg-core"
import { library, findIconDefinition } from "@fortawesome/fontawesome-svg-core"
import { fas } from "@fortawesome/free-solid-svg-icons" import { fas } from "@fortawesome/free-solid-svg-icons"
import React, {
memo,
useCallback,
useEffect,
useState,
useTransition,
} from "react"
import { ScrollView, StyleSheet, View } from "react-native"
import { Button, List, Modal, Portal, TextInput } from "react-native-paper"
import { HabitIconList } from "./HabitIconList"
export interface HabitIconSelectorModalProps { export interface HabitIconSelectorModalProps {
visible?: boolean visible?: boolean
value: string value: string
@ -25,6 +19,34 @@ export interface HabitIconSelectorModalProps {
onPress: () => void onPress: () => void
} }
interface SearchInputProps {
searchText: string
handleSearch: (text: string) => void
}
library.add(fas)
const iconNames = Object.keys(fas).map((key) => {
return fas[key]?.iconName ?? ""
})
const findIconsInLibrary = (icon: string): string[] => {
return iconNames
.filter((name, index, self) => {
return name.includes(icon) && self.indexOf(name) === index
})
.slice(0, 20)
}
const SearchInputWithoutMemo: React.FC<SearchInputProps> = (props) => {
const { searchText, handleSearch } = props
return (
<TextInput label="Search" value={searchText} onChangeText={handleSearch} />
)
}
const SearchInput = memo(SearchInputWithoutMemo)
export const HabitIconSelectorModal: React.FC<HabitIconSelectorModalProps> = ({ export const HabitIconSelectorModal: React.FC<HabitIconSelectorModalProps> = ({
visible = false, visible = false,
value, value,
@ -33,36 +55,32 @@ export const HabitIconSelectorModal: React.FC<HabitIconSelectorModalProps> = ({
onPress, onPress,
}) => { }) => {
const [selectedIcon, setSelectedIcon] = useState<string>() const [selectedIcon, setSelectedIcon] = useState<string>()
const [possibleIcons, setPossibleIcons] = useState<IconDefinition[]>([]) const [possibleIcons, setPossibleIcons] = useState<string[]>([])
const [searchText, setSearchText] = useState<string>(value) const [searchText, setSearchText] = useState<string>(value)
const [isPending, startTransition] = useTransition()
const handleSearch = useCallback((text: string): void => {
setSearchText(text)
}, [])
useEffect(() => { useEffect(() => {
const delay = setTimeout(() => {
startTransition(() => {
setPossibleIcons(findIconsInLibrary(searchText)) setPossibleIcons(findIconsInLibrary(searchText))
}, [searchText]) })
}, 500)
return () => {
return clearTimeout(delay)
}
}, [searchText, isPending])
library.add(fas) const handleIconSelect = useCallback(
(icon: string): void => {
const handleIconSelect = (icon: string): void => {
setSelectedIcon(icon) setSelectedIcon(icon)
onSelect(icon) onSelect(icon)
} },
[onSelect],
const findIconsInLibrary = (icon: string): IconDefinition[] => { )
const iconNames = Object.keys(fas).map((key) => {
return fas[key]?.iconName ?? ""
})
const matchedValue: string[] = iconNames.filter((name) => {
return name.includes(icon)
})
return matchedValue.length > 0
? matchedValue.map((name) => {
return findIconDefinition({
prefix: "fas",
iconName: name as IconName,
})
})
: []
}
return ( return (
<> <>
@ -77,46 +95,15 @@ export const HabitIconSelectorModal: React.FC<HabitIconSelectorModalProps> = ({
<ScrollView style={styles.scrollView}> <ScrollView style={styles.scrollView}>
<List.Section title="Select an icon"> <List.Section title="Select an icon">
<View style={styles.iconContainer}> <View style={styles.iconContainer}>
<TextInput <SearchInput
label="Search" searchText={searchText}
value={searchText} handleSearch={handleSearch}
onChangeText={(text) => {
return setSearchText(text)
}}
/> />
{possibleIcons.length > 0 ? ( <HabitIconList
<View> selectedIcon={selectedIcon}
{possibleIcons.map((icon) => { possibleIcons={possibleIcons}
return ( handleIconSelect={handleIconSelect}
<IconButton
key={icon.iconName}
containerColor="white"
icon={({ size }) => {
return (
<FontAwesomeIcon
icon={icon}
size={size}
color={
selectedIcon === icon.iconName
? "blue"
: "black"
}
/> />
)
}}
size={30}
onPress={() => {
handleIconSelect(icon.iconName)
}}
/>
)
})}
</View>
) : (
<View style={styles.noResults}>
<Text>No results found</Text>
</View>
)}
</View> </View>
</List.Section> </List.Section>
</ScrollView> </ScrollView>