mattermost-mobile/app/screens/edit_profile/components/select_field.tsx
Guillermo Vayá 972bd34da6
[MM-62700] User Attribute types (#8903)
* add basic types to user_profile

* fix storing multiselect in DB

* fix storing multi-select on db

* add tests

* add tests for select-multiselect

* imporove testing

* improve intl, add tests

* address comments and tests

* review comments

* more test refactoring

* remove styling tests

* remove styling tests

* small improvements

* nitpick

* improve messaging

* safer stringify

* fix tests
2025-06-12 10:59:13 +02:00

139 lines
4.1 KiB
TypeScript

// 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 {View} from 'react-native';
import AutocompleteSelector from '@components/autocomplete_selector';
import {Screens} from '@constants';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
import {logError} from '@utils/log';
import {makeStyleSheetFromTheme} from '@utils/theme';
import {getSelectedOptionIds} from '@utils/user';
import type {SelectFieldProps} from '@typings/screens/edit_profile';
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return {
container: {
marginVertical: 8,
paddingHorizontal: 20,
width: '100%',
backgroundColor: theme.centerChannelBg,
},
tabletContainer: {
paddingHorizontal: 42,
},
};
});
const SelectField = ({
fieldKey,
label,
value,
options,
isDisabled = false,
onValueChange,
onFocusNextField,
testID,
isOptional = false,
isMultiselect = false,
}: SelectFieldProps) => {
const theme = useTheme();
const intl = useIntl();
const isTablet = useIsTablet();
const styles = getStyleSheet(theme);
let formattedLabel = label;
if (isOptional) {
formattedLabel = intl.formatMessage(
{
id: 'select_field.optional',
defaultMessage: '{labelName} (optional)',
},
{labelName: label},
);
}
// Convert stored value to selected format for AutocompleteSelector
const selectedValue = useMemo(() => {
if (!value) {
return isMultiselect ? [] : undefined;
}
const selectedIds = getSelectedOptionIds(value, isMultiselect ? 'multiselect' : 'select');
if (isMultiselect) {
return selectedIds;
}
return selectedIds[0] || undefined;
}, [value, isMultiselect]);
const handleSelect = useCallback((newSelection?: SelectedDialogOption) => {
if (!newSelection) {
onValueChange(fieldKey, '');
return;
}
if (Array.isArray(newSelection)) {
// Multiselect: convert array of selections to JSON string of IDs
const selectedIds = newSelection.map((option) => option.value);
let stringifiedIds;
try {
stringifiedIds = JSON.stringify(selectedIds);
} catch (e) {
logError('Error serializing selected IDs', e);
stringifiedIds = '';
}
onValueChange(fieldKey, stringifiedIds);
} else {
// Single select: store the option ID
onValueChange(fieldKey, newSelection.value);
}
// Focus next field after selection
onFocusNextField(fieldKey);
}, [fieldKey, onValueChange, onFocusNextField]);
const containerStyle = useMemo(() => [
styles.container,
isTablet && styles.tabletContainer,
], [isTablet, styles]);
const placeholder = useMemo(() => {
if (isMultiselect) {
return intl.formatMessage({
id: 'mobile.action_menu.select_multiple',
defaultMessage: 'Select one or more options',
});
}
return intl.formatMessage({
id: 'mobile.action_menu.select',
defaultMessage: 'Select an option',
});
}, [intl, isMultiselect]);
return (
<View
style={containerStyle}
testID={testID}
>
<AutocompleteSelector
label={formattedLabel}
options={options}
selected={selectedValue}
onSelected={handleSelect}
disabled={isDisabled}
isMultiselect={isMultiselect}
testID={`${testID}.selector`}
location={Screens.EDIT_PROFILE}
placeholder={placeholder}
/>
</View>
);
};
export default SelectField;