Unify buttons (#8865)

* Unity buttons

* Fix texts and use defineMessages

* Update snapshots

* Fix disabled style
This commit is contained in:
Daniel Espino García 2025-05-30 15:59:15 +02:00 committed by GitHub
parent 705ed603bc
commit 2ad6fec90f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 661 additions and 821 deletions

View file

@ -2,14 +2,13 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {BottomSheetScrollView} from '@gorhom/bottom-sheet'; import {BottomSheetScrollView} from '@gorhom/bottom-sheet';
import {Button} from '@rneui/base';
import React, {useCallback, useMemo} from 'react'; import React, {useCallback, useMemo} from 'react';
import {useIntl} from 'react-intl'; import {useIntl} from 'react-intl';
import {ScrollView, Text, View} from 'react-native'; import {ScrollView, Text, View} from 'react-native';
import {useSafeAreaInsets} from 'react-native-safe-area-context'; import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {dismissAnnouncement} from '@actions/local/systems'; import {dismissAnnouncement} from '@actions/local/systems';
import FormattedText from '@components/formatted_text'; import Button from '@components/button';
import Markdown from '@components/markdown'; import Markdown from '@components/markdown';
import {Screens} from '@constants'; import {Screens} from '@constants';
import {useServerUrl} from '@context/server'; import {useServerUrl} from '@context/server';
@ -17,7 +16,6 @@ import {useTheme} from '@context/theme';
import {useBottomSheetListsFix} from '@hooks/bottom_sheet_lists_fix'; import {useBottomSheetListsFix} from '@hooks/bottom_sheet_lists_fix';
import {useIsTablet} from '@hooks/device'; import {useIsTablet} from '@hooks/device';
import {dismissBottomSheet} from '@screens/navigation'; import {dismissBottomSheet} from '@screens/navigation';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {getMarkdownTextStyles, getMarkdownBlockStyles} from '@utils/markdown'; import {getMarkdownTextStyles, getMarkdownBlockStyles} from '@utils/markdown';
import {makeStyleSheetFromTheme} from '@utils/theme'; import {makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography'; import {typography} from '@utils/typography';
@ -46,6 +44,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
color: theme.centerChannelColor, color: theme.centerChannelColor,
...typography('Heading', 600, 'SemiBold'), ...typography('Heading', 600, 'SemiBold'),
}, },
dismissButtonContainer: {
marginTop: 10,
},
}; };
}); });
@ -69,20 +70,7 @@ const ExpandedAnnouncementBanner = ({
const dismissBanner = useCallback(() => { const dismissBanner = useCallback(() => {
dismissAnnouncement(serverUrl, bannerText); dismissAnnouncement(serverUrl, bannerText);
close(); close();
}, [bannerText]); }, [bannerText, serverUrl]);
const buttonStyles = useMemo(() => {
return {
okay: {
button: buttonBackgroundStyle(theme, 'lg', 'primary'),
text: buttonTextStyle(theme, 'lg', 'primary'),
},
dismiss: {
button: [{marginTop: 10}, buttonBackgroundStyle(theme, 'lg', 'link')],
text: buttonTextStyle(theme, 'lg', 'link'),
},
};
}, [theme]);
const containerStyle = useMemo(() => { const containerStyle = useMemo(() => {
return [style.container, {marginBottom: insets.bottom + 10}]; return [style.container, {marginBottom: insets.bottom + 10}];
@ -118,26 +106,21 @@ const ExpandedAnnouncementBanner = ({
/> />
</Scroll> </Scroll>
<Button <Button
buttonStyle={buttonStyles.okay.button} text={intl.formatMessage({id: 'announcment_banner.okay', defaultMessage: 'Okay'})}
onPress={close} onPress={close}
> size='lg'
<FormattedText theme={theme}
id='announcment_banner.okay' />
defaultMessage={'Okay'}
style={buttonStyles.okay.text}
/>
</Button>
{allowDismissal && ( {allowDismissal && (
<Button <View style={style.dismissButtonContainer}>
buttonStyle={buttonStyles.dismiss.button} <Button
onPress={dismissBanner} text={intl.formatMessage({id: 'announcment_banner.dismiss', defaultMessage: 'Dismiss announcement'})}
> onPress={dismissBanner}
<FormattedText size='lg'
id='announcment_banner.dismiss' theme={theme}
defaultMessage={'Dismiss announcement'} emphasis='link'
style={buttonStyles.dismiss.text}
/> />
</Button> </View>
)} )}
</View> </View>
); );

View file

@ -6,8 +6,8 @@ import React, {useMemo, type ReactNode} from 'react';
import {type StyleProp, StyleSheet, Text, type TextStyle, View, type ViewStyle, type Insets} from 'react-native'; import {type StyleProp, StyleSheet, Text, type TextStyle, View, type ViewStyle, type Insets} from 'react-native';
import CompassIcon from '@components/compass_icon'; import CompassIcon from '@components/compass_icon';
import Loading from '@components/loading';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles'; import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {changeOpacity} from '@utils/theme';
type Props = { type Props = {
theme: Theme; theme: Theme;
@ -16,16 +16,17 @@ type Props = {
textStyle?: StyleProp<TextStyle>; textStyle?: StyleProp<TextStyle>;
size?: ButtonSize; size?: ButtonSize;
emphasis?: ButtonEmphasis; emphasis?: ButtonEmphasis;
buttonType?: ButtonType;
buttonState?: ButtonState;
testID?: string; testID?: string;
onPress: () => void; onPress?: () => void;
text: string; text: string;
iconComponent?: ReactNode; iconComponent?: ReactNode;
disabled?: boolean; disabled?: boolean;
hitSlop?: Insets; hitSlop?: Insets;
isIconOnTheRight?: boolean; isIconOnTheRight?: boolean;
iconName?: string; iconName?: string;
showLoader?: boolean;
isInverted?: boolean;
isDestructive?: boolean;
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -50,8 +51,6 @@ const Button = ({
textStyle, textStyle,
size = 'm', size = 'm',
emphasis, emphasis,
buttonType,
buttonState,
onPress, onPress,
text, text,
testID, testID,
@ -60,24 +59,44 @@ const Button = ({
iconComponent, iconComponent,
disabled, disabled,
hitSlop, hitSlop,
showLoader = false,
isInverted = false,
isDestructive = false,
}: Props) => { }: Props) => {
const bgStyle = useMemo(() => [ let buttonType: ButtonType = 'default';
buttonBackgroundStyle(theme, size, emphasis, buttonType, buttonState), if (isDestructive) {
backgroundStyle, buttonType = 'destructive';
], [theme, backgroundStyle, size, emphasis, buttonType, buttonState]); } else if (isInverted) {
buttonType = 'inverted';
}
const txtStyle = useMemo(() => [ const bgStyle = useMemo(() => [
buttonBackgroundStyle(theme, size, emphasis, buttonType),
backgroundStyle,
], [theme, backgroundStyle, size, emphasis, buttonType]);
const bgDisabledStyle = useMemo(() => [
buttonBackgroundStyle(theme, size, emphasis, 'disabled'),
backgroundStyle,
], [theme, backgroundStyle, size, emphasis]);
const txtStyle = useMemo(() => StyleSheet.flatten([
buttonTextStyle(theme, size, emphasis, buttonType), buttonTextStyle(theme, size, emphasis, buttonType),
textStyle, textStyle,
], [theme, textStyle, size, emphasis, buttonType]); ]), [theme, textStyle, size, emphasis, buttonType]);
let buttonStyle = StyleSheet.flatten(bgStyle); const txtDisabledStyle = useMemo(() => StyleSheet.flatten([
if (disabled) { buttonTextStyle(theme, size, emphasis, 'disabled'),
buttonStyle = { textStyle,
...buttonStyle, ]), [theme, textStyle, size, emphasis]);
backgroundColor: changeOpacity(buttonStyle.backgroundColor! as string, 0.4),
}; const txtStyleToUse = disabled ? txtDisabledStyle : txtStyle;
}
const loadingComponent = (
<Loading
color={txtStyleToUse.color}
/>
);
let icon: ReactNode; let icon: ReactNode;
@ -90,7 +109,7 @@ const Button = ({
<CompassIcon <CompassIcon
name={iconName!} name={iconName!}
size={iconSizePerSize[size]} size={iconSizePerSize[size]}
color={StyleSheet.flatten(txtStyle).color} color={txtStyleToUse.color}
testID={`${testID}-icon`} testID={`${testID}-icon`}
/> />
</View> </View>
@ -99,8 +118,9 @@ const Button = ({
return ( return (
<ElementButton <ElementButton
buttonStyle={buttonStyle} buttonStyle={bgStyle}
containerStyle={buttonContainerStyle} containerStyle={buttonContainerStyle}
disabledStyle={bgDisabledStyle}
onPress={onPress} onPress={onPress}
testID={testID} testID={testID}
disabled={disabled} disabled={disabled}
@ -110,9 +130,10 @@ const Button = ({
style={styles.container} style={styles.container}
testID={`${testID}-text-container`} testID={`${testID}-text-container`}
> >
{showLoader && loadingComponent}
{!isIconOnTheRight && icon} {!isIconOnTheRight && icon}
<Text <Text
style={[txtStyle]} style={txtStyleToUse}
numberOfLines={1} numberOfLines={1}
> >
{text} {text}

View file

@ -2,14 +2,14 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react';
import {ActivityIndicator, type StyleProp, View, type ViewStyle, Text, type TextStyle} from 'react-native'; import {ActivityIndicator, type StyleProp, View, type ViewStyle, Text, type TextStyle, type ColorValue} from 'react-native';
import {useTheme} from '@context/theme'; import {useTheme} from '@context/theme';
type LoadingProps = { type LoadingProps = {
containerStyle?: StyleProp<ViewStyle>; containerStyle?: StyleProp<ViewStyle>;
size?: number | 'small' | 'large'; size?: number | 'small' | 'large';
color?: string; color?: ColorValue;
themeColor?: keyof Theme; themeColor?: keyof Theme;
footerText?: string; footerText?: string;
footerTextStyles?: TextStyle; footerTextStyles?: TextStyle;

View file

@ -73,61 +73,106 @@ exports[`Loading Error should match snapshot 1`] = `
Error description Error description
</Text> </Text>
<View <View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={ style={
{ {
"backgroundColor": "#ffffff",
"borderRadius": 4,
"marginTop": 24, "marginTop": 24,
"opacity": 1,
"paddingHorizontal": 24,
"paddingVertical": 12,
} }
} }
> >
<Text <View
style={ style={
[ [
{ {
"fontFamily": "OpenSans-SemiBold", "overflow": "hidden",
"fontSize": 16,
"fontWeight": "600",
"lineHeight": 24,
}, },
{ {
"color": "#1c58d9", "borderRadius": 2,
}, },
undefined,
false,
] ]
} }
testID="RNE_BUTTON_WRAPPER"
> >
Retry <View
</Text> accessibilityRole="button"
accessibilityState={
{
"busy": false,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"opacity": 1,
}
}
>
<View
style={
{
"alignItems": "center",
"backgroundColor": "#1c58d9",
"borderColor": "#2089dc",
"borderRadius": 4,
"borderWidth": 0,
"flexDirection": "row",
"justifyContent": "center",
"padding": 8,
"paddingHorizontal": 24,
"paddingVertical": 12,
}
}
>
<View
style={
{
"alignItems": "center",
"flexDirection": "row",
"gap": 7,
}
}
testID="undefined-text-container"
>
<Text
numberOfLines={1}
style={
{
"color": "#ffffff",
"fontFamily": "OpenSans-SemiBold",
"fontSize": 16,
"fontWeight": "600",
"lineHeight": 24,
}
}
>
Retry
</Text>
</View>
</View>
</View>
</View>
</View> </View>
</View> </View>
`; `;

View file

@ -1,14 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useMemo} from 'react'; import React from 'react';
import {useIntl} from 'react-intl';
import {Text, View} from 'react-native'; import {Text, View} from 'react-native';
import Button from '@components/button';
import CompassIcon from '@components/compass_icon'; import CompassIcon from '@components/compass_icon';
import Loading from '@components/loading'; import Loading from '@components/loading';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {useTheme} from '@context/theme'; import {useTheme} from '@context/theme';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography'; import {typography} from '@utils/typography';
@ -49,14 +49,15 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
textAlign: 'center', textAlign: 'center',
marginTop: 4, marginTop: 4,
}, },
buttonContainer: {
marginTop: 24,
},
})); }));
const LoadingError = ({loading, message, onRetry, title}: Props) => { const LoadingError = ({loading, message, onRetry, title}: Props) => {
const theme = useTheme(); const theme = useTheme();
const intl = useIntl();
const styles = getStyleSheet(theme); const styles = getStyleSheet(theme);
const buttonStyle = useMemo(() => {
return [{marginTop: 24}, buttonBackgroundStyle(theme, 'lg', 'primary', 'inverted')];
}, [theme]);
if (loading) { if (loading) {
return ( return (
@ -81,13 +82,14 @@ const LoadingError = ({loading, message, onRetry, title}: Props) => {
<Text style={[typography('Body', 200), styles.body]}> <Text style={[typography('Body', 200), styles.body]}>
{message} {message}
</Text> </Text>
<TouchableWithFeedback <View style={styles.buttonContainer}>
style={buttonStyle} <Button
onPress={onRetry} text={intl.formatMessage({id: 'loading_error.retry', defaultMessage: 'Retry'})}
type={'opacity'} onPress={onRetry}
> size='lg'
<Text style={buttonTextStyle(theme, 'lg', 'primary', 'inverted')}>{'Retry'}</Text> theme={theme}
</TouchableWithFeedback> />
</View>
</View> </View>
); );
}; };

View file

@ -186,22 +186,13 @@ exports[`Section notice match snapshot 1`] = `
<Text <Text
numberOfLines={1} numberOfLines={1}
style={ style={
[ {
[ "color": "#ffffff",
[ "fontFamily": "OpenSans-SemiBold",
{ "fontSize": 14,
"fontFamily": "OpenSans-SemiBold", "fontWeight": "600",
"fontSize": 14, "lineHeight": 20,
"fontWeight": "600", }
"lineHeight": 20,
},
{
"color": "#ffffff",
},
],
undefined,
],
]
} }
> >
primary button primary button
@ -297,22 +288,13 @@ exports[`Section notice match snapshot 1`] = `
<Text <Text
numberOfLines={1} numberOfLines={1}
style={ style={
[ {
[ "color": "#1c58d9",
[ "fontFamily": "OpenSans-SemiBold",
{ "fontSize": 14,
"fontFamily": "OpenSans-SemiBold", "fontWeight": "600",
"fontSize": 14, "lineHeight": 20,
"fontWeight": "600", }
"lineHeight": 20,
},
{
"color": "#1c58d9",
},
],
undefined,
],
]
} }
> >
secondary button secondary button
@ -408,22 +390,13 @@ exports[`Section notice match snapshot 1`] = `
<Text <Text
numberOfLines={1} numberOfLines={1}
style={ style={
[ {
[ "color": "#1c58d9",
[ "fontFamily": "OpenSans-SemiBold",
{ "fontSize": 14,
"fontFamily": "OpenSans-SemiBold", "fontWeight": "600",
"fontSize": 14, "lineHeight": 20,
"fontWeight": "600", }
"lineHeight": 20,
},
{
"color": "#1c58d9",
},
],
undefined,
],
]
} }
> >
link button link button

View file

@ -270,10 +270,10 @@ export default function SelectedUsers({
iconName={buttonIcon} iconName={buttonIcon}
text={buttonText} text={buttonText}
theme={theme} theme={theme}
buttonType={isDisabled ? 'disabled' : 'default'}
emphasis={'primary'} emphasis={'primary'}
size={'lg'} size={'lg'}
testID={`${testID}.start.button`} testID={`${testID}.start.button`}
disabled={isDisabled}
/> />
</Animated.View> </Animated.View>
</Animated.View> </Animated.View>

View file

@ -1,13 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Button} from '@rneui/base';
import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react';
import {useIntl} from 'react-intl'; import {useIntl} from 'react-intl';
import {Keyboard, ScrollView, Text, View} from 'react-native'; import {Keyboard, ScrollView, View} from 'react-native';
import {SafeAreaView} from 'react-native-safe-area-context'; import {SafeAreaView} from 'react-native-safe-area-context';
import {handleGotoLocation} from '@actions/remote/command'; import {handleGotoLocation} from '@actions/remote/command';
import Button from '@components/button';
import CompassIcon from '@components/compass_icon'; import CompassIcon from '@components/compass_icon';
import Markdown from '@components/markdown'; import Markdown from '@components/markdown';
import {Screens} from '@constants'; import {Screens} from '@constants';
@ -18,7 +18,6 @@ import useDidUpdate from '@hooks/did_update';
import useNavButtonPressed from '@hooks/navigation_button_pressed'; import useNavButtonPressed from '@hooks/navigation_button_pressed';
import SecurityManager from '@managers/security_manager'; import SecurityManager from '@managers/security_manager';
import {filterEmptyOptions} from '@utils/apps'; import {filterEmptyOptions} from '@utils/apps';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {checkDialogElementForError, checkIfErrorsMatchElements} from '@utils/integrations'; import {checkDialogElementForError, checkIfErrorsMatchElements} from '@utils/integrations';
import {getMarkdownBlockStyles, getMarkdownTextStyles} from '@utils/markdown'; import {getMarkdownBlockStyles, getMarkdownTextStyles} from '@utils/markdown';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
@ -383,9 +382,6 @@ function AppsFormComponent({
useNavButtonPressed(CLOSE_BUTTON_ID, componentId, close, [close]); useNavButtonPressed(CLOSE_BUTTON_ID, componentId, close, [close]);
useNavButtonPressed(SUBMIT_BUTTON_ID, componentId, handleSubmit, [handleSubmit]); useNavButtonPressed(SUBMIT_BUTTON_ID, componentId, handleSubmit, [handleSubmit]);
const submitButtonStyle = useMemo(() => buttonBackgroundStyle(theme, 'lg', 'primary', 'default'), [theme]);
const submitButtonTextStyle = useMemo(() => buttonTextStyle(theme, 'lg', 'primary', 'default'), [theme]);
return ( return (
<SafeAreaView <SafeAreaView
testID='interactive_dialog.screen' testID='interactive_dialog.screen'
@ -444,10 +440,10 @@ function AppsFormComponent({
> >
<Button <Button
onPress={() => handleSubmit(o.value)} onPress={() => handleSubmit(o.value)}
buttonStyle={submitButtonStyle} theme={theme}
> size='lg'
<Text style={submitButtonTextStyle}>{o.label}</Text> text={o.label || ''}
</Button> />
</View> </View>
))} ))}
</View> </View>

View file

@ -2,18 +2,16 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react';
import {type GestureResponderEvent, Platform, Text, useWindowDimensions, View} from 'react-native'; import {Platform, useWindowDimensions, View} from 'react-native';
import CompassIcon from '@components/compass_icon'; import Button from '@components/button';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {useTheme} from '@context/theme'; import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device'; import {useIsTablet} from '@hooks/device';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
type Props = { type Props = {
disabled?: boolean; disabled?: boolean;
onPress?: (e: GestureResponderEvent) => void; onPress?: () => void;
icon?: string; icon?: string;
testID?: string; testID?: string;
text?: string; text?: string;
@ -21,22 +19,12 @@ type Props = {
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return { return {
button: {
display: 'flex',
flexDirection: 'row',
},
buttonContainer: { buttonContainer: {
paddingHorizontal: 20, paddingHorizontal: 20,
}, },
container: { container: {
backgroundColor: theme.centerChannelBg, backgroundColor: theme.centerChannelBg,
}, },
iconContainer: {
width: 24,
height: 24,
top: -1,
marginRight: 4,
},
separator: { separator: {
height: 1, height: 1,
right: 20, right: 20,
@ -56,40 +44,22 @@ function BottomSheetButton({disabled = false, onPress, icon, testID, text}: Prop
const styles = getStyleSheet(theme); const styles = getStyleSheet(theme);
const separatorWidth = Math.max(dimensions.width, 450); 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);
const iconColor = disabled ? changeOpacity(theme.centerChannelColor, 0.32) : theme.buttonColor;
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={[styles.separator, {width: separatorWidth}]}/> <View style={[styles.separator, {width: separatorWidth}]}/>
<View style={styles.buttonContainer}> <View style={styles.buttonContainer}>
<TouchableWithFeedback <Button
onPress={onPress} onPress={onPress}
type='opacity' text={text || ''}
style={[styles.button, styleButtonBackground]} iconName={icon}
testID={testID} testID={testID}
> theme={theme}
{icon && ( size='lg'
<View style={styles.iconContainer}> disabled={disabled}
<CompassIcon />
size={24}
name={icon}
color={iconColor}
/>
</View>
)}
{text && (
<Text
style={styleButtonText}
>{text}</Text>
)}
</TouchableWithFeedback>
<View style={{paddingBottom: Platform.select({ios: (isTablet ? 20 : 0), android: 20})}}/>
</View> </View>
<View style={{paddingBottom: Platform.select({ios: (isTablet ? 20 : 0), android: 20})}}/>
</View> </View>
); );
} }

View file

@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react';
import {type GestureResponderEvent, Text, useWindowDimensions, View} from 'react-native'; import {Text, useWindowDimensions, View} from 'react-native';
import {useTheme} from '@context/theme'; import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device'; import {useIsTablet} from '@hooks/device';
@ -15,7 +15,7 @@ type Props = {
buttonText?: string; buttonText?: string;
children: React.ReactNode; children: React.ReactNode;
disableButton?: boolean; disableButton?: boolean;
onPress?: (e: GestureResponderEvent) => void; onPress?: () => void;
showButton: boolean; showButton: boolean;
showTitle: boolean; showTitle: boolean;
testID?: string; testID?: string;

View file

@ -303,7 +303,6 @@ const ChannelBookmarkAddOrEdit = ({
{canDeleteBookmarks && {canDeleteBookmarks &&
<View style={styles.deleteContainer}> <View style={styles.deleteContainer}>
<Button <Button
buttonType='destructive'
size='m' size='m'
text='Delete bookmark' text='Delete bookmark'
iconName='trash-can-outline' iconName='trash-can-outline'
@ -311,6 +310,7 @@ const ChannelBookmarkAddOrEdit = ({
onPress={onDelete} onPress={onDelete}
theme={theme} theme={theme}
disabled={isSaving} disabled={isSaving}
isDestructive={true}
/> />
</View> </View>
} }

View file

@ -85,7 +85,6 @@ const MutedBanner = ({channelId}: Props) => {
/> />
<View style={styles.button}> <View style={styles.button}>
<Button <Button
buttonType='default'
onPress={onPress} onPress={onPress}
text={formatMessage({ text={formatMessage({
id: 'channel_notification_preferences.unmute_content', id: 'channel_notification_preferences.unmute_content',

View file

@ -7,14 +7,13 @@ import {Text, View} from 'react-native';
import {convertGroupMessageToPrivateChannel, switchToChannelById} from '@actions/remote/channel'; import {convertGroupMessageToPrivateChannel, switchToChannelById} from '@actions/remote/channel';
import Button from '@components/button'; import Button from '@components/button';
import Loading from '@components/loading';
import {ServerErrors} from '@constants'; import {ServerErrors} from '@constants';
import {useServerUrl} from '@context/server'; import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme'; import {useTheme} from '@context/theme';
import {isErrorWithMessage, isServerError} from '@utils/errors'; import {isErrorWithMessage, isServerError} from '@utils/errors';
import {logError} from '@utils/log'; import {logError} from '@utils/log';
import {preventDoubleTap} from '@utils/tap'; import {preventDoubleTap} from '@utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {makeStyleSheetFromTheme} from '@utils/theme';
import {displayUsername} from '@utils/user'; import {displayUsername} from '@utils/user';
import {ChannelNameInput} from '../channel_name_input'; import {ChannelNameInput} from '../channel_name_input';
@ -135,13 +134,6 @@ export const ConvertGMToChannelForm = ({
memberNames, memberNames,
}); });
const buttonIcon = conversionInProgress ? (
<Loading
containerStyle={styles.loadingContainerStyle}
color={changeOpacity(theme.centerChannelColor, 0.32)}
/>
) : null;
return ( return (
<View style={styles.container}> <View style={styles.container}>
<MessageBox <MessageBox
@ -164,9 +156,10 @@ export const ConvertGMToChannelForm = ({
onPress={handleOnPress} onPress={handleOnPress}
text={confirmButtonText} text={confirmButtonText}
theme={theme} theme={theme}
buttonType={submitButtonEnabled ? 'destructive' : 'disabled'}
size='lg' size='lg'
iconComponent={buttonIcon} disabled={!submitButtonEnabled}
isDestructive={true}
showLoader={conversionInProgress}
/> />
{ {
errorMessage && errorMessage &&

View file

@ -1,17 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Button} from '@rneui/base';
import React, {type MutableRefObject, useCallback, useEffect, useRef} from 'react'; import React, {type MutableRefObject, useCallback, useEffect, useRef} from 'react';
import {useIntl} from 'react-intl'; import {defineMessages, useIntl} from 'react-intl';
import {Keyboard, Platform, useWindowDimensions, View} from 'react-native'; import {Keyboard, Platform, useWindowDimensions, View} from 'react-native';
import Button from '@components/button';
import FloatingTextInput, {type FloatingTextInputRef} from '@components/floating_text_input_label'; import FloatingTextInput, {type FloatingTextInputRef} from '@components/floating_text_input_label';
import FormattedText from '@components/formatted_text'; import FormattedText from '@components/formatted_text';
import Loading from '@components/loading';
import {useIsTablet} from '@hooks/device'; import {useIsTablet} from '@hooks/device';
import {t} from '@i18n';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography'; import {typography} from '@utils/typography';
import {removeProtocol, stripTrailingSlashes} from '@utils/url'; import {removeProtocol, stripTrailingSlashes} from '@utils/url';
@ -49,24 +46,23 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
marginTop: 8, marginTop: 8,
...typography('Body', 75, 'Regular'), ...typography('Body', 75, 'Regular'),
}, },
connectButton: { buttonContainer: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
width: '100%', width: '100%',
marginTop: 32, marginTop: 32,
marginLeft: 20,
marginRight: 20,
},
loadingContainerStyle: {
marginRight: 10,
padding: 0,
top: -2,
},
loading: {
height: 20,
width: 20,
}, },
})); }));
const messages = defineMessages({
save: {
id: 'edit_server.save',
defaultMessage: 'Save',
},
saving: {
id: 'edit_server.saving',
defaultMessage: 'Saving',
},
});
const EditServerForm = ({ const EditServerForm = ({
buttonDisabled, buttonDisabled,
connecting, connecting,
@ -91,7 +87,7 @@ const EditServerForm = ({
keyboardAwareRef.current?.scrollToPosition(0, 0); keyboardAwareRef.current?.scrollToPosition(0, 0);
} }
} }
}, []); }, [keyboardAwareRef]);
const onUpdate = useCallback(() => { const onUpdate = useCallback(() => {
Keyboard.dismiss(); Keyboard.dismiss();
@ -114,7 +110,7 @@ const EditServerForm = ({
keyboardAwareRef.current?.scrollToPosition(0, offsetY); keyboardAwareRef.current?.scrollToPosition(0, offsetY);
}); });
} }
}, [dimensions, isTablet]); }, [dimensions, isTablet, keyboardAwareRef]);
useEffect(() => { useEffect(() => {
if (Platform.OS === 'ios' && isTablet) { if (Platform.OS === 'ios' && isTablet) {
@ -126,25 +122,6 @@ const EditServerForm = ({
} }
}, [onFocus]); }, [onFocus]);
const buttonType = buttonDisabled ? 'disabled' : 'default';
const styleButtonText = buttonTextStyle(theme, 'lg', 'primary', buttonType);
const styleButtonBackground = buttonBackgroundStyle(theme, 'lg', 'primary', buttonType);
let buttonID = t('edit_server.save');
let buttonText = 'Save';
let buttonIcon;
if (connecting) {
buttonID = t('edit_server.saving');
buttonText = 'Saving';
buttonIcon = (
<Loading
containerStyle={styles.loadingContainerStyle}
color={theme.buttonColor}
/>
);
}
const saveButtonTestId = buttonDisabled ? 'edit_server_form.save.button.disabled' : 'edit_server_form.save.button'; const saveButtonTestId = buttonDisabled ? 'edit_server_form.save.button.disabled' : 'edit_server_form.save.button';
return ( return (
@ -180,21 +157,17 @@ const EditServerForm = ({
values={{url: removeProtocol(stripTrailingSlashes(serverUrl))}} values={{url: removeProtocol(stripTrailingSlashes(serverUrl))}}
/> />
} }
<Button <View style={styles.buttonContainer}>
containerStyle={styles.connectButton} <Button
buttonStyle={styleButtonBackground} disabled={buttonDisabled || connecting}
disabledStyle={styleButtonBackground} onPress={onUpdate}
disabled={buttonDisabled} testID={saveButtonTestId}
onPress={onUpdate} size='lg'
testID={saveButtonTestId} theme={theme}
> text={formatMessage(connecting ? messages.saving : messages.save)}
{buttonIcon} showLoader={connecting}
<FormattedText
defaultMessage={buttonText}
id={buttonID}
style={styleButtonText}
/> />
</Button> </View>
</View> </View>
); );
}; };

View file

@ -1,7 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Button} from '@rneui/base';
import React, {useCallback, useEffect, useRef, useState} from 'react'; import React, {useCallback, useEffect, useRef, useState} from 'react';
import {useIntl} from 'react-intl'; import {useIntl} from 'react-intl';
import {Keyboard, Platform, Text, useWindowDimensions, View} from 'react-native'; import {Keyboard, Platform, Text, useWindowDimensions, View} from 'react-native';
@ -11,6 +10,7 @@ import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-nati
import {SafeAreaView} from 'react-native-safe-area-context'; import {SafeAreaView} from 'react-native-safe-area-context';
import {sendPasswordResetEmail} from '@actions/remote/session'; import {sendPasswordResetEmail} from '@actions/remote/session';
import Button from '@components/button';
import FloatingTextInput from '@components/floating_text_input_label'; import FloatingTextInput from '@components/floating_text_input_label';
import FormattedText from '@components/formatted_text'; import FormattedText from '@components/formatted_text';
import {Screens} from '@constants'; import {Screens} from '@constants';
@ -18,7 +18,6 @@ import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
import {useAvoidKeyboard} from '@hooks/device'; import {useAvoidKeyboard} from '@hooks/device';
import SecurityManager from '@managers/security_manager'; import SecurityManager from '@managers/security_manager';
import Background from '@screens/background'; import Background from '@screens/background';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {isEmail} from '@utils/helpers'; import {isEmail} from '@utils/helpers';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography'; import {typography} from '@utils/typography';
@ -65,7 +64,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
justifyContent: 'center', justifyContent: 'center',
paddingHorizontal: 24, paddingHorizontal: 24,
}, },
returnButton: { returnButtonContainer: {
marginTop: 32, marginTop: 32,
}, },
subheader: { subheader: {
@ -158,17 +157,15 @@ const ForgotPassword = ({componentId, serverUrl, theme}: Props) => {
<Text style={styles.successText}> <Text style={styles.successText}>
{email} {email}
</Text> </Text>
<Button <View style={styles.returnButtonContainer}>
testID='password_send.return' <Button
onPress={onReturn} testID='password_send.return'
buttonStyle={[styles.returnButton, buttonBackgroundStyle(theme, 'lg', 'primary', 'default')]} onPress={onReturn}
> size='lg'
<FormattedText theme={theme}
id='password_send.return' text={formatMessage({id: 'password_send.return', defaultMessage: 'Return to Log In'})}
defaultMessage='Return to Log In'
style={buttonTextStyle(theme, 'lg', 'primary', 'default')}
/> />
</Button> </View>
</View> </View>
); );
} }
@ -221,19 +218,16 @@ const ForgotPassword = ({componentId, serverUrl, theme}: Props) => {
theme={theme} theme={theme}
value={email} value={email}
/> />
<Button <View style={styles.returnButtonContainer}>
testID='forgot.password.button' <Button
buttonStyle={[styles.returnButton, buttonBackgroundStyle(theme, 'lg', 'primary', 'default'), error ? styles.error : undefined]} testID='forgot.password.button'
disabledStyle={[styles.returnButton, buttonBackgroundStyle(theme, 'lg', 'primary', 'disabled'), error ? styles.error : undefined]} disabled={!email}
disabled={!email} onPress={submitResetPassword}
onPress={submitResetPassword} size='lg'
> text={formatMessage({id: 'password_send.reset', defaultMessage: 'Reset my password'})}
<FormattedText theme={theme}
id='password_send.reset'
defaultMessage='Reset my password'
style={buttonTextStyle(theme, 'lg', 'primary', email ? 'default' : 'disabled')}
/> />
</Button> </View>
</View> </View>
</View> </View>
</KeyboardAwareScrollView> </KeyboardAwareScrollView>

View file

@ -256,61 +256,106 @@ exports[`components/categories_list should render channels error 1`] = `
There was a problem loading content for this team. There was a problem loading content for this team.
</Text> </Text>
<View <View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={ style={
{ {
"backgroundColor": "#ffffff",
"borderRadius": 4,
"marginTop": 24, "marginTop": 24,
"opacity": 1,
"paddingHorizontal": 24,
"paddingVertical": 12,
} }
} }
> >
<Text <View
style={ style={
[ [
{ {
"fontFamily": "OpenSans-SemiBold", "overflow": "hidden",
"fontSize": 16,
"fontWeight": "600",
"lineHeight": 24,
}, },
{ {
"color": "#1c58d9", "borderRadius": 2,
}, },
undefined,
false,
] ]
} }
testID="RNE_BUTTON_WRAPPER"
> >
Retry <View
</Text> accessibilityRole="button"
accessibilityState={
{
"busy": false,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"opacity": 1,
}
}
>
<View
style={
{
"alignItems": "center",
"backgroundColor": "#1c58d9",
"borderColor": "#2089dc",
"borderRadius": 4,
"borderWidth": 0,
"flexDirection": "row",
"justifyContent": "center",
"padding": 8,
"paddingHorizontal": 24,
"paddingVertical": 12,
}
}
>
<View
style={
{
"alignItems": "center",
"flexDirection": "row",
"gap": 7,
}
}
testID="undefined-text-container"
>
<Text
numberOfLines={1}
style={
{
"color": "#ffffff",
"fontFamily": "OpenSans-SemiBold",
"fontSize": 16,
"fontWeight": "600",
"lineHeight": 24,
}
}
>
Retry
</Text>
</View>
</View>
</View>
</View>
</View> </View>
</View> </View>
</View> </View>
@ -643,61 +688,106 @@ exports[`components/categories_list should render team error 1`] = `
There was a problem loading content for this server. There was a problem loading content for this server.
</Text> </Text>
<View <View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={ style={
{ {
"backgroundColor": "#ffffff",
"borderRadius": 4,
"marginTop": 24, "marginTop": 24,
"opacity": 1,
"paddingHorizontal": 24,
"paddingVertical": 12,
} }
} }
> >
<Text <View
style={ style={
[ [
{ {
"fontFamily": "OpenSans-SemiBold", "overflow": "hidden",
"fontSize": 16,
"fontWeight": "600",
"lineHeight": 24,
}, },
{ {
"color": "#1c58d9", "borderRadius": 2,
}, },
undefined,
false,
] ]
} }
testID="RNE_BUTTON_WRAPPER"
> >
Retry <View
</Text> accessibilityRole="button"
accessibilityState={
{
"busy": false,
"checked": undefined,
"disabled": false,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"opacity": 1,
}
}
>
<View
style={
{
"alignItems": "center",
"backgroundColor": "#1c58d9",
"borderColor": "#2089dc",
"borderRadius": 4,
"borderWidth": 0,
"flexDirection": "row",
"justifyContent": "center",
"padding": 8,
"paddingHorizontal": 24,
"paddingVertical": 12,
}
}
>
<View
style={
{
"alignItems": "center",
"flexDirection": "row",
"gap": 7,
}
}
testID="undefined-text-container"
>
<Text
numberOfLines={1}
style={
{
"color": "#ffffff",
"fontFamily": "OpenSans-SemiBold",
"fontSize": 16,
"fontWeight": "600",
"lineHeight": 24,
}
}
>
Retry
</Text>
</View>
</View>
</View>
</View>
</View> </View>
</View> </View>
</View> </View>

View file

@ -1,14 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useMemo} from 'react'; import React from 'react';
import {useIntl} from 'react-intl';
import {View} from 'react-native'; import {View} from 'react-native';
import {showUnreadChannelsOnly} from '@actions/local/channel'; import {showUnreadChannelsOnly} from '@actions/local/channel';
import Button from '@components/button';
import FormattedText from '@components/formatted_text'; import FormattedText from '@components/formatted_text';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {useServerUrl} from '@context/server'; import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme'; import {useTheme} from '@context/theme';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography'; import {typography} from '@utils/typography';
@ -19,7 +19,7 @@ type Props = {
} }
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
button: { buttonContainer: {
marginTop: 24, marginTop: 24,
}, },
container: { container: {
@ -45,13 +45,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
})); }));
function EmptyUnreads({onlyUnreads}: Props) { function EmptyUnreads({onlyUnreads}: Props) {
const intl = useIntl();
const theme = useTheme(); const theme = useTheme();
const serverUrl = useServerUrl(); const serverUrl = useServerUrl();
const styles = getStyleSheet(theme); const styles = getStyleSheet(theme);
const buttonStyle = useMemo(() => [buttonBackgroundStyle(theme, 'lg', 'tertiary', 'inverted'), styles.button],
[theme]);
const onPress = () => { const onPress = () => {
showUnreadChannelsOnly(serverUrl, !onlyUnreads); showUnreadChannelsOnly(serverUrl, !onlyUnreads);
}; };
@ -71,17 +69,16 @@ function EmptyUnreads({onlyUnreads}: Props) {
style={styles.paragraph} style={styles.paragraph}
testID='unreads.empty.paragraph' testID='unreads.empty.paragraph'
/> />
<TouchableWithFeedback <View style={styles.buttonContainer}>
style={buttonStyle} <Button
onPress={onPress} text={intl.formatMessage({id: 'unreads.empty.show_all', defaultMessage: 'Show all'})}
type={'opacity'} theme={theme}
> size='lg'
<FormattedText onPress={onPress}
id='unreads.empty.show_all' emphasis='tertiary'
defaultMessage='Show all' isInverted={true}
style={buttonTextStyle(theme, 'lg', 'tertiary', 'inverted')}
/> />
</TouchableWithFeedback> </View>
</View> </View>
); );
} }

View file

@ -1,19 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Button} from '@rneui/base';
import React, {useCallback, useMemo} from 'react'; import React, {useCallback, useMemo} from 'react';
import {useIntl} from 'react-intl'; import {defineMessages, useIntl} from 'react-intl';
import {View, Text, ScrollView} from 'react-native'; import {View, Text, ScrollView} from 'react-native';
import CompassIcon from '@components/compass_icon'; import Button from '@components/button';
import FormattedText from '@components/formatted_text';
import AlertSvg from '@components/illustrations/alert'; import AlertSvg from '@components/illustrations/alert';
import ErrorSvg from '@components/illustrations/error'; import ErrorSvg from '@components/illustrations/error';
import SuccessSvg from '@components/illustrations/success'; import SuccessSvg from '@components/illustrations/success';
import {useTheme} from '@context/theme'; import {useTheme} from '@context/theme';
import {t} from '@i18n';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme'; import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
import {typography} from '@utils/typography'; import {typography} from '@utils/typography';
@ -77,17 +73,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
flexGrow: 1, flexGrow: 1,
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'center', justifyContent: 'center',
}, gap: 8,
summaryButtonTextContainer: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
height: 24,
},
summaryButtonIcon: {
marginRight: 7,
color: theme.buttonColor,
}, },
summaryButtonContainerStyle: {flexGrow: 1}, summaryButtonContainerStyle: {flexGrow: 1},
}; };
@ -109,6 +95,21 @@ type SummaryProps = {
onBack: () => void; onBack: () => void;
} }
const messages = defineMessages({
done: {
id: 'invite.summary.done',
defaultMessage: 'Done',
},
tryAgain: {
id: 'invite.summary.try_again',
defaultMessage: 'Try again',
},
back: {
id: 'invite.summary.back',
defaultMessage: 'Go back',
},
});
export default function Summary({ export default function Summary({
result, result,
selectedIds, selectedIds,
@ -118,7 +119,7 @@ export default function Summary({
onRetry, onRetry,
onBack, onBack,
}: SummaryProps) { }: SummaryProps) {
const {formatMessage, locale} = useIntl(); const {formatMessage} = useIntl();
const theme = useTheme(); const theme = useTheme();
const styles = getStyleSheet(theme); const styles = getStyleSheet(theme);
@ -183,64 +184,41 @@ export default function Summary({
let onPress; let onPress;
let iconName = ''; let iconName = '';
let text; let text;
let styleButtonText = buttonTextStyle(theme, 'lg', 'primary'); let emphasis: ButtonEmphasis = 'primary';
let styleButtonBackground = buttonBackgroundStyle(theme, 'lg', 'primary');
const styleButtonIcon = [];
styleButtonIcon.push(styles.summaryButtonIcon);
switch (type) { switch (type) {
case SummaryButtonType.BACK: case SummaryButtonType.BACK:
onPress = onBack; onPress = onBack;
iconName = 'chevron-left'; iconName = 'chevron-left';
text = { text = messages.back;
id: t('invite.summary.back'), emphasis = 'tertiary';
defaultMessage: 'Go back',
};
styleButtonText = buttonTextStyle(theme, 'lg', 'tertiary');
styleButtonBackground = [buttonBackgroundStyle(theme, 'lg', 'tertiary'), {marginRight: 8}];
styleButtonIcon.push({color: theme.buttonBg});
break; break;
case SummaryButtonType.RETRY: case SummaryButtonType.RETRY:
onPress = onRetry; onPress = onRetry;
iconName = 'refresh'; iconName = 'refresh';
text = { text = messages.tryAgain;
id: t('invite.summary.try_again'),
defaultMessage: 'Try again',
};
break; break;
case SummaryButtonType.DONE: case SummaryButtonType.DONE:
default: default:
onPress = onClose; onPress = onClose;
text = { text = messages.done;
id: t('invite.summary.done'),
defaultMessage: 'Done',
};
break; break;
} }
return ( return (
<Button <View style={styles.summaryButtonContainerStyle}>
containerStyle={styles.summaryButtonContainerStyle} <Button
buttonStyle={styleButtonBackground} onPress={onPress}
onPress={onPress} testID={`invite.summary_button.${SummaryButtonType.RETRY}`}
testID={`invite.summary_button.${SummaryButtonType.RETRY}`} text={formatMessage(text)}
> iconName={iconName}
<View style={styles.summaryButtonTextContainer}> emphasis={emphasis}
{iconName && ( size='lg'
<CompassIcon theme={theme}
name={iconName} />
size={24} </View>
style={styleButtonIcon}
/>
)}
<FormattedText
{...text}
style={styleButtonText}
/>
</View>
</Button>
); );
}, [theme, locale, onClose, onRetry, onBack]); }, [styles.summaryButtonContainerStyle, formatMessage, theme, onBack, onRetry, onClose]);
return ( return (
<View <View

View file

@ -2,21 +2,19 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {useManagedConfig} from '@mattermost/react-native-emm'; import {useManagedConfig} from '@mattermost/react-native-emm';
import {Button} from '@rneui/base'; import {Button as RNEButton} from '@rneui/base';
import React, {useCallback, useEffect, useMemo, useRef, useState, type RefObject} from 'react'; import React, {useCallback, useEffect, useMemo, useRef, useState, type RefObject} from 'react';
import {useIntl} from 'react-intl'; import {defineMessages, useIntl} from 'react-intl';
import {Keyboard, TextInput, TouchableOpacity, View} from 'react-native'; import {Keyboard, TextInput, TouchableOpacity, View} from 'react-native';
import {login} from '@actions/remote/session'; import {login} from '@actions/remote/session';
import Button from '@components/button';
import CompassIcon from '@components/compass_icon'; import CompassIcon from '@components/compass_icon';
import FloatingTextInput from '@components/floating_text_input_label'; import FloatingTextInput from '@components/floating_text_input_label';
import FormattedText from '@components/formatted_text'; import FormattedText from '@components/formatted_text';
import Loading from '@components/loading';
import {FORGOT_PASSWORD, MFA} from '@constants/screens'; import {FORGOT_PASSWORD, MFA} from '@constants/screens';
import {useAvoidKeyboard} from '@hooks/device'; import {useAvoidKeyboard} from '@hooks/device';
import {t} from '@i18n';
import {goToScreen, loginAnimationOptions, resetToHome} from '@screens/navigation'; import {goToScreen, loginAnimationOptions, resetToHome} from '@screens/navigation';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {getFullErrorMessage, getServerError, isErrorWithMessage, isServerError} from '@utils/errors'; import {getFullErrorMessage, getServerError, isErrorWithMessage, isServerError} from '@utils/errors';
import {preventDoubleTap} from '@utils/tap'; import {preventDoubleTap} from '@utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
@ -67,12 +65,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
fontSize: 14, fontSize: 14,
fontFamily: 'OpenSans-SemiBold', fontFamily: 'OpenSans-SemiBold',
}, },
loadingContainerStyle: { loginButtonContainer: {
marginRight: 10,
padding: 0,
top: -2,
},
loginButton: {
marginTop: 25, marginTop: 25,
}, },
endAdornment: { endAdornment: {
@ -80,6 +73,17 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
}, },
})); }));
const messages = defineMessages({
signIn: {
id: 'login.signIn',
defaultMessage: 'Log In',
},
signingIn: {
id: 'login.signingIn',
defaultMessage: 'Logging In',
},
});
const LoginForm = ({config, extra, keyboardAwareRef, serverDisplayName, launchError, launchType, license, serverUrl, theme}: LoginProps) => { const LoginForm = ({config, extra, keyboardAwareRef, serverDisplayName, launchError, launchType, license, serverUrl, theme}: LoginProps) => {
const styles = getStyleSheet(theme); const styles = getStyleSheet(theme);
const loginRef = useRef<TextInput>(null); const loginRef = useRef<TextInput>(null);
@ -256,44 +260,20 @@ const LoginForm = ({config, extra, keyboardAwareRef, serverDisplayName, launchEr
}, [loginId, password]); }, [loginId, password]);
const renderProceedButton = useMemo(() => { const renderProceedButton = useMemo(() => {
const buttonType = buttonDisabled ? 'disabled' : 'default';
const styleButtonText = buttonTextStyle(theme, 'lg', 'primary', buttonType);
const styleButtonBackground = buttonBackgroundStyle(theme, 'lg', 'primary', buttonType);
let buttonID = t('login.signIn');
let buttonText = 'Log In';
let buttonIcon;
if (isLoading) {
buttonID = t('login.signingIn');
buttonText = 'Logging In';
buttonIcon = (
<Loading
containerStyle={styles.loadingContainerStyle}
color={theme.buttonColor}
/>
);
}
const signinButtonTestId = buttonDisabled ? 'login_form.signin.button.disabled' : 'login_form.signin.button';
return ( return (
<Button <View style={styles.loginButtonContainer}>
disabled={buttonDisabled} <Button
onPress={onLogin} disabled={buttonDisabled}
buttonStyle={[styles.loginButton, styleButtonBackground]} onPress={onLogin}
disabledStyle={[styles.loginButton, styleButtonBackground]} size='lg'
testID={signinButtonTestId} testID={buttonDisabled ? 'login_form.signin.button.disabled' : 'login_form.signin.button'}
> text={intl.formatMessage(isLoading ? messages.signingIn : messages.signIn)}
{buttonIcon} showLoader={isLoading}
<FormattedText theme={theme}
id={buttonID}
defaultMessage={buttonText}
style={styleButtonText}
/> />
</Button> </View>
); );
}, [buttonDisabled, loginId, password, isLoading, theme]); }, [styles.loginButtonContainer, buttonDisabled, onLogin, intl, isLoading, theme]);
const endAdornment = ( const endAdornment = (
<TouchableOpacity <TouchableOpacity
@ -356,7 +336,7 @@ const LoginForm = ({config, extra, keyboardAwareRef, serverDisplayName, launchEr
/> />
{(emailEnabled || usernameEnabled) && config.PasswordEnableForgotLink !== 'false' && ( {(emailEnabled || usernameEnabled) && config.PasswordEnableForgotLink !== 'false' && (
<Button <RNEButton
onPress={onPressForgotPassword} onPress={onPressForgotPassword}
buttonStyle={[styles.forgotPasswordBtn, error ? styles.forgotPasswordError : undefined]} buttonStyle={[styles.forgotPasswordBtn, error ? styles.forgotPasswordError : undefined]}
testID='login_form.forgot_password.button' testID='login_form.forgot_password.button'
@ -366,7 +346,7 @@ const LoginForm = ({config, extra, keyboardAwareRef, serverDisplayName, launchEr
defaultMessage='Forgot your password?' defaultMessage='Forgot your password?'
style={styles.forgotPasswordTxt} style={styles.forgotPasswordTxt}
/> />
</Button> </RNEButton>
)} )}
{renderProceedButton} {renderProceedButton}
</View> </View>

View file

@ -1,15 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Button} from '@rneui/base';
import React from 'react'; import React from 'react';
import {useIntl} from 'react-intl'; import {useIntl} from 'react-intl';
import {Image, type ImageSourcePropType, Text, View} from 'react-native'; import {Image, type ImageSourcePropType, StyleSheet, View} from 'react-native';
import CompassIcon from '@components/compass_icon'; import Button from '@components/button';
import {Sso} from '@constants'; import {Sso} from '@constants';
import {buttonBackgroundStyle} from '@utils/buttonStyles';
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
type SsoInfo = { type SsoInfo = {
text: string; text: string;
@ -26,8 +23,6 @@ type Props = {
const SsoOptions = ({goToSso, ssoOnly, ssoOptions, theme}: Props) => { const SsoOptions = ({goToSso, ssoOnly, ssoOptions, theme}: Props) => {
const {formatMessage} = useIntl(); const {formatMessage} = useIntl();
const styles = getStyleSheet(theme);
const styleButtonBackground = buttonBackgroundStyle(theme, 'lg', 'primary');
const getSsoButtonOptions = ((ssoType: string): SsoInfo => { const getSsoButtonOptions = ((ssoType: string): SsoInfo => {
const sso: SsoInfo = {} as SsoInfo; const sso: SsoInfo = {} as SsoInfo;
@ -63,12 +58,8 @@ const SsoOptions = ({goToSso, ssoOnly, ssoOptions, theme}: Props) => {
(ssoType: string) => ssoOptions[ssoType].enabled, (ssoType: string) => ssoOptions[ssoType].enabled,
); );
let styleViewContainer; const styleViewContainer = enabledSSOs.length === 2 && !ssoOnly ? styles.containerAsRow : undefined;
let styleButtonContainer; const styleButtonWrapper = enabledSSOs.length === 2 && !ssoOnly ? styles.buttonWrapper : undefined;
if (enabledSSOs.length === 2 && !ssoOnly) {
styleViewContainer = styles.containerAsRow;
styleButtonContainer = styles.buttonContainer;
}
const componentArray = []; const componentArray = [];
for (const ssoType of enabledSSOs) { for (const ssoType of enabledSSOs) {
@ -78,38 +69,24 @@ const SsoOptions = ({goToSso, ssoOnly, ssoOptions, theme}: Props) => {
}; };
componentArray.push( componentArray.push(
<Button <View style={styleButtonWrapper}>
key={ssoType} <Button
onPress={handlePress} key={ssoType}
buttonStyle={[styleButtonBackground, styles.button]} onPress={handlePress}
containerStyle={styleButtonContainer} size='lg'
> theme={theme}
{imageSrc && ( iconName={compassIcon}
<Image emphasis='secondary'
key={'image' + ssoType} iconComponent={imageSrc ? (
source={imageSrc} <Image
style={styles.logoStyle} key={'image' + ssoType}
/> source={imageSrc}
)} style={styles.logoStyle}
{compassIcon && />
<CompassIcon ) : undefined}
name={compassIcon} text={text}
size={16}
color={theme.centerChannelColor}
/> />
} </View>,
<View
style={styles.buttonTextContainer}
>
<Text
key={ssoType}
style={styles.buttonText}
testID={text}
>
{text}
</Text>
</View>
</Button>,
); );
} }
@ -120,41 +97,23 @@ const SsoOptions = ({goToSso, ssoOnly, ssoOptions, theme}: Props) => {
); );
}; };
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const styles = StyleSheet.create({
container: { container: {
marginVertical: 24, marginVertical: 24,
gap: 8,
}, },
containerAsRow: { containerAsRow: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
}, },
buttonContainer: { buttonWrapper: {
width: '48%', flex: 1,
marginRight: 8,
},
button: {
marginVertical: 4,
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: changeOpacity(theme.centerChannelColor, 0.16),
},
buttonTextContainer: {
color: theme.centerChannelColor,
flexDirection: 'row',
marginLeft: 9,
},
buttonText: {
color: theme.centerChannelColor,
fontFamily: 'OpenSans-SemiBold',
fontSize: 16,
lineHeight: 18,
top: 2,
}, },
logoStyle: { logoStyle: {
height: 18, height: 18,
marginRight: 5, marginRight: 5,
width: 18, width: 18,
}, },
})); });
export default SsoOptions; export default SsoOptions;

View file

@ -1,7 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Button} from '@rneui/base';
import React, {useCallback, useEffect, useRef, useState} from 'react'; import React, {useCallback, useEffect, useRef, useState} from 'react';
import {useIntl} from 'react-intl'; import {useIntl} from 'react-intl';
import {Keyboard, Platform, useWindowDimensions, View} from 'react-native'; import {Keyboard, Platform, useWindowDimensions, View} from 'react-native';
@ -11,18 +10,16 @@ import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-nati
import {SafeAreaView} from 'react-native-safe-area-context'; import {SafeAreaView} from 'react-native-safe-area-context';
import {login} from '@actions/remote/session'; import {login} from '@actions/remote/session';
import Button from '@components/button';
import FloatingTextInput from '@components/floating_text_input_label'; import FloatingTextInput from '@components/floating_text_input_label';
import FormattedText from '@components/formatted_text'; import FormattedText from '@components/formatted_text';
import Loading from '@components/loading';
import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
import {useAvoidKeyboard} from '@hooks/device'; import {useAvoidKeyboard} from '@hooks/device';
import {t} from '@i18n'; import {usePreventDoubleTap} from '@hooks/utils';
import SecurityManager from '@managers/security_manager'; import SecurityManager from '@managers/security_manager';
import Background from '@screens/background'; import Background from '@screens/background';
import {popTopScreen} from '@screens/navigation'; import {popTopScreen} from '@screens/navigation';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {getErrorMessage} from '@utils/errors'; import {getErrorMessage} from '@utils/errors';
import {preventDoubleTap} from '@utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography'; import {typography} from '@utils/typography';
@ -52,9 +49,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
justifyContent: 'center', justifyContent: 'center',
marginTop: Platform.select({android: 56}), marginTop: Platform.select({android: 56}),
}, },
error: {
marginTop: 64,
},
flex: { flex: {
flex: 1, flex: 1,
}, },
@ -72,16 +66,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
paddingHorizontal: 24, paddingHorizontal: 24,
height: '100%', height: '100%',
}, },
loading: { proceedButtonContainer: {
height: 20,
width: 20,
},
loadingContainerStyle: {
marginRight: 10,
padding: 0,
top: -2,
},
proceedButton: {
marginTop: 32, marginTop: 32,
}, },
shield: { shield: {
@ -114,12 +99,12 @@ const MFA = ({componentId, config, goToHome, license, loginId, password, serverD
setError(''); setError('');
}, []); }, []);
const submit = useCallback(preventDoubleTap(async () => { const submit = usePreventDoubleTap(useCallback(async () => {
Keyboard.dismiss(); Keyboard.dismiss();
if (!token) { if (!token) {
setError( setError(
formatMessage({ formatMessage({
id: t('login_mfa.tokenReq'), id: 'login_mfa.tokenReq',
defaultMessage: 'Please enter an MFA token', defaultMessage: 'Please enter an MFA token',
}), }),
); );
@ -133,7 +118,7 @@ const MFA = ({componentId, config, goToHome, license, loginId, password, serverD
return; return;
} }
goToHome(result.error); goToHome(result.error);
}), [token]); }, [config, formatMessage, goToHome, intl, license, loginId, password, serverDisplayName, serverUrl, token]));
const transform = useAnimatedStyle(() => { const transform = useAnimatedStyle(() => {
const duration = Platform.OS === 'android' ? 250 : 350; const duration = Platform.OS === 'android' ? 250 : 350;
@ -224,25 +209,18 @@ const MFA = ({componentId, config, goToHome, license, loginId, password, serverD
theme={theme} theme={theme}
value={token} value={token}
/> />
<Button <View style={styles.proceedButtonContainer}>
testID='login_mfa.submit' <Button
buttonStyle={[styles.proceedButton, buttonBackgroundStyle(theme, 'lg', 'primary', 'default'), error ? styles.error : undefined]} testID='login_mfa.submit'
disabledStyle={[styles.proceedButton, buttonBackgroundStyle(theme, 'lg', 'primary', 'disabled'), error ? styles.error : undefined]} size='lg'
disabled={!token} disabled={!token}
onPress={submit} onPress={submit}
> theme={theme}
{isLoading && showLoader={isLoading}
<Loading text={formatMessage({id: 'mobile.components.select_server_view.proceed', defaultMessage: 'Proceed'})}
containerStyle={styles.loadingContainerStyle} isDestructive={Boolean(error)}
color={theme.buttonColor}
/> />
} </View>
<FormattedText
id='mobile.components.select_server_view.proceed'
defaultMessage='Proceed'
style={buttonTextStyle(theme, 'lg', 'primary', token ? 'default' : 'disabled')}
/>
</Button>
</View> </View>
</View> </View>
</KeyboardAwareScrollView> </KeyboardAwareScrollView>

View file

@ -2,14 +2,13 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react';
import {Pressable, useWindowDimensions, View} from 'react-native'; import {Pressable, StyleSheet, useWindowDimensions, View} from 'react-native';
import Animated, {Extrapolate, interpolate, useAnimatedStyle} from 'react-native-reanimated'; import Animated, {Extrapolate, interpolate, useAnimatedStyle} from 'react-native-reanimated';
import CompassIcon from '@components/compass_icon'; import CompassIcon from '@components/compass_icon';
import FormattedText from '@components/formatted_text'; import FormattedText from '@components/formatted_text';
import {ONBOARDING_CONTENT_MAX_WIDTH} from '@screens/onboarding/slide'; import {ONBOARDING_CONTENT_MAX_WIDTH} from '@screens/onboarding/slide';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles'; import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {makeStyleSheetFromTheme} from '@utils/theme';
type Props = { type Props = {
theme: Theme; theme: Theme;
@ -19,24 +18,20 @@ type Props = {
scrollX: Animated.SharedValue<number>; scrollX: Animated.SharedValue<number>;
}; };
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const styles = StyleSheet.create({
button: { button: {
marginTop: 5, marginTop: 5,
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}, },
rowIcon: {
color: theme.buttonColor,
fontSize: 12,
marginLeft: 5,
marginTop: 4.5,
},
nextButtonText: { nextButtonText: {
flexDirection: 'row', flexDirection: 'row',
position: 'absolute', position: 'absolute',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center',
width: 120, width: 120,
gap: 5,
}, },
footerButtonsContainer: { footerButtonsContainer: {
flexDirection: 'column', flexDirection: 'column',
@ -46,7 +41,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
width: '100%', width: '100%',
alignItems: 'center', alignItems: 'center',
}, },
})); });
const AnimatedButton = Animated.createAnimatedComponent(Pressable); const AnimatedButton = Animated.createAnimatedComponent(Pressable);
const BUTTON_SIZE = 120; const BUTTON_SIZE = 120;
@ -60,7 +55,6 @@ const FooterButtons = ({
}: Props) => { }: Props) => {
const {width} = useWindowDimensions(); const {width} = useWindowDimensions();
const buttonWidth = Math.min(width * 0.8, ONBOARDING_CONTENT_MAX_WIDTH); const buttonWidth = Math.min(width * 0.8, ONBOARDING_CONTENT_MAX_WIDTH);
const styles = getStyleSheet(theme);
// keep in mind penultimate and ultimate slides to run buttons text/opacity/size animations // keep in mind penultimate and ultimate slides to run buttons text/opacity/size animations
const penultimateSlide = lastSlideIndex - 1; const penultimateSlide = lastSlideIndex - 1;
@ -114,6 +108,8 @@ const FooterButtons = ({
return {opacity: interpolatedScale}; return {opacity: interpolatedScale};
}); });
const textStyles = StyleSheet.flatten(buttonTextStyle(theme, 'lg', 'primary', 'default'));
const nextButtonText = ( const nextButtonText = (
<Animated.View style={[styles.nextButtonText, opacityNextTextStyle]}> <Animated.View style={[styles.nextButtonText, opacityNextTextStyle]}>
<FormattedText <FormattedText
@ -123,7 +119,8 @@ const FooterButtons = ({
/> />
<CompassIcon <CompassIcon
name='arrow-forward-ios' name='arrow-forward-ios'
style={styles.rowIcon} size={12}
color={textStyles.color}
/> />
</Animated.View> </Animated.View>
); );

View file

@ -2,6 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useCallback, useEffect, useMemo, useState} from 'react'; import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {useIntl} from 'react-intl';
import {Alert, Text, TouchableOpacity, View} from 'react-native'; import {Alert, Text, TouchableOpacity, View} from 'react-native';
import Animated from 'react-native-reanimated'; import Animated from 'react-native-reanimated';
import {type Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'; import {type Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
@ -10,6 +11,7 @@ import {getPosts} from '@actions/local/post';
import {fetchChannelById, joinChannel, switchToChannelById} from '@actions/remote/channel'; import {fetchChannelById, joinChannel, switchToChannelById} from '@actions/remote/channel';
import {fetchPostById, fetchPostsAround, fetchPostThread} from '@actions/remote/post'; import {fetchPostById, fetchPostsAround, fetchPostThread} from '@actions/remote/post';
import {addCurrentUserToTeam, fetchTeamByName, removeCurrentUserFromTeam} from '@actions/remote/team'; import {addCurrentUserToTeam, fetchTeamByName, removeCurrentUserFromTeam} from '@actions/remote/team';
import Button from '@components/button';
import CompassIcon from '@components/compass_icon'; import CompassIcon from '@components/compass_icon';
import FormattedText from '@components/formatted_text'; import FormattedText from '@components/formatted_text';
import Loading from '@components/loading'; import Loading from '@components/loading';
@ -21,12 +23,11 @@ import {useTheme} from '@context/theme';
import DatabaseManager from '@database/manager'; import DatabaseManager from '@database/manager';
import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
import {useIsTablet} from '@hooks/device'; import {useIsTablet} from '@hooks/device';
import {usePreventDoubleTap} from '@hooks/utils';
import SecurityManager from '@managers/security_manager'; import SecurityManager from '@managers/security_manager';
import {getChannelById, getMyChannel} from '@queries/servers/channel'; import {getChannelById, getMyChannel} from '@queries/servers/channel';
import {dismissModal} from '@screens/navigation'; import {dismissModal} from '@screens/navigation';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {closePermalink} from '@utils/permalink'; import {closePermalink} from '@utils/permalink';
import {preventDoubleTap} from '@utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {secureGetFromRecord} from '@utils/types'; import {secureGetFromRecord} from '@utils/types';
import {typography} from '@utils/typography'; import {typography} from '@utils/typography';
@ -105,11 +106,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
alignItems: 'center', alignItems: 'center',
}, },
footer: { footer: {
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
padding: 20, padding: 20,
width: '100%',
borderBottomLeftRadius: 12, borderBottomLeftRadius: 12,
borderBottomRightRadius: 12, borderBottomRightRadius: 12,
borderTopWidth: 1, borderTopWidth: 1,
@ -139,6 +136,7 @@ function Permalink({
isTeamMember, isTeamMember,
currentTeamId, currentTeamId,
}: Props) { }: Props) {
const intl = useIntl();
const [posts, setPosts] = useState<PostModel[]>([]); const [posts, setPosts] = useState<PostModel[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const theme = useTheme(); const theme = useTheme();
@ -273,17 +271,17 @@ function Permalink({
} }
dismissModal({componentId: Screens.PERMALINK}); dismissModal({componentId: Screens.PERMALINK});
closePermalink(); closePermalink();
}, [error]); }, [error?.joinedTeam, error?.teamId, serverUrl]);
useAndroidHardwareBackHandler(Screens.PERMALINK, handleClose); useAndroidHardwareBackHandler(Screens.PERMALINK, handleClose);
const handlePress = useCallback(preventDoubleTap(() => { const handlePress = usePreventDoubleTap(useCallback(() => {
if (channel) { if (channel) {
switchToChannelById(serverUrl, channel.id, channel.teamId); switchToChannelById(serverUrl, channel.id, channel.teamId);
} }
}), [channel?.id, channel?.teamId]); }, [channel, serverUrl]));
const handleJoin = useCallback(preventDoubleTap(async () => { const handleJoin = usePreventDoubleTap(useCallback(async () => {
setLoading(true); setLoading(true);
setError(undefined); setError(undefined);
if (error?.teamId && error.channelId) { if (error?.teamId && error.channelId) {
@ -296,7 +294,7 @@ function Permalink({
} }
setChannelId(error.channelId); setChannelId(error.channelId);
} }
}), [error, serverUrl]); }, [error, serverUrl]));
let content; let content;
if (loading) { if (loading) {
@ -334,18 +332,13 @@ function Permalink({
/> />
</View> </View>
<View style={style.footer}> <View style={style.footer}>
<TouchableOpacity <Button
style={[buttonBackgroundStyle(theme, 'lg', 'primary'), {width: '100%'}]} size='lg'
text={intl.formatMessage({id: 'mobile.search.jump', defaultMessage: 'Jump to recent messages'})}
theme={theme}
onPress={handlePress} onPress={handlePress}
testID='permalink.jump_to_recent_messages.button' testID='permalink.jump_to_recent_messages.button'
> />
<FormattedText
testID='permalink.search.jump'
id='mobile.search.jump'
defaultMessage='Jump to recent messages'
style={buttonTextStyle(theme, 'lg', 'primary')}
/>
</TouchableOpacity>
</View> </View>
</ExtraKeyboardProvider> </ExtraKeyboardProvider>
); );

View file

@ -2,16 +2,15 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react';
import {useIntl} from 'react-intl'; import {useIntl} from 'react-intl';
import {Text, TouchableOpacity, View} from 'react-native'; import {Text, View} from 'react-native';
import FormattedText from '@components/formatted_text'; import Button from '@components/button';
import JoinPrivateChannel from '@components/illustrations/join_private_channel'; import JoinPrivateChannel from '@components/illustrations/join_private_channel';
import JoinPublicChannel from '@components/illustrations/join_public_channel'; import JoinPublicChannel from '@components/illustrations/join_public_channel';
import MessageNotViewable from '@components/illustrations/message_not_viewable'; import MessageNotViewable from '@components/illustrations/message_not_viewable';
import Markdown from '@components/markdown'; import Markdown from '@components/markdown';
import {Screens} from '@constants'; import {Screens} from '@constants';
import {useTheme} from '@context/theme'; import {useTheme} from '@context/theme';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {getMarkdownBlockStyles, getMarkdownTextStyles} from '@utils/markdown'; import {getMarkdownBlockStyles, getMarkdownTextStyles} from '@utils/markdown';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography'; import {typography} from '@utils/typography';
@ -49,6 +48,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: changeOpacity(theme.centerChannelColor, 0.16), borderTopColor: changeOpacity(theme.centerChannelColor, 0.16),
padding: 20, padding: 20,
gap: 8,
}, },
}; };
@ -63,9 +63,6 @@ function PermalinkError({
const style = getStyleSheet(theme); const style = getStyleSheet(theme);
const intl = useIntl(); const intl = useIntl();
const buttonStylePrimary = buttonBackgroundStyle(theme, 'lg', 'primary');
const buttonTextStylePrimary = buttonTextStyle(theme, 'lg', 'primary');
if (error.notExist || error.unreachable) { if (error.notExist || error.unreachable) {
const title = intl.formatMessage({id: 'permalink.error.access.title', defaultMessage: 'Message not viewable'}); const title = intl.formatMessage({id: 'permalink.error.access.title', defaultMessage: 'Message not viewable'});
const text = intl.formatMessage({id: 'permalink.error.access.text', defaultMessage: 'The message you are trying to view is in a channel you dont have access to or has been deleted.'}); const text = intl.formatMessage({id: 'permalink.error.access.text', defaultMessage: 'The message you are trying to view is in a channel you dont have access to or has been deleted.'});
@ -77,25 +74,17 @@ function PermalinkError({
<Text style={style.errorText}>{text}</Text> <Text style={style.errorText}>{text}</Text>
</View> </View>
<View style={style.errorButtonContainer}> <View style={style.errorButtonContainer}>
<TouchableOpacity <Button
style={buttonStylePrimary} size='lg'
text={intl.formatMessage({id: 'permalink.error.okay', defaultMessage: 'Okay'})}
theme={theme}
onPress={handleClose} onPress={handleClose}
> />
<FormattedText
testID='permalink.error.okay'
id='permalink.error.okay'
defaultMessage='Okay'
style={buttonTextStylePrimary}
/>
</TouchableOpacity>
</View> </View>
</> </>
); );
} }
const buttonStyleTertiary = buttonBackgroundStyle(theme, 'lg', 'tertiary');
const buttonTextStyleTertiary = buttonTextStyle(theme, 'lg', 'tertiary');
const isPrivate = error.privateChannel || error.privateTeam; const isPrivate = error.privateChannel || error.privateTeam;
let image; let image;
let title; let title;
@ -143,23 +132,19 @@ function PermalinkError({
/> />
</View> </View>
<View style={style.errorButtonContainer}> <View style={style.errorButtonContainer}>
<TouchableOpacity <Button
style={buttonStylePrimary} size='lg'
text={button}
theme={theme}
onPress={handleJoin} onPress={handleJoin}
> />
<Text style={buttonTextStylePrimary}>{button}</Text> <Button
</TouchableOpacity> size='lg'
<TouchableOpacity emphasis='tertiary'
style={[buttonStyleTertiary, {marginTop: 8}]}
onPress={handleClose} onPress={handleClose}
> text={intl.formatMessage({id: 'permalink.error.cancel', defaultMessage: 'Cancel'})}
<FormattedText theme={theme}
testID='permalink.error.cancel' />
id='permalink.error.cancel'
defaultMessage='Cancel'
style={buttonTextStyleTertiary}
/>
</TouchableOpacity>
</View> </View>
</> </>
); );

View file

@ -3,11 +3,10 @@
import React from 'react'; import React from 'react';
import {useIntl} from 'react-intl'; import {useIntl} from 'react-intl';
import {StyleSheet, Text, View} from 'react-native'; import {Text, View} from 'react-native';
import Empty from '@components/illustrations/no_team'; import Empty from '@components/illustrations/no_team';
import {useTheme} from '@context/theme'; import {useTheme} from '@context/theme';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography'; import {typography} from '@utils/typography';
@ -40,21 +39,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
marginTop: 12, marginTop: 12,
...typography('Body', 200, 'Regular'), ...typography('Body', 200, 'Regular'),
}, },
buttonStyle: {
...StyleSheet.flatten(buttonBackgroundStyle(theme, 'lg', 'primary', 'default')),
flexDirection: 'row',
marginTop: 24,
},
buttonText: {
...StyleSheet.flatten(buttonTextStyle(theme, 'lg', 'primary', 'default')),
marginLeft: 8,
},
plusIcon: {
color: theme.sidebarText,
fontSize: 24,
lineHeight: 22,
},
})); }));
const NoTeams = () => { const NoTeams = () => {
@ -73,7 +57,7 @@ const NoTeams = () => {
<Text style={styles.description}> <Text style={styles.description}>
{intl.formatMessage({id: 'select_team.no_team.description', defaultMessage: 'To join a team, ask a team admin for an invite, or create your own team. You may also want to check your email inbox for an invitation.'})} {intl.formatMessage({id: 'select_team.no_team.description', defaultMessage: 'To join a team, ask a team admin for an invite, or create your own team. You may also want to check your email inbox for an invitation.'})}
</Text> </Text>
{/* {canCreateTeams && // TODO https://mattermost.atlassian.net/browse/MM-43622 {/* {canCreateTeams && // TODO https://mattermost.atlassian.net/browse/MM-43622 (Use Button component instead of touchable)
<TouchableWithFeedback <TouchableWithFeedback
style={styles.buttonStyle} style={styles.buttonStyle}
type={'opacity'} type={'opacity'}

View file

@ -1,17 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Button} from '@rneui/base';
import React, {type RefObject, useCallback, useRef} from 'react'; import React, {type RefObject, useCallback, useRef} from 'react';
import {useIntl} from 'react-intl'; import {defineMessages, useIntl} from 'react-intl';
import {Keyboard, View} from 'react-native'; import {Keyboard, View} from 'react-native';
import Button from '@components/button';
import FloatingTextInput, {type FloatingTextInputRef} from '@components/floating_text_input_label'; import FloatingTextInput, {type FloatingTextInputRef} from '@components/floating_text_input_label';
import FormattedText from '@components/formatted_text'; import FormattedText from '@components/formatted_text';
import Loading from '@components/loading';
import {useAvoidKeyboard} from '@hooks/device'; import {useAvoidKeyboard} from '@hooks/device';
import {t} from '@i18n';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography'; import {typography} from '@utils/typography';
@ -52,22 +49,25 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
marginTop: 8, marginTop: 8,
...typography('Body', 75, 'Regular'), ...typography('Body', 75, 'Regular'),
}, },
connectButton: { connectButtonContainer: {
width: '100%', width: '100%',
marginTop: 32, marginTop: 32,
marginLeft: 20, marginLeft: 20,
marginRight: 20, marginRight: 20,
}, },
connectingIndicator: {
marginRight: 10,
},
loadingContainerStyle: {
marginRight: 10,
padding: 0,
top: -2,
},
})); }));
const messages = defineMessages({
connect: {
id: 'mobile.components.select_server_view.connect',
defaultMessage: 'Connect',
},
connecting: {
id: 'mobile.components.select_server_view.connecting',
defaultMessage: 'Connecting',
},
});
const ServerForm = ({ const ServerForm = ({
autoFocus = false, autoFocus = false,
buttonDisabled, buttonDisabled,
@ -99,25 +99,6 @@ const ServerForm = ({
displayNameRef.current?.focus(); displayNameRef.current?.focus();
}, []); }, []);
const buttonType = buttonDisabled ? 'disabled' : 'default';
const styleButtonText = buttonTextStyle(theme, 'lg', 'primary', buttonType);
const styleButtonBackground = buttonBackgroundStyle(theme, 'lg', 'primary', buttonType);
let buttonID = t('mobile.components.select_server_view.connect');
let buttonText = 'Connect';
let buttonIcon;
if (connecting) {
buttonID = t('mobile.components.select_server_view.connecting');
buttonText = 'Connecting';
buttonIcon = (
<Loading
containerStyle={styles.loadingContainerStyle}
color={theme.buttonColor}
/>
);
}
const connectButtonTestId = buttonDisabled ? 'server_form.connect.button.disabled' : 'server_form.connect.button'; const connectButtonTestId = buttonDisabled ? 'server_form.connect.button.disabled' : 'server_form.connect.button';
return ( return (
@ -175,21 +156,17 @@ const ServerForm = ({
testID={'server_form.display_help'} testID={'server_form.display_help'}
/> />
} }
<Button <View style={styles.connectButtonContainer}>
containerStyle={styles.connectButton} <Button
disabled={buttonDisabled} disabled={buttonDisabled}
onPress={onConnect} onPress={onConnect}
testID={connectButtonTestId} testID={connectButtonTestId}
buttonStyle={styleButtonBackground} size='lg'
disabledStyle={styleButtonBackground} theme={theme}
> text={formatMessage(connecting ? messages.connecting : messages.connect)}
{buttonIcon} showLoader={connecting}
<FormattedText
defaultMessage={buttonText}
id={buttonID}
style={styleButtonText}
/> />
</Button> </View>
</View> </View>
); );
}; };

View file

@ -187,22 +187,13 @@ exports[`SendTestNotificationNotice should match snapshot 1`] = `
<Text <Text
numberOfLines={1} numberOfLines={1}
style={ style={
[ {
[ "color": "#ffffff",
[ "fontFamily": "OpenSans-SemiBold",
{ "fontSize": 14,
"fontFamily": "OpenSans-SemiBold", "fontWeight": "600",
"fontSize": 14, "lineHeight": 20,
"fontWeight": "600", }
"lineHeight": 20,
},
{
"color": "#ffffff",
},
],
undefined,
],
]
} }
> >
Send a test notification Send a test notification
@ -298,22 +289,13 @@ exports[`SendTestNotificationNotice should match snapshot 1`] = `
<Text <Text
numberOfLines={1} numberOfLines={1}
style={ style={
[ {
[ "color": "#1c58d9",
[ "fontFamily": "OpenSans-SemiBold",
{ "fontSize": 14,
"fontFamily": "OpenSans-SemiBold", "fontWeight": "600",
"fontSize": 14, "lineHeight": 20,
"fontWeight": "600", }
"lineHeight": 20,
},
{
"color": "#1c58d9",
},
],
undefined,
],
]
} }
> >
Troubleshooting docs Troubleshooting docs

View file

@ -1,12 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Button} from '@rneui/base';
import React from 'react'; import React from 'react';
import {useIntl} from 'react-intl';
import {Text, View} from 'react-native'; import {Text, View} from 'react-native';
import Button from '@components/button';
import FormattedText from '@components/formatted_text'; import FormattedText from '@components/formatted_text';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography'; import {typography} from '@utils/typography';
@ -18,7 +18,7 @@ interface AuthErrorProps {
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return { return {
button: { buttonContainer: {
marginTop: 25, marginTop: 25,
}, },
errorText: { errorText: {
@ -44,6 +44,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
}); });
const AuthError = ({error, retry, theme}: AuthErrorProps) => { const AuthError = ({error, retry, theme}: AuthErrorProps) => {
const intl = useIntl();
const style = getStyleSheet(theme); const style = getStyleSheet(theme);
return ( return (
@ -57,17 +58,15 @@ const AuthError = ({error, retry, theme}: AuthErrorProps) => {
<Text style={style.errorText}> <Text style={style.errorText}>
{`${error}.`} {`${error}.`}
</Text> </Text>
<Button <View style={style.buttonContainer}>
buttonStyle={[style.button, buttonBackgroundStyle(theme, 'lg', 'primary', 'default')]} <Button
testID='mobile.oauth.try_again' testID='mobile.oauth.try_again'
onPress={retry} onPress={retry}
> size='lg'
<FormattedText text={intl.formatMessage({id: 'mobile.oauth.try_again', defaultMessage: 'Try again'})}
id='mobile.oauth.try_again' theme={theme}
defaultMessage='Try again'
style={buttonTextStyle(theme, 'lg', 'primary', 'default')}
/> />
</Button> </View>
</View> </View>
); );
}; };

View file

@ -1,19 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useCallback, useMemo} from 'react'; import React, {useCallback} from 'react';
import {useIntl} from 'react-intl'; import {useIntl} from 'react-intl';
import {DeviceEventEmitter, StyleSheet, TouchableOpacity, View} from 'react-native'; import {DeviceEventEmitter, StyleSheet, View} from 'react-native';
import {createDirectChannel, switchToChannelById} from '@actions/remote/channel'; import {createDirectChannel, switchToChannelById} from '@actions/remote/channel';
import CompassIcon from '@components/compass_icon'; import Button from '@components/button';
import FormattedText from '@components/formatted_text';
import OptionBox, {OPTIONS_HEIGHT} from '@components/option_box'; import OptionBox, {OPTIONS_HEIGHT} from '@components/option_box';
import {Events, Screens} from '@constants'; import {Events, Screens} from '@constants';
import {useServerUrl} from '@context/server'; import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme'; import {useTheme} from '@context/theme';
import {dismissBottomSheet} from '@screens/navigation'; import {dismissBottomSheet} from '@screens/navigation';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import type {AvailableScreens} from '@typings/screens/navigation'; import type {AvailableScreens} from '@typings/screens/navigation';
@ -45,9 +43,7 @@ const styles = StyleSheet.create({
width: '100%', width: '100%',
}, },
singleContainer: { singleContainer: {
flexDirection: 'row',
marginBottom: 20, marginBottom: 20,
width: '100%',
}, },
}); });
@ -56,10 +52,6 @@ const UserProfileOptions = ({location, type, userId, username}: Props) => {
const theme = useTheme(); const theme = useTheme();
const serverUrl = useServerUrl(); const serverUrl = useServerUrl();
const buttonStyle = useMemo(() => {
return buttonBackgroundStyle(theme, 'lg', 'tertiary', 'default');
}, [theme]);
const mentionUser = useCallback(async () => { const mentionUser = useCallback(async () => {
await dismissBottomSheet(Screens.USER_PROFILE); await dismissBottomSheet(Screens.USER_PROFILE);
DeviceEventEmitter.emit(Events.SEND_TO_POST_DRAFT, {location, text: `@${username}`}); DeviceEventEmitter.emit(Events.SEND_TO_POST_DRAFT, {location, text: `@${username}`});
@ -80,7 +72,7 @@ const UserProfileOptions = ({location, type, userId, username}: Props) => {
iconName='send' iconName='send'
onPress={openChannel} onPress={openChannel}
testID='user_profile_options.send_message.option' testID='user_profile_options.send_message.option'
text={intl.formatMessage({id: 'channel_info.send_mesasge', defaultMessage: 'Send message'})} text={intl.formatMessage({id: 'channel_info.send_a_mesasge', defaultMessage: 'Send message'})}
/> />
<View style={styles.divider}/> <View style={styles.divider}/>
<OptionBox <OptionBox
@ -95,22 +87,15 @@ const UserProfileOptions = ({location, type, userId, username}: Props) => {
return ( return (
<View style={styles.singleContainer}> <View style={styles.singleContainer}>
<TouchableOpacity <Button
style={[buttonStyle, styles.singleButton]}
onPress={openChannel} onPress={openChannel}
testID='user_profile_options.send_message.option' testID='user_profile_options.send_message.option'
> size='lg'
<CompassIcon emphasis='tertiary'
color={theme.buttonBg} theme={theme}
name='send' text={intl.formatMessage({id: 'channel_info.send_mesasge', defaultMessage: 'Send message'})}
style={styles.icon} iconName='send'
/> />
<FormattedText
id='channel_info.send_a_mesasge'
defaultMessage='Send a message'
style={[buttonTextStyle(theme, 'lg', 'tertiary', 'default'), {marginLeft: 8}]}
/>
</TouchableOpacity>
</View> </View>
); );
}; };

View file

@ -158,11 +158,17 @@ const UserProfile = ({
'90%', '90%',
]; ];
}, [ }, [
headerText, showUserProfileOptions, showCustomStatus, headerText,
showNickname, showPosition, showLocalTime, showUserProfileOptions,
manageMode, bottom, showOptions, showCustomStatus,
canChangeMemberRoles, canManageAndRemoveMembers, showNickname,
enableCustomAttributes, showPosition,
showLocalTime,
manageMode,
bottom,
showOptions,
canChangeMemberRoles,
canManageAndRemoveMembers,
]); ]);
useEffect(() => { useEffect(() => {

View file

@ -464,6 +464,7 @@
"load_channels_error.title": "Couldn't load {teamDisplayName}", "load_channels_error.title": "Couldn't load {teamDisplayName}",
"load_teams_error.message": "There was a problem loading content for this server.", "load_teams_error.message": "There was a problem loading content for this server.",
"load_teams_error.title": "Couldn't load {serverName}", "load_teams_error.title": "Couldn't load {serverName}",
"loading_error.retry": "Retry",
"login_mfa.enterToken": "To complete the sign in process, please enter the code from your mobile device's authenticator app.", "login_mfa.enterToken": "To complete the sign in process, please enter the code from your mobile device's authenticator app.",
"login_mfa.token": "Enter MFA Token", "login_mfa.token": "Enter MFA Token",
"login_mfa.tokenReq": "Please enter an MFA token", "login_mfa.tokenReq": "Please enter an MFA token",