Add several UX fixes to set command screen on playbooks (#9136)

* Add several UX fixes to set command screen on playbooks

* Update app/products/playbooks/screens/edit_command/edit_command.test.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update app/components/autocomplete/autocomplete.test.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix keyboard overlap

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Daniel Espino García 2025-09-19 07:10:05 +02:00 committed by GitHub
parent a4e495a2a6
commit 62aba308f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 62 additions and 20 deletions

View file

@ -120,4 +120,36 @@ describe('Autocomplete', () => {
expect(getByTestId('slash-suggestion-mock')).toBeTruthy();
expect(getByTestId('app-slash-suggestion-mock')).toBeTruthy();
});
it('should render with the correct horizontal padding', () => {
const props = getBaseProps();
const {getByTestId, rerender} = render(<Autocomplete {...props}/>);
// Default horizontal padding is 8
expect(getByTestId('autocomplete')).toHaveStyle({left: 8, right: 8});
// Set horizontal padding to 20
props.horizontalPadding = 20;
rerender(<Autocomplete {...props}/>);
expect(getByTestId('autocomplete')).toHaveStyle({left: 20, right: 20});
});
it('should set the correct max height by default', () => {
const props = getBaseProps();
props.availableSpace = {value: 1000} as SharedValue<number>;
const {getByTestId} = render(<Autocomplete {...props}/>);
// Default max height is 230
expect(getByTestId('autocomplete')).toHaveStyle({maxHeight: 230});
});
it('should set the correct max height when useAllAvailableSpace is true', () => {
const props = getBaseProps();
props.availableSpace = {value: 1000} as SharedValue<number>;
props.useAllAvailableSpace = true;
const {getByTestId} = render(<Autocomplete {...props}/>);
expect(getByTestId('autocomplete')).toHaveStyle({maxHeight: 1000});
});
});

View file

@ -19,8 +19,6 @@ import AppSlashSuggestion from './slash_suggestion/app_slash_suggestion/';
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
base: {
left: 8,
right: 8,
position: 'absolute',
},
borders: {
@ -65,6 +63,8 @@ type Props = {
teamId?: string;
containerStyle?: StyleProp<ViewStyle>;
autocompleteProviders?: AutocompleteProviders;
useAllAvailableSpace?: boolean;
horizontalPadding?: number;
}
type AutocompleteProviders = {
@ -99,6 +99,8 @@ const Autocomplete = ({
containerStyle,
teamId,
autocompleteProviders = defaultAutocompleteProviders,
useAllAvailableSpace = false,
horizontalPadding = 8,
}: Props) => {
const theme = useTheme();
const isTablet = useIsTablet();
@ -121,8 +123,8 @@ const Autocomplete = ({
const maxHeightAdjust = (isTablet && isLandscape) ? MAX_LIST_TABLET_DIFF : 0;
const defaultMaxHeight = MAX_LIST_HEIGHT - maxHeightAdjust;
const maxHeight = useDerivedValue(() => {
return Math.min(availableSpace.value, defaultMaxHeight);
}, [defaultMaxHeight]);
return useAllAvailableSpace ? availableSpace.value : Math.min(availableSpace.value, defaultMaxHeight);
}, [defaultMaxHeight, useAllAvailableSpace, availableSpace]);
const containerAnimatedStyle = useAnimatedStyle(() => {
return growDown ?
@ -131,7 +133,7 @@ const Autocomplete = ({
}, [growDown, position]);
const containerStyles = useMemo(() => {
const s: StyleProp<ViewStyle> = [style.base, containerAnimatedStyle];
const s: StyleProp<ViewStyle> = [style.base, {left: horizontalPadding, right: horizontalPadding}, containerAnimatedStyle];
if (hasElements) {
s.push(style.borders);
}
@ -142,7 +144,7 @@ const Autocomplete = ({
s.push(containerStyle);
}
return s;
}, [hasElements, style, containerStyle, containerAnimatedStyle]);
}, [style.base, style.borders, style.shadow, horizontalPadding, containerAnimatedStyle, hasElements, containerStyle]);
return (
<Animated.View

View file

@ -58,13 +58,13 @@ describe('EditCommand', () => {
expect(editCommandForm.props.channelId).toBe(props.channelId);
});
it('renders correctly without saved command', () => {
it('renders correctly without saved command, adding a slash automatically', () => {
const props = getBaseProps();
props.savedCommand = undefined;
const {getByTestId} = renderWithEverything(<EditCommand {...props}/>, {database});
const editCommandForm = getByTestId('edit-command-form');
expect(editCommandForm.props.command).toBe('');
expect(editCommandForm.props.command).toBe('/');
});
it('sets up navigation buttons correctly', () => {

View file

@ -46,7 +46,7 @@ const CreateOrEditChannel = ({
const theme = useTheme();
const [canSave, setCanSave] = useState(false);
const [command, setCommand] = useState<string>(savedCommand || '');
const [command, setCommand] = useState<string>(savedCommand || '/');
const rightButton = useMemo(() => {
const base = buildNavigationButton(

View file

@ -63,6 +63,7 @@ describe('EditCommandForm', () => {
expect(floatingTextInput).toHaveProp('showErrorIcon', false);
expect(floatingTextInput).toHaveProp('spellCheck', false);
expect(floatingTextInput).toHaveProp('disableFullscreenUI', true);
expect(floatingTextInput).toHaveProp('autoFocus', true);
const autocomplete = getByTestId('autocomplete');
expect(autocomplete).toBeTruthy();
@ -78,6 +79,8 @@ describe('EditCommandForm', () => {
emoji: false,
slash: true,
});
expect(autocomplete).toHaveProp('useAllAvailableSpace', true);
expect(autocomplete).toHaveProp('horizontalPadding', 20);
});
it('calls onCommandChange when text input changes', () => {

View file

@ -1,12 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useState, useCallback} from 'react';
import React, {useState, useCallback, useRef} from 'react';
import {useIntl} from 'react-intl';
import {
type LayoutChangeEvent,
View,
Platform,
StyleSheet,
} from 'react-native';
import {SafeAreaView, type Edges} from 'react-native-safe-area-context';
@ -15,13 +14,13 @@ import Autocomplete from '@components/autocomplete';
import FloatingTextInput from '@components/floating_text_input_label';
import {useTheme} from '@context/theme';
import {useAutocompleteDefaultAnimatedValues} from '@hooks/autocomplete';
import {useKeyboardOverlap} from '@hooks/device';
import {
getKeyboardAppearanceFromTheme,
} from '@utils/theme';
const BOTTOM_AUTOCOMPLETE_SEPARATION = Platform.select({ios: 10, default: 10});
const BOTTOM_AUTOCOMPLETE_SEPARATION = 4;
const LIST_PADDING = 32;
const AUTOCOMPLETE_ADJUST = 5;
const styles = StyleSheet.create({
container: {
@ -60,6 +59,9 @@ export default function EditCommandForm({
const [commandFieldHeight, setCommandFieldHeight] = useState(0);
const mainView = useRef<View>(null);
const keyboardOverlap = useKeyboardOverlap(mainView, wrapperHeight);
const labelCommand = formatMessage({id: 'playbooks.edit_command.label', defaultMessage: 'Command'});
const placeholderCommand = formatMessage({id: 'playbooks.edit_command.placeholder', defaultMessage: 'Type a command here'});
@ -70,14 +72,13 @@ export default function EditCommandForm({
setWrapperHeight(e.nativeEvent.layout.height);
}, []);
const spaceOnTop = LIST_PADDING - AUTOCOMPLETE_ADJUST;
const spaceOnBottom = (wrapperHeight) - (LIST_PADDING + commandFieldHeight + BOTTOM_AUTOCOMPLETE_SEPARATION);
const workingSpace = wrapperHeight - keyboardOverlap;
const spaceOnBottom = (workingSpace) - (LIST_PADDING + commandFieldHeight + (BOTTOM_AUTOCOMPLETE_SEPARATION * 2));
const bottomPosition = (LIST_PADDING + commandFieldHeight);
const topPosition = (wrapperHeight + AUTOCOMPLETE_ADJUST) - LIST_PADDING;
const autocompletePosition = spaceOnBottom > spaceOnTop ? bottomPosition : topPosition;
const autocompleteAvailableSpace = spaceOnBottom > spaceOnTop ? spaceOnBottom : spaceOnTop;
const growDown = spaceOnBottom > spaceOnTop;
const bottomPosition = (LIST_PADDING + commandFieldHeight + BOTTOM_AUTOCOMPLETE_SEPARATION);
const autocompletePosition = bottomPosition;
const autocompleteAvailableSpace = spaceOnBottom;
const growDown = true;
const [animatedAutocompletePosition, animatedAutocompleteAvailableSpace] = useAutocompleteDefaultAnimatedValues(autocompletePosition, autocompleteAvailableSpace);
@ -87,6 +88,7 @@ export default function EditCommandForm({
style={styles.container}
testID='playbooks.edit_command.form'
onLayout={onLayoutWrapper}
ref={mainView}
>
<View style={styles.mainView}>
<FloatingTextInput
@ -103,6 +105,7 @@ export default function EditCommandForm({
value={command}
theme={theme}
onLayout={onLayoutCommand}
autoFocus={true}
/>
</View>
<Autocomplete
@ -116,6 +119,8 @@ export default function EditCommandForm({
growDown={growDown}
channelId={channelId}
autocompleteProviders={autocompleteProviders}
useAllAvailableSpace={true}
horizontalPadding={20}
/>
</SafeAreaView>
);