parent
738c7e6b1d
commit
1c0e201edf
16 changed files with 461 additions and 133 deletions
|
|
@ -1,9 +1,6 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
|
|
|||
111
app/components/chips/base_chip.test.tsx
Normal file
111
app/components/chips/base_chip.test.tsx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {fireEvent} from '@testing-library/react-native';
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithIntlAndTheme} from '@test/intl-test-helper';
|
||||
|
||||
import BaseChip from './base_chip';
|
||||
|
||||
describe('BaseChip', () => {
|
||||
const onPressMock = jest.fn();
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render correctly with default props', () => {
|
||||
const {getByText} = renderWithIntlAndTheme(
|
||||
<BaseChip
|
||||
onPress={onPressMock}
|
||||
label='Test Label'
|
||||
testID='base_chip'
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByText('Test Label')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render with the X button when showRemoveOption is true', () => {
|
||||
const {getByTestId} = renderWithIntlAndTheme(
|
||||
<BaseChip
|
||||
onPress={onPressMock}
|
||||
label='Test Label'
|
||||
testID='base_chip'
|
||||
showRemoveOption={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByTestId('base_chip.remove.button')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not render the X button when showRemoveOption is false', () => {
|
||||
const {queryByTestId} = renderWithIntlAndTheme(
|
||||
<BaseChip
|
||||
onPress={onPressMock}
|
||||
label='Test Label'
|
||||
testID='base_chip'
|
||||
showRemoveOption={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('base_chip.remove.button')).toBeNull();
|
||||
});
|
||||
|
||||
it('should call onPress when the X button is pressed', () => {
|
||||
const {getByTestId} = renderWithIntlAndTheme(
|
||||
<BaseChip
|
||||
onPress={onPressMock}
|
||||
label='Test Label'
|
||||
testID='base_chip'
|
||||
showRemoveOption={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.press(getByTestId('base_chip.remove.button'));
|
||||
expect(onPressMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should call onPress when the chip is pressed without X button', () => {
|
||||
const {getByTestId} = renderWithIntlAndTheme(
|
||||
<BaseChip
|
||||
onPress={onPressMock}
|
||||
label='Test Label'
|
||||
testID='base_chip'
|
||||
showRemoveOption={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.press(getByTestId('base_chip.chip_button'));
|
||||
expect(onPressMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle animations when showAnimation is true', () => {
|
||||
const {getByTestId} = renderWithIntlAndTheme(
|
||||
<BaseChip
|
||||
onPress={onPressMock}
|
||||
label='Test Label'
|
||||
testID='base_chip'
|
||||
showAnimation={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByTestId('base_chip').props.entering).toBeTruthy();
|
||||
expect(getByTestId('base_chip').props.exiting).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not handle animations when showAnimation is false', () => {
|
||||
const {getByTestId} = renderWithIntlAndTheme(
|
||||
<BaseChip
|
||||
onPress={onPressMock}
|
||||
label='Test Label'
|
||||
testID='base_chip'
|
||||
showAnimation={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByTestId('base_chip').props.entering).toBeUndefined();
|
||||
expect(getByTestId('base_chip').props.exiting).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {Text, TouchableOpacity, useWindowDimensions, type StyleProp, type ViewStyle} from 'react-native';
|
||||
import React from 'react';
|
||||
import {Text, TouchableOpacity, useWindowDimensions} from 'react-native';
|
||||
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
|
|
@ -11,17 +11,17 @@ import {nonBreakingString} from '@utils/strings';
|
|||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import {CHIP_BOTTOM_MARGIN, CHIP_HEIGHT} from './constants';
|
||||
|
||||
type SelectedChipProps = {
|
||||
id: string;
|
||||
text: string;
|
||||
extra?: React.ReactNode;
|
||||
onRemove: (id: string) => void;
|
||||
onPress: () => void;
|
||||
testID?: string;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
showRemoveOption?: boolean;
|
||||
showAnimation?: boolean;
|
||||
label: string;
|
||||
prefix?: JSX.Element;
|
||||
}
|
||||
|
||||
export const USER_CHIP_HEIGHT = 32;
|
||||
export const USER_CHIP_BOTTOM_MARGIN = 8;
|
||||
const FADE_DURATION = 100;
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
|
|
@ -31,9 +31,9 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
borderRadius: 16,
|
||||
height: USER_CHIP_HEIGHT,
|
||||
height: CHIP_HEIGHT,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
marginBottom: USER_CHIP_BOTTOM_MARGIN,
|
||||
marginBottom: CHIP_BOTTOM_MARGIN,
|
||||
marginRight: 8,
|
||||
paddingHorizontal: 7,
|
||||
},
|
||||
|
|
@ -49,50 +49,68 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
export default function SelectedChip({
|
||||
id,
|
||||
text,
|
||||
extra,
|
||||
onRemove,
|
||||
export default function BaseChip({
|
||||
testID,
|
||||
containerStyle,
|
||||
onPress,
|
||||
showRemoveOption,
|
||||
showAnimation,
|
||||
label,
|
||||
prefix,
|
||||
}: SelectedChipProps) {
|
||||
const theme = useTheme();
|
||||
const style = getStyleFromTheme(theme);
|
||||
const dimensions = useWindowDimensions();
|
||||
|
||||
const containerStyles = [style.container, containerStyle];
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
onRemove(id);
|
||||
}, [onRemove, id]);
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(FADE_DURATION)}
|
||||
exiting={FadeOut.duration(FADE_DURATION)}
|
||||
style={containerStyles}
|
||||
testID={testID}
|
||||
>
|
||||
{extra}
|
||||
const chipContent = (
|
||||
<>
|
||||
{prefix}
|
||||
<Text
|
||||
style={[style.text, {maxWidth: dimensions.width * 0.70}]}
|
||||
numberOfLines={1}
|
||||
testID={`${testID}.display_name`}
|
||||
>
|
||||
{nonBreakingString(text)}
|
||||
{nonBreakingString(label)}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
|
||||
let content;
|
||||
if (showRemoveOption) {
|
||||
content = (
|
||||
<>
|
||||
{chipContent}
|
||||
<TouchableOpacity
|
||||
style={style.remove}
|
||||
onPress={onPress}
|
||||
testID={`${testID}.remove.button`}
|
||||
>
|
||||
<CompassIcon
|
||||
name='close-circle'
|
||||
size={18}
|
||||
color={changeOpacity(theme.centerChannelColor, 0.32)}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<TouchableOpacity
|
||||
style={style.remove}
|
||||
onPress={onPress}
|
||||
testID={`${testID}.remove.button`}
|
||||
testID={`${testID}.chip_button`}
|
||||
>
|
||||
<CompassIcon
|
||||
name='close-circle'
|
||||
size={18}
|
||||
color={changeOpacity(theme.centerChannelColor, 0.32)}
|
||||
/>
|
||||
{chipContent}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Animated.View
|
||||
entering={showAnimation ? FadeIn.duration(FADE_DURATION) : undefined}
|
||||
exiting={showAnimation ? FadeOut.duration(FADE_DURATION) : undefined}
|
||||
style={style.container}
|
||||
testID={testID}
|
||||
>
|
||||
{content}
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
5
app/components/chips/constants.ts
Normal file
5
app/components/chips/constants.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// 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;
|
||||
42
app/components/chips/selected_chip.test.tsx
Normal file
42
app/components/chips/selected_chip.test.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {render} from '@testing-library/react-native';
|
||||
import React from 'react';
|
||||
|
||||
import BaseChip from './base_chip';
|
||||
import SelectedChip from './selected_chip';
|
||||
|
||||
jest.mock('./base_chip', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
jest.mocked(BaseChip).mockImplementation((props) => React.createElement('BaseChip', {...props}));
|
||||
|
||||
describe('SelectedChip', () => {
|
||||
const mockOnRemove = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render with the correct props', () => {
|
||||
const {getByTestId} = render(
|
||||
<SelectedChip
|
||||
id='test-id'
|
||||
text='Test Chip'
|
||||
onRemove={mockOnRemove}
|
||||
testID='selected-chip'
|
||||
/>,
|
||||
);
|
||||
|
||||
const baseChip = getByTestId('selected-chip');
|
||||
expect(baseChip.props.label).toBe('Test Chip');
|
||||
expect(baseChip.props.showRemoveOption).toBe(true);
|
||||
expect(baseChip.props.showAnimation).toBe(true);
|
||||
expect(baseChip.props.prefix).toBeUndefined();
|
||||
baseChip.props.onPress();
|
||||
expect(mockOnRemove).toHaveBeenCalledTimes(1);
|
||||
expect(mockOnRemove).toHaveBeenCalledWith('test-id');
|
||||
});
|
||||
});
|
||||
34
app/components/chips/selected_chip.tsx
Normal file
34
app/components/chips/selected_chip.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
|
||||
import BaseChip from './base_chip';
|
||||
|
||||
type SelectedChipProps = {
|
||||
id: string;
|
||||
text: string;
|
||||
onRemove: (id: string) => void;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
export default function SelectedChip({
|
||||
id,
|
||||
text,
|
||||
onRemove,
|
||||
testID,
|
||||
}: SelectedChipProps) {
|
||||
const onPress = useCallback(() => {
|
||||
onRemove(id);
|
||||
}, [onRemove, id]);
|
||||
|
||||
return (
|
||||
<BaseChip
|
||||
testID={testID}
|
||||
onPress={onPress}
|
||||
showRemoveOption={true}
|
||||
showAnimation={true}
|
||||
label={text}
|
||||
/>
|
||||
);
|
||||
}
|
||||
40
app/components/chips/selected_user_chip.test.tsx
Normal file
40
app/components/chips/selected_user_chip.test.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {render} from '@testing-library/react-native';
|
||||
import React from 'react';
|
||||
|
||||
import TestHelper from '@test/test_helper';
|
||||
|
||||
import SelectedUserChip from './selected_user_chip';
|
||||
import UserChip from './user_chip';
|
||||
|
||||
jest.mock('./user_chip', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
jest.mocked(UserChip).mockImplementation((props) => React.createElement('UserChip', {...props}));
|
||||
|
||||
describe('SelectedUserChip', () => {
|
||||
const mockOnPress = jest.fn();
|
||||
const mockUser = TestHelper.fakeUser({id: 'user-id', username: 'test-user'});
|
||||
|
||||
it('should render with the correct props', () => {
|
||||
const {getByTestId} = render(
|
||||
<SelectedUserChip
|
||||
user={mockUser}
|
||||
onPress={mockOnPress}
|
||||
testID='selected-user-chip'
|
||||
teammateNameDisplay='username'
|
||||
/>,
|
||||
);
|
||||
|
||||
const userChip = getByTestId('selected-user-chip');
|
||||
expect(userChip.props.user).toBe(mockUser);
|
||||
expect(userChip.props.teammateNameDisplay).toBe('username');
|
||||
expect(userChip.props.showRemoveOption).toBe(true);
|
||||
expect(userChip.props.showAnimation).toBe(true);
|
||||
userChip.props.onPress();
|
||||
expect(mockOnPress).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
33
app/components/chips/selected_user_chip.tsx
Normal file
33
app/components/chips/selected_user_chip.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import UserChip from './user_chip';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type SelectedChipProps = {
|
||||
user: UserModel | UserProfile;
|
||||
onPress: (id: string) => void;
|
||||
testID?: string;
|
||||
teammateNameDisplay: string;
|
||||
}
|
||||
|
||||
export default function SelectedUserChip({
|
||||
testID,
|
||||
user,
|
||||
teammateNameDisplay,
|
||||
onPress,
|
||||
}: SelectedChipProps) {
|
||||
return (
|
||||
<UserChip
|
||||
testID={testID}
|
||||
onPress={onPress}
|
||||
showRemoveOption={true}
|
||||
showAnimation={true}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
user={user}
|
||||
/>
|
||||
);
|
||||
}
|
||||
57
app/components/chips/user_chip.test.tsx
Normal file
57
app/components/chips/user_chip.test.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
import {renderWithIntlAndTheme} from '@test/intl-test-helper';
|
||||
import TestHelper from '@test/test_helper';
|
||||
|
||||
import BaseChip from './base_chip';
|
||||
import UserChip from './user_chip';
|
||||
|
||||
jest.mock('./base_chip', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
jest.mocked(BaseChip).mockImplementation((props) => React.createElement('BaseChip', {...props}));
|
||||
|
||||
jest.mock('@components/profile_picture', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
jest.mocked(ProfilePicture).mockImplementation((props) => React.createElement('ProfilePicture', {...props}));
|
||||
|
||||
describe('UserChip', () => {
|
||||
const mockOnPress = jest.fn();
|
||||
const mockUser = TestHelper.fakeUser({id: 'user-id', username: 'test-user'});
|
||||
|
||||
it('should render with the correct props', () => {
|
||||
const {getByTestId} = renderWithIntlAndTheme(
|
||||
<UserChip
|
||||
user={mockUser}
|
||||
onPress={mockOnPress}
|
||||
testID='user-chip'
|
||||
teammateNameDisplay='username'
|
||||
showRemoveOption={true}
|
||||
showAnimation={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
const baseChip = getByTestId('user-chip');
|
||||
expect(baseChip.props.label).toBe('test-user');
|
||||
expect(baseChip.props.showRemoveOption).toBe(true);
|
||||
expect(baseChip.props.showAnimation).toBe(true);
|
||||
|
||||
expect(baseChip.props.prefix).toBeDefined();
|
||||
const profilePicture = baseChip.props.prefix;
|
||||
expect(profilePicture.props.author).toBe(mockUser);
|
||||
expect(profilePicture.props.size).toBe(20);
|
||||
expect(profilePicture.props.iconSize).toBe(20);
|
||||
expect(profilePicture.props.testID).toBe('user-chip.profile_picture');
|
||||
|
||||
baseChip.props.onPress();
|
||||
expect(mockOnPress).toHaveBeenCalledTimes(1);
|
||||
expect(mockOnPress).toHaveBeenCalledWith('user-id');
|
||||
});
|
||||
});
|
||||
57
app/components/chips/user_chip.tsx
Normal file
57
app/components/chips/user_chip.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import BaseChip from './base_chip';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type SelectedChipProps = {
|
||||
user: UserModel | UserProfile;
|
||||
onPress: (id: string) => void;
|
||||
testID?: string;
|
||||
teammateNameDisplay: string;
|
||||
showRemoveOption?: boolean;
|
||||
showAnimation?: boolean;
|
||||
}
|
||||
|
||||
export default function UserChip({
|
||||
testID,
|
||||
user,
|
||||
teammateNameDisplay,
|
||||
onPress: receivedOnPress,
|
||||
showRemoveOption,
|
||||
showAnimation,
|
||||
}: SelectedChipProps) {
|
||||
const intl = useIntl();
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
receivedOnPress(user.id);
|
||||
}, [receivedOnPress, user.id]);
|
||||
|
||||
const name = displayUsername(user, intl.locale, teammateNameDisplay);
|
||||
const picture = useMemo(() => (
|
||||
<ProfilePicture
|
||||
author={user}
|
||||
size={20}
|
||||
iconSize={20}
|
||||
testID={`${testID}.profile_picture`}
|
||||
/>
|
||||
), [testID, user]);
|
||||
|
||||
return (
|
||||
<BaseChip
|
||||
testID={testID}
|
||||
onPress={onPress}
|
||||
showRemoveOption={showRemoveOption}
|
||||
showAnimation={showAnimation}
|
||||
label={name}
|
||||
prefix={picture}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -31,8 +31,9 @@ import Animated, {
|
|||
Easing,
|
||||
} from 'react-native-reanimated';
|
||||
|
||||
import {CHIP_HEIGHT} from '@components/chips/constants';
|
||||
import SelectedChip from '@components/chips/selected_chip';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import SelectedChip, {USER_CHIP_HEIGHT} from '@components/selected_chip';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
|
@ -67,7 +68,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
paddingHorizontal: 0,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
height: USER_CHIP_HEIGHT,
|
||||
height: CHIP_HEIGHT,
|
||||
flexGrow: 1,
|
||||
flexShrink: 0,
|
||||
flexBasis: 'auto',
|
||||
|
|
@ -222,7 +223,7 @@ const FloatingTextChipsInput = forwardRef<Ref, Props>(({
|
|||
}
|
||||
res.push({
|
||||
borderWidth: focusedLabel ? BORDER_FOCUSED_WIDTH : BORDER_DEFAULT_WIDTH,
|
||||
minHeight: (USER_CHIP_HEIGHT * 2.5) + ((focusedLabel ? BORDER_FOCUSED_WIDTH : BORDER_DEFAULT_WIDTH) * 2),
|
||||
minHeight: (CHIP_HEIGHT * 2.5) + ((focusedLabel ? BORDER_FOCUSED_WIDTH : BORDER_DEFAULT_WIDTH) * 2),
|
||||
});
|
||||
|
||||
if (focused) {
|
||||
|
|
@ -282,7 +283,6 @@ const FloatingTextChipsInput = forwardRef<Ref, Props>(({
|
|||
text={chipValue}
|
||||
testID={`${testID}.${chipValue}`}
|
||||
onRemove={onChipRemove}
|
||||
containerStyle={styles.chipContainer}
|
||||
/>
|
||||
))}
|
||||
<TextInput
|
||||
|
|
|
|||
|
|
@ -7,14 +7,13 @@ import Animated, {useAnimatedStyle, useDerivedValue, useSharedValue, withTiming}
|
|||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import Button from '@components/button';
|
||||
import {USER_CHIP_BOTTOM_MARGIN, USER_CHIP_HEIGHT} from '@components/selected_chip';
|
||||
import {CHIP_BOTTOM_MARGIN, CHIP_HEIGHT} from '@components/chips/constants';
|
||||
import SelectedUserChip from '@components/chips/selected_user_chip';
|
||||
import Toast from '@components/toast';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet, useKeyboardHeightWithDuration} from '@hooks/device';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import SelectedUser from './selected_user';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
|
|
@ -84,8 +83,8 @@ type Props = {
|
|||
}
|
||||
|
||||
const BUTTON_HEIGHT = 48;
|
||||
const CHIP_HEIGHT_WITH_MARGIN = USER_CHIP_HEIGHT + USER_CHIP_BOTTOM_MARGIN;
|
||||
const EXPOSED_CHIP_HEIGHT = 0.33 * USER_CHIP_HEIGHT;
|
||||
const CHIP_HEIGHT_WITH_MARGIN = CHIP_HEIGHT + CHIP_BOTTOM_MARGIN;
|
||||
const EXPOSED_CHIP_HEIGHT = 0.33 * CHIP_HEIGHT;
|
||||
const MAX_CHIP_ROWS = 2;
|
||||
const SCROLL_MARGIN_TOP = 20;
|
||||
const SCROLL_MARGIN_BOTTOM = 12;
|
||||
|
|
@ -164,18 +163,20 @@ export default function SelectedUsers({
|
|||
|
||||
const users = useMemo(() => {
|
||||
const u = [];
|
||||
for (const [id, user] of Object.entries(selectedIds)) {
|
||||
for (const user of Object.values(selectedIds)) {
|
||||
if (!user) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const userItemTestID = `${testID}.${user.id}`;
|
||||
|
||||
u.push(
|
||||
<SelectedUser
|
||||
key={id}
|
||||
<SelectedUserChip
|
||||
key={user.id}
|
||||
user={user}
|
||||
onPress={onRemove}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
onRemove={onRemove}
|
||||
testID={`${testID}.selected_user`}
|
||||
testID={userItemTestID}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
import SelectedChip from '@components/selected_chip';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type Props = {
|
||||
|
||||
/*
|
||||
* How to display the names of users.
|
||||
*/
|
||||
teammateNameDisplay: string;
|
||||
|
||||
/*
|
||||
* The user that this component represents.
|
||||
*/
|
||||
user: UserProfile|UserModel;
|
||||
|
||||
/*
|
||||
* A handler function that will deselect a user when clicked on.
|
||||
*/
|
||||
onRemove: (id: string) => void;
|
||||
|
||||
/*
|
||||
* The test ID.
|
||||
*/
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
export default function SelectedUser({
|
||||
teammateNameDisplay,
|
||||
user,
|
||||
onRemove,
|
||||
testID,
|
||||
}: Props) {
|
||||
const intl = useIntl();
|
||||
|
||||
const onPress = useCallback((id: string) => {
|
||||
onRemove(id);
|
||||
}, [onRemove]);
|
||||
|
||||
const userItemTestID = `${testID}.${user.id}`;
|
||||
|
||||
return (
|
||||
<SelectedChip
|
||||
id={user.id}
|
||||
text={displayUsername(user, intl.locale, teammateNameDisplay)}
|
||||
extra={(
|
||||
<ProfilePicture
|
||||
author={user}
|
||||
size={20}
|
||||
iconSize={20}
|
||||
testID={`${userItemTestID}.profile_picture`}
|
||||
/>
|
||||
)}
|
||||
onRemove={onPress}
|
||||
testID={userItemTestID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -8,11 +8,11 @@ import {Platform, type StyleProp, Text, type TextStyle, TouchableOpacity, View,
|
|||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {BOTTOM_SHEET_ANDROID_OFFSET} from '@screens/bottom_sheet';
|
||||
import {TITLE_HEIGHT} from '@screens/bottom_sheet/content';
|
||||
import {bottomSheet} from '@screens/navigation';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ const OVERFLOW_DISPLAY_LIMIT = 99;
|
|||
const USER_ROW_HEIGHT = 40;
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
channelId?: string;
|
||||
location: string;
|
||||
users: UserModel[];
|
||||
breakAt?: number;
|
||||
|
|
@ -100,10 +100,11 @@ const UserAvatarsStack = ({
|
|||
overflowTextStyle,
|
||||
}: Props) => {
|
||||
const theme = useTheme();
|
||||
const style = getStyleSheet(theme);
|
||||
const intl = useIntl();
|
||||
const isTablet = useIsTablet();
|
||||
|
||||
const showParticipantsList = useCallback(preventDoubleTap(() => {
|
||||
const showParticipantsList = usePreventDoubleTap(useCallback(() => {
|
||||
const renderContent = () => (
|
||||
<>
|
||||
{!isTablet && (
|
||||
|
|
@ -141,13 +142,11 @@ const UserAvatarsStack = ({
|
|||
title: intl.formatMessage({id: 'mobile.participants.header', defaultMessage: 'Thread Participants'}),
|
||||
theme,
|
||||
});
|
||||
}), [isTablet, theme, users, channelId, location]);
|
||||
}, [users, intl, theme, isTablet, style.listHeader, style.listHeaderText, channelId, location]));
|
||||
|
||||
const displayUsers = users.slice(0, breakAt);
|
||||
const overflowUsersCount = Math.min(users.length - displayUsers.length, OVERFLOW_DISPLAY_LIMIT);
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={showParticipantsList}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,14 @@ import {dismissBottomSheet, openAsBottomSheet} from '@screens/navigation';
|
|||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
channelId?: string;
|
||||
location: string;
|
||||
type?: BottomSheetList;
|
||||
users: UserModel[];
|
||||
};
|
||||
|
||||
type ItemProps = {
|
||||
channelId: string;
|
||||
channelId?: string;
|
||||
location: string;
|
||||
user: UserModel;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import {
|
|||
} from 'react-native';
|
||||
import Animated, {useAnimatedStyle, useDerivedValue} from 'react-native-reanimated';
|
||||
|
||||
import SelectedChip from '@components/selected_chip';
|
||||
import SelectedUser from '@components/selected_users/selected_user';
|
||||
import SelectedChip from '@components/chips/selected_chip';
|
||||
import SelectedUserChip from '@components/chips/selected_user_chip';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import UserItem from '@components/user_item';
|
||||
import {MAX_LIST_HEIGHT, MAX_LIST_TABLET_DIFF} from '@constants/autocomplete';
|
||||
|
|
@ -267,11 +267,11 @@ export default function Selection({
|
|||
testID={`invite.selected_item.${selectedItem}`}
|
||||
/>
|
||||
) : (
|
||||
<SelectedUser
|
||||
<SelectedUserChip
|
||||
key={id}
|
||||
user={selectedItem}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
onRemove={handleOnRemoveItem}
|
||||
onPress={handleOnRemoveItem}
|
||||
testID='invite.selected_item'
|
||||
/>
|
||||
));
|
||||
|
|
|
|||
Loading…
Reference in a new issue