From 62aba308f183dbfdb450b4a79e866eeef1638d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Espino=20Garc=C3=ADa?= Date: Fri, 19 Sep 2025 07:10:05 +0200 Subject: [PATCH] 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> --- .../autocomplete/autocomplete.test.tsx | 32 +++++++++++++++++++ app/components/autocomplete/autocomplete.tsx | 14 ++++---- .../edit_command/edit_command.test.tsx | 4 +-- .../screens/edit_command/edit_command.tsx | 2 +- .../edit_command/edit_command_form.test.tsx | 3 ++ .../edit_command/edit_command_form.tsx | 27 +++++++++------- 6 files changed, 62 insertions(+), 20 deletions(-) diff --git a/app/components/autocomplete/autocomplete.test.tsx b/app/components/autocomplete/autocomplete.test.tsx index 5c7b50769..a56c4a032 100644 --- a/app/components/autocomplete/autocomplete.test.tsx +++ b/app/components/autocomplete/autocomplete.test.tsx @@ -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(); + + // Default horizontal padding is 8 + expect(getByTestId('autocomplete')).toHaveStyle({left: 8, right: 8}); + + // Set horizontal padding to 20 + props.horizontalPadding = 20; + rerender(); + + 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; + const {getByTestId} = render(); + + // 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; + props.useAllAvailableSpace = true; + const {getByTestId} = render(); + + expect(getByTestId('autocomplete')).toHaveStyle({maxHeight: 1000}); + }); }); diff --git a/app/components/autocomplete/autocomplete.tsx b/app/components/autocomplete/autocomplete.tsx index 31939af21..a0c1f4c10 100644 --- a/app/components/autocomplete/autocomplete.tsx +++ b/app/components/autocomplete/autocomplete.tsx @@ -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; 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 = [style.base, containerAnimatedStyle]; + const s: StyleProp = [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 ( { 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(, {database}); const editCommandForm = getByTestId('edit-command-form'); - expect(editCommandForm.props.command).toBe(''); + expect(editCommandForm.props.command).toBe('/'); }); it('sets up navigation buttons correctly', () => { diff --git a/app/products/playbooks/screens/edit_command/edit_command.tsx b/app/products/playbooks/screens/edit_command/edit_command.tsx index 0cdb75e9c..72e02c513 100644 --- a/app/products/playbooks/screens/edit_command/edit_command.tsx +++ b/app/products/playbooks/screens/edit_command/edit_command.tsx @@ -46,7 +46,7 @@ const CreateOrEditChannel = ({ const theme = useTheme(); const [canSave, setCanSave] = useState(false); - const [command, setCommand] = useState(savedCommand || ''); + const [command, setCommand] = useState(savedCommand || '/'); const rightButton = useMemo(() => { const base = buildNavigationButton( diff --git a/app/products/playbooks/screens/edit_command/edit_command_form.test.tsx b/app/products/playbooks/screens/edit_command/edit_command_form.test.tsx index ffc90ebb3..cab071deb 100644 --- a/app/products/playbooks/screens/edit_command/edit_command_form.test.tsx +++ b/app/products/playbooks/screens/edit_command/edit_command_form.test.tsx @@ -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', () => { diff --git a/app/products/playbooks/screens/edit_command/edit_command_form.tsx b/app/products/playbooks/screens/edit_command/edit_command_form.tsx index d3f05f191..050402337 100644 --- a/app/products/playbooks/screens/edit_command/edit_command_form.tsx +++ b/app/products/playbooks/screens/edit_command/edit_command_form.tsx @@ -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(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} > );