mattermost-mobile/app/screens/home/search/initial/modifiers/index.tsx
Daniel Espino García 0c4a42a06a
Remove tand prevent double tap (#9078)
* Remove the t function and all wrong uses of preventDoubleTap

* Fix existing typo

* Fix tests

* Address comments

* Fix test
2025-08-25 12:03:01 +02:00

175 lines
5.8 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {type Dispatch, type RefObject, type SetStateAction, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {type IntlShape, useIntl} from 'react-intl';
import {View} from 'react-native';
import Animated, {type SharedValue, useSharedValue, useAnimatedStyle, withTiming} from 'react-native-reanimated';
import FormattedText from '@components/formatted_text';
import {ALL_TEAMS_ID} from '@constants/team';
import {useTheme} from '@context/theme';
import TeamPicker from '@screens/home/search/team_picker';
import {makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
import Modifier, {type ModifierItem} from './modifier';
import ShowMoreButton from './show_more';
import type {SearchRef} from '@components/search';
import type TeamModel from '@typings/database/models/servers/team';
const MODIFIER_LABEL_HEIGHT = 48;
const NUM_ITEMS_BEFORE_EXPAND = 4;
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
header: {
alignItems: 'center',
flexDirection: 'row',
marginTop: 20,
},
titleContainer: {
flex: 1,
},
title: {
alignItems: 'center',
color: theme.centerChannelColor,
...typography('Heading', 300, 'SemiBold'),
},
teamPickerContainer: {
flex: 1,
alignItems: 'flex-end',
},
};
});
const getModifiersSectionsData = (intl: IntlShape, teamId: string): ModifierItem[] => {
const formatMessage = intl.formatMessage;
const sectionsData = [];
if (teamId !== ALL_TEAMS_ID) {
sectionsData.push({
term: 'From:',
testID: 'search.modifier.from',
description: formatMessage({id: 'mobile.search.modifier.from', defaultMessage: 'a specific user'}),
}, {
term: 'In:',
testID: 'search.modifier.in',
description: formatMessage({id: 'mobile.search.modifier.in', defaultMessage: 'a specific channel'}),
});
}
sectionsData.push({
term: '-',
testID: 'search.modifier.exclude',
description: formatMessage({id: 'mobile.search.modifier.exclude', defaultMessage: 'exclude search terms'}),
}, {
term: '""',
testID: 'search.modifier.phrases',
description: formatMessage({id: 'mobile.search.modifier.phrases', defaultMessage: 'messages with phrases'}),
cursorPosition: -1,
});
return sectionsData;
};
type Props = {
scrollEnabled: SharedValue<boolean>;
searchRef: RefObject<SearchRef>;
setSearchValue: Dispatch<SetStateAction<string>>;
searchValue?: string;
setTeamId: (id: string) => void;
teamId: string;
teams: TeamModel[];
crossTeamSearchEnabled: boolean;
}
const Modifiers = ({scrollEnabled, searchValue, setSearchValue, searchRef, setTeamId, teamId, teams, crossTeamSearchEnabled}: Props) => {
const theme = useTheme();
const intl = useIntl();
const [showMore, setShowMore] = useState(false);
const height = useSharedValue(NUM_ITEMS_BEFORE_EXPAND * MODIFIER_LABEL_HEIGHT);
const data = useMemo(() => getModifiersSectionsData(intl, teamId), [intl, teamId]);
const timeoutRef = useRef<NodeJS.Timeout | undefined>();
const styles = getStyleFromTheme(theme);
const animatedStyle = useAnimatedStyle(() => ({
width: '100%',
height: withTiming(height.value, {duration: 300}),
overflow: 'hidden',
}), []);
const handleShowMore = useCallback(() => {
const nextShowMore = !showMore;
setShowMore(nextShowMore);
scrollEnabled.value = false;
height.value = (nextShowMore ? data.length : NUM_ITEMS_BEFORE_EXPAND) * MODIFIER_LABEL_HEIGHT;
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
scrollEnabled.value = true;
}, 350);
}, [data.length, height, scrollEnabled, showMore]);
useEffect(() => {
return () => {
if (timeoutRef.current) {
scrollEnabled.value = true;
clearTimeout(timeoutRef.current);
}
};
}, []);
const renderModifier = (item: ModifierItem) => {
return (
<Modifier
key={item.term}
item={item}
searchRef={searchRef}
searchValue={searchValue}
setSearchValue={setSearchValue}
/>
);
};
return (
<>
<View style={styles.header}>
<View style={styles.titleContainer}>
<FormattedText
style={styles.title}
id={'screen.search.modifier.header'}
defaultMessage='Search options'
testID='search.modifier.header'
/>
</View>
{teams.length > 1 &&
<View style={styles.teamPickerContainer}>
<TeamPicker
setTeamId={setTeamId}
teamId={teamId}
teams={teams}
crossTeamSearchEnabled={crossTeamSearchEnabled}
/>
</View>
}
</View>
<Animated.View style={animatedStyle}>
{data.map((item) => renderModifier(item))}
</Animated.View>
{data.length > NUM_ITEMS_BEFORE_EXPAND &&
<ShowMoreButton
onPress={handleShowMore}
showMore={showMore}
/>
}
</>
);
};
export default Modifiers;