mattermost-mobile-upstream/app/components/button/index.tsx
Daniel Espino García 84ea188366
Mattermost Blocks (#9764)
* Mattermost Blocks

* Move block renderer to its own components folder

* Sync translation layer

* UX fixes

* Refactor button to use the button component

* Code review

* Fix CI

* Add animations to collapse blocks

* Add unit tests

* Add e2e tests and related fixes

* Address coderabbit comments

* Address UX comments

* Address feedback

* Address coderabbit comments

* Fix snapshots

* Sync with webapp translation

* Add node limit to button markdown node

* Address feedback

* i18n-extract

* Fix test

* Move image utils to the utils folder

* Remove unneeded style

* Revert build script change

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-06-29 18:42:16 +02:00

168 lines
4.5 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Button as ElementButton, type ButtonProps} from '@rneui/base';
import React, {useMemo, type ReactNode} from 'react';
import {type StyleProp, StyleSheet, Text, type TextStyle, View, type ViewStyle, type Insets} from 'react-native';
import CompassIcon, {type CompassIconName} from '@components/compass_icon';
import Loading from '@components/loading';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import ButtonMarkdownText from './button_markdown_text';
type Props = Omit<ButtonProps, 'size'> & {
theme: Theme;
backgroundStyle?: StyleProp<ViewStyle>;
buttonContainerStyle?: StyleProp<ViewStyle>;
textStyle?: StyleProp<TextStyle>;
size?: ButtonSize;
emphasis?: ButtonEmphasis;
testID?: string;
onPress?: () => void;
text: string;
iconComponent?: ReactNode;
disabled?: boolean;
hitSlop?: Insets;
isIconOnTheRight?: boolean;
iconName?: CompassIconName;
showLoader?: boolean;
isInverted?: boolean;
isDestructive?: boolean;
renderLabelAsMarkdown?: boolean;
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
gap: 7,
alignItems: 'center',
},
});
const iconSizePerSize: Record<ButtonSize, number> = {
xs: 14,
s: 14,
m: 18,
lg: 22,
};
const Button = ({
theme,
backgroundStyle,
buttonContainerStyle,
textStyle,
size = 'm',
emphasis,
onPress,
text,
testID,
iconName,
isIconOnTheRight = false,
iconComponent,
disabled,
hitSlop,
showLoader = false,
isInverted = false,
isDestructive = false,
renderLabelAsMarkdown = false,
}: Props) => {
let buttonType: ButtonType = 'default';
if (isDestructive) {
buttonType = 'destructive';
} else if (isInverted) {
buttonType = 'inverted';
}
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),
textStyle,
]), [theme, textStyle, size, emphasis, buttonType]);
const txtDisabledStyle = useMemo(() => StyleSheet.flatten([
buttonTextStyle(theme, size, emphasis, 'disabled'),
textStyle,
]), [theme, textStyle, size, emphasis]);
const txtStyleToUse = disabled ? txtDisabledStyle : txtStyle;
const loadingComponent = (
<Loading
color={txtStyleToUse.color}
testID={`${testID}-loader`}
/>
);
let icon: ReactNode;
if (iconComponent) {
icon = iconComponent;
} else if (iconName) {
// We wrap the icon in a view to avoid it to follow text layout
icon = (
<View>
<CompassIcon
name={iconName!}
size={iconSizePerSize[size]}
color={txtStyleToUse.color}
testID={`${testID}-icon`}
/>
</View>
);
}
const labelTestID = testID ? `${testID}-label` : undefined;
const label = renderLabelAsMarkdown ? (
<ButtonMarkdownText
value={text}
baseStyle={txtStyleToUse}
theme={theme}
testID={labelTestID}
/>
) : (
<Text
style={txtStyleToUse}
numberOfLines={1}
testID={labelTestID}
>
{text}
</Text>
);
return (
<ElementButton
buttonStyle={bgStyle}
containerStyle={buttonContainerStyle}
disabledStyle={bgDisabledStyle}
onPress={onPress}
testID={testID}
disabled={disabled}
hitSlop={hitSlop}
>
<View
style={styles.container}
testID={`${testID}-text-container`}
>
{showLoader && !isIconOnTheRight && loadingComponent}
{!isIconOnTheRight && icon}
{label}
{showLoader && isIconOnTheRight && loadingComponent}
{isIconOnTheRight && icon}
</View>
</ElementButton>
);
};
export default Button;