mattermost-mobile/app/screens/settings/display_theme/theme_tiles.tsx
Daniel Espino García a27c82da90
Refactor and cleanup of option item (#9067)
* Refactor and cleanup of option item

* Fix tests and minor cleanup

* Fix bug and address feedback
2025-08-19 14:01:56 +02:00

156 lines
4.8 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useMemo} from 'react';
import {Text, TouchableOpacity, useWindowDimensions, View} from 'react-native';
import CompassIcon from '@components/compass_icon';
import {Preferences} from '@constants';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
import ThemeThumbnail from './theme_thumbnail';
const TILE_PADDING = 16;
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
flexDirection: 'column',
gap: 8,
},
imageWrapper: {
position: 'relative',
alignItems: 'flex-start',
},
thumbnail: {
resizeMode: 'stretch',
},
check: {
position: 'absolute',
right: 5,
bottom: 5,
color: theme.sidebarTextActiveBorder,
},
label: {
color: theme.centerChannelColor,
...typography('Body', 200),
textTransform: 'capitalize',
},
tilesContainer: {
marginBottom: 30,
flexDirection: 'row',
flexWrap: 'wrap',
backgroundColor: theme.centerChannelBg,
gap: TILE_PADDING,
},
};
});
type ThemeTileProps = {
action: (v: string) => void;
actionValue: string;
activeTheme: Theme;
label: React.ReactElement;
selected: boolean;
testID?: string;
theme: Theme;
};
export const ThemeTile = ({
action,
actionValue,
activeTheme,
label,
selected,
testID,
theme,
}: ThemeTileProps) => {
const isTablet = useIsTablet();
const styles = getStyleSheet(activeTheme);
const {width: deviceWidth} = useWindowDimensions();
const layoutStyle = useMemo(() => {
const tilesPerLine = isTablet ? 4 : 2;
const fullWidth = deviceWidth - 40;
return {
container: {
width: (fullWidth / tilesPerLine) - TILE_PADDING,
},
thumbnail: {
width: (fullWidth / tilesPerLine) - (TILE_PADDING),
},
};
}, [isTablet, deviceWidth]);
const onPressHandler = useCallback(() => {
action(actionValue);
}, [action, actionValue]);
return (
<TouchableOpacity
onPress={onPressHandler}
style={[styles.container, layoutStyle.container]}
testID={testID}
>
<View style={[styles.imageWrapper, layoutStyle.thumbnail]}>
<ThemeThumbnail
borderColorBase={selected ? activeTheme.buttonBg : activeTheme.centerChannelBg}
borderColorMix={selected ? activeTheme.buttonBg : changeOpacity(activeTheme.centerChannelColor, 0.16)}
theme={theme}
width={layoutStyle.thumbnail.width}
/>
{selected && (
<CompassIcon
name='check-circle'
size={31.2}
style={styles.check}
testID={`${testID}.selected`}
/>
)}
</View>
{label}
</TouchableOpacity>
);
};
type ThemeTilesProps = {
allowedThemeKeys: string[];
onThemeChange: (v: string) => void;
selectedTheme: string | undefined;
}
export const ThemeTiles = ({allowedThemeKeys, onThemeChange, selectedTheme}: ThemeTilesProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
return (
<View style={styles.tilesContainer}>
{
allowedThemeKeys.map((themeKey: ThemeKey) => {
if (!Preferences.THEMES[themeKey] || !selectedTheme) {
return null;
}
return (
<ThemeTile
key={themeKey}
label={(
<Text style={styles.label}>
{themeKey}
</Text>
)}
action={onThemeChange}
actionValue={themeKey}
selected={selectedTheme?.toLowerCase() === themeKey.toLowerCase()}
testID={`theme_display_settings.${themeKey}.option`}
theme={Preferences.THEMES[themeKey]}
activeTheme={theme}
/>
);
})
}
</View>
);
};