BottomSheet migration to react-native-bottom-sheet (#6907)
* BottomSheet migration to react-native-bottom-sheet * Use correct scroll view for announcement bottom sheet * ux review * Fix post options bottom sheet snap point * feedback review
This commit is contained in:
parent
e9b8160f31
commit
dbe565319d
47 changed files with 547 additions and 399 deletions
22
NOTICE.txt
22
NOTICE.txt
|
|
@ -2962,28 +2962,6 @@ IN THE SOFTWARE.
|
|||
"""
|
||||
|
||||
|
||||
---
|
||||
|
||||
## reanimated-bottom-sheet
|
||||
|
||||
This product contains a modified version of 'reanimated-bottom-sheet' by Michał Osadnik.
|
||||
|
||||
Highly configurable component imitating native bottom sheet behavior, with fully native 60 FPS animations!
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/osdnk/react-native-reanimated-bottom-sheet
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
Copyright 2019 – present Michał Osadnik
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## semver
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {dismissAnnouncement} from '@actions/local/systems';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
|
|
@ -17,6 +18,7 @@ import {ANNOUNCEMENT_BAR_HEIGHT} from '@constants/view';
|
|||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {bottomSheet} from '@screens/navigation';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {getMarkdownTextStyles} from '@utils/markdown';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
|
@ -82,6 +84,7 @@ const AnnouncementBanner = ({
|
|||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
const height = useSharedValue(0);
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const theme = useTheme();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const style = getStyle(theme);
|
||||
|
|
@ -100,19 +103,20 @@ const AnnouncementBanner = ({
|
|||
defaultMessage: 'Announcement',
|
||||
});
|
||||
|
||||
let snapPoint = SNAP_POINT_WITHOUT_DISMISS;
|
||||
if (allowDismissal) {
|
||||
snapPoint += DISMISS_BUTTON_HEIGHT;
|
||||
}
|
||||
const snapPoint = bottomSheetSnapPoint(
|
||||
1,
|
||||
SNAP_POINT_WITHOUT_DISMISS + (allowDismissal ? DISMISS_BUTTON_HEIGHT : 0),
|
||||
bottom,
|
||||
);
|
||||
|
||||
bottomSheet({
|
||||
closeButtonId: CLOSE_BUTTON_ID,
|
||||
title,
|
||||
renderContent,
|
||||
snapPoints: [snapPoint, 10],
|
||||
snapPoints: [1, snapPoint],
|
||||
theme,
|
||||
});
|
||||
}, [theme.sidebarHeaderTextColor, intl.locale, renderContent, allowDismissal]);
|
||||
}, [theme.sidebarHeaderTextColor, intl.locale, renderContent, allowDismissal, bottom]);
|
||||
|
||||
const handleDismiss = useCallback(() => {
|
||||
dismissAnnouncement(serverUrl, bannerText);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BottomSheetScrollView} from '@gorhom/bottom-sheet';
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Text, View} from 'react-native';
|
||||
import {ScrollView, Text, View} from 'react-native';
|
||||
import Button from 'react-native-button';
|
||||
import {ScrollView} from 'react-native-gesture-handler';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {dismissAnnouncement} from '@actions/local/systems';
|
||||
|
|
@ -84,6 +84,8 @@ const ExpandedAnnouncementBanner = ({
|
|||
return [style.container, {marginBottom: insets.bottom + 10}];
|
||||
}, [style, insets.bottom]);
|
||||
|
||||
const Scroll = useMemo(() => (isTablet ? ScrollView : BottomSheetScrollView), [isTablet]);
|
||||
|
||||
return (
|
||||
<View style={containerStyle}>
|
||||
{!isTablet && (
|
||||
|
|
@ -94,7 +96,7 @@ const ExpandedAnnouncementBanner = ({
|
|||
})}
|
||||
</Text>
|
||||
)}
|
||||
<ScrollView
|
||||
<Scroll
|
||||
style={style.scrollContainer}
|
||||
>
|
||||
<Markdown
|
||||
|
|
@ -106,7 +108,7 @@ const ExpandedAnnouncementBanner = ({
|
|||
theme={theme}
|
||||
location={Screens.BOTTOM_SHEET}
|
||||
/>
|
||||
</ScrollView>
|
||||
</Scroll>
|
||||
<Button
|
||||
containerStyle={buttonStyles.okay.button}
|
||||
onPress={close}
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@ type Props = {
|
|||
testID?: string;
|
||||
}
|
||||
|
||||
const OPTIONS_HEIGHT = 62;
|
||||
export const CHANNEL_ACTIONS_OPTIONS_HEIGHT = 62;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
wrapper: {
|
||||
flexDirection: 'row',
|
||||
height: OPTIONS_HEIGHT,
|
||||
height: CHANNEL_ACTIONS_OPTIONS_HEIGHT,
|
||||
},
|
||||
separator: {
|
||||
width: 8,
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ const AtMention = ({
|
|||
const intl = useIntl();
|
||||
const managedConfig = useManagedConfig<ManagedConfig>();
|
||||
const theme = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const user = useMemo(() => {
|
||||
|
|
@ -92,6 +92,7 @@ const AtMention = ({
|
|||
// @ts-expect-error: The model constructor is hidden within WDB type definition
|
||||
return new UserModel(database.get(USER), {username: ''});
|
||||
}, [users, mentionName]);
|
||||
|
||||
const userMentionKeys = useMemo(() => {
|
||||
if (mentionKeys) {
|
||||
return mentionKeys;
|
||||
|
|
@ -195,12 +196,12 @@ const AtMention = ({
|
|||
bottomSheet({
|
||||
closeButtonId: 'close-at-mention',
|
||||
renderContent,
|
||||
snapPoints: [bottomSheetSnapPoint(2, ITEM_HEIGHT, insets.bottom), 10],
|
||||
snapPoints: [1, bottomSheetSnapPoint(2, ITEM_HEIGHT, bottom)],
|
||||
title: intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}),
|
||||
theme,
|
||||
});
|
||||
}
|
||||
}, [managedConfig, intl, theme, insets]);
|
||||
}, [managedConfig, intl, theme, bottom]);
|
||||
|
||||
const mentionTextStyle = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ const MarkdownCodeBlock = ({language = '', content, textStyle}: MarkdownCodeBloc
|
|||
const intl = useIntl();
|
||||
const managedConfig = useManagedConfig<ManagedConfig>();
|
||||
const theme = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const style = getStyleSheet(theme);
|
||||
const SyntaxHighlighter = useMemo(() => {
|
||||
if (!syntaxHighlighter) {
|
||||
|
|
@ -147,12 +147,12 @@ const MarkdownCodeBlock = ({language = '', content, textStyle}: MarkdownCodeBloc
|
|||
bottomSheet({
|
||||
closeButtonId: 'close-code-block',
|
||||
renderContent,
|
||||
snapPoints: [bottomSheetSnapPoint(2, ITEM_HEIGHT, insets.bottom), 10],
|
||||
snapPoints: [1, bottomSheetSnapPoint(2, ITEM_HEIGHT, bottom)],
|
||||
title: intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}),
|
||||
theme,
|
||||
});
|
||||
}
|
||||
}, [managedConfig, intl, insets, theme]);
|
||||
}, [managedConfig, intl, bottom, theme]);
|
||||
|
||||
const trimContent = (text: string) => {
|
||||
const lines = text.split('\n');
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ const MarkdownImage = ({
|
|||
}: MarkdownImageProps) => {
|
||||
const intl = useIntl();
|
||||
const isTablet = useIsTablet();
|
||||
const insets = useSafeAreaInsets();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const theme = useTheme();
|
||||
const style = getStyleSheet(theme);
|
||||
const managedConfig = useManagedConfig<ManagedConfig>();
|
||||
|
|
@ -181,12 +181,12 @@ const MarkdownImage = ({
|
|||
bottomSheet({
|
||||
closeButtonId: 'close-mardown-image',
|
||||
renderContent,
|
||||
snapPoints: [bottomSheetSnapPoint(2, ITEM_HEIGHT, insets.bottom), 10],
|
||||
snapPoints: [1, bottomSheetSnapPoint(2, ITEM_HEIGHT, bottom)],
|
||||
title: intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}),
|
||||
theme,
|
||||
});
|
||||
}
|
||||
}, [managedConfig, intl.locale, insets.bottom, theme]);
|
||||
}, [managedConfig, intl.locale, bottom, theme]);
|
||||
|
||||
const handleOnError = useCallback(() => {
|
||||
setFailed(true);
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
|
||||
const LatexCodeBlock = ({content, theme}: Props) => {
|
||||
const intl = useIntl();
|
||||
const insets = useSafeAreaInsets();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const managedConfig = useManagedConfig<ManagedConfig>();
|
||||
const styles = getStyleSheet(theme);
|
||||
const languageDisplayName = getHighlightLanguageName('latex');
|
||||
|
|
@ -161,12 +161,12 @@ const LatexCodeBlock = ({content, theme}: Props) => {
|
|||
bottomSheet({
|
||||
closeButtonId: 'close-code-block',
|
||||
renderContent,
|
||||
snapPoints: [bottomSheetSnapPoint(2, ITEM_HEIGHT, insets.bottom), 10],
|
||||
snapPoints: [1, bottomSheetSnapPoint(2, ITEM_HEIGHT, bottom)],
|
||||
title: intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}),
|
||||
theme,
|
||||
});
|
||||
}
|
||||
}, [managedConfig?.copyAndPasteProtection, intl, insets, theme]);
|
||||
}, [managedConfig?.copyAndPasteProtection, intl, bottom, theme]);
|
||||
|
||||
const onRenderErrorMessage = useCallback(({error}: {error: Error}) => {
|
||||
return <Text style={styles.errorText}>{'Render error: ' + error.message}</Text>;
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ const parseLinkLiteral = (literal: string) => {
|
|||
|
||||
const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteURL}: MarkdownLinkProps) => {
|
||||
const intl = useIntl();
|
||||
const insets = useSafeAreaInsets();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const managedConfig = useManagedConfig<ManagedConfig>();
|
||||
const serverUrl = useServerUrl();
|
||||
const theme = useTheme();
|
||||
|
|
@ -140,12 +140,12 @@ const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteU
|
|||
bottomSheet({
|
||||
closeButtonId: 'close-mardown-link',
|
||||
renderContent,
|
||||
snapPoints: [bottomSheetSnapPoint(2, ITEM_HEIGHT, insets.bottom), 10],
|
||||
snapPoints: [1, bottomSheetSnapPoint(2, ITEM_HEIGHT, bottom)],
|
||||
title: intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}),
|
||||
theme,
|
||||
});
|
||||
}
|
||||
}, [managedConfig, intl, insets, theme]);
|
||||
}, [managedConfig, intl, bottom, theme]);
|
||||
|
||||
const renderChildren = experimentalNormalizeMarkdownLinks ? parseChildren() : children;
|
||||
|
||||
|
|
|
|||
|
|
@ -69,11 +69,10 @@ export default function CameraQuickAction({
|
|||
return;
|
||||
}
|
||||
|
||||
const snap = bottomSheetSnapPoint(2, ITEM_HEIGHT, bottom);
|
||||
bottomSheet({
|
||||
title: intl.formatMessage({id: 'mobile.camera_type.title', defaultMessage: 'Camera options'}),
|
||||
renderContent,
|
||||
snapPoints: [TITLE_HEIGHT + snap, 10],
|
||||
snapPoints: [1, bottomSheetSnapPoint(2, ITEM_HEIGHT, bottom) + TITLE_HEIGHT],
|
||||
theme,
|
||||
closeButtonId: 'camera-close-id',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@
|
|||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import PostPriorityPicker from '@components/post_priority/post_priority_picker';
|
||||
import PostPriorityPicker, {COMPONENT_HEIGHT} from '@components/post_priority/post_priority_picker';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {ICON_SIZE} from '@constants/post_draft';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {bottomSheet, dismissBottomSheet} from '@screens/navigation';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -34,6 +36,7 @@ export default function PostPriorityAction({
|
|||
}: Props) {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
|
||||
const handlePostPriorityPicker = useCallback((postPriorityData: PostPriorityData) => {
|
||||
updatePostPriority(postPriorityData);
|
||||
|
|
@ -55,11 +58,11 @@ export default function PostPriorityAction({
|
|||
bottomSheet({
|
||||
title: intl.formatMessage({id: 'post_priority.picker.title', defaultMessage: 'Message priority'}),
|
||||
renderContent,
|
||||
snapPoints: [275, 10],
|
||||
snapPoints: [1, bottomSheetSnapPoint(1, COMPONENT_HEIGHT, bottom)],
|
||||
theme,
|
||||
closeButtonId: 'post-priority-close-id',
|
||||
});
|
||||
}, [intl, renderContent, theme]);
|
||||
}, [intl, renderContent, theme, bottom]);
|
||||
|
||||
const iconName = 'alert-circle-outline';
|
||||
const iconColor = changeOpacity(theme.centerChannelColor, 0.64);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const styles = StyleSheet.create({
|
|||
|
||||
const Failed = ({post, theme}: FailedProps) => {
|
||||
const intl = useIntl();
|
||||
const insets = useSafeAreaInsets();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
|
|
@ -69,11 +69,11 @@ const Failed = ({post, theme}: FailedProps) => {
|
|||
bottomSheet({
|
||||
closeButtonId: 'close-post-failed',
|
||||
renderContent,
|
||||
snapPoints: [bottomSheetSnapPoint(2, ITEM_HEIGHT, insets.bottom), 10],
|
||||
snapPoints: [1, bottomSheetSnapPoint(2, ITEM_HEIGHT, bottom)],
|
||||
title: intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}),
|
||||
theme,
|
||||
});
|
||||
}, [insets]);
|
||||
}, [bottom]);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ type Props = {
|
|||
onSubmit: (data: PostPriorityData) => void;
|
||||
};
|
||||
|
||||
export const COMPONENT_HEIGHT = 200;
|
||||
|
||||
const getStyle = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {BottomSheetFlatList} from '@gorhom/bottom-sheet';
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {ListRenderItemInfo, StyleSheet, View} from 'react-native';
|
||||
import {FlatList} from 'react-native-gesture-handler'; // Keep the FlatList from gesture handler so it works well with bottom sheet
|
||||
|
||||
|
|
@ -12,20 +13,21 @@ import TeamListItem from './team_list_item';
|
|||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
type Props = {
|
||||
teams: Array<Team|TeamModel>;
|
||||
textColor?: string;
|
||||
iconTextColor?: string;
|
||||
iconBackgroundColor?: string;
|
||||
onPress: (id: string) => void;
|
||||
testID?: string;
|
||||
selectedTeamId?: string;
|
||||
onEndReached?: () => void;
|
||||
iconTextColor?: string;
|
||||
loading?: boolean;
|
||||
onEndReached?: () => void;
|
||||
onPress: (id: string) => void;
|
||||
selectedTeamId?: string;
|
||||
teams: Array<Team|TeamModel>;
|
||||
testID?: string;
|
||||
textColor?: string;
|
||||
type?: BottomSheetList;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexShrink: 1,
|
||||
flexGrow: 1,
|
||||
},
|
||||
contentContainer: {
|
||||
marginBottom: 4,
|
||||
|
|
@ -34,7 +36,20 @@ const styles = StyleSheet.create({
|
|||
|
||||
const keyExtractor = (item: TeamModel) => item.id;
|
||||
|
||||
export default function TeamList({teams, textColor, iconTextColor, iconBackgroundColor, onPress, testID, selectedTeamId, onEndReached, loading = false}: Props) {
|
||||
export default function TeamList({
|
||||
iconBackgroundColor,
|
||||
iconTextColor,
|
||||
loading = false,
|
||||
onEndReached,
|
||||
onPress,
|
||||
selectedTeamId,
|
||||
teams,
|
||||
testID,
|
||||
textColor,
|
||||
type = 'FlatList',
|
||||
}: Props) {
|
||||
const List = useMemo(() => (type === 'FlatList' ? FlatList : BottomSheetFlatList), [type]);
|
||||
|
||||
const renderTeam = useCallback(({item: t}: ListRenderItemInfo<Team|TeamModel>) => {
|
||||
return (
|
||||
<TeamListItem
|
||||
|
|
@ -55,7 +70,7 @@ export default function TeamList({teams, textColor, iconTextColor, iconBackgroun
|
|||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<FlatList
|
||||
<List
|
||||
data={teams}
|
||||
renderItem={renderTeam}
|
||||
keyExtractor={keyExtractor}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BottomSheetProps} from '@gorhom/bottom-sheet';
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleProp, Text, TouchableOpacity, View, ViewStyle} from 'react-native';
|
||||
|
|
@ -123,12 +124,17 @@ const UserAvatarsStack = ({breakAt = 3, channelId, location, style: baseContaine
|
|||
/>
|
||||
</>
|
||||
);
|
||||
const snap = bottomSheetSnapPoint(Math.min(users.length, 5), USER_ROW_HEIGHT, bottom);
|
||||
|
||||
const snapPoints: BottomSheetProps['snapPoints'] = [1, bottomSheetSnapPoint(Math.min(users.length, 5), USER_ROW_HEIGHT, bottom) + TITLE_HEIGHT];
|
||||
if (users.length > 5) {
|
||||
snapPoints.push('90%');
|
||||
}
|
||||
|
||||
bottomSheet({
|
||||
closeButtonId: 'close-set-user-status',
|
||||
renderContent,
|
||||
initialSnapIndex: 1,
|
||||
snapPoints: ['90%', TITLE_HEIGHT + snap, 10],
|
||||
snapPoints,
|
||||
title: intl.formatMessage({id: 'mobile.participants.header', defaultMessage: 'Thread Participants'}),
|
||||
theme,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BottomSheetFlatList} from '@gorhom/bottom-sheet';
|
||||
import React, {useCallback, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, ListRenderItemInfo, NativeScrollEvent, NativeSyntheticEvent, PanResponder, StyleProp, StyleSheet, TouchableOpacity, ViewStyle} from 'react-native';
|
||||
|
|
@ -16,6 +17,7 @@ import type UserModel from '@typings/database/models/servers/user';
|
|||
type Props = {
|
||||
channelId: string;
|
||||
location: string;
|
||||
type?: BottomSheetList;
|
||||
users: UserModel[];
|
||||
};
|
||||
|
||||
|
|
@ -58,8 +60,8 @@ const Item = ({channelId, containerStyle, location, user}: ItemProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
const UsersList = ({channelId, location, users}: Props) => {
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const UsersList = ({channelId, location, type = 'FlatList', users}: Props) => {
|
||||
const [enabled, setEnabled] = useState(type === 'BottomSheetFlatList');
|
||||
const [direction, setDirection] = useState<'down' | 'up'>('down');
|
||||
const listRef = useRef<FlatList>(null);
|
||||
const prevOffset = useRef(0);
|
||||
|
|
@ -91,6 +93,16 @@ const UsersList = ({channelId, location, users}: Props) => {
|
|||
/>
|
||||
), [channelId, location]);
|
||||
|
||||
if (type === 'BottomSheetFlatList') {
|
||||
return (
|
||||
<BottomSheetFlatList
|
||||
data={users}
|
||||
renderItem={renderItem}
|
||||
overScrollMode={'always'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={users}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const DEFAULT_EMOJIS = [
|
|||
export const LARGE_ICON_SIZE = 32;
|
||||
export const LARGE_CONTAINER_SIZE = 48;
|
||||
export const REACTION_PICKER_HEIGHT = 48;
|
||||
export const REACTION_PICKER_MARGIN = 16;
|
||||
export const SMALL_CONTAINER_SIZE = 44;
|
||||
export const SMALL_ICON_BREAKPOINT = 410;
|
||||
export const SMALL_ICON_SIZE = 28;
|
||||
|
|
@ -21,6 +22,7 @@ export default {
|
|||
LARGE_ICON_SIZE,
|
||||
LARGE_CONTAINER_SIZE,
|
||||
REACTION_PICKER_HEIGHT,
|
||||
REACTION_PICKER_MARGIN,
|
||||
SMALL_CONTAINER_SIZE,
|
||||
SMALL_ICON_BREAKPOINT,
|
||||
SMALL_ICON_SIZE,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const TEAM_SIDEBAR_WIDTH = 72;
|
|||
export const TABLET_HEADER_HEIGHT = 44;
|
||||
export const TABLET_SIDEBAR_WIDTH = 320;
|
||||
|
||||
export const IOS_STATUS_BAR_HEIGHT = 20;
|
||||
export const STATUS_BAR_HEIGHT = 20;
|
||||
export const DEFAULT_HEADER_HEIGHT = Platform.select({android: 56, default: 44});
|
||||
export const LARGE_HEADER_TITLE_HEIGHT = 60;
|
||||
export const SUBTITLE_HEIGHT = 24;
|
||||
|
|
@ -25,8 +25,6 @@ export const JOIN_CALL_BAR_HEIGHT = 38;
|
|||
export const CURRENT_CALL_BAR_HEIGHT = 74;
|
||||
export const CALL_ERROR_BAR_HEIGHT = 62;
|
||||
|
||||
export const QUICK_OPTIONS_HEIGHT = 270;
|
||||
|
||||
export const ANNOUNCEMENT_BAR_HEIGHT = 40;
|
||||
|
||||
export default {
|
||||
|
|
@ -41,11 +39,10 @@ export default {
|
|||
TABLET_SIDEBAR_WIDTH,
|
||||
TEAM_SIDEBAR_WIDTH,
|
||||
TABLET_HEADER_HEIGHT,
|
||||
IOS_STATUS_BAR_HEIGHT,
|
||||
STATUS_BAR_HEIGHT,
|
||||
DEFAULT_HEADER_HEIGHT,
|
||||
LARGE_HEADER_TITLE_HEIGHT,
|
||||
SUBTITLE_HEIGHT,
|
||||
KEYBOARD_TRACKING_OFFSET,
|
||||
QUICK_OPTIONS_HEIGHT,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ const CallScreen = ({
|
|||
}: Props) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const {width, height} = useWindowDimensions();
|
||||
const serverUrl = useServerUrl();
|
||||
const {EnableRecordings} = useCallsConfig(serverUrl);
|
||||
|
|
@ -407,11 +407,11 @@ const CallScreen = ({
|
|||
await bottomSheet({
|
||||
closeButtonId: 'close-other-actions',
|
||||
renderContent,
|
||||
snapPoints: [bottomSheetSnapPoint(items, ITEM_HEIGHT, insets.bottom), 10],
|
||||
snapPoints: [1, bottomSheetSnapPoint(items, ITEM_HEIGHT, bottom)],
|
||||
title: intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}),
|
||||
theme,
|
||||
});
|
||||
}, [insets, intl, theme, isHost, EnableRecordings, waitingForRecording, recording, startRecording,
|
||||
}, [bottom, intl, theme, isHost, EnableRecordings, waitingForRecording, recording, startRecording,
|
||||
recordOptionTitle, stopRecording, stopRecordingOptionTitle, style, switchToThread, callThreadOptionTitle,
|
||||
openChannelOptionTitle]);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {GestureResponderEvent, StyleSheet, Text, View} from 'react-native';
|
||||
import {GestureResponderEvent, Platform, Text, useWindowDimensions, View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
disabled?: boolean;
|
||||
|
|
@ -18,22 +19,43 @@ type Props = {
|
|||
text?: string;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
icon_container: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
top: -1,
|
||||
marginRight: 4,
|
||||
},
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
button: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
buttonContainer: {
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
container: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
top: -1,
|
||||
marginRight: 4,
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
right: 20,
|
||||
borderTopWidth: 1,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
marginBottom: 20,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default function BottomSheetButton({disabled = false, onPress, icon, testID, text}: Props) {
|
||||
const theme = useTheme();
|
||||
export const BUTTON_HEIGHT = 101;
|
||||
|
||||
function BottomSheetButton({disabled = false, onPress, icon, testID, text}: Props) {
|
||||
const theme = useTheme();
|
||||
const dimensions = useWindowDimensions();
|
||||
const isTablet = useIsTablet();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const separatorWidth = Math.max(dimensions.width, 450);
|
||||
const buttonType = disabled ? 'disabled' : 'default';
|
||||
const styleButtonText = buttonTextStyle(theme, 'lg', 'primary', buttonType);
|
||||
const styleButtonBackground = buttonBackgroundStyle(theme, 'lg', 'primary', buttonType);
|
||||
|
|
@ -41,27 +63,35 @@ export default function BottomSheetButton({disabled = false, onPress, icon, test
|
|||
const iconColor = disabled ? changeOpacity(theme.centerChannelColor, 0.32) : theme.buttonColor;
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={onPress}
|
||||
type='opacity'
|
||||
style={[styles.button, styleButtonBackground]}
|
||||
testID={testID}
|
||||
>
|
||||
{icon && (
|
||||
<View style={styles.icon_container}>
|
||||
<CompassIcon
|
||||
size={24}
|
||||
name={icon}
|
||||
color={iconColor}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{text && (
|
||||
<Text
|
||||
style={styleButtonText}
|
||||
>{text}</Text>
|
||||
)}
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.separator, {width: separatorWidth}]}/>
|
||||
<View style={styles.buttonContainer}>
|
||||
<TouchableWithFeedback
|
||||
onPress={onPress}
|
||||
type='opacity'
|
||||
style={[styles.button, styleButtonBackground]}
|
||||
testID={testID}
|
||||
>
|
||||
{icon && (
|
||||
<View style={styles.iconContainer}>
|
||||
<CompassIcon
|
||||
size={24}
|
||||
name={icon}
|
||||
color={iconColor}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{text && (
|
||||
<Text
|
||||
style={styleButtonText}
|
||||
>{text}</Text>
|
||||
)}
|
||||
|
||||
</TouchableWithFeedback>
|
||||
</TouchableWithFeedback>
|
||||
<View style={{paddingBottom: Platform.select({ios: (isTablet ? 20 : 32), android: 20})}}/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default BottomSheetButton;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {GestureResponderEvent, Platform, Text, useWindowDimensions, View} from 'react-native';
|
||||
import {GestureResponderEvent, Text, useWindowDimensions, View} from 'react-native';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
|
|
@ -33,7 +33,7 @@ export const TITLE_SEPARATOR_MARGIN_TABLET = 20;
|
|||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
flexGrow: 1,
|
||||
},
|
||||
titleContainer: {
|
||||
marginTop: TITLE_MARGIN_TOP,
|
||||
|
|
@ -83,18 +83,15 @@ const BottomSheetContent = ({buttonText, buttonIcon, children, disableButton, on
|
|||
{children}
|
||||
</>
|
||||
{showButton && (
|
||||
<>
|
||||
<View style={[styles.separator, {width: separatorWidth}]}/>
|
||||
<Button
|
||||
disabled={disableButton}
|
||||
onPress={onPress}
|
||||
icon={buttonIcon}
|
||||
testID={buttonTestId}
|
||||
text={buttonText}
|
||||
/>
|
||||
<View style={{paddingBottom: Platform.select({ios: (isTablet ? 20 : 32), android: 20})}}/>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
disabled={disableButton}
|
||||
onPress={onPress}
|
||||
icon={buttonIcon}
|
||||
testID={buttonTestId}
|
||||
text={buttonText}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {ReactNode, useCallback, useEffect, useRef} from 'react';
|
||||
import {DeviceEventEmitter, Keyboard, StyleSheet, View} from 'react-native';
|
||||
import {State, TapGestureHandler} from 'react-native-gesture-handler';
|
||||
import {Navigation as RNN} from 'react-native-navigation';
|
||||
import Animated, {Easing, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
|
||||
import RNBottomSheet from 'reanimated-bottom-sheet';
|
||||
import BottomSheetM, {BottomSheetBackdrop, BottomSheetBackdropProps, BottomSheetFooterProps} from '@gorhom/bottom-sheet';
|
||||
import React, {ReactNode, useCallback, useEffect, useMemo, useRef} from 'react';
|
||||
import {DeviceEventEmitter, Keyboard, View} from 'react-native';
|
||||
|
||||
import useNavButtonPressed from '@app/hooks/navigation_button_pressed';
|
||||
import {Events} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
|
|
@ -18,36 +16,94 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
|||
|
||||
import Indicator from './indicator';
|
||||
|
||||
type SlideUpPanelProps = {
|
||||
import type {WithSpringConfig} from 'react-native-reanimated';
|
||||
|
||||
export {default as BottomSheetButton, BUTTON_HEIGHT} from './button';
|
||||
export {default as BottomSheetContent, TITLE_HEIGHT} from './content';
|
||||
|
||||
type Props = {
|
||||
closeButtonId?: string;
|
||||
componentId: string;
|
||||
initialSnapIndex?: number;
|
||||
footerComponent?: React.FC<BottomSheetFooterProps>;
|
||||
renderContent: () => ReactNode;
|
||||
snapPoints?: Array<string | number>;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
export const PADDING_TOP_MOBILE = 20;
|
||||
export const PADDING_TOP_TABLET = 8;
|
||||
const PADDING_TOP_MOBILE = 20;
|
||||
const PADDING_TOP_TABLET = 8;
|
||||
|
||||
const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderContent, snapPoints = ['90%', '50%', 50], testID}: SlideUpPanelProps) => {
|
||||
const sheetRef = useRef<RNBottomSheet>(null);
|
||||
export const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
bottomSheet: {
|
||||
borderTopStartRadius: 24,
|
||||
borderTopEndRadius: 24,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 8,
|
||||
},
|
||||
shadowOpacity: 0.12,
|
||||
shadowRadius: 24,
|
||||
shadowColor: '#000',
|
||||
elevation: 24,
|
||||
},
|
||||
bottomSheetBackground: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.16),
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: PADDING_TOP_MOBILE,
|
||||
},
|
||||
contentTablet: {
|
||||
paddingTop: PADDING_TOP_TABLET,
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
borderTopWidth: 1,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const animatedConfig: Omit<WithSpringConfig, 'velocity'> = {
|
||||
damping: 50,
|
||||
mass: 0.3,
|
||||
stiffness: 121.6,
|
||||
overshootClamping: true,
|
||||
restSpeedThreshold: 0.3,
|
||||
restDisplacementThreshold: 0.3,
|
||||
};
|
||||
|
||||
const BottomSheet = ({
|
||||
closeButtonId,
|
||||
componentId,
|
||||
initialSnapIndex = 1,
|
||||
footerComponent,
|
||||
renderContent,
|
||||
snapPoints = [1, '50%', '90%'],
|
||||
testID,
|
||||
}: Props) => {
|
||||
const sheetRef = useRef<BottomSheetM>(null);
|
||||
const isTablet = useIsTablet();
|
||||
const theme = useTheme();
|
||||
const firstRun = useRef(isTablet);
|
||||
const lastSnap = snapPoints.length - 1;
|
||||
const backdropOpacity = useSharedValue(0);
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const bottomSheetBackgroundStyle = useMemo(() => [
|
||||
styles.bottomSheetBackground,
|
||||
{borderWidth: isTablet ? 0 : 1},
|
||||
], [isTablet, styles]);
|
||||
|
||||
const close = useCallback(() => {
|
||||
if (firstRun.current) {
|
||||
dismissModal({componentId});
|
||||
}
|
||||
dismissModal({componentId});
|
||||
}, [componentId]);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = DeviceEventEmitter.addListener(Events.CLOSE_BOTTOM_SHEET, () => {
|
||||
if (sheetRef.current) {
|
||||
sheetRef.current.snapTo(lastSnap);
|
||||
sheetRef.current.close();
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
|
|
@ -58,84 +114,40 @@ const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderCo
|
|||
|
||||
const handleClose = useCallback(() => {
|
||||
if (sheetRef.current) {
|
||||
sheetRef.current.snapTo(1);
|
||||
sheetRef.current.close();
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleCloseEnd = useCallback(() => {
|
||||
if (firstRun.current) {
|
||||
backdropOpacity.value = 0;
|
||||
setTimeout(close, 250);
|
||||
const handleDismissIfNeeded = useCallback((index: number) => {
|
||||
if (index <= 0) {
|
||||
close();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleOpenStart = useCallback(() => {
|
||||
backdropOpacity.value = 1;
|
||||
}, []);
|
||||
|
||||
useAndroidHardwareBackHandler(componentId, handleClose);
|
||||
useNavButtonPressed(closeButtonId || '', componentId, close, [close]);
|
||||
|
||||
useEffect(() => {
|
||||
hapticFeedback();
|
||||
Keyboard.dismiss();
|
||||
sheetRef.current?.snapTo(initialSnapIndex);
|
||||
backdropOpacity.value = 1;
|
||||
const t = setTimeout(() => {
|
||||
firstRun.current = true;
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(t);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const navigationEvents = RNN.events().registerNavigationButtonPressedListener(({buttonId}) => {
|
||||
if (closeButtonId && buttonId === closeButtonId) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
return () => navigationEvents.remove();
|
||||
}, [close]);
|
||||
|
||||
const backdropStyle = useAnimatedStyle(() => ({
|
||||
opacity: withTiming(backdropOpacity.value, {duration: 250, easing: Easing.inOut(Easing.linear)}),
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||
}));
|
||||
|
||||
const renderBackdrop = () => {
|
||||
const renderBackdrop = useCallback((props: BottomSheetBackdropProps) => {
|
||||
return (
|
||||
<TapGestureHandler
|
||||
shouldCancelWhenOutside={true}
|
||||
maxDist={10}
|
||||
onHandlerStateChange={(event) => {
|
||||
if (event.nativeEvent.state === State.END && event.nativeEvent.oldState === State.ACTIVE) {
|
||||
sheetRef.current?.snapTo(lastSnap);
|
||||
}
|
||||
}}
|
||||
testID={`${testID}.backdrop`}
|
||||
>
|
||||
<Animated.View
|
||||
style={[StyleSheet.absoluteFill, backdropStyle]}
|
||||
/>
|
||||
</TapGestureHandler>
|
||||
<BottomSheetBackdrop
|
||||
{...props}
|
||||
disappearsOnIndex={0}
|
||||
appearsOnIndex={1}
|
||||
opacity={0.6}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const renderContainerContent = () => (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.16),
|
||||
borderWidth: isTablet ? 0 : 1,
|
||||
opacity: 1,
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: isTablet ? PADDING_TOP_TABLET : PADDING_TOP_MOBILE,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
style={[styles.content, isTablet && styles.contentTablet]}
|
||||
testID={`${testID}.screen`}
|
||||
>
|
||||
{renderContent()}
|
||||
|
|
@ -143,7 +155,6 @@ const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderCo
|
|||
);
|
||||
|
||||
if (isTablet) {
|
||||
const styles = getStyleSheet(theme);
|
||||
return (
|
||||
<>
|
||||
<View style={styles.separator}/>
|
||||
|
|
@ -153,32 +164,22 @@ const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderCo
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<RNBottomSheet
|
||||
ref={sheetRef}
|
||||
snapPoints={snapPoints}
|
||||
borderRadius={12}
|
||||
initialSnap={snapPoints.length - 1}
|
||||
renderContent={renderContainerContent}
|
||||
onCloseEnd={handleCloseEnd}
|
||||
onOpenStart={handleOpenStart}
|
||||
enabledBottomInitialAnimation={false}
|
||||
renderHeader={Indicator}
|
||||
enabledContentTapInteraction={false}
|
||||
/>
|
||||
{renderBackdrop()}
|
||||
</>
|
||||
<BottomSheetM
|
||||
ref={sheetRef}
|
||||
index={initialSnapIndex}
|
||||
snapPoints={snapPoints}
|
||||
animateOnMount={true}
|
||||
backdropComponent={renderBackdrop}
|
||||
onChange={handleDismissIfNeeded}
|
||||
animationConfigs={animatedConfig}
|
||||
handleComponent={Indicator}
|
||||
style={styles.bottomSheet}
|
||||
backgroundStyle={bottomSheetBackgroundStyle}
|
||||
footerComponent={footerComponent}
|
||||
>
|
||||
{renderContainerContent()}
|
||||
</BottomSheetM>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
separator: {
|
||||
height: 1,
|
||||
borderTopWidth: 1,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default BottomSheet;
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import {Animated, StyleSheet, View} from 'react-native';
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
dragIndicatorContainer: {
|
||||
marginVertical: 10,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
top: -15,
|
||||
},
|
||||
dragIndicator: {
|
||||
backgroundColor: 'white',
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export default function ChannelDropdown({
|
|||
sharedChannelsEnabled,
|
||||
}: Props) {
|
||||
const intl = useIntl();
|
||||
const insets = useSafeAreaInsets();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const theme = useTheme();
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
|
|
@ -73,11 +73,11 @@ export default function ChannelDropdown({
|
|||
items += 1;
|
||||
}
|
||||
|
||||
const itemsSnap = bottomSheetSnapPoint(items, ITEM_HEIGHT, insets.bottom) + TITLE_HEIGHT;
|
||||
const itemsSnap = bottomSheetSnapPoint(items, ITEM_HEIGHT, bottom) + TITLE_HEIGHT;
|
||||
bottomSheet({
|
||||
title: intl.formatMessage({id: 'browse_channels.dropdownTitle', defaultMessage: 'Show'}),
|
||||
renderContent,
|
||||
snapPoints: [itemsSnap, 10],
|
||||
snapPoints: [1, itemsSnap],
|
||||
closeButtonId: 'close',
|
||||
theme,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,14 +4,16 @@
|
|||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, Platform, Text, View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {bottomSheetSnapPoint} from '@app/utils/helpers';
|
||||
import {CHANNEL_ACTIONS_OPTIONS_HEIGHT} from '@components/channel_actions/channel_actions';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import CustomStatusEmoji from '@components/custom_status/custom_status_emoji';
|
||||
import NavigationHeader from '@components/navigation_header';
|
||||
import {ITEM_HEIGHT} from '@components/option_item';
|
||||
import RoundedHeaderContext from '@components/rounded_header_context';
|
||||
import {General, Screens} from '@constants';
|
||||
import {QUICK_OPTIONS_HEIGHT} from '@constants/view';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {useDefaultHeaderHeight} from '@hooks/header';
|
||||
|
|
@ -22,7 +24,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
|||
import {typography} from '@utils/typography';
|
||||
|
||||
import OtherMentionsBadge from './other_mentions_badge';
|
||||
import QuickActions from './quick_actions';
|
||||
import QuickActions, {MARGIN, SEPARATOR_HEIGHT} from './quick_actions';
|
||||
|
||||
import type {HeaderRightButton} from '@components/navigation_header/header';
|
||||
|
||||
|
|
@ -70,6 +72,7 @@ const ChannelHeader = ({
|
|||
}: ChannelProps) => {
|
||||
const intl = useIntl();
|
||||
const isTablet = useIsTablet();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const defaultHeight = useDefaultHeaderHeight();
|
||||
|
|
@ -132,7 +135,8 @@ const ChannelHeader = ({
|
|||
}
|
||||
|
||||
// When calls is enabled, we need space to move the "Copy Link" from a button to an option
|
||||
const height = QUICK_OPTIONS_HEIGHT + (callsAvailable && !isDMorGM ? ITEM_HEIGHT : 0);
|
||||
const items = callsAvailable && !isDMorGM ? 3 : 2;
|
||||
const height = CHANNEL_ACTIONS_OPTIONS_HEIGHT + SEPARATOR_HEIGHT + MARGIN + (items * ITEM_HEIGHT);
|
||||
|
||||
const renderContent = () => {
|
||||
return (
|
||||
|
|
@ -147,11 +151,11 @@ const ChannelHeader = ({
|
|||
bottomSheet({
|
||||
title: '',
|
||||
renderContent,
|
||||
snapPoints: [height, 10],
|
||||
snapPoints: [1, bottomSheetSnapPoint(1, height, bottom)],
|
||||
theme,
|
||||
closeButtonId: 'close-channel-quick-actions',
|
||||
});
|
||||
}, [channelId, isDMorGM, isTablet, onTitlePress, theme, callsAvailable]);
|
||||
}, [bottom, channelId, isDMorGM, isTablet, onTitlePress, theme, callsAvailable]);
|
||||
|
||||
const rightButtons: HeaderRightButton[] = useMemo(() => ([
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import ChannelActions from '@components/channel_actions';
|
|||
import CopyChannelLinkOption from '@components/channel_actions/copy_channel_link_option';
|
||||
import InfoBox from '@components/channel_actions/info_box';
|
||||
import LeaveChannelLabel from '@components/channel_actions/leave_channel_label';
|
||||
import {QUICK_OPTIONS_HEIGHT} from '@constants/view';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {dismissBottomSheet} from '@screens/navigation';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
|
@ -19,20 +18,23 @@ type Props = {
|
|||
isDMorGM: boolean;
|
||||
}
|
||||
|
||||
export const SEPARATOR_HEIGHT = 17;
|
||||
export const MARGIN = 8;
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {
|
||||
minHeight: QUICK_OPTIONS_HEIGHT,
|
||||
flex: 1,
|
||||
},
|
||||
line: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
height: 1,
|
||||
marginVertical: 8,
|
||||
marginVertical: MARGIN,
|
||||
},
|
||||
wrapper: {
|
||||
marginBottom: 8,
|
||||
marginBottom: MARGIN,
|
||||
},
|
||||
separator: {
|
||||
width: 8,
|
||||
width: MARGIN,
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {useServerUrl} from '@context/server';
|
|||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {TITLE_HEIGHT} from '@screens/bottom_sheet/content';
|
||||
import PanelItem from '@screens/edit_profile/components/panel_item';
|
||||
import {bottomSheet} from '@screens/navigation';
|
||||
import PickerUtil from '@utils/file/file_picker';
|
||||
|
|
@ -121,13 +122,12 @@ const ProfileImagePicker = ({
|
|||
);
|
||||
};
|
||||
|
||||
const snapPointsCount = canRemovePicture ? 5 : 4;
|
||||
const snapPoint = bottomSheetSnapPoint(snapPointsCount, ITEM_HEIGHT, bottom);
|
||||
const snapPoint = bottomSheetSnapPoint(4, ITEM_HEIGHT, bottom) + TITLE_HEIGHT;
|
||||
|
||||
return bottomSheet({
|
||||
closeButtonId: 'close-edit-profile',
|
||||
renderContent,
|
||||
snapPoints: [snapPoint, 10],
|
||||
snapPoints: [1, snapPoint],
|
||||
title: 'Change profile photo',
|
||||
theme,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ const UserStatus = ({currentUser}: Props) => {
|
|||
bottomSheet({
|
||||
closeButtonId: 'close-set-user-status',
|
||||
renderContent,
|
||||
snapPoints: [(snapPoint + TITLE_HEIGHT), 10],
|
||||
snapPoints: [1, (snapPoint + TITLE_HEIGHT)],
|
||||
title: intl.formatMessage({id: 'user_status.title', defaultMessage: 'Status'}),
|
||||
theme,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ const ChannelListHeader = ({
|
|||
const theme = useTheme();
|
||||
const isTablet = useIsTablet();
|
||||
const intl = useIntl();
|
||||
const insets = useSafeAreaInsets();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const serverDisplayName = useServerDisplayName();
|
||||
const marginLeft = useSharedValue(iconPad ? 50 : 0);
|
||||
const styles = getStyles(theme);
|
||||
|
|
@ -150,11 +150,11 @@ const ChannelListHeader = ({
|
|||
bottomSheet({
|
||||
closeButtonId,
|
||||
renderContent,
|
||||
snapPoints: [bottomSheetSnapPoint(items, ITEM_HEIGHT, insets.bottom) + (separators * SEPARATOR_HEIGHT), 10],
|
||||
snapPoints: [1, bottomSheetSnapPoint(items, ITEM_HEIGHT, bottom) + (separators * SEPARATOR_HEIGHT)],
|
||||
theme,
|
||||
title: intl.formatMessage({id: 'home.header.plus_menu', defaultMessage: 'Options'}),
|
||||
});
|
||||
}, [intl, insets, isTablet, theme]);
|
||||
}, [intl, bottom, isTablet, theme]);
|
||||
|
||||
const onPushAlertPress = useCallback(() => {
|
||||
if (pushProxyStatus === PUSH_PROXY_STATUS_NOT_AVAILABLE) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BottomSheetProps} from '@gorhom/bottom-sheet';
|
||||
import React, {useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import ServerIcon from '@components/server_icon';
|
||||
import {useServerUrl} from '@context/server';
|
||||
|
|
@ -11,14 +13,21 @@ import {useTheme} from '@context/theme';
|
|||
import {subscribeAllServers} from '@database/subscription/servers';
|
||||
import {subscribeUnreadAndMentionsByServer, UnreadObserverArgs} from '@database/subscription/unreads';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {BUTTON_HEIGHT, TITLE_HEIGHT} from '@screens/bottom_sheet';
|
||||
import {bottomSheet} from '@screens/navigation';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {sortServersByDisplayName} from '@utils/server';
|
||||
|
||||
import ServerList from './servers_list';
|
||||
import ServerList, {AddServerButton} from './servers_list';
|
||||
|
||||
import type ServersModel from '@typings/database/models/app/servers';
|
||||
import type {UnreadMessages, UnreadSubscription} from '@typings/database/subscriptions';
|
||||
|
||||
export type ServersRef = {
|
||||
openServers: () => void;
|
||||
}
|
||||
|
||||
export const SERVER_ITEM_HEIGHT = 72;
|
||||
const subscriptions: Map<string, UnreadSubscription> = new Map();
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
@ -34,16 +43,13 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
export type ServersRef = {
|
||||
openServers: () => void;
|
||||
}
|
||||
|
||||
const Servers = React.forwardRef<ServersRef>((props, ref) => {
|
||||
const Servers = React.forwardRef<ServersRef>((_, ref) => {
|
||||
const intl = useIntl();
|
||||
const [total, setTotal] = useState<UnreadMessages>({mentions: 0, unread: false});
|
||||
const registeredServers = useRef<ServersModel[]|undefined>();
|
||||
const currentServerUrl = useServerUrl();
|
||||
const isTablet = useIsTablet();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const theme = useTheme();
|
||||
|
||||
const updateTotal = () => {
|
||||
|
|
@ -111,21 +117,25 @@ const Servers = React.forwardRef<ServersRef>((props, ref) => {
|
|||
);
|
||||
};
|
||||
|
||||
const snapPoints = ['50%', 10];
|
||||
if (registeredServers.current.length > 3) {
|
||||
snapPoints[0] = '90%';
|
||||
const snapPoints: BottomSheetProps['snapPoints'] = [
|
||||
1,
|
||||
bottomSheetSnapPoint(Math.min(2.5, registeredServers.current.length), 72, bottom) + TITLE_HEIGHT + BUTTON_HEIGHT,
|
||||
];
|
||||
if (registeredServers.current.length > 1) {
|
||||
snapPoints.push('90%');
|
||||
}
|
||||
|
||||
const closeButtonId = 'close-your-servers';
|
||||
bottomSheet({
|
||||
closeButtonId,
|
||||
renderContent,
|
||||
footerComponent: AddServerButton,
|
||||
snapPoints,
|
||||
theme,
|
||||
title: intl.formatMessage({id: 'your.servers', defaultMessage: 'Your servers'}),
|
||||
});
|
||||
}
|
||||
}, [isTablet, theme]);
|
||||
}, [bottom, isTablet, theme]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
openServers: onPress,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BottomSheetFooter, BottomSheetFooterProps} from '@gorhom/bottom-sheet';
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
import {BottomSheetButton} from '@screens/bottom_sheet';
|
||||
import {addNewServer} from '@utils/server';
|
||||
|
||||
const AddServerButton = (props: BottomSheetFooterProps) => {
|
||||
const theme = useTheme();
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
const onAddServer = useCallback(async () => {
|
||||
addNewServer(theme);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BottomSheetFooter {...props}>
|
||||
<BottomSheetButton
|
||||
onPress={onAddServer}
|
||||
icon='plus'
|
||||
testID='servers.create_button'
|
||||
text={formatMessage({id: 'servers.create_button', defaultMessage: 'Add a server'})}
|
||||
/>
|
||||
</BottomSheetFooter>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddServerButton;
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {BottomSheetFlatList} from '@gorhom/bottom-sheet';
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {ListRenderItemInfo, StyleSheet, View} from 'react-native';
|
||||
import {FlatList} from 'react-native-gesture-handler';
|
||||
import {FlatList, ListRenderItemInfo, StyleSheet, View} from 'react-native';
|
||||
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
|
|
@ -16,13 +16,15 @@ import ServerItem from './server_item';
|
|||
|
||||
import type ServersModel from '@typings/database/models/app/servers';
|
||||
|
||||
export {default as AddServerButton} from './add_server_button';
|
||||
|
||||
type Props = {
|
||||
servers: ServersModel[];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
flexGrow: 1,
|
||||
},
|
||||
contentContainer: {
|
||||
marginVertical: 4,
|
||||
|
|
@ -51,18 +53,20 @@ const ServerList = ({servers}: Props) => {
|
|||
);
|
||||
}, []);
|
||||
|
||||
const List = useMemo(() => (isTablet ? FlatList : BottomSheetFlatList), [isTablet]);
|
||||
|
||||
return (
|
||||
<BottomSheetContent
|
||||
buttonIcon='plus'
|
||||
buttonText={intl.formatMessage({id: 'servers.create_button', defaultMessage: 'Add a server'})}
|
||||
onPress={onAddServer}
|
||||
showButton={true}
|
||||
showButton={isTablet}
|
||||
showTitle={!isTablet}
|
||||
testID='server_list'
|
||||
title={intl.formatMessage({id: 'your.servers', defaultMessage: 'Your servers'})}
|
||||
>
|
||||
<View style={[styles.container, {marginTop: isTablet ? 12 : 0}]}>
|
||||
<FlatList
|
||||
<List
|
||||
data={servers}
|
||||
renderItem={renderServer}
|
||||
keyExtractor={keyExtractor}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ type Props = {
|
|||
title: string;
|
||||
}
|
||||
|
||||
export default function SelectTeamSlideUp({teams, title, setTeamId, teamId}: Props) {
|
||||
export default function BottomSheetTeamList({teams, title, setTeamId, teamId}: Props) {
|
||||
const isTablet = useIsTablet();
|
||||
const showTitle = !isTablet && Boolean(teams.length);
|
||||
|
||||
|
|
@ -38,6 +38,7 @@ export default function SelectTeamSlideUp({teams, title, setTeamId, teamId}: Pro
|
|||
teams={teams}
|
||||
onPress={onPress}
|
||||
testID='search.select_team_slide_up.team_list'
|
||||
type={isTablet ? 'FlatList' : 'BottomSheetFlatList'}
|
||||
/>
|
||||
</BottomSheetContent>
|
||||
);
|
||||
|
|
@ -40,7 +40,8 @@ export const showMobileOptionsBottomSheet = ({
|
|||
closeButtonId: 'close-search-file-options',
|
||||
renderContent,
|
||||
snapPoints: [
|
||||
bottomSheetSnapPoint(numOptions, ITEM_HEIGHT, insets.bottom) + HEADER_HEIGHT, 10,
|
||||
1,
|
||||
bottomSheetSnapPoint(numOptions, ITEM_HEIGHT, insets.bottom) + HEADER_HEIGHT,
|
||||
],
|
||||
theme,
|
||||
title: '',
|
||||
|
|
|
|||
|
|
@ -93,12 +93,13 @@ const Header = ({
|
|||
|
||||
const snapPoints = useMemo(() => {
|
||||
return [
|
||||
1,
|
||||
bottomSheetSnapPoint(
|
||||
NUMBER_FILTER_ITEMS,
|
||||
FILTER_ITEM_HEIGHT,
|
||||
bottom,
|
||||
) + TITLE_HEIGHT + DIVIDERS_HEIGHT + (isTablet ? TITLE_SEPARATOR_MARGIN_TABLET : TITLE_SEPARATOR_MARGIN),
|
||||
10];
|
||||
];
|
||||
}, []);
|
||||
|
||||
const handleFilterPress = useCallback(() => {
|
||||
|
|
|
|||
|
|
@ -3,23 +3,28 @@
|
|||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View, useWindowDimensions} from 'react-native';
|
||||
import {View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
||||
import TeamIcon from '@components/team_sidebar/team_list/team_item/team_icon';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {TITLE_HEIGHT} from '@screens/bottom_sheet';
|
||||
import {bottomSheet} from '@screens/navigation';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {getTeamsSnapHeight} from '@utils/team_list';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import SelectTeamSlideUp from './select_team_slideup';
|
||||
import BottomSheetTeamList from './bottom_sheet_team_list';
|
||||
|
||||
import type {BottomSheetProps} from '@gorhom/bottom-sheet';
|
||||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
const MENU_DOWN_ICON_SIZE = 24;
|
||||
const NO_TEAMS_HEIGHT = 392;
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
teamContainer: {
|
||||
|
|
@ -52,9 +57,8 @@ type Props = {
|
|||
const TeamPickerIcon = ({size = 24, divider = false, setTeamId, teams, teamId}: Props) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const dimensions = useWindowDimensions();
|
||||
const styles = getStyleFromTheme(theme);
|
||||
const insets = useSafeAreaInsets();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
|
||||
const selectedTeam = teams.find((t) => t.id === teamId);
|
||||
|
||||
|
|
@ -63,7 +67,7 @@ const TeamPickerIcon = ({size = 24, divider = false, setTeamId, teams, teamId}:
|
|||
const handleTeamChange = useCallback(preventDoubleTap(() => {
|
||||
const renderContent = () => {
|
||||
return (
|
||||
<SelectTeamSlideUp
|
||||
<BottomSheetTeamList
|
||||
setTeamId={setTeamId}
|
||||
teams={teams}
|
||||
teamId={teamId}
|
||||
|
|
@ -72,15 +76,23 @@ const TeamPickerIcon = ({size = 24, divider = false, setTeamId, teams, teamId}:
|
|||
);
|
||||
};
|
||||
|
||||
const height = getTeamsSnapHeight({dimensions, teams, insets});
|
||||
const snapPoints: BottomSheetProps['snapPoints'] = [
|
||||
1,
|
||||
teams.length ? (bottomSheetSnapPoint(Math.min(3, teams.length), ITEM_HEIGHT, bottom) + TITLE_HEIGHT) : NO_TEAMS_HEIGHT,
|
||||
];
|
||||
|
||||
if (teams.length > 3) {
|
||||
snapPoints.push('90%');
|
||||
}
|
||||
|
||||
bottomSheet({
|
||||
closeButtonId: 'close-team_list',
|
||||
renderContent,
|
||||
snapPoints: [height, 10],
|
||||
snapPoints,
|
||||
theme,
|
||||
title,
|
||||
});
|
||||
}), [theme, setTeamId, teamId, teams]);
|
||||
}), [theme, setTeamId, teamId, teams, bottom]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import NavigationStore from '@store/navigation_store';
|
|||
import {appearanceControlledScreens, mergeNavigationOptions} from '@utils/navigation';
|
||||
import {changeOpacity, setNavigatorStyles} from '@utils/theme';
|
||||
|
||||
import type {BottomSheetFooterProps} from '@gorhom/bottom-sheet';
|
||||
import type {LaunchProps} from '@typings/launch';
|
||||
import type {NavButtons} from '@typings/screens/navigation';
|
||||
|
||||
|
|
@ -738,13 +739,14 @@ export async function dismissOverlay(componentId: string) {
|
|||
type BottomSheetArgs = {
|
||||
closeButtonId: string;
|
||||
initialSnapIndex?: number;
|
||||
footerComponent?: React.FC<BottomSheetFooterProps>;
|
||||
renderContent: () => JSX.Element;
|
||||
snapPoints: Array<number | string>;
|
||||
theme: Theme;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export async function bottomSheet({title, renderContent, snapPoints, initialSnapIndex = 0, theme, closeButtonId}: BottomSheetArgs) {
|
||||
export async function bottomSheet({title, renderContent, footerComponent, snapPoints, initialSnapIndex = 1, theme, closeButtonId}: BottomSheetArgs) {
|
||||
const {isSplitView} = await isRunningInSplitView();
|
||||
const isTablet = Device.IS_TABLET && !isSplitView;
|
||||
|
||||
|
|
@ -753,12 +755,14 @@ export async function bottomSheet({title, renderContent, snapPoints, initialSnap
|
|||
closeButtonId,
|
||||
initialSnapIndex,
|
||||
renderContent,
|
||||
footerComponent,
|
||||
snapPoints,
|
||||
}, bottomSheetModalOptions(theme, closeButtonId));
|
||||
} else {
|
||||
showModalOverCurrentContext(Screens.BOTTOM_SHEET, {
|
||||
initialSnapIndex,
|
||||
renderContent,
|
||||
footerComponent,
|
||||
snapPoints,
|
||||
}, bottomSheetModalOptions(theme));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,21 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BottomSheetProps, BottomSheetScrollView} from '@gorhom/bottom-sheet';
|
||||
import {useManagedConfig} from '@mattermost/react-native-emm';
|
||||
import React, {useMemo} from 'react';
|
||||
import {ScrollView} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {CopyPermalinkOption, FollowThreadOption, ReplyOption, SaveOption} from '@components/common_post_options';
|
||||
import {ITEM_HEIGHT} from '@components/option_item';
|
||||
import {Screens} from '@constants';
|
||||
import {REACTION_PICKER_HEIGHT, REACTION_PICKER_MARGIN} from '@constants/reaction_picker';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import BottomSheet from '@screens/bottom_sheet';
|
||||
import {dismissBottomSheet} from '@screens/navigation';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {isSystemMessage} from '@utils/post';
|
||||
|
||||
import AppBindingsPostOptions from './options/app_bindings_post_option';
|
||||
|
|
@ -48,6 +54,9 @@ const PostOptions = ({
|
|||
sourceScreen, post, thread, bindings, serverUrl,
|
||||
}: PostOptionsProps) => {
|
||||
const managedConfig = useManagedConfig<ManagedConfig>();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const isTablet = useIsTablet();
|
||||
const Scroll = useMemo(() => (isTablet ? ScrollView : BottomSheetScrollView), [isTablet]);
|
||||
|
||||
const close = () => {
|
||||
return dismissBottomSheet(Screens.POST_OPTIONS);
|
||||
|
|
@ -63,17 +72,31 @@ const PostOptions = ({
|
|||
const shouldRenderFollow = !(sourceScreen !== Screens.CHANNEL || !thread);
|
||||
const shouldShowBindings = bindings.length > 0 && !isSystemPost;
|
||||
|
||||
const snapPoints = [
|
||||
const snapPoints = useMemo(() => {
|
||||
const items: BottomSheetProps['snapPoints'] = [1];
|
||||
const optionsCount = [
|
||||
canCopyPermalink, canCopyText, canDelete, canEdit,
|
||||
canMarkAsUnread, canPin, canReply, !isSystemPost, shouldRenderFollow,
|
||||
].reduce((acc, v) => {
|
||||
return v ? acc + 1 : acc;
|
||||
}, 0) + (shouldShowBindings ? 0.5 : 0);
|
||||
|
||||
items.push(bottomSheetSnapPoint(optionsCount, ITEM_HEIGHT, bottom) + (canAddReaction ? REACTION_PICKER_HEIGHT + REACTION_PICKER_MARGIN : 0));
|
||||
|
||||
if (shouldShowBindings) {
|
||||
items.push('90%');
|
||||
}
|
||||
|
||||
return items;
|
||||
}, [
|
||||
canAddReaction, canCopyPermalink, canCopyText,
|
||||
canDelete, canEdit, shouldRenderFollow,
|
||||
canMarkAsUnread, canPin, canReply, !isSystemPost,
|
||||
].reduce((acc, v) => {
|
||||
return v ? acc + 1 : acc;
|
||||
}, 0);
|
||||
canDelete, canEdit, shouldRenderFollow, shouldShowBindings,
|
||||
canMarkAsUnread, canPin, canReply, isSystemPost, bottom,
|
||||
]);
|
||||
|
||||
const renderContent = () => {
|
||||
return (
|
||||
<>
|
||||
<Scroll bounces={false}>
|
||||
{canAddReaction &&
|
||||
<ReactionBar
|
||||
bottomSheetId={Screens.POST_OPTIONS}
|
||||
|
|
@ -147,31 +170,17 @@ const PostOptions = ({
|
|||
bindings={bindings}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
</Scroll>
|
||||
);
|
||||
};
|
||||
|
||||
const finalSnapPoints = useMemo(() => {
|
||||
const additionalSnapPoints = 2;
|
||||
|
||||
const lowerSnapPoints = snapPoints + additionalSnapPoints;
|
||||
if (!shouldShowBindings) {
|
||||
return [lowerSnapPoints * ITEM_HEIGHT, 10];
|
||||
}
|
||||
|
||||
const upperSnapPoints = lowerSnapPoints + bindings.length;
|
||||
return [upperSnapPoints * ITEM_HEIGHT, lowerSnapPoints * ITEM_HEIGHT, 10];
|
||||
}, [snapPoints, shouldShowBindings, bindings.length]);
|
||||
|
||||
const initialSnapIndex = shouldShowBindings ? 1 : 0;
|
||||
|
||||
return (
|
||||
<BottomSheet
|
||||
renderContent={renderContent}
|
||||
closeButtonId={POST_OPTIONS_BUTTON}
|
||||
componentId={Screens.POST_OPTIONS}
|
||||
initialSnapIndex={initialSnapIndex}
|
||||
snapPoints={finalSnapPoints}
|
||||
initialSnapIndex={1}
|
||||
snapPoints={snapPoints}
|
||||
testID='post_options'
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
|
||||
import {Screens} from '@constants';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import BottomSheet from '@screens/bottom_sheet';
|
||||
import {getEmojiFirstAlias} from '@utils/emoji/helpers';
|
||||
|
||||
|
|
@ -20,8 +21,10 @@ type Props = {
|
|||
}
|
||||
|
||||
const Reactions = ({initialEmoji, location, reactions}: Props) => {
|
||||
const isTablet = useIsTablet();
|
||||
const [sortedReactions, setSortedReactions] = useState(Array.from(new Set(reactions?.map((r) => getEmojiFirstAlias(r.emojiName)))));
|
||||
const [index, setIndex] = useState(sortedReactions.indexOf(initialEmoji));
|
||||
|
||||
const reactionsByName = useMemo(() => {
|
||||
return reactions?.reduce((acc, reaction) => {
|
||||
const emojiAlias = getEmojiFirstAlias(reaction.emojiName);
|
||||
|
|
@ -59,6 +62,7 @@ const Reactions = ({initialEmoji, location, reactions}: Props) => {
|
|||
key={emojiAlias}
|
||||
location={location}
|
||||
reactions={reactionsByName.get(emojiAlias)!}
|
||||
type={isTablet ? 'FlatList' : 'BottomSheetFlatList'}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
@ -81,7 +85,7 @@ const Reactions = ({initialEmoji, location, reactions}: Props) => {
|
|||
closeButtonId='close-post-reactions'
|
||||
componentId={Screens.REACTIONS}
|
||||
initialSnapIndex={1}
|
||||
snapPoints={['90%', '50%', 10]}
|
||||
snapPoints={[1, '50%', '90%']}
|
||||
testID='reactions'
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BottomSheetFlatList} from '@gorhom/bottom-sheet';
|
||||
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import {ListRenderItemInfo, NativeScrollEvent, NativeSyntheticEvent, PanResponder} from 'react-native';
|
||||
import {FlatList} from 'react-native-gesture-handler';
|
||||
|
|
@ -15,9 +16,10 @@ import type ReactionModel from '@typings/database/models/servers/reaction';
|
|||
type Props = {
|
||||
location: string;
|
||||
reactions: ReactionModel[];
|
||||
type?: BottomSheetList;
|
||||
}
|
||||
|
||||
const ReactorsList = ({location, reactions}: Props) => {
|
||||
const ReactorsList = ({location, reactions, type = 'FlatList'}: Props) => {
|
||||
const serverUrl = useServerUrl();
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [direction, setDirection] = useState<'down' | 'up'>('down');
|
||||
|
|
@ -56,6 +58,17 @@ const ReactorsList = ({location, reactions}: Props) => {
|
|||
fetchUsersByIds(serverUrl, userIds);
|
||||
}, []);
|
||||
|
||||
if (type === 'BottomSheetFlatList') {
|
||||
return (
|
||||
<BottomSheetFlatList
|
||||
data={reactions}
|
||||
renderItem={renderItem}
|
||||
overScrollMode={'always'}
|
||||
testID='reactions.reactors_list.flat_list'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={reactions}
|
||||
|
|
|
|||
|
|
@ -133,8 +133,8 @@ const ThreadOptions = ({
|
|||
renderContent={renderContent}
|
||||
closeButtonId={THREAD_OPTIONS_BUTTON}
|
||||
componentId={Screens.THREAD_OPTIONS}
|
||||
initialSnapIndex={0}
|
||||
snapPoints={[snapPoint, 10]}
|
||||
initialSnapIndex={1}
|
||||
snapPoints={[1, snapPoint]}
|
||||
testID='thread_options'
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {Screens} from '@constants';
|
|||
import {useServerUrl} from '@context/server';
|
||||
import {getLocaleFromLanguage} from '@i18n';
|
||||
import BottomSheet from '@screens/bottom_sheet';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {getUserCustomStatus, getUserTimezone, isCustomStatusExpired} from '@utils/user';
|
||||
|
||||
import UserProfileCustomStatus from './custom_status';
|
||||
|
|
@ -45,7 +46,6 @@ const TITLE_HEIGHT = 118;
|
|||
const OPTIONS_HEIGHT = 82;
|
||||
const SINGLE_OPTION_HEIGHT = 68;
|
||||
const LABEL_HEIGHT = 58;
|
||||
const EXTRA_HEIGHT = 60;
|
||||
|
||||
const UserProfile = ({
|
||||
channelId, closeButtonId, currentUserId, enablePostIconOverride, enablePostUsernameOverride,
|
||||
|
|
@ -55,14 +55,14 @@ const UserProfile = ({
|
|||
}: Props) => {
|
||||
const {formatMessage, locale} = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
const insets = useSafeAreaInsets();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const channelContext = [Screens.CHANNEL, Screens.THREAD].includes(location);
|
||||
const showOptions: OptionsType = channelContext && !user.isBot ? 'all' : 'message';
|
||||
const override = Boolean(userIconOverride || usernameOverride);
|
||||
const timezone = getUserTimezone(user);
|
||||
const customStatus = getUserCustomStatus(user);
|
||||
const showCustomStatus = isCustomStatusEnabled && Boolean(customStatus) && !user.isBot && !isCustomStatusExpired(user);
|
||||
let localTime: string|undefined;
|
||||
|
||||
if (timezone) {
|
||||
moment.locale(getLocaleFromLanguage(locale).toLowerCase());
|
||||
let format = 'H:mm';
|
||||
|
|
@ -73,36 +73,42 @@ const UserProfile = ({
|
|||
localTime = mtz.tz(Date.now(), timezone).format(format);
|
||||
}
|
||||
|
||||
const showCustomStatus = isCustomStatusEnabled && Boolean(customStatus) && !user.isBot && !isCustomStatusExpired(user);
|
||||
const showUserProfileOptions = (!isDirectMessage || !channelContext) && !override;
|
||||
const showNickname = Boolean(user.nickname) && !override && !user.isBot;
|
||||
const showPosition = Boolean(user.position) && !override && !user.isBot;
|
||||
const showLocalTime = Boolean(localTime) && !override && !user.isBot;
|
||||
|
||||
const snapPoints = useMemo(() => {
|
||||
let initial = TITLE_HEIGHT;
|
||||
if ((!isDirectMessage || !channelContext) && !override) {
|
||||
initial += showOptions === 'all' ? OPTIONS_HEIGHT : SINGLE_OPTION_HEIGHT;
|
||||
let title = TITLE_HEIGHT;
|
||||
if (showUserProfileOptions) {
|
||||
title += showOptions === 'all' ? OPTIONS_HEIGHT : SINGLE_OPTION_HEIGHT;
|
||||
}
|
||||
|
||||
let labels = 0;
|
||||
if (!override && !user.isBot) {
|
||||
if (showCustomStatus) {
|
||||
labels += 1;
|
||||
}
|
||||
|
||||
if (user.nickname) {
|
||||
labels += 1;
|
||||
}
|
||||
|
||||
if (user.position) {
|
||||
labels += 1;
|
||||
}
|
||||
|
||||
if (localTime) {
|
||||
labels += 1;
|
||||
}
|
||||
initial += (labels * LABEL_HEIGHT);
|
||||
if (showCustomStatus) {
|
||||
labels += 1;
|
||||
}
|
||||
|
||||
return [initial + insets.bottom + EXTRA_HEIGHT, 10];
|
||||
if (showNickname) {
|
||||
labels += 1;
|
||||
}
|
||||
|
||||
if (showPosition) {
|
||||
labels += 1;
|
||||
}
|
||||
|
||||
if (showLocalTime) {
|
||||
labels += 1;
|
||||
}
|
||||
|
||||
return [
|
||||
1,
|
||||
bottomSheetSnapPoint(labels, LABEL_HEIGHT, bottom) + title,
|
||||
];
|
||||
}, [
|
||||
isChannelAdmin, isDirectMessage, isSystemAdmin,
|
||||
isTeamAdmin, user, localTime, insets.bottom, override,
|
||||
showUserProfileOptions, showCustomStatus, showNickname,
|
||||
showPosition, showLocalTime, bottom,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -125,7 +131,7 @@ const UserProfile = ({
|
|||
userIconOverride={userIconOverride}
|
||||
usernameOverride={usernameOverride}
|
||||
/>
|
||||
{(!isDirectMessage || !channelContext) && !override &&
|
||||
{showUserProfileOptions &&
|
||||
<UserProfileOptions
|
||||
location={location}
|
||||
type={showOptions}
|
||||
|
|
@ -134,21 +140,21 @@ const UserProfile = ({
|
|||
/>
|
||||
}
|
||||
{showCustomStatus && <UserProfileCustomStatus customStatus={customStatus!}/>}
|
||||
{Boolean(user.nickname) && !override && !user.isBot &&
|
||||
{showNickname &&
|
||||
<UserProfileLabel
|
||||
description={user.nickname}
|
||||
testID='user_profile.nickname'
|
||||
title={formatMessage({id: 'channel_info.nickname', defaultMessage: 'Nickname'})}
|
||||
/>
|
||||
}
|
||||
{Boolean(user.position) && !override && !user.isBot &&
|
||||
{showPosition &&
|
||||
<UserProfileLabel
|
||||
description={user.position}
|
||||
testID='user_profile.position'
|
||||
title={formatMessage({id: 'channel_info.position', defaultMessage: 'Position'})}
|
||||
/>
|
||||
}
|
||||
{Boolean(localTime) && !override && !user.isBot &&
|
||||
{showLocalTime &&
|
||||
<UserProfileLabel
|
||||
description={localTime!}
|
||||
testID='user_profile.local_time'
|
||||
|
|
@ -164,7 +170,7 @@ const UserProfile = ({
|
|||
renderContent={renderContent}
|
||||
closeButtonId={closeButtonId}
|
||||
componentId={Screens.USER_PROFILE}
|
||||
initialSnapIndex={0}
|
||||
initialSnapIndex={1}
|
||||
snapPoints={snapPoints}
|
||||
testID='user_profile'
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {NativeModules, Platform} from 'react-native';
|
|||
|
||||
import {Device} from '@constants';
|
||||
import {CUSTOM_STATUS_TIME_PICKER_INTERVALS_IN_MINUTES} from '@constants/custom_status';
|
||||
import {IOS_STATUS_BAR_HEIGHT} from '@constants/view';
|
||||
import {STATUS_BAR_HEIGHT} from '@constants/view';
|
||||
|
||||
const {MattermostManaged} = NativeModules;
|
||||
const isRunningInSplitView = MattermostManaged.isRunningInSplitView;
|
||||
|
|
@ -141,12 +141,9 @@ export async function isTablet() {
|
|||
|
||||
export const pluckUnique = (key: string) => (array: Array<{[key: string]: unknown}>) => Array.from(new Set(array.map((obj) => obj[key])));
|
||||
|
||||
export function bottomSheetSnapPoint(itemsCount: number, itemHeight: number, bottomInset = 0) {
|
||||
let bottom = bottomInset;
|
||||
if (!bottom && Platform.OS === 'ios') {
|
||||
bottom = IOS_STATUS_BAR_HEIGHT;
|
||||
}
|
||||
return ((itemsCount + Platform.select({android: 1, default: 0})) * itemHeight) + (bottom * 2.5);
|
||||
export function bottomSheetSnapPoint(itemsCount: number, itemHeight: number, bottomInset: number) {
|
||||
const bottom = Platform.select({ios: bottomInset, default: 0}) + STATUS_BAR_HEIGHT;
|
||||
return (itemsCount * itemHeight) + bottom;
|
||||
}
|
||||
|
||||
export function hasTrailingSpaces(term: string) {
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ScaledSize} from 'react-native';
|
||||
import {EdgeInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {ITEM_HEIGHT} from '@components/team_list/team_list_item/team_list_item';
|
||||
import {PADDING_TOP_MOBILE} from '@screens/bottom_sheet';
|
||||
import {TITLE_HEIGHT, TITLE_SEPARATOR_MARGIN} from '@screens/bottom_sheet/content';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
|
||||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
type TeamsSnapProps = {
|
||||
teams: TeamModel[];
|
||||
dimensions: ScaledSize;
|
||||
insets: EdgeInsets;
|
||||
}
|
||||
|
||||
const NO_TEAMS_HEIGHT = 392;
|
||||
export const getTeamsSnapHeight = ({dimensions, teams, insets}: TeamsSnapProps) => {
|
||||
let height = NO_TEAMS_HEIGHT;
|
||||
if (teams.length) {
|
||||
const itemsHeight = bottomSheetSnapPoint(teams.length, ITEM_HEIGHT, 0);
|
||||
const heightWithHeader = PADDING_TOP_MOBILE +
|
||||
TITLE_HEIGHT + (TITLE_SEPARATOR_MARGIN * 2) +
|
||||
itemsHeight + insets.bottom;
|
||||
const maxHeight = Math.round((dimensions.height * 0.9));
|
||||
height = Math.min(maxHeight, heightWithHeader);
|
||||
}
|
||||
return height;
|
||||
};
|
||||
63
package-lock.json
generated
63
package-lock.json
generated
|
|
@ -16,6 +16,7 @@
|
|||
"@formatjs/intl-numberformat": "8.3.3",
|
||||
"@formatjs/intl-pluralrules": "5.1.8",
|
||||
"@formatjs/intl-relativetimeformat": "11.1.8",
|
||||
"@gorhom/bottom-sheet": "4.4.5",
|
||||
"@mattermost/compass-icons": "0.1.35",
|
||||
"@mattermost/react-native-emm": "1.3.3",
|
||||
"@mattermost/react-native-network-client": "1.0.2",
|
||||
|
|
@ -94,7 +95,6 @@
|
|||
"react-native-webview": "11.26.0",
|
||||
"react-syntax-highlighter": "15.5.0",
|
||||
"readable-stream": "3.6.0",
|
||||
"reanimated-bottom-sheet": "1.0.0-alpha.22",
|
||||
"semver": "7.3.8",
|
||||
"serialize-error": "11.0.0",
|
||||
"shallow-equals": "1.0.0",
|
||||
|
|
@ -2349,6 +2349,33 @@
|
|||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@gorhom/bottom-sheet": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@gorhom/bottom-sheet/-/bottom-sheet-4.4.5.tgz",
|
||||
"integrity": "sha512-Z5Z20wshLUB8lIdtMKoJaRnjd64wBR/q8EeVPThrg+skrcBwBPHfUwZJ2srB0rEszA/01ejSJy/ixyd7Ra7vUA==",
|
||||
"dependencies": {
|
||||
"@gorhom/portal": "1.0.14",
|
||||
"invariant": "^2.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-gesture-handler": ">=1.10.1",
|
||||
"react-native-reanimated": ">=2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@gorhom/portal": {
|
||||
"version": "1.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@gorhom/portal/-/portal-1.0.14.tgz",
|
||||
"integrity": "sha512-MXyL4xvCjmgaORr/rtryDNFy3kU4qUbKlwtQqqsygd0xX3mhKjOLn6mQK8wfu0RkoE0pBE0nAasRoHua+/QZ7A==",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/hoek": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
|
||||
|
|
@ -19082,17 +19109,6 @@
|
|||
"resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz",
|
||||
"integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg=="
|
||||
},
|
||||
"node_modules/reanimated-bottom-sheet": {
|
||||
"version": "1.0.0-alpha.22",
|
||||
"resolved": "https://registry.npmjs.org/reanimated-bottom-sheet/-/reanimated-bottom-sheet-1.0.0-alpha.22.tgz",
|
||||
"integrity": "sha512-NxecCn+2iA4YzkFuRK5/b86GHHS2OhZ9VRgiM4q18AC20YE/psRilqxzXCKBEvkOjP5AaAvY0yfE7EkEFBjTvw==",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-gesture-handler": "*",
|
||||
"react-native-reanimated": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/recast": {
|
||||
"version": "0.20.5",
|
||||
"resolved": "https://registry.npmjs.org/recast/-/recast-0.20.5.tgz",
|
||||
|
|
@ -23595,6 +23611,23 @@
|
|||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@gorhom/bottom-sheet": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@gorhom/bottom-sheet/-/bottom-sheet-4.4.5.tgz",
|
||||
"integrity": "sha512-Z5Z20wshLUB8lIdtMKoJaRnjd64wBR/q8EeVPThrg+skrcBwBPHfUwZJ2srB0rEszA/01ejSJy/ixyd7Ra7vUA==",
|
||||
"requires": {
|
||||
"@gorhom/portal": "1.0.14",
|
||||
"invariant": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"@gorhom/portal": {
|
||||
"version": "1.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@gorhom/portal/-/portal-1.0.14.tgz",
|
||||
"integrity": "sha512-MXyL4xvCjmgaORr/rtryDNFy3kU4qUbKlwtQqqsygd0xX3mhKjOLn6mQK8wfu0RkoE0pBE0nAasRoHua+/QZ7A==",
|
||||
"requires": {
|
||||
"nanoid": "^3.3.1"
|
||||
}
|
||||
},
|
||||
"@hapi/hoek": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
|
||||
|
|
@ -36257,12 +36290,6 @@
|
|||
"resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz",
|
||||
"integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg=="
|
||||
},
|
||||
"reanimated-bottom-sheet": {
|
||||
"version": "1.0.0-alpha.22",
|
||||
"resolved": "https://registry.npmjs.org/reanimated-bottom-sheet/-/reanimated-bottom-sheet-1.0.0-alpha.22.tgz",
|
||||
"integrity": "sha512-NxecCn+2iA4YzkFuRK5/b86GHHS2OhZ9VRgiM4q18AC20YE/psRilqxzXCKBEvkOjP5AaAvY0yfE7EkEFBjTvw==",
|
||||
"requires": {}
|
||||
},
|
||||
"recast": {
|
||||
"version": "0.20.5",
|
||||
"resolved": "https://registry.npmjs.org/recast/-/recast-0.20.5.tgz",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"@formatjs/intl-numberformat": "8.3.3",
|
||||
"@formatjs/intl-pluralrules": "5.1.8",
|
||||
"@formatjs/intl-relativetimeformat": "11.1.8",
|
||||
"@gorhom/bottom-sheet": "4.4.5",
|
||||
"@mattermost/compass-icons": "0.1.35",
|
||||
"@mattermost/react-native-emm": "1.3.3",
|
||||
"@mattermost/react-native-network-client": "1.0.2",
|
||||
|
|
@ -91,7 +92,6 @@
|
|||
"react-native-webview": "11.26.0",
|
||||
"react-syntax-highlighter": "15.5.0",
|
||||
"readable-stream": "3.6.0",
|
||||
"reanimated-bottom-sheet": "1.0.0-alpha.22",
|
||||
"semver": "7.3.8",
|
||||
"serialize-error": "11.0.0",
|
||||
"shallow-equals": "1.0.0",
|
||||
|
|
|
|||
4
types/components/bottom_sheet.d.ts
vendored
Normal file
4
types/components/bottom_sheet.d.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
type BottomSheetList = 'FlatList' | 'BottomSheetFlatList';
|
||||
Loading…
Reference in a new issue