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 <mattermod@users.noreply.github.com>
This commit is contained in:
Michael Kochell
2022-08-23 12:33:48 -04:00
committed by GitHub
parent 6201270029
commit c1936040c3
5 changed files with 72 additions and 20 deletions

View File

@@ -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<Props, State> {
@@ -70,7 +71,15 @@ export default class AutocompleteSelector extends PureComponent<Props, State> {
}
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<Props, State> {
};
}
handleClear = () => {
this.setState({selectedText: ''});
this.props.onClear?.();
};
handleSelect = (selected: Selection) => {
if (!selected) {
return;
@@ -230,14 +244,14 @@ export default class AutocompleteSelector extends PureComponent<Props, State> {
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<Props, State> {
>
{text}
</Text>
<CompassIcon
name={chevron}
color={changeOpacity(theme.centerChannelColor, 0.32)}
style={style.icon}
/>
{!disabled && onClear && selected && (
<TouchableWithFeedback
type={'opacity'}
onPress={this.handleClear}
disabled={disabled}
style={style.clearx}
hitSlop={clearXHitSlop}
>
<CompassIcon
name='close-circle'
color={changeOpacity(theme.centerChannelColor, 0.5)}
size={20}
/>
</TouchableWithFeedback>
)}
</View>
</TouchableWithFeedback>
{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,
};

View File

@@ -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 (
<AutocompleteSelector

View File

@@ -150,7 +150,7 @@ export type AppForm = {
depends_on?: string[];
};
export type AppFormValue = string | AppSelectOption | boolean | null;
export type AppFormValue = string | AppSelectOption | AppSelectOption[] | boolean | null;
export type AppFormValues = {[name: string]: AppFormValue};
export type AppSelectOption = {

View File

@@ -26,12 +26,12 @@ export type Props = {
theme: Theme;
value: AppFormValue;
onChange: (name: string, value: string | AppSelectOption | AppSelectOption[] | boolean) => void;
onChange: (name: string, value: AppFormValue) => void;
performLookup: (name: string, userInput: string) => Promise<AppSelectOption[]>;
}
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<Props, State> {
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<Props, State> {
dataSource={dataSource}
options={options}
optional={!field.is_required}
onClear={this.handleClear}
onSelected={field.multiselect ? this.handleMultioptionAutocompleteSelect : this.handleAutocompleteSelect}
getDynamicOptions={this.getDynamicOptions}
helpText={field.description}

View File

@@ -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}