From c1936040c384652beaaa966b059d0b61dcbb18a5 Mon Sep 17 00:00:00 2001 From: Michael Kochell <6913320+mickmister@users.noreply.github.com> Date: Tue, 23 Aug 2022 12:33:48 -0400 Subject: [PATCH] MM-33993 Allow apps form and interactive dialogs to clear select fields (#5398) * allow apps form and int dialogs to clear select fields * don't show x if the field is disabled * change down chevron to right chevron * increase padding around close x to increase touch area * positioning * remove chevron and divider * use hitSlop instead of padding. fix position of x * lint * types * update type * lint * make onClear optional * optional chaining * set multiselect value to empty array on clear * include null in type * useCallback improvement * fix clearing multi-selected items state issue * fix clearing multi-selected items state issue * lint Co-authored-by: Mattermod --- .../autocomplete_selector.tsx | 62 ++++++++++++++----- .../action_menu/action_menu.tsx | 6 +- app/mm-redux/types/apps.ts | 2 +- app/screens/apps_form/apps_form_field.tsx | 14 ++++- .../interactive_dialog/dialog_element.js | 8 +++ 5 files changed, 72 insertions(+), 20 deletions(-) diff --git a/app/components/autocomplete_selector/autocomplete_selector.tsx b/app/components/autocomplete_selector/autocomplete_selector.tsx index d1c7f2cf25..08283494ff 100644 --- a/app/components/autocomplete_selector/autocomplete_selector.tsx +++ b/app/components/autocomplete_selector/autocomplete_selector.tsx @@ -32,12 +32,13 @@ type Props = { placeholder?: string; dataSource?: string; options?: DialogOption[]; - selected?: DialogOption | DialogOption[]; + selected?: DialogOption | DialogOption[] | null; optional?: boolean; showRequiredAsterisk?: boolean; teammateNameDisplay?: string; theme: Theme; onSelected?: ((item: DialogOption) => void) | ((item: DialogOption[]) => void); + onClear?: () => void; helpText?: string; errorText?: string; roundedBorders?: boolean; @@ -47,7 +48,7 @@ type Props = { type State = { selectedText: string; - selected?: DialogOption | DialogOption[]; + selected?: DialogOption | DialogOption[] | null; } export default class AutocompleteSelector extends PureComponent { @@ -70,7 +71,15 @@ export default class AutocompleteSelector extends PureComponent { } static getDerivedStateFromProps(props: Props, state: State) { - if (!props.selected || props.selected === state.selected) { + if (props.selected === state.selected) { + return null; + } + + if (!props.selected) { + if (state.selected) { + return {selected: props.selected}; + } + return null; } @@ -99,6 +108,11 @@ export default class AutocompleteSelector extends PureComponent { }; } + handleClear = () => { + this.setState({selectedText: ''}); + this.props.onClear?.(); + }; + handleSelect = (selected: Selection) => { if (!selected) { return; @@ -230,14 +244,14 @@ export default class AutocompleteSelector extends PureComponent { showRequiredAsterisk, roundedBorders, disabled, + selected, + onClear, } = this.props; const {selectedText} = this.state; const style = getStyleSheet(theme); const textStyles = getMarkdownTextStyles(theme); const blockStyles = getMarkdownBlockStyles(theme); - const chevron = Platform.select({ios: 'chevron-right', default: 'chevron-down'}); - let text = placeholder || intl.formatMessage({id: 'mobile.action_menu.select', defaultMessage: 'Select an option'}); let selectedStyle = style.dropdownPlaceholder; @@ -326,11 +340,21 @@ export default class AutocompleteSelector extends PureComponent { > {text} - + {!disabled && onClear && selected && ( + + + + )} {helpTextContent} @@ -366,18 +390,21 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { dropdownPlaceholder: { top: 3, marginLeft: 5, + paddingRight: 55, color: changeOpacity(theme.centerChannelColor, 0.5), }, dropdownSelected: { top: 3, marginLeft: 5, + paddingRight: 55, color: theme.centerChannelColor, }, - icon: { + clearx: { position: 'absolute', - top: 6, - right: 12, - fontSize: 28, + top: 1, + right: 5, + padding: 8, + marginRight: 7, }, labelContainer: { flexDirection: 'row', @@ -419,3 +446,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { }, }; }); + +const clearXHitSlop = { + left: 30, + right: 20, + top: 20, + bottom: 20, +}; diff --git a/app/components/post_list/post/body/content/message_attachments/action_menu/action_menu.tsx b/app/components/post_list/post/body/content/message_attachments/action_menu/action_menu.tsx index e9f06b39b8..b8db549603 100644 --- a/app/components/post_list/post/body/content/message_attachments/action_menu/action_menu.tsx +++ b/app/components/post_list/post/body/content/message_attachments/action_menu/action_menu.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React from 'react'; +import React, {useCallback} from 'react'; import AutocompleteSelector from '@components/autocomplete_selector'; import {PostActionOption} from '@mm-redux/types/integration_actions'; @@ -28,13 +28,13 @@ const ActionMenu = ({dataSource, defaultOption, disabled, id, name, options, pos isSelected = selected; } - const handleSelect = (selectedItem?: PostActionOption) => { + const handleSelect = useCallback((selectedItem?: PostActionOption) => { if (!selectedItem) { return; } selectAttachmentMenuAction(postId, id, selectedItem.text, selectedItem.value); - }; + }, [postId, id]); return ( void; + onChange: (name: string, value: AppFormValue) => void; performLookup: (name: string, userInput: string) => Promise; } type State = { - selected?: DialogOption | DialogOption[]; + selected?: DialogOption | DialogOption[] | null; } const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { @@ -100,6 +100,15 @@ export default class AppsFormField extends React.PureComponent { this.props.onChange(field.name, selectedOption); }; + handleClear = () => { + const {field, onChange} = this.props; + + const selected = null; + + this.setState({selected}); + onChange(field.name, selected); + }; + handleMultioptionAutocompleteSelect = (selected: DialogOption[]) => { if (!selected) { return; @@ -237,6 +246,7 @@ export default class AppsFormField extends React.PureComponent { dataSource={dataSource} options={options} optional={!field.is_required} + onClear={this.handleClear} onSelected={field.multiselect ? this.handleMultioptionAutocompleteSelect : this.handleAutocompleteSelect} getDynamicOptions={this.getDynamicOptions} helpText={field.description} diff --git a/app/screens/interactive_dialog/dialog_element.js b/app/screens/interactive_dialog/dialog_element.js index 17bb9761c6..fa7c3f3c7a 100644 --- a/app/screens/interactive_dialog/dialog_element.js +++ b/app/screens/interactive_dialog/dialog_element.js @@ -54,6 +54,13 @@ export default class DialogElement extends PureComponent { onChange(name, newValue); }; + handleClear = () => { + const {name, onChange} = this.props; + + this.setState({selected: null}); + onChange(name, null); + }; + handleAutocompleteSelect = (selected) => { if (!selected) { return; @@ -134,6 +141,7 @@ export default class DialogElement extends PureComponent { options={options} optional={optional} onSelected={this.handleAutocompleteSelect} + onClear={this.handleClear} helpText={helpText} errorText={errorText} placeholder={placeholder}