Standardize tags and chips through the app (#8856)
* Standardize tags and chips through the app * Add missing texts * Fix tests and snapshots * Address feedback * Fix snapshots * Address UX feedback * Apply text styles to outer text component --------- Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
parent
9b1d8cccee
commit
cdd91dbbb8
39 changed files with 818 additions and 331 deletions
|
|
@ -108,4 +108,15 @@ describe('BaseChip', () => {
|
|||
expect(getByTestId('base_chip').props.entering).toBeUndefined();
|
||||
expect(getByTestId('base_chip').props.exiting).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not have a touchable if onPress is not provided', () => {
|
||||
const {queryByTestId} = renderWithIntlAndTheme(
|
||||
<BaseChip
|
||||
label='Test Label'
|
||||
testID='base_chip'
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('base_chip.chip_button')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,25 +1,27 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Platform, Text, TouchableOpacity, useWindowDimensions} from 'react-native';
|
||||
import React, {useMemo} from 'react';
|
||||
import {Platform, Text, TouchableOpacity} from 'react-native';
|
||||
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useWindowDimensions} from '@hooks/device';
|
||||
import {nonBreakingString} from '@utils/strings';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import {CHIP_BOTTOM_MARGIN, CHIP_HEIGHT} from './constants';
|
||||
import {CHIP_HEIGHT} from './constants';
|
||||
|
||||
type SelectedChipProps = {
|
||||
onPress: () => void;
|
||||
onPress?: () => void;
|
||||
testID?: string;
|
||||
showRemoveOption?: boolean;
|
||||
showAnimation?: boolean;
|
||||
label: string;
|
||||
prefix?: JSX.Element;
|
||||
maxWidth?: number;
|
||||
}
|
||||
|
||||
const FADE_DURATION = 100;
|
||||
|
|
@ -33,18 +35,20 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
borderRadius: 16,
|
||||
height: CHIP_HEIGHT,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
marginBottom: CHIP_BOTTOM_MARGIN,
|
||||
marginRight: 8,
|
||||
paddingHorizontal: 7,
|
||||
padding: 2,
|
||||
},
|
||||
text: {
|
||||
marginLeft: 8,
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 100, 'SemiBold'),
|
||||
...typography('Body', 100),
|
||||
},
|
||||
remove: {
|
||||
justifyContent: 'center',
|
||||
marginLeft: 7,
|
||||
marginLeft: 5,
|
||||
marginRight: 4,
|
||||
},
|
||||
chipContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -56,16 +60,25 @@ export default function BaseChip({
|
|||
showAnimation,
|
||||
label,
|
||||
prefix,
|
||||
maxWidth,
|
||||
}: SelectedChipProps) {
|
||||
const theme = useTheme();
|
||||
const style = getStyleFromTheme(theme);
|
||||
const dimensions = useWindowDimensions();
|
||||
const textStyle = useMemo(() => {
|
||||
// We set the max width to 70% of the screen width to make sure
|
||||
// text like names get ellipsized correctly.
|
||||
const textMaxWidth = maxWidth || dimensions.width * 0.70;
|
||||
const marginRight = showRemoveOption ? undefined : 7;
|
||||
const marginLeft = prefix ? 5 : 7;
|
||||
return [style.text, {maxWidth: textMaxWidth, marginRight, marginLeft}];
|
||||
}, [maxWidth, dimensions.width, showRemoveOption, style.text, prefix]);
|
||||
|
||||
const chipContent = (
|
||||
<>
|
||||
{prefix}
|
||||
<Text
|
||||
style={[style.text, {maxWidth: dimensions.width * 0.70}]}
|
||||
style={textStyle}
|
||||
numberOfLines={1}
|
||||
testID={`${testID}.display_name`}
|
||||
>
|
||||
|
|
@ -74,7 +87,7 @@ export default function BaseChip({
|
|||
</>
|
||||
);
|
||||
|
||||
let content;
|
||||
let content = chipContent;
|
||||
if (showRemoveOption) {
|
||||
content = (
|
||||
<>
|
||||
|
|
@ -86,16 +99,16 @@ export default function BaseChip({
|
|||
>
|
||||
<CompassIcon
|
||||
name='close-circle'
|
||||
size={18}
|
||||
size={16}
|
||||
color={changeOpacity(theme.centerChannelColor, 0.32)}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
} else if (onPress) {
|
||||
content = (
|
||||
<TouchableOpacity
|
||||
style={style.remove}
|
||||
style={style.chipContent}
|
||||
onPress={onPress}
|
||||
testID={`${testID}.chip_button`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export const CHIP_HEIGHT = 32;
|
||||
export const CHIP_BOTTOM_MARGIN = 8;
|
||||
export const CHIP_HEIGHT = 24;
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export default function UserChip({
|
|||
size={20}
|
||||
iconSize={20}
|
||||
testID={`${testID}.profile_picture`}
|
||||
showStatus={false}
|
||||
/>
|
||||
), [testID, user]);
|
||||
|
||||
|
|
|
|||
|
|
@ -224,6 +224,7 @@ const FloatingTextChipsInput = forwardRef<Ref, Props>(({
|
|||
res.push({
|
||||
borderWidth: focusedLabel ? BORDER_FOCUSED_WIDTH : BORDER_DEFAULT_WIDTH,
|
||||
minHeight: (CHIP_HEIGHT * 2.5) + ((focusedLabel ? BORDER_FOCUSED_WIDTH : BORDER_DEFAULT_WIDTH) * 2),
|
||||
gap: 8,
|
||||
});
|
||||
|
||||
if (focused) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
displayName: {
|
||||
color: theme.centerChannelColor,
|
||||
flexGrow: 1,
|
||||
marginRight: 5,
|
||||
...typography('Body', 200, 'SemiBold'),
|
||||
},
|
||||
displayNameCustomEmojiWidth: {
|
||||
|
|
|
|||
|
|
@ -59,24 +59,19 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
wrapper: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 5,
|
||||
},
|
||||
time: {
|
||||
color: theme.centerChannelColor,
|
||||
marginTop: 5,
|
||||
opacity: 0.5,
|
||||
...typography('Body', 75, 'Regular'),
|
||||
},
|
||||
visibleToYou: {
|
||||
color: theme.centerChannelColor,
|
||||
marginTop: 5,
|
||||
marginLeft: 5,
|
||||
opacity: 0.5,
|
||||
...typography('Body', 75, 'Regular'),
|
||||
},
|
||||
postPriority: {
|
||||
alignSelf: 'center',
|
||||
marginLeft: 6,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -142,11 +137,9 @@ const Header = (props: HeaderProps) => {
|
|||
/>
|
||||
)}
|
||||
{showPostPriority && post.metadata?.priority?.priority && (
|
||||
<View style={style.postPriority}>
|
||||
<PostPriorityLabel
|
||||
label={post.metadata.priority.priority}
|
||||
/>
|
||||
</View>
|
||||
<PostPriorityLabel
|
||||
label={post.metadata.priority.priority}
|
||||
/>
|
||||
)}
|
||||
{!isCRTEnabled && showReply && commentCount > 0 &&
|
||||
<HeaderReply
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {fetchAndSwitchToThread} from '@actions/remote/thread';
|
|||
import CompassIcon from '@components/compass_icon';
|
||||
import {SEARCH} from '@constants/screens';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
|
@ -24,13 +24,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
return {
|
||||
replyWrapper: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
replyIconContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
minWidth: 40,
|
||||
paddingTop: 2,
|
||||
flex: 1,
|
||||
},
|
||||
|
|
@ -47,10 +45,10 @@ const HeaderReply = ({commentCount, location, post, theme}: HeaderReplyProps) =>
|
|||
const style = getStyleSheet(theme);
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const onPress = useCallback(preventDoubleTap(() => {
|
||||
const onPress = usePreventDoubleTap(useCallback(() => {
|
||||
const rootId = post.rootId || post.id;
|
||||
fetchAndSwitchToThread(serverUrl, rootId);
|
||||
}), [serverUrl]);
|
||||
}, [post.id, post.rootId, serverUrl]));
|
||||
|
||||
return (
|
||||
<View
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import {defineMessage} from 'react-intl';
|
||||
|
||||
import Tag, {BotTag, GuestTag} from '@components/tag';
|
||||
import {t} from '@i18n';
|
||||
|
||||
type HeaderTagProps = {
|
||||
isAutomation?: boolean;
|
||||
|
|
@ -13,38 +12,25 @@ type HeaderTagProps = {
|
|||
showGuestTag?: boolean;
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
tag: {
|
||||
marginLeft: 0,
|
||||
marginRight: 5,
|
||||
marginBottom: 5,
|
||||
},
|
||||
});
|
||||
const autoResponderMessage = defineMessage({id: 'post_info.auto_responder', defaultMessage: 'Automatic Reply'});
|
||||
|
||||
const HeaderTag = ({
|
||||
isAutomation, isAutoResponder, showGuestTag,
|
||||
}: HeaderTagProps) => {
|
||||
if (isAutomation) {
|
||||
return (
|
||||
<BotTag
|
||||
style={style.tag}
|
||||
testID='post_header.bot.tag'
|
||||
/>
|
||||
<BotTag testID='post_header.bot.tag'/>
|
||||
);
|
||||
} else if (showGuestTag) {
|
||||
return (
|
||||
<GuestTag
|
||||
style={style.tag}
|
||||
testID='post_header.guest.tag'
|
||||
/>
|
||||
<GuestTag testID='post_header.guest.tag'/>
|
||||
);
|
||||
} else if (isAutoResponder) {
|
||||
return (
|
||||
<Tag
|
||||
id={t('post_info.auto_responder')}
|
||||
defaultMessage={'Automatic Reply'}
|
||||
style={style.tag}
|
||||
message={autoResponderMessage}
|
||||
testID='post_header.auto_responder.tag'
|
||||
uppercase={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
48
app/components/post_priority/post_priority_label.test.tsx
Normal file
48
app/components/post_priority/post_priority_label.test.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Tag from '@components/tag';
|
||||
import {PostPriorityType} from '@constants/post';
|
||||
import {renderWithIntlAndTheme} from '@test/intl-test-helper';
|
||||
|
||||
import PostPriorityLabel from './post_priority_label';
|
||||
|
||||
jest.mock('@components/tag', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
jest.mocked(Tag).mockImplementation((props) => React.createElement('Tag', {...props}));
|
||||
|
||||
describe('PostPriorityLabel', () => {
|
||||
it('should return null for standard priority', () => {
|
||||
const {toJSON} = renderWithIntlAndTheme(
|
||||
<PostPriorityLabel label={PostPriorityType.STANDARD}/>);
|
||||
expect(toJSON()).toBeNull();
|
||||
});
|
||||
|
||||
it('should render urgent priority label correctly', () => {
|
||||
const {getByTestId} = renderWithIntlAndTheme(
|
||||
<PostPriorityLabel label={PostPriorityType.URGENT}/>);
|
||||
|
||||
const tag = getByTestId('urgent_post_priority_label');
|
||||
expect(tag.props.message.id).toBe('post_priority.label.urgent');
|
||||
expect(tag.props.icon).toBe('alert-outline');
|
||||
expect(tag.props.type).toBe('danger');
|
||||
expect(tag.props.size).toBe('xs');
|
||||
expect(tag.props.uppercase).toBe(true);
|
||||
});
|
||||
|
||||
it('should render important priority label correctly', () => {
|
||||
const {getByTestId} = renderWithIntlAndTheme(
|
||||
<PostPriorityLabel label={PostPriorityType.IMPORTANT}/>);
|
||||
|
||||
const tag = getByTestId('important_post_priority_label');
|
||||
expect(tag.props.message.id).toBe('post_priority.label.important');
|
||||
expect(tag.props.icon).toBe('alert-circle-outline');
|
||||
expect(tag.props.type).toBe('info');
|
||||
expect(tag.props.size).toBe('xs');
|
||||
expect(tag.props.uppercase).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -2,68 +2,42 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {type StyleProp, StyleSheet, Text, View, type ViewStyle} from 'react-native';
|
||||
import {defineMessages} from 'react-intl';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {PostPriorityColors, PostPriorityType} from '@constants/post';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
alignSelf: 'flex-start',
|
||||
flexDirection: 'row',
|
||||
borderRadius: 4,
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
urgent: {
|
||||
backgroundColor: PostPriorityColors.URGENT,
|
||||
},
|
||||
important: {
|
||||
backgroundColor: PostPriorityColors.IMPORTANT,
|
||||
},
|
||||
label: {
|
||||
color: '#fff',
|
||||
...typography('Body', 25, 'SemiBold'),
|
||||
},
|
||||
icon: {
|
||||
color: '#fff',
|
||||
fontSize: 12,
|
||||
marginRight: 4,
|
||||
},
|
||||
});
|
||||
import Tag from '@components/tag';
|
||||
import {PostPriorityType} from '@constants/post';
|
||||
|
||||
type Props = {
|
||||
label: PostPriority['priority'];
|
||||
};
|
||||
|
||||
const PostPriorityLabel = ({label}: Props) => {
|
||||
const intl = useIntl();
|
||||
const messages = defineMessages({
|
||||
urgent: {
|
||||
id: 'post_priority.label.urgent',
|
||||
defaultMessage: 'URGENT',
|
||||
},
|
||||
important: {
|
||||
id: 'post_priority.label.important',
|
||||
defaultMessage: 'IMPORTANT',
|
||||
},
|
||||
});
|
||||
|
||||
const containerStyle: StyleProp<ViewStyle> = [style.container];
|
||||
let iconName = '';
|
||||
let labelText = '';
|
||||
if (label === PostPriorityType.URGENT) {
|
||||
containerStyle.push(style.urgent);
|
||||
iconName = 'alert-outline';
|
||||
labelText = intl.formatMessage({id: 'post_priority.label.urgent', defaultMessage: 'URGENT'});
|
||||
} else if (label === PostPriorityType.IMPORTANT) {
|
||||
containerStyle.push(style.important);
|
||||
iconName = 'alert-circle-outline';
|
||||
labelText = intl.formatMessage({id: 'post_priority.label.important', defaultMessage: 'IMPORTANT'});
|
||||
const PostPriorityLabel = ({label}: Props) => {
|
||||
if (label === PostPriorityType.STANDARD) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isUrgent = label === PostPriorityType.URGENT; // else it is important
|
||||
|
||||
return (
|
||||
<View
|
||||
<Tag
|
||||
message={isUrgent ? messages.urgent : messages.important}
|
||||
icon={isUrgent ? 'alert-outline' : 'alert-circle-outline'}
|
||||
type={isUrgent ? 'danger' : 'info'}
|
||||
size='xs'
|
||||
testID={`${label}_post_priority_label`}
|
||||
style={containerStyle}
|
||||
>
|
||||
<CompassIcon
|
||||
name={iconName}
|
||||
style={style.icon}
|
||||
/>
|
||||
<Text style={style.label}>{labelText}</Text>
|
||||
</View>
|
||||
uppercase={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ exports[`Section notice match snapshot 1`] = `
|
|||
style={
|
||||
[
|
||||
{
|
||||
"borderRadius": 4,
|
||||
"borderStyle": "solid",
|
||||
"borderWidth": 1,
|
||||
},
|
||||
|
|
@ -13,7 +12,9 @@ exports[`Section notice match snapshot 1`] = `
|
|||
"backgroundColor": "rgba(93,137,234,0.08)",
|
||||
"borderColor": "rgba(93,137,234,0.16)",
|
||||
},
|
||||
undefined,
|
||||
{
|
||||
"borderRadius": 4,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="sectionNoticeContainer"
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useMemo} from 'react';
|
||||
import {Pressable, Text, View, type StyleProp, type ViewStyle} from 'react-native';
|
||||
import {Pressable, Text, View} from 'react-native';
|
||||
|
||||
import Tag from '@components/tag';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {getMarkdownBlockStyles, getMarkdownTextStyles} from '@utils/markdown';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
|
@ -27,10 +28,8 @@ type Props = {
|
|||
onDismissClick?: () => void;
|
||||
location: AvailableScreens;
|
||||
tags?: string[];
|
||||
tagsVariant?: 'default' | 'subtle';
|
||||
testID?: string;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
iconSize?: number;
|
||||
squareCorners?: boolean;
|
||||
}
|
||||
|
||||
const iconByType = {
|
||||
|
|
@ -47,6 +46,8 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
container: {
|
||||
borderWidth: 1,
|
||||
borderStyle: 'solid',
|
||||
},
|
||||
roundCorners: {
|
||||
borderRadius: 4,
|
||||
},
|
||||
content: {
|
||||
|
|
@ -150,6 +151,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
tagsContainer: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 4,
|
||||
},
|
||||
|
||||
};
|
||||
|
|
@ -166,10 +168,8 @@ const SectionNotice = ({
|
|||
type = 'info',
|
||||
location,
|
||||
tags,
|
||||
tagsVariant = 'default',
|
||||
squareCorners,
|
||||
testID,
|
||||
containerStyle,
|
||||
iconSize = 20,
|
||||
}: Props) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleFromTheme(theme);
|
||||
|
|
@ -182,9 +182,9 @@ const SectionNotice = ({
|
|||
const combinedContainerStyle = useMemo(() => [
|
||||
styles.container,
|
||||
styles[`${type}Container`],
|
||||
containerStyle,
|
||||
], [type, containerStyle]);
|
||||
const iconStyle = useMemo(() => styles[`${type}Icon`], [type]);
|
||||
!squareCorners && styles.roundCorners,
|
||||
], [styles, type, squareCorners]);
|
||||
const iconStyle = useMemo(() => styles[`${type}Icon`], [styles, type]);
|
||||
return (
|
||||
<View
|
||||
style={combinedContainerStyle}
|
||||
|
|
@ -195,7 +195,7 @@ const SectionNotice = ({
|
|||
<CompassIcon
|
||||
name={icon}
|
||||
style={iconStyle}
|
||||
size={iconSize}
|
||||
size={20}
|
||||
testID='sectionNoticeHeaderIcon'
|
||||
/>
|
||||
)}
|
||||
|
|
@ -206,6 +206,8 @@ const SectionNotice = ({
|
|||
theme={theme}
|
||||
location={location}
|
||||
baseTextStyle={styles.baseText}
|
||||
textStyles={getMarkdownTextStyles(theme)}
|
||||
blockStyles={getMarkdownBlockStyles(theme)}
|
||||
value={text}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -214,9 +216,8 @@ const SectionNotice = ({
|
|||
{tags.map((tag) => (
|
||||
<Tag
|
||||
key={tag}
|
||||
id={`tag.${tag}`}
|
||||
defaultMessage={tag}
|
||||
variant={tagsVariant}
|
||||
testID={`tag.${tag}`}
|
||||
message={tag}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import Animated, {useAnimatedStyle, useDerivedValue, useSharedValue, withTiming}
|
|||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import Button from '@components/button';
|
||||
import {CHIP_BOTTOM_MARGIN, CHIP_HEIGHT} from '@components/chips/constants';
|
||||
import {CHIP_HEIGHT} from '@components/chips/constants';
|
||||
import SelectedUserChip from '@components/chips/selected_user_chip';
|
||||
import Toast from '@components/toast';
|
||||
import {useTheme} from '@context/theme';
|
||||
|
|
@ -83,7 +83,8 @@ type Props = {
|
|||
}
|
||||
|
||||
const BUTTON_HEIGHT = 48;
|
||||
const CHIP_HEIGHT_WITH_MARGIN = CHIP_HEIGHT + CHIP_BOTTOM_MARGIN;
|
||||
const CHIP_GAP = 8;
|
||||
const CHIP_HEIGHT_WITH_MARGIN = CHIP_HEIGHT + CHIP_GAP;
|
||||
const EXPOSED_CHIP_HEIGHT = 0.33 * CHIP_HEIGHT;
|
||||
const MAX_CHIP_ROWS = 2;
|
||||
const SCROLL_MARGIN_TOP = 20;
|
||||
|
|
@ -125,6 +126,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
flexDirection: 'row',
|
||||
flexGrow: 1,
|
||||
flexWrap: 'wrap',
|
||||
gap: CHIP_GAP,
|
||||
},
|
||||
message: {
|
||||
color: theme.centerChannelBg,
|
||||
|
|
|
|||
49
app/components/tag/base_tag.test.tsx
Normal file
49
app/components/tag/base_tag.test.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithIntlAndTheme} from '@test/intl-test-helper';
|
||||
|
||||
import Tag from './base_tag';
|
||||
|
||||
describe('Tag', () => {
|
||||
const defaultProps = {
|
||||
message: 'Test Tag',
|
||||
};
|
||||
|
||||
it('should render correctly', () => {
|
||||
const {getByText} = renderWithIntlAndTheme(<Tag {...defaultProps}/>);
|
||||
expect(getByText('Test Tag')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render icon correctly', () => {
|
||||
const {getByTestId, rerender, queryByTestId} = renderWithIntlAndTheme(
|
||||
<Tag
|
||||
{...defaultProps}
|
||||
icon='check'
|
||||
testID='test-tag'
|
||||
/>,
|
||||
);
|
||||
expect(getByTestId('test-tag.icon')).toBeDefined();
|
||||
|
||||
rerender(
|
||||
<Tag
|
||||
{...defaultProps}
|
||||
testID='test-tag'
|
||||
/>,
|
||||
);
|
||||
expect(queryByTestId('test-tag.icon')).toBeNull();
|
||||
});
|
||||
|
||||
it('should render uppercase correctly', () => {
|
||||
const {getByText} = renderWithIntlAndTheme(
|
||||
<Tag
|
||||
{...defaultProps}
|
||||
uppercase={true}
|
||||
/>,
|
||||
);
|
||||
expect(getByText('Test Tag')).toBeDefined();
|
||||
expect(getByText('Test Tag')).toHaveStyle({textTransform: 'uppercase'});
|
||||
});
|
||||
});
|
||||
154
app/components/tag/base_tag.tsx
Normal file
154
app/components/tag/base_tag.tsx
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useMemo} from 'react';
|
||||
import {Text, View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography, type FontSizes} from '@utils/typography';
|
||||
|
||||
import type {MessageDescriptor} from 'react-intl';
|
||||
|
||||
type TagProps = {
|
||||
message: MessageDescriptor | string;
|
||||
icon?: string;
|
||||
type?: 'general' | 'info' | 'danger' | 'success' | 'warning' | 'infoDim';
|
||||
size?: 'xs' | 's' | 'm';
|
||||
uppercase?: boolean;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
borderRadius: 4,
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
generalContainer: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
},
|
||||
generalText: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
infoContainer: {
|
||||
backgroundColor: theme.buttonBg,
|
||||
},
|
||||
infoText: {
|
||||
color: theme.buttonColor,
|
||||
},
|
||||
dangerContainer: {
|
||||
backgroundColor: theme.dndIndicator,
|
||||
},
|
||||
dangerText: {
|
||||
color: theme.buttonColor,
|
||||
},
|
||||
successContainer: {
|
||||
backgroundColor: theme.onlineIndicator,
|
||||
},
|
||||
successText: {
|
||||
color: theme.buttonColor,
|
||||
},
|
||||
warningContainer: {
|
||||
backgroundColor: theme.awayIndicator,
|
||||
},
|
||||
warningText: {
|
||||
color: theme.buttonColor,
|
||||
},
|
||||
infoDimContainer: {
|
||||
backgroundColor: changeOpacity(theme.buttonBg, 0.12),
|
||||
},
|
||||
infoDimText: {
|
||||
color: theme.buttonBg,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const textTypographyPerSize: Record<Required<TagProps>['size'], FontSizes> = {
|
||||
xs: 25,
|
||||
s: 75,
|
||||
m: 100,
|
||||
};
|
||||
|
||||
const iconSizePerSize: Record<Required<TagProps>['size'], number> = {
|
||||
xs: 10,
|
||||
s: 12,
|
||||
m: 14,
|
||||
};
|
||||
|
||||
const Tag = ({
|
||||
message,
|
||||
icon,
|
||||
type = 'general',
|
||||
size = 's',
|
||||
uppercase = false,
|
||||
testID,
|
||||
}: TagProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleFromTheme(theme);
|
||||
|
||||
const textStyle = useMemo(() => {
|
||||
const sizeRelated = typography('Heading', textTypographyPerSize[size]);
|
||||
const colorRelated = styles[`${type}Text`];
|
||||
const uppercaseRelated = uppercase ? {textTransform: 'uppercase' as const} : {};
|
||||
return [sizeRelated, colorRelated, uppercaseRelated];
|
||||
}, [size, styles, type, uppercase]);
|
||||
|
||||
const containerStyle = useMemo(() => {
|
||||
const colorRelated = styles[`${type}Container`];
|
||||
return [styles.container, colorRelated];
|
||||
}, [styles, type]);
|
||||
|
||||
let iconComponent;
|
||||
if (icon) {
|
||||
iconComponent = (
|
||||
<CompassIcon
|
||||
size={iconSizePerSize[size]}
|
||||
name={icon}
|
||||
color={styles[`${type}Text`].color}
|
||||
testID={testID ? `${testID}.icon` : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
let textComponent;
|
||||
if (typeof message === 'string') {
|
||||
textComponent = (
|
||||
<Text
|
||||
style={textStyle}
|
||||
testID={testID}
|
||||
>
|
||||
{message}
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
textComponent = (
|
||||
<FormattedText
|
||||
{...message}
|
||||
style={textStyle}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={containerStyle}>
|
||||
{/* We wrap the icon and text in a Text component to avoid
|
||||
the ellipsis to go out of the box on iOS */}
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={textStyle}
|
||||
>
|
||||
{iconComponent}
|
||||
{' '}
|
||||
{textComponent}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tag;
|
||||
46
app/components/tag/bot_tag.test.tsx
Normal file
46
app/components/tag/bot_tag.test.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithIntlAndTheme} from '@test/intl-test-helper';
|
||||
|
||||
import Tag from './base_tag';
|
||||
import BotTag from './bot_tag';
|
||||
|
||||
jest.mock('./base_tag', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
jest.mocked(Tag).mockImplementation((props) => React.createElement('Tag', {...props}));
|
||||
|
||||
describe('BotTag', () => {
|
||||
it('should render with the correct props', () => {
|
||||
const {getByTestId} = renderWithIntlAndTheme(
|
||||
<BotTag
|
||||
testID='bot-tag'
|
||||
size='m'
|
||||
/>,
|
||||
);
|
||||
|
||||
const tag = getByTestId('bot-tag');
|
||||
expect(tag.props.message.id).toBe('post_info.bot');
|
||||
expect(tag.props.message.defaultMessage).toBe('Bot');
|
||||
expect(tag.props.uppercase).toBe(true);
|
||||
expect(tag.props.size).toBe('m');
|
||||
});
|
||||
|
||||
it('should render with default props', () => {
|
||||
const {getByTestId} = renderWithIntlAndTheme(
|
||||
<BotTag
|
||||
testID='bot-tag'
|
||||
/>,
|
||||
);
|
||||
|
||||
const tag = getByTestId('bot-tag');
|
||||
expect(tag.props.message.id).toBe('post_info.bot');
|
||||
expect(tag.props.message.defaultMessage).toBe('Bot');
|
||||
expect(tag.props.uppercase).toBe(true);
|
||||
expect(tag.props.size).toBeUndefined();
|
||||
});
|
||||
});
|
||||
30
app/components/tag/bot_tag.tsx
Normal file
30
app/components/tag/bot_tag.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {type ComponentProps} from 'react';
|
||||
import {defineMessage} from 'react-intl';
|
||||
|
||||
import Tag from './base_tag';
|
||||
|
||||
const botMessage = defineMessage({
|
||||
id: 'post_info.bot',
|
||||
defaultMessage: 'Bot',
|
||||
});
|
||||
|
||||
type BotTagProps = Omit<ComponentProps<typeof Tag>, 'message' | 'icon' | 'type'>;
|
||||
|
||||
const BotTag = ({
|
||||
size,
|
||||
testID,
|
||||
}: BotTagProps) => {
|
||||
return (
|
||||
<Tag
|
||||
message={botMessage}
|
||||
testID={testID}
|
||||
uppercase={true}
|
||||
size={size}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BotTag;
|
||||
46
app/components/tag/guest_tag.test.tsx
Normal file
46
app/components/tag/guest_tag.test.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithIntlAndTheme} from '@test/intl-test-helper';
|
||||
|
||||
import Tag from './base_tag';
|
||||
import GuestTag from './guest_tag';
|
||||
|
||||
jest.mock('./base_tag', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
jest.mocked(Tag).mockImplementation((props) => React.createElement('Tag', {...props}));
|
||||
|
||||
describe('GuestTag', () => {
|
||||
it('should render with the correct props', () => {
|
||||
const {getByTestId} = renderWithIntlAndTheme(
|
||||
<GuestTag
|
||||
testID='guest-tag'
|
||||
size='m'
|
||||
/>,
|
||||
);
|
||||
|
||||
const tag = getByTestId('guest-tag');
|
||||
expect(tag.props.message.id).toBe('post_info.guest');
|
||||
expect(tag.props.message.defaultMessage).toBe('Guest');
|
||||
expect(tag.props.uppercase).toBe(true);
|
||||
expect(tag.props.size).toBe('m');
|
||||
});
|
||||
|
||||
it('should render with default props', () => {
|
||||
const {getByTestId} = renderWithIntlAndTheme(
|
||||
<GuestTag
|
||||
testID='guest-tag'
|
||||
/>,
|
||||
);
|
||||
|
||||
const tag = getByTestId('guest-tag');
|
||||
expect(tag.props.message.id).toBe('post_info.guest');
|
||||
expect(tag.props.message.defaultMessage).toBe('Guest');
|
||||
expect(tag.props.uppercase).toBe(true);
|
||||
expect(tag.props.size).toBeUndefined();
|
||||
});
|
||||
});
|
||||
30
app/components/tag/guest_tag.tsx
Normal file
30
app/components/tag/guest_tag.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {type ComponentProps} from 'react';
|
||||
import {defineMessage} from 'react-intl';
|
||||
|
||||
import Tag from './base_tag';
|
||||
|
||||
const botMessage = defineMessage({
|
||||
id: 'post_info.guest',
|
||||
defaultMessage: 'Guest',
|
||||
});
|
||||
|
||||
type GuestTagProps = Omit<ComponentProps<typeof Tag>, 'message' | 'icon' | 'type'>;
|
||||
|
||||
const GuestTag = ({
|
||||
testID,
|
||||
size,
|
||||
}: GuestTagProps) => {
|
||||
return (
|
||||
<Tag
|
||||
message={botMessage}
|
||||
testID={testID}
|
||||
uppercase={true}
|
||||
size={size}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default GuestTag;
|
||||
|
|
@ -1,116 +1,12 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {type StyleProp, type TextStyle, View, type ViewStyle} from 'react-native';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
type TagProps = {
|
||||
id: string;
|
||||
defaultMessage: string;
|
||||
inTitle?: boolean;
|
||||
show?: boolean;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
testID?: string;
|
||||
textStyle?: StyleProp<TextStyle>;
|
||||
variant?: 'default' | 'subtle';
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
alignSelf: 'center',
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
borderRadius: 4,
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
text: {
|
||||
color: theme.centerChannelColor,
|
||||
fontFamily: 'OpenSans-SemiBold',
|
||||
fontSize: 10,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
title: {
|
||||
backgroundColor: changeOpacity(theme.sidebarHeaderTextColor, 0.15),
|
||||
color: changeOpacity(theme.sidebarHeaderTextColor, 0.6),
|
||||
},
|
||||
|
||||
// Variant styles
|
||||
subtleContainer: {
|
||||
borderRadius: 6,
|
||||
paddingVertical: 4,
|
||||
paddingHorizontal: 8,
|
||||
marginRight: 8,
|
||||
marginBottom: 8,
|
||||
},
|
||||
subtleText: {
|
||||
fontSize: 12,
|
||||
textTransform: 'none',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export function BotTag(props: Omit<TagProps, 'id' | 'defaultMessage'>) {
|
||||
const id = t('post_info.bot');
|
||||
const defaultMessage = 'Bot';
|
||||
|
||||
return (
|
||||
<Tag
|
||||
{...props}
|
||||
id={id}
|
||||
defaultMessage={defaultMessage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function GuestTag(props: Omit<TagProps, 'id' | 'defaultMessage'>) {
|
||||
const id = t('post_info.guest');
|
||||
const defaultMessage = 'Guest';
|
||||
|
||||
return (
|
||||
<Tag
|
||||
{...props}
|
||||
id={id}
|
||||
defaultMessage={defaultMessage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const Tag = ({id, defaultMessage, inTitle, show = true, style, testID, textStyle, variant = 'default'}: TagProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const styles = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
variant === 'subtle' && styles.subtleContainer,
|
||||
style,
|
||||
]}
|
||||
>
|
||||
<FormattedText
|
||||
id={id}
|
||||
defaultMessage={defaultMessage}
|
||||
style={[
|
||||
styles.text,
|
||||
inTitle ? styles.title : null,
|
||||
variant === 'subtle' && styles.subtleText,
|
||||
textStyle,
|
||||
]}
|
||||
testID={testID}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
import Tag from './base_tag';
|
||||
import BotTag from './bot_tag';
|
||||
import GuestTag from './guest_tag';
|
||||
|
||||
export default Tag;
|
||||
export {
|
||||
GuestTag,
|
||||
BotTag,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -76,16 +76,12 @@ const nonThemedStyles = StyleSheet.create({
|
|||
rowInfoContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
icon: {
|
||||
marginLeft: 4,
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
},
|
||||
profile: {
|
||||
marginRight: 12,
|
||||
},
|
||||
tag: {
|
||||
marginLeft: 6,
|
||||
},
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
|
|
@ -201,29 +197,19 @@ const UserItem = ({
|
|||
)}
|
||||
</Text>
|
||||
{showBadges && bot && (
|
||||
<BotTag
|
||||
testID={`${userItemTestId}.bot.tag`}
|
||||
style={nonThemedStyles.tag}
|
||||
/>
|
||||
<BotTag testID={`${userItemTestId}.bot.tag`}/>
|
||||
)}
|
||||
{showBadges && guest && !hideGuestTags && (
|
||||
<GuestTag
|
||||
testID={`${userItemTestId}.guest.tag`}
|
||||
style={nonThemedStyles.tag}
|
||||
/>
|
||||
<GuestTag testID={`${userItemTestId}.guest.tag`}/>
|
||||
)}
|
||||
{Boolean(isCustomStatusEnabled && !bot && customStatus?.emoji && !customStatusExpired) && (
|
||||
<CustomStatusEmoji
|
||||
customStatus={customStatus!}
|
||||
style={nonThemedStyles.icon}
|
||||
/>
|
||||
<CustomStatusEmoji customStatus={customStatus!}/>
|
||||
)}
|
||||
{shared && (
|
||||
<CompassIcon
|
||||
name={'circle-multiple-outline'}
|
||||
size={16}
|
||||
color={theme.centerChannelColor}
|
||||
style={nonThemedStyles.icon}
|
||||
/>
|
||||
)}
|
||||
<View style={nonThemedStyles.flex}/>
|
||||
|
|
|
|||
|
|
@ -814,8 +814,10 @@ exports[`components/channel_list_row should show results and tutorial 1`] = `
|
|||
<View
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"gap": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
|
@ -1134,8 +1136,10 @@ exports[`components/channel_list_row should show results no tutorial 1`] = `
|
|||
<View
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"gap": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
|
@ -1492,8 +1496,10 @@ exports[`components/channel_list_row should show results no tutorial 2 users 1`]
|
|||
<View
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"gap": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
|
@ -1711,8 +1717,10 @@ exports[`components/channel_list_row should show results no tutorial 2 users 1`]
|
|||
<View
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"gap": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {defineMessage, useIntl} from 'react-intl';
|
||||
import {Platform, Text, TouchableOpacity, View} from 'react-native';
|
||||
|
||||
import {useCurrentCall} from '@calls/state';
|
||||
|
|
@ -65,9 +65,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
unmutedIcon: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
},
|
||||
hostTag: {
|
||||
paddingVertical: 4,
|
||||
},
|
||||
raiseHandIcon: {
|
||||
color: theme.awayIndicator,
|
||||
},
|
||||
|
|
@ -76,6 +73,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
const hostMessage = defineMessage({
|
||||
id: 'mobile.calls_host',
|
||||
defaultMessage: 'host',
|
||||
});
|
||||
|
||||
export const Participant = ({sess, teammateNameDisplay, onPress}: Props) => {
|
||||
const intl = useIntl();
|
||||
const currentCall = useCurrentCall();
|
||||
|
|
@ -122,9 +124,8 @@ export const Participant = ({sess, teammateNameDisplay, onPress}: Props) => {
|
|||
}
|
||||
{sess.userId === currentCall.hostId &&
|
||||
<Tag
|
||||
id={'mobile.calls_host'}
|
||||
defaultMessage={'host'}
|
||||
style={styles.hostTag}
|
||||
message={hostMessage}
|
||||
uppercase={true}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -34,13 +34,10 @@ type Props = {
|
|||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
botContainer: {
|
||||
alignSelf: 'flex-end',
|
||||
bottom: 7.5,
|
||||
height: 20,
|
||||
marginBottom: 0,
|
||||
marginLeft: 4,
|
||||
paddingVertical: 0,
|
||||
displayNameContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
},
|
||||
botText: {
|
||||
fontSize: 14,
|
||||
|
|
@ -164,7 +161,7 @@ const DirectChannel = ({
|
|||
{getGMIntroMessageSpecificPart(userNotifyProps, channelNotifyProps, styles.boldText)}
|
||||
</Text>
|
||||
);
|
||||
}, [channel.displayName, theme, channelNotifyProps, userNotifyProps]);
|
||||
}, [channel.type, channel.displayName, hasGMasDMFeature, styles.message, styles.boldText, userNotifyProps, channelNotifyProps]);
|
||||
|
||||
const profiles = useMemo(() => {
|
||||
if (channel.type === General.DM_CHANNEL) {
|
||||
|
|
@ -195,14 +192,14 @@ const DirectChannel = ({
|
|||
userIds={channelMembers.map((cm) => cm.userId)}
|
||||
/>
|
||||
);
|
||||
}, [members, theme]);
|
||||
}, [channel.id, channel.name, channel.type, currentUserId, members, theme]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.profilesContainer}>
|
||||
{profiles}
|
||||
</View>
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<View style={styles.displayNameContainer}>
|
||||
<Text
|
||||
style={[styles.title, channel.type === General.GM_CHANNEL ? styles.titleGroup : undefined]}
|
||||
testID='channel_post_list.intro.display_name'
|
||||
|
|
@ -210,10 +207,7 @@ const DirectChannel = ({
|
|||
{channel.displayName}
|
||||
</Text>
|
||||
{isBot &&
|
||||
<BotTag
|
||||
style={styles.botContainer}
|
||||
textStyle={styles.botText}
|
||||
/>
|
||||
<BotTag size={'m'}/>
|
||||
}
|
||||
</View>
|
||||
{message}
|
||||
|
|
|
|||
|
|
@ -288,9 +288,7 @@ export default function ChannelAddMembers({
|
|||
onDismissClick={handleDismissBanner}
|
||||
location={Screens.CHANNEL_ADD_MEMBERS}
|
||||
testID={`${TEST_ID}.notice`}
|
||||
containerStyle={style.flatBottomBanner}
|
||||
iconSize={24}
|
||||
tagsVariant='subtle'
|
||||
squareCorners={true}
|
||||
/>
|
||||
)}
|
||||
<View style={style.searchBar}>
|
||||
|
|
|
|||
|
|
@ -25,18 +25,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
},
|
||||
displayName: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
alignItems: 'center',
|
||||
},
|
||||
position: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.72),
|
||||
...typography('Body', 200),
|
||||
},
|
||||
tagContainer: {
|
||||
marginLeft: 12,
|
||||
},
|
||||
tag: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 100, 'SemiBold'),
|
||||
},
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
marginLeft: 16,
|
||||
|
|
@ -81,15 +76,13 @@ const DirectMessage = ({
|
|||
</Text>
|
||||
{user?.isGuest && !hideGuestTags &&
|
||||
<GuestTag
|
||||
textStyle={styles.tag}
|
||||
style={styles.tagContainer}
|
||||
size={'m'}
|
||||
testID={`${directMessageUserTestId}.guest.tag`}
|
||||
/>
|
||||
}
|
||||
{user?.isBot &&
|
||||
<BotTag
|
||||
textStyle={styles.tag}
|
||||
style={styles.tagContainer}
|
||||
size={'m'}
|
||||
testID={`${directMessageUserTestId}.bot.tag`}
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ const propPossibilities = {};
|
|||
|
||||
const buttonSizeValues = ['xs', 's', 'm', 'lg'];
|
||||
const buttonEmphasisValues = ['primary', 'secondary', 'tertiary', 'link'];
|
||||
const buttonTypeValues = ['default', 'destructive', 'inverted', 'disabled'];
|
||||
const buttonStateValues = ['default', 'hover', 'active', 'focus'];
|
||||
|
||||
const onPress = () => Alert.alert('Button pressed!');
|
||||
|
||||
|
|
@ -25,28 +23,32 @@ const ButtonComponentLibrary = () => {
|
|||
const [disabled, disabledSelector] = useBooleanProp('disabled', false);
|
||||
const [buttonSize, buttonSizePosibilities, buttonSizeSelector] = useDropdownProp('size', 'm', buttonSizeValues, true);
|
||||
const [buttonEmphasis, buttonEmphasisPosibilities, buttonEmphasisSelector] = useDropdownProp('emphasis', 'primary', buttonEmphasisValues, true);
|
||||
const [buttonType, buttonTypePosibilities, buttonTypeSelector] = useDropdownProp('buttonType', 'default', buttonTypeValues, true);
|
||||
const [buttonState, buttonStatePosibilities, buttonStateSelector] = useDropdownProp('buttonState', 'default', buttonStateValues, true);
|
||||
const [iconName, iconNameSelector] = useStringProp('iconName', '', true);
|
||||
const [isIconOnTheRight, isIconOnTheRightSelector] = useBooleanProp('isIconOnTheRight', false);
|
||||
const [showLoader, showLoaderSelector] = useBooleanProp('showLoader', false);
|
||||
const [isInverted, isInvertedSelector] = useBooleanProp('isInverted', false);
|
||||
const [isDestructive, isDestructiveSelector] = useBooleanProp('isDestructive', false);
|
||||
|
||||
const components = useMemo(
|
||||
() => buildComponent(Button, propPossibilities, [
|
||||
buttonSizePosibilities,
|
||||
buttonEmphasisPosibilities,
|
||||
buttonTypePosibilities,
|
||||
buttonStatePosibilities,
|
||||
], [
|
||||
text,
|
||||
disabled,
|
||||
buttonSize,
|
||||
buttonEmphasis,
|
||||
buttonType,
|
||||
buttonState,
|
||||
iconName,
|
||||
isIconOnTheRight,
|
||||
showLoader,
|
||||
isInverted,
|
||||
isDestructive,
|
||||
{
|
||||
theme,
|
||||
onPress,
|
||||
},
|
||||
]),
|
||||
[buttonEmphasis, buttonEmphasisPosibilities, buttonSize, buttonSizePosibilities, buttonState, buttonStatePosibilities, buttonType, buttonTypePosibilities, disabled, text, theme],
|
||||
[buttonEmphasis, buttonEmphasisPosibilities, buttonSize, buttonSizePosibilities, disabled, text, theme, iconName, isIconOnTheRight, showLoader, isInverted, isDestructive],
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -55,8 +57,11 @@ const ButtonComponentLibrary = () => {
|
|||
{disabledSelector}
|
||||
{buttonSizeSelector}
|
||||
{buttonEmphasisSelector}
|
||||
{buttonTypeSelector}
|
||||
{buttonStateSelector}
|
||||
{iconNameSelector}
|
||||
{isIconOnTheRightSelector}
|
||||
{showLoaderSelector}
|
||||
{isInvertedSelector}
|
||||
{isDestructiveSelector}
|
||||
<View>{components}</View>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
55
app/screens/component_library/chip.cl.tsx
Normal file
55
app/screens/component_library/chip.cl.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useMemo} from 'react';
|
||||
import {Alert, View} from 'react-native';
|
||||
|
||||
import BaseChip from '@components/chips/base_chip';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {useTheme} from '@context/theme';
|
||||
|
||||
import {useBooleanProp, useStringProp} from './hooks';
|
||||
import {buildComponent} from './utils';
|
||||
|
||||
const propPossibilities = {};
|
||||
|
||||
const onPress = () => Alert.alert('Button pressed!');
|
||||
|
||||
const ChipComponentLibrary = () => {
|
||||
const theme = useTheme();
|
||||
const [label, labelSelector] = useStringProp('label', 'Chip text', false);
|
||||
const [showRemoveOption, showRemoveOptionSelector] = useBooleanProp('showRemoveOption', false);
|
||||
const [hasPrefix, hasPrefixSelector] = useBooleanProp('hasPrefix', false);
|
||||
|
||||
const components = useMemo(
|
||||
() => buildComponent(BaseChip, propPossibilities, [], [
|
||||
label,
|
||||
showRemoveOption,
|
||||
{
|
||||
onPress,
|
||||
prefix: hasPrefix.hasPrefix ? (
|
||||
<CompassIcon
|
||||
name='account-outline'
|
||||
size={16}
|
||||
color={theme.centerChannelColor}
|
||||
style={{marginLeft: 6}}
|
||||
/>
|
||||
) : undefined,
|
||||
testID: 'chip-example',
|
||||
theme,
|
||||
},
|
||||
]),
|
||||
[label, showRemoveOption, hasPrefix.hasPrefix, theme],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{labelSelector}
|
||||
{showRemoveOptionSelector}
|
||||
{hasPrefixSelector}
|
||||
<View style={{flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 16}}>{components}</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChipComponentLibrary;
|
||||
|
|
@ -12,11 +12,17 @@ import SecurityManager from '@managers/security_manager';
|
|||
import {popTopScreen} from '@screens/navigation';
|
||||
|
||||
import ButtonComponentLibrary from './button.cl';
|
||||
import ChipComponentLibrary from './chip.cl';
|
||||
import SectionNoticeComponentLibrary from './section_notice.cl';
|
||||
import TagComponentLibrary from './tag.cl';
|
||||
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
const componentMap = {
|
||||
Button: ButtonComponentLibrary,
|
||||
Chip: ChipComponentLibrary,
|
||||
Tag: TagComponentLibrary,
|
||||
SectionNotice: SectionNoticeComponentLibrary,
|
||||
};
|
||||
|
||||
type ComponentName = keyof typeof componentMap
|
||||
|
|
|
|||
102
app/screens/component_library/section_notice.cl.tsx
Normal file
102
app/screens/component_library/section_notice.cl.tsx
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useMemo} from 'react';
|
||||
import {Alert, View} from 'react-native';
|
||||
|
||||
import SectionNotice from '@components/section_notice';
|
||||
|
||||
import {useBooleanProp, useDropdownProp, useStringProp} from './hooks';
|
||||
import {buildComponent} from './utils';
|
||||
|
||||
const propPossibilities = {};
|
||||
|
||||
const sectionNoticeTypeValues = ['info', 'success', 'danger', 'welcome', 'warning', 'hint'];
|
||||
|
||||
const onButtonPress = (name: string) => Alert.alert(`Button pressed: ${name}`);
|
||||
const onDismissPress = () => Alert.alert('Notice dismissed!');
|
||||
|
||||
const PRIMARY_BUTTON = {
|
||||
onClick: () => onButtonPress('primary'),
|
||||
text: 'Primary Action',
|
||||
};
|
||||
|
||||
const SECONDARY_BUTTON = {
|
||||
onClick: () => onButtonPress('secondary'),
|
||||
text: 'Secondary Action',
|
||||
};
|
||||
|
||||
const LINK_BUTTON = {
|
||||
onClick: () => onButtonPress('link'),
|
||||
text: 'Link Action',
|
||||
};
|
||||
|
||||
const TAGS = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6', 'tag7', 'tag8', 'tag9', 'tag10'];
|
||||
|
||||
const SectionNoticeComponentLibrary = () => {
|
||||
const [title, titleSelector] = useStringProp('title', 'Notice Title', false);
|
||||
const [text, textSelector] = useStringProp('text', 'This is the notice text content with **markdown**.', false);
|
||||
const [isDismissable, isDismissableSelector] = useBooleanProp('isDismissable', false);
|
||||
const [squareCorners, squareCornersSelector] = useBooleanProp('squareCorners', false);
|
||||
const [hasPrimaryButton, hasPrimaryButtonSelector] = useBooleanProp('hasPrimaryButton', false);
|
||||
const [hasSecondaryButton, hasSecondaryButtonSelector] = useBooleanProp('hasSecondaryButton', false);
|
||||
const [hasLinkButton, hasLinkButtonSelector] = useBooleanProp('hasLinkButton', false);
|
||||
const [hasTags, hasTagsSelector] = useBooleanProp('hasTags', false);
|
||||
const [sectionNoticeType, sectionNoticeTypePossibilities, sectionNoticeTypeSelector] = useDropdownProp('type', 'info', sectionNoticeTypeValues, true);
|
||||
|
||||
const primaryButton = hasPrimaryButton.hasPrimaryButton ? PRIMARY_BUTTON : undefined;
|
||||
const secondaryButton = hasSecondaryButton.hasSecondaryButton ? SECONDARY_BUTTON : undefined;
|
||||
const linkButton = hasLinkButton.hasLinkButton ? LINK_BUTTON : undefined;
|
||||
|
||||
const tags = hasTags.hasTags ? TAGS : undefined;
|
||||
|
||||
const components = useMemo(
|
||||
() => buildComponent(SectionNotice, propPossibilities, [
|
||||
sectionNoticeTypePossibilities,
|
||||
], [
|
||||
title,
|
||||
text,
|
||||
isDismissable,
|
||||
squareCorners,
|
||||
{
|
||||
primaryButton,
|
||||
secondaryButton,
|
||||
linkButton,
|
||||
tags,
|
||||
onDismissClick: isDismissable.isDismissable ? onDismissPress : undefined,
|
||||
location: 'ComponentLibrary',
|
||||
testID: 'section-notice-example',
|
||||
},
|
||||
sectionNoticeType,
|
||||
]),
|
||||
[
|
||||
sectionNoticeTypePossibilities,
|
||||
title,
|
||||
text,
|
||||
isDismissable,
|
||||
squareCorners,
|
||||
primaryButton,
|
||||
secondaryButton,
|
||||
linkButton,
|
||||
tags,
|
||||
sectionNoticeType,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{titleSelector}
|
||||
{textSelector}
|
||||
{isDismissableSelector}
|
||||
{squareCornersSelector}
|
||||
{hasPrimaryButtonSelector}
|
||||
{hasSecondaryButtonSelector}
|
||||
{hasLinkButtonSelector}
|
||||
{hasTagsSelector}
|
||||
{sectionNoticeTypeSelector}
|
||||
<View style={{gap: 16, marginTop: 16}}>{components}</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SectionNoticeComponentLibrary;
|
||||
58
app/screens/component_library/tag.cl.tsx
Normal file
58
app/screens/component_library/tag.cl.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useMemo} from 'react';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import Tag from '@components/tag/base_tag';
|
||||
import {useTheme} from '@context/theme';
|
||||
|
||||
import {useBooleanProp, useDropdownProp, useStringProp} from './hooks';
|
||||
import {buildComponent} from './utils';
|
||||
|
||||
const propPossibilities = {};
|
||||
|
||||
const tagTypeValues = ['general', 'info', 'danger', 'success', 'warning', 'infoDim'];
|
||||
const tagSizeValues = ['xs', 's', 'm'];
|
||||
|
||||
const TagComponentLibrary = () => {
|
||||
const theme = useTheme();
|
||||
const [message, messageSelector] = useStringProp('message', 'Tag text', false);
|
||||
const [icon, iconSelector] = useStringProp('icon', 'check', false);
|
||||
const [useIcon, useIconSelector] = useBooleanProp('useIcon', true);
|
||||
const [uppercase, uppercaseSelector] = useBooleanProp('uppercase', true);
|
||||
const [tagType, tagTypePossibilities, tagTypeSelector] = useDropdownProp('type', 'general', tagTypeValues, true);
|
||||
const [tagSize, tagSizePossibilities, tagSizeSelector] = useDropdownProp('size', 's', tagSizeValues, true);
|
||||
|
||||
const components = useMemo(
|
||||
() => buildComponent(Tag, propPossibilities, [
|
||||
tagTypePossibilities,
|
||||
tagSizePossibilities,
|
||||
], [
|
||||
message,
|
||||
uppercase,
|
||||
{
|
||||
icon: useIcon.useIcon ? icon.icon : undefined,
|
||||
theme,
|
||||
testID: 'tag-example',
|
||||
},
|
||||
tagType,
|
||||
tagSize,
|
||||
]),
|
||||
[tagTypePossibilities, tagSizePossibilities, message, uppercase, useIcon.useIcon, icon.icon, theme, tagType, tagSize],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{messageSelector}
|
||||
{iconSelector}
|
||||
{useIconSelector}
|
||||
{uppercaseSelector}
|
||||
{tagTypeSelector}
|
||||
{tagSizeSelector}
|
||||
<View style={{gap: 8, alignItems: 'flex-start'}}>{components}</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagComponentLibrary;
|
||||
|
|
@ -96,6 +96,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
flexWrap: 'wrap',
|
||||
marginHorizontal: 20,
|
||||
marginVertical: 16,
|
||||
gap: 8,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -316,9 +316,7 @@ export default function ManageChannelMembers({
|
|||
tags={attributeTags.length > 0 ? attributeTags : undefined}
|
||||
location={Screens.MANAGE_CHANNEL_MEMBERS}
|
||||
testID={`${TEST_ID}.notice`}
|
||||
containerStyle={styles.flatBottomBanner}
|
||||
iconSize={24}
|
||||
tagsVariant='subtle'
|
||||
squareCorners={true}
|
||||
/>
|
||||
)}
|
||||
<View style={styles.searchBar}>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ exports[`SendTestNotificationNotice should match snapshot 1`] = `
|
|||
style={
|
||||
[
|
||||
{
|
||||
"borderRadius": 4,
|
||||
"borderStyle": "solid",
|
||||
"borderWidth": 1,
|
||||
},
|
||||
|
|
@ -22,7 +21,9 @@ exports[`SendTestNotificationNotice should match snapshot 1`] = `
|
|||
"backgroundColor": "rgba(93,137,234,0.08)",
|
||||
"borderColor": "rgba(93,137,234,0.16)",
|
||||
},
|
||||
undefined,
|
||||
{
|
||||
"borderRadius": 4,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="sectionNoticeContainer"
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
details: {
|
||||
marginLeft: 24,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-start',
|
||||
gap: 4,
|
||||
flex: 1,
|
||||
},
|
||||
displayName: {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import {defineMessages} from 'react-intl';
|
||||
|
||||
import Tag, {BotTag, GuestTag} from '@components/tag';
|
||||
|
||||
|
|
@ -14,37 +14,37 @@ type Props = {
|
|||
isTeamAdmin: boolean;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tag: {
|
||||
alignSelf: 'flex-start',
|
||||
paddingHorizontal: 6,
|
||||
marginBottom: 4,
|
||||
const messages = defineMessages({
|
||||
sysAdmin: {
|
||||
id: 'user_profile.system_admin',
|
||||
defaultMessage: 'System Admin',
|
||||
},
|
||||
teamAdmin: {
|
||||
id: 'user_profile.team_admin',
|
||||
defaultMessage: 'Team Admin',
|
||||
},
|
||||
channelAdmin: {
|
||||
id: 'user_profile.channel_admin',
|
||||
defaultMessage: 'Channel Admin',
|
||||
},
|
||||
});
|
||||
|
||||
const UserProfileTag = ({isBot, isChannelAdmin, showGuestTag, isSystemAdmin, isTeamAdmin}: Props) => {
|
||||
if (isBot) {
|
||||
return (
|
||||
<BotTag
|
||||
style={styles.tag}
|
||||
testID='user_profile.bot.tag'
|
||||
/>);
|
||||
<BotTag testID='user_profile.bot.tag'/>);
|
||||
}
|
||||
|
||||
if (showGuestTag) {
|
||||
return (
|
||||
<GuestTag
|
||||
style={styles.tag}
|
||||
testID='user_profile.guest.tag'
|
||||
/>);
|
||||
<GuestTag testID='user_profile.guest.tag'/>);
|
||||
}
|
||||
|
||||
if (isSystemAdmin) {
|
||||
return (
|
||||
<Tag
|
||||
id='user_profile.system_admin'
|
||||
defaultMessage='System Admin'
|
||||
style={styles.tag}
|
||||
message={messages.sysAdmin}
|
||||
uppercase={true}
|
||||
testID='user_profile.system_admin.tag'
|
||||
/>
|
||||
);
|
||||
|
|
@ -53,10 +53,9 @@ const UserProfileTag = ({isBot, isChannelAdmin, showGuestTag, isSystemAdmin, isT
|
|||
if (isTeamAdmin) {
|
||||
return (
|
||||
<Tag
|
||||
id='user_profile.team_admin'
|
||||
defaultMessage='Team Admin'
|
||||
style={styles.tag}
|
||||
message={messages.teamAdmin}
|
||||
testID='user_profile.team_admin.tag'
|
||||
uppercase={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -64,10 +63,9 @@ const UserProfileTag = ({isBot, isChannelAdmin, showGuestTag, isSystemAdmin, isT
|
|||
if (isChannelAdmin) {
|
||||
return (
|
||||
<Tag
|
||||
id='user_profile.channel_admin'
|
||||
defaultMessage='Channel Admin'
|
||||
style={styles.tag}
|
||||
message={messages.channelAdmin}
|
||||
testID='user_profile.channel_admin.tag'
|
||||
uppercase={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ export const getBackgroundStyles = (theme: Theme): BackgroundStyles => {
|
|||
},
|
||||
inverted: {
|
||||
default: {
|
||||
backgroundColor: changeOpacity(theme.sidebarText, 0.12),
|
||||
backgroundColor: changeOpacity(theme.buttonColor, 0.12),
|
||||
},
|
||||
hover: {
|
||||
backgroundColor: changeOpacity(theme.sidebarText, 0.16),
|
||||
|
|
|
|||
|
|
@ -1272,7 +1272,10 @@
|
|||
"unreads.empty.paragraph": "Turn off the unread filter to show all your channels.",
|
||||
"unreads.empty.show_all": "Show all",
|
||||
"unreads.empty.title": "No more unreads",
|
||||
"user_profile.channel_admin": "Channel Admin",
|
||||
"user_profile.custom_status": "Custom Status",
|
||||
"user_profile.system_admin": "System Admin",
|
||||
"user_profile.team_admin": "Team Admin",
|
||||
"user_settings.notifications.test_notification.body": "Not receiving notifications? Start by sending a test notification to all your devices to check if they’re working as expected. If issues persist, explore ways to solve them with troubleshooting steps.",
|
||||
"user_settings.notifications.test_notification.go_to_docs": "Troubleshooting docs",
|
||||
"user_settings.notifications.test_notification.send_button.error": "Error sending test notification",
|
||||
|
|
|
|||
Loading…
Reference in a new issue