import { BottomSheetModal, BottomSheetProps } from '@gorhom/bottom-sheet' import React, { Dispatch, SetStateAction, useRef } from 'react' import { useTranslation } from 'react-i18next' import { StyleSheet, Text, View } from 'react-native' import FastImage from 'react-native-fast-image' import { ScrollView } from 'react-native-gesture-handler' import BottomSheetBase from 'src/components/BottomSheetBase' import BottomSheetScrollView from 'src/components/BottomSheetScrollView' import Touchable from 'src/components/Touchable' import Checkmark from 'src/icons/Checkmark' import Colors from 'src/styles/colors' import { typeScale } from 'src/styles/fonts' import { Spacing } from 'src/styles/styles' const OPTION_HEIGHT = 60 const MAX_OPTIONS_IN_VIEW = 10.5 export interface MultiSelectBottomSheetProps { forwardedRef: React.RefObject onChange?: BottomSheetProps['onChange'] onSelect?: (ids: T[]) => void onOpen?: () => void handleComponent?: BottomSheetProps['handleComponent'] options: Option[] selectedOptions: T[] setSelectedOptions: Dispatch> selectAllText: string title: string mode?: 'select-all-or-one' | 'select-multiple' } interface Option { id: T text: string iconUrl?: string } function MultiSelectBottomSheet({ forwardedRef, onSelect, onOpen, options, setSelectedOptions, selectedOptions, selectAllText, title, mode = 'select-multiple', }: MultiSelectBottomSheetProps) { const { t } = useTranslation() const scrollViewRef = useRef(null) const isEveryOptionSelected = options.length === selectedOptions.length const handleClose = () => { forwardedRef.current?.close() } const handleSelection = (newOptions: T[]) => { setSelectedOptions(newOptions) onSelect?.(newOptions) } const handleSelectAll = () => { handleSelection(options.map((option) => option.id)) if (mode === 'select-all-or-one') { handleClose() } } const toggleOption = (option: Option) => { if (mode === 'select-all-or-one') { // automatically close the bottom sheet after state update if only one option can be selected handleSelection([option.id]) handleClose() } else { const isOptionPreviouslySelected = selectedOptions.includes(option.id) const updatedSelectedOptions = isEveryOptionSelected ? [option.id] : isOptionPreviouslySelected ? selectedOptions.filter((selectedOption) => selectedOption !== option.id) : [...selectedOptions, option.id] handleSelection(updatedSelectedOptions) } } return ( {title} {options.map((option) => ( toggleOption(option)} /> ))} {t('done')} ) } interface OptionLineItemProps { onPress?: () => void text: string isSelected: boolean iconUrl?: string } function OptionLineItem({ onPress, text, iconUrl, isSelected }: OptionLineItemProps) { return ( {text} {isSelected && ( )} ) } const styles = StyleSheet.create({ doneButton: { borderRadius: Spacing.Regular16, backgroundColor: Colors.backgroundPrimary, height: 56, flexGrow: 1, alignItems: 'center', justifyContent: 'center', paddingVertical: 5, paddingHorizontal: Spacing.Thick24, }, doneButtonContainer: { flexDirection: 'column', }, icon: { width: Spacing.Large32, height: Spacing.Large32, borderRadius: Spacing.Regular16, }, leftColumn: { alignItems: 'flex-start', width: Spacing.Large32, justifyContent: 'center', }, centerColumn: { flex: 1, alignItems: 'center', }, rightColumn: { width: Spacing.Large32, alignItems: 'flex-end', }, checkMarkContainer: { flex: 1, flexDirection: 'column', justifyContent: 'center', }, optionContainer: { flexDirection: 'column', }, option: { height: OPTION_HEIGHT, flexGrow: 1, alignItems: 'center', justifyContent: 'center', paddingVertical: 5, paddingHorizontal: Spacing.Thick24, borderTopWidth: 1, borderColor: Colors.borderPrimary, }, optionRow: { flexDirection: 'row', justifyContent: 'space-between', }, borderRadiusTop: { borderTopLeftRadius: Spacing.Regular16, borderTopRightRadius: Spacing.Regular16, backgroundColor: Colors.backgroundPrimary, }, optionsContainer: { flexDirection: 'column', marginBottom: Spacing.Smallest8, backgroundColor: Colors.backgroundPrimary, borderBottomLeftRadius: Spacing.Regular16, borderBottomRightRadius: Spacing.Regular16, }, textStyle: { ...typeScale.bodyLarge, }, boldTextStyle: { ...typeScale.labelSemiBoldLarge, }, bottomSheetScrollView: { marginHorizontal: Spacing.Regular16, padding: 0, }, }) export default MultiSelectBottomSheet